昨天,大家可能都看了代码了,不知道昨天有没有在汇编代码的基础上,实现注入计算器.
如果没有,今天则会讲解,不过建议把昨天代码熟悉一遍(课程是紧跟着来的,请不要拉下任何一天,因为今天的知识,
可能就和昨天的知识挂钩,昨天的知识,和前天的挂钩.....,当然你如你懂汇编,不是新手,那么则可以直接往下看)
一丶远程线程注入,和汇编远程注入的区别
昨天的代码,大家可能看了(没看也没有关系,就是远程线程注入的代码,开发角度,和汇编代码注入,底层角度的两份代码)
这里说下,他们的区别
首先我们知道,任何注入方式都有它们使用的特定场合
1.远程线程注入
这个只针对软件的保护防范低使用的,病毒很少用这个,为什么
我们知道,远程线程注入,它会创建远程线程,进而加载我们的DLL,而对有保护的程序来说,它可能不能防范你使用CreateRemoteThread函数,但是可以针对你的dll,比如遍历dll个数
发现多了一个,程序就退出.等等.
2.汇编的远程注入
汇编的远程注入,这个就有点狠了,为什么,因为你写的不是一个dll,而是一断汇编代码,写到对面的内存中,让他去执行,这样除非对面软件,试试的检测内存状态,否则不容易检测出自己程序的异常
当然汇编的远程注入,还是会开辟内存,但是我们知道,注入方法很多种,我们可以发挥想象,只重剑意不重剑招,我们可以这样想,你不是要申请内存吗,我们可以不申请内存,对面程序肯定会存再对齐的问题
,比如为了保证对齐,对面程序肯定会用NOP指令填充,那么我们则可以利用这块内存,这样软件除非也检测NOP,和对齐,否则你注入进去对面也发现不了,再比如对面软件很厉害,检测很到位,厉害到
对齐也检测了,那么我们可以把对面栈的内存抬高,把我们的程序代码写进去,对面总不注意试试的检测栈吧,执行完我们的代码就出栈,对面不可能检测栈的进出吧.所以重在想像(废话有点多,可以省略不看直接看下边 :) )
3.汇编远程注入代码分析(0D分析)
昨天我们因为时间关系,没有具体分析昨天的代码,今天我们用OD(OlleyDbg)一步一步分析,
这个分析也能让我们快速的掌握调试技巧
①分析FindWindow,看下汇编代码执行了什么
首先贴出我们昨天的代码
invoke FindWindow,NULL,offset g_szWindowName ,第二个参数是计算器的字符串 mov @hWnd, eax .if eax == NULL invoke ShowLastError ret .endif 上面代码的逻辑: 寻找计算器,返回计算器的窗口句柄,如果成功,(返回值默认放eax中) 如果成功,继续往下执行,如果失败,调用ShowLastError显示错误信息
OD分析
这个是我们大体的FindWindow界面
我们查找下窗口,看下是否找到,找到则窗口句柄放在eax当中
我们可以看出,已经找到窗口了,并且窗口句柄已经在eax当中了,所以 eax == NULL 不成立,则跳转到下一条指令位置执行,而下一条指令位置,则开始调用GetWindowThreadProcessID了
②GetWindowThreadProcessID,获得进程的ID
首先还是对应着伪指令的汇编代码查看
invoke GetWindowThreadProcessId, @hWnd, addr @dwPID
代码很简单,我们知道,调用函数传参的时候,代码都是从右往左压栈的,所以第一个会 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的值了
汇编代码:
invoke OpenProcess,PROCESS_ALL_ACCESS, FALSE, @dwPID mov @hProcess, eax .if eax == NULL invoke ShowLastError ret .endif
首先,把PID压栈,然后把FALSE(汇编中是0)压栈,然后把权限压栈(权限就是常量)
最后打开进程,如果成功获得进程句柄,则返回值放在eax中,把eax给局部变量
然后判断局部变量是否==NULL,不想等继续走,相等就是打开失败,执行错误代码提示(ShowlastError)
OD分析
这个地方我也不用细讲了
1.首先,我们把进程的PID,也就是局部变量第三个(ebp - c里面的值)压栈
2.其次从右往左压入第二个参数,也就是FALSE
3.然后压入权限
4.调用OpenProcess
5.成功则eax保存的是进程的实例句柄
6.判断eax是否等于NULL,相等(获取失败)继续往下执行,调用Call injectas.0040118b
④VirtualAllocEx,远程申请内存
汇编代码
invoke VirtualAllocEx,@hProcess, NULL, 1000h, MEM_COMMIT, PAGE_EXECUTE_READWRITE mov @lpBuff, eax .if eax == NULL invoke ShowLastError ret .endif
这个和上面一样,都是从右向左入栈,如果成功,返回在远程进程申请的内存的首地址,放在eax当中
失败则下面判断.
OD分析(注意,这种的上面都已经分析了很多遍了,API调用的传参,出栈,以及寄存器给局部变量赋值)
所以下方的API我会提供图片去看,但是不具体分析了,都是一样的,如果又兴趣的可以,自己练练手,手动分析,看下代码流程怎么执行.
已经成功了,肯定会执行,现在介绍OD的第二种用法
当一个应用程序被打开的时候,我们可以选择附加的方式,将这个程序挂起
现在我们把计算机附加,看下这个地方是否申请了内存
重新打开OD,现在是两个OD
,选择我们的计算器程序
搜索我们用Vir...申请成功的内存首地址,看看是否申请成功
申请成功,然后我们继续下一条指令执行,写内存数据到这里面
⑤,利用WriteProcessMemory写内存数据到这里面来
注意,我们写的使我们的INJECT_CODE的代码的二进制,所以程序在调用远程线程的时候,
会把我们的而二进制当做代码运行
看下汇编代码:
invoke WriteProcessMemory,@hProcess, @lpBuff, INJECT_CODE, start - INJECT_CODE, NULL
传参,什么的不说了,这里需要注意一下 要写入的内容是我们刚才申请的内存首地址
现在给的是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的时候的问题
我们在汇编代码中随便看一个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) = 实际函数地址
这个公式请熟练记住
看字不明白,看图:
先看公式,再看箭头指向
那么基于这个公式我们就开始写我们的汇编代码了
现在的函数地址重定义问题已经解决了,但是注意,只是函数地址的重定位
下面写完汇编代码,就明白,另一个函数调用地址无关性的重定位问题了,
也就是我们要解决的第二个问题,说的有点多,看代码,其实代码很简单
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 ;写入另外实际函数地址并且调用
对于最后两个,求出标号所在的位置,和写入实际函数地址并且调用
这个则是在我们的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 和我们下方写的代码的偏移
算这一段距离,但是这个你在远程进程中也不好算,所以就有了新的方法
看汇编代码
push ebp push ebx call $+5 ;CALL 下行指令 TEXT: pop ebp ;地址重定位 sub ebp, offset TEXT
首先保存栈环境,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