天天看点

WINDOWS+PE权威指南读书笔记(3)

目录

32 位系统下的 PE 结构:

PE文件结构及各部分解析:

32 位系统下的 PE 结构:

DOS MZ 头中有一个字段非常重要,即 IMAGE DOS_HEADER.e lfanew,没有它操作系统就定位不到标准的 PE 头部,可执行程序也就会被操作系统认为是非法的 PE 映像。

1. 定位标准PE头:

由于 DOS Stub 的长度不国定,导致了DOS 头也不是一个固定大小的数据结构。所以定位PE头要用到字段e_lfanew ,该字段的值是一个相对偏移量,绝对定位时需要加上 DOS MZ 头的基地址。

通过以下公式可以得出 PE 头的绝对位置:

PE_start=DOS MZ 基地址+IMAGE_DOS_HEADER.e_lfanew

=13B7:0100+000000B0H

=13B7:01B0

2. PE 文件结构:

32 位系统下的 PE 结构可以划分如图所示:

WINDOWS+PE权威指南读书笔记(3)

每个节的描述信息则是个固定值,共 40 个字节,节表是由不确定数量的节描述信息组成的,其大小等于节的数量 X40,节的数量由字段 IMAGE_ FILE_HEADER.NumberOfSections 来定义。DOS Stub 和节内容都是大小不确定的。

节表是 PE 中所有节的目录,每个目录都是一个“BookStore”,其字节码就是节内容。它按照目录里的指针指向的地址,分别将节的字节码在文件空间中排列起来,从而组成了一个完整的 PE 文件。PE 文件头部等于DOS 头 +PE 头。

PE文件结构及各部分解析:

WINDOWS+PE权威指南读书笔记(3)

如图所示,一个标准的 PE 文件一般由四大部分组成;

口DOS 头

口PE 头 (IMAGE_NT_HEADERS)

口节表〈多个 IMAGE_ SECTION HEADER 结构)

口 节内容

其中,PE 头的数据结构最为复杂。简单来说,PE 头包含:

口 4 个字节的标识符号 (Signature)

口 20 个字节的基本头信息 (IMAGE_FILE_HEADER)

口216 个字节的扩展头信息 (IMAGE OPTIONAL_ HEADER32)

说明 如果按照“头部+身体”的信息组织方式来看:

PE 文件头部 =DOS 头 +PE 头+节表

PE 文件身体=节内容

PE 文件头部数据结构解析:

DOS头:

WINDOWS+PE权威指南读书笔记(3)

DOS MZ 头IMAGE_DOS_HEADER:

WINDOWS+PE权威指南读书笔记(3)

PE头:

WINDOWS+PE权威指南读书笔记(3)

PE 头标识 Signature:

紧跟在 DOS Stub 后面的是 PE 头标识 Signature。与大部分文件格式的头部结构一样,PE头部信息中有一个四字节的标识,该标识位于指针 IMAGE_DOS_HEADER.e_lfanew 指向的位置。其内容固定,对应于 ASCII 码的字符串“PE\0\0 ”。

标准 PE 头IMAGE_FILE_HEADER:

标准PE头IMAGE_FILE_HEADER紧跟在PE头标识后,即位于IMAGE_DOS_HEADER 的e_lfanew值+4的位置。由此位置开始的20个字节为数据结构标准PE 头IMAGE_FILE_HEADER 的内容。

该结构在微软的官方文档中被称为标准通用对象文件格式(Common Object File Format,COFF) 头。它记录了 PE 文件的全局属性,如该 PE 文件运行的平台、PE 文件类型(是 EXE 文件还是 DLL 文件)、文件中存在的节的总数等。

其详细定义如下:

WINDOWS+PE权威指南读书笔记(3)

该结构常用于判断 PE 文件是 EXE 类型还是 DLL 类型,不但可以通过该结构得到 PE 文件中节的总量,还可以当成对节区信息进行遍历操作时的循环次数。

扩展PE 头IMAGE_OPTIONAL_HEADER32:

尽管从名字上看好像该部分数据是可选 (optional) 的,但在 PE 文件结构中,它却有着比标准 PE 头更多的内容,让人感觉似乎它才是真正的 PE 头。

文件执行时的入口地址、文件被操作系统装入内存后的默认基地址,以及节在磁盘和内存中的对齐单位等信息均可以在此结构中找到。对该结构中的某些数值的随意改动可能会造成PE 文件的加载或运行失败。

其详细定义如下:

WINDOWS+PE权威指南读书笔记(3)
WINDOWS+PE权威指南读书笔记(3)

PE头IMAGE_NT_HEADERS:

这个结构是广义上的PE 头,在标准的PE文件中其大小为 456 个字节。它是 3.4.2 节、3.4.3 节和 3.4.4 节中提到的三个数据结构的组合,即 IMAGE_NT_HEADERS=4 个字节的 PE标识 +IMAGE _ FILE_HEADER+IMAGE_OPTIONAL_ HEADER32。

WINDOWS+PE权威指南读书笔记(3)

该结构的详细定义如下:

与 DOS 头一样,PE 头开始也是一个标志,用一个双字的“PE\0\0”来命名,这也是 PE头的由来。

WINDOWS+PE权威指南读书笔记(3)

数据目录项 IMAGE_DATA_DIRECTORY:

IMAGE_OPTIONAL HEADER32 (扩展PE 头) 结构的最后一个字段为 DataDirectory 。该字段定义了 PE 文件中出现的所有不同类型的数据的目录信息。

如前所述,应用程序中的数据被按照用途分成很多种类,如导出表、导入表、资源、重定位表等。

在内存中,这些数据被操作系统以页为单位组织起来,并赋以不同的访问属性;

在文件中,这些数据也同样被组织起来,按照不同类别分别存放在文件的指定位置。

该结构就是用来描述这些不同类别的数据在文件(和内存) 中的位置及大小的。

IMAGE_DATA_DIRECTORY 结构只有两个字段,结构具体定义如下:

WINDOWS+PE权威指南读书笔记(3)

从 Windows NT 3.1操作系统开始到现在,该数据目录中定义的数据类型一直是 16 种。 两个字段依次为VirtualAddress 和 isize。如下图所示,总的数据目录一共由 16 个相同的 IMAGE_DATA_DIRECTORY 结构连续排列在一起组成。

WINDOWS+PE权威指南读书笔记(3)

这 16 个元组的数组每一项均代表 PE 中的某一个类型的数据,各数据类型详见下表:

WINDOWS+PE权威指南读书笔记(3)
WINDOWS+PE权威指南读书笔记(3)

如果想在 PE 文件中寻找特定类型的数据,就需要从该结构开始。比如,要想查看 PE 中都调用了哪些动态链接库的函数,则需要从数据目录表的第 2 个元素〈数组编号为1) 的 IMAGE_DATIA_DIRECTORY 结构获取导和人表在文件中的起始位置和大小,然后再根据 VirtualAddress_1 字段地址指向的位置找到导入表相关的字节码。这种信息组织方式正是“头部+ 身体”的数据组织方式。

下面是将这 16 个数据目录项依次展开后的新结构:

WINDOWS+PE权威指南读书笔记(3)
WINDOWS+PE权威指南读书笔记(3)

注意:以上结构在 PE 中并不存在,这里介绍主要是为了以后编程时可作为参考,当然,你也可以使用该结构覆盖字段 IMAGE_ OPTIONAL_HEADER32. DataDirectory。另外要注意的是,该虚拟结构每个字段后的偏移是基于 IMAGE_NT_HEADERS 头的。

节表:

WINDOWS+PE权威指南读书笔记(3)

节表项 IMAGE_SECTION_HEADER:

PE 头 IMAGE_ NT_HEADERS 后紧跟着节表。它由许多个节表项 (IMAGE_SECTION _HEADER) 组成,每个节表项记录了PE 中与某个特定的节有关的信息,如节的属性、节的大小、在文件和内存中的起始位置等。节表中节的数量由字段 IMAGE_FILE_HEADER.NumberOfSections 来定义。

节表项的数据结构详细定义如下:

WINDOWS+PE权威指南读书笔记(3)

PE 文件头部字段解析:

WINDOWS+PE权威指南读书笔记(3)

PE头IMAGE_NT_HEADER 的字段:

1. IMAGE_NT_HEADER.Signature:

+0000h,双字。PE 文件标识,被定义为 00004550h。也就是“P”,“E”加上两个0,这也是 PE 这个称呼的由来。

如果更改其中的任何一个字节,操作系统就无法把该文件识别为正确的 PE 文件。通过修改这个字段,会导致 PE 文件在 32 位系统中加载失败,但由于文件的其他部分〈特别是 DOS 头) 并没有被破坏,系统还是可以识别出其为 DOS 系统下的可执行程序,并通过调用纯 DOS 环境来运行 DOS Stub 中的程序代码。

如果你确认操作系统中的某个PE 文件携带病毒,并且开机后会被加载进内存运行,最简单的处理办法是通过 Windows PE 盘启动系统。在系统中找到病毒文件,使用记事本简单地修改其中任何一个字符,保存文件,重新开机启动后即可防止病毒文件被加载。

注意:此PE非披PE,Windows PE 是一个操作系统,其全称为 : Windows PreInstallation Environment,即 Windows 的预安装环境。该操作系统区别于 Windows XP/2000/Vista 等,可以从光盘引导。

2. IMAGE_NT_HEADER.FileHeader:

+0004h,结构。该结构指向IMAGE FILE_ HEADER,由于PE扩展自通用 COFF 规范,所以,该字段在官方文档中被称为标准 COFF 头。

3. IMAGE_NT_HEADER.OptionalHeader:

+0018h,结构。该结构指向 IMAGE OPTIONAL _ HEADER32。Windows 操作系统可执行文件的大部分特性均在这个结构里呈现。因为在符合 COFF 规范的“.obj”目标文件中该部分并不存在,所以该部分被称为 OptionalHeader (可选的头部信息,简称“可选头”),它是操作系统映像文件所独有的头部信息。

可选头又分为两部分,前 10 个字段原属于 COFF,用来加载和运行一个可执行文件 , 后21 个字段则是通过链接器追加的。它们作为PE 扩展的部分,用于描述可执行文件的一些信息,供 PE 加载器加载使用。

标准PE 头IAMGE_FILE_HEADER 的字段(由IMAGE_NT_HEADER.FileHeader指向):

1:IMAGE_FILE_HEADER.Machine:

+0004h,单字。用来指定 PE 文件运行的平台。

由于 Windows 最初被设计为可以运行在Intel、Sun、Dec、IBM 等多种硬件平台上,或者能模拟这些平台的软件环境中,而不同的硬件平台其指令的机器码不相同,因此为不同平台编译的 EXE 文件是无法通用的。

假设将运行在 Intel 386 机器上的 PE 文件的该字段设置为01f0h,即指定平台为 IBM POWER PC (小尾方式),则系统会有如图 3-12 所示的提示。

WINDOWS+PE权威指南读书笔记(3)

如果使用汇编语言,可以通过如下的伪指令代码对平台进行定义:

.386

IMAGE_FILE_HEADER.Machine 的常见值:

WINDOWS+PE权威指南读书笔记(3)

2:IMAGE_FILE_HEADER.NumberoOfSections:

+0006h,单字。文件中存在的节的总数。在 Windows XP 中,可以有10个节,但数值不能小于1。也不能超过96。如果将该值设置为0,则操作系统装载时会提示不是有效的Win32 程序。

如果想在 PE 中增加或删除节,必须变更此处的值。另外,这个值既不能比实际内存中存在的节多,也不能比它少,否则装载时会发生错误,提示不是有效的 Win32 应用程序。

3:IMAGE_FILE_HEADER.TimeDateStamp:

+0008h,双字。编译器创建此文件时的时间戳。低 32 位存放的值是自 1970年1月1日00:00 时开始到创建时间为止的总秒数。

该数值可以随意修改而不会影响程序运行。所以,有的链接器在这里填人固定的值,有的则随意写和任何值,这对用户创建的文件并没有实际的意义。另外,这个时间值与操作系统文件属性里看到的三个时间〈创建时间、修改时间、访问时间) 也没有任何联系。

4:IMAGE_FILE_HEADER.PointerToSymbolTable:

+000Ch,双字。COFF 符号表的文件偏移。如果不存在 COFF 符号表,此值为0。对于映像文件来说,此值为0,因为微软已经不赞成在 PE 中使用 COFF 调试信息了。

5:IMAGE_FILE_HEADER.NumberOfSymbols:

+0010h,双字。符号表中元素的数目。由于字符串表紧跟在符号表后,所以可以利用这个值来定位字符串表。对于映像文件来说,此值为0,主要用于调试。

6:IMAGE_FILE_HEADER.SizeOfOptionalHeader:

+0014h。单字。指定结构 IMAGE _ OPTIONAL_HEADER32 的长度,默认情况下这个值等于 00e0h;, 如果是 64 位 PE 文件,该结构的默认大小为 00F0h。

用户可以自己定义这个值的大小,不过需要注意两点:

1) 更改完以后,需要自行将文件中 IMAGE_OPITONAL_HEADER32 的大小扩充为你指定的值(一般以0补足)。

2) 扩充完以后,要维持文件中的对齐特性〈比如在 HelloWorld.exe 中,此处增加了8 个字节后,一定在后面相应地删除 8 个字节,以保证 .text 节起始位置处于 0400h )。

10. IMAGE_FILE_HEADER.Characteristics:

+0016h,单字。文件属性标志字段,它的不同数据位定义了不同的文件属性,有具体内容见表 3-3。这是一个很重要的字段,不同的定义将影响系统对文件的装入方式。

比如,当位 13 为1 时,表示这是一个DLL 文件,那么系统将使用调用 DLL 入口函数的方式执行文件入口函数。

当位 13 为0时,表示这是一个普通的可执行文件,系统直接跳到人口处执行。

对于普通的可执行PE 文件来说,这个字段的值一般是 010,而对于 DLL 文件来说,这个字段的值一般是210eh。

表 3-3 IMAGE_FILE_HEADER.Characteristics 属性位的含义:

WINDOWS+PE权威指南读书笔记(3)

如表 3-3 所示,当第 0 位为 1时,表明此文件不包含基址重定位信息,因此必须将其加载到文件头中指定的基地址字段位置。如果进程空间此处的基地址被占用,加载器会报错。在程序运行前如果发现文件中存在可重定位信息,链接器会执行移除可执行文件中的重定位信息的操作。

当第 1 位为1时,表明此映像文件是合法的,可以运行。如果未设置此标志,表明出现了链接器错误。

当第 7 位为1时,表示文件为小尾方式,即内存中,最低有效位 LSB 位于最高有效位MSB 的前面,与第 15 位的大尾方式 (MSB 在前,LSB 在后) 一样,都不赞成使用该标志,最好将其设置为 0。

当第 10 位为 1 时,如果此映像文件在可移动存储介质上,那么加载器将完全加载它并把它复制到内存交换文件中。

当第 11 位为 1 时,如果此映像文件在网络上,那么加载器也将完全加载它并把它复制到内存交换文件中。

当第 13 位为1时,表明此映像文件是动态链接库 (DLL)。这样的文件总被认为是可执行文件,尽管它们并不能直接运行。

可执行文件的标志位设置为010th,即第 0、1、2、3、8 位分别被设置为1 (如下所示),表示该文件为可执行文件,不含重定位信息,不含符号和行号信息,文件只在 32 位平台运行。

WINDOWS+PE权威指南读书笔记(3)

扩展 PE 头IMAGE_OPTIONAL_HEADER32 的字段:

1:IMAGE_OPTIONAL_HEADER32.Magic:

+0018h,单字。魔术字,说明文件的类型,如果为 010BH,则表示该文件为 PE32; 如果为0107h,则表示文件为ROM 映像 , 如果为 020BH,则表示该文件为 PE32+,即 64 位下的 PE文件。

2:IMAGE_OPTIONAL_HEADER32.MajorLinkerVersion:

3:IMAGE_OPTIONAL_HEADER32.MinorLinkerVersion:

+001ah,单字。这两个字段都是字节型,指定链接器版本号,对执行没有任何影响。

4:IMAGE_OPTIONAL_HEADER32.SizeOfCode:

+001ch,双字。所有代码节的总和(以字节计算),该大小是基于文件对齐后的大小,而非内存对齐后的大小。稍后还会介绍一个字段 SizeOfImage,它是基于内存对齐后的大小。需要注意一点 : 判断某个节是否包含代码的方法不是根据节的属性中是否含有IMAGE _SCN_MEM_EXECUTE 标志,而是根据节的属性中是否含有IMAGE _SCN_CNT_CODE 标志。

5:IMAGE_OPTIONAL_HEADER32.SizeOflnitializedData:

+0020h,双字。所有包含已经初始化的数据的节的总大小。

6:IMAGE_OPTIONAL_HEADER32.SizeOfUninitializedData:

+0024h,双字。所有包含未初始化的数据的节的总大小。这些数据被定义为未初始化,在文件中不占用空间 ; 但在被加载到内存以后,PE 加载程序应该为这些数据分配适当大小的虚拟地址空间。

7:IMAGE_OPTIONAL_HEADER32.AddressOfEntryPoint:

+0028h,双字。在 Windows 中,可执行程序运行在虚拟地址空间中,由于 4GB 空间对于程序是唯一的,所以这里的虚拟空间可以简单地理解为真实的地址〈我们暂且忘记物理内存地址的概念,这样就不需要理解页面调度机制了)。该字段的值是一个RVA,它记录了启动代码距离该 PE 加载后的起始位置到底有多少个字节。

如果在一个可执行文件中附加了一段自己的代码,并且想让这段代码首先被执行,一般都要修改这里的值使之指向自己的代码位置。对于一般程序映像来说,它就是启动地址; 对于设备驱动程序来说,它是初始化函数的地址。入口点对于 DLL 来说是可选的,如果不存在人口点,这个字段必须设置为 0。

注意:许多病毒程序、加密程序、补丁程序都会劫持这里的值,使其指向其他用途的代码地址。

8:IMAGE_OPTIONAL_HEADER32.BaseOfCode:

+002Ch,双字。代码节的起始RVA,表示映像被加载进内存时代码节的开头相对于映像基址的偏移地址。一般情况下,代码节紧跟在PE 头部后面,节的名称通常为“.text”。

9:IMAGE_OPTIONAL_HEADER32.BaseOfData:

+0030h,双字。数据节的起始 RVA,表示映像被加载进内存时数据节的开头相对于映像基地址的偏移地址。一般情况下,数据节位于文件末尾,节的名称通常为“.data”。

10:IMAGE_OPTIONAL_HEADER32.ImageBase:

+0034h,双字。该字段指出了 PE 映像的优先装入地址。

也就是在IMAGE_OPTIONAL_HEADER32.AddressOfEntryPoint中说的程序被加载到内存后的起始 VA。

链接器在产生可执行文件的时候,是对应这个地址来生成机器码的。如果操作系统也是按照这个地址加载机器码到内存中,那么指令中的许多重定位信息就不需要修改了,这样运行速度就会更快一些。

前面说过,对于 EXE 文件来说,每个文件使用的都是独立的虚拟地址空间,所以,优先装入的地址通常不会被其他模块占据。也就是说,EXE 文件总是能按照这个地址装入,这就意味着装入后的 EXE 文件不需要进行重定位了,例如,在 HelloWorld.exe 中就看不到重定位信息。

在链接的时候,可以使用参数“-base”来指定优先装和人的地址,如果不指定,那么链接器默认装入 EXE 的地址就是 0x00400000。而相对于 DLL 文件来说,它默认优先装入地址则是 0x10000000。如果一个进程用到了多个DLL文件,其装入地址可能会发生训突。PE 加载器会调整其中的地址,使所有的 DLL 文件都能被正确装人人。所以,不要错

误地认为内存中动态链接库的基地址和其文件头字段 IAMGE_OPTIONAL_HEADER32.ImageBase 指定的完全一样。

可以自己定义这个值,但取值有限制 :

第一,取值不能超出边界,即取的值必须在进程地址空间中 第二,该值必须是 64KB 的整数倍。

11:IMAGE_OPTIONAL_HEADER32.SectionAlignment

+0038h,双字。内存中节的对齐粒度,该字段指定了节被装入内存后的对齐单位。

内存中的数据存取以页面为单位。Win32 的页面大小是4KB,所以 Win32 PE 中节的内存对齐粒度一般都选择 4KB 大小。十六进制表示为 01000h 。

SectionAlignment 必须大于或等于 FileAlignment。当它小于系统页面大小时,必须保证SectionAlignment 和 FileAlignment 相等。

12:IMAGE_OPTIONAL_HEADER32.FileAlignment

+003ch,双字。文件中节的对齐粒度。

文件中的节对齐并不是提高本身代码的执行效率,同样也是为了提高文件从磁盘加载的效率。Windows XP 用来组织硬盘的所有文件系统都是基于簇〈分配单元) 的,每个簇包含几个物理扇区。扇区是磁盘物理存取的最小单位。簇越大,磁盘存储信息的容量就越大,但存取所花费的时间也越长。通常情况下,Windows 会选择使用 512 字节的簇大小〈一个物理扇区的大小) 来格式化分区,最大可以达到 4KB。在本书的例子中,文件对齐粒度选择了 512 字节,十六进制表示为 200h。

小技巧:如何查看当前系统的簇及扇区大小

方法一 ,在磁盘上创建一个 10 个字节的文件,然后查看文件属性,其中占用空间显示的就是簇大小。

13:IMAGE_OPTIONAL_HEADER32.MajorOperatingSystemVersion

14:IMAGE_OPTIONAL_HEADER32.MinorOperatingSystemVersion

+0040h,23和24标注的两个字段都为单字,共计为双字。标识操作系统的版本号,分为主版本号和次版本号两部分。

15:IMAGE_OPTIONAL_HEADER32.MajorlImageVersion

16:IMAGE_OPTIONAL_HEADER32.MinorlImageVersion

+0044h,双字。本 PE 文件映像的版本号。

17:IMAGE_OPTIONAL_HEADER32.MajorSubsystemVersion

18:IMAGE_OPTIONAL_HEADER32.MinorSubsystemVersion

+0048h,双字。运行所需要的子系统的版本号。

19:IMAGE_OPTIONAL_HEADER32.Win32VersionValue

+004ch,双字。子系统版本的值,暂时保留未用,必须设置为0。比如将此处的值更改为696C6971h,程序运行将失败,提示信息如图 3-13 所示。

WINDOWS+PE权威指南读书笔记(3)

20:IMAGE_OPTIONAL_HEADER32.SizeOflmage

+0050h,双字。内存中整个PE 文件的映射尺寸。

以加载到内存中的 HelloWorld.exe 为例, HelloWorld.exe 中文件头占用了 1000h 字节,三个节各占用 1000h 字节,所以文件在内存中占用的空间总共大小为 04000h。该值可以比实际的值大,但不能比它小,而且必须保证该值是 SectionAlignment 的整数倍。

21:IMAGE_OPTIONAL_HEADER32.SizeOfHeaders

+0054h,双字。所有头+节表按照文件对齐粒度对齐后的大小(即含补足的0),HelloWorld.exe 中的值为0400h。在 PE 文件中,该部分数据是严格按照 200h 对齐的,如果不对齐,系统在加载时会提示出错。

22:IMAGE_OPTIONAL_HEADER32.CheckSum

+0058h,双字。校验和,在大多数的PE文件中,该值是0,但在一些内核模式的驱动程序和系统 DLL 中,该值则是必须存在且是正确的,比如 kernel32.dll 中 PE 的校验和是0011E97Eh。

Windows 系统目录下有一个动态链接库 IMAGEHLPDLL,它是 Win32 中专门用来操作 PE 文件的函数库,这里面的函数 CheckSumMappedFile 就是用来计算文件头校验和的,对于整个 PE 文件也有一个校验和函数 MapFileAndCheckSum。该动态链接库中还包括其他一些常用的函数,可以通过小工具 PEInfo 输出并查看。关于校验和的具体计算方法,可以参照3.7 节关于 PE 文件头编程的部分。

23:IMAGE_OPTIONAL_HEADER32.Subsystem

+005ch,单字。指定使用界面的子系统,它的取值如表 3-4 所示。这个字段决定了系统如何为程序建立初始的界面,链接时使用的参数 -subsystem:xxx 选项指定的就是这个字段的值,如果将子系统指定为 Windows 命令行用户交互模式 (Command User Interface,CUI),那么系统会自动为程序建立一个控制台窗口 ,如果指定为 Windows GUI,窗口程序代码必须由用户自己建立。

下表为IMAGE_OPTIONAL_HEADER32.Subsystem 的取值:

WINDOWS+PE权威指南读书笔记(3)
WINDOWS+PE权威指南读书笔记(3)

MASM32 的link 程序的链接开关 -subsystem 的常见选项如下表所示:

WINDOWS+PE权威指南读书笔记(3)

24:IMAGE_OPTIONAL_HEADER32.DllCharacteristics:

+005eh,单字。DLL 文件属性。它是一个标志集,不是针对 DLL 文件的,而是针对所有PE 文件的。

其详细定义如下表所示:

WINDOWS+PE权威指南读书笔记(3)
WINDOWS+PE权威指南读书笔记(3)

25:IMAGE_OPTIONAL_HEADER32.SizeOfStackReserve:

+0060h,双字。初始化时保留栈的大小。

该字段表示为初始线程的栈而保留的虚拟内存数量,然而并不是留出的所有虚拟内存都可以做栈〈真正的栈大小由下一个字段 SizeOfStackCommit 决定)。该字段的默认值为0x100000 (1MB),如果调用API函数数CreateThread 时,把 NULL 当做传入的参数,那么创建出来的栈大小也会是 1MB。

26:IMAGE_OPTIONAL_HEADER32.SizeoOfStackCommit

+0064h,双字。初始化时实际提交的栈大小。

保证初始线程的栈实际占用内存空间的大小,它是被系统提交的。这些提交的栈不存在于交换文件里,而是存在于内存中。对于 Microsoft 的链接器来说,这个域的初始值为 0x1000 字节〈1 页),而对于 TLINK32,则为2页。

27:IMAGE_OPTIONAL_HEADER32.SizeOfHeapReserve

+0068h,双字。初始化时保留的堆大小,用来保留给初始进程堆使用的虚拟内存。

这个堆的句柄可以通过调用 GetProcessHeap 函数获得。每一个进程至少会有一个默认的进程堆,该堆在进程启动的时候被创建,而且在进程的生命期中永远不会被删除。默认值为 1MB,我们可以通过链接器的“-heap”参数指定起始的保留堆内存大小和实际提交的堆大小。

28:IMAGE_OPTIONAL_HEADER32.SizeOfHeapCommit

+006ch,双字。初始化时实际提交的堆大小,在进程初始化时设定的堆所占用的内存空间。默认值为1页。

29:IMAGE_OPTIONAL_HEADER32.LoaderFlags

+0070h,双字。加载标志。

30:IMAGE_OPTIONAL_HEADER32.NumberOfRvaAndSize

+0074h,双字。定义数据目录结构的数量,一般为 00000010h,即 16 个。该值由字段SizeOfOptionalHeaders 决定,实际应用中可以取2一16的值。

31:IMAGE_OPTIONAL_HEADER32.DataDirectory

+0078h,结构。由 16个IMAGE DATIA_DIRECTORY 结构线性排列而成,用于定义PE中的 16 种不同类别的数据所在的位置和大小。

先给出前面给过的数据类型图:

WINDOWS+PE权威指南读书笔记(3)
WINDOWS+PE权威指南读书笔记(3)

[0] 导出数据所在的节通常被命名为 .edata,它包含一些可被其他 EXE 程序访问的符号的相关信息,比如导出函数和资源等。这些符号通常出现在 DLL 中,但 DLL 也可以包含导入符号,而且在某些 EXE 中也可以有导出符号。对导出表的详细介绍请阅读本书第 5 章。

[1] 导入数据所在的节通常被命名为 .idata,它包含了 PE 映像中所有导入的符号。导入信息在 EXE 和 DLL 中几乎都存在。对导入表的详细介绍请阅读本书第 4章。

[2] 异常表数据所在的节通常被命名为 .pdata。该节是由用于异常处理的函数表项组成的数组。可选文件头中的 ExceptionTable (异常表) 字段指向它。在将它们放进最终的映像文件之前,这些表项必须按函数地址进行排序,并且这些函数表项的描述必须符合特定的目标平台。该部分数据主要用于基于表的异常处理,适用于除 x86 之外的所有类型的CPU 。

[3] 资源数据所在的节通常被命名为 .rsrc。该节是一个多层的二叉排序树,该树的节点指向 PE 中各种类型的资源,如图标、对话框、菜单等。树的深度可达 2”层,但是 PE中经常使用的只有 3 层 : 类型层、名称层、语言代码层。对资源表的详细信息请参照本书第7章。

[4] 属性证书数据的作用类似于 PE 文件的校验和或者 MD5 码,通过这种属性证书方式可以验证一个PE 文件是否被非法修改过。为 PE 文件添加属性证书表可以使该 PE 与属性证书相关联。属性证书表是由一组连续的按八进制字 (从任意字节边界开始的 16 个连续字节 )边界对齐的属性证书表项组成,每个属性证书表项指向 WIN_CERTIFICATE 结构。此结构可以在 WinTrust.H 文件中找到,

该结构的详细定义如下:

WINDOWS+PE权威指南读书笔记(3)

注意 :该数据并不作为映像的一部分被映射到内存,因此,DataDirectory.Certificate_VirtualAddress字段是文件偏移,而不是 RVA。

DataDirectory.Certificate_ VirtualAddress字段给出了属性证书表中第一个属性证书表项在文件中的偏移。对于后续的属性证书表项,可以通过将当前属性证书表项的文件偏移加上WIN_CERTIFICATE.dwLength 字段的值,并将结果向上舍入为 8 个字节的倍数来访问。

后续的属性证书表项可以一直以这种方式访问,直到这些 WIN_CERTIFICATE.dwLength 字段 〈已经向上舍人为8 字节的倍数) 的和等于可选文件头中的 DataDirectory.Certificate_isize 的值。如果上述的值最后不等于 isize 字段的值,要么是属性证书表被破坏了,要么是 isize 域被修改了。

[5] 基址重定位信息所处的节通常被命名为 .reloc,基址重定位表包含了映像中所有需要重定位的内容。它被划分成许多块,每一块表示一个 4KB 页面范围内的基址重定位信息,它必须从 32 位边界开始。

一般情况下,Windows 加载器是不需要处理由链接器解析的基址重定位信息的,除非该映像不能被加载到 IMAGEOPTIONAL_HEADER32.ImageBase 指定的位置。

[6] 调试数据所处的节通常被命名为 .debug,它指向 IMAGE_DEBUG_DIRECTORY 结构数组。其中的每个元素都描述了 PE 中的一些调试信息。要获得 IMAGE_DEBUG_DIRECTORY 结构的数目,可以用 isize 字段除以 IMAGE _ DEBUG DIRECTORY 结构的大小。

注意:在默认情况下,调试信息并不会映射到映像的虚拟地址空间中。调试目录可以位于一个可丢弃的 .debug 节 (如果存在) 中,或者位于PE 文件的其他节中,或者不在任何节中。所以,它可能被加载到虚拟内存中,而大部分情况下是被丢弃的。

[7] 预留,必须为 0。

[8] Global Ptr 数据描述的是被存储在全局指针寄存器中的一个值。

[9] 线程本地存储数据所处的节,通常命名为 ds。线程本地存储 (TLS) 是 Windows 支持的一种特殊存储类别,其中的数据对象不是栈变量,而是对应于运行相应代码的单个线程。因此,每个线程都可以为使用 TLS 定义的变量来维护一个不同于其他线程的值。

当创建线程时,PE 加载器通过将线程环境块 (CTEB) 的地址放入 FS 寄存器来传递线程的TLS 数组地址,距 TEB 开头 0x2C 的位置处有一个指针指向该 TLS 数组。线程本地存储技术是特定于 Intel x86 平台的。

[10] 加载配置信息用于包含保留的 SEH 技术。该技术基于 x86 的 32 位系统,它提供了一个安全的结构化异常处理程序列表,操作系统在进行异常处理时要用到这些异常处理程序。

[11] 绑定导入数据的存在主要是为了优化导入信息,提高 PE 的加载效率。当 PE 文件被加载到内存时,加载器会先检查导入表,然后把需要加载的 DLL 载入到地址空间中 。加载器还有一项比较重要的工作是根据导入信息的摘述使用动态链接库里输入函数的实际地址去替换 IAT 表的内容,这个步骤会花去一部分时间。但是,如果程序员(或者链接

器) 可以完全知道图数的地址,就可以直接把数组中的元素替换为地址,这能节省相当多的时间。这种方法就称为绑定 (Binding)。简单来讲,绑定是指由程序员或链接器代替Windows PE 加载器完成了一部分对导入表的处理工作〈在加载时)。

[12] IAT 是导入地址表的英文缩写。准确地讲,它是导入表的一部分,这个双字数组里定义了所有导入函数的 VA,程序可以直接通过跳转指令跳转到该 VA 处执行。

[13] 延迟导入数据也和动态链接库调用有关,这种数据的存在是为了给“应用程序直到首次调用某个 DLL 中的函数或数据时才加载这个DLL 〈即延迟加载)”这种行为提供一种统一的访问机制。

[14] CLR 数据所处的节通常被命名为 .cormeta,该信息是 .NET 框架的一个重要组成部分,所有基于 .NET 框架开发的程序,其初始化部分都是通过访问这部分定义而实现的。PE加载时将通过该结构加载代码托管机制需要的所有动态链接库文件,并完成与CLR 有关的一些其他操作。

[15] 系统预留,未定义。

提示 :要想在PE 中找到特定类型的数据或者资源,就应从这个结构开始的。通过查找不同类别资源的RVA 地址和长度,即可获取到相关数据,这在前面已经提到过。

数据目录项 IMAGE_DAITA_DIRECTORY 的字段:

32:IMAGE_DAITIA_DIRECTORY.VirtualAddress

+0000h,双字。如上所述,这个字段记录了特定类型数据的起始 RVA。当然,针对不同的数据结构,该字段包含的数据含义并不一样,有的数据甚至还不是 RVA (如属性证书数据中该字段的值表示的是FOA )。

33:IMAGE_DATA_DIRECTORY.isize

+0004h,双字。该字段记录了特定类型的数据块的长度。

节表项 IMAGE_SECTION_HEADER 的字段:

WINDOWS+PE权威指南读书笔记(3)

1:IMAGE_SECTION_HEADER.Name1

+0000h,8 字节。该字段一共 8 个字节,一般情况下是一个以“\0”结尾的 ASCII 码字符串来标识节的名称,内容可以自行定义。

该名称并不遵循 Ansi 字符串必须以“\0”结尾的规律,如果不以“\0”结尾,系统依然会认为它是一个字符串 , 但会根据 8 个字节的长度对其进行截断处理。

2:IMAGE_SECTION_HEADER.Misc

+0008h,双字。该字段是一个 union 型的数据,这是节的数据在没有对齐前的真实尺寸,不过很多 PE 文件里该值并不准确。

3:IMAGE_SECTION_HEADER.VirtualAddress

+000ch,双字。节区的 RVA 地址。

4:IMAGE_SECTION_HEADER.SizeOfRawData

+0010h,双字。节在文件中对齐后的尺寸。在 HelloWorld.exe 中,数据量不大的节,其大小一般为 200h。

5:IMAGE_SECTION_HEADER.PointerToRawData

+0014h,双字。节区起始数据在文件中的偏移。比如我们的例子中,.text 节是在偏移0x00000400 处。

6:IMAGE_SECTION_HEADER.PointerToRelocations

+0018h,双字。在“.obj”文件中使用,指向重定位表的指针。

7:IMAGE_SECTION_HEADER.PointerToLinenumbers

+001ch,双字。行号表的位置〈供调试用)。

8:IMAGE_SECTION_HEADER.NumberOfRelocations

+0020h,单字。重定位表的个数〈在 OBJ 文件中使用)。

9:IMAGE_SECTION_HEADER.NumberOfLinenumbers

+0022h,单字。行号表中行号的数量。

10:IMAGE_SECTION_HEADER.Characteristics

+0024h,双字。节的属性。这个字段很重要,这是节的属性标志字段,其中不同的数据位代表了不同的属性,具体的定义如表 3-7 所示。这些数据位的组合描述了内存中的一个节所拥有的访问属性。

IMAGE_SECTION_HEADER.Characteristics 数据位含义:

WINDOWS+PE权威指南读书笔记(3)

代码节的属性一般为 60000020h,也就是可执行、可读和 “节中包含代码”,数据节的属性一般为 c0000040h,也就是可读、可写和 “包含已初始化数据” ,而常量节〈对应源代码中的 .const段) 的属性为 40000040h,也就是可读和“包含已初始化数据” ;资源节的属性和常量节的属性一般是相同的。

当然,节属性的定义不一定必须是这些值。比如,当 PE 文件被压缩工具压缩以后,包含代码的节往往被同时设置成具有可执行、可读和可写属性,因为解压部分需要将解压后的代码回写到代码段中。大家可以做个实验: 先往代码段中写数据,编译链接完成后执行,这时肯定会引发异常如果用压缩软件压缩后再执行,就会发现文件可以正常执行了,这就是因为压缩软件为了解压的需要而将节的属性设置为可写了。

继续阅读