可移植執行檔 (Portable Executable, PE) 是 Windows 核心執行檔格式 (EXE、DLL、SYS、OCX 等)。深入理解 PE 結構是逆向工程、病毒分析、載入器開發、二進位修補、編譯器後端優化的基石。本文系統梳理 PE 文件的物理佈局、核心結構體定義 (基於 winnt.h)、關鍵目錄表與區段表關係,並以資源目錄樹為例展示多層級資料組織。
+------------------+ <-- 文件偏移 0
| DOS MZ Header | (IMAGE_DOS_HEADER) 含 e_lfanew 指向 PE Header
| DOS Stub | 通常顯示 "This program cannot run in DOS mode"
+------------------+ <-- e_lfanew (通常 0x80 或 0x100)
| PE Signature | "PE\0\0" (4 bytes)
| COFF File Header | (IMAGE_FILE_HEADER) 機器類型、區段數、時間戳...
| Optional Header | (IMAGE_OPTIONAL_HEADER) 邏輯佈局核心:入口點、映像基址、區段對齊、資料目錄表...
+------------------+
| Section Table | 區段表陣列 (IMAGE_SECTION_HEADER[]) 每區段 40 bytes
| (Section Headers)|
+------------------+ <-- 區段原始數據起始 (PointerToRawData 對齊)
| Section 1 (.text)| 代碼區
| Section 2 (.data)| 初始化數據
| Section 3 (.rsrc)| 資源區
| ... |
| Section n |
+------------------+
typedef struct _IMAGE_NT_HEADERS {
DWORD Signature; // "PE\0\0"
IMAGE_FILE_HEADER FileHeader; // 物理佈局信息
IMAGE_OPTIONAL_HEADER32 OptionalHeader; // 邏輯佈局信息 (32位)
} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;
typedef struct _IMAGE_FILE_HEADER {
WORD Machine; // 0x14c = Intel 386+, 0x8664 = x64
WORD NumberOfSections; // 區段數量
DWORD TimeDateStamp; // 編譯時間戳
DWORD PointerToSymbolTable; // COFF 符號表 (調試用)
DWORD NumberOfSymbols;
WORD SizeOfOptionalHeader; // OptionalHeader 大小
WORD Characteristics; // 0x0002=EXE, 0x2000=DLL, 0x0020=系統檔...
} IMAGE_FILE_HEADER;
typedef struct _IMAGE_OPTIONAL_HEADER {
WORD Magic; // 0x10b = PE32, 0x20b = PE32+ (64位)
BYTE MajorLinkerVersion;
BYTE MinorLinkerVersion;
DWORD SizeOfCode; // 所有代碼區段大小總和
DWORD SizeOfInitializedData; // 初始化數據區段大小
DWORD SizeOfUninitializedData; // 未初始化數據 (.bss)
DWORD AddressOfEntryPoint; // 程式入口點 RVA (相對虛擬地址) **關鍵**
DWORD BaseOfCode; // 代碼區段起始 RVA
DWORD BaseOfData; // 數據區段起始 RVA
DWORD ImageBase; // 建議載入基址 (EXE通常0x400000, DLL 0x10000000)
DWORD SectionAlignment; // 記憶體中區段對齊粒度 (通常 0x1000 = 4KB)
DWORD FileAlignment; // 文件中區段對齊粒度 (通常 0x200 = 512B)
WORD MajorOperatingSystemVersion;
WORD MinorOperatingSystemVersion;
WORD MajorImageVersion;
WORD MinorImageVersion;
WORD MajorSubsystemVersion; // 4.0 以上才有 3D 邊框
WORD MinorSubsystemVersion;
DWORD Win32VersionValue; // 保留 0
DWORD SizeOfImage; // 映像在記憶體中總大小 (含頭、區段對齊後)
DWORD SizeOfHeaders; // 所有頭 + 區段表大小 (FileAlignment 對齊)
DWORD CheckSum; // 校驗和 (驅動程式/系統檔必填)
WORD Subsystem; // 2=GUI, 3=CUI, 1=Native, 9=Windows CE
WORD DllCharacteristics; // ASLR, DEP, 高熵VA 等旗標
DWORD SizeOfStackReserve/Commit;
DWORD SizeOfHeapReserve/Commit;
DWORD LoaderFlags; // 保留
DWORD NumberOfRvaAndSizes; // 固定 16
IMAGE_DATA_DIRECTORY DataDirectory[16]; // **關鍵: 16個重要資料目錄**
} IMAGE_OPTIONAL_HEADER32;
typedef struct _IMAGE_DATA_DIRECTORY {
DWORD VirtualAddress; // 目錄表 RVA
DWORD Size; // 目錄表大小
} IMAGE_DATA_DIRECTORY;
16 個預定義目錄索引 (部分):
| 索引 | 宏定義 | 用途 |
|---|---|---|
| 0 | IMAGE_DIRECTORY_ENTRY_EXPORT | 導出表 (DLL 導出函式) |
| 1 | IMAGE_DIRECTORY_ENTRY_IMPORT | 導入表 (IAT, 引入函式) |
| 2 | IMAGE_DIRECTORY_ENTRY_RESOURCE | **資源表** (圖標、對話框、字串表...) |
| 5 | IMAGE_DIRECTORY_ENTRY_BASERELOC | 基址重定位表 (ASLR 關鍵) |
| 6 | IMAGE_DIRECTORY_ENTRY_DEBUG | 調試資訊目錄 |
| 9 | IMAGE_DIRECTORY_ENTRY_TLS | 執行緒區域儲存 (TLS) 回呼 |
| 12 | IMAGE_DIRECTORY_ENTRY_IAT | 導入地址表 (綁定後) |
| 14 | IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR | .NET 元資料 (CLI Header) |
typedef struct _IMAGE_SECTION_HEADER {
BYTE Name[8]; // 區段名 ".text", ".data", ".rsrc"...
union { DWORD PhysicalAddress; DWORD VirtualSize; } Misc;
DWORD VirtualAddress; // 區段 RVA (載入後相對 ImageBase 偏移)
DWORD SizeOfRawData; // 文件中區段大小 (FileAlignment 對齊)
DWORD PointerToRawData; // 文件偏移 **關鍵: 定位原始數據**
DWORD PointerToRelocations;
DWORD PointerToLinenumbers;
WORD NumberOfRelocations;
WORD NumberOfLinenumbers;
DWORD Characteristics; // 0x60000020=代碼/可執行/可讀, 0xC0000040=數據/可讀寫...
} IMAGE_SECTION_HEADER;
RVA 轉檔案偏移公式:
FileOffset = PointerToRawData + (RVA - VirtualAddress) (前提:RVA 在該區段範圍內)。
資源區 (.rsrc) 內部組織為三層樹狀結構:**類型 -> 名稱/ID -> 語言 -> 數據**。
typedef struct _IMAGE_RESOURCE_DIRECTORY {
DWORD Characteristics; // 保留 0
DWORD TimeDateStamp; // 資源建立時間
WORD MajorVersion;
WORD MinorVersion;
WORD NumberOfNamedEntries; // 使用字串名稱的項目數
WORD NumberOfIdEntries; // 使用整數 ID 的項目數
// 緊接著: IMAGE_RESOURCE_DIRECTORY_ENTRY DirectoryEntries[];
} IMAGE_RESOURCE_DIRECTORY;
typedef struct _IMAGE_RESOURCE_DIRECTORY_ENTRY {
union {
struct { DWORD NameOffset:31; DWORD NameIsString:1; }; // 高位=1 表示字串名
DWORD Name; // 整數 ID (低 16 位有效) 或字串 RVA
WORD Id; // 別名
};
union {
DWORD OffsetToData; // 高位=0: 指向資源數據項; 高位=1: 指向子目錄
struct { DWORD OffsetToDirectory:31; DWORD DataIsDirectory:1; };
};
} IMAGE_RESOURCE_DIRECTORY_ENTRY;
關鍵位操作:
NameIsString (0x80000000):1=名稱是字串 (RVA 指向 IMAGE_RESOURCE_DIR_STRING_U),0=名稱是整數 ID。DataIsDirectory (0x80000000):1=指向子目錄 (IMAGE_RESOURCE_DIRECTORY),0=指向數據項 (IMAGE_RESOURCE_DATA_ENTRY)。0x7FFFFFFF。
typedef struct _IMAGE_RESOURCE_DIR_STRING_U {
WORD Length; // 字元數 (不含 NULL)
WCHAR NameString[1]; // Unicode 字串 (變長)
} IMAGE_RESOURCE_DIR_STRING_U;
typedef struct _IMAGE_RESOURCE_DATA_ENTRY {
DWORD OffsetToData; // 資源數據 RVA (相對 ImageBase,**非**檔案偏移)
DWORD Size; // 資源數據大小
DWORD CodePage; // 代碼頁 (通常 0 或 1252/65001)
DWORD Reserved; // 0
} IMAGE_RESOURCE_DATA_ENTRY;
| 1: RT_CURSOR | 2: RT_BITMAP | 3: RT_ICON | 4: RT_MENU | 5: RT_DIALOG |
| 6: RT_STRING | 7: RT_FONTDIR | 8: RT_FONT | 9: RT_ACCELERATOR | 10: RT_RCDATA |
| 11: RT_MESSAGETABLE | 12: RT_GROUP_CURSOR | 14: RT_GROUP_ICON | 16: RT_VERSION | 17: RT_DLGINCLUDE |
| 19: RT_PLUGPLAY | 20: RT_VXD | 21: RT_ANICURSOR | 22: RT_ANIICON | 23: RT_HTML |
| 24: RT_MANIFEST |
dumpbin /headers (VC)、PEview、CFF Explorer、LordPE、010 Editor PE Template。winnt.h (原始碼定義)。PE 格式雖然結構繁複,但邏輯清晰:DOS 頭定位 PE 頭 -> 可選頭提供邏輯佈局鍵值 (入口點、基址、對齊、資料目錄) -> 區段表映射物理文件到虛擬記憶體 -> 資料目錄索引關鍵子系統 (導入、導出、資源、重定位、TLS...)。掌握上述核心結構體與 RVA/檔案偏移轉換,即可編寫 PE 解析器、修補器、載入器,進行深度二進位分析與工程開發。