PE(Portable Executable)格式是 Windows 系統中用於執行檔(.exe)、動態連結庫(.dll)及系統驅動(.sys)的標準檔案格式。它的結構設計是為了讓作業系統的載入器(Loader)能有效地將檔案映射到記憶體中執行。
以下是 PE 檔案的主要結構組成:1. MS-DOS 標頭(MS-DOS Header)
每個 PE 檔案都以一個 64 位元組的 DOS MZ 標頭 開始。這是為了相容性而設計的。
- 關鍵欄位:
e_magic必須是MZ(0x5A4D)。 - 關鍵欄位:
e_lfanew位在標頭末尾,指向 PE 標頭 的起始偏移位置。
2. DOS 存根(DOS Stub)
這是一段微小的程式,當檔案在純 DOS 環境下執行時,會顯示「This program cannot be run in DOS mode」並退出。
3. PE 標頭 (NT Headers)
這是 PE 檔案的核心,包含三個部分:
- Signature:4 位元組,值為
PE\0\0(0x50450000)。 - 影像檔案標頭 (File Header):包含檔案的基本實體屬性,例如:
- Machine:目標架構(如 x64, i386)。
- NumberOfSections:區段(Sections)的數量。
- TimeDateStamp:檔案建立的時間戳。
- 選擇性標頭 (Optional Header):雖然叫「選擇性」,但對於執行檔來說是必備的。它定義了檔案在記憶體中的佈局:
- AddressOfEntryPoint:程式執行的起點(OEP)。
- ImageBase:建議的記憶體載入地址。
- SectionAlignment:區段在記憶體中的對齊單位(通常是 4KB)。
- DataDirectory:包含匯入表(Import Table)、匯出表(Export Table)、資源表等重要結構的索引。
4. 區段表 (Section Table / Section Headers)
這是一個陣列,描述了緊跟在其後的各個 區段(Sections)。每個表項包含:
- Name:區段名稱(如
.text,.data)。 - VirtualSize / VirtualAddress:載入到記憶體後的長度與地址。
- SizeOfRawData / PointerToRawData:在硬碟檔案中的長度與地址。
- Characteristics:權限設定(唯讀、可讀寫、可執行)。
5. 區段數據 (Sections)
這是檔案的實際內容,常見的區段包括:
.text:存放可執行的機器碼(程式邏輯)。.data:存放已初始化的全域變數和靜態變數。.rdata:存放唯讀數據(如常數、字串字面量)。.idata:匯入表,列出程式運行所需的 DLL 及其函數。.edata:匯出表,列出此檔案提供的函數(常見於 DLL)。.rsrc:資源文件,如圖標、選單、位圖。.reloc:重定位資訊,用於當檔案無法載入到首選ImageBase時進行地址修正。
如果你想親自觀察這些結構,推薦使用以下工具:
- PE-bear 介面非常直覺,適合視覺化查看各個標頭的內容。


總結結構視圖
結構層次 說明
DOS Header 識別標記 MZ 與 PE 標頭偏移量
PE Header (NT) 包含 CPU 架構、入口點、記憶體佈局參數
Section Table 各區段的屬性清單(地圖)
Sections (.text) 程式碼區
Sections (.data) 數據區
Sections (...) 匯入/匯出、資源、重定位等
理解 PE 格式對於逆向工程、惡意軟體分析、驅動開發以及底層系統優化至關重要。
既然對 PE 格式的結構有了初步概念,我們進一步深入看它在實務開發或分析中最重要的兩個核心:記憶體對齊(Alignment)與數據目錄(Data Directory)。
這兩個概念決定了程式如何從硬碟上的「死檔案」變成記憶體中「活的進程」。
6. 檔案與記憶體的映射 (Alignment)
PE 格式中有兩個非常重要的「對齊」參數,這常讓初學者感到困惑:
- File Alignment(檔案對齊): 在硬碟上,為了磁碟讀取效率,區段通常以 512 位元組(一個磁區)為單位對齊。
- Section Alignment(記憶體對齊): 當載入到記憶體後,為了配合 CPU 的分頁機制,通常以 4KB (4096 bytes) 為單位對齊。
這意味著同一個區段,在記憶體中的地址與在硬碟中的地址通常是不一樣的,這就是為什麼我們需要計算 RVA(相對虛擬地址)。
7. 數據目錄 (Data Directory):功能的核心
在 Optional Header 的末尾有一個陣列,稱為 DataDirectory。它就像是檔案的「功能清單」,包含了 16 個槽位,指向檔案中最重要的結構:
- Export Table (匯出表): 告訴系統這個檔案提供了哪些函數供別人使用(DLL 必備)。
- Import Table (匯出表): 記錄了程式啟動時需要從哪些 DLL 載入哪些函數。
- Resource Table (資源表): 存放圖標、對話框字串、甚至嵌入的檔案。
- Base Relocation Table (重定位表): 如果作業系統無法將檔案載入到預期的
ImageBase地址,就會根據這張表修正程式碼中的硬編碼地址。
8. 如何在程式中存取這些結構?
如果你正在開發一個工具(例如分析器或注入器),在 C/C++ 中你可以直接使用 winnt.h 定義的結構體:
C++
PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)buffer;
if (pDosHeader->e_magic == IMAGE_DOS_SIGNATURE) {
// 透過 e_lfanew 找到 NT Headers
PIMAGE_NT_HEADERS pNtHeaders = (PIMAGE_NT_HEADERS)((BYTE*)buffer + pDosHeader->e_lfanew);
if (pNtHeaders->Signature == IMAGE_NT_SIGNATURE) {
// 取得機器架構 (x64/x86)
WORD machine = pNtHeaders->FileHeader.Machine;
// 取得進入點地址
DWORD entryPoint = pNtHeaders->OptionalHeader.AddressOfEntryPoint;
}
}





















