从零讲解隐藏导入表
字数 776 2025-08-29 08:30:06
深入解析Windows程序隐藏导入表技术
1. 导入表基础概念
在Windows操作系统中,可执行文件(.exe)和动态链接库(DLL)通常需要调用其他DLL中的函数来实现特定功能。程序的导入表(Import Table)就是用于记录这些外部依赖信息的一种数据结构。
1.1 导入表示例
#include <windows.h>
#include <stdio.h>
int main() {
// 调用MessageBoxA函数(ANSI版本)
int result = MessageBoxA(NULL, "这是控制台程序中弹出的消息框。", "消息提示", MB_OKCANCEL);
return 0;
}
在这个例子中,程序调用了user32.dll中的MessageBoxA函数。使用工具如CFF Explorer查看导入表,可以清楚地看到对user32.dll和MessageBoxA函数的依赖。
2. 隐藏导入表的基本方法
2.1 手动加载DLL技术
传统方式依赖编译器自动生成导入表,我们可以通过手动加载DLL并获取函数地址来绕过这一机制:
- 定义函数指针类型
- 手动加载DLL
- 在DLL中查找函数地址
- 将函数地址转换为指针并调用
2.2 函数指针类型定义
根据Microsoft官方文档定义函数指针类型:
// 定义MessageBoxA函数指针类型
typedef UINT(CALLBACK* fnMessageBoxA)(
HWND hWnd,
LPCSTR lpText,
LPCSTR lpCaption,
UINT uType
);
2.3 手动加载DLL和获取函数地址
HMODULE hUser32 = LoadLibraryA("user32.dll");
FARPROC messageBoxAddr = GetFunctionAddress(hUser32, "MessageBoxA");
fnMessageBoxA myMessageBoxA = (fnMessageBoxA)messageBoxAddr;
myMessageBoxA(NULL, "11111", "1111", MB_OK);
FreeLibrary(hUser32);
3. 函数地址查找实现
3.1 GetFunctionAddress函数实现
FARPROC GetFunctionAddress(HMODULE hModule, const char* functionName) {
// 获取DOS头
PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)hModule;
// 获取NT头
PIMAGE_NT_HEADERS pNtHeaders = (PIMAGE_NT_HEADERS)((DWORD_PTR)hModule + pDosHeader->e_lfanew);
// 获取导出表
PIMAGE_EXPORT_DIRECTORY pExportDirectory = (PIMAGE_EXPORT_DIRECTORY)((DWORD_PTR)hModule +
pNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);
// 获取名称表、序号表和函数地址表
PDWORD pNames = (PDWORD)((DWORD_PTR)hModule + pExportDirectory->AddressOfNames);
PWORD pNameOrdinals = (PWORD)((DWORD_PTR)hModule + pExportDirectory->AddressOfNameOrdinals);
PDWORD pFunctions = (PDWORD)((DWORD_PTR)hModule + pExportDirectory->AddressOfFunctions);
// 遍历名称表查找目标函数
for (DWORD i = 0; i < pExportDirectory->NumberOfNames; i++) {
const char* currentFunctionName = (const char*)((DWORD_PTR)hModule + pNames[i]);
if (strcmp(currentFunctionName, functionName) == 0) {
WORD ordinal = pNameOrdinals[i];
return (FARPROC)((DWORD_PTR)hModule + pFunctions[ordinal]);
}
}
return NULL;
}
4. 字符串隐藏技术
虽然上述方法隐藏了导入表,但函数名字符串仍可能被检测到。可以通过哈希加密技术进一步隐藏。
4.1 旋转哈希函数实现
unsigned int rotatingHash(const char* str) {
unsigned int hash = 0;
int i;
for (i = 0; str[i] != '\0'; i++) {
hash = (hash << 4) ^ (hash >> 28) ^ str[i];
}
return hash;
}
4.2 使用哈希值查找函数
static LPVOID getAPIAddr(HMODULE h, DWORD myHash) {
PIMAGE_DOS_HEADER img_dos_header = (PIMAGE_DOS_HEADER)h;
PIMAGE_NT_HEADERS img_nt_header = (PIMAGE_NT_HEADERS)((LPBYTE)h + img_dos_header->e_lfanew);
PIMAGE_EXPORT_DIRECTORY img_edt = (PIMAGE_EXPORT_DIRECTORY)(
(LPBYTE)h + img_nt_header->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);
PDWORD fAddr = (PDWORD)((LPBYTE)h + img_edt->AddressOfFunctions);
PDWORD fNames = (PDWORD)((LPBYTE)h + img_edt->AddressOfNames);
PWORD fOrd = (PWORD)((LPBYTE)h + img_edt->AddressOfNameOrdinals);
for (DWORD i = 0; i < img_edt->AddressOfFunctions; i++) {
LPSTR pFuncName = (LPSTR)((LPBYTE)h + fNames[i]);
if (rotatingHash(pFuncName) == myHash) {
printf("successfully found! %s - %d\n", pFuncName, myHash);
return (LPVOID)((LPBYTE)h + fAddr[fOrd[i]]);
}
}
return NULL;
}
4.3 使用示例
void main() {
HMODULE mod = LoadLibraryA("user32.dll");
LPVOID addr = getAPIAddr(mod, 1460732901); // MessageBoxA的哈希值
printf("0x%p\n", addr);
fnMessageBoxA myMessageBoxA = (fnMessageBoxA)addr;
myMessageBoxA(NULL, "这是弹窗", "嘻嘻嘻", MB_OK);
}
5. 完整实现代码
#include <windows.h>
#include <stdio.h>
typedef UINT(CALLBACK* fnMessageBoxA)(
HWND hWnd,
LPCSTR lpText,
LPCSTR lpCaption,
UINT uType
);
unsigned int rotatingHash(const char* str) {
unsigned int hash = 0;
int i;
for (i = 0; str[i] != '\0'; i++) {
hash = (hash << 4) ^ (hash >> 28) ^ str[i];
}
return hash;
}
static LPVOID getAPIAddr(HMODULE h, DWORD myHash) {
PIMAGE_DOS_HEADER img_dos_header = (PIMAGE_DOS_HEADER)h;
PIMAGE_NT_HEADERS img_nt_header = (PIMAGE_NT_HEADERS)((LPBYTE)h + img_dos_header->e_lfanew);
PIMAGE_EXPORT_DIRECTORY img_edt = (PIMAGE_EXPORT_DIRECTORY)(
(LPBYTE)h + img_nt_header->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);
PDWORD fAddr = (PDWORD)((LPBYTE)h + img_edt->AddressOfFunctions);
PDWORD fNames = (PDWORD)((LPBYTE)h + img_edt->AddressOfNames);
PWORD fOrd = (PWORD)((LPBYTE)h + img_edt->AddressOfNameOrdinals);
for (DWORD i = 0; i < img_edt->AddressOfFunctions; i++) {
LPSTR pFuncName = (LPSTR)((LPBYTE)h + fNames[i]);
if (rotatingHash(pFuncName) == myHash) {
printf("successfully found! %s - %d\n", pFuncName, myHash);
return (LPVOID)((LPBYTE)h + fAddr[fOrd[i]]);
}
}
return NULL;
}
void main() {
HMODULE mod = LoadLibraryA("user32.dll");
LPVOID addr = getAPIAddr(mod, 1460732901); // MessageBoxA的哈希值
printf("0x%p\n", addr);
fnMessageBoxA myMessageBoxA = (fnMessageBoxA)addr;
myMessageBoxA(NULL, "这是弹窗", "嘻嘻嘻", MB_OK);
}
6. 技术总结
- 导入表隐藏:通过手动加载DLL和获取函数地址,避免在导入表中留下痕迹
- 字符串隐藏:使用哈希值代替函数名字符串,防止字符串扫描检测
- PE结构解析:深入理解PE文件格式,直接操作内存中的DLL结构
- 灵活性:可以进一步扩展为动态计算哈希值,增加对抗分析的难度
这种方法可以有效规避静态分析工具的检测,但需要注意:
- 哈希冲突的可能性(虽然概率很低)
- 不同系统版本中DLL导出函数的变化
- 异常处理机制的完善