UPack PE文件分析与调试
Last Update:
参考文章:https://blog.csdn.net/learn112/article/details/112029389
您可以将本篇文章当作该参考文章的拓展版、翻译版、压缩版,总之算是结合之后自己上手的过程记录。若您发现文章中出现错误,请务必指正。
封面插图id:81508349
范例:notepad_upack.exe
准备工具:010Editor、Stud_PE
UPack:个人对其理解为一种压缩方法。将文件经过一定的算法编码压缩,在启动被压缩文件时将会按照逆过程解码。而其中比较经典的是其对PE文件头的压缩。我个人对这个过程的理解就是——将PE文件头原有的为可读性设计的格式打乱,在那些本不被用到的地方填补上需要用到的数据,最后将导入表等需要记录地址的数据改为那些本来不被使用的段落,以此减少空间浪费,但文件头不再拥有设计好的模板,最后阅读的时候会显得东拼西凑(指那些virtualaddress到处指,但是我觉得就算没压缩,看的时候还是感觉很乱就是了)……
上面两图分别为经过UPack压缩和未经过压缩的notepad.exe文件。在010的模板中可以明显看出其区别,至少010已经没办法识别出Section和SectionHeaders(实际上也识别出了节区头,但这个识别结果是错误的,在Upack压缩的PE模板里,NtHeader以下部分都会出现错误。)
而上图为Stud_PE分析出的区段结果,也有着明显的差异。(但既然分析的是压缩后的文件,所以还是以后图为准。但目前编者还不会直接通过阅读16进制文件来推算偏移、大小等(upack压缩后的被打乱了位置,没了模板就读不来的废物),所以目前还需要用到该工具)
以及还有一些奇怪的地方,在压缩后的文件中,第一和第三节区的实际偏移相同,实际大小相同,实际上是UPack压缩后产生的重叠节区。
(最后映射到内存中时,第一和第三节区会分别映射到不同的位置——1001000、1015000、1027000三处)
回到正题,首先观察下图。4D5A为签名,然后紧跟着就是KERNEL32.DLL,这个名字显然就是动态链接库的名称了。对比未压缩的文件,这个区域本来是无用的区域,所以用其他有用的东西填进去以弥补了空间浪费。
另外一个需要注意的是,AddressOfNewExeHeader的数值被改为了10,这个数值在本来的文件中为E0。
DOS存根直接消失了,在模板中点开该栏后什么也没有。
以及Nt头中SizeOfOptionalHeade由E0增加到了148。
但我们实际打开可选头的模板,010显示其大小为B0,并且NumberOfSections被降到了0A,少掉了6个数组(如图中蓝色区域为现存的表,而蓝色以下的紫区为被忽略的表,正好有6个被忽略了)。
(注:在实际中,16张表的数量其实是固定的,但有可能我们还需要用到更多的数据,这16张可能不太够,所以往往还需要另外输入NumberOfRvaAndSize的大小来规定该结构体内容的量)。
并且可以注意到,可选头从28开始,大小为148,但其结束点却只到D7,而不是170。
于是这些被扩增的区域实际上存放了UPack的解码代码(如图蓝色部分,但010的识别多了一行,还是忽视的比较好)
(反调试器中的该段位置对应的汇编代码)(ImageBase[1000000]+VisualOffset[1000]+D8=10010D8)
接下来尝试计算文件实际的EP。
AddressOfEntryPoitn为1018,VisualAddress为1000,而PointerToRawData在010中已经找不到了,从节区头开始,模板都是错误的,而该数值就在节区头中。
猜了一下其位置,大致在这个蓝色加深的位置,但实际上手去找还是不太行。现在姑且当其为10。那么计算结果应为1018-1000+10=28
跟入之后发现并不是动态链接库的名称。该盲区出自于这个PointerToRawData的数值和FileAliganment不成倍数(指其不为0/200/400/600/……)
(此处参考:http://blog.sina.com.cn/s/blog_1511e79950102xcws.html 之所以要有这种倍数关系,还是因为PE文件的对齐规范)
所以最后应把其当作0开始一个个试错,本例中1018-1000+0=18就已经得到答案了。
(但这里遇到了一些奇怪的问题。不论在x32dbg还是ollydbg中,只要移动光标后,1001018处地址就会消失,被1001017取代,并且再也无法找回)
(不过我的Ollydbg在打开文件的时候就会自动加载到该位置,所以该问题暂时还无需顾虑……)
计算导入表:
VirtualAddress为271EE,对应第三区段,实际偏移RVA为271EE-27000=1EE
(IMAGE_IMPORT_DESCRIPTOR结构体大小为6个DWORD类型数据,对应蓝色区域)
跟入02位置,即可见到刚才所说的kernel32.dll的名称。
(注:“该结构体之后既不是第二个结构体,也不是NULL结构体。实际上到从1EE~200便是第三节区的结束。运行时偏移在200以下的部分不会映射到第三个节区内存。”)
(01FF[第三节区]————27000271FF,而27200~28000则全由NULL填充)
以及11E8为IAT,换算后得到11E8-1000+0(同上计算盲区一样)=1E8(下图即为转入后数据。对应IAT域,也作为INT使用,也用NULL结束)
调试:
在图示附近存在存在一个大循环,观察堆栈信息猜测其为程序的解码过程。
Ctrl+F7自动步进调试,最终卡在该处。将数据循环写入ESI当前位置,判断其真的是一个解码过程。至此完成调试。