文章目录
- PE文件解析器详细文档
-
- [1. 项目概述与目标](#1. 项目概述与目标)
- [2. 技术栈与环境依赖](#2. 技术栈与环境依赖)
-
- [2.1 技术栈](#2.1 技术栈)
- [2.2 环境依赖](#2.2 环境依赖)
- [3. PE文件结构详细分析](#3. PE文件结构详细分析)
-
- [3.1 DOS头(IMAGE_DOS_HEADER)](#3.1 DOS头(IMAGE_DOS_HEADER))
- [3.2 NT头(IMAGE_NT_HEADERS)](#3.2 NT头(IMAGE_NT_HEADERS))
- [3.3 文件头(IMAGE_FILE_HEADER)](#3.3 文件头(IMAGE_FILE_HEADER))
- [3.4 可选头(IMAGE_OPTIONAL_HEADER32)](#3.4 可选头(IMAGE_OPTIONAL_HEADER32))
- [3.5 可选头(IMAGE_OPTIONAL_HEADER64)](#3.5 可选头(IMAGE_OPTIONAL_HEADER64))
- [3.6 数据目录(IMAGE_DATA_DIRECTORY)](#3.6 数据目录(IMAGE_DATA_DIRECTORY))
- [3.7 节区头(IMAGE_SECTION_HEADER)](#3.7 节区头(IMAGE_SECTION_HEADER))
- [3.8 导入描述符(IMAGE_IMPORT_DESCRIPTOR)](#3.8 导入描述符(IMAGE_IMPORT_DESCRIPTOR))
- [3.9 导出目录(IMAGE_EXPORT_DIRECTORY)](#3.9 导出目录(IMAGE_EXPORT_DIRECTORY))
- [3.10 重定位块(IMAGE_BASE_RELOCATION)](#3.10 重定位块(IMAGE_BASE_RELOCATION))
- [3.11 重定位条目](#3.11 重定位条目)
- [4. 项目结构](#4. 项目结构)
- [5. 核心类/函数说明](#5. 核心类/函数说明)
-
- [5.1 PeParser 类](#5.1 PeParser 类)
-
- [5.1.1 公共方法](#5.1.1 公共方法)
- [5.1.2 私有方法](#5.1.2 私有方法)
- [5.2 异常处理类](#5.2 异常处理类)
- [6. 解析流程详解](#6. 解析流程详解)
-
- [6.1 加载文件](#6.1 加载文件)
- [6.2 解析DOS头](#6.2 解析DOS头)
- [6.3 解析NT头](#6.3 解析NT头)
- [6.4 解析节区表](#6.4 解析节区表)
- [6.5 解析导入表](#6.5 解析导入表)
- [6.6 解析导出表](#6.6 解析导出表)
- [6.7 解析重定位表](#6.7 解析重定位表)
- [7. 关键算法解析](#7. 关键算法解析)
-
- [7.1 RVA到文件偏移量的转换](#7.1 RVA到文件偏移量的转换)
- [7.2 导入表解析](#7.2 导入表解析)
- [7.3 导出表解析](#7.3 导出表解析)
- [8. 示例代码与使用说明](#8. 示例代码与使用说明)
-
- [8.1 基本使用示例](#8.1 基本使用示例)
- [8.2 命令行工具使用](#8.2 命令行工具使用)
- [9. 异常处理机制](#9. 异常处理机制)
- [10. 性能优化策略](#10. 性能优化策略)
- [11. 代码风格与规范](#11. 代码风格与规范)
- [12. 未来改进方向](#12. 未来改进方向)
- [13. 结论](#13. 结论)
- error_handling.h
- pe_parser.h
- pe_structures.h
- pe_parser.cpp
- main.cpp
PE文件解析器详细文档
1. 项目概述与目标
本项目旨在开发一个全面、高效的PE(Portable Executable)文件解析器,使用现代C++20标准实现。PE文件是Windows操作系统上的可执行文件格式,包括.exe、.dll、.sys等文件类型。
项目目标:
-
全面解析PE文件的各个组成部分,包括DOS头、NT头、节表、导入表、导出表、重定位表等
-
提供清晰、结构化的代码架构,便于维护和扩展
-
实现详细的错误处理机制,确保解析过程的稳定性
-
提供友好的API接口,方便其他项目集成使用
-
支持PE32(32位)和PE32+(64位)文件格式
2. 技术栈与环境依赖
2.1 技术栈
-
编程语言: C++20
-
构建系统: CMake
-
标准库: STL(Standard Template Library)
-
编译器: 支持C++20的编译器(如MSVC、GCC、Clang)
2.2 环境依赖
-
操作系统: Windows(主要测试平台)
-
CMake版本: 3.16或更高
-
C++编译器: 支持C++20标准
3. PE文件结构详细分析
3.1 DOS头(IMAGE_DOS_HEADER)
| 字段名称 | 偏移量 | 大小(字节) | 功能描述 |
|---|---|---|---|
| e_magic | 0x00 | 2 | DOS魔术字,值为0x5A4D("MZ") |
| e_cblp | 0x02 | 2 | 最后一页的字节数 |
| e_cp | 0x04 | 2 | 文件的页数 |
| e_crlc | 0x06 | 2 | 重定位项的数量 |
| e_cparhdr | 0x08 | 2 | DOS头的段落数 |
| e_minalloc | 0x0A | 2 | 程序需要的最小额外段落数 |
| e_maxalloc | 0x0C | 2 | 程序需要的最大额外段落数 |
| e_ss | 0x0E | 2 | 初始堆栈段值 |
| e_sp | 0x10 | 2 | 初始堆栈指针值 |
| e_csum | 0x12 | 2 | 校验和 |
| e_ip | 0x14 | 2 | 初始指令指针值 |
| e_cs | 0x16 | 2 | 初始代码段值 |
| e_lfarlc | 0x18 | 2 | 重定位表的文件偏移量 |
| e_ovno | 0x1A | 2 | 覆盖号 |
| e_res[4] | 0x1C | 8 | 保留字 |
| e_oemid | 0x24 | 2 | OEM标识符 |
| e_oeminfo | 0x26 | 2 | OEM信息 |
| e_res2[10] | 0x28 | 20 | 保留字 |
| e_lfanew | 0x3C | 4 | NT头的文件偏移量 |
3.2 NT头(IMAGE_NT_HEADERS)
| 字段名称 | 偏移量 | 大小(字节) | 功能描述 |
|---|---|---|---|
| Signature | 0x00 | 4 | NT签名,值为0x00004550("PE") |
3.3 文件头(IMAGE_FILE_HEADER)
| 字段名称 | 偏移量 | 大小(字节) | 功能描述 |
|---|---|---|---|
| Machine | 0x04 | 2 | 目标机器类型 |
| NumberOfSections | 0x06 | 2 | 节区数量 |
| TimeDateStamp | 0x08 | 4 | 时间戳 |
| PointerToSymbolTable | 0x0C | 4 | 符号表的文件偏移量 |
| NumberOfSymbols | 0x10 | 4 | 符号数量 |
| SizeOfOptionalHeader | 0x14 | 2 | 可选头的大小 |
| Characteristics | 0x16 | 2 | 文件特性 |
3.4 可选头(IMAGE_OPTIONAL_HEADER32)
| 字段名称 | 偏移量 | 大小(字节) | 功能描述 |
|---|---|---|---|
| Magic | 0x18 | 2 | 魔术字,PE32为0x10B |
| MajorLinkerVersion | 0x1A | 1 | 链接器主版本号 |
| MinorLinkerVersion | 0x1B | 1 | 链接器次版本号 |
| SizeOfCode | 0x1C | 4 | 代码节的大小 |
| SizeOfInitializedData | 0x20 | 4 | 初始化数据节的大小 |
| SizeOfUninitializedData | 0x24 | 4 | 未初始化数据节的大小 |
| AddressOfEntryPoint | 0x28 | 4 | 入口点地址 |
| BaseOfCode | 0x2C | 4 | 代码节的基址 |
| BaseOfData | 0x30 | 4 | 数据节的基址 |
| ImageBase | 0x34 | 4 | 镜像加载基址 |
| SectionAlignment | 0x38 | 4 | 节区对齐值 |
| FileAlignment | 0x3C | 4 | 文件对齐值 |
| MajorOperatingSystemVersion | 0x40 | 2 | 操作系统主版本号 |
| MinorOperatingSystemVersion | 0x42 | 2 | 操作系统次版本号 |
| MajorImageVersion | 0x44 | 2 | 镜像主版本号 |
| MinorImageVersion | 0x46 | 2 | 镜像次版本号 |
| MajorSubsystemVersion | 0x48 | 2 | 子系统主版本号 |
| MinorSubsystemVersion | 0x4A | 2 | 子系统次版本号 |
| Win32VersionValue | 0x4C | 4 | Win32版本值 |
| SizeOfImage | 0x50 | 4 | 镜像大小 |
| SizeOfHeaders | 0x54 | 4 | 头部大小 |
| CheckSum | 0x58 | 4 | 校验和 |
| Subsystem | 0x5C | 2 | 子系统类型 |
| DllCharacteristics | 0x5E | 2 | DLL特性 |
| SizeOfStackReserve | 0x60 | 4 | 堆栈保留大小 |
| SizeOfStackCommit | 0x64 | 4 | 堆栈提交大小 |
| SizeOfHeapReserve | 0x68 | 4 | 堆保留大小 |
| SizeOfHeapCommit | 0x6C | 4 | 堆提交大小 |
| LoaderFlags | 0x70 | 4 | 加载器标志 |
| NumberOfRvaAndSizes | 0x74 | 4 | 数据目录数量 |
| DataDirectory[16] | 0x78 | 128 | 数据目录表 |
3.5 可选头(IMAGE_OPTIONAL_HEADER64)
| 字段名称 | 偏移量 | 大小(字节) | 功能描述 |
|---|---|---|---|
| Magic | 0x18 | 2 | 魔术字,PE32+为0x20B |
| MajorLinkerVersion | 0x1A | 1 | 链接器主版本号 |
| MinorLinkerVersion | 0x1B | 1 | 链接器次版本号 |
| SizeOfCode | 0x1C | 4 | 代码节的大小 |
| SizeOfInitializedData | 0x20 | 4 | 初始化数据节的大小 |
| SizeOfUninitializedData | 0x24 | 4 | 未初始化数据节的大小 |
| AddressOfEntryPoint | 0x28 | 4 | 入口点地址 |
| BaseOfCode | 0x2C | 4 | 代码节的基址 |
| ImageBase | 0x30 | 8 | 镜像加载基址(64位) |
| SectionAlignment | 0x38 | 4 | 节区对齐值 |
| FileAlignment | 0x3C | 4 | 文件对齐值 |
| MajorOperatingSystemVersion | 0x40 | 2 | 操作系统主版本号 |
| MinorOperatingSystemVersion | 0x42 | 2 | 操作系统次版本号 |
| MajorImageVersion | 0x44 | 2 | 镜像主版本号 |
| MinorImageVersion | 0x46 | 2 | 镜像次版本号 |
| MajorSubsystemVersion | 0x48 | 2 | 子系统主版本号 |
| MinorSubsystemVersion | 0x4A | 2 | 子系统次版本号 |
| Win32VersionValue | 0x4C | 4 | Win32版本值 |
| SizeOfImage | 0x50 | 4 | 镜像大小 |
| SizeOfHeaders | 0x54 | 4 | 头部大小 |
| CheckSum | 0x58 | 4 | 校验和 |
| Subsystem | 0x5C | 2 | 子系统类型 |
| DllCharacteristics | 0x5E | 2 | DLL特性 |
| SizeOfStackReserve | 0x60 | 8 | 堆栈保留大小(64位) |
| SizeOfStackCommit | 0x68 | 8 | 堆栈提交大小(64位) |
| SizeOfHeapReserve | 0x70 | 8 | 堆保留大小(64位) |
| SizeOfHeapCommit | 0x78 | 8 | 堆提交大小(64位) |
| LoaderFlags | 0x80 | 4 | 加载器标志 |
| NumberOfRvaAndSizes | 0x84 | 4 | 数据目录数量 |
| DataDirectory[16] | 0x88 | 128 | 数据目录表 |
3.6 数据目录(IMAGE_DATA_DIRECTORY)
| 字段名称 | 偏移量 | 大小(字节) | 功能描述 |
|---|---|---|---|
| VirtualAddress | 0x00 | 4 | 虚拟地址 |
| Size | 0x04 | 4 | 大小 |
3.7 节区头(IMAGE_SECTION_HEADER)
| 字段名称 | 偏移量 | 大小(字节) | 功能描述 |
|---|---|---|---|
| Name | 0x00 | 8 | 节区名称 |
| Misc.VirtualSize | 0x08 | 4 | 节区的虚拟大小 |
| VirtualAddress | 0x0C | 4 | 节区的虚拟地址 |
| SizeOfRawData | 0x10 | 4 | 节区在文件中的大小 |
| PointerToRawData | 0x14 | 4 | 节区在文件中的偏移量 |
| PointerToRelocations | 0x18 | 4 | 重定位表的偏移量 |
| PointerToLinenumbers | 0x1C | 4 | 行号表的偏移量 |
| NumberOfRelocations | 0x20 | 2 | 重定位项的数量 |
| NumberOfLinenumbers | 0x22 | 2 | 行号的数量 |
| Characteristics | 0x24 | 4 | 节区特性 |
3.8 导入描述符(IMAGE_IMPORT_DESCRIPTOR)
| 字段名称 | 偏移量 | 大小(字节) | 功能描述 |
|---|---|---|---|
| OriginalFirstThunk | 0x00 | 4 | 原始导入名称表的RVA |
| TimeDateStamp | 0x04 | 4 | 时间戳 |
| ForwarderChain | 0x08 | 4 | 前向链 |
| Name | 0x0C | 4 | DLL名称的RVA |
| FirstThunk | 0x10 | 4 | 导入地址表的RVA |
3.9 导出目录(IMAGE_EXPORT_DIRECTORY)
| 字段名称 | 偏移量 | 大小(字节) | 功能描述 |
|---|---|---|---|
| Characteristics | 0x00 | 4 | 特性 |
| TimeDateStamp | 0x04 | 4 | 时间戳 |
| MajorVersion | 0x08 | 2 | 主版本号 |
| MinorVersion | 0x0A | 2 | 次版本号 |
| Name | 0x0C | 4 | 模块名称的RVA |
| Base | 0x10 | 4 | 导出函数的基地址 |
| NumberOfFunctions | 0x14 | 4 | 导出函数的数量 |
| NumberOfNames | 0x18 | 4 | 导出名称的数量 |
| AddressOfFunctions | 0x1C | 4 | 导出函数地址表的RVA |
| AddressOfNames | 0x20 | 4 | 导出名称表的RVA |
| AddressOfNameOrdinals | 0x24 | 4 | 导出序号表的RVA |
3.10 重定位块(IMAGE_BASE_RELOCATION)
| 字段名称 | 偏移量 | 大小(字节) | 功能描述 |
|---|---|---|---|
| VirtualAddress | 0x00 | 4 | 块的虚拟地址 |
| SizeOfBlock | 0x04 | 4 | 块的大小 |
3.11 重定位条目
| 字段名称 | 偏移量 | 大小(字节) | 功能描述 |
|---|---|---|---|
| Offset | 0x00 | 2 | 偏移量(低12位) |
| Type | 0x00 | 2 | 类型(高4位) |
4. 项目结构
parser_pe_file/
├── CMakeLists.txt # 项目构建配置
├── src/
│ ├── pe_structures.h # PE文件结构定义
│ ├── error_handling.h # 异常处理类
│ ├── pe_parser.h # PE解析器类定义
│ ├── pe_parser.cpp # PE解析器实现
│ └── main.cpp # 主程序
└── build/ # 构建输出目录
5. 核心类/函数说明
5.1 PeParser 类
5.1.1 公共方法
| 方法名称 | 参数 | 返回值 | 功能描述 |
|---|---|---|---|
LoadFile |
std::string_view file_path |
void |
加载PE文件到内存,验证文件大小 |
Parse |
无 | void |
解析PE文件的各个部分 |
IsLoaded |
无 | bool |
检查文件是否已加载 |
Is64Bit |
无 | bool |
检查是否为64位PE文件 |
GetResult |
无 | const PeParseResult& |
获取解析结果 |
ToString |
无 | std::string |
将解析结果转换为字符串 |
5.1.2 私有方法
| 方法名称 | 参数 | 返回值 | 功能描述 |
|---|---|---|---|
ParseDosHeader |
无 | void |
解析DOS头,填充DosHeaderInfo结构 |
ParseNtHeaders |
无 | void |
解析NT头、文件头和可选头 |
ParseSections |
无 | void |
解析节区表,填充SectionInfo结构 |
ParseImports |
无 | void |
解析导入表,填充ImportInfo结构 |
ParseExports |
无 | void |
解析导出表,填充ExportInfo结构 |
ParseRelocations |
无 | void |
解析重定位表,填充RelocationBlock结构 |
RvaToFileOffset |
std::uint32_t rva |
std::uint32_t |
将RVA转换为文件偏移量 |
ReadString |
std::uint32_t offset |
std::string |
从指定偏移量读取字符串 |
ValidateDosHeader |
无 | void |
验证DOS魔术字 |
ValidateNtSignature |
无 | void |
验证NT签名 |
GetPtr |
std::size_t offset |
const T* |
获取指定偏移量的指针 |
ValidateOffset |
std::size_t offset, std::size_t size, std::string_view field_name |
void |
验证偏移量是否有效 |
5.2 异常处理类
| 类名称 | 父类 | 功能描述 |
|---|---|---|
PeException |
std::runtime_error |
基础异常类 |
FileException |
PeException |
文件操作异常 |
InvalidPeException |
PeException |
无效PE文件异常 |
ParseException |
PeException |
解析异常 |
MemoryAccessException |
PeException |
内存访问异常 |
6. 解析流程详解
6.1 加载文件
-
打开指定路径的文件
-
获取文件大小并验证是否足够大(至少包含DOS头)
-
将文件数据读取到内存中的
std::vector<std::uint8_t>
6.2 解析DOS头
-
验证DOS头偏移量
-
读取DOS头结构
-
填充
DosHeaderInfo结构 -
验证DOS魔术字
6.3 解析NT头
-
根据DOS头中的
e_lfanew字段获取NT头偏移量 -
验证NT头偏移量
-
读取NT签名并验证
-
读取文件头并填充
FileHeaderInfo结构 -
根据文件头中的
SizeOfOptionalHeader字段确定可选头大小 -
读取可选头魔术字,确定是PE32还是PE32+
-
读取并填充相应的可选头信息
-
填充数据目录信息
6.4 解析节区表
-
计算节区表偏移量(NT头偏移量 + NT头大小 + 可选头大小)
-
验证节区表偏移量
-
循环解析每个节区头
-
填充
SectionInfo结构
6.5 解析导入表
-
从数据目录中获取导入表的RVA和大小
-
将RVA转换为文件偏移量
-
循环解析导入描述符,直到遇到全0的描述符
-
对每个导入描述符,解析DLL名称和导入函数
-
填充
ImportInfo结构
6.6 解析导出表
-
从数据目录中获取导出表的RVA和大小
-
将RVA转换为文件偏移量
-
解析导出目录结构
-
解析模块名称、导出函数名称等信息
-
填充
ExportInfo结构
6.7 解析重定位表
-
从数据目录中获取重定位表的RVA和大小
-
将RVA转换为文件偏移量
-
循环解析重定位块,直到遇到大小为0的块
-
对每个重定位块,解析重定位条目
-
填充
RelocationBlock和RelocationEntry结构
7. 关键算法解析
7.1 RVA到文件偏移量的转换
算法步骤:
-
遍历所有节区
-
对于每个节区,检查RVA是否在该节区的虚拟地址范围内
-
如果找到对应的节区,计算文件偏移量:
文件偏移量 = RVA - 节区虚拟地址 + 节区文件偏移量 -
验证计算得到的文件偏移量是否有效
-
返回计算得到的文件偏移量或0(如果未找到对应的节区)
实现代码:
std::uint32_t PeParser::RvaToFileOffset(std::uint32_t rva) const {
for (const auto& section : result_.sections) {
if (rva >= section.virtual_address &&
rva < section.virtual_address + section.virtual_size) {
const auto file_offset = rva - section.virtual_address + section.pointer_to_raw_data;
if (file_offset < file_data_.size()) {
return static_cast<std::uint32_t>(file_offset);
}
break;
}
}
return 0;
}
7.2 导入表解析
算法步骤:
-
从数据目录中获取导入表的RVA和大小
-
将RVA转换为文件偏移量
-
循环解析导入描述符,直到遇到全0的描述符
-
对每个导入描述符: a. 解析DLL名称 b. 解析导入函数(通过OriginalFirstThunk) c. 区分按名称导入和按序号导入
-
填充ImportInfo结构
7.3 导出表解析
算法步骤:
-
从数据目录中获取导出表的RVA和大小
-
将RVA转换为文件偏移量
-
解析导出目录结构
-
解析模块名称
-
解析导出函数名称表和序号表
-
填充ExportInfo结构
8. 示例代码与使用说明
8.1 基本使用示例
#include "pe_parser.h"
#include <iostream>
int main() {
try {
// 创建解析器实例
pe_parser::PeParser parser;
// 加载PE文件
parser.LoadFile("C:\\Windows\\System32\\notepad.exe");
// 解析PE文件
parser.Parse();
// 获取解析结果
const auto& result = parser.GetResult();
// 输出基本信息
std::cout << "PE File Type: " << (result.is_64bit ? "PE32+ (64-bit)" : "PE32 (32-bit)") << std::endl;
std::cout << "Number of Sections: " << result.file_header.number_of_sections << std::endl;
std::cout << "Entry Point: 0x" << std::hex << (result.is_64bit ? result.opt64.address_of_entry_point : result.opt32.address_of_entry_point) << std::endl;
std::cout << "Image Base: 0x" << std::hex << (result.is_64bit ? result.opt64.image_base : result.opt32.image_base) << std::endl;
// 输出所有导入的DLL
std::cout << "\nImports:" << std::endl;
for (const auto& import : result.imports) {
std::cout << " - " << import.dll_name << std::endl;
}
} catch (const std::exception& e) {
// 捕获并处理异常
std::cerr << "Error: " << e.what() << std::endl;
return 1;
}
return 0;
}
8.2 命令行工具使用
编译后,可以通过命令行运行PE解析器:
PEParser.exe <pe_file_path>
例如:
PEParser.exe C:\Windows\System32\notepad.exe
9. 异常处理机制
本项目实现了完整的异常处理机制,主要包括以下异常类:
-
PeException: 基础异常类,所有PE解析相关的异常都继承自此类
-
FileException: 文件操作异常,如文件打开失败、读取失败等
-
InvalidPeException: 无效PE文件异常,如DOS魔术字错误、NT签名错误等
-
ParseException: 解析异常,如偏移量无效、数据格式错误等
-
MemoryAccessException: 内存访问异常,如内存越界等
在解析过程中,当遇到错误时,会抛出相应的异常,调用者可以捕获这些异常并进行处理。
异常处理示例:
try {
// 解析操作
parser.Parse();
} catch (const pe_parser::FileException& e) {
std::cerr << "File error: " << e.what() << std::endl;
} catch (const pe_parser::InvalidPeException& e) {
std::cerr << "Invalid PE file: " << e.what() << std::endl;
} catch (const pe_parser::ParseException& e) {
std::cerr << "Parse error: " << e.what() << std::endl;
} catch (const pe_parser::MemoryAccessException& e) {
std::cerr << "Memory access error: " << e.what() << std::endl;
} catch (const std::exception& e) {
std::cerr << "Other error: " << e.what() << std::endl;
}
10. 性能优化策略
-
内存管理: 使用
std::vector<std::uint8_t>存储文件数据,避免频繁的内存分配和释放 -
偏移量验证: 在访问内存前进行偏移量验证,避免内存越界
-
字符串处理: 限制字符串长度,避免读取过长的字符串导致性能问题
-
缓存机制: 解析结果存储在
PeParseResult结构中,避免重复解析 -
边界检查: 对所有内存访问进行边界检查,确保解析过程的稳定性
-
算法优化: 优化RVA到文件偏移量的转换算法,减少遍历次数
-
异常处理: 合理使用异常处理,避免过度使用影响性能
11. 代码风格与规范
本项目遵循以下代码风格和规范:
-
命名规范: 使用驼峰命名法,类名首字母大写,函数和变量名首字母小写
-
注释规范: 每个类、函数、字段都有详细的注释,说明其功能和用法
-
异常处理: 使用异常处理机制处理错误,避免使用错误码
-
内存管理: 使用RAII原则管理内存,避免内存泄漏
-
代码组织: 代码结构清晰,逻辑分明,便于维护和扩展
-
常量定义: 使用
constexpr定义常量,提高代码可读性和性能 -
类型安全: 使用强类型,避免使用void*等不安全的类型
12. 未来改进方向
-
支持更多PE文件特性: 如资源表、TLS表、异常表等
-
添加PE文件修改功能: 支持修改PE文件的各个部分
-
增加更多的分析工具: 如导入导出函数分析、节区分析、重定位分析等
-
优化解析性能: 进一步提高解析速度和内存使用效率
-
跨平台支持: 支持在Linux和macOS上解析PE文件
-
添加图形界面: 提供可视化的PE文件分析工具
-
支持更多文件格式: 如ELF、Mach-O等其他可执行文件格式
13. 结论
本PE文件解析器项目实现了全面、高效的PE文件解析功能,支持PE32和PE32+格式,提供了清晰的API接口和详细的错误处理机制。通过本项目,开发者可以深入了解PE文件的结构和解析原理,为Windows平台的应用开发和安全分析提供有力的工具。
本文档详细介绍了PE文件的各个结构和字段,以及解析器的实现细节,希望能帮助开发者更好地理解和使用本项目。
error_handling.h
cpp
#pragma once
// 包含标准异常类型
#include <stdexcept>
#include <string>
#include <string_view>
// PE解析器命名空间
namespace pe_parser {
// 基础异常类,继承自std::runtime_error
// 表示PE文件解析过程中的所有异常
class PeException : public std::runtime_error {
public:
// 构造函数,接受字符串参数
// 参数: message - 异常消息
explicit PeException(const std::string& message)
: std::runtime_error(message) {}
// 构造函数,接受C风格字符串参数
// 参数: message - 异常消息
explicit PeException(const char* message)
: std::runtime_error(message) {}
};
// 文件异常类,继承自PeException
// 表示文件操作相关的异常
class FileException : public PeException {
public:
// 构造函数,接受文件路径、操作名称和异常消息
// 参数: file_path - 文件路径
// operation - 操作名称
// message - 异常消息
FileException(std::string_view file_path, std::string_view operation, std::string_view message)
: PeException("文件错误: " + std::string(file_path) + ", 操作: " + std::string(operation) + ", 消息: " + std::string(message)) {
}
};
// 无效PE文件异常类,继承自PeException
// 表示无效的PE文件格式异常
class InvalidPeException : public PeException {
public:
// 构造函数,接受异常消息和偏移量
// 参数: message - 异常消息
// offset - 无效位置偏移
InvalidPeException(std::string_view message, std::size_t offset)
: PeException("无效的PE文件: " + std::string(message) + ", 偏移: 0x" + std::to_string(offset)) {
}
};
// 解析异常类,继承自PeException
// 表示解析过程中的异常
class ParseException : public PeException {
public:
// 构造函数,接受解析器名称、字段名称和异常消息
// 参数: parser_name - 解析器名称
// field_name - 字段名称
// message - 异常消息
ParseException(std::string_view parser_name, std::string_view field_name, std::string_view message)
: PeException(std::string(parser_name) + "错误: 字段 " + std::string(field_name) + ", 消息: " + std::string(message)) {
}
};
// 内存访问异常类,继承自PeException
// 表示内存访问相关的异常
class MemoryAccessException : public PeException {
public:
// 构造函数,接受异常消息和偏移量
// 参数: message - 异常消息
// offset - 内存访问偏移
MemoryAccessException(std::string_view message, std::size_t offset)
: PeException("内存访问错误: " + std::string(message) + ", 偏移: 0x" + std::to_string(offset)) {
}
};
} // namespace pe_parser
pe_parser.h
cpp
#pragma once
// 包含必要的头文件
#include <cstdint>
#include <string>
#include <string_view>
#include <vector>
// 包含自定义头文件
#include "error_handling.h"
#include "pe_structures.h"
// PE解析器命名空间
namespace pe_parser {
// 导入信息结构
// 存储导入表信息
struct ImportInfo {
std::string dll_name; // 导入的DLL名称
std::vector<std::string> functions; // 导入的函数名称列表
};
// 导出信息结构
// 存储导出表信息
struct ExportInfo {
std::string module_name; // 模块名称
std::uint32_t base = 0; // 导出函数基址
std::uint32_t number_of_functions = 0; // 导出函数数量
std::uint32_t number_of_names = 0; // 导出函数名称数量
std::vector<std::string> function_names; // 导出函数名称列表
};
// 重定位条目结构
// 存储单个重定位条目
struct RelocationEntry {
std::uint16_t offset; // 重定位偏移
std::uint8_t type = 0; // 重定位类型
};
// 重定位块结构
// 存储重定位块
struct RelocationBlock {
std::uint32_t virtual_address = 0; // 块虚拟地址
std::uint32_t size_of_block = 0; // 块大小
std::vector<RelocationEntry> entries; // 重定位条目列表
};
// 节信息结构
// 存储节信息
struct SectionInfo {
std::string name; // 节名称
std::uint32_t virtual_size = 0; // 节虚拟大小
std::uint32_t virtual_address = 0; // 节虚拟地址
std::uint32_t size_of_raw_data = 0; // 节在文件中的大小
std::uint32_t pointer_to_raw_data = 0; // 节在文件中的偏移
std::uint32_t characteristics = 0; // 节特征
};
// DOS头信息结构
// 存储DOS头信息
struct DosHeaderInfo {
std::uint16_t magic = 0; // 魔数,通常为0x5A4D("MZ")
std::uint16_t last_page_bytes = 0; // 最后一页的字节数
std::uint16_t pages = 0; // 文件页数
std::uint16_t relocations = 0; // 重定位项数
std::uint16_t header_paragraphs = 0; // 头部的段落数
std::uint16_t min_extra_paragraphs = 0; // 最小额外段落数
std::uint16_t max_extra_paragraphs = 0; // 最大额外段落数
std::uint16_t stack_segment = 0; // 堆栈段
std::uint16_t stack_pointer = 0; // 堆栈指针
std::uint16_t checksum = 0; // 校验和
std::uint16_t instruction_pointer = 0; // 指令指针
std::uint16_t code_segment = 0; // 代码段
std::uint16_t relocation_offset = 0; // 重定位表偏移
std::uint16_t overlay_number = 0; // 覆盖号
std::uint16_t oem_id = 0; // OEM标识符
std::uint16_t oem_info = 0; // OEM信息
std::int32_t nt_header_offset = 0; // NT头偏移量
};
// 文件头信息结构
// 存储文件头信息
struct FileHeaderInfo {
std::uint16_t machine = 0; // 目标计算机类型
std::uint16_t number_of_sections = 0; // 节的数量
std::uint32_t time_date_stamp = 0; // 时间戳
std::uint32_t pointer_to_symbol_table = 0; // 符号表偏移
std::uint32_t number_of_symbols = 0; // 符号数量
std::uint16_t size_of_optional_header = 0; // 可选头大小
std::uint16_t characteristics = 0; // 文件特征
};
// 数据目录信息结构
// 存储数据目录信息
struct DataDirectoryInfo {
std::string name; // 数据目录名称
std::uint32_t virtual_address = 0; // 数据目录虚拟地址
std::uint32_t size = 0; // 数据目录大小
};
// 32位可选头信息结构
// 存储PE32可选头信息
struct OptionalHeader32Info {
std::uint16_t magic = 0; // 魔数,PE32为0x10B
std::uint8_t major_linker_version = 0; // 主链接器版本号
std::uint8_t minor_linker_version = 0; // 次链接器版本号
std::uint32_t size_of_code = 0; // 代码大小
std::uint32_t size_of_initialized_data = 0; // 已初始化数据大小
std::uint32_t size_of_uninitialized_data = 0; // 未初始化数据大小
std::uint32_t address_of_entry_point = 0; // 入口点地址
std::uint32_t base_of_code = 0; // 代码基址
std::uint32_t base_of_data = 0; // 数据基址
std::uint32_t image_base = 0; // 映像基址
std::uint32_t section_alignment = 0; // 节对齐
std::uint32_t file_alignment = 0; // 文件对齐
std::uint16_t major_operating_system_version = 0; // 主操作系统版本号
std::uint16_t minor_operating_system_version = 0; // 次操作系统版本号
std::uint16_t major_image_version = 0; // 主映像版本号
std::uint16_t minor_image_version = 0; // 次映像版本号
std::uint16_t major_subsystem_version = 0; // 主子系统版本号
std::uint16_t minor_subsystem_version = 0; // 次子系统版本号
std::uint32_t win32_version_value = 0; // Win32版本值
std::uint32_t size_of_image = 0; // 映像大小
std::uint32_t size_of_headers = 0; // 头大小
std::uint32_t check_sum = 0; // 校验和
std::uint16_t subsystem = 0; // 子系统类型
std::uint16_t dll_characteristics = 0; // DLL特征
std::uint32_t size_of_stack_reserve = 0; // 堆栈保留大小
std::uint32_t size_of_stack_commit = 0; // 堆栈提交大小
std::uint32_t size_of_heap_reserve = 0; // 堆保留大小
std::uint32_t size_of_heap_commit = 0; // 堆提交大小
std::uint32_t loader_flags = 0; // 加载器标志
std::uint32_t number_of_rva_and_sizes = 0; // 数据目录数量
std::vector<DataDirectoryInfo> data_directories; // 数据目录列表
};
// 64位可选头信息结构
// 存储PE32+可选头信息
struct OptionalHeader64Info {
std::uint16_t magic = 0; // 魔数,PE32+为0x20B
std::uint8_t major_linker_version = 0; // 主链接器版本号
std::uint8_t minor_linker_version = 0; // 次链接器版本号
std::uint32_t size_of_code = 0; // 代码大小
std::uint32_t size_of_initialized_data = 0; // 已初始化数据大小
std::uint32_t size_of_uninitialized_data = 0; // 未初始化数据大小
std::uint32_t address_of_entry_point = 0; // 入口点地址
std::uint32_t base_of_code = 0; // 代码基址
std::uint64_t image_base = 0; // 映像基址(64位)
std::uint32_t section_alignment = 0; // 节对齐
std::uint32_t file_alignment = 0; // 文件对齐
std::uint16_t major_operating_system_version = 0; // 主操作系统版本号
std::uint16_t minor_operating_system_version = 0; // 次操作系统版本号
std::uint16_t major_image_version = 0; // 主映像版本号
std::uint16_t minor_image_version = 0; // 次映像版本号
std::uint16_t major_subsystem_version = 0; // 主子系统版本号
std::uint16_t minor_subsystem_version = 0; // 次子系统版本号
std::uint32_t win32_version_value = 0; // Win32版本值
std::uint32_t size_of_image = 0; // 映像大小
std::uint32_t size_of_headers = 0; // 头大小
std::uint32_t check_sum = 0; // 校验和
std::uint16_t subsystem = 0; // 子系统类型
std::uint16_t dll_characteristics = 0; // DLL特征
std::uint64_t size_of_stack_reserve = 0; // 堆栈保留大小(64位)
std::uint64_t size_of_stack_commit = 0; // 堆栈提交大小(64位)
std::uint64_t size_of_heap_reserve = 0; // 堆保留大小(64位)
std::uint64_t size_of_heap_commit = 0; // 堆提交大小(64位)
std::uint32_t loader_flags = 0; // 加载器标志
std::uint32_t number_of_rva_and_sizes = 0; // 数据目录数量
std::vector<DataDirectoryInfo> data_directories; // 数据目录列表
};
// PE解析结果结构
// 存储所有解析出的PE文件信息
struct PeParseResult {
DosHeaderInfo dos_header; // DOS头信息
std::uint32_t nt_signature = 0; // NT签名
FileHeaderInfo file_header; // 文件头信息
bool is_64bit = false; // 是否为64位PE文件
OptionalHeader32Info opt32; // 32位可选头信息
OptionalHeader64Info opt64; // 64位可选头信息
std::vector<SectionInfo> sections; // 节信息列表
std::vector<ImportInfo> imports; // 导入信息列表
ExportInfo exports; // 导出信息
std::vector<RelocationBlock> relocations; // 重定位信息列表
};
// PE解析器类
// 解析PE文件
class PeParser {
public:
// 默认构造函数
PeParser() = default;
// 默认析构函数
~PeParser() = default;
// 移动构造函数
PeParser(PeParser&&) noexcept = default;
// 移动赋值运算符
auto operator=(PeParser&&) noexcept -> PeParser & = default;
// 禁用拷贝构造函数
PeParser(const PeParser&) = delete;
// 禁用拷贝赋值运算符
auto operator=(const PeParser&) -> PeParser & = delete;
// 将PE文件加载到内存中
// 参数: file_path - PE文件路径
void LoadFile(std::string_view file_path);
// 解析PE文件
void Parse();
// 检查文件是否已加载
// 返回值: 已加载返回true,否则返回false
[[nodiscard]] auto IsLoaded() const noexcept -> bool {
return !file_data_.empty();
}
// 检查是否为64位PE文件
// 返回值: 64位返回true,否则返回false
[[nodiscard]] auto Is64Bit() const noexcept -> bool {
return result_.is_64bit;
}
// 获取解析结果
// 返回值: 解析结果的常量引用
[[nodiscard]] auto GetResult() const -> const PeParseResult& {
return result_;
}
// 将解析结果转换为字符串
// 返回值: 解析结果的字符串表示
[[nodiscard]] auto ToString() const -> std::string;
private:
std::vector<std::uint8_t> file_data_; // 文件数据存储
PeParseResult result_; // 解析结果存储
// 解析DOS头
void ParseDosHeader();
// 解析NT头
void ParseNtHeaders();
// 解析节表
void ParseSections();
// 解析导入表
void ParseImports();
// 解析导出表
void ParseExports();
// 解析重定位表
void ParseRelocations();
// 模板函数:获取指定偏移处的指针
// 参数: offset - 偏移量
// 返回值: 指定类型的指针,无效时返回nullptr
template <typename T>
[[nodiscard]] auto GetPtr(std::size_t offset) const -> const T*;
// 将RVA转换为文件偏移
// 参数: rva - 相对虚拟地址
// 返回值: 对应的文件偏移,无效时返回0
[[nodiscard]] auto RvaToFileOffset(std::uint32_t rva) const -> std::uint32_t;
// 从指定偏移处读取字符串
// 参数: offset - 偏移量
// 返回值: 读取的字符串
[[nodiscard]] auto ReadString(std::uint32_t offset) const -> std::string;
// 模板函数:验证偏移量
// 参数: offset - 偏移量
// size - 大小
// field_name - 字段名称
// 抛出: 偏移量无效时抛出ParseException异常
template <typename T>
void ValidateOffset(std::size_t offset, std::size_t size, std::string_view field_name) const;
template <typename T>
void ValidateOffset(std::size_t offset, std::string_view field_name) const ;
// 验证DOS头
void ValidateDosHeader() const;
// 验证NT签名
void ValidateNtSignature() const;
};
// 便捷函数:解析PE文件
// 参数: file_path - PE文件路径
// 返回值: 解析结果
[[nodiscard]] inline auto ParsePeFile(std::string_view file_path) -> PeParseResult {
PeParser parser;
parser.LoadFile(file_path);
parser.Parse();
return parser.GetResult();
}
} // namespace pe_parser
pe_structures.h
cpp
#pragma once
// 包含标准整数类型
#include <cstdint>
// PE解析器命名空间
namespace pe_parser {
// PE文件机器类型枚举
// 标识目标计算机架构
enum class PeMachineType : uint16_t {
kI386 = 0x014cu, // Intel 386 及兼容处理器
kIa64 = 0x0200u, // Intel Itanium 处理器
kAmd64 = 0x8664u, // AMD64 处理器
kArm = 0x01cu, // ARM 处理器
kArmNt = 0x0AA4u, // ARM NT 处理器
kEbc = 0x0EBCu, // EFI 字节码
kMips16 = 0x0266u, // MIPS 16位处理器
kMipsFpu = 0x0366u, // 带FPU的MIPS处理器
kMipsFpu16 = 0x0466u, // 带FPU的MIPS 16位处理器
kAlpha = 0x0184u, // Alpha 处理器
kSH3 = 0x01a2u, // SH3 处理器
kSH3E = 0x01a4u, // SH3E 处理器
kSH4 = 0x01a6u, // SH4 处理器
kMipsWce = 0x0169u // MIPS WCE 处理器
};
// 文件特征枚举
// 标识PE文件的各种属性
enum class PeCharacteristics : uint16_t {
kRelocsStripped = 0x0001u, // 重定位信息已剥离
kExecutableImage = 0x0002u, // 可执行映像
kLineNumsStripped = 0x0004u, // 行号信息已剥离
kLocalSymsStripped = 0x0008u, // 局部符号已剥离
kAggressiveWsTrim = 0x0100u, // 积极的工作集修剪
kLargeAddressAware = 0x0200u, // 支持大地址
kBytesReversedLo = 0x0080u, // 低字节序反转
k32BitMachine = 0x1000u, // 32位计算机
kDebugStripped = 0x0400u, // 调试信息已剥离
kRemovableRunFromSwap = 0x0800u, // 可移动介质从交换区运行
kNetRunFromSwap = 0x1000u, // 网络从交换区运行
kSystem = 0x2000u, // 系统文件
kDll = 0x4000u, // DLL文件
kUpSystemOnly = 0x8000u, // 仅在单处理器上运行
kBytesReversedHi = 0x8000u // 高字节序反转
};
// 子系统枚举
// 标识执行环境
enum class PeSubsystem : uint16_t {
kUnknown = 0u, // 未知子系统
kNative = 1u, // 本地子系统
kWindowsGui = 2u, // Windows GUI子系统
kWindowsCui = 3u, // Windows CUI子系统
kOs2Cui = 5u, // OS/2 CUI子系统
kPosixCui = 7u, // POSIX CUI子系统
kNativeWindows = 8u, // 本地Windows子系统
kWindowsCeGui = 9u, // Windows CE GUI子系统
kEfiApplication = 10u, // EFI应用程序
kEfiBootServiceDriver = 11u, // EFI引导服务驱动程序
kEfiRuntimeDriver = 12u, // EFI运行时驱动程序
kEfiRom = 13u, // EFI ROM
kXbox = 14u, // Xbox
kWindowsBootApplication = 16u // Windows引导应用程序
};
// 数据目录索引枚举
// 标识PE文件中的各种数据目录
enum class DataDirectoryIndex : uint32_t {
kExport = 0, // 导出表
kImport = 1, // 导入表
kResource = 2, // 资源表
kException = 3, // 异常表
kSecurity = 4, // 安全表
kBaseReloc = 5, // 基址重定位表
kDebug = 6, // 调试信息
kArchitecture = 7, // 架构特定数据
kGlobalPtr = 8, // 全局指针
kTls = 9, // TLS表
kLoadConfig = 10, // 加载配置表
kBoundImport = 11, // 绑定导入表
kIat = 12, // 导入地址表
kDelayImport = 13, // 延迟导入表
kComDescriptor = 14, // COM描述符
kReserved = 15 // 保留
};
// 节特征枚举
// 标识节的各种属性
enum class SectionCharacteristics : uint32_t {
kTypeMask = 0x00000000u, // 类型掩码
kNoPad = 0x00000008u, // 无填充
kCode = 0x00000020u, // 代码节
kInitializedData = 0x00000040u, // 已初始化数据节
kUninitializedData = 0x00000080u, // 未初始化数据节
kShare = 0x10000000u, // 可共享
kExecute = 0x20000000u, // 可执行
kRead = 0x40000000u, // 可读
kWrite = 0x80000000u // 可写
};
// 重定位类型枚举
// 标识重定位条目类型
enum class RelocationType : uint8_t {
kAbsolute = 0, // 绝对重定位
kHigh = 1, // 高16位重定位
kLow = 2, // 低16位重定位
kHighLow = 3, // 高16位和低16位重定位
kHighAdj = 4, // 高位调整重定位
kMipsJmpaddr = 5, // MIPS跳转地址重定位
kArmMov32 = 5, // ARM MOV32重定位
kRisc386HighLow = 3, // RISC 386高16位和低16位重定位
kImageRelative = 10 // 映像相对重定位
};
// DOS头结构
// 存储DOS头信息
struct ImageDosHeader {
uint16_t e_magic; // 魔数,通常为0x5A4D("MZ")
uint16_t e_cblp; // 最后一页的字节数
uint16_t e_cp; // 文件页数
uint16_t e_crlc; // 重定位项数
uint16_t e_cparhdr; // 头部的段落数
uint16_t e_minalloc; // 最小额外段落数
uint16_t e_maxalloc; // 最大额外段落数
uint16_t e_ss; // 堆栈段
uint16_t e_sp; // 堆栈指针
uint16_t e_csum; // 校验和
uint16_t e_ip; // 指令指针
uint16_t e_cs; // 代码段
uint16_t e_lfarlc; // 重定位表偏移
uint16_t e_ovno; // 覆盖号
uint16_t e_res[4]; // 保留字段
uint16_t e_oemid; // OEM标识符
uint16_t e_oeminfo; // OEM信息
uint16_t e_res2[10]; // 保留字段
int32_t e_lfanew; // NT头偏移量
};
// 文件头结构
// 存储文件头信息
struct ImageFileHeader {
uint16_t machine; // 目标计算机类型
uint16_t number_of_sections; // 节的数量
uint32_t time_date_stamp; // 时间戳
uint32_t pointer_to_symbol_table; // 符号表偏移
uint32_t number_of_symbols; // 符号数量
uint16_t size_of_optional_header; // 可选头大小
uint16_t characteristics; // 文件特征
};
// 数据目录结构
// 存储数据目录信息
struct ImageDataDirectory {
uint32_t virtual_address; // 虚拟地址
uint32_t size; // 大小
};
// 32位可选头结构
// 存储PE32可选头信息
struct ImageOptionalHeader32 {
uint16_t magic; // 魔数,PE32为0x10B
uint8_t major_linker_version; // 主链接器版本号
uint8_t minor_linker_version; // 次链接器版本号
uint32_t size_of_code; // 代码大小
uint32_t size_of_initialized_data; // 已初始化数据大小
uint32_t size_of_uninitialized_data; // 未初始化数据大小
uint32_t address_of_entry_point; // 入口点地址
uint32_t base_of_code; // 代码基址
uint32_t base_of_data; // 数据基址
uint32_t image_base; // 映像基址
uint32_t section_alignment; // 节对齐
uint32_t file_alignment; // 文件对齐
uint16_t major_operating_system_version; // 主操作系统版本号
uint16_t minor_operating_system_version; // 次操作系统版本号
uint16_t major_image_version; // 主映像版本号
uint16_t minor_image_version; // 次映像版本号
uint16_t major_subsystem_version; // 主子系统版本号
uint16_t minor_subsystem_version; // 次子系统版本号
uint32_t win32_version_value; // Win32版本值
uint32_t size_of_image; // 映像大小
uint32_t size_of_headers; // 头大小
uint32_t check_sum; // 校验和
uint16_t subsystem; // 子系统类型
uint16_t dll_characteristics; // DLL特征
uint32_t size_of_stack_reserve; // 堆栈保留大小
uint32_t size_of_stack_commit; // 堆栈提交大小
uint32_t size_of_heap_reserve; // 堆保留大小
uint32_t size_of_heap_commit; // 堆提交大小
uint32_t loader_flags; // 加载器标志
uint32_t number_of_rva_and_sizes; // 数据目录数量
ImageDataDirectory data_directory[16]; // 数据目录数组
};
// 64位可选头结构
// 存储PE32+可选头信息
struct ImageOptionalHeader64 {
uint16_t magic; // 魔数,PE32+为0x20B
uint8_t major_linker_version; // 主链接器版本号
uint8_t minor_linker_version; // 次链接器版本号
uint32_t size_of_code; // 代码大小
uint32_t size_of_initialized_data; // 已初始化数据大小
uint32_t size_of_uninitialized_data; // 未初始化数据大小
uint32_t address_of_entry_point; // 入口点地址
uint32_t base_of_code; // 代码基址
uint64_t image_base; // 映像基址(64位)
uint32_t section_alignment; // 节对齐
uint32_t file_alignment; // 文件对齐
uint16_t major_operating_system_version; // 主操作系统版本号
uint16_t minor_operating_system_version; // 次操作系统版本号
uint16_t major_image_version; // 主映像版本号
uint16_t minor_image_version; // 次映像版本号
uint16_t major_subsystem_version; // 主子系统版本号
uint16_t minor_subsystem_version; // 次子系统版本号
uint32_t win32_version_value; // Win32版本值
uint32_t size_of_image; // 映像大小
uint32_t size_of_headers; // 头大小
uint32_t check_sum; // 校验和
uint16_t subsystem; // 子系统类型
uint16_t dll_characteristics; // DLL特征
uint64_t size_of_stack_reserve; // 堆栈保留大小(64位)
uint64_t size_of_stack_commit; // 堆栈提交大小(64位)
uint64_t size_of_heap_reserve; // 堆保留大小(64位)
uint64_t size_of_heap_commit; // 堆提交大小(64位)
uint32_t loader_flags; // 加载器标志
uint32_t number_of_rva_and_sizes; // 数据目录数量
ImageDataDirectory data_directory[16]; // 数据目录数组
};
// NT头结构
// 存储NT头信息
struct ImageNtHeaders {
uint32_t signature; // NT签名,通常为0x00004550("PE")
ImageFileHeader file_header; // 文件头
};
// 节头结构
// 存储节头信息
struct ImageSectionHeader {
uint8_t name[8]; // 节名称(最多8个字符)
union {
uint32_t physical_address; // 物理地址
uint32_t virtual_size; // 虚拟大小
} misc; // 联合体
uint32_t virtual_address; // 虚拟地址
uint32_t size_of_raw_data; // 原始数据大小
uint32_t pointer_to_raw_data; // 原始数据指针
uint32_t pointer_to_relocations; // 重定位指针
uint32_t pointer_to_line_numbers; // 行号指针
uint16_t number_of_relocations; // 重定位项数
uint16_t number_of_line_numbers; // 行号项数
uint32_t characteristics; // 节特征
};
// 导入描述符结构
// 存储导入表信息
struct ImageImportDescriptor {
union {
uint32_t characteristics; // 特征(未使用)
uint32_t original_first_thunk; // 原始第一个Thunk的RVA
};
uint32_t time_date_stamp; // 时间戳
uint32_t forwarder_chain; // 转发器链
uint32_t name; // DLL名称RVA
uint32_t first_thunk; // 第一个Thunk的RVA
};
// 导出目录结构
// 存储导出表信息
struct ImageExportDirectory {
uint32_t characteristics; // 特征(未使用)
uint32_t time_date_stamp; // 时间戳
uint16_t major_version; // 主版本号
uint16_t minor_version; // 次版本号
uint32_t name; // 模块名称RVA
uint32_t base; // 导出函数基址
uint32_t number_of_functions; // 函数数量
uint32_t number_of_names; // 名称数量
uint32_t address_of_functions; // 函数地址的RVA
uint32_t address_of_names; // 名称地址的RVA
uint32_t address_of_name_ordinals; // 名称序号的RVA
};
// 基址重定位块结构
// 存储重定位表信息
struct ImageBaseRelocation {
uint32_t virtual_address; // 块虚拟地址
uint32_t size_of_block; // 块大小
};
// 导入提示结构
// 存储导入函数提示信息
struct ImageImportHint {
uint16_t hint; // 导入提示(序号)
char name[1]; // 导入名称(可变长度)
};
} // namespace pe_parser
pe_parser.cpp
cpp
// 包含必要的头文件
#include <fstream>
#include <iomanip>
#include <sstream>
// 包含自定义头文件
#include "pe_parser.h"
// PE解析器命名空间
namespace pe_parser {
// 常量定义
constexpr std::uint16_t kDosMagic = 0x5A4Du; // DOS魔数 "MZ"
constexpr std::uint32_t kNtSignature = 0x00004550u; // NT签名 "PE"
constexpr std::uint16_t kPe32Magic = 0x10Bu; // PE32魔数
constexpr std::uint16_t kPe32PlusMagic = 0x20Bu; // PE32+魔数
// 将PE文件加载到内存中
// 参数: file_path - PE文件路径
void PeParser::LoadFile(std::string_view file_path) {
// 打开文件
std::ifstream file(file_path.data(), std::ios::binary);
if (!file.is_open()) {
throw FileException(file_path, "打开", "无法打开文件");
}
// 获取文件大小
file.seekg(0, std::ios::end);
const auto file_size = file.tellg();
file.seekg(0, std::ios::beg);
// 检查文件大小是否有效
if (file_size < static_cast<std::streampos>(sizeof(ImageDosHeader))) {
throw InvalidPeException("文件太小,不是有效的PE文件", 0);
}
// 读取文件数据到内存
file_data_.resize(static_cast<std::size_t>(file_size));
if (!file.read(reinterpret_cast<char*>(file_data_.data()), file_size)) {
throw FileException(file_path, "读取", "读取文件失败");
}
}
// 解析PE文件
void PeParser::Parse() {
// 检查文件是否已加载
if (!IsLoaded()) {
throw PeException("未加载文件");
}
// 解析DOS头
ParseDosHeader();
// 验证DOS头
ValidateDosHeader();
// 解析NT头
ParseNtHeaders();
// 验证NT签名
ValidateNtSignature();
// 解析节表
ParseSections();
// 解析导入表
ParseImports();
// 解析导出表
ParseExports();
// 解析重定位表
ParseRelocations();
}
// 解析DOS头
void PeParser::ParseDosHeader() {
// 验证偏移量
ValidateOffset<ImageDosHeader>(0, sizeof(ImageDosHeader), "DOS头");
// 获取DOS头指针
const auto* dos_header = GetPtr<ImageDosHeader>(0);
if (!dos_header) {
throw ParseException("PeParser", "DOS头", "获取DOS头指针失败");
}
// 填充DOS头信息
result_.dos_header.magic = dos_header->e_magic;
result_.dos_header.last_page_bytes = dos_header->e_cblp;
result_.dos_header.pages = dos_header->e_cp;
result_.dos_header.relocations = dos_header->e_crlc;
result_.dos_header.header_paragraphs = dos_header->e_cparhdr;
result_.dos_header.min_extra_paragraphs = dos_header->e_minalloc;
result_.dos_header.max_extra_paragraphs = dos_header->e_maxalloc;
result_.dos_header.stack_segment = dos_header->e_ss;
result_.dos_header.stack_pointer = dos_header->e_sp;
result_.dos_header.checksum = dos_header->e_csum;
result_.dos_header.instruction_pointer = dos_header->e_ip;
result_.dos_header.code_segment = dos_header->e_cs;
result_.dos_header.relocation_offset = dos_header->e_lfarlc;
result_.dos_header.overlay_number = dos_header->e_ovno;
result_.dos_header.oem_id = dos_header->e_oemid;
result_.dos_header.oem_info = dos_header->e_oeminfo;
result_.dos_header.nt_header_offset = dos_header->e_lfanew;
}
// 解析NT头
void PeParser::ParseNtHeaders() {
// 获取NT头偏移量
const auto nt_header_offset = result_.dos_header.nt_header_offset;
// 验证偏移量
ValidateOffset<ImageNtHeaders>(nt_header_offset, sizeof(ImageNtHeaders), "NT头");
// 获取NT头指针
const auto* nt_header = GetPtr<ImageNtHeaders>(nt_header_offset);
if (!nt_header) {
throw ParseException("PeParser", "NT头", "获取NT头指针失败");
}
// 存储NT签名
result_.nt_signature = nt_header->signature;
// 填充文件头信息
result_.file_header.machine = nt_header->file_header.machine;
result_.file_header.number_of_sections = nt_header->file_header.number_of_sections;
result_.file_header.time_date_stamp = nt_header->file_header.time_date_stamp;
result_.file_header.pointer_to_symbol_table = nt_header->file_header.pointer_to_symbol_table;
result_.file_header.number_of_symbols = nt_header->file_header.number_of_symbols;
result_.file_header.size_of_optional_header = nt_header->file_header.size_of_optional_header;
result_.file_header.characteristics = nt_header->file_header.characteristics;
// 计算可选头偏移量
const auto optional_header_offset = nt_header_offset + sizeof(ImageNtHeaders);
// 验证可选头偏移量
ValidateOffset<std::uint16_t>(optional_header_offset, sizeof(std::uint16_t), "可选头魔数");
// 获取魔数指针
const auto* magic_ptr = GetPtr<std::uint16_t>(optional_header_offset);
if (!magic_ptr) {
throw ParseException("PeParser", "可选头魔数", "获取可选头魔数指针失败");
}
// 检查魔数以确定是PE32还是PE32+
const auto magic = *magic_ptr;
if (magic == kPe32Magic) {
// PE32
result_.is_64bit = false;
// 验证32位可选头偏移量
ValidateOffset<ImageOptionalHeader32>(optional_header_offset, sizeof(ImageOptionalHeader32), "可选头32");
// 获取32位可选头指针
const auto* opt_header = GetPtr<ImageOptionalHeader32>(optional_header_offset);
if (!opt_header) {
throw ParseException("PeParser", "可选头32", "获取32位可选头指针失败");
}
// 填充32位可选头信息
result_.opt32.magic = opt_header->magic;
result_.opt32.major_linker_version = opt_header->major_linker_version;
result_.opt32.minor_linker_version = opt_header->minor_linker_version;
result_.opt32.size_of_code = opt_header->size_of_code;
result_.opt32.size_of_initialized_data = opt_header->size_of_initialized_data;
result_.opt32.size_of_uninitialized_data = opt_header->size_of_uninitialized_data;
result_.opt32.address_of_entry_point = opt_header->address_of_entry_point;
result_.opt32.base_of_code = opt_header->base_of_code;
result_.opt32.base_of_data = opt_header->base_of_data;
result_.opt32.image_base = opt_header->image_base;
result_.opt32.section_alignment = opt_header->section_alignment;
result_.opt32.file_alignment = opt_header->file_alignment;
result_.opt32.major_operating_system_version = opt_header->major_operating_system_version;
result_.opt32.minor_operating_system_version = opt_header->minor_operating_system_version;
result_.opt32.major_image_version = opt_header->major_image_version;
result_.opt32.minor_image_version = opt_header->minor_image_version;
result_.opt32.major_subsystem_version = opt_header->major_subsystem_version;
result_.opt32.minor_subsystem_version = opt_header->minor_subsystem_version;
result_.opt32.win32_version_value = opt_header->win32_version_value;
result_.opt32.size_of_image = opt_header->size_of_image;
result_.opt32.size_of_headers = opt_header->size_of_headers;
result_.opt32.check_sum = opt_header->check_sum;
result_.opt32.subsystem = opt_header->subsystem;
result_.opt32.dll_characteristics = opt_header->dll_characteristics;
result_.opt32.size_of_stack_reserve = opt_header->size_of_stack_reserve;
result_.opt32.size_of_stack_commit = opt_header->size_of_stack_commit;
result_.opt32.size_of_heap_reserve = opt_header->size_of_heap_reserve;
result_.opt32.size_of_heap_commit = opt_header->size_of_heap_commit;
result_.opt32.loader_flags = opt_header->loader_flags;
result_.opt32.number_of_rva_and_sizes = opt_header->number_of_rva_and_sizes;
// 填充数据目录信息
const char* data_dir_names[] = {
"导出表", "导入表", "资源表", "异常表", "安全表", "基址重定位表",
"调试信息", "架构数据", "全局指针", "TLS表", "加载配置表", "绑定导入表",
"导入地址表", "延迟导入表", "COM描述符", "保留"
};
for (std::size_t i = 0; i < 16; ++i) {
DataDirectoryInfo dd_info;
dd_info.name = data_dir_names[i];
dd_info.virtual_address = opt_header->data_directory[i].virtual_address;
dd_info.size = opt_header->data_directory[i].size;
result_.opt32.data_directories.push_back(dd_info);
}
}
else if (magic == kPe32PlusMagic) {
// PE32+
result_.is_64bit = true;
// 验证64位可选头偏移量
ValidateOffset<ImageOptionalHeader64>(optional_header_offset, sizeof(ImageOptionalHeader64), "可选头64");
// 获取64位可选头指针
const auto* opt_header = GetPtr<ImageOptionalHeader64>(optional_header_offset);
if (!opt_header) {
throw ParseException("PeParser", "可选头64", "获取64位可选头指针失败");
}
// 填充64位可选头信息
result_.opt64.magic = opt_header->magic;
result_.opt64.major_linker_version = opt_header->major_linker_version;
result_.opt64.minor_linker_version = opt_header->minor_linker_version;
result_.opt64.size_of_code = opt_header->size_of_code;
result_.opt64.size_of_initialized_data = opt_header->size_of_initialized_data;
result_.opt64.size_of_uninitialized_data = opt_header->size_of_uninitialized_data;
result_.opt64.address_of_entry_point = opt_header->address_of_entry_point;
result_.opt64.base_of_code = opt_header->base_of_code;
result_.opt64.image_base = opt_header->image_base;
result_.opt64.section_alignment = opt_header->section_alignment;
result_.opt64.file_alignment = opt_header->file_alignment;
result_.opt64.major_operating_system_version = opt_header->major_operating_system_version;
result_.opt64.minor_operating_system_version = opt_header->minor_operating_system_version;
result_.opt64.major_image_version = opt_header->major_image_version;
result_.opt64.minor_image_version = opt_header->minor_image_version;
result_.opt64.major_subsystem_version = opt_header->major_subsystem_version;
result_.opt64.minor_subsystem_version = opt_header->minor_subsystem_version;
result_.opt64.win32_version_value = opt_header->win32_version_value;
result_.opt64.size_of_image = opt_header->size_of_image;
result_.opt64.size_of_headers = opt_header->size_of_headers;
result_.opt64.check_sum = opt_header->check_sum;
result_.opt64.subsystem = opt_header->subsystem;
result_.opt64.dll_characteristics = opt_header->dll_characteristics;
result_.opt64.size_of_stack_reserve = opt_header->size_of_stack_reserve;
result_.opt64.size_of_stack_commit = opt_header->size_of_stack_commit;
result_.opt64.size_of_heap_reserve = opt_header->size_of_heap_reserve;
result_.opt64.size_of_heap_commit = opt_header->size_of_heap_commit;
result_.opt64.loader_flags = opt_header->loader_flags;
result_.opt64.number_of_rva_and_sizes = opt_header->number_of_rva_and_sizes;
// 填充数据目录信息
const char* data_dir_names[] = {
"导出表", "导入表", "资源表", "异常表", "安全表", "基址重定位表",
"调试信息", "架构数据", "全局指针", "TLS表", "加载配置表", "绑定导入表",
"导入地址表", "延迟导入表", "COM描述符", "保留"
};
for (std::size_t i = 0; i < 16; ++i) {
DataDirectoryInfo dd_info;
dd_info.name = data_dir_names[i];
dd_info.virtual_address = opt_header->data_directory[i].virtual_address;
dd_info.size = opt_header->data_directory[i].size;
result_.opt64.data_directories.push_back(dd_info);
}
}
else {
// 无效魔数
throw InvalidPeException(
"无效的可选头魔数,期望0x10B(PE32)或0x20B(PE32+)",
optional_header_offset);
}
}
// 解析节表
void PeParser::ParseSections() {
// 计算节表偏移量
const auto nt_header_offset = result_.dos_header.nt_header_offset;
const auto section_table_offset = nt_header_offset + sizeof(ImageNtHeaders) + result_.file_header.size_of_optional_header;
// 验证节表偏移量
ValidateOffset<ImageSectionHeader>(section_table_offset, sizeof(ImageSectionHeader) * result_.file_header.number_of_sections, "节表");
// 解析每个节
for (std::uint16_t i = 0; i < result_.file_header.number_of_sections; ++i) {
// 计算当前节头偏移量
const auto section_header_offset = section_table_offset + i * sizeof(ImageSectionHeader);
// 获取节头指针
const auto* section_header = GetPtr<ImageSectionHeader>(section_header_offset);
if (!section_header) {
throw ParseException("PeParser", "节头", "获取节头指针失败");
}
// 填充节信息
SectionInfo section_info;
// 提取节名称
char name[9] = { 0 };
std::memcpy(name, section_header->name, 8);
section_info.name = name;
section_info.virtual_size = section_header->misc.virtual_size;
section_info.virtual_address = section_header->virtual_address;
section_info.size_of_raw_data = section_header->size_of_raw_data;
section_info.pointer_to_raw_data = section_header->pointer_to_raw_data;
section_info.characteristics = section_header->characteristics;
result_.sections.push_back(section_info);
}
}
// 解析导入表
void PeParser::ParseImports() {
// 获取导入表RVA和大小
std::uint32_t import_table_rva = 0;
std::uint32_t import_table_size = 0;
if (result_.is_64bit) {
import_table_rva = result_.opt64.data_directories[static_cast<std::size_t>(DataDirectoryIndex::kImport)].virtual_address;
import_table_size = result_.opt64.data_directories[static_cast<std::size_t>(DataDirectoryIndex::kImport)].size;
}
else {
import_table_rva = result_.opt32.data_directories[static_cast<std::size_t>(DataDirectoryIndex::kImport)].virtual_address;
import_table_size = result_.opt32.data_directories[static_cast<std::size_t>(DataDirectoryIndex::kImport)].size;
}
// 检查导入表是否存在
if (import_table_rva == 0 || import_table_size == 0) {
return; // 无导入表
}
// 将RVA转换为文件偏移
const auto import_table_offset = RvaToFileOffset(import_table_rva);
if (import_table_offset == 0) {
return; // 无效的RVA
}
// 解析导入描述符
std::size_t current_offset = import_table_offset;
while (true) {
// 验证偏移量
ValidateOffset<ImageImportDescriptor>(current_offset, sizeof(ImageImportDescriptor), "导入描述符");
// 获取导入描述符指针
const auto* import_descriptor = GetPtr<ImageImportDescriptor>(current_offset);
if (!import_descriptor) {
throw ParseException("PeParser", "导入描述符", "获取导入描述符指针失败");
}
// 检查是否到达导入表末尾(所有字段都为0)
if (import_descriptor->original_first_thunk == 0 &&
import_descriptor->time_date_stamp == 0 &&
import_descriptor->forwarder_chain == 0 &&
import_descriptor->name == 0 &&
import_descriptor->first_thunk == 0) {
break;
}
// 解析DLL名称
const auto name_offset = RvaToFileOffset(import_descriptor->name);
if (name_offset == 0) {
current_offset += sizeof(ImageImportDescriptor);
continue;
}
const auto dll_name = ReadString(name_offset);
if (dll_name.empty()) {
current_offset += sizeof(ImageImportDescriptor);
continue;
}
// 创建导入信息
ImportInfo import_info;
import_info.dll_name = dll_name;
// 解析导入函数
const auto original_first_thunk = import_descriptor->original_first_thunk;
if (original_first_thunk != 0) {
const auto thunk_offset = RvaToFileOffset(original_first_thunk);
if (thunk_offset != 0) {
std::size_t thunk_current = thunk_offset;
while (true) {
// 验证偏移量
ValidateOffset<std::uint32_t>(thunk_current, sizeof(std::uint32_t), "导入Thunk");
// 获取导入提示/名称表条目
const auto* thunk_data = GetPtr<std::uint32_t>(thunk_current);
if (!thunk_data) {
break;
}
// 检查是否到达末尾
if (*thunk_data == 0) {
break;
}
// 检查是否为按名称导入(高位为0)
if ((*thunk_data & 0x80000000u) == 0) {
// 按名称导入
const auto hint_name_offset = RvaToFileOffset(*thunk_data);
if (hint_name_offset != 0) {
// 验证偏移量
ValidateOffset<ImageImportHint>(hint_name_offset, sizeof(ImageImportHint), "导入提示");
// 获取导入提示指针
const auto* hint = GetPtr<ImageImportHint>(hint_name_offset);
if (hint) {
// 提取函数名称(跳过提示字段)
const auto function_name = ReadString(hint_name_offset + sizeof(hint->hint));
if (!function_name.empty()) {
import_info.functions.push_back(function_name);
}
}
}
}
else {
// 按序号导入
// 序号 = 低16位
const auto ordinal = static_cast<std::uint16_t>(*thunk_data & 0xFFFFu);
import_info.functions.push_back("#" + std::to_string(ordinal));
}
// 移动到下一个导入条目
thunk_current += sizeof(std::uint32_t);
}
}
}
// 添加到导入信息列表
result_.imports.push_back(import_info);
// 移动到下一个导入描述符
current_offset += sizeof(ImageImportDescriptor);
}
}
// 解析导出表
void PeParser::ParseExports() {
// 获取导出表RVA和大小
std::uint32_t export_table_rva = 0;
std::uint32_t export_table_size = 0;
if (result_.is_64bit) {
export_table_rva = result_.opt64.data_directories[static_cast<std::size_t>(DataDirectoryIndex::kExport)].virtual_address;
export_table_size = result_.opt64.data_directories[static_cast<std::size_t>(DataDirectoryIndex::kExport)].size;
}
else {
export_table_rva = result_.opt32.data_directories[static_cast<std::size_t>(DataDirectoryIndex::kExport)].virtual_address;
export_table_size = result_.opt32.data_directories[static_cast<std::size_t>(DataDirectoryIndex::kExport)].size;
}
// 检查导出表是否存在
if (export_table_rva == 0 || export_table_size == 0) {
return; // 无导出表
}
// 将RVA转换为文件偏移
const auto export_table_offset = RvaToFileOffset(export_table_rva);
if (export_table_offset == 0) {
return; // 无效的RVA
}
// 验证偏移量
ValidateOffset<ImageExportDirectory>(export_table_offset, sizeof(ImageExportDirectory), "导出目录");
// 获取导出目录指针
const auto* export_dir = GetPtr<ImageExportDirectory>(export_table_offset);
if (!export_dir) {
throw ParseException("PeParser", "导出目录", "获取导出目录指针失败");
}
// 填充导出信息
result_.exports.base = export_dir->base;
result_.exports.number_of_functions = export_dir->number_of_functions;
result_.exports.number_of_names = export_dir->number_of_names;
// 解析模块名称
const auto name_offset = RvaToFileOffset(export_dir->name);
if (name_offset != 0) {
result_.exports.module_name = ReadString(name_offset);
}
// 解析导出函数名称
if (export_dir->number_of_names > 0) {
const auto names_rva = export_dir->address_of_names;
const auto ordinals_rva = export_dir->address_of_name_ordinals;
const auto names_offset = RvaToFileOffset(names_rva);
const auto ordinals_offset = RvaToFileOffset(ordinals_rva);
if (names_offset != 0 && ordinals_offset != 0) {
for (std::uint32_t i = 0; i < export_dir->number_of_names; ++i) {
// 验证偏移量
ValidateOffset<std::uint32_t>(names_offset + i * sizeof(std::uint32_t), sizeof(std::uint32_t), "导出名称RVA");
ValidateOffset<std::uint16_t>(ordinals_offset + i * sizeof(std::uint16_t), sizeof(std::uint16_t), "导出序号");
// 获取名称RVA
const auto* name_rva_ptr = GetPtr<std::uint32_t>(names_offset + i * sizeof(std::uint32_t));
if (!name_rva_ptr) {
continue;
}
// 获取序号
const auto* ordinal_ptr = GetPtr<std::uint16_t>(ordinals_offset + i * sizeof(std::uint16_t));
if (!ordinal_ptr) {
continue;
}
// 解析函数名称
const auto function_name_offset = RvaToFileOffset(*name_rva_ptr);
if (function_name_offset != 0) {
const auto function_name = ReadString(function_name_offset);
if (!function_name.empty()) {
result_.exports.function_names.push_back(function_name);
}
}
}
}
}
}
// 解析重定位表
void PeParser::ParseRelocations() {
// 获取重定位表RVA和大小
std::uint32_t reloc_table_rva = 0;
std::uint32_t reloc_table_size = 0;
if (result_.is_64bit) {
reloc_table_rva = result_.opt64.data_directories[static_cast<std::size_t>(DataDirectoryIndex::kBaseReloc)].virtual_address;
reloc_table_size = result_.opt64.data_directories[static_cast<std::size_t>(DataDirectoryIndex::kBaseReloc)].size;
}
else {
reloc_table_rva = result_.opt32.data_directories[static_cast<std::size_t>(DataDirectoryIndex::kBaseReloc)].virtual_address;
reloc_table_size = result_.opt32.data_directories[static_cast<std::size_t>(DataDirectoryIndex::kBaseReloc)].size;
}
// 检查重定位表是否存在
if (reloc_table_rva == 0 || reloc_table_size == 0) {
return; // 无重定位表
}
// 将RVA转换为文件偏移
const auto reloc_table_offset = RvaToFileOffset(reloc_table_rva);
if (reloc_table_offset == 0) {
return; // 无效的RVA
}
// 解析重定位块
std::size_t current_offset = reloc_table_offset;
const std::size_t end_offset = reloc_table_offset + reloc_table_size;
while (current_offset < end_offset) {
// 验证偏移量
ValidateOffset<ImageBaseRelocation>(current_offset, sizeof(ImageBaseRelocation), "基址重定位块");
// 获取重定位块指针
const auto* reloc_block = GetPtr<ImageBaseRelocation>(current_offset);
if (!reloc_block) {
throw ParseException("PeParser", "基址重定位块", "获取基址重定位块指针失败");
}
// 检查是否到达重定位表末尾(大小为0)
if (reloc_block->size_of_block == 0) {
break;
}
// 创建重定位块信息
RelocationBlock block;
block.virtual_address = reloc_block->virtual_address;
block.size_of_block = reloc_block->size_of_block;
// 计算重定位条目数量
const std::size_t entry_count = (reloc_block->size_of_block - sizeof(ImageBaseRelocation)) / sizeof(std::uint16_t);
// 解析重定位条目
const auto entries_offset = current_offset + sizeof(ImageBaseRelocation);
for (std::size_t i = 0; i < entry_count; ++i) {
// 验证偏移量
ValidateOffset<std::uint16_t>(entries_offset + i * sizeof(std::uint16_t), sizeof(std::uint16_t), "重定位条目");
// 获取重定位条目
const auto* entry_ptr = GetPtr<std::uint16_t>(entries_offset + i * sizeof(std::uint16_t));
if (!entry_ptr) {
continue;
}
// 提取重定位类型和偏移
const auto entry = *entry_ptr;
const auto type = static_cast<std::uint8_t>((entry >> 12) & 0x0Fu);
const auto offset = static_cast<std::uint16_t>(entry & 0x0FFFu);
// 添加到重定位条目列表
RelocationEntry reloc_entry;
reloc_entry.offset = offset;
reloc_entry.type = type;
block.entries.push_back(reloc_entry);
}
// 添加到重定位块列表
result_.relocations.push_back(block);
// 移动到下一个重定位块
current_offset += reloc_block->size_of_block;
}
}
// 模板函数:获取指定偏移处的指针
// 参数: offset - 偏移量
// 返回值: 指定类型的指针,无效时返回nullptr
template <typename T>
auto PeParser::GetPtr(std::size_t offset) const -> const T* {
// 检查偏移量是否有效
if (offset + sizeof(T) > file_data_.size()) {
return nullptr;
}
// 返回指定类型的指针
return reinterpret_cast<const T*>(file_data_.data() + offset);
}
// 将RVA转换为文件偏移
// 参数: rva - 相对虚拟地址
// 返回值: 对应的文件偏移,无效时返回0
auto PeParser::RvaToFileOffset(std::uint32_t rva) const -> std::uint32_t {
// 遍历所有节,查找包含此RVA的节
for (const auto& section : result_.sections) {
// 检查RVA是否在当前节中
if (rva >= section.virtual_address &&
rva < section.virtual_address + section.virtual_size) {
// 计算文件偏移
const auto file_offset = rva - section.virtual_address + section.pointer_to_raw_data;
// 检查文件偏移是否有效
if (file_offset < file_data_.size()) {
return static_cast<std::uint32_t>(file_offset);
}
break;
}
}
// 未找到节或文件偏移无效
return 0;
}
// 从指定偏移处读取字符串
// 参数: offset - 偏移量
// 返回值: 读取的字符串
auto PeParser::ReadString(std::uint32_t offset) const -> std::string {
// 检查偏移量是否超出文件大小
if (offset >= file_data_.size()) {
return {};
}
// 获取字符串起始指针
const char* ptr = reinterpret_cast<const char*>(file_data_.data() + offset);
// 计算最大字符串长度
std::size_t max_len = file_data_.size() - offset;
std::size_t len = 0;
// 读取字符串直到遇到空字符或达到最大长度
while (len < max_len && len < 256 && ptr[len] != '\0') {
++len;
}
// 返回读取的字符串
return std::string(ptr, len);
}
// 模板函数:验证偏移量
// 参数: offset - 偏移量
// size - 大小
// field_name - 字段名称
// 抛出: 偏移量无效时抛出ParseException异常
template <typename T>
void PeParser::ValidateOffset(std::size_t offset, std::size_t size, std::string_view field_name) const {
// 检查偏移量+大小是否超出文件大小
if (offset + size > file_data_.size()) {
throw ParseException("PeParser", std::string(field_name), "偏移量超出范围");
}
}
// 模板辅助函数(可选)
template <typename T>
void PeParser::ValidateOffset(std::size_t offset, std::string_view field_name) const {
ValidateOffset(offset, sizeof(T), field_name);
}
// 验证DOS头
// 抛出: DOS魔数无效时抛出InvalidPeException异常
void PeParser::ValidateDosHeader() const {
// 检查DOS魔数是否为"MZ"
if (result_.dos_header.magic != kDosMagic) {
throw InvalidPeException(
"无效的DOS魔数,期望0x5A4D,实际0x" +
std::to_string(result_.dos_header.magic),
0);
}
}
// 验证NT签名
// 抛出: NT签名无效时抛出InvalidPeException异常
void PeParser::ValidateNtSignature() const {
// 检查NT签名是否为"PE"
if (result_.nt_signature != kNtSignature) {
throw InvalidPeException(
"无效的NT签名,期望0x00004550,实际0x" +
std::to_string(result_.nt_signature),
result_.dos_header.nt_header_offset);
}
}
// 将解析结果转换为字符串
// 返回值: 解析结果的字符串表示
auto PeParser::ToString() const -> std::string {
std::ostringstream oss; // 用于构建输出的字符串流
oss << std::hex << std::setfill('0'); // 设置输出为十六进制,填充字符为'0'
// 输出DOS头信息
oss << "[DOS头]\n";
oss << " e_magic: 0x" << std::setw(4) << result_.dos_header.magic << "\n";
oss << " e_cblp: 0x" << std::setw(4) << result_.dos_header.last_page_bytes << "\n";
oss << " e_cp: 0x" << std::setw(4) << result_.dos_header.pages << "\n";
oss << " e_crlc: 0x" << std::setw(4) << result_.dos_header.relocations << "\n";
oss << " e_cparhdr: 0x" << std::setw(4) << result_.dos_header.header_paragraphs << "\n";
oss << " e_minalloc: 0x" << std::setw(4) << result_.dos_header.min_extra_paragraphs << "\n";
oss << " e_maxalloc: 0x" << std::setw(4) << result_.dos_header.max_extra_paragraphs << "\n";
oss << " e_ss: 0x" << std::setw(4) << result_.dos_header.stack_segment << "\n";
oss << " e_sp: 0x" << std::setw(4) << result_.dos_header.stack_pointer << "\n";
oss << " e_csum: 0x" << std::setw(4) << result_.dos_header.checksum << "\n";
oss << " e_ip: 0x" << std::setw(4) << result_.dos_header.instruction_pointer << "\n";
oss << " e_cs: 0x" << std::setw(4) << result_.dos_header.code_segment << "\n";
oss << " e_lfarlc: 0x" << std::setw(4) << result_.dos_header.relocation_offset << "\n";
oss << " e_ovno: 0x" << std::setw(4) << result_.dos_header.overlay_number << "\n";
oss << " e_oemid: 0x" << std::setw(4) << result_.dos_header.oem_id << "\n";
oss << " e_oeminfo: 0x" << std::setw(4) << result_.dos_header.oem_info << "\n";
oss << " e_lfanew: 0x" << result_.dos_header.nt_header_offset << "\n";
// 输出NT头信息
oss << "\n[NT头]\n";
oss << " 签名: 0x" << result_.nt_signature << "\n";
// 输出文件头信息
oss << "\n[文件头]\n";
oss << " 计算机类型: 0x" << std::setw(4) << result_.file_header.machine << "\n";
oss << " 节数量: " << std::dec << result_.file_header.number_of_sections << "\n";
oss << " 时间戳: 0x" << std::hex << result_.file_header.time_date_stamp << "\n";
oss << " 符号表指针: 0x" << std::hex << result_.file_header.pointer_to_symbol_table << "\n";
oss << " 符号数量: " << std::dec << result_.file_header.number_of_symbols << "\n";
oss << " 可选头大小: 0x" << std::hex << result_.file_header.size_of_optional_header << "\n";
oss << " 特征: 0x" << std::setw(4) << result_.file_header.characteristics << "\n";
// 输出可选头信息
oss << "\n[可选头]\n";
if (result_.is_64bit) {
const auto& opt = result_.opt64;
oss << " 魔数: 0x" << std::setw(4) << opt.magic << " (PE32+, 64位)\n";
oss << " 主链接器版本: " << std::dec << static_cast<int>(opt.major_linker_version) << "\n";
oss << " 次链接器版本: " << std::dec << static_cast<int>(opt.minor_linker_version) << "\n";
oss << " 代码大小: 0x" << std::hex << opt.size_of_code << "\n";
oss << " 已初始化数据大小: 0x" << std::hex << opt.size_of_initialized_data << "\n";
oss << " 未初始化数据大小: 0x" << std::hex << opt.size_of_uninitialized_data << "\n";
oss << " 入口点地址: 0x" << std::hex << opt.address_of_entry_point << "\n";
oss << " 代码基址: 0x" << std::hex << opt.base_of_code << "\n";
oss << " 映像基址: 0x" << std::hex << opt.image_base << "\n";
oss << " 节对齐: 0x" << std::hex << opt.section_alignment << "\n";
oss << " 文件对齐: 0x" << std::hex << opt.file_alignment << "\n";
oss << " 主操作系统版本: " << std::dec << opt.major_operating_system_version << "\n";
oss << " 次操作系统版本: " << std::dec << opt.minor_operating_system_version << "\n";
oss << " 主映像版本: " << std::dec << opt.major_image_version << "\n";
oss << " 次映像版本: " << std::dec << opt.minor_image_version << "\n";
oss << " 主子系统版本: " << std::dec << opt.major_subsystem_version << "\n";
oss << " 次子系统版本: " << std::dec << opt.minor_subsystem_version << "\n";
oss << " Win32版本值: 0x" << std::hex << opt.win32_version_value << "\n";
oss << " 映像大小: 0x" << std::hex << opt.size_of_image << "\n";
oss << " 头大小: 0x" << std::hex << opt.size_of_headers << "\n";
oss << " 校验和: 0x" << std::hex << opt.check_sum << "\n";
oss << " 子系统: 0x" << std::setw(2) << opt.subsystem << "\n";
oss << " DLL特征: 0x" << std::setw(4) << opt.dll_characteristics << "\n";
oss << " 堆栈保留大小: 0x" << std::hex << opt.size_of_stack_reserve << "\n";
oss << " 堆栈提交大小: 0x" << std::hex << opt.size_of_stack_commit << "\n";
oss << " 堆保留大小: 0x" << std::hex << opt.size_of_heap_reserve << "\n";
oss << " 堆提交大小: 0x" << std::hex << opt.size_of_heap_commit << "\n";
oss << " 加载器标志: 0x" << std::hex << opt.loader_flags << "\n";
oss << " 数据目录数量: " << std::dec << opt.number_of_rva_and_sizes << "\n";
// 输出数据目录信息
oss << "\n[数据目录]\n";
for (const auto& dd : opt.data_directories) {
if (dd.virtual_address != 0 || dd.size != 0) {
oss << " " << dd.name << ": VA=0x" << std::hex << dd.virtual_address
<< ", 大小=0x" << dd.size << "\n";
}
}
}
else {
const auto& opt = result_.opt32;
oss << " 魔数: 0x" << std::setw(4) << opt.magic << " (PE32, 32位)\n";
oss << " 主链接器版本: " << std::dec << static_cast<int>(opt.major_linker_version) << "\n";
oss << " 次链接器版本: " << std::dec << static_cast<int>(opt.minor_linker_version) << "\n";
oss << " 代码大小: 0x" << std::hex << opt.size_of_code << "\n";
oss << " 已初始化数据大小: 0x" << std::hex << opt.size_of_initialized_data << "\n";
oss << " 未初始化数据大小: 0x" << std::hex << opt.size_of_uninitialized_data << "\n";
oss << " 入口点地址: 0x" << std::hex << opt.address_of_entry_point << "\n";
oss << " 代码基址: 0x" << std::hex << opt.base_of_code << "\n";
oss << " 数据基址: 0x" << std::hex << opt.base_of_data << "\n";
oss << " 映像基址: 0x" << std::hex << opt.image_base << "\n";
oss << " 节对齐: 0x" << std::hex << opt.section_alignment << "\n";
oss << " 文件对齐: 0x" << std::hex << opt.file_alignment << "\n";
oss << " 主操作系统版本: " << std::dec << opt.major_operating_system_version << "\n";
oss << " 次操作系统版本: " << std::dec << opt.minor_operating_system_version << "\n";
oss << " 主映像版本: " << std::dec << opt.major_image_version << "\n";
oss << " 次映像版本: " << std::dec << opt.minor_image_version << "\n";
oss << " 主子系统版本: " << std::dec << opt.major_subsystem_version << "\n";
oss << " 次子系统版本: " << std::dec << opt.minor_subsystem_version << "\n";
oss << " Win32版本值: 0x" << std::hex << opt.win32_version_value << "\n";
oss << " 映像大小: 0x" << std::hex << opt.size_of_image << "\n";
oss << " 头大小: 0x" << std::hex << opt.size_of_headers << "\n";
oss << " 校验和: 0x" << std::hex << opt.check_sum << "\n";
oss << " 子系统: 0x" << std::setw(2) << opt.subsystem << "\n";
oss << " DLL特征: 0x" << std::setw(4) << opt.dll_characteristics << "\n";
oss << " 堆栈保留大小: 0x" << std::hex << opt.size_of_stack_reserve << "\n";
oss << " 堆栈提交大小: 0x" << std::hex << opt.size_of_stack_commit << "\n";
oss << " 堆保留大小: 0x" << std::hex << opt.size_of_heap_reserve << "\n";
oss << " 堆提交大小: 0x" << std::hex << opt.size_of_heap_commit << "\n";
oss << " 加载器标志: 0x" << std::hex << opt.loader_flags << "\n";
oss << " 数据目录数量: " << std::dec << opt.number_of_rva_and_sizes << "\n";
// 输出数据目录信息
oss << "\n[数据目录]\n";
for (const auto& dd : opt.data_directories) {
if (dd.virtual_address != 0 || dd.size != 0) {
oss << " " << dd.name << ": VA=0x" << std::hex << dd.virtual_address
<< ", 大小=0x" << dd.size << "\n";
}
}
}
// 输出节信息
oss << "\n[节]\n";
for (std::size_t i = 0; i < result_.sections.size(); ++i) {
const auto& sec = result_.sections[i];
oss << " 节 " << i << ": " << sec.name << "\n";
oss << " 虚拟大小: 0x" << std::hex << sec.virtual_size << "\n";
oss << " 虚拟地址: 0x" << std::hex << sec.virtual_address << "\n";
oss << " 原始数据大小: 0x" << std::hex << sec.size_of_raw_data << "\n";
oss << " 原始数据指针: 0x" << std::hex << sec.pointer_to_raw_data << "\n";
oss << " 特征: 0x" << std::hex << sec.characteristics << "\n";
}
// 输出导入信息
oss << "\n[导入表]\n";
if (result_.imports.empty()) {
oss << " (无导入)\n";
}
else {
for (std::size_t i = 0; i < result_.imports.size(); ++i) {
const auto& imp = result_.imports[i];
oss << " 导入 " << i << ": " << imp.dll_name << "\n";
}
}
// 输出导出信息
oss << "\n[导出表]\n";
if (result_.exports.module_name.empty()) {
oss << " (无导出)\n";
}
else {
oss << " 模块名称: " << result_.exports.module_name << "\n";
oss << " 基址: 0x" << std::hex << result_.exports.base << "\n";
oss << " 函数数量: " << std::dec << result_.exports.number_of_functions << "\n";
oss << " 名称数量: " << std::dec << result_.exports.number_of_names << "\n";
for (std::size_t i = 0; i < result_.exports.function_names.size() && i < 10; ++i) {
oss << " - " << result_.exports.function_names[i] << "\n";
}
if (result_.exports.function_names.size() > 10) {
oss << " ... 还有 " << (result_.exports.function_names.size() - 10) << " 个函数\n";
}
}
// 输出重定位信息
oss << "\n[重定位表]\n";
if (result_.relocations.empty()) {
oss << " (无重定位)\n";
}
else {
oss << " 块数量: " << std::dec << result_.relocations.size() << "\n";
for (std::size_t i = 0; i < result_.relocations.size(); ++i) {
const auto& block = result_.relocations[i];
oss << " 块 " << i << ": VA=0x" << std::hex << block.virtual_address
<< ", 大小=0x" << block.size_of_block << "\n";
oss << " 条目数: " << std::dec << block.entries.size() << "\n";
for (std::size_t j = 0; j < block.entries.size() && j < 5; ++j) {
oss << " 条目 " << j << ": 类型=0x" << std::hex
<< static_cast<int>(block.entries[j].type)
<< ", 偏移=0x" << std::setw(3) << std::setfill('0')
<< block.entries[j].offset << "\n";
}
if (block.entries.size() > 5) {
oss << " ... 还有 " << (block.entries.size() - 5) << " 个条目\n";
}
}
}
// 返回构建的字符串
return oss.str();
}
} // namespace pe_parser
main.cpp
cpp
// 包含PE解析器头文件
#include "pe_parser.h"
// 包含必要的标准头文件
#include <iostream> // 提供输入/输出流操作
#include <string> // 提供字符串类
// 主函数
// 参数: argc - 命令行参数个数
// argv - 命令行参数数组
// 返回值: 成功返回0,失败返回1
int main(int argc, char* argv[]) {
// 检查命令行参数是否正确
if (argc != 2) {
// 输出使用说明
std::cerr << "使用方法: PEParser <pe文件路径>" << std::endl;
return 1; // 返回错误码1
}
// 获取PE文件路径
const std::string file_path = argv[1];
try {
// 创建PE解析器实例
pe_parser::PeParser parser;
// 加载PE文件
parser.LoadFile(file_path);
// 解析PE文件
parser.Parse();
// 输出解析结果
std::cout << parser.ToString() << std::endl;
}
catch (const std::exception& e) {
// 捕获并输出异常信息
std::cerr << "错误: " << e.what() << std::endl;
return 1; // 返回错误码1
}
return 0; // 返回成功码0
}