PE 檔案中的資源區(.rsrc)採用三層樹狀結構組織:**類型 → 名稱/ID → 語言**,每層皆為 IMAGE_RESOURCE_DIRECTORY 節點,葉節點指向實際資料 IMAGE_RESOURCE_DATA_ENTRY。本文詳細解析資源目錄佈局、關鍵結構體、遍歷算法與手動修改資源實戰技巧。
.rsrc 區段 +--------------------------+ | Resource Directory (根) | IMAGE_RESOURCE_DIRECTORY +--------------------------+ | Type Directory Entries | IMAGE_RESOURCE_DIRECTORY_ENTRY[] (類型層) +--------------------------+ | Name/ID Directory | IMAGE_RESOURCE_DIRECTORY (名稱/ID層) +--------------------------+ | Language Directory | IMAGE_RESOURCE_DIRECTORY (語言層) +--------------------------+ | Resource Data Entries | IMAGE_RESOURCE_DATA_ENTRY[] (葉節點) +--------------------------+ | Raw Resource Data | 位元組資料 (圖示、位圖、字串表、版本資訊等) +--------------------------+
typedef struct _IMAGE_RESOURCE_DIRECTORY {
DWORD Characteristics; // 保留,通常 0
DWORD TimeDateStamp; // 建立時間
WORD MajorVersion;
WORD MinorVersion;
WORD NumberOfNamedEntries; // 以名稱索引的項目數
WORD NumberOfIdEntries; // 以 ID 索引的項目數
// 緊接著 IMAGE_RESOURCE_DIRECTORY_ENTRY[NumberOfNamedEntries + NumberOfIdEntries]
} IMAGE_RESOURCE_DIRECTORY;
typedef struct _IMAGE_RESOURCE_DIRECTORY_ENTRY {
union {
DWORD NameOffset; // 高位 0:字串名稱 (指向 IMAGE_RESOURCE_DIR_STRING_U)
DWORD Id; // 高位 1:整數 ID
};
union {
DWORD OffsetToData; // 高位 0:指向資料項 (IMAGE_RESOURCE_DATA_ENTRY)
DWORD OffsetToDirectory; // 高位 1:指向下一層目錄 (IMAGE_RESOURCE_DIRECTORY)
};
} IMAGE_RESOURCE_DIRECTORY_ENTRY;
關鍵位操作:
NameOffset & 0x80000000 → 1=ID, 0=字串名稱OffsetToData & 0x80000000 → 1=子目錄, 0=資料項
typedef struct _IMAGE_RESOURCE_DATA_ENTRY {
DWORD OffsetToData; // 資料相對 .rsrc 區段起始的 RVA
DWORD Size; // 資料大小
DWORD CodePage; // 代碼頁 (0=Unicode/CP_ACP)
DWORD Reserved; // 保留
} IMAGE_RESOURCE_DATA_ENTRY;
typedef struct _IMAGE_RESOURCE_DIR_STRING_U {
WORD Length; // 字元數 (非位元組)
WCHAR NameString[1]; // 變長 Unicode 字串
} IMAGE_RESOURCE_DIR_STRING_U;
| ID | 常數 | 說明 |
|---|---|---|
| 1 | RT_CURSOR | 游標 |
| 2 | RT_BITMAP | 位圖 |
| 3 | RT_ICON | 圖示 (單一幀) |
| 4 | RT_MENU | 選單 |
| 5 | RT_DIALOG | 對話框模板 |
| 6 | RT_STRING | 字串表 |
| 7 | RT_FONTDIR | 字型目錄 |
| 8 | RT_FONT | 字型 |
| 9 | RT_ACCELERATOR | 加速鍵表 |
| 10 | RT_RCDATA | 自訂原始資料 |
| 11 | RT_MESSAGETABLE | 訊息表 |
| 12 | RT_GROUP_CURSOR | 游標組 |
| 14 | RT_GROUP_ICON | 圖示組 (含多尺寸/色深) |
| 16 | RT_VERSION | 版本資訊 (VS_VERSIONINFO) |
| 17 | RT_DLGINCLUDE | 對話框包含 |
| 19 | RT_PLUGPLAY | Plug&Play |
| 20 | RT_VXD | VxD |
| 21 | RT_ANICURSOR | 動畫游標 |
| 22 | RT_ANIICON | 動畫圖示 |
| 23 | RT_HTML | HTML 資源 |
| 24 | RT_MANIFEST | 應用程式資訊清單 |
void EnumResources(PBYTE rsrcBase, PIMAGE_RESOURCE_DIRECTORY dir, int level = 0)
{
WORD named = dir->NumberOfNamedEntries;
WORD id = dir->NumberOfIdEntries;
PIMAGE_RESOURCE_DIRECTORY_ENTRY entry = (PIMAGE_RESOURCE_DIRECTORY_ENTRY)(dir + 1);
for (int i = 0; i < named + id; i++, entry++)
{
// 解析類型/名稱/ID
BOOL isName = (entry->NameOffset & 0x80000000) == 0;
LPSTR typeName;
if (isName)
{
PIMAGE_RESOURCE_DIR_STRING_U str = (PIMAGE_RESOURCE_DIR_STRING_U)(rsrcBase + entry->NameOffset);
typeName = WideCharToAnsi(str->NameString, str->Length);
}
else
{
typeName = IntToStr(entry->Id);
}
// 判斷是子目錄還是資料項
if (entry->OffsetToData & 0x80000000)
{
// 子目錄:遞歸
PIMAGE_RESOURCE_DIRECTORY subDir = (PIMAGE_RESOURCE_DIRECTORY)(rsrcBase + (entry->OffsetToData & 0x7FFFFFFF));
EnumResources(rsrcBase, subDir, level + 1);
}
else
{
// 資料項:葉節點
PIMAGE_RESOURCE_DATA_ENTRY dataEntry = (PIMAGE_RESOURCE_DATA_ENTRY)(rsrcBase + entry->OffsetToData);
PBYTE data = rsrcBase + dataEntry->OffsetToData;
DWORD size = dataEntry->Size;
// 此處可讀取/修改/導出資料
}
}
}
.rsrc 區段(DataDirectory[IMAGE_DIRECTORY_ENTRY_RESOURCE])。RT_BITMAP)。0x0409 en-US 或 0x0804 zh-CN)。IMAGE_RESOURCE_DATA_ENTRY,指向資料區。SizeOfRawData、VirtualSize、對齊。NumberOfNamedEntries/IdEntries 計數。BeginUpdateResource/UpdateResource/EndUpdateResource(官方標準方式)pe.write()資源類型 RT_VERSION (16),ID 通常為 1,資料為結構化二進位:
VS_VERSIONINFO
- FixedFileInfo (結構化版本號、檔案旗標、OS、類型等)
- StringFileInfo
- StringTable (語言/代碼頁)
- CompanyName, FileDescription, FileVersion, InternalName, LegalCopyright, OriginalFilename, ProductName, ProductVersion 等
- VarFileInfo
- Translation (語言/代碼頁陣列)
修改版本資訊建議使用 VerSetResourceFile 系列 API 或 UpdateResource 替換整個 RT_VERSION 資源。
| 風險 | 後果 | 緩解 |
|---|---|---|
| 區段空間不足 | 覆蓋其他區段、檔案損壞 | 檢查剩餘空間,必要時擴展區段或新增區段 |
| 對齊錯誤 | 載入失敗、資料截斷 | 嚴格遵循 FileAlignment (通常 0x200) 對齊 |
| 目錄計數不一致 | 資源枚舉器漏讀/誤讀 | 修改後重新計算所有層級 NumberOfNamedEntries/IdEntries |
| 數位簽名失效 | Authenticode 驗證失敗 | 修改後重新簽名 |
深入掌握 PE 資源目錄樹結構,是實現資源編輯器、殼程式圖示同步、版本資訊自動化、惡意軟體資源分析等進階技術的鑰匙。