目录
简介
今天将具体学习一下数据目录表和区域表的相关内容,这两部分中有很多内容在文件被执行时起关键作用。下面我们将具体分析一下数据目录表和区域表。
Data Directories
Data Directories,以下简称数据目录表,它是可选择头中最后的一部分。
IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
这是可选择头中定义它的部分,不难看出这是一个array。它的大小是16,即最多有十六个数据目录表。为什么是十六呢?因为在C语言中有定义,IMAGE_NUMBEROF_DIRECTORY_ENTRIES是一个宏定义的常量,值为16。
#define IMAGE_NUMBEROF_DIRECTORY_ENTRIES 16
下面是对所有ENTRIES的宏定义:
// Directory Entries
#define IMAGE_DIRECTORY_ENTRY_EXPORT 0 // Export Directory
#define IMAGE_DIRECTORY_ENTRY_IMPORT 1 // Import Directory
#define IMAGE_DIRECTORY_ENTRY_RESOURCE 2 // Resource Directory
#define IMAGE_DIRECTORY_ENTRY_EXCEPTION 3 // Exception Directory
#define IMAGE_DIRECTORY_ENTRY_SECURITY 4 // Security Directory
#define IMAGE_DIRECTORY_ENTRY_BASERELOC 5 // Base Relocation Table
#define IMAGE_DIRECTORY_ENTRY_DEBUG 6 // Debug Directory
// IMAGE_DIRECTORY_ENTRY_COPYRIGHT 7 // (X86 usage)
#define IMAGE_DIRECTORY_ENTRY_ARCHITECTURE 7 // Architecture Specific Data
#define IMAGE_DIRECTORY_ENTRY_GLOBALPTR 8 // RVA of GP
#define IMAGE_DIRECTORY_ENTRY_TLS 9 // TLS Directory
#define IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG 10 // Load Configuration Directory
#define IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT 11 // Bound Import Directory in headers
#define IMAGE_DIRECTORY_ENTRY_IAT 12 // Import Address Table
#define IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT 13 // Delay Load Import Descriptors
#define IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR 14 // COM Runtime descriptor
一个数据目录表是定义在winnt.h中的名称为IMAGE_DATA_DIRECTORY的结构体。
实际上,数据目录表的结构很简单,它结构中只有两个值,分别是VirtualAddress和Size,大小都是DWORD。
//
// Directory format.
//
typedef struct _IMAGE_DATA_DIRECTORY {
DWORD VirtualAddress;
DWORD Size;
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;
第一个成员是image在内存中的地址实际上应该是相对于镜像基址的RVA(据某位亲爱的学长的文章,这是指向数据目录表开始位置的RVA,我对于这句话有点疑惑)。第二个成员是这个表的大小。用PE-bear我们可以看到这个数据目录表:
抛出问题:在通过一些文章学习PE的时候总会看到RVA(Relative Virtual Address)个人认为所有的RVA都是想对镜像基址的一个地址偏移量。
上述问题其实已经有了解答,其实是我自己没有理解学长文章的意思,下面是微软官方的解释:


值得注意的是:Address和Size都为0时,即这个文件的数据目录表中没有相关表。
总之,数据目录表其实就是定义在PE文件中的一段数据。它里面有很多重要信息。比如import表,它里边存了 许多从别的库中导入的拓展函数。
值得注意的是:并不是所有的表内部结构都是相同的,表的结构由它的类型而定,解析时依据
IMAGE_DATA_DIRECTORY.VirtualAddress
来定位表,然后根据表的特征进行解析。比如说Export表,解析它时需要用一个循环来导出它的函数相关信息,具体代码实现之后在实验中给出。
Sections
区域表里面是该文件的真实数据(为什么要这样说呢?因为怕有人把这部分数据和Image,这不是镜像数据!!不是复制过来的而是该可执行文件的真实数据)。每一个区域表都有一个特别的后缀名.xxx,下面我将列出几个后缀名,详细请看微软的官方文档:传送门
.text 可执行文件的代码
.rdata 仅可读的已经初始化的数据
.data 已经初始化的数据
.pdata 异常信息(里面存的是异常处理函数)
.bss 未初始化的数据
.rsrc 资源表
.reloc 镜像的重定位表(里面存放了所有的镜像中需要重定位的条目,还怎么理解,感觉像是这个表里面存了许多用于定位的偏移量和地址)
这些只是一部分表,还有很多,具体解释可以看看微软的官方文档,解释的很清楚!再感慨一下,微软的官方文档真的解释得很清楚。

Section Headers
每一个区域表都有一个区域表头来解释这一段区域表的整体信息。它位于区域表信息和可选择头之间。我们先来看看区域表头的结构:
一个Section Header是一个被定义在winnt.h中的结构体,名称为IMAGE_SECTION_HEADER。
typedef struct _IMAGE_SECTION_HEADER {
BYTE Name[IMAGE_SIZEOF_SHORT_NAME];
union {
DWORD PhysicalAddress;
DWORD VirtualSize;
} Misc;
DWORD VirtualAddress;
DWORD SizeOfRawData;
DWORD PointerToRawData;
DWORD PointerToRelocations;
DWORD PointerToLinenumbers;
WORD NumberOfRelocations;
WORD NumberOfLinenumbers;
DWORD Characteristics;
} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;
Name:
这是区域表的名称(.text,.rdata等等),由一个byte大小的数组存储,由下方的宏定义可知,一个区域表头的名称不能超过八个字节。
#define IMAGE_SIZEOF_SHORT_NAME 8PhysicalAddress or VirtualSize:
以一个共用体union定义这两个数据。这里实际上所表示的内容是一样的,即当被加载进内存时这个区段的全部大小。
VirtualAddress:
(我这里翻译一下官方文档的解释)对于一个可执行镜像文件,当它被加载进入内存时,它是该区域表第一个字节的RVA;对于对象文件,它是在重定位被应用之前第一个字节的地址。这里的翻译会不够准确,详细以官方文档为主,实际上,英文的解释更加易懂,因为它明确地使用“Virtual”和“actual”来区分镜像文件在内存中的地址和RVA与实际文件在磁盘中的地址。值得注意的是,文件在磁盘中的存储方式和在内存中的存储方式是不同的。
SizeOfRawData:
它存的是该区域段在磁盘中的大小。其实看英文名字也能看懂,这个翻译过来就是“元数据的大小”。值得注意的是,对于一个镜像文件,它的大小一定是可选择头中文件对齐值的整数倍。实际上它和VirtualSize大小是不同的。
PointerToRawData:
一个指针指向文件的区域段在内存中所占的第一页内存页面,它的大小一定是可选择头中文件对齐值的整数倍。
PointerToRelocations:
一个文件指针指向该表重定位后的开端。如果没有重定位或者为镜像文件,那么值设置为零。有个问题,为什么镜像文件的这个值要设置为零?
PointerToLinenumbers:
一个文件指针指向节的行号的条目的开头。如果没有COFF行号,或者为镜像我呢见那么设定值为零。官方文档说镜像文件的debug信息已经被抛弃了,所以值设为零。
NumberOfRelocations:
这个区域段的重定位目录条数。对于可执行的镜像文件,它被设置为零。为啥啊?不理解
NumberOfLinenumbers:
它是COFF的行号条目数,但是它的值被设置为零,因为COFF的调试信息已经被弃用了。
Characteristics:
描述这个区域段的特征,详情请看官方文件。
我们来看看为什么SizeOfRawData和VirtualSize会不同,下面的解释(从某位专业人士那儿扒过来的,如侵权请联系我,不过他肯定没有时间看我的博客【狗头】)非常详细,但是我不想翻译所以看看截图:

第一点原因是,在磁盘中,原始数据的存放方式是一页一页存的,每一页都有特定大小,这就导致会有一些空余的不被占有的空间被浪费了。而当这个区域表被导入进实际内存时,它的存放方式是有多大内存占多大内存。
第二点原因是,在如果区域表有未初始化的数据,这些数据在磁盘中不会有什么影响,但当他们被导入到内存中时,内存得给它们留足空间,因为这些为初始化的数据可能实际执行文件时被初始化。
以下是一个具体示例:
分析一下前四列数据和Characteristics,以.text为例。
我们可以通过RawAddress和RawSize来计算该区段在磁盘中的末尾位置,通过VirtualAddress和VirtualSize来计算该区段在内存中的末尾位置。计算的方式直接地址+大小即可,不难注意到,内存中的值符合预期,而磁盘中的末尾位置不符合预期,原因还是和存储方式有关。
Characteristics的这个值代表什么意思呢?我们看看官方文档给出的信息:Section-flags 其实我是没找到这个值代表的意思的,如果有找到的朋友可以在评论区里边说说(或者说我没有看懂这部分值代表什么)。
总结
内容实际上是不多的,这篇博客写太久了,质量应该比第一篇好一点。下一篇博客应该会写分析数据目录表中的一些表,比如导出表之类的。
感谢阅读!
本文介绍了PE文件中的数据目录表和区域表,包括DataDirectories的结构、每个ENTRY的定义以及其在内存中的地址和大小。区域表包含文件的实际数据,如代码、数据等,每个区域表都有对应的SectionHeader提供详细信息。文章强调了RVA在PE文件中的作用,并讨论了SizeOfRawData和VirtualSize的区别。



3252

被折叠的 条评论
为什么被折叠?



