Windows PE结构
4、导入表解析
1、课程目标
- 理解PE导入机制的工作原理
- 掌握导入表结构及其解析方法
- 理解IAT的作用和工作方式
- 学会修改和添加导入表项
2、名词解释
| 名词 | 全称 | 解释 |
|---|---|---|
| Import Table | 导入表 | 记录程序依赖的外部函数 |
| IID | Import Descriptor | 导入描述符 |
| INT | Import Name Table | 导入名称表/查找表 |
| IAT | Import Address Table | 导入地址表 |
| Thunk | - | 函数地址/名称条目 |
| Ordinal | 序号 | 通过序号导入函数 |
| Hint | 提示 | 加速函数查找的索引 |
| Bound Import | 绑定导入 | 预先计算的函数地址 |
3、使用工具
- CFF Explorer
- PE-bear
- Dependency Walker
- x64dbg
4、技术原理
4.1、导入表结构
导入表结构图:
IMAGE_DATA_DIRECTORY[1]
|
v
+-------------------+
| Import Descriptor | --> kernel32.dll
+-------------------+
| Import Descriptor | --> user32.dll
+-------------------+
| Import Descriptor | --> ntdll.dll
+-------------------+
| NULL (结束标志) |
+-------------------+
每个Import Descriptor:
+------------------+
| OriginalFirstThunk| ---> INT (Import Name Table)
| TimeDateStamp | |
| ForwarderChain | v
| Name | --+ +------------+
| FirstThunk | | | Thunk 1 | -> Hint + Name
+------------------+ | | Thunk 2 | -> Ordinal
| | | NULL |
| | +------------+
| |
| +--> DLL名称字符串
|
+---> IAT (Import Address Table)
|
v
+------------+
| 加载前: INT| (与INT相同)
| 加载后: VA | (实际函数地址)
+------------+
4.2、导入描述符结构
typedef struct _IMAGE_IMPORT_DESCRIPTOR {
union {
DWORD Characteristics;
DWORD OriginalFirstThunk; // RVA to INT (Import Name Table)
};
DWORD TimeDateStamp; // 时间戳(绑定导入用)
DWORD ForwarderChain; // 转发链
DWORD Name; // RVA to DLL name
DWORD FirstThunk; // RVA to IAT
} IMAGE_IMPORT_DESCRIPTOR, *PIMAGE_IMPORT_DESCRIPTOR;
// Thunk数据(32位)
typedef struct _IMAGE_THUNK_DATA32 {
union {
DWORD ForwarderString; // 转发器字符串RVA
DWORD Function; // 函数地址
DWORD Ordinal; // 序号(高位为1)
DWORD AddressOfData; // RVA to IMAGE_IMPORT_BY_NAME
} u1;
} IMAGE_THUNK_DATA32;
// Thunk数据(64位)
typedef struct _IMAGE_THUNK_DATA64 {
union {
ULONGLONG ForwarderString;
ULONGLONG Function;
ULONGLONG Ordinal;
ULONGLONG AddressOfData;
} u1;
} IMAGE_THUNK_DATA64;
// 导入函数名称
typedef struct _IMAGE_IMPORT_BY_NAME {
WORD Hint; // 函数索引提示
CHAR Name[1]; // 函数名(变长)
} IMAGE_IMPORT_BY_NAME;
// 判断是否按序号导入
#define IMAGE_SNAP_BY_ORDINAL32(Ordinal) ((Ordinal & 0x80000000) != 0)
#define IMAGE_SNAP_BY_ORDINAL64(Ordinal) ((Ordinal & 0x8000000000000000) != 0)
#define IMAGE_ORDINAL32(Ordinal) (Ordinal & 0xFFFF)
#define IMAGE_ORDINAL64(Ordinal) (Ordinal & 0xFFFF)
5、代码实现
5.1、解析导入表
#include <windows.h>
#include <stdio.h>
// 解析导入表
void ParseImportTable(LPVOID fileData) {
PIMAGE_DOS_HEADER dosHeader = (PIMAGE_DOS_HEADER)fileData;
PIMAGE_NT_HEADERS ntHeaders = (PIMAGE_NT_HEADERS)(
(PBYTE)fileData + dosHeader->e_lfanew
);
BOOL is64 = (ntHeaders->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR64_MAGIC);
// 获取导入表数据目录
PIMAGE_DATA_DIRECTORY importDir;
if (is64) {
PIMAGE_OPTIONAL_HEADER64 opt = (PIMAGE_OPTIONAL_HEADER64)&ntHeaders->OptionalHeader;
importDir = &opt->DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT];
} else {
PIMAGE_OPTIONAL_HEADER32 opt = (PIMAGE_OPTIONAL_HEADER32)&ntHeaders->OptionalHeader;
importDir = &opt->DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT];
}
if (importDir->VirtualAddress == 0) {
printf("[-] 没有导入表\n");
return;
}
printf("\n========== Import Table ==========\n");
printf("RVA: 0x%08X, Size: 0x%08X\n\n",
importDir->VirtualAddress, importDir->Size);
// 获取导入描述符数组
DWORD importFoa = RvaToFoa(fileData, importDir->VirtualAddress);
PIMAGE_IMPORT_DESCRIPTOR importDesc = (PIMAGE_IMPORT_DESCRIPTOR)(
(PBYTE)fileData + importFoa
);
int dllIndex = 0;
// 遍历所有导入的DLL
while (importDesc->Name != 0) {
// 获取DLL名称
DWORD nameFoa = RvaToFoa(fileData, importDesc->Name);
char* dllName = (char*)((PBYTE)fileData + nameFoa);
printf("[%d] %s\n", dllIndex, dllName);
printf(" OriginalFirstThunk: 0x%08X\n", importDesc->OriginalFirstThunk);
printf(" TimeDateStamp: 0x%08X\n", importDesc->TimeDateStamp);
printf(" ForwarderChain: 0x%08X\n", importDesc->ForwarderChain);
printf(" Name: 0x%08X -> %s\n", importDesc->Name, dllName);
printf(" FirstThunk (IAT): 0x%08X\n", importDesc->FirstThunk);
// 解析函数列表
ParseImportFunctions(fileData, importDesc, is64);
printf("\n");
importDesc++;
dllIndex++;
}
printf("\n[*] 共导入 %d 个DLL\n", dllIndex);
}
// 解析导入函数
void ParseImportFunctions(LPVOID fileData,
PIMAGE_IMPORT_DESCRIPTOR importDesc,
BOOL is64) {
// 优先使用OriginalFirstThunk(INT),如果为0则使用FirstThunk(IAT)
DWORD thunkRva = importDesc->OriginalFirstThunk;
if (thunkRva == 0) {
thunkRva = importDesc->FirstThunk;
}
DWORD thunkFoa = RvaToFoa(fileData, thunkRva);
DWORD iatRva = importDesc->FirstThunk;
int funcIndex = 0;
if (is64) {
PIMAGE_THUNK_DATA64 thunk = (PIMAGE_THUNK_DATA64)(
(PBYTE)fileData + thunkFoa
);
while (thunk->u1.AddressOfData != 0) {
printf(" [%3d] ", funcIndex);
if (IMAGE_SNAP_BY_ORDINAL64(thunk->u1.Ordinal)) {
// 按序号导入
WORD ordinal = (WORD)IMAGE_ORDINAL64(thunk->u1.Ordinal);
printf("Ordinal: %5d", ordinal);
} else {
// 按名称导入
DWORD hintNameFoa = RvaToFoa(fileData, (DWORD)thunk->u1.AddressOfData);
PIMAGE_IMPORT_BY_NAME hintName = (PIMAGE_IMPORT_BY_NAME)(
(PBYTE)fileData + hintNameFoa
);
printf("Hint: %4d Name: %s", hintName->Hint, hintName->Name);
}
printf(" (IAT RVA: 0x%08X)\n", iatRva + funcIndex * 8);
thunk++;
funcIndex++;
}
} else {
PIMAGE_THUNK_DATA32 thunk = (PIMAGE_THUNK_DATA32)(
(PBYTE)fileData + thunkFoa
);
while (thunk->u1.AddressOfData != 0) {
printf(" [%3d] ", funcIndex);
if (IMAGE_SNAP_BY_ORDINAL32(thunk->u1.Ordinal)) {
WORD ordinal = (WORD)IMAGE_ORDINAL32(thunk->u1.Ordinal);
printf("Ordinal: %5d", ordinal);
} else {
DWORD hintNameFoa = RvaToFoa(fileData, thunk->u1.AddressOfData);
PIMAGE_IMPORT_BY_NAME hintName = (PIMAGE_IMPORT_BY_NAME)(
(PBYTE)fileData + hintNameFoa
);
printf("Hint: %4d Name: %s", hintName->Hint, hintName->Name);
}
printf(" (IAT RVA: 0x%08X)\n", iatRva + funcIndex * 4);
thunk++;
funcIndex++;
}
}
printf(" Total: %d functions\n", funcIndex);
}
5.2、查找特定导入
// 查找导入的DLL
PIMAGE_IMPORT_DESCRIPTOR FindImportDll(LPVOID fileData, const char* dllName) {
PIMAGE_DOS_HEADER dosHeader = (PIMAGE_DOS_HEADER)fileData;
PIMAGE_NT_HEADERS ntHeaders = (PIMAGE_NT_HEADERS)(
(PBYTE)fileData + dosHeader->e_lfanew
);
BOOL is64 = (ntHeaders->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR64_MAGIC);
PIMAGE_DATA_DIRECTORY importDir;
if (is64) {
importDir = &((PIMAGE_OPTIONAL_HEADER64)&ntHeaders->OptionalHeader)
->DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT];
} else {
importDir = &ntHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT];
}
if (importDir->VirtualAddress == 0) {
return NULL;
}
DWORD importFoa = RvaToFoa(fileData, importDir->VirtualAddress);
PIMAGE_IMPORT_DESCRIPTOR importDesc = (PIMAGE_IMPORT_DESCRIPTOR)(
(PBYTE)fileData + importFoa
);
while (importDesc->Name != 0) {
DWORD nameFoa = RvaToFoa(fileData, importDesc->Name);
char* name = (char*)((PBYTE)fileData + nameFoa);
if (_stricmp(name, dllName) == 0) {
return importDesc;
}
importDesc++;
}
return NULL;
}
// 查找导入的函数
DWORD FindImportFunction(LPVOID fileData,
const char* dllName,
const char* funcName) {
PIMAGE_IMPORT_DESCRIPTOR importDesc = FindImportDll(fileData, dllName);
if (!importDesc) {
return 0;
}
PIMAGE_DOS_HEADER dosHeader = (PIMAGE_DOS_HEADER)fileData;
PIMAGE_NT_HEADERS ntHeaders = (PIMAGE_NT_HEADERS)(
(PBYTE)fileData + dosHeader->e_lfanew
);
BOOL is64 = (ntHeaders->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR64_MAGIC);
DWORD thunkRva = importDesc->OriginalFirstThunk;
if (thunkRva == 0) {
thunkRva = importDesc->FirstThunk;
}
DWORD thunkFoa = RvaToFoa(fileData, thunkRva);
DWORD iatRva = importDesc->FirstThunk;
int index = 0;
if (is64) {
PIMAGE_THUNK_DATA64 thunk = (PIMAGE_THUNK_DATA64)(
(PBYTE)fileData + thunkFoa
);
while (thunk->u1.AddressOfData != 0) {
if (!IMAGE_SNAP_BY_ORDINAL64(thunk->u1.Ordinal)) {
DWORD hintFoa = RvaToFoa(fileData, (DWORD)thunk->u1.AddressOfData);
PIMAGE_IMPORT_BY_NAME hintName = (PIMAGE_IMPORT_BY_NAME)(
(PBYTE)fileData + hintFoa
);
if (strcmp(hintName->Name, funcName) == 0) {
return iatRva + index * 8; // 返回IAT条目的RVA
}
}
thunk++;
index++;
}
} else {
PIMAGE_THUNK_DATA32 thunk = (PIMAGE_THUNK_DATA32)(
(PBYTE)fileData + thunkFoa
);
while (thunk->u1.AddressOfData != 0) {
if (!IMAGE_SNAP_BY_ORDINAL32(thunk->u1.Ordinal)) {
DWORD hintFoa = RvaToFoa(fileData, thunk->u1.AddressOfData);
PIMAGE_IMPORT_BY_NAME hintName = (PIMAGE_IMPORT_BY_NAME)(
(PBYTE)fileData + hintFoa
);
if (strcmp(hintName->Name, funcName) == 0) {
return iatRva + index * 4;
}
}
thunk++;
index++;
}
}
return 0;
}
5.3、运行时Hook IAT
// IAT Hook结构
typedef struct _IAT_HOOK {
LPVOID originalFunc;
LPVOID hookFunc;
LPVOID* iatEntry;
char dllName[64];
char funcName[128];
} IAT_HOOK;
// 安装IAT Hook
BOOL InstallIATHook(HMODULE hModule,
const char* dllName,
const char* funcName,
LPVOID hookFunc,
IAT_HOOK* hook) {
// 获取模块基址
LPBYTE base = (LPBYTE)hModule;
PIMAGE_DOS_HEADER dosHeader = (PIMAGE_DOS_HEADER)base;
PIMAGE_NT_HEADERS ntHeaders = (PIMAGE_NT_HEADERS)(
base + dosHeader->e_lfanew
);
PIMAGE_DATA_DIRECTORY importDir = &ntHeaders->OptionalHeader
.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT];
if (importDir->VirtualAddress == 0) {
return FALSE;
}
// 遍历导入描述符
PIMAGE_IMPORT_DESCRIPTOR importDesc = (PIMAGE_IMPORT_DESCRIPTOR)(
base + importDir->VirtualAddress
);
while (importDesc->Name) {
char* name = (char*)(base + importDesc->Name);
if (_stricmp(name, dllName) == 0) {
// 找到目标DLL
PIMAGE_THUNK_DATA originalThunk = (PIMAGE_THUNK_DATA)(
base + importDesc->OriginalFirstThunk
);
PIMAGE_THUNK_DATA firstThunk = (PIMAGE_THUNK_DATA)(
base + importDesc->FirstThunk
);
while (originalThunk->u1.AddressOfData) {
if (!IMAGE_SNAP_BY_ORDINAL(originalThunk->u1.Ordinal)) {
PIMAGE_IMPORT_BY_NAME hintName = (PIMAGE_IMPORT_BY_NAME)(
base + originalThunk->u1.AddressOfData
);
if (strcmp(hintName->Name, funcName) == 0) {
// 找到目标函数
hook->originalFunc = (LPVOID)firstThunk->u1.Function;
hook->hookFunc = hookFunc;
hook->iatEntry = (LPVOID*)&firstThunk->u1.Function;
strcpy(hook->dllName, dllName);
strcpy(hook->funcName, funcName);
// 修改内存保护
DWORD oldProtect;
VirtualProtect(hook->iatEntry, sizeof(LPVOID),
PAGE_READWRITE, &oldProtect);
// 替换函数地址
*hook->iatEntry = hookFunc;
// 恢复保护
VirtualProtect(hook->iatEntry, sizeof(LPVOID),
oldProtect, &oldProtect);
printf("[+] IAT Hook: %s!%s\n", dllName, funcName);
printf(" Original: 0x%p\n", hook->originalFunc);
printf(" Hook: 0x%p\n", hookFunc);
return TRUE;
}
}
originalThunk++;
firstThunk++;
}
}
importDesc++;
}
return FALSE;
}
// 卸载IAT Hook
BOOL UninstallIATHook(IAT_HOOK* hook) {
if (!hook->iatEntry) {
return FALSE;
}
DWORD oldProtect;
VirtualProtect(hook->iatEntry, sizeof(LPVOID),
PAGE_READWRITE, &oldProtect);
*hook->iatEntry = hook->originalFunc;
VirtualProtect(hook->iatEntry, sizeof(LPVOID),
oldProtect, &oldProtect);
printf("[+] IAT Hook removed: %s!%s\n", hook->dllName, hook->funcName);
return TRUE;
}
// 示例:Hook MessageBoxA
typedef int (WINAPI* PFN_MessageBoxA)(HWND, LPCSTR, LPCSTR, UINT);
IAT_HOOK g_MessageBoxHook = {0};
int WINAPI HookedMessageBoxA(HWND hWnd, LPCSTR lpText,
LPCSTR lpCaption, UINT uType) {
printf("[Hook] MessageBoxA called!\n");
printf(" Text: %s\n", lpText);
printf(" Caption: %s\n", lpCaption);
// 调用原函数(修改内容)
PFN_MessageBoxA original = (PFN_MessageBoxA)g_MessageBoxHook.originalFunc;
return original(hWnd, "[HOOKED]", lpCaption, uType);
}
6、课后作业
-
编写完整的导入表解析器
- 列出所有导入的DLL
- 列出每个DLL的所有函数
- 支持序号导入和名称导入
-
实现IAT Hook框架
- 支持Hook任意导入函数
- 支持保存和调用原函数
- 实现自动卸载功能
-
分析PE导入表安全问题
- 研究导入表注入技术
- 分析绑定导入的影响
- 了解DLL劫持原理