PE文件结构解析 Part4 Data Directories, Section Headers and Sections
文章来源:https://0xrick.github.io/win-internals/pe5/
简介
上一篇文章中,我们讨论了NT Headers,但是我们跳过了Optional Header最后的data directories字段。
这篇文章中,我们会讨论data directories是什么以及它们在什么位置。
我们也会讲讲section headers和sections。
Data Directories
IMAGE_OPTIONAL_HEADER
结构最后的一个成员是类型为IMAGE_DATA_DIRECTORY
的一个数组:
IMAGE_DATA_DIRECTORY DataDirectory[16];
然后我们看看IMAGE_DATA_DIRETORY
结构体的定义:
typedef struct _IMAGE_DATA_DIRECTORY {
DWORD VirtualAddress;
DWORD Size;
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;
这是一个非常简单的结构体,只有两个字段,第一个是相对虚拟地址,第二个是data directory的大小。
所以data directory是什么呢?基本上data directory就是存储在PE文件中一个section里面的一块数据。
data directory包含加载器所需的有用的信息,一个非常重要的direcotry就是Import Directory,包含一组从其他Lib导入的外部函数。这个我们会在讲到PE import的时候再更细致地讨论。
但不是所有的data directory的格式都一样。IMAGE_DATA_DIRECTORY.VirtualAddress
字段指向data directory,但是directory的类型影响数据是如何被解析的。
下面是定义在winnt.h
的data directory的类型。下面每个值都表示DataDirectory数组中的一个索引。
// 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
如果我们看一眼实际PE文件中的IMAGE_OPTIONAL_HEADER.DataDirectory
的内容,我们大概率能看到两个字段都是0的情况。
这意味着特定的data directory在可执行文件中不存在。
Sections 以及 Section Headers
section是可执行文件的实际数据的容器,headers数据之后就是section部分的数据。
一些特殊的section的名字就暗示了他们的作用,我们会过一遍其中的section,完整的列表可以在微软官方文档的"Special Section"小节找到。
.text
: 包含应用程序的可执行代码。.data
: 包含初始化过的数据。.bss
: 包含未初始化的数据。.rdata
: 包含只读的初始化过的数据。.edata
: 包含导出表 export tables。.idata
: 包含导入表 import tables。.reloc
: 包含镜像重定位信息。.rsrc
: 包含程序使用的资源,比如图片,内嵌的二进制代码等。.tls
: 线程本地存储(Thread Local Storage), 为进程的执行线程提供存储。
Section Headers
在optional header和section中间的部分就是section headers。这些header包含文件中section的相关信息。
一个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;
https://learn.microsoft.com/en-us/windows/win32/api/winnt/ns-winnt-image_section_header
Name
:对应section的名称,字符数组大小是8字节。PhysicalAddress
或者VirtualSize
:union对相同事物定义的不同的名称。这个字段表示 在内存中这个section的大小。VirtualAddress
:The address of the first byte of the section when loaded into memory, relative to the image base. For object files, this is the address of the first byte before relocation is applied.SizeOfRawData
:在磁盘上这个section的大小,必须是IMAGE_OPTIONAL_HEADER.FileAlignment
的倍数。SizeOfRawData
和PhysicalAddress
的大小可能不一样,下面会讨论这点。PointerToRawData
:指针指向文件里该section第一页的位置,对于可执行镜像文件,这个字段必须是IMAGE_OPTIONAL_HEADER.FileAlignment
的倍数。PointerToRelocations
:指针指向该section重定位信息开始的位置。对于可执行文件来说,这个字段的值是0.PointerToLineNumbers
:A file pointer to the beginning of COFF line-number entries for the section. 由于调试信息被作废,这个字段为0.NumberOfRelocations
: 该secion重定位信息的个数,如果是可执行镜像文件,值为0.NumberOfLinenumbers
:The number of COFF line-number entries for the section, 由于调试信息被作废,这个字段为0.Characteristics
:描述该section特征的flag。这些特征包含该section是否包含可执行代码,是否包含初始化/未初始化数据,是否能在内存中被共享。完整的特征清单可以访问官方信息。
SizeOfRawData
和VirtualSize
可能不同,原因有很多种。
SizeOfRawData
必须是IMAGE_OPTIONAL_HEADER.FileAlignment
的倍数,如果section大小比那个值小,SizeOfRawData
就会近似到最近的IMAGE_OPTIONAL_HEADER.FileAlignment
的倍数。
但是当section被加载到内存时,并没有这种对齐规则的限制,因此内存中只会占用section实际的大小。
这种情况下,SizeOfRawData
会比VirtualSize
大。
相反的情况也有可能发生。
如果section包含未被初始化的数据,这些数据在磁盘上不会占用空间,但是当这些section被映射到内存时,这些section会展开给未被初始化的数据提供内存空间。
这意味着这个section在磁盘上相对于内存中会占用更少的空间,这种情况就是VirtualSize
比SizeOfRawData
大。
然后让我们看一下PE-Bear中Secton Headers的样子。
我们可以看到有 Raw Addr
和Virtual Addr
列,这些其实对应的是IMAGE_SECTION_HEADER.PointerToRawData
和IMAGE_SECTION_HEADER.VirtualAddress
.
Raw Size
和 Virtual Size
对应的是IMAGE_SECTION_HEADER.SizeOfRawData
和IMAGE_SECTION_HEADER.VirtualSize
.
我们可以看到这两个字段是如何用于计算section的结束位置的(磁盘和内存)。
比如我们可以拿.text
section举例,它的在磁盘上的地址是0x400以及磁盘上的大小是0xE00,如果我们将两者相加,我们可以得到0x1200就是磁盘上该section结束的位置。
类似的,我们可以计算内存中的结束位置,将虚拟地址0x1000加上内存中的大小0xD2c,我们就得到0x1D2C。
Characteristics
会标记某些section为read-only,某些为read-write以及一些事readable和executable
PointerToRelocations
, NumberOfRelocations
and NumberOfLinenumbers
按照预期会被设置为0.
总结
That’s it for this post, we’ve discussed what Data Directories are and we talked about sections.
The next post will be about PE imports.
Thanks for reading.