逆向工程核心原理03

逆向工程核心原理03

exdoubled Lv4

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
typedef struct _IMAGE_DOS_HEADER {      // DOS .EXE header
WORD e_magic; // Magic number
WORD e_cblp; // Bytes on last page of file
WORD e_cp; // Pages in file
WORD e_crlc; // Relocations
WORD e_cparhdr; // Size of header in paragraphs
WORD e_minalloc; // Minimum extra paragraphs needed
WORD e_maxalloc; // Maximum extra paragraphs needed
WORD e_ss; // Initial (relative) SS value
WORD e_sp; // Initial SP value
WORD e_csum; // Checksum
WORD e_ip; // Initial IP value
WORD e_cs; // Initial (relative) CS value
WORD e_lfarlc; // File address of relocation table
WORD e_ovno; // Overlay number
WORD e_res[4]; // Reserved words
WORD e_oemid; // OEM identifier (for e_oeminfo)
WORD e_oeminfo; // OEM information; e_oemid specific
WORD e_res2[10]; // Reserved words
LONG e_lfanew; // File address of new exe header
} IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;

IMAGE_DOS_HEADER 结构体的大小为 64 字节,其中有两个重要成员

  • e_magic DOS 签名,比如签名是 4D5A (“MZ”)

  • e_lfanew 指向 PE 头的起始位置 IMAGE_NT_HEADERS 结构体

1240X121/3-1.png

可以看到,notepad.exe 文件的前两个字节是 4D5A

e_lfanew 的值是 000000E0,表示 PE 头从文件偏移 0xE0 处开始

下面是 dnSpy 分析的 Notepad.exe

1104X1429/3-3.png

DOS 存根

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

1249X301/3-2.png

可以看到会显示 “This program cannot be run in DOS mode.”

NT 头

1
2
3
4
5
typedef struct _IMAGE_NT_HEADERS {
DWORD Signature; // PE 签名 "PE\0\0"
IMAGE_FILE_HEADER FileHeader; // 文件头
IMAGE_OPTIONAL_HEADER OptionalHeader;// 可选头
} IMAGE_NT_HEADERS, *PIMAGE_NT_HEADERS;

可以看到 NT 头由三个成员组成

第一个成员为 PE 签名,值为 50 45 00 00 (“PE\0\0”)

第二个成员为文件头 IMAGE_FILE_HEADER 结构体,第三个成员为可选头 IMAGE_OPTIONAL_HEADER 结构体

文件头

1
2
3
4
5
6
7
8
9
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;

文件头是表现文件大致属性的结构体

有下面四个重要成员

Machine

每个 CPU 都有唯一的 machine 码,兼容32位 Intel x86 的值为 0x014C

下面是 winnt.h 中的 machine 码定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#define IMAGE_FILE_MACHINE_UNKNOWN 0
#define IMAGE_FILE_MACHINE_I386 0x014c // Intel 386
#define IMAGE_FILE_MACHINE_R3000 0x0162 // MIPS little-endian, 0x160 big-endian
#define IMAGE_FILE_MACHINE_R4000 0x0166 // MIPS little-endian
#define IMAGE_FILE_MACHINE_R10000 0x0168 // MIPS little-endian
#define IMAGE_FILE_MACHINE_WCEMIPSV2 0x0169 // MIPS little-endian WCE v2
#define IMAGE_FILE_MACHINE_ALPHA 0x0184 // Alpha_AXP
#define IMAGE_FILE_MACHINE_SH3 0x01a2 // SH3 little-endian
#define IMAGE_FILE_MACHINE_SH3DSP 0x01a3 // SH3-DSP
#define IMAGE_FILE_MACHINE_SH3E 0x01a4 // SH3E little-endian
#define IMAGE_FILE_MACHINE_SH4 0x01a6 // SH4 little-endian
#define IMAGE_FILE_MACHINE_SH5 0x01a8 // SH5
#define IMAGE_FILE_MACHINE_ARM 0x01c0 // ARM Little-Endian
#define IMAGE_FILE_MACHINE_ARMV7 0x01c4 // ARMv7 (or higher) Thumb mode only
#define IMAGE_FILE_MACHINE_ARMNT 0x01c4 // ARMv7 (or higher) Thumb mode only
#define IMAGE_FILE_MACHINE_ARM64 0xaa64 // ARM64 Little-Endian
#define IMAGE_FILE_MACHINE_THUMB 0x01c2 // ARM Thumb-2 Little-Endian
#define IMAGE_FILE_MACHINE_AM33 0x01d3 // Matsushita AM33
#define IMAGE_FILE_MACHINE_POWERPC 0x01F0 // IBM PowerPC Little-Endian
#define IMAGE_FILE_MACHINE_POWERPCFP 0x01f1 // IBM PowerPC with floating point support
#define IMAGE_FILE_MACHINE_IA64 0x0200 // Intel Itanium
#define IMAGE_FILE_MACHINE_MIPS16 0x0266 // MIPS16
#define IMAGE_FILE_MACHINE_ALPHA64 0x0284 // ALPHA64
#define IMAGE_FILE_MACHINE_MIPSFPU 0x0366 // MIPS with FPU
#define IMAGE_FILE_MACHINE_MIPSFPU16 0x0466 // MIPS16 with FPU
#define IMAGE_FILE_MACHINE_AXP64 IMAGE_FILE_MACHINE_ALPHA64 // ALPHA64
#define IMAGE_FILE_MACHINE_TRICORE 0x0520 // Infineon
#define IMAGE_FILE_MACHINE_CEF 0x0CEF // Common Execution Format
#define IMAGE_FILE_MACHINE_EBC 0x0EBC // EFI Byte Code
#define IMAGE_FILE_MACHINE_AMD64 0x8664 // AMD64 (K8)
#define IMAGE_FILE_MACHINE_M32R 0x9041 // M32R little-endian
#define IMAGE_FILE_MACHINE_CEE 0xc0ee // Common Execution Environment

NumberOfSections

PE文件把代码,数据,资源等依据属性分类到各节区中存储,NumberOfSection 用来指出文件中存在的节区数量

该值一定要大于0,且当定义的节区数量与实际截取不同时,将发生运行错误

SizeOfOptionalHeader

IMAGE_OPTIONAL_HEADER 结构体的大小

PE32+格式文件中使用的是 IMAGE_OPTIONAL_HEADER64 结构体,其大小为 240 字节

所以需要在 SizeOfOptionalHeader 成员中指明结构体大小

Characteristics

Characteristics用于标识文件的属性,文件是否是可运行的形态,是否为DLL文件等信息,以bit OR形式组合起来

下面是定义在winnt.h中的Characteristics标志

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#define IMAGE_FILE_RELOCS_STRIPPED 0x0001		// Relocation info stripped from file
#define IMAGE_FILE_EXECUTABLE_IMAGE 0x0002 // File is executable (i.e. no unresolved externel references)
#define IMAGE_FILE_LINE_NUMS_STRIPPED 0x0004 // Line numbers stripped from file
#define IMAGE_FILE_LOCAL_SYMS_STRIPPED 0x0008 // Local symbols stripped from file
#define IMAGE_FILE_AGGRESIVE_WS_TRIM 0x0010 // Aggressively trim working set
#define IMAGE_FILE_LARGE_ADDRESS_AWARE 0x0020 // App can handle >2gb addresses
#define IMAGE_FILE_BYTES_REVERSED_LO 0x0080 // Bytes of machine word are reversed.
#define IMAGE_FILE_32BIT_MACHINE 0x0100 // 32 bit word machine
#define IMAGE_FILE_DEBUG_STRIPPED 0x0200 // Debugging info stripped from file in .DBG file
#define IMAGE_FILE_REMOVABLE_RUN_FROM_SWAP 0x0400 // If Image is on removable media, copy and run from the swap file.
#define IMAGE_FILE_NET_RUN_FROM_SWAP 0x0800 // If Image is on Net, copy and run from the swap file.
#define IMAGE_FILE_SYSTEM 0x1000 // System File
#define IMAGE_FILE_DLL 0x2000 // File is a DLL
#define IMAGE_FILE_UP_SYSTEM_ONLY 0x4000 // File should only be run on a UP machine
#define IMAGE_FILE_BYTES_REVERSED_HI 0x8000 // Bytes of machine word are reversed.

主要记住0x00020x2000

701X786/3-4.png

可选头

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
    typedef struct _IMAGE_DATA_DIRECTORY {
DWORD VirtualAddress;
DWORD Size;
} IMAGE_DATA_DIRECTORY,*PIMAGE_DATA_DIRECTORY;

#define IMAGE_NUMBEROF_DIRECTORY_ENTRIES 16

typedef struct _IMAGE_OPTIONAL_HEADER {

// Standard fields.
WORD Magic;
BYTE MajorLinkerVersion;
BYTE MinorLinkerVersion;
DWORD SizeOfCode;
DWORD SizeOfInitializedData;
DWORD SizeOfUninitializedData;
DWORD AddressOfEntryPoint;
DWORD BaseOfCode;
DWORD BaseOfData;

// NT additional fields.
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;

需要关注以下的成员,如果他们设置不正确,可能导致文件无法正常运行

Magic

  • IMAGE_OPTIONAL_HEADER32时,Magic为10B

  • IMAGE_OPTIONAL_HEADER64时,Magic为20B

AddressOfEntryPoint

拥有EPRVA值,指出程序最先执行的代码起始地址

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的值可能相同也可能不相同

磁盘文件或内存的节区大小必定为 FileAlignmentSectionAlignment 的整数倍

SizeOfImage

加载PE文件到内存时,SizeOfImage指定了PE Image在虚拟内存中所占的空间大小

一般而言,文件的大小与加载到内存中的大小是不同的

SizeOfHeaders

SizeOfHeaders用来指出整个PE头的大小,由于该值也必须是FileAlignment的整数倍,因此第一节区位置和 SizeOfHeaders 距文件开始偏移的量相同

Subsystem

Subsystem的值用来区分系统驱动文件*.sys 和普通的可执行文件 *.exe, *.dll

含义备注
1Driver文件系统驱动(*.sys)
2GUI文件窗口应用程序(ida.exe)
3CUI文件控制台应用程序(cmd.exe)

NumberOfRvaAndSizes

用来指定DataDirectory(IMAGE_OPTIONAL_HEADER32最后一个成员)数组的个数

DataDirectory

IMAGE_DATA_DIRECTORY 结构体数组组成,每个成员包含两个 DWORD 成员,分别表示某个数据目录的 RVA 和大小

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
DataDirectory[O] = EXPORT Directory
DataDirectory[1] = IMPOTR Directory
DataDirectory[2] = RESOURCE Directory
DataDirectory[3] = EXCEPTION Directory
DataDirectory[4] = SECURITY Directory
DataDirectory[5] = BASERELOC Directory
DataDirectory[6] = DEBUG Directory
DataDirectory[7] = COPYRIGHT Directory
DataDirectory[8] = GLOBALPTR Directory
DataDirectory[9] = TLS Directory
DataDirectory[A] = LOAD_CONFIG Directory
DataDirectory[B] = BOUND_IMPORT Directory
DataDirectory[C] = IAT Directory
DataDirectory[D] = DELAY_IMPORT Directory
DataDirectory[E] = COM_DESCRIPTOR Directory
DataDirectory[F] = Reserver Directory

主要注意 EXPORT DirectoryIMPORT Directory 还有 TLS Directory

下图是 notepad.exe 的可选头部分

796X3255/3-5.png

节区头

节区头定义了个节区的属性

类别访问权限
code执行,读取权限
data非执行,读写权限
resource非执行,读取权限

把具有相同属性的内容放在同一个节区中,方便操作系统对节区进行保护

假设不这样做,容易引发安全问题

节区头由 IMAGE_SECTION_HEADER 结构体定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
   typedef struct _IMAGE_SECTION_HEADER {
BYTE Name[IMAGE_SIZEOF_SHORT_NAME];
union {
DWORD PhysicalAddress;
DWORD VirtualSize; // S4:内存大小
} Misc;
DWORD VirtualAddress; // S3:内存地址:基于模块基址
DWORD SizeOfRawData; // S2:文件大小
DWORD PointerToRawData; // S1:文件偏移
DWORD PointerToRelocations;
DWORD PointerToLinenumbers;
WORD NumberOfRelocations;
WORD NumberOfLinenumbers;
DWORD Characteristics; // 节属性,取值IMAGE_SCN_...系列宏(bit OR)
} IMAGE_SECTION_HEADER,*PIMAGE_SECTION_HEADER;

下面列出了重要成员:

项目含义
VirtualSize内存中节区所占大小
VirtualAddress内存中节区起始地址(RVA)
SizeOfRawData磁盘文件中节区所占大小
PointerToRawData磁盘文件中节区起始位置
Characteristics节区属性
694X1074/3-6.png
673X1079/3-7.png
682X1084/3-8.png

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.
Comments