WinUpack壳浅析:
1.为程序加WinUpack壳:
tips:WinUPack会直接压缩源文件本身,且不会另外备份。因此,压缩重要文件前一定要先备份。

2.加壳前后程序对比:
使用EXEinfo查看加壳后的程序:

加壳前后PE头对比:

左图为未加壳时的文件元数据,可以清晰的看到文件的结构,右图为加壳后的元数据,DOS的魔术和PE的魔术挨得很近,没有看到DOS存根,并且出现大量的字符串。
分析WinUPack的PE文件头:
1.重叠文件头:
WinUpack壳将MZ文件头与NT头巧妙的重叠在了一起,实现方法为:
实际上对于程序运行来说MZ文件头也就只有
e_magic
和e_lfanew
有用。定位NT头(
IMAGE_NT_HEADERS
)的成员是MZ文件头(IMAGE_DOS_HEADER
)的最后一个成员e_lfanew
,通常我们所见到的NT头基本都是接在DOS存根的后面,但是跳出这个惯性思维,既然NT头起始位置由e_lfanew
决定那它本事其实就是可变的。
查看加壳后的程序,
e_lfanew
的值为0x10,指向的也就是第二行处的NT头。2.数据目录表(IMAGE_DATA_DIRECTORY
):
在Upack中IMAGE_DATA_DIRECTORY的个数从16个变成了10个。
可选头结构体中倒数第二个成员
NumberOfRvaAndSizes
,用于记录数据目录表的个数:
NumberOfRvaAndSizes

NumberOfRvaAndSizes
在WinUpack中
IMAGE_DATA_DIRECTORY
数组的后6个元素都被忽略了3.文件IMAGE_OPTIONAL_HEADER.SizeOfOptionalHeader
:
SizeOfOptionalHeader
成员的作用:表示的是可选头的大小(SizeOfOptionalHeader32
的大小是E0h
,SizeOfOptionalHeader64
的大小F0h
)。WinUpack壳通过修改该成员值,向文件头插入解码代码。设计该成员的意义:
- 32位的可选头和64位的可选头大小是不一样的32位的可选头大小为E0,64位可选头大小为F0
- 确定节区头(
IMAGE_SECTION_HEADER
)的起始偏移。
WinUpack通过增大可选头的大小在数据目录表后面添加加密代码,下面尝试找到加密代码的位置:
查看实例程序的
SizeOfOptionalHeader
,NT头的起始位置为0x10,SizeOfOptionalHeader
成员的相对偏移为0x14:

大小为:0x148。程序的可选头起始位置为0x28,所以节区表的起始位置为0x170
接着计算数据目录的结尾值:加壳后的NT头的起始位置为0x10,加上数据目录表之前的成员(0x78),加上10个数据目录表(8*A=0x50),所以
IMAGE_SECTION_HEADER
的起始位置为0xD8。所以被插入的代码所在区域为0xD8~0x16F。

将这些内容Dump出来,放入IDA可以看到加密代码:


4.IMAGE_SECTION_HEADER
:
在节区表的结构体中,WinUpack会把自身数据记录到程序运行不需要的项目,思路和上面一样。先回顾一下
IMAGE_SECTION_HEADER
结构体内容:在该结构体中左边四个连续的成员大概都是一些辅助调试的信息,于程序的运行并没有意义。
使用Stud_PE查看加壳后的程序:

这里注意第一个与第三个节区的文件内存大小与起始偏移的值是相同的这就表示这两个节区是重叠的
根据上图偏移该程序的内存映射关系如左图,在文件中整个文件头和第一节区第三节区重叠在了一起共占用0x200字节,而第二个节区占用了0xAE28字节。

需要注意的是内存中的第一个节区区域,它的内存尺寸为0x14000,与原文件(notepad.exe)的Size of Image具有相同的值。也就是说,压缩在第二个节区中的文件映像会被原样解压缩到第一个节区(notepad的内存映像)。另外,原notepad.exe拥有3个节区,它们被解压到一个节区(解压到了第二个节区中)。
解压缩后第一个节区为:

总结:
压缩的notepad在内存的第二个节区,解压缩的同时被记录到第一个节区。重要的是,notepad.exe(原文件)的内存映像会被整体解压,所以程序能够正常运行(地址变得准确而一致)。
5.RVA to RAW:
首先复习一下RVA→RAW的常规方法:
按照这个思路尝试找到程序的EP代码:
- RVA:记录程序入口的RVA是文件可选头的AddressOfEntryPoint成员

该值为0x1018
- VirtualAddress、PointerToRawData:
这两个值都记录在节区头中

计算RAW:
接着我们用010Editer查看该地址的内容:

可以发现这并不是EP代码。
分析出错原因:
一般而言,指向节区开始的文件偏移的
PointerToRawData
值应该是FileAlignment
的整数倍。 UPack的FileAlignment为200
故PointerToRawData值应为0、200、400、600等值。PE装载器发现第一个节区的
PointerToRawData(10)
不是FileAlignment(200)的整数倍时,它会强制将其识别为整数倍(该情况下为0)。这使UPck文件能够正常运行,但是许多PE相关实用程序都会发生错误。所以计算RAW的公式应该为:

6.导入表IMAGE_IMPORT_DESCRIPTOR
:
导入表结构体总共八个字节:

前四个字节为导入表的地址RVA
271EE
,后面四个字节为导入表的大小0x14
接着计算其RAW:
RVA的值为
271EE
,其所在的节区为第三个节区:
IMAGE_IMPORT_DESCRIPTOP
结构体:(结构体大小为20字节)
根据PE规范,导人表是由一系列MAGE IMPORT_DESCRIPTOR结构体组成的数组,最后以一个内容为NULL的结构体结束。
所选区域就是导入表的内容偏移1EE~ 201为第一个结构体,其后既不是第二个结构体,也不是(表示导入表结束的)NULL结构体。乍一看这种做法分明是违反PE规范的。但是注意第三个节区的偏移200上方的粗线。该线条表示文件中第三个节区的结束。

故运行时偏移在200以下的部分不会映射到第三个节区内存。
