PE文件结构—Data Diretoris,Sections,Section Header

本文介绍了PE文件中的数据目录表和区域表,包括DataDirectories的结构、每个ENTRY的定义以及其在内存中的地址和大小。区域表包含文件的实际数据,如代码、数据等,每个区域表都有对应的SectionHeader提供详细信息。文章强调了RVA在PE文件中的作用,并讨论了SizeOfRawData和VirtualSize的区别。

目录

简介

Data Directories

Sections

Section Headers

总结


 

简介

今天将具体学习一下数据目录表和区域表的相关内容,这两部分中有很多内容在文件被执行时起关键作用。下面我们将具体分析一下数据目录表和区域表。

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              8

PhysicalAddress 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 其实我是没找到这个值代表的意思的,如果有找到的朋友可以在评论区里边说说(或者说我没有看懂这部分值代表什么)。

总结

内容实际上是不多的,这篇博客写太久了,质量应该比第一篇好一点。下一篇博客应该会写分析数据目录表中的一些表,比如导出表之类的。

感谢阅读!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值