Windows 9x/NT/2000 以上所有可執行檔(.exe、.dll、.sys 等)均遵循 Microsoft 定義的 PE (Portable Executable) 格式。理解 PE 結構是逆向工程、病毒分析、外掛開發、二進位修補、殼開發等進階技術的基石。本文系統梳理 PE 檔案整體架構、關鍵資料結構與修改實戰要點。
+------------------------+ | DOS MZ Header | 偏移 0x00:DOS 可執行檔標誌 "MZ" +------------------------+ | DOS Stub | 實模式下顯示 "This program cannot run in DOS mode" +------------------------+ | PE Header (IMAGE_NT_HEADERS) | 真正的 PE 資訊起點,DOS Header 0x3C 處指向 +------------------------+ | Section Table | 區段表(節表),描述各 Section 屬性與位置 +------------------------+ | Section 1 (.text) | 程式碼區 | Section 2 (.data) | 初始化資料區 | Section 3 (.rsrc) | 資源區 | ... | | Section n | +------------------------+
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;
WORD e_sp;
WORD e_csum;
WORD e_ip;
WORD e_cs;
WORD e_lfarlc;
WORD e_ovno;
WORD e_res[4];
WORD e_oemid;
WORD e_oeminfo;
WORD e_res2[10];
LONG e_lfanew; // 關鍵:PE Header 相對檔案開頭的偏移 (通常 0x80, 0x100 等)
} IMAGE_DOS_HEADER;
typedef struct _IMAGE_NT_HEADERS {
DWORD Signature; // "PE\0\0" (0x00004550)
IMAGE_FILE_HEADER FileHeader; // 實體檔案屬性
IMAGE_OPTIONAL_HEADER32 OptionalHeader; // 邏輯映像屬性(最重要)
} IMAGE_NT_HEADERS32;
typedef struct _IMAGE_FILE_HEADER {
WORD Machine; // 0x014c = Intel 386+
WORD NumberOfSections; // 區段數
DWORD TimeDateStamp; // 建立時間
DWORD PointerToSymbolTable; // 除錯符號表
DWORD NumberOfSymbols;
WORD SizeOfOptionalHeader; // OptionalHeader 大小
WORD Characteristics; // 0x0002=EXE, 0x2000=DLL, 0x0001=無重定位等
} IMAGE_FILE_HEADER;
typedef struct _IMAGE_OPTIONAL_HEADER {
WORD Magic; // 0x10b = PE32, 0x20b = PE32+
BYTE MajorLinkerVersion;
BYTE MinorLinkerVersion;
DWORD SizeOfCode; // 所有代碼區段總大小
DWORD SizeOfInitializedData;
DWORD SizeOfUninitializedData;
DWORD AddressOfEntryPoint; // 關鍵:程式進入點 RVA
DWORD BaseOfCode; // 代碼區段起始 RVA
DWORD BaseOfData; // 資料區段起始 RVA
DWORD ImageBase; // 建議載入基址 (EXE 常 0x00400000)
DWORD SectionAlignment; // 記憶體對齊粒度 (通常 0x1000)
DWORD FileAlignment; // 檔案對齊粒度 (通常 0x200)
WORD MajorOperatingSystemVersion;
WORD MinorOperatingSystemVersion;
WORD MajorImageVersion;
WORD MinorImageVersion;
WORD MajorSubsystemVersion; // 4.0 以上才有 3D 對話框
WORD MinorSubsystemVersion;
DWORD Win32VersionValue; // 保留
DWORD SizeOfImage; // 映像在記憶體總大小 (含所有區段)
DWORD SizeOfHeaders; // 所有 Header + 區段表大小
DWORD CheckSum; // 校驗和
WORD Subsystem; // 2=GUI, 3=CUI, 1=Native
WORD DllCharacteristics;
DWORD SizeOfStackReserve;
DWORD SizeOfStackCommit;
DWORD SizeOfHeapReserve;
DWORD SizeOfHeapCommit;
DWORD LoaderFlags;
DWORD NumberOfRvaAndSizes; // 固定 16
IMAGE_DATA_DIRECTORY DataDirectory[16]; // 關鍵目錄表
} IMAGE_OPTIONAL_HEADER32;
| 索引 | 常數 | 用途 |
|---|---|---|
| 0 | IMAGE_DIRECTORY_ENTRY_EXPORT | 匯出表 |
| 1 | IMAGE_DIRECTORY_ENTRY_IMPORT | 匯入表 (IAT) |
| 2 | IMAGE_DIRECTORY_ENTRY_RESOURCE | 資源表 (.rsrc) |
| 3 | IMAGE_DIRECTORY_ENTRY_EXCEPTION | 例外處理表 |
| 4 | IMAGE_DIRECTORY_ENTRY_SECURITY | 安全性憑證 |
| 5 | IMAGE_DIRECTORY_ENTRY_BASERELOC | 基址重定位表 |
| 6 | IMAGE_DIRECTORY_ENTRY_DEBUG | 除錯資訊 |
| 7 | IMAGE_DIRECTORY_ENTRY_ARCHITECTURE | 架構專用 |
| 8 | IMAGE_DIRECTORY_ENTRY_GLOBALPTR | 全域指標 |
| 9 | IMAGE_DIRECTORY_ENTRY_TLS | 執行緒區域儲存 |
| 10 | IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG | 載入配置 |
| 11 | IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT | 綁定匯入表 |
| 12 | IMAGE_DIRECTORY_ENTRY_IAT | 匯入地址表 |
| 13 | IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT | 延遲匯入表 |
| 14 | IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR | .NET 元資料 (CLR Header) |
| 15 | 保留 | - |
typedef struct _IMAGE_SECTION_HEADER {
BYTE Name[8]; // 區段名 (如 ".text", ".data", ".rsrc")
DWORD VirtualSize; // 區段在記憶體大小
DWORD VirtualAddress; // 區段 RVA (相對 ImageBase)
DWORD SizeOfRawData; // 區段在檔案大小 (對齊 FileAlignment)
DWORD PointerToRawData; // 區段在檔案偏移
DWORD PointerToRelocations;
DWORD PointerToLinenumbers;
WORD NumberOfRelocations;
WORD NumberOfLinenumbers;
DWORD Characteristics; // 0x60000020 = 代碼/可讀/可執行 等
} IMAGE_SECTION_HEADER;
RVA (Relative Virtual Address):相對映像基址的虛擬位址(載入記憶體後)。
FOA (File Offset Address):檔案中的實際位移。
// RVA -> FOA
DWORD RvaToFoa(DWORD rva, PIMAGE_SECTION_HEADER sections, int count)
{
for (int i = 0; i < count; i++)
{
DWORD start = sections[i].VirtualAddress;
DWORD end = start + sections[i].VirtualSize;
if (rva >= start && rva < end)
{
return sections[i].PointerToRawData + (rva - start);
}
}
return 0; // 不在任何區段
}
// FOA -> RVA
DWORD FoaToRva(DWORD foa, PIMAGE_SECTION_HEADER sections, int count)
{
for (int i = 0; i < count; i++)
{
DWORD start = sections[i].PointerToRawData;
DWORD end = start + sections[i].SizeOfRawData;
if (foa >= start && foa < end)
{
return sections[i].VirtualAddress + (foa - start);
}
}
return 0;
}
| 場景 | 修改目標 | 關鍵步驟 |
|---|---|---|
| 修改程式進入點 | OptionalHeader.AddressOfEntryPoint | 設為新代碼區段 RVA,注意堆疊平衡 |
| 新增區段 (加殼/外掛) | Section Table 新增 IMAGE_SECTION_HEADER | 調整 NumberOfSections、SizeOfImage、對齊計算、寫入資料 |
| 修改匯入表 (IAT Hook) | DataDirectory[1] 匯入表 | 定位目標 DLL/函式,修改 IAT 項指向自訂函式 |
| 資源替換/新增 | .rsrc 區段 + Resource Directory Tree | 解析資源目錄樹,插入/替換葉節點,調整 SizeOfRawData |
| 去除殼/解壓 | Entry Point、區段屬性、重定位表 | 找到 OEP,修復 IAT、重定位、區段屬性 (MEM_WRITE→MEM_READ) |
| 修改 Subsystem | OptionalHeader.Subsystem | 2(GUI)↔3(CUI) 切換控制台視窗顯示 |
PE 修改技術屬雙面刃:
請嚴格遵守當地法律法規,僅在授權範圍內進行二進位分析與修改。
掌握 PE 格式細節,即擁有了「透視 Windows 可執行檔本質」的 X 光眼,是系統級開發與安全研究的核心內功。