栈平衡原理定位 OEP
加壳软件必须保证外壳初始化的现场环境(各寄存器的值)与原程序的现场环境是相同的;
加壳后的程序在初始化时保存各寄存器的值,待外壳执行完毕再恢复各寄存器的内容,最后跳转到原程序执行;
通常,加壳后的程序在开始时使用 PUSHAD/PUSHFD 来保护现场,外壳执行完毕后,使用 POPAD/POPFD 恢复现场;
也就是说,加壳软件必须遵守栈平衡原理;
使用工具
- OllyDbg 1.10原版,简称
OD
; OD
汉化
和插件
均来自互联网;- UnPackMe来自互联网,仅供学习使用;
- 加壳工具为UPX,感谢 🙏 开源;
- 文中特殊数字均是
HEX
,为了书写方便采用DEC
;
分析思路
方法一
首先,使用 UPX 压缩 CM;
将加壳后的 CM 导入 OD 后,查看 EP 以及 ESP:
EP 处使用 PUSHAD 备份当前各通用寄存器的值,然后将会在外壳执行完毕后再使用 POPAD 恢复各寄存器的值,以保证程序到达 OEP 时,寄存器的内容保持不变;
此时 ESP 的值为:0012FFC4;
F7
单步执行程序,也就是执行PUSHAD
指令后,8 个通用寄存器的内容压入栈:加壳程序必须遵守栈平衡原理,所以在执行完毕后,一定会访问栈以恢复各寄存器的内容,所以给处于栈中的任意寄存器内容设置硬件访问断点:
这里以 EAX 寄存器的内容为例,使用
HR 12FFC0
,设置硬件访问断点;设置完毕后,可以在调试菜单,硬件断点选项下看到已经设置的硬件断点;
运行程序后, 程序中断:
中断位置的 POPAD 访问了保存在栈中的寄存器内容,触发了断点;
同时,各寄存器已经恢复到 EP 时的状态,ESP 重新指向 0012FFC4,说明栈中保存的内容被丢弃了,使用后释放,这就是堆栈平衡;
F7
单步执行程序,程序跳转到 OEP:此时各寄存器的内容以及 ESP 的值与程序处于 EP 时保持一致;
可以把整个外壳理解为一个函数或子程序,在执行过程中会遵守栈平衡原理,所以当外壳执行完毕跳转到 OEP 时,ESP 的值保持不变;
方法二
大多数程序 OEP 的第 1 行指令都是压栈指令(PUSH):
向栈压入数据时,栈指针减小,向低地址移动;从栈中弹出数据时,栈指针增加,向高地址移动;
以当前 CM 为例,执行
PUSH 0
后,ESP 由 0012FFC4 指向 0012FFC0,地址减小了,这是程序到达 OEP 后执行的第 1 条指令;反向思考一下,如果给 0012FFC0 设置写入断点,是不是就可以到达 OEP?
将程序载入 OD 后,程序停留在 EP 位置,此时的 ESP 为 0012FFC4,根据栈平衡原理,外壳执行完毕后,ESP 保持不变,所以,OEP 的第 1 条 PUSH 指令,会将内容写入 0012FFC0(向栈压入数据时,栈指针减小,向低地址移动);
确定写入地址后设置硬件写入断点
HW 0012FFC0
;运行程序后,程序中断在代码段:
可以看到,由于
PUSH 0
指令向 0012FFC0 写入内容,从而触发了硬件写入断点,导致中断,而这里就是 OEP;