PE 檔案 = DOS 相容區塊 + PE 標誌/頭部 + 區段表 + 區段資料。本文以系列教程 4.exe (主控台輸出範例) 為實例,結合 UltraEdit 十六進位檢視與 W32Dasm 反組譯,層層剖析。
IMAGE_DOS_HEADER + DOS Stub 程式碼 (顯示 "This program cannot be run in DOS mode.")。CODE / .text)。IMAGE_DOS_HEADER STRUCT
e_magic WORD ? ; "MZ" (0x5A4D)
e_cblp WORD ?
...
e_lfanew DWORD ? ; **關鍵:PE Header 檔案偏移量**
IMAGE_DOS_HEADER ENDS
本例 e_lfanew = 0xB0 (Offset 0x3C 處),指向 PE Header 起始位置。
IMAGE_NT_HEADERS STRUCT
Signature DWORD ? ; "PE\0\0" (0x00004550)
FileHeader IMAGE_FILE_HEADER <>
OptionalHeader IMAGE_OPTIONAL_HEADER32 <>
IMAGE_NT_HEADERS ENDS
50 45 00 00 ("PE\0\0")。AddressOfEntryPoint=0x1000 (RVA, 程式入口點在 .text 區段內),ImageBase=0x00400000 (建議載入基址),SectionAlignment=0x1000 (記憶體對齊), FileAlignment=0x200 (檔案對齊),Subsystem=3 (IMAGE_SUBSYSTEM_WINDOWS_CUI 主控台),DataDirectory[16]:第 1 項 Export Table (0), 第 2 項 Import Table RVA=0x2000, Size=0x?? (指向 .rdata)。IMAGE_SECTION_HEADER STRUCT
Name BYTE 8 dup(?) ; ".text", ".rdata", ".data"
VirtualSize DWORD ?
VirtualAddress DWORD ? ; RVA (相對虛擬位址)
SizeOfRawData DWORD ? ; 檔案中大小 (FileAlignment 對齊)
PointerToRawData DWORD ? ; 檔案偏移
Characteristics DWORD ? ; 屬性旗標
IMAGE_SECTION_HEADER ENDS
本例三區段 (W32Dasm 驗證):
| 區段 | Name | RVA | File Offset | Raw Size | Flags (特徵) |
|---|---|---|---|---|---|
| 1 | .text | 0x1000 | 0x400 | 0x200 | 0x60000020 (CODE | EXECUTE | READ) |
| 2 | .rdata | 0x2000 | 0x600 | 0x200 | 0x40000040 (INITIALIZED_DATA | READ) |
| 3 | .data | 0x3000 | 0x800 | 0x200 | 0xC0000040 (INITIALIZED_DATA | READ | WRITE) |
W32Dasm 反組譯入口點 00401000 (ImageBase 0x400000 + RVA 0x1000):
:00401000 6800304000 push 00403000 ; 字串 "How are you !" 位址 (RVA 0x3000 -> VA 0x403000)
:00401005 E80E000000 call 00401018 ; 內部函式 (lstrlen 邏輯)
:0040100A 6A00 push 00000000
:0040100C E801000000 call 00401012 ; -> ExitProcess Thunk
:00401011 CC int 3
:00401012 FF2508204000 jmp dword ptr [00402008] ; **IAT Thunk: ExitProcess**
:00401018 55 push ebp ; lstrlen 開始
...
:00401088 FF2500204000 jmp dword ptr [00402000] ; **IAT Thunk: GetStdHandle**
:0040108E FF2504204000 jmp dword ptr [00402004] ; **IAT Thunk: WriteFile**
關鍵模式:所有外部 API 呼叫均為 CALL Near Thunk -> JMP [IAT_Entry]。載入器只需修補 IAT 表 (0x402000 區域),無需修改程式碼區段 (實現 Code Sharing / ASLR 友善)。
包含三個關鍵結構陣列:
00402000: GetStdHandle00402004: WriteFile00402008: ExitProcessOriginalFirstThunk (INT RVA) -> 指向 Hint/Name 表。FirstThunk (IAT RVA) -> 指向 IAT 表 (同上 0x2000)。Name RVA -> 指向 "KERNEL32.dll" 字串。IMAGE_IMPORT_BY_NAME 結構的 RVA。IMAGE_IMPORT_BY_NAME:Hint (WORD) + Name (ASCIIZ)。Hint=0x13D, Name="GetStdHandle", Hint=0x2B9, Name="WriteFile", Hint=0x75, Name="ExitProcess"。Offset 0x800 (VA 0x403000): "How are you !", 0
IMAGE_DOS_HEADER STRUCT ... e_lfanew DWORD ? ENDS
IMAGE_NT_HEADERS STRUCT Signature DWORD ?, FileHeader IMAGE_FILE_HEADER <>, OptionalHeader IMAGE_OPTIONAL_HEADER32 <> ENDS
IMAGE_FILE_HEADER STRUCT Machine WORD ?, NumberOfSections WORD ?, TimeDateStamp DWORD ?, PointerToSymbolTable DWORD ?, NumberOfSymbols DWORD ?, SizeOfOptionalHeader WORD ?, Characteristics WORD ? ENDS
IMAGE_OPTIONAL_HEADER32 STRUCT ... ImageBase DWORD ?, SectionAlignment DWORD ?, FileAlignment DWORD ?, Subsystem WORD ?, DllCharacteristics WORD ?, SizeOfStackReserve DWORD ?, SizeOfStackCommit DWORD ?, SizeOfHeapReserve DWORD ?, SizeOfHeapCommit DWORD ?, LoaderFlags DWORD ?, NumberOfRvaAndSizes DWORD ?, DataDirectory IMAGE_DATA_DIRECTORY 16 dup(<>) ENDS
IMAGE_DATA_DIRECTORY STRUCT VirtualAddress DWORD ?, Size DWORD ? ENDS
IMAGE_SECTION_HEADER STRUCT Name BYTE 8 dup(?), VirtualSize DWORD ?, VirtualAddress DWORD ?, SizeOfRawData DWORD ?, PointerToRawData DWORD ?, PointerToRelocations DWORD ?, PointerToLinenumbers DWORD ?, NumberOfRelocations WORD ?, NumberOfLinenumbers WORD ?, Characteristics DWORD ? ENDS
IMAGE_IMPORT_DESCRIPTOR STRUCT OriginalFirstThunk DWORD ?, TimeDateStamp DWORD ?, ForwarderChain DWORD ?, Name DWORD ?, FirstThunk DWORD ? ENDS
IMAGE_IMPORT_BY_NAME STRUCT Hint WORD ?, Name BYTE ? ENDS
IMAGE_THUNK_DATA32 STRUCT union ForwarderString DWORD ?, Function DWORD ?, Ordinal DWORD ?, AddressOfData DWORD ? ends ENDS
DataDirectory[1] (Import Table RVA) 定位 Import Directory 陣列。IMAGE_IMPORT_DESCRIPTOR:LoadLibrary(Name) 載入 DLL。OriginalFirstThunk (INT) 指向的 Hint/Name 陣列。GetProcAddress(hDll, Name) (或按 Ordinal)。FirstThunk (IAT) 對應項目。CALL Thunk -> JMP [IAT] 即可正確跳轉。e_lfanew、區段表、IAT 實際內容。理解 PE 結構是:
本文基於 Win9x/早期 Win32 時代教程 (4.exe 為主控台程式,ImageBase 0x400000,無 ASLR/DEP/SEH/Guard CF)。現代 PE (Win10/11) 引入 ASLR (動態基址)、DEP (NX)、CFG (Control Flow Guard)、簽名強制、最小化 IAT 等防護,分析思路同但細節更複雜。