写在前面
PELock 1.0x -> Bartosz Wojcik 壳也是一个比较老的壳,此壳有运用了stolen bytes ,Anti dump,检测断点等防脱手段。我们接下来就来脱此壳并讨论如何解决这些防脱手段。
分析工具
- OD
- PEditor
- ImportRE
- LordPE
- 运行环境虚拟机 Windows XP
去除外壳
我们应该在Windows XP下脱壳,因为这个壳比较老如果在win10或win7上脱可能会出现一些问题。
寻找OEP
我们运行程序发现程序存在多处异常,这时我们最好的方法就是采用 “最后一次异常法” 来寻找OEP。我们先将设置OD忽略所有的异常
我们运行程序然后查看日志窗口,我们发现最后一次异常为内存写入异常,发生在地址0x396744处。
接着我们把刚才忽略的所有异常都取消。我们多次Shift+F9运行程序来到最后一次异常处。
来到最后一次异常处后我们Alt+M打开内存窗口,并在主模块的提示含有代码的段下内存访问断点。
运行程序后程序会停在OEP处,但是我们发现此OEP处的代码和正常OEP处的代码不一样。一般OEP处的代码都是push ebp mov ebp,esp
,所以我们可以得出应该是入口处的代码放在了壳代码空间中了,此OEP也就是假的OEP。这种防脱壳手段被称为stolen bytes,理解为盗取代码也行,我们需要还原被盗取的代码并找到真正的OEP。
解决stolen bytes
因为其盗取的代码需要在跳转到假的OEP前执行,所以我们需要在壳代码所有的异常发生后去寻找被盗取的代码。
重新载入程序来到最后一次异常处,我们在堆栈窗口中发现此异常的SEH异常处理程序的地址为0x00396485,我们在对应的地址处下断点。
Shift+F9运行程序之后程序会停在对应的异常处理程序入口处(0x00396485处)。
接着我们我们F7往下单步我们会来到函数ZwContext()处,函数的完整形式为ZwContext(HANDLE hThread,CONTEXT * lpContext)
此函数的第二个参数为CONTEXT类型的数据的指针,CONTEXT结构如下。
DWORD ContextFlags;
// This section is specified/returned if CONTEXT_DEBUG_REGISTERS is
// set in ContextFlags. Note that CONTEXT_DEBUG_REGISTERS is NOT
// included in CONTEXT_FULL.
DWORD Dr0;
DWORD Dr1;
DWORD Dr2;
DWORD Dr3;
DWORD Dr6;
DWORD Dr7;
// This section is specified/returned if the
// ContextFlags word contains the flag CONTEXT_FLOATING_POINT.
FLOATING_SAVE_AREA FloatSave;
// This section is specified/returned if the
// ContextFlags word contains the flag CONTEXT_SEGMENTS.
DWORD SegGs;
DWORD SegFs;
DWORD SegEs;
DWORD SegDs;
// This section is specified/returned if the
// ContextFlags word contains the flag CONTEXT_INTEGER.
DWORD Edi;
DWORD Esi;
DWORD Ebx;
DWORD Edx;
DWORD Ecx;
DWORD Eax;
// This section is specified/returned if the
// ContextFlags word contains the flag CONTEXT_CONTROL.
DWORD Ebp;
DWORD Eip;
DWORD SegCs; // MUST BE SANITIZED
DWORD EFlags; // MUST BE SANITIZED
DWORD Esp;
DWORD SegSs;
// This section is specified/returned if the ContextFlags word
// contains the flag CONTEXT_EXTENDED_REGISTERS.
// The format and contexts are processor specific
BYTE ExtendedRegisters[MAXIMUM_SUPPORTED_EXTENSION];
} CONTEXT;
我们由堆栈数据可得lpContext为0x0012FCE4。
在内存窗口中查看此地址最后由CONTEXT结构得EIP为0x00396746,也就是刚才发生内存写入异常指令的下一条指令的地址。
我们在地址0x00396746处下断点然后运行程序,程序会停在此地址处。
接着我们利用RUN 跟踪来查找被窃取的代码。设置跟踪条件:EIP的范围为0x400000 - 0x480000,即把程序所有的区块都包含进来,因为程序真正的入口点代码第一条指令应为push ebp,而在到达入口点代码前一般需要popad恢复寄存器。所以设置跟踪的暂停条件为遇见指令popad或push ebp。
我们Ctrl + F11运行程序发现程序停在了push ebp指令处。接着我们单步往下跟踪判断此处是否为程序入口店代码,当我们往下单步跟踪时我们到达了假的OEP跳转处,所以我们可以确定此处就是被窃取的代码。
我们打开RUN跟踪窗口,我们可以看到从push ebp开始到跳转到假的OEP处之间所执行的所有的指令,其中大量的jmp指令都是一些花指令。
我们重新加载程序,并从刚才的跟踪到的push ebp指令地址处开始记录所有有效指令的机器码,知道跳转到假的OEP。最后得到被窃取的代码的机器码为
55 8B EC 6A FF 68 60 0E 45 00 68 C8 92 42 00 64 A1 00 00 00 00 50 64 89 25 00 00 00 00 83 C4 A8 53 56 57 89 65 E8
我们来到假的OEP处,因为被窃取的机器码共0x26个,所以我们在数据窗口搜索地址0x4271B0(0x4271D6 - 0x26),并将被窃取的机器码全部粘贴。然后我们可以看到被窃取的代码恢复,程序真正的OEP的RVA为 0x271B0。
DUMP程序
用工具LordPE选择被调试程序进程,先修复其镜像大小,然后在完整转存。
重建输入表
我们先查询程序的IAT在什么位置,随便找到一条API调用指令然后我们在数据窗口中查看内存地址。我们可以得知IAT的起始RVA为0x60818。
我们打开工具ImportRE选择被调试进程,输入OEP的RVA为0x271B0,IAT的起始RVA为0x60818。点击获取输入表,接着把无效函数剪切后得到最后有效的输入表数据。
接着我们修复转存文件,修复后我们运行程序发现无法运行。我们用OD查看修复后的转存文件,单步往下跟踪。到达指令jmp 00A94865
处时如果F7单步程序就出错。
我们查看内存发现并不存在地址A9XXXX,这说明此地址应该是属于外壳的,在脱壳DUMP时并没有被dump。
我们查看未脱壳的程序此处跳转,结果发现此处跳转实际是调到API的调用处。即外壳会把函数的调用放在壳代码空间中,当需要调用API时先由原程序空间跳到壳代码空间中,然后在壳代码空间中调用API随后再返回原程序中。
如果这样做我们在脱壳时dump的程序就不完整,因为原程序的API调用在壳代码空间中,而壳代码空间在dump时是被忽略的,以此达到反DUMP的目的。
解决Anti dump
我们需要将被忽略的区块给重新添加到dump程序中才行,我们利用LordPE的区域转存功能将0xA90000所在的区段转存。
然后通过工具PEditor利用刚刚转存的区块文件为修复过的dump文件添加一个区块。
然后更改此区块的RVA(虚拟偏移)为 0x690000(0xA90000 - 0x400000)。
我们运行程序结果显示文件无效,一般修复完遇见无效的情况我们就需要重建PE。
我们利用工具LordPE重建文件。
重建后再运行程序发现正常运行,脱壳成功。
总结
此壳的难点主要是对抗stolen bytes和Anti Dump。其中此壳只是这两种防脱壳手段的一种形式,这两种还有很多其他的形式。