ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

《逆向工程核心原理》第18.19章——UPack PE文件头详细描述、UPack调试——查找EOP

2020-12-21 21:06:04  阅读:389  来源: 互联网

标签:文件 18.19 跳转 EOP PE IAT UPack 节区


UPack PE文件头详细描述


UPack说明

UPack是一种运行时压缩器,其特点是用一种非常独特的形式对PE头进行变形。

使用UPack压缩notepad.exe。先将两文件复制到合适的文件夹中:
在这里插入图片描述
进行压缩
在这里插入图片描述
Upack会压缩源文件本身且不会另外备份。压缩重要文件前一定要先备份。

比较PE文件头

下载Stud_PE工具:http://www.cgsoftlabs.ro/

重叠文件头

该方法把DOS头与NT头巧妙地重叠在一起:
在这里插入图片描述
查看DOS头,可以看到两个重要成员e_magic与e_lfanew。后者表示NT头偏移,此处为00000010。

由此可知DOS存根被省略NT头与DOS头进行了重叠

IMAGE_FILE_HEADER.SizeOfOptionalHeader

在这里插入图片描述
对比源文件中上述四项,发现可选头长度SizeOfOptionalHeader改变为148,它比正常值E0或F0更大一些,因此节区头从偏移170(28+148)开始。

可选头大小增加,也就意味着在可选头与节区头之间增加了额外的空间,UPack就是在这个区域添加解码代码。

IMAGE_OPTIONAL_HEADER.NumberOfRvaAndSizes

查看可选头
在这里插入图片描述
NumberOfRvaAndSizes由原本的10h变为0Ah,也就是说DATA_DIRECTORY的后六个元素被忽略(文件偏移D8后面)。在这之后的区域被添加了UPack解码代码。

使用调制器查看后面的汇编代码:
在这里插入图片描述
在这里插入图片描述

IMAGE_SECTION_HEADER

查看节区头,前面可知节区头从偏移170开始:
在这里插入图片描述
节区头中未框选部分对程序运行没有任何意义。

重叠节区

使用Stud_PE查看文件节区信息:
在这里插入图片描述
这里可以看到,第一节区和第三节区的文件偏移均为10,偏移10是PE头区域,但在UPack中该位置起就是节区部分。也就是说UPack对PE头、第一节区、第三节区进行重叠。
在这里插入图片描述
PE装载器会将文件偏移0~1FF的区域分别映射到3个不同的内存位置。

文件的头区域大小为200,而第二节区大小为AE28,占据了后面的全部区域。原文件notepad.exe即压缩于此。另外,第一节区区域内存尺寸为14000,与原文件的SizeOfImage(PE文件在内存中的大小)具有相同的值。也就是说,压缩在第二节区中的文件映像会被原样解压缩到第一节区。
在这里插入图片描述解压缩后第一节区:
在这里插入图片描述

RVA to RAW

各种PE实用程序对UPack束手无策的原因就是无法正确进行RVA→RAW的变换。
UPack文件的EP的RVA为1018。
在这里插入图片描述
若按以往的变换方法:RAW=1018-1000+10=28。
在这里插入图片描述
显然RAW 28处并不是代码区域,也就不可能是EP。

出现上面这样的问题就在于第一节区的PointerToRawData值10。一般而言,指向节区中的文件偏移应该是FileAlignment的整数倍,也就是0、200、400、600等值。PE装载器发现第一个节区的PointerToRawData值不是FileAlignment的整数倍时,会将其强制转化为整数倍(该情况下为0),使得UPack文件能够正常运行。
因此正确的RAW应该是1018-1000+10=18。
在这里插入图片描述

导入表(IMAGE_IMPORT_DESCRIPTOR array)

UPack的导入表结构也很独特。
在这里插入图片描述
RVA=000271EE,RAW=271EE-27000+0=1EE。
在这里插入图片描述
选中区域即为IMAGE_IMPORT_DESCRIPTOR结构体,导入表是由一系列该结构体组成的数组。
在这里插入图片描述
这里需要注意的是,偏移200上方的粗线表示文件中第三个节区的结束,因此运行时偏移200以后的部分不会被映射到第三节区内存。仅0~1FF被映射,后面用NULL填充。
查看调试器就可以看到:
在这里插入图片描述
因此这里其实被隔断了,最终只用了2字节表示了FirstThunk,并且后面也没有IMAGE_IMPORT_DESCRIPTOR结构体了。

导入地址表

由上可知INT=0,Name=2,FirstThunk(IAT)=11E8(均为RVA)。
在这里插入图片描述
可以看到Name为Kernel32.dll,接下来再看一看导入了哪些API函数。
一般跟踪INT,此处INT=0,跟踪IAT也有同样结果,IAT对应RAW=11E8-1000+0=1E8。
在这里插入图片描述
上图框选部分即为IAT域,也是INT,即Name Pointer数组。可以看到由两个API,查看对应位置:
在这里插入图片描述在这里插入图片描述
对应位置存在着导入API的[ordinary+名称字符串],也就是说分别为LoadLibraryA与GetProcAddress。(它们在形成原文件IAT时非常方便,所以普通压缩器也常常导入使用。)

INT与IAT到底有什么区别呢?

引用博客https://www.cnblogs.com/Rev-omi/p/13308430.html的解释:
程序加载前,IAT和INT指向同一结构,而加载后INT不变依旧保存dll函数名与函数序号的地址信息。而IAT则根据导入表INT(IAT加载前)的内容和库文件导出表信息,修改为对应的函数的地址信息。这也是为什么INT被称为OriginalFirstThunk的原因。
在这里插入图片描述


UPack调试——查找OEP

解码循环

UPack把压缩后的数据放在第二节区,再运行解码循环将这些数据解压缩后放到第一个节区。

接下来开始调试程序:
在这里插入图片描述
在这里插入图片描述
框选部分实现的操作是:将ESI(010270F0)所指的区域中的27h个Dword大小的数据移动到EDI(0101FE28)所指的地址中。

01020F0对应RAW=F0,即将F0~18C复制到对应区域,即将解码代码释放出来,以供后面执行:
在这里插入图片描述
而EDI=0101FE28,对应RAW=B028,恰好是PE文件末尾处。
在这里插入图片描述
复制完成后:
在这里插入图片描述
继续调试:
在这里插入图片描述
后面的指令将接下来的区域进行填充,一次填充一个FFFFFFFF,一个00000,4个01,1C00个400,一直填充到01026EDC处,接近第二节区结尾处。
REP STOS DWORD PTR ES:[EDI] 用EAX的值,填充EDI,填充ECX个DWORD。
在这里插入图片描述
接下来会进入一个循环:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

分析循环

在这里插入图片描述
首先这里反复调用的函数地址101FCCB即为decode()函数地址,查看函数:
在这里插入图片描述

这里注意在这个函数中edx的值始终不变,最开始指向001FEC4,即前面复制的结尾

当不发生跳转时:
在这里插入图片描述
当发生跳转时:
在这里插入图片描述
分支结束后:
在这里插入图片描述

[EBX] , [EBX-4] , [EBX+4]这三个变量分别命名为A,B,C
1.将A与[EDX]相乘然后除以1000H,放在EAX中。
2.[B]按字节逆序再减去C,放在EDX中。
3. EAX的值与EDX进行比较
(1)若大于则不跳转 :A = EAX,[EDX] += (800 - [EDX]) / 20H) , 通常[EDX] <= 400H 。
(2)若小于等于则跳转:C += EAX , B -= EAX , [EDX] -= [EDX] / 20H .然后置CF位为1
4.对比[EBX+3]字节是否为0,若为0,则 B++, C /= 3000H , A /= 3000H 。
执行返回。

函数返回后继续调试:
在这里插入图片描述
可以看到紧接着就是jb语句,这里CF=1跳转,CF=0顺序执行。

首先看不发生跳转的分支,对应上面函数的分支(1):CF=0。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述整个跳转过程如下图所示:

在这里插入图片描述
总而言之经过一系列变换,最终到达FE5D并重新跳转至FD13开始新的循环。

接下来看不发生跳转的分支,对应上面函数的分支(2):CF=1。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
同样的经过一些列变换,最终到达FE5D处并返回最初的循环。
在这里插入图片描述
两种加密方式最终都执行上图指令再返回,显然上面指令是将EAX的内容写入ES:[EDI]中。也就是说前面的命令先执行解压缩的操作,然后将结果写入实际内存。

在FE5D处下断点,F9调试,查看对应区域:
在这里插入图片描述
可以看到对应区域不断被填充。
在这里插入图片描述
最后可以看到:直到填充至0104B5A位置,接近整个内存区域的末尾(01014FFF)。

设置IAT

一般而言,压缩器执行完解码循环后会根据原文件重新组织IAT。(前面UPX也是这样:第二个循环解压缩,第三个循环恢复CALL/JMP指令,第四个循环恢复IAT),接下来分析IAT设置过程。
在这里插入图片描述
循环1:需要读取[edi]+18后结果为0X00或0X01才可以继续执行,也就是说要查找存储E9或E8的字节。至01001810第一次获得符合要求字节。
在这里插入图片描述
循环2:需要读取紧接着的一个字,且该字al为1。至01001829获得第一个符合循环1,2数据。
在这里插入图片描述
上述循环结束后,继续调试:
在这里插入图片描述
逐个读取名称字符,直到0结尾处:
在这里插入图片描述
获取紧跟着的API函数的地址:GetProcAddress()函数依次将获得到的函数地址写入EDI所指向的地址。
在这里插入图片描述
直到当前库中的API函数全部获取完成,此时会遇到两个0:
在这里插入图片描述
字符串结束后,下一个字符仍为0,说明当前库中API函数全部huo’q获取地址。跳转获取下一个库:
在这里插入图片描述最终执行结束,RETN返回,此时程序就到达了OEP:0100139D处。

书中还提到了设置硬件断点查找OEP的方法:
在这里插入图片描述
F9直接运行到OEP处,但这种方法的前提是事先知道该值时OEP。

标签:文件,18.19,跳转,EOP,PE,IAT,UPack,节区
来源: https://blog.csdn.net/diamond_biu/article/details/111420150

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有