逆向工程核心原理-第十五章-调试UPX压缩的notepad程序
15.1 查看notepad.exe的EP代码
先看一下未压缩的notepad
红框中都是notepad的特征, 我们从上到下解释三个红框:
- 第一个红框调用了GetModuleHandleA(), 获取了notepad的ImageBase也就是虚拟基址
- 第二个红框比较了MZ签名, 判断是DOS还是Windows上的文件, (5A = ‘Z’, 4D = ‘M’)
- 第三个红框比较了PE签名, 判断是否为PE文件
15.2 查看notepad_upx的EP代码
EP(入口点)在0x1015330, 位于第二个节区的末端部分, 压缩后的源代码在EP(入口点)上方
两个红框为notepad_upx的重要语句, 逐个分析
- 第一个PUSHAD指令是将EAX ~ EDI的值保存到栈, 我们可以理解为将初始的状态保存下来
- 第二个红框中的第一条汇编, 是将第二个节区的起始地址保存在ESI中
- 第二个红框中的第二条汇编, 是用第二个节区的起始地址减去10000, 运算得出了第一个节区的起始地址并存放在了EDI寄存器中
第一个节区就是在内存中(在磁盘中没有空间)存放解压后代码的地方
EDI跟ESI的作用就是从ESI(第二节区)中读取数据, 然后解压, 最后存放在EDI(第一个节区中)
接下来我们会跟进具体的过程
15.3 跟踪UPX文件
1 | 我们遵循下面的法则: |
15.3.1 OD的跟踪命令
分为四类:
animate跟trace之间的区别是:
- animate会显示跟踪过程
- trace是跟踪到暂停条件(F7)后停下, 并生成日志文件
15.3.2 循环#1
我们使用Animate Over, 遇到一个段循环后停下
我们看看汇编指令, 循环的次数为36B, 条件是ECX, 初始值是36B.
循环内的行为是: 从EDX中读取一个字节给al, 然后地址加一个字节指向下一个字节的内存, 然后将al中的值给EDI
我们直接跳出循环, 继续调试(跳出循环: 循环下一条语句按F4, 或在下一条语句处下一个断点, 然后F9)
15.3.3 循环#2
等待较长一段时间后, 我们遇到了一个较大的循环
这个循环就是正式的解码(解压缩)循环.
解压缩过程:
- 从ESI所指向的第二个节区(UPX1)地址中依次读取值
- 经过适当的运算解压缩后
- 将值写入EDI所指的第一个节区(UPX0)中
这是在解压缩过程中的寄存器状态
我们跟进EDI去看看第一个节区(UPX0)的情况
15.3.4 循环#3
这段循环代码用于恢复源代码的CALL/JMP指令的地址
到这里就已经接近尾声了, 只要再设置好IAT, 解压就结束了
15.3.5 循环#4
- 第一个小红框, 此处获得了第二个节区的中存放API函数名称字符串的地址EDI(1001000) + 13000 = 1014000, 接着提取处程序中调用的API名称列表(说明是多个)
我们使用OD查看
- 这是看到汇编窗口中的第二个小红框, 我们使用了这些API名称字符串调用了GetProcAddress()函数, 获得了API的起始地址, 然后把API地址输入EBX所指向的IAT区域, 反复进行, 知道恢复IAT. 简单的来说: 就是利用字符串查找到相应的API地址, 然后放入IAT中
至此对notepad_upx.exe的解压结束
我们来到最后阶段———跳转到OEP
- 第一红框内即恢复寄存器环境, 就像是一开始就进入了OEP一样
- 第二红框即跳转至OEP
15.4 快速查找UPX OEP的方法
15.4.1 在POPAD后JMP设置断点
即直接跳过前面的解压过程, 前提是要找到POPAD
15.4.2 在栈中设置硬件断点
这一个也是利用了PUSHAD与POPAD指令的特点: 将寄存器状态压入到一个栈中位置, 当它再次弹出(被使用)的时候那就是解压结束的时候, 所以我们可以在这个位置设下一个硬件断点. 这个方法的好处是不用找POPAD
通过寄存器的值我们可以找到PUSHAD在栈中的作用范围.
这里插入介绍一下硬件断点的特性: 等待设置断点的指令执行完成后才停止调试. 所以我们只要在任意一个栈位置设置一个硬件断点即可.
下硬件断点: 在Hex区域进行如下操作