第15章 调试UPX压缩的notepad程序

本文最后更新于:2022年5月19日 晚上

本章将调试UPX压缩的notepad_upx.exe程序,进一步了解运行时压缩的相关概念。我们的目 标是通过调试一点点地跟踪代码,最终找出原notepadexe程序代码。最后再简单讲解一下经过UPX压缩的文件如何通过调试器。

本章示例使用的是Windows XP SP3中的notepad.exe程序。

调试环境为Win7 环境

15.1 notepad.exe 的 EP 代码

首先看一下原notepad.exe程序的EP代码,如图15-1所示。

图15-1

在010073B2地址处调用了GetModuleHandleA() API,获取notepad.exe程序的ImageBase。然后 在010073B4与010073C0地址处比较MZ与PE签名。希望各位熟记原notepad.exe的EP代码。

15.2 notepad_upx.exe 的 EP 代码

使用x32dbg打开notepad_upx.exe显示岀UPX EP代码,如图15-3所示。

图15-3

EP地址为010143E0,该处即为第二个节区的末端部分。实际压缩的notepad源代码存在于EP地址(010143E0)的上方。

下面看一下代码的开始部分( 01014400)。

1
2
3
01015330            | 60                  | pushad                                             |
01015331 | BE 00100101 | mov esi,notepad_upx.1011000 |
01015336 | 8DBE 0000FFFF | lea edi,dword ptr ds:[esi-10000] |

首先使用PUSHAD命令将EAX~EDI寄存器的值保存到栈,然后分别把第二个节区的起始地址(1011000)与第一个节区的起始地址( 1001000)设置到ESI与EDI寄存器。UPX文件第一个节区存在于内存。该处即是解压缩后保存源文件代码的地方。

调试时像这样同设置ESI与EDI,就能预见从ESI所指缓冲区到EDI所指缓冲区的内存发生了复制。此时从Source ( ESI )读取数据,解压缩后保存到Destination ( EDI )。我们的目标是跟踪图15-3中的全部UPXEP代码,并最终找到原notepad的EP代码,如图15-1所示。

  • 代码逆向分析称源文件的EP为OEP。
  • “跟踪” 一词的含义是通过逐一分析代码进行追踪。
  • 实际的代码逆向分析中并不会逐一跟踪执行压缩代码,常使用自动化脚本、特殊技巧等找到OEP。但是对于初次学习运行时压缩文件的朋友而言,逐一跟踪代码才是正确的学习方法

15.3 跟踪UPX文件

下面开始跟踪代码,跟踪数量庞大的代码时,请遵循如下法则。

“遇到循环(Loop)时,先了解作用再跳出。”

整个解压缩过程由无数循环组成。因此,只有适当跳岀循环才能加快速度。

15.3.1 x32dbg的跟踪命令

跟踪数量庞大的代码时,通常不会使用Step Int0(F7)命令,而使用x32dbg中另外提供的跟踪调试命令,如下表所示。

命令 快捷键 说明
自动步进(Animate Into) Ctrl + F7 反复执行步进命令(画面显示)
自动步过(Animate Over) Ctrl + F8 反复执行步过命令(画面显示)
步进直到条件满足(Trace Into) Ctrl + Alt + F7 反复执行步进命令直到条件满足(画面不显示)
步过直到条件满足(Trace Over) Ctrl + Alt + F8 反复执行步过命令直到条件满足(画面不显示)

除了画面显示的之外,Animate命令与Trace命令是类似的,由于Animate命令要把跟踪过程显示在画面中,所以执行速度略微慢一些。而两者最大的差别在于,跟踪命令会自动在事先设置的跟踪条件处停下来,并生成日志文件。在UPX文件跟踪中将使用Animate Over(Ctrl+F8)命令。

15.3.2 循环 #1

在EP代码处执行Animate Over(Ctrl+F8)命令,开始跟踪代码。可以看到光标快速上下移动。

若想停止跟踪,执行Pause(F12)命令即可。

开始跟踪代码不久后,会遇到一个短循环。暂停跟踪,仔细查看相应循环,如图15-4所示。

图15-4

循环次数ECX=36B,循环内容为“从EDX( 01001000 )中读取一个字节写人EDI( 01001001 )“。

EDI寄存器所指的01001000地址即是第一个节区(UPX0 )的起始地址,仅存在于内存中的节区(反正内容全部为NULL)。

调试经过运行时压缩的文件时,遇到这样的循环应该跳岀来。在010153E6地址处按F2键设置好断点后,按F9跳出循环。

15.3.3 循环 #2

在断点处再次使用Animate Over(Ctrl+F8)命令继续跟踪代码,不久后遇到图15-5所示的循环

图15-5

该循环是正式的解码循环(或称为解压缩循环)。

先从ESI所指的第二个节区(UPX1)地址中依次读取值,经过适当的运算解压缩后,将值写
入EDI所指的第一个节区(UPX0)地址。该过程中使用的指令如下:

1
2
3
4
5
6
7
8
0101534B            | 8807                | mov byte ptr ds:[edi],al                           |
0101534D | 47 | inc edi |
....
010153E0 | 8807 | mov byte ptr ds:[edi],al |
010153E2 | 47 | inc edi |
...
010153F1 | 8907 | mov dword ptr ds:[edi],eax |
010153F3 | 83C7 04 | add edi,4 |

解压缩后的数据在AL(EAX)中,EDI指向第一个节区的地址。

图15-5

只要在010144B2地址处设置好断点再运行,即可跳出第二个循环,如图15-5所示。运行到010144B2地址后,在转储窗口中可以看到解压缩后的代码已经被写入第一个节区(UPX0)区域(01007000),如图15-5中原来用NULL填充的区域。

15.3.4 循环 #3

重新跟踪代码,稍后会遇到图15-6所示的第三个循环。

图15-6

该段循环代码用于恢复源代码的CALL/JMP指令(操作码:E8/E9)的destination地址。在 01015436地址处设置断点运行后即可跳出循环。

到此几乎接近尾声了,只要再设置好IAT,UPX解压缩代码就结束了。

对于普通的运行时压缩文件,源文件代码、数据、资源解压缩之后,先设置好IAT再转到OEP。

15.3.5 循环 #4

重新跟踪代码,再稍微进行一段。

图15-7深色显示的部分即为设置IAT的循环。在01015436地址处设置EDI=01014000,它指向第二个节区(UPX1)区域,该区域中保存着原notepad.exe调用的API函数名称的字符串(参考图15-8 )。

图15-7

图15-8

UPX压缩原notepad.exe文件时,它会分析其IAT,提取岀程序中调用的API名称列表,形成API名称字符串。

用这些API名称字符串调用图15-7中01015467地址处的GetProcAddress()函数,获取API的起始地址,然后把API地址输人EBX寄存器所指的原notepad.exe的IAT区域。该过程会反复进行至API名称字符串结束,最终恢复原notepad.exe的IAT。

notepad.exe全部解压缩完成后,应该将程序的控制返回到OEP处。图15-9显示的就是跳转到OEP的代码。

图15-9

另外,010154AD地址处的POPAD命令与UPX代码的第一条PUSHAD命令对应,用来把当前寄存器恢复原状(参考图15-3 )。

最终,使用010154BB地址处的JMP命令跳转到OEP处,要跳转到的目标地址为0100739D,它就是原notepad.exe的EP地址(请各位确认)。

15.4 快速查找UPX OEP的方法

各位都像上面这样顺利完成代码跟踪了吗?代码逆向技术的初学者一定要亲自试试,这有助于调试用其他压缩器压缩的文件。但每次都使用上述方法(跳出循环)查找OEP非常麻烦,实际代码逆向分析中有一些更简单的方法可以找到OEP (以UPX压缩的文件为例)。

15.4.1 在POPAD指令后的JMP指令处设置断点

UPX压缩器的特征之一是,其EP代码被包含在PUSHAD/POPAD指令之间。并且,跳转到OEP代码的JMP指令紧接着岀现在POPAD指令之后。只要在JMP指令处设置好断点,运行后就能直接找到OEP。

  • PUSHAD指令将8个通用寄存器(EAX~EDI)的值保存到栈。
  • POPAD指令把PUSHAD命令存储在栈的值再次恢复到各个寄存器。

15.4.2 在栈中设置硬件断点

该方法也利用UPX的PUSHAD/POPAD指令的特点。在图15-3中执行01015330地址处的PUSHAD命令后,查看栈,如图15-10所示。

图15-10

EAX到EDI寄存器的值依次被存储到栈。从x32dbg的Dump窗口进入栈地址(006FF6C)。将 鼠标光标准确定位到006FF6C地址,使用鼠标右键菜单设置硬件断点,如图15-11所示。

图15-11

硬件断点是CPU支持的断点,最多可以设置4个。与普通断点不同的是,设置断点的指令执行完成后才暂停调试。在这种状态下运行,程序就会边解压缩边执行代码,在执行POPAD的瞬间访问设置有硬件断点的0006FFA4地址,然后暂停调试。其下方即是跳转到OEP的JMP指令(熟悉该方法的操作原理才能在以后调试各种文件时得心应手)。

15.5 小结

前面学习了有关调试UPX运行时压缩文件的内容。建议各位参照书中讲解亲自操作,通过逐一跳出各循环的方法查找OEP。经过这样一系列的实际操作后,相信各位的调试水平都会得到很大提高。

Q.解压缩(Unpacking)过程中打开Dump窗口,若不重新设置IAT就会出现初始化错误。这到底是怎么一回事?

A.比如,运行UPX文件后转储时,IAT中存在(对应于当前系统的)准确的API地址。但是INT却处于损坏状态。
PE装栽器使用INT中的API名称字符串(LoadLibrary()/GetProcAddress())来获取实际API地址,并将它们记录到IAT。由于INT已经损坏,该过程中自然会发生错误。

Q.很多汇编指令都不懂,请介绍可以查找汇编指令的网站吧,谢谢!

A.此时,我通常会去查Intel的官方文档:http://www.intel.com/products/processor/manuals/。

Q.如何知道ESI、EDI所指的地址对应于哪个节区的地址呢?我想知道该如何才能识别出恢复IAT的代码以及解码循环。

A.内存复制命令中,ESI指Source,EDI指Destination。所以使用PEView (或者x32dbg的内存映射窗口)查看ESI/EDI所指的地址,即可知道它们对应的节区。从反复调用GetProcAddress()函数可知,这是在恢复文件的IAT。此外,如果拥有丰富的解压缩经验,就更容易预测,这是刚刚接触的人无法企及的。

参考

《逆向工程核心原理》 第15章


第15章 调试UPX压缩的notepad程序
https://m0ck1ng-b1rd.github.io/1999/02/13/逆向工程核心原理/第15章 调试UPX压缩的notepad程序/
作者
何语灵
发布于
1999年2月13日
许可协议