逆向工程核心原理03
PE 文件格式
PE(Portable Executable)文件格式是 Windows 操作系统中可执行文件、对象代码和 DLL 文件的标准格式。它基于 Microsoft 的 COFF(Common Object File Format)规范,并添加了一些特定于 Windows 的扩展。
| 主扩展名 | 种类 |
|---|---|
| 可执行系列 | EXE、SCR |
| 驱动文件系列 | SYS、VXD |
| 库系列 | DLL、OCX、CPL、DRV |
| 对象文件系列 | OBJ |
严格来说,除了 OBJ 文件以外所有文件都是可执行的
DLL、SYS 文件虽然不能在 shell 中直接运行,但可以通过调试器、服务等执行
下面以 Windows XP SE3 的 notepad.exe 为例,介绍 PE 文件格式的基本结构
| DOS 头 |
|---|
| DOS 存根 |
| NT 头 |
| 节区头(.text) |
| 节区头(.data) |
| 节区头(.rsrc) |
| NULL |
| 节区(.text) |
| NULL |
| 节区(.data) |
| NULL |
| 节区(.rsrc) |
| NULL |
从 DOS 头到节区头是 PE 头部分,其下的节区合称 PE 体,文件中使用偏移(offset),内存中使用虚拟地址(VA) 来定位各个部分
根据所用的不同开发工具(VB/VC++/Delphi/etc)与编译选项,节区的名称、大小、个数、存储的内容等都是不同的,最重要的是它们按照不同的用途分类保存到不同的节中
PE 头和各节区的尾部存在一个区域,称为 NULL 填充区,用于对齐节区,使得节区在文件中和内存中的起始位置符合对齐要求
VA 和 RVA
VA 指的是进程虚拟内存的绝对地址,RVA(Relative Virtual Address)指从某个基准位置(ImageBase)开始的相对偏移地址 \[ RVA+ImageBase=VA \] PE 头内部信息大多以 RVA 形式存在,PE 文件加载到进程虚拟内存的特定位置时,该位置可能已经加载了其他 PE 文件,此时需要重定位将其加载到其他空白位置,因此 RVA 只要相对基准位置的相对地址没有变化,就能正常访问到指定信息不会出现问题
PE 头
DOS 头
微软考虑了 PE 文件对 DOS 文件的兼容性,结果就是在 PE 头的最前面添加了一个 IMAGE_DOS_HEADER 结构体,用来扩展已有的 DOS EXE 头
1 | typedef struct _IMAGE_DOS_HEADER { // DOS .EXE header |
IMAGE_DOS_HEADER 结构体的大小为 64 字节,其中有两个重要成员
e_magicDOS 签名,比如签名是4D5A(“MZ”)e_lfanew指向 PE 头的起始位置IMAGE_NT_HEADERS结构体

可以看到,notepad.exe 文件的前两个字节是 4D5A
e_lfanew 的值是 000000E0,表示 PE 头从文件偏移 0xE0 处开始
下面是 dnSpy 分析的 Notepad.exe

DOS 存根
DOS 存根在 DOS 头下方,是可选项且大小不固定,由代码和数据混合而成,一般用于在 DOS 环境下显示一条消息,提示用户该程序只能在 Windows 环境下运行

可以看到会显示 “This program cannot be run in DOS mode.”
NT 头
1 | typedef struct _IMAGE_NT_HEADERS { |
可以看到 NT 头由三个成员组成
第一个成员为 PE 签名,值为 50 45 00 00 (“PE\0\0”)
第二个成员为文件头 IMAGE_FILE_HEADER 结构体,第三个成员为可选头 IMAGE_OPTIONAL_HEADER 结构体
文件头
1 | typedef struct _IMAGE_FILE_HEADER { |
文件头是表现文件大致属性的结构体
有下面四个重要成员
Machine
每个 CPU 都有唯一的 machine 码,兼容32位 Intel x86 的值为 0x014C
下面是 winnt.h 中的 machine 码定义
1 |
NumberOfSections
PE文件把代码,数据,资源等依据属性分类到各节区中存储,NumberOfSection 用来指出文件中存在的节区数量
该值一定要大于0,且当定义的节区数量与实际截取不同时,将发生运行错误
SizeOfOptionalHeader
IMAGE_OPTIONAL_HEADER 结构体的大小
PE32+格式文件中使用的是 IMAGE_OPTIONAL_HEADER64 结构体,其大小为 240 字节
所以需要在 SizeOfOptionalHeader 成员中指明结构体大小
Characteristics
Characteristics用于标识文件的属性,文件是否是可运行的形态,是否为DLL文件等信息,以bit OR形式组合起来
下面是定义在winnt.h中的Characteristics标志
1 |
主要记住0x0002和0x2000

可选头
1 | typedef struct _IMAGE_DATA_DIRECTORY { |
需要关注以下的成员,如果他们设置不正确,可能导致文件无法正常运行
Magic
IMAGE_OPTIONAL_HEADER32时,Magic为10BIMAGE_OPTIONAL_HEADER64时,Magic为20B
AddressOfEntryPoint
拥有EP的RVA值,指出程序最先执行的代码起始地址
ImageBase
进程虚拟内存的范围是0~0xFFFFFFFF(32位系统)
ImageBase指出文件的优先装入地址
exe、dll 文件被装载到用户内存的 0~0x7FFFFFFF 范围内
sys 文件被装载到内核内存的 0x80000000~0xFFFFFFFF 范围内
一般来说,使用开发工具编译生成的 exe 文件,ImageBase 默认为 0x400000,dll 文件默认为 0x10000000,sys 文件默认为 0x80000000
执行 PE 文件时,PE 装载器先创建进程,再将文件载入内存,然后把 EIP 指向 ImageBase + AddressOfEntryPoint 处开始执行
SectionAlignment, FileAlignment
FileAlignment指定了节区在磁盘文件中的最小单位
SectionAlignment指定了节区在内存中的最小单位
一个文件中SectionAlignment, FileAlignment的值可能相同也可能不相同
磁盘文件或内存的节区大小必定为 FileAlignment 或 SectionAlignment 的整数倍
SizeOfImage
加载PE文件到内存时,SizeOfImage指定了PE Image在虚拟内存中所占的空间大小
一般而言,文件的大小与加载到内存中的大小是不同的
SizeOfHeaders
SizeOfHeaders用来指出整个PE头的大小,由于该值也必须是FileAlignment的整数倍,因此第一节区位置和 SizeOfHeaders 距文件开始偏移的量相同
Subsystem
Subsystem的值用来区分系统驱动文件*.sys 和普通的可执行文件 *.exe, *.dll
| 值 | 含义 | 备注 |
|---|---|---|
| 1 | Driver文件 | 系统驱动(*.sys) |
| 2 | GUI文件 | 窗口应用程序(ida.exe) |
| 3 | CUI文件 | 控制台应用程序(cmd.exe) |
NumberOfRvaAndSizes
用来指定DataDirectory(IMAGE_OPTIONAL_HEADER32最后一个成员)数组的个数
DataDirectory
由 IMAGE_DATA_DIRECTORY 结构体数组组成,每个成员包含两个 DWORD 成员,分别表示某个数据目录的 RVA 和大小
1 | DataDirectory[O] = EXPORT Directory |
主要注意 EXPORT Directory 和 IMPORT Directory 还有 TLS Directory
下图是 notepad.exe 的可选头部分

节区头
节区头定义了个节区的属性
| 类别 | 访问权限 |
|---|---|
| code | 执行,读取权限 |
| data | 非执行,读写权限 |
| resource | 非执行,读取权限 |
把具有相同属性的内容放在同一个节区中,方便操作系统对节区进行保护
假设不这样做,容易引发安全问题
节区头由 IMAGE_SECTION_HEADER 结构体定义
1 | typedef struct _IMAGE_SECTION_HEADER { |
下面列出了重要成员:
| 项目 | 含义 |
|---|---|
| VirtualSize | 内存中节区所占大小 |
| VirtualAddress | 内存中节区起始地址(RVA) |
| SizeOfRawData | 磁盘文件中节区所占大小 |
| PointerToRawData | 磁盘文件中节区起始位置 |
| Characteristics | 节区属性 |



RVA to RAW
下面说明 PE 文件从磁盘到内存的映射关系
- 查找 RVA 所在节区
- 使用公式计算文件偏移 RAW
根据 IMAGE_SECTION_HEADER 结构体,公式如下 \[ RAW - PointerToRawData = RVA - VirtualAddress \]
\[ RAW= RVA - VirtualAddress + PointerToRawData \]
其中VirtualAddress为RVA所在节区的VA(VA - ImageBase)地址,PointerToRawData为RVA所在节区对应在文件中节区的偏移(Offset)
IAT
EAT
- Title: 逆向工程核心原理03
- Author: exdoubled
- Created at : 2026-01-23 10:00:00
- Updated at : 2026-01-23 11:38:35
- Link: https://github.com/exdoubled/exdoubled.github.io.git/reverse/reverse3/
- License: This work is licensed under CC BY-NC-SA 4.0.