在 Windows 系統程式設計或逆向工程領域,PE (Portable Executable) 載入器的核心任務是將磁盤上的可執行文件正確映射到進程的虛擬地址空間中。這篇文章深入剖析一個精簡的 PE 映射實現 LdrLayoutPe,展示從文件校驗、頭部解析、內存分配到節區拷貝的完整流程。
載入器的工作可分為四個關鍵階段:
/* 將文件內容按虛擬地址信息映射到地址空間,返回模塊的基地址 */
char* LdrLayoutPe(char* pImage, DWORD FileSize)
{
PIMAGE_FILE_HEADER pFileHeader;
PIMAGE_SECTION_HEADER pSectionHeader;
char* pOptionalHeader;
char* pImageBase;
char* pRealImageBase;
DWORD SizeOfImage;
WORD Magic;
DWORD i;
// 1. 文件校驗
if(!LdrValidateImage(pImage, FileSize))
return NULL;
// 2. 定位關鍵頭部結構
pFileHeader = _GetFileHeaderFromPe(pImage);
pSectionHeader = _GetSectionHeaderFromPe(pFileHeader);
pOptionalHeader = (char*)pFileHeader + sizeof(IMAGE_FILE_HEADER);
Magic = *(WORD*)pOptionalHeader; // 區分 PE32 / PE32+
pImageBase = _GetImageBaseFromPe(pFileHeader); // 首選裝載基址
SizeOfImage = *(DWORD*)(pOptionalHeader + 0x38); // OptionalHeader 中的 SizeOfImage 字段偏移
// 3. 內存分配:優先嘗試首選基址
pRealImageBase = (char*)VirtualAlloc(pImageBase, SizeOfImage, MEM_RESERVE|MEM_COMMIT, PAGE_READWRITE);
if(!pRealImageBase){
// 首選基址被佔用,讓系統自動尋找可用區域
pRealImageBase = (char*)VirtualAlloc(NULL, SizeOfImage, MEM_RESERVE|MEM_COMMIT, PAGE_READWRITE);
if(!pRealImageBase)
return FALSE;
}
// 4. 拷貝文件頭 (DOS Header + NT Headers + Section Table)
RtlCopyMemory(pRealImageBase, pImage,
(char*)pSectionHeader - pImage + pFileHeader->NumberOfSections*sizeof(IMAGE_SECTION_HEADER));
// 5. 逐節區拷貝數據
for(i = 0; i < pFileHeader->NumberOfSections; i++){
if(!pSectionHeader[i].PointerToRawData)
continue; // 僅佔用虛擬空間的節區 (如 .bss) 無需拷貝文件數據
RtlCopyMemory(pRealImageBase + pSectionHeader[i].VirtualAddress,
pImage + pSectionHeader[i].PointerToRawData,
pSectionHeader[i].SizeOfRawData);
}
return pRealImageBase;
}
代碼中先嘗試 VirtualAlloc(pImageBase, ...),若失敗則退回 VirtualAlloc(NULL, ...)。這模擬了 Windows 載入器的行為:優先按 ImageBase 裝載,若地址被佔用則觸發基址重定位。本示例雖未實現重定位修正表的應用,但內存分配策略已為後續重定位鋪平道路。
PointerToRawData 與 SizeOfRawData 遵循 FileAlignment (通常 0x200)。VirtualAddress 與 VirtualSize 遵循 SectionAlignment (通常 0x1000)。VirtualAddress 為目標偏移,PointerToRawData 為源偏移,長度取 SizeOfRawData,完美體現「文件到內存」的對齊轉換。(char*)pSectionHeader - pImage + NumberOfSections*sizeof(IMAGE_SECTION_HEADER) 精確涵蓋:DOS Stub、PE 簽名、File Header、Optional Header (含數據目錄表)、完整的 Section Table。這確保載入後的內存映像在結構上與文件頭部完全一致,便於後續解析導入表、導出表等目錄項。
Characteristics) 設置 PAGE_EXECUTE_READ、PAGE_READWRITE 等差異化權限,而非統一 PAGE_READWRITE。Magic 字段 (0x10b=PE32, 0x20b=PE32+) 動態計算 OptionalHeader 大小與數據目錄表偏移,實現雙架構支持。LdrLayoutPe 雖精簡,卻完整呈現了 PE 靜態映射的骨架:校驗 → 解析 → 分配 → 拷貝。理解這一流程是編寫 Shellcode Loader、用戶態調試器、沙箱注入模組的基石。下一步自然是引入重定位表處理、導入表綁定 (IAT Fix)、TLS 回調執行,構建功能完備的用戶態載入器。