32位汇编第四讲,干货分享,汇编注入的实现,以及快速定位调用API的数量(OD查看)
2021-05-14 08:28
标签:用户 show roc 内容 地址 能力 资料 commit 病毒 昨天,大家可能都看了代码了,不知道昨天有没有在汇编代码的基础上,实现注入计算器. 如果没有,今天则会讲解,不过建议把昨天代码熟悉一遍(课程是紧跟着来的,请不要拉下任何一天,因为今天的知识, 可能就和昨天的知识挂钩,昨天的知识,和前天的挂钩.....,当然你如你懂汇编,不是新手,那么则可以直接往下看) 昨天的代码,大家可能看了(没看也没有关系,就是远程线程注入的代码,开发角度,和汇编代码注入,底层角度的两份代码) 这里说下,他们的区别 首先我们知道,任何注入方式都有它们使用的特定场合 这个只针对软件的保护防范低使用的,病毒很少用这个,为什么 我们知道,远程线程注入,它会创建远程线程,进而加载我们的DLL,而对有保护的程序来说,它可能不能防范你使用CreateRemoteThread函数,但是可以针对你的dll,比如遍历dll个数 发现多了一个,程序就退出.等等. 汇编的远程注入,这个就有点狠了,为什么,因为你写的不是一个dll,而是一断汇编代码,写到对面的内存中,让他去执行,这样除非对面软件,试试的检测内存状态,否则不容易检测出自己程序的异常 当然汇编的远程注入,还是会开辟内存,但是我们知道,注入方法很多种,我们可以发挥想象,只重剑意不重剑招,我们可以这样想,你不是要申请内存吗,我们可以不申请内存,对面程序肯定会存再对齐的问题 ,比如为了保证对齐,对面程序肯定会用NOP指令填充,那么我们则可以利用这块内存,这样软件除非也检测NOP,和对齐,否则你注入进去对面也发现不了,再比如对面软件很厉害,检测很到位,厉害到 对齐也检测了,那么我们可以把对面栈的内存抬高,把我们的程序代码写进去,对面总不注意试试的检测栈吧,执行完我们的代码就出栈,对面不可能检测栈的进出吧.所以重在想像(废话有点多,可以省略不看直接看下边 :) ) 昨天我们因为时间关系,没有具体分析昨天的代码,今天我们用OD(OlleyDbg)一步一步分析, 这个分析也能让我们快速的掌握调试技巧 首先贴出我们昨天的代码 OD分析 这个是我们大体的FindWindow界面 我们查找下窗口,看下是否找到,找到则窗口句柄放在eax当中 我们可以看出,已经找到窗口了,并且窗口句柄已经在eax当中了,所以 eax == NULL 不成立,则跳转到下一条指令位置执行,而下一条指令位置,则开始调用GetWindowThreadProcessID了 首先还是对应着伪指令的汇编代码查看 代码很简单,我们知道,调用函数传参的时候,代码都是从右往左压栈的,所以第一个会 push dwPid,第二个会push hWnd OD分析 因为走一步截次图太麻烦,而且影响大家观看,索性直接标号,把每一步写出来,这样大家自己调试,不懂的时候来看我的每一步代表什么意思 首先我把每一步执行的代码都用标号圈起来了 1. lea eax,[local,3] 意思是我要拿第三个局部变量,也就是栈的第三个局部变量,而我们以前说过,局部变量都是 ebp -xxx来获取,而现在是32位的汇编了,所以每个寄存器是4个字节,所以第三个局部变量则是-12 ,而对应局部变量 则是 -c 这句代码真实的代码则被翻译成了 lea eax,dword ptr ss:[ebp - oxc]位置,我们就可以去栈中看下ebp -c的位置是什么了,注意这里因为我走到下边 所以已经获取到了进程PID的值,所以是810,默认的时候是0,那么是什么意思那,就是取得 ebp -c 的地址 2.取得ebp的地址(假设地址是18ff44)那么吧地址给eax,再把eax入栈, 3.把我们的第一个局部变量,也就是ebp - 4的值,(40D40,因为这里是中括号,所以对栈取内容得出的,而上面的是没有取内容,因为我们用的是lea指令) 4.调用GetWindowThreadProcessId,这个时候,因为我们把第二部的eax入栈(eax是ebx-c的栈地址),所以获得的PID 值则会给对应栈地址的内容(什么意思: 就是你提供局部变量的地址,也就是我们先前ebp -c的地址,操作系统获得PID的值,则会根据你给的地址,把对应地址里面的内容修改了,所以相当于是 mov dword ptr[18ff44],810) 至此,我们可以总结下,这个GetWindowThreadProcessId,执行的过程 1.先获得局部变量的地址(ebp - c的地址,注意,不是值) 2.压栈 3.获得栈中第一个参数的值,(注意是栈地址里面的值,而不是栈地址)也就是上次获得的窗口的句柄 4.调用GetWindowThreadProcessId,把对应栈地址里面的值修改为我们的PID值(810),所以我们已经得到PID的值了 汇编代码: OD分析 这个地方我也不用细讲了 1.首先,我们把进程的PID,也就是局部变量第三个(ebp - c里面的值)压栈 2.其次从右往左压入第二个参数,也就是FALSE 3.然后压入权限 4.调用OpenProcess 5.成功则eax保存的是进程的实例句柄 6.判断eax是否等于NULL,相等(获取失败)继续往下执行,调用Call injectas.0040118b 汇编代码 这个和上面一样,都是从右向左入栈,如果成功,返回在远程进程申请的内存的首地址,放在eax当中 失败则下面判断. OD分析(注意,这种的上面都已经分析了很多遍了,API调用的传参,出栈,以及寄存器给局部变量赋值) 所以下方的API我会提供图片去看,但是不具体分析了,都是一样的,如果又兴趣的可以,自己练练手,手动分析,看下代码流程怎么执行. 已经成功了,肯定会执行,现在介绍OD的第二种用法 当一个应用程序被打开的时候,我们可以选择附加的方式,将这个程序挂起 现在我们把计算机附加,看下这个地方是否申请了内存 重新打开OD,现在是两个OD ,选择我们的计算器程序 搜索我们用Vir...申请成功的内存首地址,看看是否申请成功 申请成功,然后我们继续下一条指令执行,写内存数据到这里面 注意,我们写的使我们的INJECT_CODE的代码的二进制,所以程序在调用远程线程的时候, 会把我们的而二进制当做代码运行 看下汇编代码: 传参,什么的不说了,这里需要注意一下 要写入的内容是我们刚才申请的内存首地址 现在给的是lpBuff,也就是我们往哪里写,(往计算机器我们申请的哪块内存写,所以lpbuff就是计算器这块内存的首地址了) 写入的数据是 INJECT_CODE的代码的二进制 写入的大小是START - INJECT_CODE数据的大小 INJECT_CODE是在START标号的上面,我们看下 汇编代码,和OD分析 OD分析 可以看到,我的标号 1.表示我们要写入对面内存的起始地址(也就是我们用vir申请的) 2.我们要写入的缓冲区,也就是我要写入inject为开始,开始把这块内存写入 3.写入的大小就是我们计算出来的START- INJECT_CODE 4.实际写入的字节我们不关心,关于START - INJECT_CODE我们看下代码开始出就明白了 相当于00401017 - 00401000 = 17个字节,所以要写入17个字节 看下计算机程序中,有没有写入我们的二进制代码 正好17个字节,而且代码也写进去了 最后我们调用CreateReomteThread开始把INJECT_CODE当做代码去执行了,这里传参和上面一样 不在分析了 至此,分析到这里就完了,下面写代码就不分析了,开始真正的写汇编代码注入的程序了,因为汇编代码和上面大同小异都是调用API,而后API传参.保存返回值给局部变量,出栈等等都是一样的,所以下方开始真正写.如果感性区,想提升自己的调试能力,以及对OD的熟练程度,可以自己去分析一下 首先,如果做过昨天作业的同学应该知道,会遇到对面代码和我方代码的的位置不一样 比如 我们INJECT_CODE的位置,和对面INJECT_CODE代码的位置 还有就是DLL加在的位置不同,也会影响API的调用 比如我们代码在INJECT_CODE里面调用一个MessageBox,他可以弹窗 但是要注意,在对面的那边调用这个就会出错,为什么 所以我们要注意几个问题 1.Call的时候问题 2.地址重定位问题 首先是1问题 我们在汇编代码中随便看一个Call 然后按下空格键,看下汇编是什么 我们分别在自己程序的INJECT_CODE 和对面程序的INJECT_CODE看下执行MessageBox会出现什么问题 这个是汇编代码 看下OD 我们可以看到都是调用0x401204,但是结果是正确的吗 我们用在反汇编窗口 CTRL + G 跳转到00401204 我们发现 第一个程序,也就是我们的注入程序,它调用MessageBox,是有的 而计算器的程序调用的时候,是没有的,找不到这块内存,所以就出错了 为什么会出现这个问题,这个就是著名的重定位问题,以前我们DLL注入的时候,是系统帮我们重定位了 而现在我们要自己去重定位这个问题 首先我们知道,任何程序运行的时候,都会加在ntdll, 而kernel32.dll也会加载,user32.dll也会加载 而kernel32.dll并不是必须加载的,但是%99.999的程序都会加载这个dll ((*^▽^*)) user32.dll是和用户相关的,也会有%99的加载 那么就产生一个问题,看下图 我们注入程序调用MessageBox会从user32.dll中找到MessAgebox的地址,并且调用 而B程序,显然DLL的首地址是2000的位置,首先不说我们能不能调用它 就我们刚才看Call的时候,他是直接call了一个常量 00401204,而显然,这块内存是不属于B进程的所以出错了 他是属于A进程的, 所以我们要重定位API地址 怎么定位 1.获得当前注入程序的User32.dll的加载的实例句柄 2.并且创建进程快照遍历计算机器进程模块User32.dll的实例句柄 然后看下图 首先注入程序得出1000h ,远程的程序得到的user32.dll的模块地址是2000h 3.获得MessageBox距离模块的偏移,注意,这个偏移获取出来,是两方都一样的,因为函数的位置都是一样的,只有 模块地址加载的位置不一样 看图 算出来的都是100, 所以我们就有了一个公式 函数首地址 - 模块首地址 = 得出了函数距离模块的实际偏移 然后远程模块 + 函数距离模块的实际偏移,得出远程进程的Messagebox的实际偏移 假设我们本地进程是1000h Messagebox的距离是1100 那么 1100 (函数首地址) - 模块首地址(1000) = 实际偏移(100) 然后 远程模块地址(2000) + 实际偏移(100) = 实际函数地址 这个公式请熟练记住 看字不明白,看图: 先看公式,再看箭头指向 那么基于这个公式我们就开始写我们的汇编代码了 现在的函数地址重定义问题已经解决了,但是注意,只是函数地址的重定位 下面写完汇编代码,就明白,另一个函数调用地址无关性的重定位问题了, 也就是我们要解决的第二个问题,说的有点多,看代码,其实代码很简单 对于最后两个,求出标号所在的位置,和写入实际函数地址并且调用 这个则是在我们的INJECT_CODE里面,新申请了一个标号位置, 然后里面的内存写入的使我们的实际地址 但是现在我们发现出现了新的问题 虽然我们已经写给了MSG_BOX,但是还是不能正常运行 为什么我们写进去的代码确实是MSG函数的地址 但是我们要知道,我们现在并不知道代码执行的位置在哪里 比如INJECT_CODE 中我们要CALL这个MSG_BOX的地址 你会发现,CALL的时候MSG_BOX还是一个全局常量,也就是说,你API地址的重定位问题已经解决了,现在的 代码重定位还没有解决 看下图理解: 现在我们也计算出来了API的地址了,但是地址还没有计算出来,这个时候大家会问,我们不是遍历了dll模块的地址了吗,把它拿过来用不行吗,可以,但是问题不在这,你拿过来也是也内存,但是只要你在INJECT_CODE里面call的时候 都不是call的它,而是你在本地进程call的,给远程内存写过去了,远程也call,call的也是一个地址,而这个地址压根不存在,那么就会出错. 比如: 我们要计算的是代码和我们的代码call的位置的偏移 也就是 inject_code 和我们下方写的代码的偏移 算这一段距离,但是这个你在远程进程中也不好算,所以就有了新的方法 看汇编代码 首先保存栈环境,ebp,我们下方会用到ebx,也保存, call $+5是什么意思, 一起就是call指令占五个字节,在call的时候会把下一条指令入栈,也就是TEXT指令位置入栈 而下方紧接着pop ebp,这个比较重要了,主要为了什么,我们主要为了拿到IP的位置 试想一下,CALL 一次会把下面的地址入栈,然后出栈就得到了当前IP执行代码的地址了 对不对 紧接着我们又写了 sub ebp,offset TEXT,这个是为了什么,我们想一下,在我们本进程,offset TEXT会被翻译为一个常量 当我们ebp减去TEXT位置,就得到了ebp和代码位置处的偏移了,看图 想想一下,另外一个进程减掉我们的本地的偏移,得到一个偏移,是不是相当于另外一个进程也得到自己代码的位置了,然后我方用ebp + 函数的偏移位置 得出函数地址 相当于对面的程序 减去我们的地址 加上 函数位置的偏移 也是一样的调用函数地址 不明白看图,这里比较绕,但是很重要: 2000 - 1000 = 1000 这个1000是地址重定位 也就是代码的位置在这里,而后加上函数的偏移= 实际执行代码的位置 也就是远程线程也是这样的 已经深夜了,基于时间关系,大家先把概念弄清楚,明天白天重新编辑更新 课堂代码连接: 链接:http://pan.baidu.com/s/1bprSUcf 密码:rsag 32位汇编第四讲,干货分享,汇编注入的实现,以及快速定位调用API的数量(OD查看) 标签:用户 show roc 内容 地址 能力 资料 commit 病毒 原文地址:http://www.cnblogs.com/iBinary/p/7524289.html32位汇编第四讲,干货分享,汇编注入的实现,以及快速定位调用API的数量(OD查看)
一丶远程线程注入,和汇编远程注入的区别
1.远程线程注入
2.汇编的远程注入
3.汇编远程注入代码分析(0D分析)
①分析FindWindow,看下汇编代码执行了什么
invoke FindWindow,NULL,offset g_szWindowName ,第二个参数是计算器的字符串
mov @hWnd, eax
.if eax == NULL
invoke ShowLastError
ret
.endif
上面代码的逻辑:
寻找计算器,返回计算器的窗口句柄,如果成功,(返回值默认放eax中)
如果成功,继续往下执行,如果失败,调用ShowLastError显示错误信息
②GetWindowThreadProcessID,获得进程的ID
invoke GetWindowThreadProcessId, @hWnd, addr @dwPID
invoke OpenProcess,PROCESS_ALL_ACCESS, FALSE, @dwPID
mov @hProcess, eax
.if eax == NULL
invoke ShowLastError
ret
.endif
首先,把PID压栈,然后把FALSE(汇编中是0)压栈,然后把权限压栈(权限就是常量)
最后打开进程,如果成功获得进程句柄,则返回值放在eax中,把eax给局部变量
然后判断局部变量是否==NULL,不想等继续走,相等就是打开失败,执行错误代码提示(ShowlastError)④VirtualAllocEx,远程申请内存
invoke VirtualAllocEx,@hProcess, NULL, 1000h, MEM_COMMIT, PAGE_EXECUTE_READWRITE
mov @lpBuff, eax
.if eax == NULL
invoke ShowLastError
ret
.endif
⑤,利用WriteProcessMemory写内存数据到这里面来
invoke WriteProcessMemory,@hProcess,
@lpBuff,
INJECT_CODE,
start - INJECT_CODE,
NULL
二丶汇编注入代码的编写,以及应该注意的各种问题
①Call的时候的问题
LOCAL @hLocalUser32Module:MODULE ;存放本地User32.dll的模块地址
LOCAL @hRemoteUser32Module:MODULE ;存放远程user32.dll的模块地址
;1.调用GetModule加载user32.dll获得user32.dll的模块地址
invoke GetModule,offset g_szUser32 ;g_szUser32看做字符串user32.dll,就是dll需要这个字符串,去寻找user32.dll,如果想看完整工程
请下载每天资料查看
mov @hLocalUser32Module,eax ;返回值存放user32.dll模块地址,给局部变量 @hRemoteUser32Module,eax; 按理说这里应该遍历被注入进程的模块
;获得user32.dll的地址,但是这里我的都是同系统,所以dll位置是一样的,如果你把这个注入程序给另外一个系统就要自己遍历了,遍历代码就不写了,和调用API
;一样,如果不会写,可以下方评论.
mov
;2.获得MessageBox函数的地址
invoke GetProcess,@hLocalUser32Module,offset g_szMsgbox
sup eax,@hLocalUser32Module ;函数地址 - 模块地址 = 实际偏移
mov ebx,hRemoteUser32Module ; 把另外进程的句柄给ebx
add ebx,eax ; 远程模块地址 + 实际偏移 = 远程函数实际偏移位置
这里给ebx是为了中转一下计算
lea eax,MSG_BOX ;求出inject标号所在的位置
mov[eax],ebx ;写入另外实际函数地址并且调用
push ebp
push ebx
call $+5 ;CALL 下行指令
TEXT:
pop ebp ;地址重定位
sub ebp, offset TEXT
下一篇:Winform学习知识汇总
文章标题:32位汇编第四讲,干货分享,汇编注入的实现,以及快速定位调用API的数量(OD查看)
文章链接:http://soscw.com/index.php/essay/85530.html