Windows PE结构
2、PE文件头解析
1、课程目标
- 深入理解DOS头结构
- 掌握PE文件头(File Header)的各个字段
- 掌握可选头(Optional Header)的各个字段
- 理解不同字段对程序执行的影响
2、名词解释
| 名词 | 全称 | 解释 |
|---|---|---|
| DOS Header | DOS头 | PE文件开头的64字节 |
| e_lfanew | - | 指向PE签名的偏移 |
| File Header | 文件头 | 描述文件基本属性 |
| Optional Header | 可选头 | 包含加载和运行时信息 |
| Machine | 机器类型 | 目标CPU架构 |
| Characteristics | 特征 | 文件属性标志 |
| Magic | 魔数 | 区分PE32和PE32+ |
| EntryPoint | 入口点 | 程序执行起始地址 |
3、使用工具
- CFF Explorer
- PE-bear
- 010 Editor
- x64dbg
4、技术原理
4.1、DOS头结构
typedef struct _IMAGE_DOS_HEADER {
WORD e_magic; // MZ标识 (0x5A4D)
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]; // 保留
WORD e_oemid; // OEM标识
WORD e_oeminfo; // OEM信息
WORD e_res2[10]; // 保留
LONG e_lfanew; // PE头偏移 ★重要★
} IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;
4.2、文件头结构
typedef struct _IMAGE_FILE_HEADER {
WORD Machine; // 目标CPU架构
WORD NumberOfSections; // 区段数量
DWORD TimeDateStamp; // 时间戳
DWORD PointerToSymbolTable; // 符号表指针(已废弃)
DWORD NumberOfSymbols; // 符号数量(已废弃)
WORD SizeOfOptionalHeader; // 可选头大小
WORD Characteristics; // 文件特征
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;
// Machine类型
#define IMAGE_FILE_MACHINE_I386 0x014c // x86
#define IMAGE_FILE_MACHINE_AMD64 0x8664 // x64
#define IMAGE_FILE_MACHINE_ARM 0x01c0 // ARM
#define IMAGE_FILE_MACHINE_ARM64 0xAA64 // ARM64
// Characteristics标志
#define IMAGE_FILE_RELOCS_STRIPPED 0x0001 // 无重定位
#define IMAGE_FILE_EXECUTABLE_IMAGE 0x0002 // 可执行
#define IMAGE_FILE_LINE_NUMS_STRIPPED 0x0004 // 无行号
#define IMAGE_FILE_LOCAL_SYMS_STRIPPED 0x0008 // 无本地符号
#define IMAGE_FILE_LARGE_ADDRESS_AWARE 0x0020 // 大地址感知
#define IMAGE_FILE_32BIT_MACHINE 0x0100 // 32位机器
#define IMAGE_FILE_DEBUG_STRIPPED 0x0200 // 无调试信息
#define IMAGE_FILE_DLL 0x2000 // DLL文件
4.3、可选头结构(PE32)
typedef struct _IMAGE_OPTIONAL_HEADER32 {
// 标准字段
WORD Magic; // 0x10B=PE32, 0x20B=PE32+
BYTE MajorLinkerVersion; // 链接器主版本
BYTE MinorLinkerVersion; // 链接器次版本
DWORD SizeOfCode; // 代码段大小
DWORD SizeOfInitializedData; // 已初始化数据大小
DWORD SizeOfUninitializedData; // 未初始化数据大小
DWORD AddressOfEntryPoint; // 入口点RVA ★重要★
DWORD BaseOfCode; // 代码段基址
DWORD BaseOfData; // 数据段基址(PE32+无此字段)
// NT扩展字段
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; // DLL特征 ★重要★
DWORD SizeOfStackReserve; // 栈保留大小
DWORD SizeOfStackCommit; // 栈提交大小
DWORD SizeOfHeapReserve; // 堆保留大小
DWORD SizeOfHeapCommit; // 堆提交大小
DWORD LoaderFlags; // 加载器标志
DWORD NumberOfRvaAndSizes; // 数据目录数量
IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
} IMAGE_OPTIONAL_HEADER32;
5、代码实现
5.1、解析DOS头
#include <windows.h>
#include <stdio.h>
// 解析DOS头
void ParseDosHeader(LPVOID fileData) {
PIMAGE_DOS_HEADER dosHeader = (PIMAGE_DOS_HEADER)fileData;
printf("\n========== DOS Header ==========\n");
printf("%-25s 0x%04X", "e_magic:", dosHeader->e_magic);
printf(" (%c%c)\n", dosHeader->e_magic & 0xFF, (dosHeader->e_magic >> 8) & 0xFF);
printf("%-25s %d\n", "e_cblp:", dosHeader->e_cblp);
printf("%-25s %d\n", "e_cp:", dosHeader->e_cp);
printf("%-25s %d\n", "e_crlc:", dosHeader->e_crlc);
printf("%-25s %d\n", "e_cparhdr:", dosHeader->e_cparhdr);
printf("%-25s %d\n", "e_minalloc:", dosHeader->e_minalloc);
printf("%-25s %d\n", "e_maxalloc:", dosHeader->e_maxalloc);
printf("%-25s 0x%04X\n", "e_ss:", dosHeader->e_ss);
printf("%-25s 0x%04X\n", "e_sp:", dosHeader->e_sp);
printf("%-25s 0x%04X\n", "e_csum:", dosHeader->e_csum);
printf("%-25s 0x%04X\n", "e_ip:", dosHeader->e_ip);
printf("%-25s 0x%04X\n", "e_cs:", dosHeader->e_cs);
printf("%-25s 0x%04X\n", "e_lfarlc:", dosHeader->e_lfarlc);
printf("%-25s %d\n", "e_ovno:", dosHeader->e_ovno);
printf("%-25s 0x%04X\n", "e_oemid:", dosHeader->e_oemid);
printf("%-25s 0x%04X\n", "e_oeminfo:", dosHeader->e_oeminfo);
printf("%-25s 0x%08X\n", "e_lfanew:", dosHeader->e_lfanew);
// DOS Stub
if (dosHeader->e_lfanew > sizeof(IMAGE_DOS_HEADER)) {
printf("\n[DOS Stub Message]\n");
char* stub = (char*)fileData + sizeof(IMAGE_DOS_HEADER);
int stubSize = dosHeader->e_lfanew - sizeof(IMAGE_DOS_HEADER);
// 查找可打印字符串
for (int i = 0; i < stubSize; i++) {
if (stub[i] >= 0x20 && stub[i] < 0x7F) {
printf("%c", stub[i]);
} else if (stub[i] == '\r' || stub[i] == '\n') {
printf("%c", stub[i]);
}
}
printf("\n");
}
}
5.2、解析文件头
// 获取Machine类型名称
const char* GetMachineName(WORD machine) {
switch (machine) {
case IMAGE_FILE_MACHINE_I386: return "Intel 386 (x86)";
case IMAGE_FILE_MACHINE_AMD64: return "AMD64 (x64)";
case IMAGE_FILE_MACHINE_ARM: return "ARM";
case IMAGE_FILE_MACHINE_ARMNT: return "ARM Thumb-2";
case IMAGE_FILE_MACHINE_ARM64: return "ARM64";
case IMAGE_FILE_MACHINE_IA64: return "Intel Itanium";
default: return "Unknown";
}
}
// 解析Characteristics
void PrintCharacteristics(WORD chars) {
printf("%-25s 0x%04X\n", "Characteristics:", chars);
struct { WORD flag; const char* name; } flags[] = {
{ IMAGE_FILE_RELOCS_STRIPPED, "RELOCS_STRIPPED" },
{ IMAGE_FILE_EXECUTABLE_IMAGE, "EXECUTABLE_IMAGE" },
{ IMAGE_FILE_LINE_NUMS_STRIPPED, "LINE_NUMS_STRIPPED" },
{ IMAGE_FILE_LOCAL_SYMS_STRIPPED, "LOCAL_SYMS_STRIPPED" },
{ IMAGE_FILE_AGGRESIVE_WS_TRIM, "AGGRESIVE_WS_TRIM" },
{ IMAGE_FILE_LARGE_ADDRESS_AWARE, "LARGE_ADDRESS_AWARE" },
{ IMAGE_FILE_BYTES_REVERSED_LO, "BYTES_REVERSED_LO" },
{ IMAGE_FILE_32BIT_MACHINE, "32BIT_MACHINE" },
{ IMAGE_FILE_DEBUG_STRIPPED, "DEBUG_STRIPPED" },
{ IMAGE_FILE_REMOVABLE_RUN_FROM_SWAP, "REMOVABLE_RUN_FROM_SWAP" },
{ IMAGE_FILE_NET_RUN_FROM_SWAP, "NET_RUN_FROM_SWAP" },
{ IMAGE_FILE_SYSTEM, "SYSTEM" },
{ IMAGE_FILE_DLL, "DLL" },
{ IMAGE_FILE_UP_SYSTEM_ONLY, "UP_SYSTEM_ONLY" },
{ IMAGE_FILE_BYTES_REVERSED_HI, "BYTES_REVERSED_HI" },
};
for (int i = 0; i < sizeof(flags)/sizeof(flags[0]); i++) {
if (chars & flags[i].flag) {
printf(" -> %s\n", flags[i].name);
}
}
}
// 解析文件头
void ParseFileHeader(LPVOID fileData) {
PIMAGE_DOS_HEADER dosHeader = (PIMAGE_DOS_HEADER)fileData;
PIMAGE_NT_HEADERS ntHeaders = (PIMAGE_NT_HEADERS)(
(PBYTE)fileData + dosHeader->e_lfanew
);
PIMAGE_FILE_HEADER fileHeader = &ntHeaders->FileHeader;
printf("\n========== File Header ==========\n");
printf("%-25s 0x%04X (%s)\n", "Machine:",
fileHeader->Machine, GetMachineName(fileHeader->Machine));
printf("%-25s %d\n", "NumberOfSections:", fileHeader->NumberOfSections);
// 解析时间戳
time_t timestamp = fileHeader->TimeDateStamp;
struct tm* tm = gmtime(×tamp);
char timeStr[64];
strftime(timeStr, sizeof(timeStr), "%Y-%m-%d %H:%M:%S UTC", tm);
printf("%-25s 0x%08X (%s)\n", "TimeDateStamp:",
fileHeader->TimeDateStamp, timeStr);
printf("%-25s 0x%08X\n", "PointerToSymbolTable:",
fileHeader->PointerToSymbolTable);
printf("%-25s %d\n", "NumberOfSymbols:", fileHeader->NumberOfSymbols);
printf("%-25s %d bytes\n", "SizeOfOptionalHeader:",
fileHeader->SizeOfOptionalHeader);
PrintCharacteristics(fileHeader->Characteristics);
}
5.3、解析可选头
// 获取子系统名称
const char* GetSubsystemName(WORD subsystem) {
switch (subsystem) {
case IMAGE_SUBSYSTEM_UNKNOWN: return "Unknown";
case IMAGE_SUBSYSTEM_NATIVE: return "Native";
case IMAGE_SUBSYSTEM_WINDOWS_GUI: return "Windows GUI";
case IMAGE_SUBSYSTEM_WINDOWS_CUI: return "Windows Console";
case IMAGE_SUBSYSTEM_OS2_CUI: return "OS/2 Console";
case IMAGE_SUBSYSTEM_POSIX_CUI: return "POSIX Console";
case IMAGE_SUBSYSTEM_WINDOWS_CE_GUI: return "Windows CE GUI";
case IMAGE_SUBSYSTEM_EFI_APPLICATION: return "EFI Application";
case IMAGE_SUBSYSTEM_EFI_BOOT_SERVICE_DRIVER: return "EFI Boot Service Driver";
case IMAGE_SUBSYSTEM_EFI_RUNTIME_DRIVER: return "EFI Runtime Driver";
case IMAGE_SUBSYSTEM_EFI_ROM: return "EFI ROM";
case IMAGE_SUBSYSTEM_XBOX: return "Xbox";
case IMAGE_SUBSYSTEM_WINDOWS_BOOT_APPLICATION: return "Windows Boot Application";
default: return "Unknown";
}
}
// 解析DLL特征
void PrintDllCharacteristics(WORD chars) {
printf("%-25s 0x%04X\n", "DllCharacteristics:", chars);
struct { WORD flag; const char* name; } flags[] = {
{ IMAGE_DLLCHARACTERISTICS_HIGH_ENTROPY_VA, "HIGH_ENTROPY_VA" },
{ IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE, "DYNAMIC_BASE (ASLR)" },
{ IMAGE_DLLCHARACTERISTICS_FORCE_INTEGRITY, "FORCE_INTEGRITY" },
{ IMAGE_DLLCHARACTERISTICS_NX_COMPAT, "NX_COMPAT (DEP)" },
{ IMAGE_DLLCHARACTERISTICS_NO_ISOLATION, "NO_ISOLATION" },
{ IMAGE_DLLCHARACTERISTICS_NO_SEH, "NO_SEH" },
{ IMAGE_DLLCHARACTERISTICS_NO_BIND, "NO_BIND" },
{ IMAGE_DLLCHARACTERISTICS_APPCONTAINER, "APPCONTAINER" },
{ IMAGE_DLLCHARACTERISTICS_WDM_DRIVER, "WDM_DRIVER" },
{ IMAGE_DLLCHARACTERISTICS_GUARD_CF, "GUARD_CF (CFG)" },
{ IMAGE_DLLCHARACTERISTICS_TERMINAL_SERVER_AWARE, "TERMINAL_SERVER_AWARE" },
};
for (int i = 0; i < sizeof(flags)/sizeof(flags[0]); i++) {
if (chars & flags[i].flag) {
printf(" -> %s\n", flags[i].name);
}
}
}
// 解析可选头(PE32和PE32+)
void ParseOptionalHeader(LPVOID fileData) {
PIMAGE_DOS_HEADER dosHeader = (PIMAGE_DOS_HEADER)fileData;
PIMAGE_NT_HEADERS ntHeaders = (PIMAGE_NT_HEADERS)(
(PBYTE)fileData + dosHeader->e_lfanew
);
BOOL is64 = (ntHeaders->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR64_MAGIC);
printf("\n========== Optional Header ==========\n");
if (is64) {
PIMAGE_OPTIONAL_HEADER64 opt = (PIMAGE_OPTIONAL_HEADER64)&ntHeaders->OptionalHeader;
printf("%-25s 0x%04X (PE32+/64-bit)\n", "Magic:", opt->Magic);
printf("%-25s %d.%d\n", "LinkerVersion:",
opt->MajorLinkerVersion, opt->MinorLinkerVersion);
printf("%-25s 0x%08X\n", "SizeOfCode:", opt->SizeOfCode);
printf("%-25s 0x%08X\n", "SizeOfInitializedData:", opt->SizeOfInitializedData);
printf("%-25s 0x%08X\n", "SizeOfUninitializedData:", opt->SizeOfUninitializedData);
printf("%-25s 0x%08X (RVA)\n", "AddressOfEntryPoint:", opt->AddressOfEntryPoint);
printf("%-25s 0x%08X\n", "BaseOfCode:", opt->BaseOfCode);
printf("%-25s 0x%016llX\n", "ImageBase:", opt->ImageBase);
printf("%-25s 0x%08X (%d bytes)\n", "SectionAlignment:",
opt->SectionAlignment, opt->SectionAlignment);
printf("%-25s 0x%08X (%d bytes)\n", "FileAlignment:",
opt->FileAlignment, opt->FileAlignment);
printf("%-25s %d.%d\n", "OSVersion:",
opt->MajorOperatingSystemVersion, opt->MinorOperatingSystemVersion);
printf("%-25s %d.%d\n", "ImageVersion:",
opt->MajorImageVersion, opt->MinorImageVersion);
printf("%-25s %d.%d\n", "SubsystemVersion:",
opt->MajorSubsystemVersion, opt->MinorSubsystemVersion);
printf("%-25s 0x%08X\n", "SizeOfImage:", opt->SizeOfImage);
printf("%-25s 0x%08X\n", "SizeOfHeaders:", opt->SizeOfHeaders);
printf("%-25s 0x%08X\n", "CheckSum:", opt->CheckSum);
printf("%-25s %d (%s)\n", "Subsystem:",
opt->Subsystem, GetSubsystemName(opt->Subsystem));
PrintDllCharacteristics(opt->DllCharacteristics);
printf("%-25s 0x%016llX\n", "SizeOfStackReserve:", opt->SizeOfStackReserve);
printf("%-25s 0x%016llX\n", "SizeOfStackCommit:", opt->SizeOfStackCommit);
printf("%-25s 0x%016llX\n", "SizeOfHeapReserve:", opt->SizeOfHeapReserve);
printf("%-25s 0x%016llX\n", "SizeOfHeapCommit:", opt->SizeOfHeapCommit);
printf("%-25s %d\n", "NumberOfRvaAndSizes:", opt->NumberOfRvaAndSizes);
} else {
PIMAGE_OPTIONAL_HEADER32 opt = (PIMAGE_OPTIONAL_HEADER32)&ntHeaders->OptionalHeader;
printf("%-25s 0x%04X (PE32/32-bit)\n", "Magic:", opt->Magic);
printf("%-25s %d.%d\n", "LinkerVersion:",
opt->MajorLinkerVersion, opt->MinorLinkerVersion);
printf("%-25s 0x%08X\n", "SizeOfCode:", opt->SizeOfCode);
printf("%-25s 0x%08X\n", "AddressOfEntryPoint:", opt->AddressOfEntryPoint);
printf("%-25s 0x%08X\n", "BaseOfCode:", opt->BaseOfCode);
printf("%-25s 0x%08X\n", "BaseOfData:", opt->BaseOfData);
printf("%-25s 0x%08X\n", "ImageBase:", opt->ImageBase);
printf("%-25s 0x%08X\n", "SectionAlignment:", opt->SectionAlignment);
printf("%-25s 0x%08X\n", "FileAlignment:", opt->FileAlignment);
printf("%-25s 0x%08X\n", "SizeOfImage:", opt->SizeOfImage);
printf("%-25s 0x%08X\n", "SizeOfHeaders:", opt->SizeOfHeaders);
printf("%-25s %d (%s)\n", "Subsystem:",
opt->Subsystem, GetSubsystemName(opt->Subsystem));
PrintDllCharacteristics(opt->DllCharacteristics);
}
}
5.4、修改PE头信息
// 修改入口点
BOOL ModifyEntryPoint(LPVOID fileData, DWORD newEntryPoint) {
PIMAGE_DOS_HEADER dosHeader = (PIMAGE_DOS_HEADER)fileData;
PIMAGE_NT_HEADERS ntHeaders = (PIMAGE_NT_HEADERS)(
(PBYTE)fileData + dosHeader->e_lfanew
);
printf("[*] 原入口点: 0x%08X\n",
ntHeaders->OptionalHeader.AddressOfEntryPoint);
ntHeaders->OptionalHeader.AddressOfEntryPoint = newEntryPoint;
printf("[+] 新入口点: 0x%08X\n", newEntryPoint);
return TRUE;
}
// 修改ImageBase
BOOL ModifyImageBase(LPVOID fileData, ULONGLONG newImageBase) {
PIMAGE_DOS_HEADER dosHeader = (PIMAGE_DOS_HEADER)fileData;
PIMAGE_NT_HEADERS ntHeaders = (PIMAGE_NT_HEADERS)(
(PBYTE)fileData + dosHeader->e_lfanew
);
BOOL is64 = (ntHeaders->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR64_MAGIC);
if (is64) {
PIMAGE_OPTIONAL_HEADER64 opt = (PIMAGE_OPTIONAL_HEADER64)&ntHeaders->OptionalHeader;
opt->ImageBase = newImageBase;
} else {
PIMAGE_OPTIONAL_HEADER32 opt = (PIMAGE_OPTIONAL_HEADER32)&ntHeaders->OptionalHeader;
opt->ImageBase = (DWORD)newImageBase;
}
return TRUE;
}
// 禁用ASLR
BOOL DisableASLR(LPVOID fileData) {
PIMAGE_DOS_HEADER dosHeader = (PIMAGE_DOS_HEADER)fileData;
PIMAGE_NT_HEADERS ntHeaders = (PIMAGE_NT_HEADERS)(
(PBYTE)fileData + dosHeader->e_lfanew
);
ntHeaders->OptionalHeader.DllCharacteristics &=
~IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE;
printf("[+] ASLR已禁用\n");
return TRUE;
}
// 禁用DEP
BOOL DisableDEP(LPVOID fileData) {
PIMAGE_DOS_HEADER dosHeader = (PIMAGE_DOS_HEADER)fileData;
PIMAGE_NT_HEADERS ntHeaders = (PIMAGE_NT_HEADERS)(
(PBYTE)fileData + dosHeader->e_lfanew
);
ntHeaders->OptionalHeader.DllCharacteristics &=
~IMAGE_DLLCHARACTERISTICS_NX_COMPAT;
printf("[+] DEP已禁用\n");
return TRUE;
}
6、课后作业
-
编写完整的PE头解析器
- 解析所有DOS头字段
- 解析所有文件头字段
- 解析所有可选头字段
- 支持PE32和PE32+
-
实现PE头修改工具
- 修改入口点
- 修改时间戳
- 禁用/启用安全特性
-
分析PE头与程序行为的关系
- 修改Subsystem观察程序运行变化
- 修改Characteristics观察加载行为
- 研究DllCharacteristics的安全意义