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

PE 資源區深度研究:資源目錄樹結構、目錄項編碼與資料項定位完整指南

     編輯:關於C++

PE 文件的資源區 (.rsrc 區段) 採用三層樹狀結構組織:**類型 -> 名稱/ID -> 語言 -> 數據**。本文基於 Microsoft PE/COFF 規格與實戰逆向經驗,詳細解析 IMAGE_RESOURCE_DIRECTORY、IMAGE_RESOURCE_DIRECTORY_ENTRY、IMAGE_RESOURCE_DIR_STRING_U、IMAGE_RESOURCE_DATA_ENTRY 四大核心結構,重點闡述「高位標誌位」編碼邏輯、Unicode 字串存儲、RVA 定位數據的完整路徑,並提供 Delphi/C++ 對應聲明與遍歷偽代碼。

資源區整體佈局


.rsrc 區段 (PointerToRawData 開始)
+--------------------------+
| 根資源目錄               |  (IMAGE_RESOURCE_DIRECTORY)
|   NumberOfNamedEntries   |
|   NumberOfIdEntries      |
+--------------------------+
| 目錄項陣列               |  (IMAGE_RESOURCE_DIRECTORY_ENTRY[]) 排序: 名稱優先、升序
|   [0] Type: RT_ICON (3)  |  -> 指向 子目錄 (Type 節點)
|   [1] Type: RT_BITMAP(2) |  -> 指向 子目錄
|   [2] Name: "MY_RES"     |  -> 指向 子目錄 (Name 節點, NameIsString=1)
+--------------------------+
| 子目錄: Type=RT_ICON     |  (IMAGE_RESOURCE_DIRECTORY)
|   ...                    |  項目: ID=101 (圖標組 ID) -> 指向語言子目錄
+--------------------------+
| 子目錄: Language=0x0409  |  (IMAGE_RESOURCE_DIRECTORY)
|   項目: DataIsDirectory=0|  -> 指向 資源數據項
+--------------------------+
| 資源數據項               |  (IMAGE_RESOURCE_DATA_ENTRY)
|   OffsetToData (RVA)     |  -> 實際資源數據 (圖標位圖數據...)
|   Size                   |
+--------------------------+
| 實際資源數據區塊         |  (原始位元組)
+--------------------------+

核心結構體詳細解析

1. 資源目錄頭 (IMAGE_RESOURCE_DIRECTORY) - 16 位元組


typedef struct _IMAGE_RESOURCE_DIRECTORY {
    DWORD Characteristics;        // 保留,通常 0
    DWORD TimeDateStamp;          // 資源編譯時間戳
    WORD  MajorVersion;           // 版本號
    WORD  MinorVersion;
    WORD  NumberOfNamedEntries;   // 使用字串名稱的項目數 (NameIsString=1)
    WORD  NumberOfIdEntries;      // 使用整數 ID 的項目數 (NameIsString=0)
    // 緊接著: IMAGE_RESOURCE_DIRECTORY_ENTRY DirectoryEntries[];
} IMAGE_RESOURCE_DIRECTORY;
  • 項目陣列按以下順序排列:**先所有 Named (按 Unicode 字串升序),後所有 ID (按數值升序)**。
  • 總項目數 = NumberOfNamedEntries + NumberOfIdEntries。

2. 目錄項目 (IMAGE_RESOURCE_DIRECTORY_ENTRY) - 8 位元組


typedef struct _IMAGE_RESOURCE_DIRECTORY_ENTRY {
    union {
        struct { DWORD NameOffset:31; DWORD NameIsString:1; }; // 位域
        DWORD Name;          // 整體視圖
        WORD  Id;            // 別名 (低 16 位)
    };
    union {
        DWORD OffsetToData;  // 整體視圖
        struct { DWORD OffsetToDirectory:31; DWORD DataIsDirectory:1; };
    };
} IMAGE_RESOURCE_DIRECTORY_ENTRY;

關鍵位域邏輯 (核心難點)

字段位置含義處理方式
NameIsString最高位 (0x80000000)1 = 名稱是字串 (RVA 指向 IMAGE_RESOURCE_DIR_STRING_U)
0 = 名稱是整數 ID (低 16/32 位)
if (entry.Name & 0x80000000) { // 字串 } else { // ID }
DataIsDirectory最高位 (0x80000000)1 = 指向子目錄 (IMAGE_RESOURCE_DIRECTORY)
0 = 指向資源數據項 (IMAGE_RESOURCE_DATA_ENTRY)
if (entry.OffsetToData & 0x80000000) { // 子目錄 } else { // 數據 }
NameOffset / OffsetToDirectory低 31 位 (0x7FFFFFFF)相對於**資源目錄區塊起始**的偏移量 (非 RVA,非檔案偏移)DWORD offset = entry.OffsetToData & 0x7FFFFFFF;

3. 資源目錄字串 (IMAGE_RESOURCE_DIR_STRING_U) - 變長


typedef struct _IMAGE_RESOURCE_DIR_STRING_U {
    WORD  Length;           // 字元數 (不含結尾 NULL)
    WCHAR NameString[1];    // Unicode 字串內容 (變長)
} IMAGE_RESOURCE_DIR_STRING_U;
  • 儲存於資源區塊內部,由 NameOffset 定位。
  • 對齊:通常 2 位元組對齊。

4. 資源數據項 (IMAGE_RESOURCE_DATA_ENTRY) - 16 位元組


typedef struct _IMAGE_RESOURCE_DATA_ENTRY {
    DWORD OffsetToData; // **RVA** (相對 ImageBase),指向實際資源數據
    DWORD Size;         // 數據大小 (位元組)
    DWORD CodePage;     // 代碼頁 (0=默認, 1200=Unicode, 65001=UTF-8)
    DWORD Reserved;     // 0
} IMAGE_RESOURCE_DATA_ENTRY;
  • 關鍵區別:目錄項的 OffsetToDirectory 是**相對資源目錄起始的偏移**;數據項的 OffsetToData 是**相對 ImageBase 的 RVA**。
  • 定位實際數據檔案偏移:FileOffset = RVAToFileOffset(OffsetToData) (需區段表協助)。

Delphi / C++ Builder 對應聲明


// Delphi 風格
type
  PIMAGE_RESOURCE_DIRECTORY = ^IMAGE_RESOURCE_DIRECTORY;
  IMAGE_RESOURCE_DIRECTORY = packed record
    Characteristics: DWORD;
    TimeDateStamp: DWORD;
    MajorVersion: WORD;
    MinorVersion: WORD;
    NumberOfNamedEntries: WORD;
    NumberOfIdEntries: WORD;
  end;

  PIMAGE_RESOURCE_DIRECTORY_ENTRY = ^IMAGE_RESOURCE_DIRECTORY_ENTRY;
  IMAGE_RESOURCE_DIRECTORY_ENTRY = packed record
    case Integer of
      0: (Name: DWORD); // 或 NameOffset/NameIsString
      1: (Id: WORD);
    case Integer of
      0: (OffsetToData: DWORD);
      1: (OffsetToDirectory: DWORD); // 或 DataIsDirectory
  end;

  PIMAGE_RESOURCE_DIR_STRING_U = ^IMAGE_RESOURCE_DIR_STRING_U;
  IMAGE_RESOURCE_DIR_STRING_U = packed record
    Length: WORD;
    NameString: array[0..0] of WideChar;
  end;

  PIMAGE_RESOURCE_DATA_ENTRY = ^IMAGE_RESOURCE_DATA_ENTRY;
  IMAGE_RESOURCE_DATA_ENTRY = packed record
    OffsetToData: DWORD;
    Size: DWORD;
    CodePage: DWORD;
    Reserved: DWORD;
  end;

遍歷資源樹偽代碼 (核心邏輯)


function EnumResources(ModuleBase: Pointer; ResDirRVA: DWORD; Level: Integer = 0):
var
  ResDir: PIMAGE_RESOURCE_DIRECTORY;
  Entry: PIMAGE_RESOURCE_DIRECTORY_ENTRY;
  i: Integer;
  NameStr: string;
  DataEntry: PIMAGE_RESOURCE_DATA_ENTRY;
  DataRVA, DataSize: DWORD;
  DataPtr: Pointer;
begin
  // 1. 定位當前目錄 (RVA -> VA)
  ResDir := PIMAGE_RESOURCE_DIRECTORY(DWORD(ModuleBase) + ResDirRVA);
  Entry := PIMAGE_RESOURCE_DIRECTORY_ENTRY(ResDir + 1); // 緊接頭部之後

  // 2. 遍歷所有項目
  for i := 0 to ResDir.NumberOfNamedEntries + ResDir.NumberOfIdEntries - 1 do
  begin
    // 3. 解析名稱/ID
    if (Entry[i].Name and $80000000) <> 0 then begin // 字串名
      NameStr := GetResourceString(ModuleBase, Entry[i].Name and $7FFFFFFF);
    end else begin // 整數 ID
      NameStr := Format('#%d', [Entry[i].Id]);
    end;

    // 4. 判斷子目錄還是數據
    if (Entry[i].OffsetToData and $80000000) <> 0 then begin // 子目錄
      EnumResources(ModuleBase, 
        Entry[i].OffsetToData and $7FFFFFFF, // 相對資源目錄起始偏移
        Level + 1);
    end else begin // 葉子節點: 數據項
      DataEntry := PIMAGE_RESOURCE_DATA_ENTRY(
        DWORD(ModuleBase) + (Entry[i].OffsetToData and $7FFFFFFF) // 注意: 這裡 OffsetToData 實為數據項 RVA? 
        // 修正: 目錄項 OffsetToData 低31位指向 IMAGE_RESOURCE_DATA_ENTRY 結構
        // 該結構位於資源區塊內,其 RVA = 資源目錄起始 RVA + OffsetToData
      );
      DataRVA := DataEntry.OffsetToData; // 實際數據 RVA
      DataSize := DataEntry.Size;
      DataPtr := Pointer(DWORD(ModuleBase) + RVAToFileOffset(DataRVA)); // 實際指標
      // 此處處理資源數據: 圖標、位圖、版本資訊、自訂數據...
      ProcessResourceData(NameStr, DataPtr, DataSize, DataEntry.CodePage);
    end;
  end;
end;

常見陷阱與修正

  1. RVA 基準混淆:
    • PE OptionalHeader.DataDirectory[2].VirtualAddress = **資源目錄根節點 RVA** (相對 ImageBase)。
    • 目錄項 OffsetToDirectory (低 31 位) = **相對資源目錄根節點起始的偏移** (非 RVA)。
    • 數據項 OffsetToData = **實際資源數據 RVA** (相對 ImageBase)。
  2. 字串名稱查找:NameOffset 指向 IMAGE_RESOURCE_DIR_STRING_U,需讀取 Length 後複製 Length 個 WCHAR。
  3. 排序規則:Named 先於 ID,Named 按 Unicode 字串比較升序,ID 按數值升序。二分查找需遵守此序。
  4. 語言層級:第 3 層通常為語言 ID (如 0x0409=英文, 0x0804=中文簡體)。LANGIDFROMLCID 宏可提取。

總結

PE 資源目錄樹雖結構精巧,但「雙高位標誌位 + 兩種不同基準偏移」極易導致解析錯誤。牢記:

  • NameIsString 決定名稱解析方式。
  • DataIsDirectory 決定遞歸還是讀數據。
  • 目錄項偏移 **相對資源目錄起始**;數據項偏移 **相對 ImageBase (RVA)**。
  • 字串為 Unicode、長度前綴、變長結構。

掌握上述核心邏輯,即可編寫健壯的 PE 資源解析器、圖標提取器、版本資訊讀取器或資源修改工具。

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