最近在学PE结构,遇到了个比较有意思的实验,学过PE结构的都知道因为文件对齐FileAlignment和区块对齐SectionAlignment的缘故,在磁盘中的PE文件的块与块表 块与块之间有许多的空白间隙,这些间隙以0为填充,在文件执行中这些空白是没有意义的,所以我们就可以利用这段空白插入我们想执行的一些代码。
演示程序 经典扫雷
演示dll user32.dll.MessageBoxW
演示所用工具 X64dbg,PEGAME,010Editor
演示目标 实现修改程序使其弹出一个MessageBox消息对话框
查看硬编码
这里用user32.dll中的MessageBoxW函数为例,MessageBoxA也可以,无非一个是宽字节版一个是单字节版,可以在VC中调用此接口然后调试查看反汇编代码。
获取硬编码如下
PUSH 6A 00
CALL E8 00 00 00 00
JMP E9 00 00 00 00
PUSH 指令通常用于在函数调时传递参数,会在函数调用时将参数压如栈中
CALL 通常用来调用函数 后面的00 00 00 00为跳转步长
JMP 无条件跳转指令 使用该指令无条件跳转到我们想要的指令处
查看所引用的DLL在程序中的VA地址
注意该程序必须导入了user32.dll这个动态链接库,否者将不会又MessageBox这个函数接口,也就无法找到该函数地址
将演示程序加载进动态调试工具X64dbg,Ctrl+G定位到该程序中MessageBox的函数,
获取该VA地址为76AB1E50
插入硬编码
将演示程序加载入十六进制编辑工具,选择一个空白区段,我这里选的是第一个区块和第二个区块之间的空白区段
我们若想插入MessageBox函数必须用PUSH指令先传递所需参数,CALL指令调用,JMP指令再回到原本程序正常执行处,即AddrressOfEntryPoint处。
查阅WIN32API 可知MessageBox的四个参数都可为0
CALL 指令调用,因此我们需要计算下CALL指令的步长,有两种方法
步长=要跳转的目的地址-当前指令地址-指令长度
步长=要跳转的目的地址-当前指令的下一条指令地址
我们采用第一个公式
在上面我们已经得到了MessageBoxW的地址即要跳转的目的地址为76AB1E50
当前指令的地址即CALL指令E8的地址为40A8
但要注意的是76AB1E50为VA即虚拟地址,40A8为文件偏移地址Fileoffset,因此需要将40A8转化为虚拟地址。
这里可以手算,因为插入硬编码的地址在区块中,所以计算公式为
VA=Fileoffset-ImageBase-(所在区块.VirtualAddress-所在区块.PointerToRawData)
也可以借助带有地址转换工具的PE结构查看器,如LordPE,我这里用我自己写的PE结构查看工具,感兴趣的话可以移步至【逆向工程】QT编写PE结构分析工具
查看到其VA地址为1004AA8
E8 00 00 00 00指令长度为五字节
则步长=76AB1E50-1004AA8-5=75AA D3A3
因为PE文件是小端存储所以 应该插入A3 D3 AA 75
我们调用完MessageBoxW后还需要让程序正常执行,因此还要JMP指令跳转到程序入口处,通过PE查询工具可以看到程序入口,这里还是使用我自己编写的PE结构查询工具查看。
则JMP指令步长=1003E21-1004AAD-5=FFFF F36F
同样的小端存储格式 E9 6D F3 FF FF
修改程序入口AddressOfEntryPoint
操作完如上步骤后,程序并不会执行我们的代码,因此还需要修改程序的入口地址为我们插入代码的地址,这里直接用十六进制编辑工具修改,这里填入的数字应该是RVA地址
插入的代码只能在本机上运行,还算不上是ShellCode。
白菜信安网