一、数据目录表的说明
由16个IMAGE_DATA_DIRECTORY结构线性排列而成,用于定义PE中的16种不同类别的数据所在的位置和大小
具体的16个数据目录表前面已经说过了,这里再介绍一下:
数组编号 | 英文描述 | 中文描述 |
0 | Export table address and size | 导出表地址和大小 |
1 | Import table address and size | 导入表地址和大小 |
2 | Resource table address and size | 资源表地址和大小 |
3 | Exception table address and size | 异常表地址和大小 |
4 | Certificate table address and size | 属性证书数据地址和大小 |
5 | Base relocation table address and size | 基地址重定位表地址和大小 |
6 | Debugging infomation starting address and size | 调试信息地址和大小 |
7 | Architecture-specific data | 预留为0 |
8 | Global pointer register relative virtual address | 指向全局指针寄存器的值 |
9 | Thread local storage(TLS) table address and size | 线程局部存储地址和大小 |
10 | Load configuration table address and size | 加载配置表地址和大小 |
11 | Bound import table address and size | 绑定导入表地址和大小 |
12 | Import address table address and size | 导入函数地址表地址和大小 |
13 | Delay import descriptor address and size | 延迟导入表地址和大小 |
14 | CLR Runtime Header address and size | CLR运行时头部数据地址和大小 |
15 | Reserved | 系统保留 |
[0]导出数据所在的节通常被命名为.edata,包含了一些可被其他exe程序访问的符号的相关信息,比较重要的就是导出函数和资源等。
[1]导入数据所在的节通常被命名为.idata,包含了PE映像中所有导入的符号。导入信息在dll和exe中几乎都存在
[2]资源数据所在的节通常被命名为.rsrc,该节是一个多层的二叉排序树,该树的节点指向PE中各种类型的资源,如图标、对话框、菜单等。树的深度可达231层
[3]异常数据表所在的节通常命名为.pdata,该节是由用于一长串处理的函数表项组成的数组。
[4]属性证书数据的作用类似于PE文件的校验和或者MD5,通过这种属性证书方式可以验证一个PE文件是否被非法修改过。为PE文件添加属性证书表可以使该PE与属性证书相关联。(如果想防止PE文件被修改,比如说修改跳转的字节码,绕过验证等,是否可以考虑这种方法)
[5]基址重定位信息所处的节通常被命名为.reloc,表中包含了映像中所有需要重定位的内容。被划分为很多块,每一块表示一个4KB页面范围内的基址重定位信息。
[6]调试数据所处的节通常被命名为.debug,它指向IMAGE_DEBUG_DIRECTORY结构数组。其中的每个元素都描述了PE中的一些调试信息。
调试目录可能被加载到虚拟内存中,而大部分情况下是被丢弃的
[7]预留,必须为0
[8]Global Ptr数据描述的是被存储在全局指针寄存器中的一个值
[9]线程本地存储所在的节,通常命名为.tls,本地线程存储TLS是windows支持的一种特殊存储类别,其中的数据对象不是栈变量,而是对应于运行相应代码的单个线程。
当创建线程时,PE加载器通过将线程环境块(TEB)的地址放入FS寄存器来传递线程的TLS数组地址,距TEB开头0x2C(这个地址很重要,特别是在内核调试的时候)的位置处有一个指针指向TLS数组。线程本地存储技术是特定于Intel x86平台的
[10]加载配置信息用于包含保留的SEH技术。该技术基于x86的32位系统,它提供了一个安全的结构化异常处理程序列表,操作系统在进行异常处理的时候要用到这些异常处理程序
[11]绑定导入数据的存在主要是为了优化导入信息,提高PE的加载效率。当PE文件加载到内存时,加载器会先检查导入表,然后把需要加载的DLL载入到地址空间中。
还有一个比较重要的工作是根据导入信息的描述使用动态链接库里输入函数的实际地址去替换IAT表的内容,这个步骤会花去一部分时间。
如果能够知道函数的地址,就可以直接把数组中的元素替换成地址,这种方法称为绑定。
[12]IAT是导入地址表的英文缩写。准确来说,它是导入表的一部分,这个双字数组里定义了所有导入表的VA,程序可以直接通过跳转指令跳转到VA处执行。
[13]延迟导入表也和动态链接库调用有关,这种数据的存在是为了给“应用程序知道首次调用某个DLL中的函数或数据时才加载这个DLL(也就是延迟加载)”这种行为提供了一种统一的访问机制
[14]CLR(Common Language Runtime,公共语言运行时)数据所处的节通常被命名为.cormeta,该信息是.NET框架的一个重要责成部分,所有基于.NET框架开发的程序,其初始化部分都是通过访问这部分定义而实现的。
PE加载时将通过该结构加载托管代码机制(.NET中代码执行的方式,代码由虚拟机托管执行)需要的所有动态链接库文件,并完成与CLR有关的一些其他操作
[15]系统预留,未定义
二、数据目录项IMAGE_DATA_DIRECTORY的字段
typedef struct _IMAGE_DATA_DIRECTORY { DWORD VirtualAddress; //数据的起始RVA DWORD Size; //数据块的大小(长度) } IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;
1、IMAGE_DATA_DIRECTORY.VirtualAddress
这个字段记录了特定类型数据的起始RVA,针对不同的数据结构,该字段包含的数据含义并不一样,有的数据甚至还不是RVA(如属性证书数据中该字段的值表示的是FOA)
2、IMAGE_DATA_DIRECTORY.Size
记录了特定类型的数据块的长度
三、节表项IMAGE_SECTION_HEADER的字段
节表中的内容就是PE文件中大部分数据的所在,包括代码,数据,字符串等信息都在这部分
#define IMAGE_SIZEOF_SHORT_NAME 8typedef struct _IMAGE_SECTION_HEADER { BYTE Name[IMAGE_SIZEOF_SHORT_NAME]; //8个字节节命,这个值在上面有定义 union { DWORD PhysicalAddress; DWORD VirtualSize; //节区的尺寸 } Misc; DWORD VirtualAddress; //节区的RVA地址 DWORD SizeOfRawData; //在文件中对齐后的尺寸 DWORD PointerToRawData; //在文件中的偏移 DWORD PointerToRelocations; //在OBJ文件中用到 DWORD PointerToLinenumbers; //行号表的位置(供调试使用) WORD NumberOfRelocations; //在OBJ文件中用到 WORD NumberOfLinenumbers; //行号表中行号的数量 DWORD Characteristics; //节的属性 } IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;
1、IMAGE_SECTION_HEADER.Name
该字段一共8个字节,一般情况下是一个以"\0"结尾的ASCII码字符串来标识节的名称,内容可以自行定义
该名称并不遵循Ansi字符串必须以"\0"结尾的规律,如果不以"\0"结尾,系统依然会认为它是一个字符串,但是会以8字节长度进行截断
2、IMAGE_SECTION_HEADER.Misc
该字段是一个union型的数据,这是节的数据在没有对齐前的真实尺寸,不过很多PE文件里该值并不准确
3、IMAGE_SECTION_HEADER.VirtualAddress
节区的RVA地址
4、IMAGE_SECTION_HEADER.SizeOfRawData
节在文件对齐后的尺寸
5、IMAGE_SECTION_HEADER.PointerToRawData
节区起始数据在文件中的偏移
6、IMAGE_SECTION_HEADER.PointerToRelocations
在".obj"文件中使用, 指向重定位表的指针
7、IMAGE_SECTION_HEADER.PointerToLinenumbers
行号表的位置(供调试用)
8、IMAGE_SECTION_HEADER.NumberOfRelocations
重定位表的个数(在OBJ文件中使用)
9、IMAGE_SECTION_HEADER.NumberOfLinenumbers
行号表中行号的数量
10、IMAGE_SECTION_HEADER.Characteristics
节的属性,这是一个四字节的字段,这个字段很重要,不同的数据位代表了不同的属性,这些数据位的组合描述了内存中一个节所拥有的访问属性
代码节的属性一般为0x60000020,二进制如下所示
也就是数据位的第5位、第29位、第30位的值为1,对照上面的表,就能够知道0x60000020代表的意思就是可执行,可读和节中包含代码。
数据节的属性一般为0xC0000040,二进制如下:
第6位、第30位和第31位的值为1,也就是0xC0000040代表可读,可写和节中包含已初始化数据
常量节(.const段)的属性为0x40000040,也就是可读和包含已初始化数据
资源节的属性和常量节的属性一般是相同的
当PE文件被压缩工具压缩以后,节属性就不一定是这些值了。包含代码的节往往被同时设置成具有可执行,可读和可写属性(正常是没有可写属性的),因为解压部分需要将解压后的代码回写到代码段中。
PE文件理论方面就介绍到这里,后面会从实际应用的角度去加深对PE文件结构的理解