Jirairya

压缩


压缩的基本内容和实际例子操作

运行时压缩器

运行时压缩器是针对可执行(PE)文件而言的,可执行文件内部含有解压缩代码,文件在运行瞬间于内存 中解压缩后执行。

运行时压缩文件也是PE文件,内部含有原PE文件与解码程序。在程序的EP代码中执行解码程序,同时在内存中解压缩后执行。

项目 普通压缩 运行时压缩
对象文件 所有文件 PE文件(exe、dll、sys)
压缩结果 压缩文件(zip、rar) PE文件(exe、dll、sys)
解压缩方式 使用专门解压缩程序 内部含有解码程序
文件是否可执行 本身不可执行 本身可执行
优点 对所有文件高压缩率压缩 无需解压程序解压就可运行
缺点 若无专门解压缩软件则无法使用压缩文件 每次运行时均需要调用解码程序导致运行时间很长

把普通PE文件创建成运行时压缩文件的实用程序称为“压缩器”(Packer),经反逆向技术特别处理的压缩器称为保护器(Protector)

调试UPX压缩的notepad程序

0x01 压缩前notepad.exe程序的EP代码:

notepad.exe的EP代码.png

010073B2地址处调用了GetModuleHandleA()API,获取notepad.exe程序的ImageBase。然后在010073B4010073C0地址处比较MZ与PE签名。

0x02 使用UPX压缩notepad

使用OD打开有弹窗告警:

OD警告消息框.png

选择“是”之后,出现以下:

notepad_upx.exe的EP代码.png

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

01015330 > $  60            pushad
01015331   .  BE 00100101   mov esi,0x1011000
01015336   .  8DBE 0000FFFF lea edi,dword ptr ds:[esi-0x10000]

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

调试时像这样设置ESI和EDI,就能预见ESI所指缓冲区到EDI所指缓冲区的内存发生复制。此时,从源地址(ESI)读取数据,解压缩后保存到目标地址(EDI)。跟踪上图全部UPX EP代码,并最终找到原notepad的EP代码。。

跟踪UPX文件

跟踪代码,跟踪数量庞大的代码时,遵循:“遇到循环,先了解作用再跳出”的法则。

命令 快捷键 说明
Animate Into Ctrl+F7 反复执行Step Into命令(画面显示)
Animate Over Ctrl+F8 反复执行Step Over命令(画面显示)
Trace Into Ctrl+F11 反复执行Step Into命令(画面不显示)
Trace Over Ctrl+F12 反复执行Step Over命令(画面不显示)

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

0x03第一个循环

在EP代码处执行Animate Over(Ctrl+F8)跟踪代码,光标上下快速移动(按F7可停止跟踪)

第一个循环.png

循环次数ECX=36B,循环内容为“从EDX(01001000)中读取一个字节写入EDI(01001001)”。EDI寄存器所指的01001000地址即是第一个节区(UPX0)的起始地址,仅存于内存中的节区(内容全部为NULL)

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

0x04第二个循环

在断点处再次使用Animate Over(Ctrl+F8)命令继续跟踪代码:

第二个循环.png

该循环为正式的解码循环(解压缩)

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

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,0x4

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

只要在01015402地址处F2设置号断点之后再运行即可跳出第二个循环。运行到01015402地址后,在转储窗口可看到解压缩后的代码已被写入第一个节区(UPX0,起始地址为01011000)

0x05第三个循环:

继续跟踪代码,遇到第三个循环:

第三个循环.png

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

到此只要设置IAT,UPX解压缩代码就结束。对于普通运行时压缩软件,源文件代码、数据、资源解压缩之后,先设置好IAT再转到OEP(源文件的EP)

0x06第四个循环:

继续跟踪代码:

第四个循环.png

图中循环为IAT循环。在01015436地址处设置EDI=01014000,它指向第二个节区(UPX1)区域,该区域中保存着原notepa.exe程序调用的API函数名称的字符串。

API名称字符串.png

UPX压缩原notepad.exe文件时,会分析其IAT,提取出程序中调用的API名称列表,形成API名称字符串。 用这些API名称字符串调用01015467地址处的GetProcAddress()函数,获取API的起始地址,然后把API地址输入EBX寄存器所指的原notepad.exe的IAT区域。该过程会反复进行至API名称字符串结束,最终恢复原notepad.exe的IAT。 notepad全部解压缩完之后,将程序的控制返回到OEP处。

JUMP to OEP.png

010154AD地址处的POPAD命令与UPX代码的第一条PUSHAD命令对应,用来把当前寄存器恢复原状。 最终,使用010154BB地址处的JMP命令跳转到OEP处,要跳转到的目标地址为0100739D,这就是原文件的EP地址。

快速查找UPX OEP

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

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

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

在栈中设置硬件断点

01015330处执行PUSHAD命令后,查看栈。(用ESP定律直接查看ESP值)

ESP值.png

EAX到EDI寄存器的值依次存入栈中。从OD的Dump窗口进入栈地址0006FF6C。定位到该地址,使用右键设置硬件断点(硬件访问->Word)。

设置硬件断点.png

然后按F9运行到达POPAD处,就可以找到jmp指令,即找到OEP

JUMP to OEP.png

硬件断点是CPU支持的断点,最多可以设置4个断点。与普通断点不同,设置断点的指令执行完成后才暂停调试。在这种状态下运行,程序会边解压缩边执行代码,在执行POPAD的瞬间访问设置有硬件断点的0006FF6C地址,然后暂停调试。其下方就跳转到OEP的JMP指令。

ESP定律 :

  1. 打开程序,F8运行,观察ESP寄存器是否突显(变红色)(执行pushad指令之后的第一个ESP值)
  2. 在命令行下,输入:dd ESP值,跳转到该地址
  3. 在该地址处设置断点:断点->硬件访问断点->WORD断点
  4. 按F9运行之后,就可以达到popad处,找到JMP指令处,就找到OEP

下一篇 基址重定位表

Comments

Content