PE之不同区域的结构体定义


PE之不同区域的结构体定义
1)PE文件不同区域的结构体各有其用:DOS头(_IMAGE_DOS_HEADER)标识DOS兼容性并定位PE头;PE头(_IMAGE_NT_HEADERS)含文件头和可选头,定整体信息;节表(_IMAGE_SECTION_HEADER)描述代码/数据节;有文件头、可选头、数据目录表及导出表、导入表、重定位表等,分工处理架构、加载配置、功能结构定位及函数导入导出、地址修正,共同构成 PE 结构框架。
2)DOS头:IMAGE_DOS_HEADER
DOS头是 PE文件的第一个区域,用于标识文件为DOS可执行格式(.EXE),结构定义如下:
typedef struct _IMAGE_DOS_HEADER {
WORD e_magic; // DOS签名,固定为"0x5A4D"(即ASCII的"MZ")
WORD e_cblp; // 最后一个块的字节数
WORD e_cp; // 文件占用的块数
WORD e_crlc; // 重定位表项数
WORD e_cparhdr; // 头部大小(以段落为单位)
WORD e_minalloc; // 所需的最小额外内存(段落)
WORD e_maxalloc; // 所需的最大额外内存(段落)
WORD e_ss; // 初始SS值(相对偏移)
WORD e_sp; // 初始SP值
WORD e_csum; // 校验和
WORD e_ip; // 初始IP值
WORD e_cs; // 初始CS值(相对偏移)
WORD e_lfarlc; // 重定位表的偏移量
WORD e_ovno; // 覆盖号
WORD e_res[4]; // 保留字段(4个WORD)
WORD e_oemid; // OEM标识符
WORD e_oeminfo; // OEM信息
WORD e_res2[10]; // 保留字段(10个WORD)
LONG e_lfanew; // PE头的偏移量(关键字段,指向真正的PE结构)
} IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;
核心作用:通过e_magic标识DOS兼容性,通过e_lfanew指向PE文件的真正头部(_IMAGE_NT_HEADERS)。
3)PE头:IMAGE_NT_HEADERS
PE头是PE文件的核心区域,包含文件的整体信息,定义如下:
typedef struct _IMAGE_NT_HEADERS {
DWORD Signature; // PE签名,固定为"0x00004550"(即"PE\0\0")
IMAGE_FILE_HEADER FileHeader; // 文件头(基本属性)
IMAGE_OPTIONAL_HEADER32 OptionalHeader; // 可选头(32位)
} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;
// 64位版本(OptionalHeader结构不同)
typedef struct _IMAGE_NT_HEADERS64 {
DWORD Signature;
IMAGE_FILE_HEADER FileHeader;
IMAGE_OPTIONAL_HEADER64 OptionalHeader; // 64位可选头
} IMAGE_NT_HEADERS64, *PIMAGE_NT_HEADERS64;
结构体中字段说明:
Signature:确认文件为PE格式。
FileHeader:包含文件类型(如 EXE/DLL)、节表数量、时间戳等基础信息。
OptionalHeader:包含更详细的加载信息(如入口点地址、内存映射基址、节表偏移等)。
4)节表:IMAGE_SECTION_HEADER
节表由多个节头组成,每个节头描述PE文件中的一个 "节"(如.text代码节、.data数据节等),定义如下:
typedef struct _IMAGE_SECTION_HEADER {
BYTE Name[IMAGE_SIZEOF_SHORT_NAME]; // 节名(8字节,如".text\0")
union {
DWORD PhysicalAddress; // 物理地址(与VirtualSize含义相同)
DWORD VirtualSize; // 节在内存中的大小(未对齐)
} Misc;
DWORD VirtualAddress; // 节在内存中的起始地址(相对基址)
DWORD SizeOfRawData; // 节在磁盘上的大小(对齐后)
DWORD PointerToRawData; // 节在磁盘上的偏移量
DWORD PointerToRelocations; // 重定位信息偏移(OBJ文件用)
DWORD PointerToLinenumbers; // 行号信息偏移
WORD NumberOfRelocations; // 重定位项数
WORD NumberOfLinenumbers; // 行号项数
DWORD Characteristics; // 节属性(如可读、可写、可执行)
} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;
每个节对应文件中一段连续的数据,通过节表可以定位节在磁盘和内存中的位置及属性。
这些结构体是解析PE文件格式的基础,从DOS头过渡到PE头,再通过节表定位具体内容,共同构成了 PE 文件的结构框架。如果需要更深入了解某部分细节(如可选头字段、节属性等),可以进一步补充说明。
5)文件头:IMAGE_FILE_HEADER(PE头的子结构)
IMAGE_FILE_HEADER是PE头(_IMAGE_NT_HEADERS)的核心子结构,存储文件的基础属性,定义如下:
typedef struct _IMAGE_FILE_HEADER {
WORD Machine; // 目标机器架构(如0x8664表示x64,0x014C表示x86)
WORD NumberOfSections; // 节表数量(决定节头的总个数)
DWORD TimeDateStamp; // 文件创建时间戳(从1970年1月1日起的秒数)
DWORD PointerToSymbolTable; // 符号表偏移(调试用,通常为0)
DWORD NumberOfSymbols; // 符号数量(调试用,通常为0)
WORD SizeOfOptionalHeader; // 可选头大小(32位为0xE0,64位为0xF0)
WORD Characteristics; // 文件属性(如0x0002表示可执行,0x2000表示DLL)
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;
核心作用:定义文件的架构、节数量、类型(EXE/DLL)等基础元信息,是解析节表和可选头的前提。
6)可选头:IMAGE_OPTIONAL_HEADER(PE头的子结构)
可选头是PE加载的核心配置区,32位和64位结构类似(差异用注释标注),定义如下:
typedef struct _IMAGE_OPTIONAL_HEADER32 {
WORD Magic; // 标识位数(0x10B=32位,0x20B=64位)
BYTE MajorLinkerVersion; // 链接器主版本
BYTE MinorLinkerVersion; // 链接器次版本
DWORD SizeOfCode; // 代码节总大小(磁盘上)
DWORD SizeOfInitializedData; // 已初始化数据总大小
DWORD SizeOfUninitializedData; // 未初始化数据总大小
DWORD AddressOfEntryPoint; // 程序入口点(内存中的RVA)
DWORD BaseOfCode; // 代码节起始RVA(32位特有)
DWORD BaseOfData; // 数据节起始RVA(32位特有)
DWORD ImageBase; // 首选加载基址(32位:0x00400000常见;64位:0x0000000140000000)
DWORD SectionAlignment; // 内存中节对齐粒度(通常为0x1000)
DWORD FileAlignment; // 磁盘中节对齐粒度(通常为0x200)
WORD MajorOperatingSystemVersion; // 目标系统主版本
WORD MinorOperatingSystemVersion; // 目标系统次版本
WORD MajorImageVersion; // 镜像主版本
WORD MinorImageVersion; // 镜像次版本
WORD MajorSubsystemVersion; // 子系统主版本
WORD MinorSubsystemVersion; // 子系统次版本
DWORD Win32VersionValue; // 保留(通常为0)
DWORD SizeOfImage; // 内存中总大小(对齐后)
DWORD SizeOfHeaders; // 所有头部总大小(对齐后)
DWORD CheckSum; // 校验和(系统文件常用)
WORD Subsystem; // 运行子系统(如0x02=Windows GUI,0x03=Windows CUI)
WORD DllCharacteristics; // DLL属性(如0x0040=动态基址)
DWORD SizeOfStackReserve; // 栈保留大小
DWORD SizeOfStackCommit; // 栈提交大小
DWORD SizeOfHeapReserve; // 堆保留大小
DWORD SizeOfHeapCommit; // 堆提交大小
DWORD LoaderFlags; // 加载标志(已废弃,为0)
DWORD NumberOfRvaAndSizes; // 数据目录表项数(固定为0x10)
IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES]; // 数据目录表(16项)
} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;
// 64位差异:无BaseOfData,ImageBase为QWORD,SizeOfStackReserve等为QWORD
typedef struct _IMAGE_OPTIONAL_HEADER64 {
// ... 前半部分同32位(无BaseOfData)
QWORD ImageBase; // 64位基址(QWORD)
// ... 中间字段同32位
QWORD SizeOfStackReserve; // 64位栈保留(QWORD)
// ... 其余同32位
} IMAGE_OPTIONAL_HEADER64, *PIMAGE_OPTIONAL_HEADER64;
核心作用:通过`AddressOfEntryPoint`定义程序入口,`ImageBase`指定加载基址,`DataDirectory`定位导入表、导出表等关键结构,是PE加载器的核心配置依据。
7)数据目录表项:IMAGE_DATA_DIRECTORY(可选头的子结构)
数据目录表是可选头的最后一个字段,共16项,每项指向一个特定功能结构(如导入表、资源表),定义如下:
typedef struct _IMAGE_DATA_DIRECTORY {
DWORD VirtualAddress; // 结构在内存中的RVA(相对虚拟地址)
DWORD Size; // 结构大小(字节)
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;
常见表项含义(通过索引区分):
- 0:导出表(IMAGE_EXPORT_DIRECTORY)
- 1:导入表(IMAGE_IMPORT_DESCRIPTOR)
- 2:资源表(IMAGE_RESOURCE_DIRECTORY)
- 5:重定位表(IMAGE_BASE_RELOCATION)
- 6:调试信息(IMAGE_DEBUG_DIRECTORY)
核心作用:作为“索引目录”,让加载器快速定位PE文件中的功能结构(如通过索引1找到导入表位置)。
8)导出表:IMAGE_EXPORT_DIRECTORY(用于DLL导出函数)
导出表存储DLL对外暴露的函数信息,定义如下:
typedef struct _IMAGE_EXPORT_DIRECTORY {
DWORD Characteristics; // 保留(为0)
DWORD TimeDateStamp; // 导出表创建时间戳
WORD MajorVersion; // 主版本
WORD MinorVersion; // 次版本
DWORD Name; // 模块名称的RVA(如"DLL名称"字符串)
DWORD Base; // 导出函数起始序号(用于序号导出)
DWORD NumberOfFunctions; // 导出函数总数
DWORD NumberOfNames; // 按名称导出的函数数
DWORD AddressOfFunctions; // 函数地址表RVA(存储每个函数的RVA)
DWORD AddressOfNames; // 函数名称表RVA(存储函数名的RVA)
DWORD AddressOfNameOrdinals; // 名称序号表RVA(关联名称与函数地址表索引)
} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;
核心作用:让其他程序通过函数名或序号调用DLL中的函数(如`kernel32.dll`导出`CreateFileA`)。
9)导入表:IMAGE_IMPORT_DESCRIPTOR(用于导入外部DLL函数)
导入表描述程序依赖的外部DLL及函数,定义如下:
typedef struct _IMAGE_IMPORT_DESCRIPTOR {
union {
DWORD Characteristics; // 0(废弃)
DWORD OriginalFirstThunk; // 导入名称表(INT)RVA(存储函数名/序号)
} DUMMYUNIONNAME;
DWORD TimeDateStamp; // 导入时间戳(0表示未绑定)
DWORD ForwarderChain; // 转发链(通常为0)
DWORD Name; // DLL名称的RVA(如"user32.dll")
DWORD FirstThunk; // 导入地址表(IAT)RVA(加载时填充函数实际地址)
} IMAGE_IMPORT_DESCRIPTOR, *PIMAGE_IMPORT_DESCRIPTOR;
核心作用:记录程序需要从外部DLL导入的函数,加载时由系统填充函数实际地址(如EXE导入`printf`来自`msvcrt.dll`)。
10)重定位表:IMAGE_BASE_RELOCATION(解决基址冲突)
当PE文件加载地址与`ImageBase`不同时,需通过重定位表修正内存地址,定义如下:
typedef struct _IMAGE_BASE_RELOCATION {
DWORD VirtualAddress; // 重定位块的起始RVA
DWORD SizeOfBlock; // 重定位块总大小(包括本结构)
// WORD TypeOffset[1]; // 重定位项(实际数量=(SizeOfBlock-8)/2)
} IMAGE_BASE_RELOCATION, *PIMAGE_BASE_RELOCATION;
每个重定位项(TypeOffset)是16位值:高4位为类型(0x3=IMAGE_REL_BASED_HIGHLOW,最常见),低12位为相对于VirtualAddress的偏移。
核心作用:修正内存中依赖基址的地址(如全局变量指针、函数调用地址),确保程序在非首选基址加载时正常运行。
完整框架:DOS头定位PE头,PE头通过可选头的数据目录表定位导出表、导入表等功能结构,节表则对应实际的代码/数据存储区,最终实现程序的加载与执行。
