庖丁解牛之UPack工作原理及实例分析(3)

UPack是软件逆向工程中常见课题;前两篇对于Runtime压缩基础知识和UPack压缩原理进行了详细解读。 本文在前两篇的基础上对于动态调试之UPack中查找OEP作了深入阐述。

动态调试之UPack中查找OEP

经UPack压缩器压缩有的文件,其文件头部和文件节区内容都发生了变动,尤其对于一些病毒、木马类的PE文件,为了能够分析其内部功能,需要先通过动态跟踪调试技术找到文件的OEP入口点,之后才可以分析文件的具体功能。

查找解密函数

接下来继续以压缩的notepad的文件为例,来看看UPack是怎样处理的文件的各个部分。前面介绍到,文件头部发生了变化,同时正常文件的’text’,’data’等节区也被压缩到了notepad的第二个节区(第一,第三节区的部分空间已经映射了文件的头部)。我们也知道对于一般的正常的PE文件,文件的第一节区应该是代码段,第二节区是数据段。压缩后的notepad数据在第二节区,UPack解码需要将该节区的部分数据解密到第一节区。

接下来就需要查找文件的解密函数,根据压缩文件的变形格式,前面已经计算除了压缩文件在内存的入口点是1018+内存基址=100108,通过实用工具也可以找到

notepad

notepad

OD加载notepad文件,由于OD是解析PE文件时会按照正常的PE文件解析加载文件,但是经过压缩后的notepad文件中,IMAGE_OPTIONAL_HAEADER中的NumberOfRvaAndSizes值设置成A,在利用OD打开文件时会提示如下错误。

wrong

wrong

直接按确认即可。上面错误会导致OD停留在ntdll.dll中,而无法定位到EP位置。手工找到地址0x1001018并将OD工具中的EIP指针指向该位置。如下图所示:

二进制3

二进制3

现在压缩文件被加载到内存中,看看内存中布局情况,如下图

二进制4

二进制4

内存中只加载了三个模块,其中notepad只识别出了头部,kernel32是前面文件头部识别时我们已经知道的已经加载的模块。

4.1.1 解密前准备

接下来就可以正常调试notepad,查找解码函数。

所有压缩器中都存在解码循环;调试过程中要仔细观察寄存器,注意响应值被写入哪个地址(当然这需要丰富的经验)。UPack把压缩代码将数据放到第二个节区,再运行解码循环将这些数据解压后放到第一个节区。从EP代码0x1001018开始调试。从该地址可以,压缩文件的开始入口在第一节区,动态单步执行可知,前两条指令是将0x100739D压入堆栈(系统和软件版本不同,该值也不同)

二进制5

二进制5

地址0x100739D,根据压缩文件notepad节区分配,知道该地址属于第一节区,这些将第一节区的一个地址压栈,很可能将来第二节区数据解密数据被解密到第一个节区的该地址,也就是说,该地址很可能就是OEP。

notepad2

notepad2

单步运行几步后,查看堆栈会发现Loadlibrary,GetProcAddr函数也会入栈,其实不言而喻,两个函数是为了在文件解密完成后对原始文件重建IAT表。

单步跟踪几步后发现,会将第三节区0x10260f0-0x102618c拷贝到第二节区如下图所示

二进制6

二进制6

继续单步跟踪一段后,会执行到如下指令

二进制7

二进制7

从执行地址0x1001018到执行到此,代码都是在压缩文件的第一节区执行,一直到该执行指令才跳转到101EC57,也就是第二节区,执行解密函数。

4.1.2 执行解密函数

执行地址0x101ec57处指令,单步跟踪几步就会到达解密函数如下图所示:

二进制8

二进制8

解密函数本事由一些条件分支语句和循环构成,如下图

解密函数

解密函数

解密函数跟踪也相当复杂,其目的就是要解密第二节区的数据到第一节区。由于解密本身的复杂性,就不在单独跟踪。既然知道解密函数,知道F8单步过该函数,就完成了解密过程。解密完成后查看内存布局还是没什么变化,如下图所示

解密函数1

解密函数1

其中原始notepad引入的动态库函数也没有显示。这是因为UPack压缩notepad后,破坏了文件的IAT表,需要重新建立IAT表,在notepad运行时才能找到对应的库函数。

4.2 重建IAT表

压缩后执行完解密函数后,会根据原文件重建IAT表,UPack使用导入的2个函数LoadLibrary和GetProcAddress,边执行边循环构建原本notepad的IAT。

IAT

IAT

上图是重建IAT的代码,从图中可知,该代码的执行还在第二节区,图中地址0x1013004是刚解密后还未重建IAT表时,库模块和函数的符号地址,如下图

IAT1

IAT1

地址10012c4是重建IAT表后,IAT表的地址,通过函数LoadLibrary加载模块,有GetProcAddress将函数对应的地址写入0x10012c4,IAT表重建完成后如下图

IAT2

IAT2

此时查看内存布局,如下图

IAT3

IAT3

原始notepad文件引入的模块已经全部显示载入,这样解密后的重建IAT表也就完成了。

4.3 定位OEP及主函数入口

IAT表重建完成后执行一条RETN指令,直接到了地址0x100739D,这个地址属于解密后第一节区的地址,并且这个地址很熟悉;前面在介绍解密前准备工作时,这个地址压栈的一个地址,前面说这个地址很可能就是OEP的入口地址,如下图所示

OEP

OEP

使用鼠标对反汇编窗口翻屏,会看到几个函数

OEP1

OEP1

经常动态调试程序的同学会知道,这是系统加载文件,执行主函数之前做的初始化工作,由此验证了地址0x100739D正好是要找的OEP。

继续单步跟踪执行到地址地址0x100750c时就是要执行的主函数。

OEP2

OEP2

该处调用地址0x1002936,开始正式执行notepad.exe程序。

OEP3

OEP3

到此UPack对notepad解压过程介绍完毕。

总结

本文通过实例对UPack运行时压缩器做了详细的介绍,通过对压缩文件分析,熟悉了PE文件结构,及文件加载的整体过程,同时对UPack压缩中的使用的技术做了详细介绍。接下来从技术角度和对UPack压缩文件的加载流程方面做总结。

5.1 技术要点总结

  1. DOS头部变形,去掉DOS STUB部分,修改e_lfanew字段,将IMAGE_FILE_HEADER的标示重叠到DOS头部之中。
  2. IMAGE_FILE_HEADER变形,增大字段SizeOfOptionHeader的值,这样就增大了IMAGE_OPTION_HEADER结构的大小,用于存放一些解密代码。
  3. 对IMAGE_OPTIONAL_HEADER变形,改变字段NumberOfRvaAndSizes的大小将DATA_DIRECTORY结构数组的部分空间存储代码数据。
  4. 对IMAGE_SECTION_HEADER及节区内存压缩变形,改变原始节区的起始位置和大小,对磁盘文件到内存的映射做了重叠变形,即将第一节区和第二节区重叠,第二节区解密数据后释放到第一节区。
  5. 利用加载器解析文件对齐方式的特性,计算RVA方式颠覆传统的计算思维。绕过加载器解析规范,实现文件的重叠加载。
  6. 对IAT地址表做了变化,利用节区分割的特性,逆转IMAGE_IMPORT_DESCRIPTORE结构的解析流程。
  7. 定位OEP入口点,执行主程序。

5.2 流程处理总结

流程处理

流程处理

相关内容链接:
庖丁解牛之UPack工作原理及实例分析(1)
庖丁解牛之UPack工作原理及实例分析(2)

如果您需要了解更多内容,可以
加入QQ群:486207500
直接询问:010-68438880-8669

发表评论