第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所示。
在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所示。
EP地址为010143E0,该处即为第二个节区的末端部分。实际压缩的notepad源代码存在于EP地址(010143E0)的上方。
下面看一下代码的开始部分( 01014400)。
1 |
|
首先使用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所示。
循环次数ECX=36B,循环内容为“从EDX( 01001000 )中读取一个字节写人EDI( 01001001 )“。
EDI寄存器所指的01001000地址即是第一个节区(UPX0 )的起始地址,仅存在于内存中的节区(反正内容全部为NULL)。
调试经过运行时压缩的文件时,遇到这样的循环应该跳岀来。在010153E6地址处按F2键设置好断点后,按F9跳出循环。
15.3.3 循环 #2
在断点处再次使用Animate Over(Ctrl+F8)命令继续跟踪代码,不久后遇到图15-5所示的循环
该循环是正式的解码循环(或称为解压缩循环)。
先从ESI所指的第二个节区(UPX1)地址中依次读取值,经过适当的运算解压缩后,将值写
入EDI所指的第一个节区(UPX0)地址。该过程中使用的指令如下:
1 |
|
解压缩后的数据在AL(EAX)中,EDI指向第一个节区的地址。
只要在010144B2地址处设置好断点再运行,即可跳出第二个循环,如图15-5所示。运行到010144B2地址后,在转储窗口中可以看到解压缩后的代码已经被写入第一个节区(UPX0)区域(01007000),如图15-5中原来用NULL填充的区域。
15.3.4 循环 #3
重新跟踪代码,稍后会遇到图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 )。
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的代码。
另外,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所示。
EAX到EDI寄存器的值依次被存储到栈。从x32dbg的Dump窗口进入栈地址(006FF6C)。将 鼠标光标准确定位到006FF6C地址,使用鼠标右键菜单设置硬件断点,如图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章