PE 文件结构剖析:手工压缩与注入实战
字数 1674 2025-08-22 12:22:15
PE文件结构剖析与手工压缩、注入实战
1. PE文件概述
PE (Portable Executable) 是可移植的可执行文件,是Windows操作系统下可执行文件的标准格式,包括:
- 可执行文件 (exe, scr)
- 动态链接库 (dll, ocx, cpl)
- 驱动程序 (sys, vxd)
了解PE文件结构对于逆向工程、软件分析和安全研究非常重要。
2. PE文件结构详解
2.1 DOS头 (DOS Header + DOS Stub)
DOS Header (IMAGE_DOS_HEADER结构体,0x40字节):
typedef struct _IMAGE_DOS_HEADER {
WORD e_magic; // 标识符"MZ"(0x5A4D)
// ...其他字段...
LONG e_lfanew; // 指向PE头的偏移地址
} IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;
关键字段:
e_magic:标识文件为可执行文件e_lfanew:指向PE头的偏移地址
DOS Stub:
- 在DOS环境中运行的简单程序
- 在Windows下不会运行
- 大小不固定,通常可以删除
2.2 NT头 (NT Header)
IMAGE_NT_HEADERS结构体(32位下0xF8字节):
typedef struct _IMAGE_NT_HEADERS {
DWORD Signature; // PE签名"PE\0\0"
IMAGE_FILE_HEADER FileHeader; // PE文件头
IMAGE_OPTIONAL_HEADER32 OptionalHeader; // PE可选头
} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;
FileHeader (IMAGE_FILE_HEADER结构体,0x14字节):
typedef struct _IMAGE_FILE_HEADER {
WORD Machine; // 目标机器类型
WORD NumberOfSections; // 节区数量
DWORD TimeDateStamp; // 时间戳
// ...其他字段...
WORD Characteristics; // 文件特性标志
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;
OptionalHeader (IMAGE_OPTIONAL_HEADER32结构体):
typedef struct _IMAGE_OPTIONAL_HEADER {
WORD Magic; // 可选头类型(0x10B=32位,0x20B=64位)
// ...标准域...
DWORD AddressOfEntryPoint; // 程序入口点RVA
DWORD ImageBase; // 镜像基址
DWORD SectionAlignment; // 内存对齐大小
DWORD FileAlignment; // 文件对齐大小
// ...NT附加域...
DWORD SizeOfImage; // 镜像在内存中的大小
DWORD SizeOfHeaders; // PE头物理大小
// ...数据目录...
IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;
2.3 节表区 (Section Table)
节表由一系列IMAGE_SECTION_HEADER结构组成,每个结构描述一个节区:
typedef struct _IMAGE_SECTION_HEADER {
BYTE Name[8]; // 节区名称
union {
DWORD PhysicalAddress;
DWORD VirtualSize; // 节区实际大小
} Misc;
DWORD VirtualAddress; // 节区RVA
DWORD SizeOfRawData; // 文件中对齐后大小
DWORD PointerToRawData; // 文件偏移
// ...其他字段...
DWORD Characteristics; // 节区属性
} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;
2.4 导入表与导出表
导入表:
- 记录PE文件依赖的模块(dll)及其函数
- 每个依赖模块对应一个IMAGE_IMPORT_DESCRIPTOR结构(0x14字节)
typedef struct _IMAGE_IMPORT_DESCRIPTOR {
union {
DWORD Characteristics;
DWORD OriginalFirstThunk; // 指向IAT(导入地址表)
} DUMMYUNIONNAME;
DWORD TimeDateStamp;
DWORD ForwarderChain;
DWORD Name; // 指向DLL名称的RVA
DWORD FirstThunk; // 指向IAT
} IMAGE_IMPORT_DESCRIPTOR;
导出表:
- 记录DLL提供的导出函数
- 包含函数地址、名称和序号
- 可通过API获取导出函数:
LoadLibrary/GetModuleHandle获取模块句柄GetProcAddress获取函数地址
3. PE文件手工压缩实战
3.1 压缩思路
- 删除冗余数据(DOS Stub等)
- 修改对齐方式(最小为0x4)
- 合并节区(如代码节和数据节)
- 见缝插针(将有用数据填充到空白区域)
3.2 具体步骤
-
DOS头处理:
- 保留
e_magic和e_lfanew - 删除DOS Stub
- 修改
e_lfanew指向新的PE头位置
- 保留
-
NT头处理:
- 修改对齐方式(
SectionAlignment和FileAlignment为0x4) - 更新
AddressOfEntryPoint、SizeOfImage和SizeOfHeaders - 修正导入表和导入地址表的RVA和Size
- 修改对齐方式(
-
节表处理:
- 修改各节表的
VirtualSize、VirtualAddress、SizeOfRawData和PointerToRawData - 保留节区属性(
Characteristics)
- 修改各节表的
-
节区数据处理:
- 删除多余填充数据
- 修正代码中的硬编码地址
- 确保字符串有
\x00终止符
3.3 注意事项
SizeOfHeaders必须正确,否则无法定位节区.rdata节中的字符串必须有终止符- 修改后需保持所有RVA引用的正确性
- 合并节区需注意权限设置
4. PE文件代码注入实战
4.1 针对EXE的代码注入
- 在数据段找空白处插入字符串
- 在有执行权限的内存中注入指令
push 0 push 0x010051B0 ; 字符串地址 push 0x010051BB ; 标题地址 push 0 call MessageBoxA jmp original_entry ; 跳回原入口点 - 修改程序入口地址为注入代码的RVA
4.2 针对DLL的代码注入
- 使用Process Monitor查找目标程序依赖的系统DLL
- 使用AHeadLib.Net工具生成DLL源码模板
- 在
__ExecuteUserCutomCodes函数中添加恶意代码system("calc.exe"); - 编译后将DLL放在目标程序同级目录(利用DLL搜索顺序)
5. 总结
PE文件结构复杂但规律性强,掌握其结构可以:
- 深入理解Windows程序加载执行机制
- 进行有效的文件压缩和优化
- 实现代码注入和功能扩展
- 开展逆向分析和安全研究
关键点在于理解各结构体的含义和相互关系,以及RVA与文件偏移的转换。实际操作中需结合调试工具(如IDA、010Editor)验证修改效果。