APT之旅 - PE静态内容结构

admin 2023年7月29日14:52:57评论10 views字数 4866阅读16分13秒阅读模式
APT之旅 - PE静态内容结构

⼀、前⾔⼆、PE 结构   1. DOS Header  2. NT Headers     (1)File Header     (2)Optional Header     (3)DataDirectory   3. Section Headers   4. PE 结构总结     (1)微软获取 Section Headers 位置的宏定义 三、PE 解析器编写 四、参考⽂献

本文来自公众号:锦鲤安全

一、前言

PE 一种文件格式,在Windows操作系统上的执行可执行文件(.exe)、动态链接库(.dll)、驱动程序以及其他可执行文件类型都是 PE 格式。了解其格式对恶意分析及使用高级的攻击手法有很大的帮助,很多高级的攻击手段都需要对 PE、PEB 有详细的了解。

二、PE 结构

APT之旅 - PE静态内容结构

1. DOS Header

DOS Header 用于兼容早期的DOS系统,此结构中的 e_magic 必定等于"MZ",大小不固定,大多数结构没有用,只需要记住结构中的 e_lfranew 位指明了 NT 头的所在位置,它的作用就是获取 NT Headers 的位置起点 RVA。

RVA(Relative Virtual Address),即相对于 PE 内容起点(基址)的偏移。

APT之旅 - PE静态内容结构
2. NT Headers

NT Headers 同 DOS Header 的 e_magic 位一样有一个校验结构是否有效的位 Signature,其值必定等于"PEx00x00",NT Headers 还包含了两个结构体:File Header 和 Optional Header:

typedef struct _IMAGE_NT_HEADERS {  DWORD                   Signature;  IMAGE_FILE_HEADER       FileHeader;  IMAGE_OPTIONAL_HEADER32 OptionalHeader;} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;

APT之旅 - PE静态内容结构

(1)File Header

File Header 包含了PE文件的一些基本信息,如文件类型、目标CPU等。

typedef struct _IMAGE_FILE_HEADER {  WORD  Machine;  WORD  NumberOfSections;  DWORD TimeDateStamp;  DWORD PointerToSymbolTable;  DWORD NumberOfSymbols;  WORD  SizeOfOptionalHeader;  WORD  Characteristics;} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;

APT之旅 - PE静态内容结构

*表格加灰表示不重要条目。

(2)Optional Header

Optional Header 是由编译最后一阶段由编译器补上的资讯,包含了 PE 文件的一些可选信息,如程序入口点、内存对齐方式等。

typedef struct _IMAGE_OPTIONAL_HEADER {  WORD                 Magic;  BYTE                 MajorLinkerVersion;  BYTE                 MinorLinkerVersion;  DWORD                SizeOfCode;  DWORD                SizeOfInitializedData;  DWORD                SizeOfUninitializedData;  DWORD                AddressOfEntryPoint;  DWORD                BaseOfCode;  DWORD                BaseOfData;  DWORD                ImageBase;  DWORD                SectionAlignment;  DWORD                FileAlignment;  WORD                 MajorOperatingSystemVersion;  WORD                 MinorOperatingSystemVersion;  WORD                 MajorImageVersion;  WORD                 MinorImageVersion;  WORD                 MajorSubsystemVersion;  WORD                 MinorSubsystemVersion;  DWORD                Win32VersionValue;  DWORD                SizeOfImage;  DWORD                SizeOfHeaders;  DWORD                CheckSum;  WORD                 Subsystem;  WORD                 DllCharacteristics;  DWORD                SizeOfStackReserve;  DWORD                SizeOfStackCommit;  DWORD                SizeOfHeapReserve;  DWORD                SizeOfHeapCommit;  DWORD                LoaderFlags;  DWORD                NumberOfRvaAndSizes;  IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;

APT之旅 - PE静态内容结构

APT之旅 - PE静态内容结构

APT之旅 - PE静态内容结构

(3)DataDirectory

在(2)中最后的 DataDirectory 数组索引取值可以是以下值之一:

APT之旅 - PE静态内容结构

#1 和 # 12 看起来比较相似,实际上 idata 整个区段都是 #12 全局导入函数地址表,而 #12 上的函数需要引用自那个 DLL 则是记录在 #1 中的 IMAGE_IMPORT_DESCRIPTOR 位。

3. Section Headers

编译过程中将源码转换为了多个 Section Data 区段,每一个 Section Data 的大小、起点位置、执行时存放的位置都不一样,因此需要用 Section Header 来记录。NT Headers 的结尾处就是 Section Headers 的起点,而 NT Headers 大小固定,因此从 DOS Header 的 e_magic("MZ") 位置出发很容易手工爬取到 Section Headers 位置。

Section Headers 是 Section Header (IMAGE_SECTION_HEADER) 结构数组,从 NT Headers -> File Header -> NumberOfSections 获取到了 Section Headers 数组的大小为 3,那么其占用空间就是 sizeof(IMAGE_SECTION_HEADER) * 3 这么大。

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;

APT之旅 - PE静态内容结构

4. PE 结构总结

APT之旅 - PE静态内容结构

整个程序从 offset=0 处即 DOS Header 到程序的最后一处 EOF 的所有块状区域经 File Alignment 对齐之后都是紧密贴合的没有任何空隙。因此,理论上最后区段即 Section Headers 数组的最后一项的 PointerToRawData + SizeOfRawData 正好等于你使用 WinAPI GetFileSize 或 ftell 函数计算出来的大小,而整个程序在磁碟槽里面的大小则为下面两者相加:

  1. DOS Header + NT Headers + Section Headers 的总大小对 File Alignment 对齐之后占用的大小。

  2. 各个 Section Data 对 File Alignment 对齐之后占用的大小之和。

我们随便打开一个 exe 的属性页,就可以看到其大小和程序在磁碟槽里面的大小区别:

APT之旅 - PE静态内容结构

需要注意的是,对于 Section Header 中 SizeOfRawData 和 Misc.VirtualSize 两项,当程序开发时所有全局变量都没有被分配初始值,而是执行时才写入这些变量,那么 .data Data 或 .bss Data 则可能出现:静态内容没有初值,但却要在执行时分配空间的状况导致 SizeOfRawData 为 0 但是 Misc.VirtualSize 有值的情况。

(1)微软获取 Section Headers 位置的宏定义

在 winnt.h 文件中,能找到微软获取 Section Headers 位置的宏定义,引入 windows.h 后自动引入,其中使用了 FIELD_OFFSET 宏,根据微软文档,FIELD_OFFSET宏返回已知结构类型中命名字段的字节偏移量:

APT之旅 - PE静态内容结构

可以看到其算法为 NT Headers 地址 + Optional Header 在 NT Headers 中的偏移 + Optional Header 占用的大小,正好是 NT Headers 结构的末端,就是 Section Headers 的起址。

需要注意的是,并不能通过简单的 NT Headers 地址 + sizeof(IMAGE_NT_HEADERS) 获取 Section Headers 的地址。根据C/C++语言的标准,结构体中成员的排列是按照声明的顺序进行的,但由于编译器对结构体进行了字节对齐和填充,结构体的实际大小可能比成员大小之和要大。所以,通过 NT Headers 地址 + sizeof(IMAGE_NT_HEADERS) 来获取节区表的地址是不可行的,因为 IMAGE_NT_HEADERS 结构体的大小已经包含了 IMAGE_FILE_HEADER 和 IMAGE_OPTIONAL_HEADER 的大小,并且在内存中的排列情况可能会有填充字节。

三、PE 解析器编写

根据之前的内容,我们需要读取一个 PE 文件内容,其返回指针就是 DOS Header 地址,然后根据 DOS Header->e_lfanew 获取到 NT Headers 地址,然后使用微软的 NT Headers 地址 + Optional Header 在 NT Headers 中的偏移 + Optional Header 占用的大小的方法获取 Section Headers 数组地址。

首先,编写一个函数读取 PE 文件:

APT之旅 - PE静态内容结构

读取到 PE 文件内容并保存到 pe_content 指针中,然后直接转换为 PIMAGE_DOS_HEADER 结构就是 DOS Header 了:

APT之旅 - PE静态内容结构

通过 DOS Header->e_lfanew 获取到 NT Headers 地址:

APT之旅 - PE静态内容结构

到打印 Optional Header 的时候需要注意,IMAGE_OPTIONAL_HEADER 结构体还有 IMAGE_OPTIONAL_HEADER32 与 IMAGE_OPTIONAL_HEADER64 之分,不同区别是其中的指针变量,在 32 位下是 4 字节在 64 位下是 8 字节,你也可以用 IMAGE_OPTIONAL_HEADER32 结构体去解析 64 位,其仍然可以正常显示,因为结构体是向后兼容的,但在某些数据上可能会出错,如 ImageBase 字段。

最好通过 File Header 的 Machine 字段判断 PE 文件的架构后再调用对应的结构体进行解析:

APT之旅 - PE静态内容结构

通过微软的 IMAGE_FIRST_SECTION 宏定义加 NT Headers 地址获取到 Section Headers 数组地址,再通过 File Header 的 NumberOfSections 字段获取到数组的大小,循环遍历数组打印 Section Header 信息,并在最后一个 Section Header 打印 PointerToRawData + SizeOfRawData 的值验证是否等于我们用 WinAPI GetFileSize 或 ftell 函数计算出来的大小:

APT之旅 - PE静态内容结构

执行打印出来 DOS Header、NT Headers、Section Headers 的信息,可以看到用 ftell 计算的文件大小 326656:

APT之旅 - PE静态内容结构

最后一个节点打印 PointerToRawData + SizeOfRawData 的值,可以看到同样是 326656:

APT之旅 - PE静态内容结构

四、参考文献

[1] Windows APT Warfare

[2] https://learn.microsoft.com/zh-cn/windows/win32/api/winnt/

APT之旅 - PE静态内容结构

原文始发于微信公众号(棉花糖网络安全圈):APT之旅 - PE静态内容结构

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2023年7月29日14:52:57
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   APT之旅 - PE静态内容结构http://cn-sec.com/archives/1916801.html

发表评论

匿名网友 填写信息