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的倍数。SizeOfRawDataPhysicalAddress的大小可能不一样,下面会讨论这点。
  • 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是否包含可执行代码,是否包含初始化/未初始化数据,是否能在内存中被共享。完整的特征清单可以访问官方信息

SizeOfRawDataVirtualSize 可能不同,原因有很多种。

SizeOfRawData 必须是IMAGE_OPTIONAL_HEADER.FileAlignment的倍数,如果section大小比那个值小,SizeOfRawData就会近似到最近的IMAGE_OPTIONAL_HEADER.FileAlignment的倍数。
但是当section被加载到内存时,并没有这种对齐规则的限制,因此内存中只会占用section实际的大小。
这种情况下,SizeOfRawData会比VirtualSize大。

相反的情况也有可能发生。
如果section包含未被初始化的数据,这些数据在磁盘上不会占用空间,但是当这些section被映射到内存时,这些section会展开给未被初始化的数据提供内存空间。
这意味着这个section在磁盘上相对于内存中会占用更少的空间,这种情况就是VirtualSizeSizeOfRawData大。

然后让我们看一下PE-Bear中Secton Headers的样子。

我们可以看到有 Raw AddrVirtual Addr列,这些其实对应的是IMAGE_SECTION_HEADER.PointerToRawDataIMAGE_SECTION_HEADER.VirtualAddress.
Raw Size Virtual Size 对应的是IMAGE_SECTION_HEADER.SizeOfRawDataIMAGE_SECTION_HEADER.VirtualSize.
我们可以看到这两个字段是如何用于计算section的结束位置的(磁盘和内存)。

比如我们可以拿.textsection举例,它的在磁盘上的地址是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.

posted @ 2024-11-29 21:58  dewxin  阅读(52)  评论(0编辑  收藏  举报