前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >APT之旅 - PE静态内容结构

APT之旅 - PE静态内容结构

作者头像
Creaper
发布2023-11-20 12:52:26
2390
发布2023-11-20 12:52:26
举报
文章被收录于专栏:锦鲤安全锦鲤安全

一、前言

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

二、PE 结构

1. DOS Header

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

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

2. NT Headers

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

代码语言:javascript
复制
typedef struct _IMAGE_NT_HEADERS {
  DWORD                   Signature;
  IMAGE_FILE_HEADER       FileHeader;
  IMAGE_OPTIONAL_HEADER32 OptionalHeader;
} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;
(1)File Header

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

代码语言:javascript
复制
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;

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

(2)Optional Header

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

代码语言:javascript
复制
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;
(3)DataDirectory

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

#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 这么大。

代码语言:javascript
复制
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;

4. 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 的属性页,就可以看到其大小和程序在磁碟槽里面的大小区别:

需要注意的是,对于 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宏返回已知结构类型中命名字段的字节偏移量:

可以看到其算法为 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 文件:

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

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

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

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

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

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

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

四、参考文献

[1] Windows APT Warfare

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

锦鲤安全

一个安全技术学习与工具分享平台

点分享

点收藏

点点赞

点在看

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2023-07-29,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 锦鲤安全 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、前言
  • 二、PE 结构
    • (1)File Header
      • (2)Optional Header
        • (3)DataDirectory
          • 3. Section Headers
            • 4. PE 结构总结
              • (1)微软获取 Section Headers 位置的宏定义
          • 三、PE 解析器编写
          • 四、参考文献
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档