x64dbg调试upx壳
upx壳特征
节区
upx壳属于是压缩壳,经过upx加壳后,程序会剩下三个节区
其中,UPX0查看Raw Size会发现是0,也就是没有任何数据的。UPX1则存储了upx解压壳的相关指令。
但是Virtual Size却是有值的(0x78000),也就是说在进行拉伸时,UPX0时会填充内容的。
查看可选PE头的AddressOfEntryPoint也是指向UPX1的。
关键字
查看十六进制会有UPX关键字,一些魔改UPX会把这个关键字做修改,导致upx无法脱壳。
调试upx
保存原始堆栈
在最开始,会通过pushad把eax~edi寄存器的值保存到栈中。
填充UPX0节区
查看PE结构时,UPX0是空的(其实会在最开始程序启动时将UPX0进行填充)
首先将内存中UPX1节区起始地址赋值给esi,UPX0节区起始地址复制给edi。
怎么确定2A900D是UPX0、ds:[esi - 7800D]是UPX1?
按F8运行完这两行指令后查看ESI、EDI寄存器的值
在“内存布局”选项卡查看UPX0、UPX1节区地址跟EDI、ESI是能对应上的。
如下图,随后的指令中还对esi - (-4),最终esi:0x2A9011
循环复制
在接下来的指令中,有一个小循环,作用就是把UPX1偏移0x11的位置数据复制到UPX0中,就完成了UPX0节区填充的效果。
循环结束后,UPX0数据如下
在之后的操作中,基本都是在填充UPX0,但并不是简单的复制,还会夹杂着一些解压缩、还原CALL/JMP指令地址(没看出来,书上说的--!)等操作。
观察还原UPX0节区
我们可以在UPX壳完成最开始的ESI、EDI初始化操作后,在内存窗口中跟进UPX0节区的位置,然后ctrl+f8可视化的观察指令的执行,就会发现UPX0节区的内容在一点点的填充。
动图请看:http://121.37.221.150:4040/img/upx.gif
壳的最后
在UPX壳的最后,会使用popad指令把最开始pushad入栈的数据全部弹出,达到寄存器和堆栈为最原始的模样。
这时候查看UPX0节区的数据,就是被完整填充了。
在完成popad后,程序还会push几个0到堆栈中,然后就jmp到OEP的位置。
执行完jmp putty_upx.2A5CA0后就进入了我加壳的Putty程序原来的EP了,也就是OEP。通过调试未加壳的putty可以看到指令是完全一致的,只是因为开启了随机基址,基地址不一样而已。
因此可以得出跟网上一样的结论,upx壳的相关代码是包裹在pushad和popad指令之间的,只要找到popad指令,往下就会jmp到OEP。
所谓OEP就是Original Entry Point,也就是原始程序入口(百度来的),是相对于EP而言的,因为加壳后的程序EP值(可选PE头的AddressOfEntryPoint)被改变了,所以OEP指的应该就是程序加壳前的EP(非权威,自己理解)。
找到UPX壳的OEP
根据上述分析,要快速找到UPX壳的OEP,只需要在pushad后,esp地址所在内存位置设置硬件访问断点,然后F9执行到断点,这时就是执行了popad指令的时候,也就能找到OEP了。
- 首先f8执行一下pushad,然后跳转到esp指向的内存地址。
- 然后在esp所指向内存位置设置硬件访问断点
- 按f9执行到断点位置,查看eip往上一个指令就是popad了。
- 接着执行完jmp指令,就能到达OEP了