逆核系列No.5--Upack压缩过后的OEP

写在前面

本节内容关注利用OllyDbg调试UPack压缩过的文件,并初步体验UPack的解压缩过程。

首先了解两个概念:EP & OEP

EP是EntryPoint简称,就是入口点。
如果程序加壳后,程序会有个入口点,就是EntryPoint。但加壳前的程序入口点就称为OEP(Original EntryPoint)

因此本节的目的在于找到UPack加壳前的程序,即OEP

依照惯例附上PE格式结构图


Part .1:设置OD的EP

先前有文章分析过UPack对PE文件的修改,接着进行分析

并且第一个节区的VirtualSize是未压缩前notepad.exe的 ImageSize 并且UPack的压缩过后的notepad.exe的数据存放在第二个节区中,此外UPack解压缩需要的代码以及数据存放在UPack对PE格式中一些不必要字段的利用以及在符合规范下进行扩充的部分==(IMAGE_OPTIONAL_HEADER大小由0xE0 -> 0x148即OptionalHeader正常结束的位置到SectionHeader的空间 以及 OptionalHeader.DataDirectory节省下来的空间 以及每个节区的 SectionHeader中不必要字段)==

OllyDbg载入

载入过程出现如下的提示

这是由于Upack在压缩时改变了PE格式,而Ollydby在载入文件时会校验PE格式是否正确(Upack畸形的并未通过,因此提示)

这时需要手动完成EP的设置,需要载入的ImageBase以及EntryPoint的RVA

使用工具查看:

出于学习目的还是回顾下上次的《UPack分析》文章手工找出这两个字段并验证。

这两个字段都是OptionalHeader中的字段,根据《UPack分析》知道OptionalHeader起始偏移0x28,结合文首PE结构图AddressOfEntruPoint 、 ImageBase 字段在OptionalHeader中的相对偏移找出这两个字段:

AddressOfEntruPoint = 0x00001018 ImageBase = 0x01000000,结果与工具分析出的一致。

知道了这两个值,程序EP应该是 0x01001018,在OllyDbg进行设置(有些三方修改过的OD已经可以识别就省略这一步)


Part .2:解码循环

所有压缩器中都存在解码循环(DecodeLoop),循环读取加密的内容进行解码然后进行写回操作恢复,先前分析知道UPack会把压缩的数据放在第二个节区,在运行解码循环将这些数据解压缩后放到第一个节区。

因此在调试的过程中需要留意数据传输的指令所操作的地址所在的区段是否与第二个节区、第一节区相关:

从EP开始调试,0x01001018

运行到光标处时前面两行指令完成的是从0x010011B0处读取DWORD(4byte)的内容到EAX,此时可见EAX的值是:0x0100739D(《逆向工程核心原理》提到这个值是OEP,猜测是经验积累的结果),如果事先知道0x0100739D就是OEP则直接设置断点让debug自己运行即可得到解压后的代码了。出于学习目的,该方案不做考虑,探究UPack的解压缩一些过程。

书中提到UPack的Decode()是在0x0101FD18处,会被反复调用进行压缩单元的解压

查看esi的值 = 0x0102718C,由于是DS段,故在数据窗口跟踪这个地址,找到对应的值是 ==0x0101FCCB(Decode函数入口)==

也就是实际上 call dword ptr ds:[esi] 调用的是0x0101FCCB处的函数,跟进去看看:

decode()会将第二节区的压缩的内容进行循环解压写回到第一节区,因此decode对应的指令需要着重关注同时设置了edi esi寄存器的mov指令的部分,因为这通常意味着数据的传输(并不是第一次执行,因为实在调试中截的图)

可以看到重复从ds : [esi] (第一节区)往es : [edi]写入数据,写入长度看ecx = 0x110,观察此次写入操作的数据窗口内容变化

经过调试观察得到rep movs byte ptr es:[edi] , byte ptr ds:[esi]完成的是清空ds:[edi]的0x110空间的内容。真正实现解码内容传递的是在decode()中的这条指令:(在最开始这个指令并不是一样,运行过后会变动,位置时第二节区)

出于好奇进行观察,跟踪地址0x0101FE5D的数据变化,结果发现如下的指令改变了这个地址的内容(把第三节区的内容复制到第二节区),使得指令发生变化:

发现从地址0x010270F0起始的0x27个DWORD内容传送至0x010FE28起始往后0x27个DWORD空间

接着调试分析发现下面这个指令是解压数据的产生位置,会

接下来就是这行指令完成解压数据的写回,观察写入地址,发现地址是属于第一节区的部分

使用OD打开未经UPack处理的原版程序发现写入的位置相同的部分数据相同,观察数据窗口的变化以及指令的执行情况,发现从0x0100136B开始往后写解码数据

目前调试以及写回的解码部分(选中)

未被UPack处理的原始数据中对应的部分

结束解码写回即是退出解码循环:

查看ds:[esi+0x34]地址中存放的4字节值 = 0x01014B5A, 即当edi的值 = 0x01014B5A解码结束。

在跳出循环指令下一条处设置断点运行程序,跳出解码循环:

观察解码数据写入情况:

UPack版本:

原始版本:

数据完全一致,说明UPack的解码部分完全还原原始代码。

14B5A - 136B = 137EF,相比ImageSize少了一部分,应该是在解码的时候写入了其他位置,因为在开启写入136B位置之前,写入指令的地址指向是第一节区的其他位置。


Part .3:设置IAT

一般而言,压缩器执行完解码循环后会根据源文件重新组织IAT。这就是凭经验判断OEP的方法,UPack也有类似的过程。

《逆向工程核心原理》书中是根据DLL名以及导入的函数来确定压缩器重新组织IAT的指令群的,同样,在结束解码循环后在OD中顺着指令执行流程一路F8也看得到这个代码:

之后碰到retn指令观察到栈顶元素,即文首开始提到的OEP了

Author: Victory+
Link: https://cvjark.github.io/2022/05/01/逆核系列No-5-Upack压缩过后的OEP/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.