PE 文件结构全解析:从地址类型到头部结构详解
- 作者: 刘杰
- 来源: 技术那些事
- 阅读:247
- 发布: 2025-07-17 07:47
- 最后更新: 2025-07-17 07:47
PE文件
地址
PE中涉及的地址有四类:
-
虚拟内存地址(VA)
PE被加载到内存空间,在这个内存空间中定位的地址称为虚拟地址。
-
相对虚拟内存地址(RVA)
一个进程被加载到虚拟内存空间后,与其相关的动态链接库也会被加载。这些同时加载到内存空间的文件称为模块。每个模块在加载时都有一个基地址,也就是它会预先告诉操作系统,它会占用4G空间的哪一部分。不同模块的基地址是不同的,如果两个模块的基地址相同,就由操作系统来决定这两个模块在虚拟地址空间中的具体位置。
相对虚拟地址,是相对于模块基地址的偏移。即RVA是虚拟内存中用来定位某个特定位置的地址,该地址的值,是这个特定位置距离某个模块基地址的偏移量。所以说,RVA 是相对于某个模块而存在的。
-
文件偏移地址(FOA)
文件偏移地址和内存无关,它指的某个位置距离文件头的偏移地址。我们用一些HEX文件编辑工具或者静态调试工具(IDA)时候,显示的地址一般是文件偏移地址。只有这个模块加载到内存后,内存中的地址才是虚拟地址。
-
特殊地址
在PE结构中还有一类特殊地址,其计算方法并不是从头算起,也不是从内存的某个模块的基地址算起,而是从某个特定的位置算起。这种地址在PE结构中很少见,在资源表里就出现过这样的地址。
数据目录
目前已经定义的数据目录有15种,包括导出表,导入表,资源表,异常表,属性证书表,重定位表,调试数据,Architecture,Global Ptr,线程局部存储,加载配置表,绑定导入表,IAT,延迟导入表,CLR运行时头部。
节(section)
节就是存放不同类型的数据(比如代码、数据、常量、资源)的地方,不同的节具有不同的访问权限。节是PE文件中存放代码或者数据的基本单元。一个节中的所有原始数据必须被加载到连续的内存空间中。
从操作系统加载的角度来看,节是相同属性的数据的组合。与数据目录不同的是,尽管有些数据类型不同,分别属于不同的数据目录,但由于其访问属性相同,便被归类到一个节中。这个节最终可能会占用一个或者多个页(虚拟内存页);但是不论有多少个页,所有页都会被赋予相同的属性。这些属性包括,只读、只写、可读、可写。
汇编语言中,一些以.开头的伪指令,其实就是在声明不同的数据类型。
出于节约资源考虑,操作系统允许节在磁盘和在内存中的对齐尺寸不一样,这就直接造成了PE在文件中和在内存中的大小也不一致。通常PE在内存中的尺寸要比在磁盘文件中的尺寸大,用户可以自定义这些对齐的值。
注意:如果内存对齐被定义为小于操作系统页的大小,则文件对齐和内存对齐的值必须一致。
结构

Dos头结构
c
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; // 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;
NT头结构
c
typedef struct _IMAGE_NT_HEADERS {
DWORD Signature;
IMAGE_FILE_HEADER FileHeader;
IMAGE_OPTIONAL_HEADER32 OptionalHeader;
} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;
(1)PE文件标准PE头(用于判断PE文件是exe还是dll,得到节的总量)
c
typedef struct _IMAGE_FILE_HEADER {
WORD Machine; //运行平台
WORD NumberOfSections; //PE中节的数量
DWORD TimeDateStamp; //文件创建日期和时间
DWORD PointerToSymbolTable; //指向符号表(用于调试)
DWORD NumberOfSymbols; //符号表中的符号数量(用于调试)
WORD SizeOfOptionalHeader; //扩展头结构长度
WORD Characteristics; //文件属性
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;
(2)PE扩展头(虽然是扩展头,但更像真正的PE头,非常重要,包含各种属性)
c
typedef struct _IMAGE_OPTIONAL_HEADER {
//
// Standard fields.
//
WORD Magic; //魔幻字 0x10b 32位 0x20b 64位
BYTE MajorLinkerVersion; //链接器版本号
BYTE MinorLinkerVersion; //链接器版本号
DWORD SizeOfCode; //所有含代码节的总大小
DWORD SizeOfInitializedData; //初始化的数据长度。
DWORD SizeOfUninitializedData; //未初始化的数据长度。
DWORD AddressOfEntryPoint; //程序入口的RVA
DWORD BaseOfCode; //代码段起始地址的RVA
DWORD BaseOfData; //数据段起始地址的RVA
//
// NT additional fields.
//
DWORD ImageBase; //映象(加载到内存中的PE文件)的基地址,这个基地址是建议,对于DLL来说,如果无法加载到这个地址,系统会自动为其选择地址。
DWORD SectionAlignment; //内存中节对齐粒度,PE中的节被加载到内存时会按照这个域指定的值来对齐,比如这个值是0x1000,那么每个节的起始地址的低12位都为0。
DWORD FileAlignment; //文件中节对齐粒度,SectionAlignment必须大于或等于FileAlignment。
WORD MajorOperatingSystemVersion; //所需操作系统的版本号
WORD MinorOperatingSystemVersion; //
WORD MajorImageVersion; //映象的版本号,这个是开发者自己指定的,由连接器填写。
WORD MinorImageVersion; //
WORD MajorSubsystemVersion; //所需子系统版本号
WORD MinorSubsystemVersion; //
DWORD Win32VersionValue; //保留,必须为0。
DWORD SizeOfImage; //映象的大小,PE文件加载到内存中空间是连续的,这个值指定占用虚拟空间的大小。
DWORD SizeOfHeaders; //所有文件头(包括节表)的大小,这个值是以FileAlignment对齐的。
DWORD CheckSum; //映象文件的校验和。
WORD Subsystem; //运行该PE文件所需的子系统
WORD DllCharacteristics; //DLL的文件属性,只对DLL文件有效
DWORD SizeOfStackReserve; //运行时为每个线程栈保留内存的大小。
DWORD SizeOfStackCommit; //运行时为每个线程栈保留内存的大小。
DWORD SizeOfHeapReserve; //运行时为进程堆保留内存大小。
DWORD SizeOfHeapCommit; //运行时进程堆初始占用内存大小。
DWORD LoaderFlags; //保留,必须为0。
DWORD NumberOfRvaAndSizes; //数据目录的项数
IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES]; //数据目录,这是一个数组,定义了PE文件中所有出现的不同类型的数据目录信息
} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;
本头的第一个字节相对于nt头的偏移量为 0x18h,数据目录(DataDirectory)开始字节相对于nt头的偏移量为0x78h,所以除去数据目录(大小可变)之外的部分大小为 0x60,即96个字节大小。
数据目录项
总的数据目录一共由16个相同的_IMAGE_DATA_DIRECTORY组成。每一个代表一个类型的数据。
结构体
c
typedef struct _IMAGE_DATA_DIRECTORY {
DWORD VirtualAddress; //数据的起始RVA
DWORD Size; //数据块的大小
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;
节表项的数据结构
c
typedef struct _IMAGE_SECTION_HEADER {
BYTE Name[IMAGE_SIZEOF_SHORT_NAME]; //8个字节的节名
union {
DWORD PhysicalAddress; //节区大小
DWORD VirtualSize;
} Misc;
DWORD VirtualAddress; //节区RVA地址
DWORD SizeOfRawData; //在文件中对齐后的地址
DWORD PointerToRawData; //在文件中的偏移
DWORD PointerToRelocations; //在OBJ文件中使用
DWORD PointerToLinenumbers; //行号表的位置(调试用)
WORD NumberOfRelocations; //OBJ文件中使用
WORD NumberOfLinenumbers; //行号表中行号的数量
DWORD Characteristics; //节的属性
} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;
节表的大小为 0x0028h,40个字节大小。
c
#define IMAGE_FILE_MACHINE_UNKNOWN 0 // 适用于任何机器
#define IMAGE_FILE_MACHINE_TARGET_HOST 0x0001
#define IMAGE_FILE_MACHINE_I386 0x014c // intel386处理器或后续兼容处理器
#define IMAGE_FILE_MACHINE_R3000 0x0162 // MIPS little-endian, 0x160 big-endian
#define IMAGE_FILE_MACHINE_R4000 0x0166 // MIPS小尾处理器
#define IMAGE_FILE_MACHINE_R10000 0x0168 // MIPS little-endian
#define IMAGE_FILE_MACHINE_WCEMIPSV2 0x0169 // MIPS小尾WCEv2处理器
#define IMAGE_FILE_MACHINE_ALPHA 0x0184 // Alpha_AXP
#define IMAGE_FILE_MACHINE_SH3 0x01a2 // Hitachi SH3处理器
#define IMAGE_FILE_MACHINE_SH3DSP 0x01a3 // Hitachi SH3 DSP处理器
#define IMAGE_FILE_MACHINE_SH3E 0x01a4 // SH3E little-endian
#define IMAGE_FILE_MACHINE_SH4 0x01a6 // Hitachi SH4处理器
#define IMAGE_FILE_MACHINE_SH5 0x01a8 // Hitachi SH5处理器
#define IMAGE_FILE_MACHINE_ARM 0x01c0 // ARM小尾处理器
#define IMAGE_FILE_MACHINE_THUMB 0x01c2 // ARM或Thumb处理器
#define IMAGE_FILE_MACHINE_ARMNT 0x01c4 // ARMv7(或更高)处理器的Thumb模式
#define IMAGE_FILE_MACHINE_AM33 0x01d3 // MatsushitaAM33处理器
#define IMAGE_FILE_MACHINE_POWERPC 0x01F0 // PowerPC小尾处理器
#define IMAGE_FILE_MACHINE_POWERPCFP 0x01f1 // 带浮点支持的PowerPC处理器
#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 // 带FPU的MIPS处理器
#define IMAGE_FILE_MACHINE_MIPSFPU16 0x0466 // 带FPU的MIPS16处理器
#define IMAGE_FILE_MACHINE_AXP64 IMAGE_FILE_MACHINE_ALPHA64
#define IMAGE_FILE_MACHINE_TRICORE 0x0520 // Infineon
#define IMAGE_FILE_MACHINE_CEF 0x0CEF
#define IMAGE_FILE_MACHINE_EBC 0x0EBC // EFI字节码处理器
#define IMAGE_FILE_MACHINE_AMD64 0x8664 // x64处理器
#define IMAGE_FILE_MACHINE_M32R 0x9041 // MatsushitaM32R小尾处理器
#define IMAGE_FILE_MACHINE_ARM64 0xAA64 // ARM64 Little-Endian
#define IMAGE_FILE_MACHINE_CEE 0xC0EE
IMAGE_NT_HEADERS
这个结构是广义上的PE头,在标准PE文件中其大小为456个字节。4 + 20 + 96 + 16 x 8 = 248。
IMAGE_NT_HEADERS = 4个字节的pe标识 + IMAGE_FILE_HEADER + IMAGE_OPTIONAL_HEADER32

详细的结构定义如下:
c
typedef struct _IMAGE_NT_HEADERS {
DWORD Signature;
IMAGE_FILE_HEADER FileHeader;
IMAGE_OPTIONAL_HEADER32 OptionalHeader;
} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;