PE变形技术
本章将研究 PE 文件的可塑性,通过对 PE 文件进行变形,看是否能通过操作系统的 PE 加载器。本章的目标是通过手工打造一些小的 PE 程序,以便探究 PE 文件结构与操作系统 PE 加载器之间的关系。
研究 PE 变形技术不局限于了解 PE 加载器加载 PE 的机制,还在于通过变形可以实现反调试、运行动持等。
变形技术的分类:
所谓变形是指通过改变链接器生成的 PE 文件内容,扩大或缩小文件尺寸,用以测试 PE 加载器的机制及健壮性。
本节主要讲述静态 PE 文件中的四种变形技术,它们依次是:
口 结构重叠技术
口 空间调整技术
口 数据转移技术
口 数据压缩技术
回顾要用上的字段:
首先附上要用上的 IMAGE_DOS_HEADER 和 IMAGE_NT_HEADERS 结构:
IMAGE_DOS_HEADER 结构:
IMAGE_NT_HEADERS 结构:
结构重叠技术:
结构重叠技术是指在不影响正常性能的前提下,将某些数据结构进行重登的技术。在缩小 PE 的变形中将大量使用这种技术。
以下是一个使用了结构重要的 PE 文件头部字节码:
该文件头部就是典型的 IMAGE_DOS_HEADER 和 IMAGE_NT_HEADERS 两个结构的重合 。可以看到,指向 PE 文件头部的字段依然是在偏移 3Ch 处。IMAGE_DOS_HEADER 的 40个字节一个不缺,所以,它是一个完整的 DOS MZ 头结构。由于两个结构并不是从一开始就重叠,所以在 IMAGE_DOS_HEADER 结构的 0ch 偏移处两个结构开始重叠。
首先分开来看,如果把这部分数据看成是 IMAGE_DOS_HEADER,各部分的值为:
从该位置处开始的 IMAGE_NT_HEADERS 结构各字段的值分别是:
从上面的分析可以看出,两个结构从以下字段开始发生重叠:
这里的意思是后面的字段也重叠,signature是双字大小,所以要对应两个 DOS 头的 word 字段。
为什么结构重叠了却没有发生加载错误呢? 得益于以下三点:
1) 被覆盖的数据可能是另一个结构中无用的数据。
2) 有用的数据可能只对一个结构起作用,但有时被覆盖的数据在两个结构中都有用。此种情况下发生的重叠必须保证重叠的字段在两个结构有中拥有相同值。
3) PE 加载器并不检测所有的字段。
重叠以后的两个数据结构关系见图 12-1:(重叠以后的数据明显变少了)
空间调整技术:
本小节以不固定大小的数据块 DOS STUB 为例介绍空间调整技术。具体思路是,通过调整字段IMAGE_DOS_HEADER.e_lfanew 的值,实现动态地扩充或缩小 DOS STUB 块空间,从而达到 PE 变形的目的。
该字段指向 PE 头位置,但是 PE 头紧连着 DOS,所以中间部分就是 stub 空间:
以第 6 章的免导入、免重定位的 HelloWord1_1.exe 作为蓝本,目标是将该 PE 文件扩充一个内存页大小:
使用 FlexHex 建立一个大小为 5120 字节(16进制1000h)的 HelloWorld1_10.exe 程序,并执行以下操作:
步骤1 修改IMAGE_DOS_HEADER.e_lfanew 的值,增加一个页面大小 1000h,由原来的 000000A8 更改为 00010A8:
步骤2 将HelloWorld1_1.exe 的PE标识符起始位置开始的所有非零数据全部复制到 000010A8 位置(采用覆盖方式):
步骤3 修改字段 IMAGE_OPTIONAL_HEADER32AddressOfEntryPoint 的值,由原来的 00001124 更改为 00002124:
步骤4 修改字段 IMAGE_OPTIONAL_HEADER32SizeOfImage的值,由原来的 00002000 更改为 00003000:
步骤5 修改字段 IMAGE_OPTIONAL_HEADER32SizeOfHeaders 的值,由原来的 00000200 更改为 00001200:
步骤6 修改字段 IMAGE_SECTION_HEADER.VirtualAddress 的值,由原来的 00001000 更改为 00002000:
步骤7 修改字段 IMAGE_SECTION_HEADER.PointerToRawData 的值,由原来的 00000200 更改为 00001200:
因为该文件没有重定位信息、没有导入表、没有数据段、没有数据目录项,且只有一个节,所以本测试中所有需要修改的参数都已列出。
运行 chapter12\HelloWorld1_10.exe,发现可以正常显示对话框:
在 OD 中查看内存分配,可以看到文件头部被扩充了一个页面大小,如下图所示:
在操作系统查看两个文件大小之差为 4096,十六进制刚好是 1000h,打造 HelloWorld1_10 的实验证明调整DOS_STUB 块空间是可行的。该实例演示了 PE 变形中的扩大技术。可以看到,HelloWorld.exe 在被加载到虚拟内存空间的 PE 文件头占用空间的大小,由原来的 00001000h 变成了 00002000h。
数据转移技术:
在编程过程中,出于某种考虑,经常会将 PE 中的一部分数据转移到另一个位置。比如,将程序中的变量存储到文件头部结构的某个字段中,将代码转移到头部结构的某个连续空间中等,这就是数据转移技术。该技术包括对变量的存储和代码的存储。
1. 变量存储
变量存储的例子节选自随书文件 chapterl2\HelloWorld7.exe 的 PE 头部,如下所示:
该示例将程序要显示的字符串变量移动到了文件头部的 IMAGE_DOS_HEADER 中,而且与数据结构 IMAGE_NT_HEADERS 的 PE 标识字段自动重合,重合的部分为:
该部分既可以认为是 IMAGE_NT_HEADERS.Signature,也可以认为是字符串 “HelloWorldPE\O\0” 的一部分。该部分变量原来的位置是在一个独立的节 “.data” 中,占据文件中的 200h 个字节 ; 通过这样的转移,使得 PE 产生变形,不仅节的内容没有了,节表中也少了一个描述该节信息的表项。
2. 代码存储:(没有压缩)
对代码的转储比较普遍,常见的有 :
PE 压缩、病毒、加密与解密等。文件头部的连续空间被认为是存储代码的好地方,如果连续空间的长度无法容纳所有的代码,则可以将代码分解。
例如,看 OD 对第 1 章中 HelloWorld.exe 的反汇编代码:
指令字节码总长度为 36 字节。变形空间中能够容纳这些代码的有两处 : 一个是 IMAGE_DOS_HEADER,另一个是数据目录表。
假设以上两处没有空间能存放这些代码,我们也可以将这些代码分开来存储,但分开存储时必须要保证调用指令之间的先后顺序。
下面是对 HelloWorld.exe 反汇编代码的连续指令长度的一个统计:
口长度为 6 字节的指令有 3 个
口长度为 5 字节的指令有 2 个
口长度为 2 字节的指令有 4 个
口长度为 1 字节的指令有 1 个
现在对比变形空间中描述的可用连续字段,将这些指令分别存储在不同的空间位置:
第一种用固定字段可用空间来替换:(比较复杂,要掌握可用的空间及其适应的大小以及手动计算跳转空间)
可以用字段扩展 PE 头中的 BaseOfCode 开始的 8 字节存储 6A 00 6A 00 指令,另加一条近跳转指令 EB 00,剩余的2个字节用 00 00填充。
用扩展 PE 头中的 MajorOperatingSystemVersion 字段开始的 8 个字节存储 68 00304000 指令,另外加一条近跳转指令 EB 00,剩余的一个字节用 00 填充。
然后根据两部分的距离修正跳转指令中的操作数如下:
如上所示,从字段 BaseOfCode 到字段 MajorOperatingSystemVersion,中间隔了四个双字的字段,所以第一条近跳转指令中的操作数为 4*3+2=0Eh;, 另一个操作数则要根据下一条指令所在字段的位置进行计算。
这样,原来的指令:
就变成了现在的指令:
第二种方法:直接在代码块中通过程序编码,就是在代码中实现,机器编译是会自动处理:
步骤1:未修正前的指令字节码,以下内容节选自 HelloWorld.exe 的代码段;
步骤2:修正以后的程序,通过程序将原始指令字节码分解为多个小块代码:
行 26 使用伪指令语句 db 定义了第一块代码 (行22~25) 到第二块代码 (行28一32)之间的间隔(以字节记)。
步骤3 修正后的代码:(下面是加入了补足数据的字节码)
与该字节码对应的汇编代码如下:
代码清单 12-1 的行 25 使用了跳转语句(翻译为指令字节码是EB)。在程序源代码中,开发者只需要简单地使用标号来表明跳转指令要跳转到的位置,以及跳转指令后的操作数,即可由编译程序自动生成。从以上反汇编代码中可以看到,EB 指令后的操作数为 08,这 8 个字节是代码清单 12-1 的行 26 定义的 8 个 0AAh。通过这种简单的方法就可以让编译器帮助我们计算跳转指令的操作数了。
数据压缩技术:
在编程的过程中,如果指令代码比较长,还可以先对主要逻辑代码实施压缩,然后在 PE 头部找一块比较大的连续区域存放解压缩用的代码。程序被 PE 加载器加载后,文件头就基本不再使用了。这时可以将存储的压缩代码通过 PE 头部的解压缩程序进行解压,解压后即可通过跳转指令实施程序指令的转移。
由于压缩以后的代码不便于通过十六进制直观地看到,所以这种方法在一些病毒程序代码中比较常见,另外,在一些加壳程序中会经常看到数据压缩技术。
先来看一个这种技术的应用,以下是某病毒代码的头部信息:
该病毒对两个标识字段并没有进行大的改动。注意观察节表部分内容,按照基础知识中所介绍的,每个节表最少应该有 40 个字节,在这里明显大小并不符合。经过仔细分析之后才知道,病毒程序对这部分数据进行了加密处理。下面详细分析病毒是如何加密该部分数据的。
>>50 45 00 02 4C 01
根据前面所学的知识,PE 头部应该有两个“\0”,在这里只是用了 00-02 来表示这两个“\0”,看起来好像使用了简单的行程压缩算法。凡是有连续“\0”的地方都将 0 的个数作为紧跟在“\0”后面的一项。
来看节 SCODE 的内容,如下所示:
根据以上的猜测来还原该节的实际内容如下(符合猜想):
再仔细分析一下,可以看到,该病毒的作者只对文件头部进行了加密,其他部分还是没有更改的。也就是说,要想恢复这个 PE 文件的内容,只需要对头部进行处理即可。
知道原理之后,接下来的解密工作就容易多了,代码清单 12-2 是解密的源代码:
程序首先打开两个文件,一个是待解压的文件,用来读 ,另一个是解压后的文件,用来写。解压缩的代码在行 56 一 84。行 61 取出一个字节,然后判断是否为 0,如果是则跳转到标号 @@1 处执行,否则将字节原样写人目标缓冲区 szBuffer1。
如果取到的是 0,则再取一个字节,该字节记录了 0 的个数。将该值赋给 cl 寄存器,使用语句 rep stosb 将指定个数的 0 存入目标缓冲区 ,然后,调整循环次数,并跳转到标号 @@0 处继续执行下一个循环。
最后,将目标缓冲区中已经解压的字节写和人目标文件(行 86)。
变形技术可用的空间:
要想对 PE 进行变形,需要掌握 PE 文件中每个位置的数据的可替换特性,即该位置数据是否可以被替换为别的值,某段数据是否可以被其他用途利用等。总体上讲,PE 中可以用作变形的空间有以下四类。
文件头部未用的字段:
通过基础知识部分的学习我们知道,在 PE 文件头部有许多字段的值可以被修改和利用。也就是说,出于兼容上的考虑,PE 头部的数据结构中为将来预留了很多的字段,这些字段现在有的被强制设置为 0,有的则未加任何限制。
这些可以被替换的数据见表 12-1 (以下结论是笔者测试得出的,并不保证能适应所有的场合):
由于文件头中大部分字段不是连续的,受不同 PE 内容的影响很大。比如,上表并没有列出数据目录表中加载配置和延迟导入表项的空间。如果一个 PE 中不存在以上特性,则这些空间中的 [x].size 域就是可用的,不连续的空间通常的用途是存放数据。
对于连续的但字节数不多的空间,则可以存放人代码:(以下常用的指令其字节码本身就不大)
相对较大的连续空间则可以存放一段较长的指令字节码。这些空间主要包括 :
IMAGE_DOS_HEADER 中的 54 个字节、标准头 12 个字节、扩展头 14 个字节、数据目录 52 个字节、每个节表项中的 20 个字节。
大小不固定的数据块:
通过对一些大小不固定的数据块进行扩展,也可以获取足够的空间。
这些大小不固定的数据块包括:
(1) DOS STUB:
由于DOS STUB 是为 16 位系统保留的,其中的任何一个字节都可以填充为任意值。
(2) PE 扩展头 IMAGE_OPTIONAL_HEADER32:
在 IMAGE_FILE_HEADER中有一个字段记录了 PE 扩展头的长度。 该字段为: SizeOfOptionalHeader。注意,这个字段为 DW 类型,最多能扩展一个字的空间。
(3) 数据目录项:
数据目录表的项数由字段 IMAGE_OPTIONAL_HEADER32.NumberOfRvaAndSizes 来定义,通过修改该值也可以扩充或缩小文件头的尺寸。
(4) 节表:
在节表数据结构中有一个值 SizeOfRawData,表示节在文件对齐后的尺寸。修改这个值也可以起到扩充节大小的作用。
注意:若修改了一个节的大小,其他节在文件的起始地址都要跟着修改。
理论上讲,只要是大小不固定的块,都有被扩展的可能。关键的问题是,PE 加载器的机制是否允许修改。如果可执行文件不存在输出表,那么当 PE 加载器将其加载到内存以后,PE 的文件头部分数据就已经是无用的了。这也就意味着,PE 文件头相关数据结构中的所有的字段,在运行期均是可随意填充任何值的。唯一遗憾的是 PE 加载器在加载完 PE 以后,把该段内存设置成了只读的 R 属性。
因对齐产生的补足空间:
操作系统对 PE 文件的强制对齐特性,使得PE 文件的节中存有大量为对齐而补足的 0。这种机制同样影响到文件头部。由于默认对齐尺寸为 200h 大小的限制,大部分的系统文件(如记事本、kernel32.dll 等)的文件头部只剩下很少的空间 。
PE 文件变形原则:
前面对 PE 变形时与字段有关的空间进行了简单的分析,本节重点研究变形时要遵循的一些原则。在对 PE 文件进行变形时,改动 PE 数据结构中某些字段的值需要遵循一些原则,如果没有原则地随意变形,将会导致生成的目标 PE 文件无法被操作系统识别并加载。
关于数据目录表:
数据目录表的个数必须大于等于2。如果 PE 文件的最后一个字节位于目录表之间,如介于第 3 项资源表定义之间,即 [DD[2].VirtualAddress]< 文件总长度<[DD[2].isize],则文件中无法定义资源表的大小,PE 加载器默认资源表的大小为 0。
一个完整的数据目录表在普通的 PE 文件中可读写的字段如下:
以下截取了测试用的 PE 文件的数据目录表。从测试看,连续 AA 的部分可以是任意值。
关于节表:
PE 文件头中可以没有节的定义,但必须将文件头部的字段 IMAGE_FILE_HEADER.NumberOfSections 设置为 1。
关于导入表:
导入表是 PE 的核心。要想在已有的 PE 中静态引入动态链接库的函数,必须通过变形技术构造一个合理的导入表(这里的“合理”指的是结构上的合理),或者重构已有导人表。
在 12.5.7 小节将看到一个只有 133 字节的 PE 文件。在该文件中,PE 头部的数据结构中的字段能减的都减了,能重叠的也都重叠了,但即使是在这么短的 PE 中,导入表的双桥结构还是存在的。
导入表的 IMAGE_IMPORIT_DESCRIPTOR (对应动态链接库的导入表描述符)结构是顺序排列的。前面我们讲过 ”指向的数组最后以一个内容全 0 的结构作为结束"。其实,这个条件可以宽限到只判断 IMAGE_IMPORT_DESCRIPTOR.Name1 是否为0即可(20字节的导入表描述符中该字段是指向链接库名字的指针)。如果一个PE 文件的结尾刚好没有空间存储该字段对应的值,则系统会默认该字段是存在的,并且其值为 0。
关于程序数据:
数据可以存储在内存中的任何位置,可以位于文件头,也可以位于其他节中。
代码和数据一样,可以在内存的任何位置,但所在的节(无论是指定的节还是文件头部),其节的属性必须可读、可写、可执行。将代码段设置为可写属性主要是考虑到某些程序会将一些变量存储在代码段,且在程序中有为该变量赋值的代码。
如果所有的数据(程序变量、导入表、IATI 等)都在文件头部,也就是说加载进内存的 PE 文件只有文件头存在,假设其大小为一个页面 1000h,那么操作系统会因为 IAT 的缘故自动将该页面设置为 ERW,即可读、可写、可执行(这和以前我们看到的文件头部只读是不一样的)。
关于对齐:
节的对齐尺寸必须大于或等于文件的对齐尺寸。由于文件的对齐尺寸被定义为 2 的 N 次幂,所以通常会将文件对齐尺寸设置得更小,并使两个的值相等,以达到缩小 PE 文件的目的。
如后面展示的两个代码例子中,文件对齐粒度用了 10h,内存对齐粒度用了 4h,即:
Sectionalignment=Filenlignment=10h ;10h是16,4h是4
Sectionhlignment=FileAlignment=4h
几个关注的字段:
每当修改了程序的尺寸后,程序中相关的字节码的位置、字节码的长度会发生或多或少的变化,这种变化势必会影响一些记录这些位置和大小的字段。
表 12-2 所列字段是在变形时必须要关注、指定或修改的:
表 12-3 所列是在变形时可当做固定标志的(即对大多数 EXE 文件来说经常不变的) 字段:
表 12-3 中带有中括号 [] 的表达式表示由该地址处取出的值作为定位对应字段的偏移。
将 PE 变小的实例 HelloWorldPE:
本节要分析的源程序与第 1 章的 HelloWorld.asm 有一点区别,即将字符串定义为 “HelloWorldPE”。为了能与最终手工打造修改后的 PE 程序有所区别,这里将源代码及最终生成的 PE 的字节码分别列出来,即在源代码生成的 PE 的文件码上手工改造。
源程序 HelloWorld 的字节码分类 (2560 字节):
要手工打造的源代码见代码清单 12-3 :
源代码比较简单,程序实现了弹出窗口的功能,弹出的窗口中显示字符为 “HelloWorldPE ”。
将HelloWorld.exe的字节码按照类别分为以下四部分,文件头部、代码段、导入表和数据段:
(1) 文件头部 = 文件头 + 节表 + 补齐(大小400h)
(2)代码段 = 代码 + 补齐(大小200h)
(3)导入表 = 导人表及相关结构 + 补齐(大小200h)
(4)数据段 = 数据 + 补齐(大小200h)
目标 PE 文件的字节码(432 字节)
最终打造的目标 PE 见随书文件 chapter12\HelloWorld_7.exe,其所有的字节码长度为 432 字节,可以在 Windows XP SP3 环境运行。(由于我没有 Windows XP SP3环境,所以我运行和分析不了)
在 OD 中调试时其内存空间分配如图 12-3 所示:
字节码中存在连续 AA 字节的部分都是可以再次利用的空间:
开始打造目标 PE:
上面那一节为我们展示了打造前后 PE 文件的字节码对比,通过对比可以发现,打造后的目标 PE 文件尽管变得更小,却依然具备打造前的 PE 的所有功能,本节将详细介绍此次打造的全过程。
对文件头的处理:
根据前面介绍的结构覆盖技术和数据转移技术压缩文件头,主要操作包括:把 NT 头提前,覆盖 DOS 头部分,只保留最重要的 e_lfanew 字段。因为数据段的起始地址 BaseOfData 是一个可以修改的字段,所以让 BaseOfData 刚好落在
e_lfanew 这里,然后将这一部分更改为指向 PE 头的 0ch。
如下所示:
将 IMAGE_NT_HEADERS 提到前面来并不影响程序的运行。除了 BaseOfData 字段需要改成指向 PE 头的指针外,其他都无需改动。(BaseOfData 是数据节装载的起始 RVA)
从偏移 02h 开始一直到 0Ch 的数据没有什么用处。于是把数据段中的数据放到了这里。不幸的是,原来要显示的字符串 “HelloWorldPE” 长度好像超出了这个范围, 幸运的是,字符串里的 “PE” 刚好和 PE 文件的标志重叠了。
进行修改如下所示:
删除节 .data 的内容(因为前面修改的BaseOfData字段是指向数据节装载的起始 RVA),即从 800h 处开始的内容全部删除,然后将 .rdata 节表后的文件头数据的所有内容清零,将节数量从原来的 3 更改为 2。
对代码段的处理:
首先来看 HelloWorld.exe 代码段字节码反汇编的结果。
1. 程序代码段反汇编代码:
使用 OD 打开 HelloWorld.exe,将以下代码的字节码整理出来,然后将这些字节码移动到数据目录表中。
2. 将代码蓄入数据目录表:
将代码移动到 PE 文件头部的数据目录表中,见加黑部分。在覆盖时需要注意不要将有用的部分覆盖。
以上显示的字节码中,短跳转代码指令 E8 中涉及的偏移部分已经做了修改。由于独立代码部分长度刚好填完数据目录表项 03、04 和 05(从 00 开始),免去了按照较短的空闲长度重新构造代码的麻烦。
对导入表的处理:
首先附上导入表的数据目录结构:
导入表描述符 IMAGE_ IMPORT_DESCRIPTOR 结构:
导入表的双桥结构:
IMAGE_THUNK_DATA字段:
IMAGE_IMPORT_BY_NAME字段:
按照第 4 章介绍的导入表重组的方法,将导入表更改为如下字节码:
可以看到,从 0130h~013Fh 的 16 个字节为 IAT 的内容(从 0140h 开始部分即为导入表结构数组),与之相关的由字段 originalFirstThunk 指向的数据结构则放到了导入表的最后一个全 0 的 IMAGE_IMPORT_DESCRIPTOR 结构中。
因为前面说过,只要保证该结构的 name1(深色字体部分)字段 为 0,即可满足导入表结构数组以全 0 结束的条件,所以最后一个结构除了 Name1 的部分外都可以填充利用。
对部分字段值的修正:
相关数据基本安排就绪,接下来的工作就是修正文件头部因数据迁移而导致的字段的值的变更。
1. 定义节 .HelloPE:
由于 .HelloPE 段中存放了和常量、数据和代码,所以该段必须可读、可写、可执行。
口 节的名字:自定义
口 字符串为:.HelloPE
口 节区的实际尺寸:01b0h
口 节区起始 RVA:从头开始,即 0000h
口 文件对齐后的长度:01b0h
口 节位于文件的偏移:从头开始,即 0000h
注意:节区的实际尺寸可以在 0800h 范围内随意更改,不受任何影响,这里选择文件长度 0lb0h。
首先附上节表项的数据结构:
.HelloPE 节表项结构的相关数据如下:
节的数量在 IMAGE_FILE_HEADER 结构中的 NumberOfSections 字段中。固定的数据节和代码节在IMAGE_OPTIONAL_HEADER32 中定义,自定义节的信息在 IMAGE_SECTION_HEADER 中定义。
2. 基地址、执行入口和代码段大小:(这里都在 PE 头 IMAGE_NT_HEADER 结构字段中定义)
首先附上 IMAGE_NT_HEADER 的数据结构:
装入的基地址不变,依然是 00400000h;而执行入口则更改为 009Ch(这里定义成 9Ch 是因为前面对代码段的处理就嵌入在数据目录表中),即文件偏移 009Ch 处。
由于可执行文件很小(小于 200h),所以这里的文件偏移地址即为 RVA,无需转换。代码段大小即整个文件的大小 000001b0h(这里看成 1AF 更好,这里是刚好到 1B0 而已)。
相关数据如下:
4. 文件头大小与 PE 内存映像大小
所有头 + 节表的大小为 00000130h,而 PE 在内存中的映像大小为 00001000h,相关数据如下:
5. 数据目录表中导入表字段
导入表的起始 RVA=00000140h,长度为 3Ch。130h 是 IAT 导入函数地址表数据,140h 是导入表的数据。
修改后的文件结构:
手动修改以后的 PE 文件结构如图 12-4 所示:
源 PE 中数据段的数据存储在目标 PE 的 DOS MZ 头和 PE 标识之间,源 PE 的程序代码存储在目标 PE 的数据目录表中 ; 文件头部定义了一个节表项,导入表和 IAT 表安排在目标 PE 的尾部。
修改后的文件分析:
接下来将使用工具 PEInfo 和 PEComp 分别对比两个文件,得到的结果如下 。
1. PEInfo 运行结果对比
与源 PE 相比,目标 PE 中的节少了,但导入表还是很完整的。模块的基地址没有发生变化,程序代码由于搬迁到数据目录表中,所以人口地址发生了变化。
使用 PEComp 工具对比结果:
从图中可以看出,源 PE 与目标 PE 文件头部不相同的地方很多。造成这种结果的最主要的原因是在手工打造时使用了数据转移技术。
目标文件更小的实例分析:(133字节)
下面看一个能显示指定信息对话框的更小的 PE 文件 miniPE 程序,其大小总共为 133 字节。
1.该文件的字节码如下:
2.源程序
为了去除微软编译器的提示错误,避免在链接时追加任何其他内容,以及汇编指令调用时对 invoke 指令的分解,这次使用了 Borland 公司的 Tasm 和 Tlink 作为这个源文件的编译器和链接器。有具体方法可以参照源文件头部的注释。
从代码的注释可以看出,该 PE 使用的数据结构包括:
口 IMAGE_DOS_HEADER
口 IMAGE_FILE_HEADER
口 IMAGE_OPTIONAL_HEADER32
口 IMAGE_IMPORT_DESCRIPTOR [0]
口 IMAGE_IMPORT_DESCRIPTOR [1].VirtualAddress
其中数据目录只用了两个,且最后一个还没有用全,因为节表在程序里没有定义。对该代码的详细分析如下:
如图所示,为了便于分析,源程序中每行被按照功能划分为 6 列,它们依次是:
第 1 列 标号,用于标识源程序中的一些特殊位置。
第 2 列 指令,即汇编源代码。
第 3 列 结构字段名,定义此处的结构和字段。
第 4 列 字段的值,为每个字段赋值。
第 5 列 用分号做的和注释,标注该行的含义。
第 6 列 对应的字节码。
小结:
本章介绍了 PE 的变形技术。所谓变形就是通过技术手段使 PE 文件的大小发生变化,或缩小或扩大 : 无论怎么变,都能保证 PE 文件能被 Windows PE 加载器加载,且能正常运行。
本章首先介绍了四种变形技术、PE 数据结构和了PE 文件中可以被二次利用的空间,以及变形时需要遵循的原则 : 最后,通过对 HelloWorldPE 的变形过程进行分析,帮助我们全面理解和把握 PE 数据结构中相关字段的作用。