Windows PE结构
6、资源表解析
1、课程目标
- 理解PE资源的组织结构
- 掌握资源表的三级目录结构
- 学会提取和修改PE资源
- 了解资源在木马开发中的应用
2、名词解释
| 名词 | 全称 | 解释 |
|---|---|---|
| Resource Table | 资源表 | 存储程序资源的数据结构 |
| Resource Directory | 资源目录 | 资源的层级结构 |
| RT_ICON | - | 图标资源类型 |
| RT_BITMAP | - | 位图资源类型 |
| RT_STRING | - | 字符串资源类型 |
| RT_VERSION | - | 版本信息资源 |
| RT_MANIFEST | - | 清单资源 |
| RT_RCDATA | - | 原始数据资源 |
3、使用工具
- Resource Hacker
- CFF Explorer
- PE-bear
- ResEdit
4、技术原理
4.1、资源表三级结构
资源表结构(三级目录):
Level 1: 资源类型(Type)
|
+-- RT_ICON (3)
| |
+-- RT_BITMAP (2)
| |
+-- RT_VERSION (16)
|
Level 2: 资源名称/ID(Name/ID)
|
+-- ID: 1
| |
+-- ID: 2
| |
+-- Name: "MYICON"
|
Level 3: 资源语言(Language)
|
+-- 0x0409 (English-US)
| |
+-- 0x0804 (Chinese-Simplified)
|
v
IMAGE_RESOURCE_DATA_ENTRY
|
v
实际资源数据
4.2、资源目录结构
// 资源目录
typedef struct _IMAGE_RESOURCE_DIRECTORY {
DWORD Characteristics; // 特征(通常为0)
DWORD TimeDateStamp; // 时间戳
WORD MajorVersion; // 主版本
WORD MinorVersion; // 次版本
WORD NumberOfNamedEntries; // 命名条目数
WORD NumberOfIdEntries; // ID条目数
// 后跟 IMAGE_RESOURCE_DIRECTORY_ENTRY 数组
} IMAGE_RESOURCE_DIRECTORY, *PIMAGE_RESOURCE_DIRECTORY;
// 资源目录条目
typedef struct _IMAGE_RESOURCE_DIRECTORY_ENTRY {
union {
struct {
DWORD NameOffset:31; // 名称偏移(相对资源段)
DWORD NameIsString:1; // 1=名称,0=ID
};
DWORD Name;
WORD Id; // 资源ID
};
union {
DWORD OffsetToData; // 数据条目偏移
struct {
DWORD OffsetToDirectory:31; // 子目录偏移
DWORD DataIsDirectory:1; // 1=目录,0=数据
};
};
} IMAGE_RESOURCE_DIRECTORY_ENTRY;
// 资源数据条目
typedef struct _IMAGE_RESOURCE_DATA_ENTRY {
DWORD OffsetToData; // 数据RVA
DWORD Size; // 数据大小
DWORD CodePage; // 代码页
DWORD Reserved; // 保留
} IMAGE_RESOURCE_DATA_ENTRY;
// 资源名称字符串
typedef struct _IMAGE_RESOURCE_DIR_STRING_U {
WORD Length; // 字符数(非字节数)
WCHAR NameString[1]; // Unicode字符串
} IMAGE_RESOURCE_DIR_STRING_U;
4.3、标准资源类型
#define RT_CURSOR 1
#define RT_BITMAP 2
#define RT_ICON 3
#define RT_MENU 4
#define RT_DIALOG 5
#define RT_STRING 6
#define RT_FONTDIR 7
#define RT_FONT 8
#define RT_ACCELERATOR 9
#define RT_RCDATA 10
#define RT_MESSAGETABLE 11
#define RT_GROUP_CURSOR 12
#define RT_GROUP_ICON 14
#define RT_VERSION 16
#define RT_DLGINCLUDE 17
#define RT_PLUGPLAY 19
#define RT_VXD 20
#define RT_ANICURSOR 21
#define RT_ANIICON 22
#define RT_HTML 23
#define RT_MANIFEST 24
5、代码实现
5.1、解析资源表
#include <windows.h>
#include <stdio.h>
// 资源基地址(用于计算偏移)
LPBYTE g_ResourceBase = NULL;
// 获取资源类型名称
const char* GetResourceTypeName(DWORD type) {
switch (type) {
case 1: return "RT_CURSOR";
case 2: return "RT_BITMAP";
case 3: return "RT_ICON";
case 4: return "RT_MENU";
case 5: return "RT_DIALOG";
case 6: return "RT_STRING";
case 7: return "RT_FONTDIR";
case 8: return "RT_FONT";
case 9: return "RT_ACCELERATOR";
case 10: return "RT_RCDATA";
case 11: return "RT_MESSAGETABLE";
case 12: return "RT_GROUP_CURSOR";
case 14: return "RT_GROUP_ICON";
case 16: return "RT_VERSION";
case 17: return "RT_DLGINCLUDE";
case 19: return "RT_PLUGPLAY";
case 20: return "RT_VXD";
case 21: return "RT_ANICURSOR";
case 22: return "RT_ANIICON";
case 23: return "RT_HTML";
case 24: return "RT_MANIFEST";
default: return "UNKNOWN";
}
}
// 获取资源名称
void GetResourceName(PIMAGE_RESOURCE_DIRECTORY_ENTRY entry,
char* buffer, int bufferSize) {
if (entry->NameIsString) {
// 名称是字符串
PIMAGE_RESOURCE_DIR_STRING_U nameStr = (PIMAGE_RESOURCE_DIR_STRING_U)(
g_ResourceBase + entry->NameOffset
);
// 转换Unicode到ANSI
WideCharToMultiByte(CP_ACP, 0, nameStr->NameString, nameStr->Length,
buffer, bufferSize, NULL, NULL);
buffer[nameStr->Length] = '\0';
} else {
// 名称是ID
sprintf(buffer, "#%d", entry->Id);
}
}
// 解析资源数据条目
void ParseResourceDataEntry(PIMAGE_RESOURCE_DATA_ENTRY dataEntry,
int indent) {
char indentStr[64];
memset(indentStr, ' ', indent * 2);
indentStr[indent * 2] = '\0';
printf("%sData RVA: 0x%08X, Size: 0x%08X (%d bytes)\n",
indentStr, dataEntry->OffsetToData, dataEntry->Size, dataEntry->Size);
printf("%sCodePage: %d\n", indentStr, dataEntry->CodePage);
}
// 递归解析资源目录
void ParseResourceDirectory(PIMAGE_RESOURCE_DIRECTORY dir,
int level,
const char* parentName) {
char indent[64];
memset(indent, ' ', level * 2);
indent[level * 2] = '\0';
int totalEntries = dir->NumberOfNamedEntries + dir->NumberOfIdEntries;
printf("%sDirectory: Named=%d, ID=%d, Total=%d\n",
indent, dir->NumberOfNamedEntries, dir->NumberOfIdEntries, totalEntries);
// 条目紧跟在目录结构后面
PIMAGE_RESOURCE_DIRECTORY_ENTRY entries = (PIMAGE_RESOURCE_DIRECTORY_ENTRY)(dir + 1);
for (int i = 0; i < totalEntries; i++) {
PIMAGE_RESOURCE_DIRECTORY_ENTRY entry = &entries[i];
char name[256];
GetResourceName(entry, name, sizeof(name));
// 第一级是类型
if (level == 0) {
if (!entry->NameIsString) {
printf("%s[%d] Type: %s (%s)\n",
indent, i, name, GetResourceTypeName(entry->Id));
} else {
printf("%s[%d] Type: %s (Custom)\n", indent, i, name);
}
}
// 第二级是名称/ID
else if (level == 1) {
printf("%s[%d] Name/ID: %s\n", indent, i, name);
}
// 第三级是语言
else if (level == 2) {
printf("%s[%d] Language: 0x%04X\n", indent, i, entry->Id);
}
// 检查是目录还是数据
if (entry->DataIsDirectory) {
// 子目录
PIMAGE_RESOURCE_DIRECTORY subDir = (PIMAGE_RESOURCE_DIRECTORY)(
g_ResourceBase + entry->OffsetToDirectory
);
ParseResourceDirectory(subDir, level + 1, name);
} else {
// 数据条目
PIMAGE_RESOURCE_DATA_ENTRY dataEntry = (PIMAGE_RESOURCE_DATA_ENTRY)(
g_ResourceBase + entry->OffsetToData
);
ParseResourceDataEntry(dataEntry, level + 1);
}
}
}
// 解析资源表
void ParseResourceTable(LPVOID fileData) {
PIMAGE_DOS_HEADER dosHeader = (PIMAGE_DOS_HEADER)fileData;
PIMAGE_NT_HEADERS ntHeaders = (PIMAGE_NT_HEADERS)(
(PBYTE)fileData + dosHeader->e_lfanew
);
PIMAGE_DATA_DIRECTORY rsrcDir = &ntHeaders->OptionalHeader
.DataDirectory[IMAGE_DIRECTORY_ENTRY_RESOURCE];
if (rsrcDir->VirtualAddress == 0) {
printf("[-] 没有资源表\n");
return;
}
printf("\n========== Resource Table ==========\n");
printf("RVA: 0x%08X, Size: 0x%08X\n\n",
rsrcDir->VirtualAddress, rsrcDir->Size);
DWORD rsrcFoa = RvaToFoa(fileData, rsrcDir->VirtualAddress);
g_ResourceBase = (LPBYTE)fileData + rsrcFoa;
PIMAGE_RESOURCE_DIRECTORY rootDir = (PIMAGE_RESOURCE_DIRECTORY)g_ResourceBase;
ParseResourceDirectory(rootDir, 0, "ROOT");
}
5.2、提取资源
// 资源信息
typedef struct _RESOURCE_ENTRY {
DWORD type;
DWORD id;
WCHAR name[256];
WORD language;
DWORD dataRva;
DWORD dataSize;
LPBYTE data;
} RESOURCE_ENTRY;
// 查找特定资源
BOOL FindResource(LPVOID fileData, DWORD type, DWORD id,
WORD language, RESOURCE_ENTRY* result) {
PIMAGE_DOS_HEADER dosHeader = (PIMAGE_DOS_HEADER)fileData;
PIMAGE_NT_HEADERS ntHeaders = (PIMAGE_NT_HEADERS)(
(PBYTE)fileData + dosHeader->e_lfanew
);
PIMAGE_DATA_DIRECTORY rsrcDir = &ntHeaders->OptionalHeader
.DataDirectory[IMAGE_DIRECTORY_ENTRY_RESOURCE];
if (rsrcDir->VirtualAddress == 0) {
return FALSE;
}
DWORD rsrcFoa = RvaToFoa(fileData, rsrcDir->VirtualAddress);
LPBYTE rsrcBase = (LPBYTE)fileData + rsrcFoa;
// 第一级:类型
PIMAGE_RESOURCE_DIRECTORY typeDir = (PIMAGE_RESOURCE_DIRECTORY)rsrcBase;
int totalTypes = typeDir->NumberOfNamedEntries + typeDir->NumberOfIdEntries;
PIMAGE_RESOURCE_DIRECTORY_ENTRY typeEntries = (PIMAGE_RESOURCE_DIRECTORY_ENTRY)(typeDir + 1);
for (int i = 0; i < totalTypes; i++) {
if (!typeEntries[i].NameIsString && typeEntries[i].Id == type) {
// 找到类型
if (!typeEntries[i].DataIsDirectory) continue;
// 第二级:名称/ID
PIMAGE_RESOURCE_DIRECTORY idDir = (PIMAGE_RESOURCE_DIRECTORY)(
rsrcBase + typeEntries[i].OffsetToDirectory
);
int totalIds = idDir->NumberOfNamedEntries + idDir->NumberOfIdEntries;
PIMAGE_RESOURCE_DIRECTORY_ENTRY idEntries = (PIMAGE_RESOURCE_DIRECTORY_ENTRY)(idDir + 1);
for (int j = 0; j < totalIds; j++) {
if (!idEntries[j].NameIsString && idEntries[j].Id == id) {
// 找到ID
if (!idEntries[j].DataIsDirectory) continue;
// 第三级:语言
PIMAGE_RESOURCE_DIRECTORY langDir = (PIMAGE_RESOURCE_DIRECTORY)(
rsrcBase + idEntries[j].OffsetToDirectory
);
int totalLangs = langDir->NumberOfNamedEntries + langDir->NumberOfIdEntries;
PIMAGE_RESOURCE_DIRECTORY_ENTRY langEntries = (PIMAGE_RESOURCE_DIRECTORY_ENTRY)(langDir + 1);
for (int k = 0; k < totalLangs; k++) {
if (language == 0 || langEntries[k].Id == language) {
// 找到资源
PIMAGE_RESOURCE_DATA_ENTRY dataEntry = (PIMAGE_RESOURCE_DATA_ENTRY)(
rsrcBase + langEntries[k].OffsetToData
);
result->type = type;
result->id = id;
result->language = langEntries[k].Id;
result->dataRva = dataEntry->OffsetToData;
result->dataSize = dataEntry->Size;
DWORD dataFoa = RvaToFoa(fileData, dataEntry->OffsetToData);
result->data = (LPBYTE)fileData + dataFoa;
return TRUE;
}
}
}
}
}
}
return FALSE;
}
// 提取资源到文件
BOOL ExtractResource(LPVOID fileData, DWORD type, DWORD id,
const char* outputPath) {
RESOURCE_ENTRY entry;
if (!FindResource(fileData, type, id, 0, &entry)) {
printf("[-] 未找到资源: Type=%d, ID=%d\n", type, id);
return FALSE;
}
printf("[+] 找到资源: Type=%d, ID=%d, Size=%d bytes\n",
entry.type, entry.id, entry.dataSize);
// 写入文件
HANDLE hFile = CreateFileA(
outputPath,
GENERIC_WRITE,
0,
NULL,
CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
NULL
);
if (hFile == INVALID_HANDLE_VALUE) {
return FALSE;
}
DWORD written;
WriteFile(hFile, entry.data, entry.dataSize, &written, NULL);
CloseHandle(hFile);
printf("[+] 资源已提取到: %s\n", outputPath);
return TRUE;
}
// 提取图标组
BOOL ExtractIconGroup(LPVOID fileData, DWORD groupId,
const char* outputPath) {
// 首先获取GROUP_ICON资源
RESOURCE_ENTRY groupEntry;
if (!FindResource(fileData, 14, groupId, 0, &groupEntry)) { // RT_GROUP_ICON = 14
return FALSE;
}
// 解析GRPICONDIR结构
#pragma pack(push, 2)
typedef struct {
WORD idReserved;
WORD idType;
WORD idCount;
} GRPICONDIR;
typedef struct {
BYTE bWidth;
BYTE bHeight;
BYTE bColorCount;
BYTE bReserved;
WORD wPlanes;
WORD wBitCount;
DWORD dwBytesInRes;
WORD nID;
} GRPICONDIRENTRY;
#pragma pack(pop)
GRPICONDIR* iconDir = (GRPICONDIR*)groupEntry.data;
GRPICONDIRENTRY* entries = (GRPICONDIRENTRY*)(iconDir + 1);
printf("[+] 图标组包含 %d 个图标\n", iconDir->idCount);
// 创建ICO文件...
// (需要将多个RT_ICON资源组合成ICO格式)
return TRUE;
}
5.3、资源在木马开发中的应用
// 从资源加载ShellCode
BOOL LoadShellcodeFromResource(DWORD resourceId, LPVOID* shellcode, DWORD* size) {
HRSRC hResource = FindResourceA(NULL, MAKEINTRESOURCEA(resourceId), RT_RCDATA);
if (!hResource) {
return FALSE;
}
HGLOBAL hGlobal = LoadResource(NULL, hResource);
if (!hGlobal) {
return FALSE;
}
*shellcode = LockResource(hGlobal);
*size = SizeofResource(NULL, hResource);
return TRUE;
}
// 从资源加载加密的PE
BOOL LoadEncryptedPEFromResource(DWORD resourceId,
const BYTE* key,
LPVOID* decrypted,
DWORD* size) {
LPVOID encData;
DWORD encSize;
if (!LoadShellcodeFromResource(resourceId, &encData, &encSize)) {
return FALSE;
}
// 分配解密缓冲区
*decrypted = VirtualAlloc(NULL, encSize, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
if (!*decrypted) {
return FALSE;
}
// XOR解密
BYTE* src = (BYTE*)encData;
BYTE* dst = (BYTE*)*decrypted;
int keyLen = strlen((char*)key);
for (DWORD i = 0; i < encSize; i++) {
dst[i] = src[i] ^ key[i % keyLen];
}
*size = encSize;
return TRUE;
}
// 修改版本信息
BOOL ModifyVersionInfo(LPVOID fileData,
const char* companyName,
const char* productName,
const char* fileDescription) {
RESOURCE_ENTRY entry;
if (!FindResource(fileData, 16, 1, 0, &entry)) { // RT_VERSION = 16
printf("[-] 未找到版本信息\n");
return FALSE;
}
// 版本信息格式复杂,需要解析VS_VERSIONINFO结构
// 这里只是示例框架
printf("[+] 找到版本信息,大小: %d bytes\n", entry.dataSize);
return TRUE;
}
6、课后作业
-
编写资源提取工具
- 列出所有资源
- 提取指定类型的资源
- 支持提取图标和位图
-
实现资源嵌入器
- 向PE文件添加资源
- 替换现有资源
- 修改版本信息
-
分析资源隐写应用
- 在资源中隐藏数据
- 从资源加载ShellCode
- 研究常见木马的资源使用方式