首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

PE Loader 核心實現:將映像映射到內存的關鍵步驟解析

     編輯:關於C語言

前言

在 Windows 系統程式設計或逆向工程領域,PE (Portable Executable) 載入器的核心任務是將磁盤上的可執行文件正確映射到進程的虛擬地址空間中。這篇文章深入剖析一個精簡的 PE 映射實現 LdrLayoutPe,展示從文件校驗、頭部解析、內存分配到節區拷貝的完整流程。

核心流程概覽

載入器的工作可分為四個關鍵階段:

  1. 文件合法性校驗:確保輸入緩衝區包含有效的 PE 結構。
  2. 關鍵結構體定位:解析 DOS Header、NT Headers、File Header、Optional Header 以及 Section Table。
  3. 虛擬內存分配:嘗試在首選基址預留並提交內存,失敗則由系統自動選址。
  4. 映像構建:按文件對齊與內存對齊規則,將頭部與各節區內容複製到目標內存區域。

代碼深度解析

/* 將文件內容按虛擬地址信息映射到地址空間,返回模塊的基地址 */
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;
}

關鍵技術點詳解

1. 基址衝突與重定位的前奏

代碼中先嘗試 VirtualAlloc(pImageBase, ...),若失敗則退回 VirtualAlloc(NULL, ...)。這模擬了 Windows 載入器的行為:優先按 ImageBase 裝載,若地址被佔用則觸發基址重定位。本示例雖未實現重定位修正表的應用,但內存分配策略已為後續重定位鋪平道路。

2. 節區映射的對齊邏輯

  • 文件對齊:PointerToRawData 與 SizeOfRawData 遵循 FileAlignment (通常 0x200)。
  • 內存對齊:VirtualAddress 與 VirtualSize 遵循 SectionAlignment (通常 0x1000)。
  • 拷貝時以 VirtualAddress 為目標偏移,PointerToRawData 為源偏移,長度取 SizeOfRawData,完美體現「文件到內存」的對齊轉換。

3. 頭部拷貝大小的精確計算

(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。
  • 異常處理:引入 SEH 或返回碼機制,細化分配失敗、校驗失敗、拷貝越界等錯誤碼。
  • 64 位兼容:通过 Magic 字段 (0x10b=PE32, 0x20b=PE32+) 動態計算 OptionalHeader 大小與數據目錄表偏移,實現雙架構支持。

總結

LdrLayoutPe 雖精簡,卻完整呈現了 PE 靜態映射的骨架:校驗 → 解析 → 分配 → 拷貝。理解這一流程是編寫 Shellcode Loader、用戶態調試器、沙箱注入模組的基石。下一步自然是引入重定位表處理、導入表綁定 (IAT Fix)、TLS 回調執行,構建功能完備的用戶態載入器。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved