API钩取:通过DLL注入钩取IAT表
字数 1241 2025-08-25 22:58:20
API钩取:通过DLL注入钩取IAT表技术详解
概述
本文详细讲解通过DLL注入方式钩取目标进程IAT表来完成API钩取的技术。该技术属于DLL注入和代码注入两种主要注入方式中的DLL注入方式。
技术原理
IAT表基础
- PE文件的可选文件头中包含
IMAGE_DATA_DIRECTORY结构体,存储了指向IAT结构的指针 - 未加壳的PE文件可以通过解析其二进制数据找到IAT结构的具体地址
- IAT表中存储了PE文件引用的所有外部函数的真实地址
- 程序运行时调用外部函数时,会从IAT表中查找该函数的真实地址
调用机制示例
例如程序调用外部函数A:
- 函数A在内存中的真实地址:0x77C84590
- 该地址存储在IAT中的位置:0x01001110
- 实际调用指令:
CALL DWORD PTR[01001110] - 等效于:
CALL 77C84590
钩取原理
通过修改IAT表中存储的API函数地址为自定义函数地址,可以劫持程序执行流:
- 当程序调用该API时,实际执行自定义函数
- 根据需求设计自定义函数的返回操作:
- 仅修改参数:返回执行原始API
- 完全截取:直接返回
目标API选择
选择方法
- 使用PE解析工具查看目标程序的导入函数
- 根据功能需求判断可能涉及的API
示例场景
将计算器的数字显示从阿拉伯数字改为汉字:
- 涉及界面显示功能
- 主要查找USER32.dll中的API
- 确定目标API为
SetWindowTextW(设置控件标题文本)
DLL实现详解
主要组件
- 伪造函数(MySetWindowsTextW)
- IAT钩取函数(IAT_Hook)
- DLL主函数(DllMain)
伪造函数实现
typedef BOOL(WINAPI* PFSETWINDOWTEXTW)(HWND hWnd, LPWSTR lpString);
FARPROC g_orgFunc = NULL;
BOOL WINAPI MySetWindowsTextW(HWND hWnd, LPWSTR lpString)
{
wchar_t cNum[] = L"零一二三四五六七八九";
wchar_t temp[2] = { 0, }; // Unicode中文字符使用两个字节
int i = 0, len = 0, index = 0;
len = wcslen(lpString);
for (i = 0; i < len; i++)
{
if (L'0' <= lpString[i] && lpString[i]<= L'9')
{
temp[0] = lpString[i];
index = _wtoi(temp);
lpString[i] = cNum[index];
}
}
return ((PFSETWINDOWTEXTW)g_orgFunc)(hWnd, lpString);
}
IAT钩取函数实现
BOOL IAT_Hook(LPCSTR DllName, PROC OrgFuncAddr, PROC NewFuncAddr)
{
HMODULE hModule = NULL;
LPCSTR szLibName = NULL;
PIMAGE_IMPORT_DESCRIPTOR pImportDesc;
PIMAGE_THUNK_DATA pThunk;
DWORD dwOldProtect;
DWORD dwRVA;
PBYTE pAddr = NULL;
// 获取当前模块基址
hModule = GetModuleHandle(NULL);
pAddr = (PBYTE)hModule;
// 定位PE头结构
pAddr += *((DWORD*)&pAddr[0x3C]); // 找到IMAGE_NT_HEADER
dwRVA = *((DWORD*)&pAddr[0x80]); // 找到IMAGE_DATA_DIRECTORY Import的RVA
pImportDesc = (PIMAGE_IMPORT_DESCRIPTOR)((DWORD)hModule + dwRVA);
// 遍历导入描述符
for (; pImportDesc->Name; pImportDesc++)
{
szLibName = (LPCSTR)((DWORD)hModule + pImportDesc->Name);
if (!_stricmp(DllName, szLibName)) // 匹配目标DLL
{
pThunk = (PIMAGE_THUNK_DATA)((DWORD)hModule + pImportDesc->FirstThunk);
// 遍历IAT表
for (; pThunk->u1.Function; pThunk++)
{
if (pThunk->u1.Function == (DWORD)OrgFuncAddr)
{
// 修改内存保护属性
VirtualProtect((LPVOID)&pThunk->u1.Function, 4,
PAGE_EXECUTE_READWRITE, &dwOldProtect);
// 修改IAT表项
pThunk->u1.Function = (DWORD)NewFuncAddr;
// 恢复内存保护属性
VirtualProtect((LPVOID)&pThunk->u1.Function, 4,
dwOldProtect, &dwOldProtect);
return TRUE;
}
}
}
}
return FALSE;
}
DLL主函数实现
BOOL APIENTRY DllMain(HINSTANCE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
// 保存原始函数地址并钩取
g_orgFunc = GetProcAddress(GetModuleHandle(L"user32.dll"), "SetWindowTextW");
IAT_Hook("user32.dll", g_orgFunc, (PROC)MySetWindowsTextW);
break;
case DLL_PROCESS_DETACH:
// 卸载时恢复原始函数地址
IAT_Hook("user32.dll", (PROC)MySetWindowsTextW, g_orgFunc);
break;
}
return TRUE;
}
注入工具实现
主要功能
- 提权(EnableDebugPriv)
- DLL注入(Inject)
- DLL卸载(Eject)
注入函数实现
BOOL Inject(DWORD dwPID, LPCTSTR szDllName)
{
HANDLE hProcess = NULL;
HANDLE hThread = NULL;
LPVOID pfRemoteBuf = NULL;
DWORD dwBufSize = (DWORD)(_tcslen(szDllName) + 1) * sizeof(TCHAR);
LPTHREAD_START_ROUTINE pThreadProc;
// 打开目标进程
if (!(hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID)))
{
printf("OpenProcess failed!\n");
return FALSE;
}
// 在目标进程中分配内存
pfRemoteBuf = VirtualAllocEx(hProcess, NULL, dwBufSize,
MEM_COMMIT, PAGE_READWRITE);
// 写入DLL路径
WriteProcessMemory(hProcess, pfRemoteBuf, (LPVOID)szDllName, dwBufSize, NULL);
// 获取LoadLibraryW地址
pThreadProc = (LPTHREAD_START_ROUTINE)GetProcAddress(
GetModuleHandle(L"kernel32.dll"), "LoadLibraryW");
// 创建远程线程执行注入
hThread = CreateRemoteThread(hProcess, NULL, 0, pThreadProc, pfRemoteBuf, 0, NULL);
if (hThread)
{
printf("inject successfully!");
}
else
{
printf("inject failed!");
CloseHandle(hProcess);
CloseHandle(hThread);
return FALSE;
}
WaitForSingleObject(hThread, INFINITE);
CloseHandle(hProcess);
CloseHandle(hThread);
return TRUE;
}
卸载函数实现
BOOL Eject(DWORD dwPID, LPCTSTR szDllName)
{
BOOL bMore = FALSE, bFound = FALSE;
HANDLE hSnapshot = NULL;
HANDLE hProcess = NULL;
HANDLE hThread = NULL;
MODULEENTRY32 me = { sizeof(MODULEENTRY32) };
LPTHREAD_START_ROUTINE pThreadProc;
// 获取进程模块快照
if ((hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, dwPID)) == INVALID_HANDLE_VALUE)
{
printf("Snapshot get failed!\n");
return FALSE;
}
// 查找目标模块
bMore = Module32First(hSnapshot, &me);
for (; bMore; bMore = Module32Next(hSnapshot, &me))
{
if (!_tcsicmp(me.szModule, szDllName) || !_tcsicmp(me.szExePath, szDllName))
{
bFound = TRUE;
break;
}
}
if (!bFound)
{
printf("Module SnapShot not found!\n");
CloseHandle(hSnapshot);
return FALSE;
}
// 打开目标进程
if (!(hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID)))
{
printf("OpenProcess failed!\n");
CloseHandle(hSnapshot);
return FALSE;
}
// 获取FreeLibrary地址
pThreadProc = (LPTHREAD_START_ROUTINE)GetProcAddress(
GetModuleHandle(L"kernel32.dll"), "FreeLibrary");
// 创建远程线程执行卸载
hThread = CreateRemoteThread(hProcess, NULL, 0, pThreadProc,
me.modBaseAddr, 0, NULL);
if (hThread)
{
printf("Eject successfully!");
}
else
{
printf("Eject failed!");
CloseHandle(hProcess);
CloseHandle(hThread);
return FALSE;
}
WaitForSingleObject(hThread, INFINITE);
CloseHandle(hSnapshot);
CloseHandle(hProcess);
CloseHandle(hThread);
return TRUE;
}
调试与分析
调试步骤
- 使用x32dbg打开目标程序(如计算器)
- 设置中断于新模块载入
- 运行程序至出现操作窗口
- 执行注入操作(需管理员权限)
- 调试器将在DLL入口点暂停
关键调试点
- IAT_Hook函数调用位置
- GetModuleHandle和GetProcAddress调用
- IAT表遍历和比较过程
- IAT表项修改操作
实际应用示例
操作流程
- 启动目标程序(如计算器)
- 运行注入工具注入DLL:
IATHook.exe i <PID> IATinjectDll.dll - 观察目标程序行为变化(数字显示变为汉字)
- 卸载DLL恢复原始行为:
IATHook.exe e <PID> IATinjectDll.dll
效果验证
- 注入前:计算器显示阿拉伯数字
- 注入后:计算器显示中文数字
- 卸载后:恢复阿拉伯数字显示
技术要点总结
- PE结构解析:准确找到IAT表位置是关键
- 内存保护修改:修改IAT表项前需调整内存属性
- 函数劫持设计:伪造函数需正确处理参数和返回值
- 注入/卸载对称:确保卸载后程序能恢复正常运行
- 错误处理:完善的错误检查确保操作可靠性
参考资料
- 《逆向工程核心原理》[韩] 李承远
- MSDN官方文档
- PE文件格式规范