36、Windows API Hook(一):IAT Hook原理与实现
API Hook,说白了就是拦截某个函数的调用。你想想看,一个程序跑着跑着,突然调用了 MessageBoxW,我们能不能在它执行之前,先插一手?这就是 Hook 要做的事。
我最早接触 Hook 是在做游戏外挂分析的时候。那时候看到别人能拦截 Send 包,觉得特别神奇。后来自己动手写了一个 IAT Hook,才明白——其实原理没那么玄乎。
什么是 IAT?
IAT,全称 Import Address Table,导入地址表。每个 PE 文件里都有一张表,记录了它从别的 DLL 里导入的函数地址。
举个例子:你的程序调用了 MessageBoxW,这个函数在 user32.dll 里。程序启动时,加载器会把 MessageBoxW 的真实地址填到 IAT 里。之后程序调用它,实际上就是通过 IAT 跳转过去的。
嗯,这里要注意:IAT 是每个模块独立的。也就是说,notepad.exe 的 IAT 和 calc.exe 的 IAT 是两码事。
IAT Hook 的核心思想
IAT Hook 的思路很简单:
- 找到目标模块的 IAT
- 找到你要 Hook 的那个函数条目
- 把 IAT 里原来的函数地址,改成你的函数地址
- 你的函数执行完,再决定要不要调用原函数
说白了,就是偷梁换柱。程序以为它调的是 MessageBoxW,实际上跳到了你的 MyMessageBox 里。
GetProcAddress 动态获取地址的,IAT Hook 就管不着了。这个坑我踩过,后面会讲。
动手实现一个 IAT Hook
我们拿 MessageBoxW 开刀。目标:拦截所有对 MessageBoxW 的调用,把弹窗内容改成我们自己的。
先看核心代码:
// 定义原函数类型
typedef int (WINAPI* pMessageBoxW)(
HWND hWnd,
LPCWSTR lpText,
LPCWSTR lpCaption,
UINT uType
);
// 保存原函数地址
pMessageBoxW OriginalMessageBoxW = NULL;
// 我们的 Hook 函数
int WINAPI HookedMessageBoxW(
HWND hWnd,
LPCWSTR lpText,
LPCWSTR lpCaption,
UINT uType
) {
// 把弹窗内容改成我们自己的
wchar_t* newText = L"你被 Hook 了!";
wchar_t* newCaption = L"IAT Hook Demo";
// 调用原函数(注意:这里用的是原函数地址)
return OriginalMessageBoxW(hWnd, newText, newCaption, uType);
}
// 安装 Hook
BOOL InstallHook() {
// 1. 获取当前模块的基址
HMODULE hModule = GetModuleHandle(NULL);
// 2. 解析 PE 头,找到 IAT
PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)hModule;
PIMAGE_NT_HEADERS pNtHeaders = (PIMAGE_NT_HEADERS)(
(BYTE*)hModule + pDosHeader->e_lfanew
);
// 3. 找到导入表
PIMAGE_IMPORT_DESCRIPTOR pImportDesc = (PIMAGE_IMPORT_DESCRIPTOR)(
(BYTE*)hModule +
pNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress
);
// 4. 遍历导入表,找到 user32.dll
while (pImportDesc->Name) {
char* dllName = (char*)((BYTE*)hModule + pImportDesc->Name);
if (_stricmp(dllName, "user32.dll") == 0) {
// 5. 遍历 IAT,找到 MessageBoxW
PIMAGE_THUNK_DATA pThunk = (PIMAGE_THUNK_DATA)(
(BYTE*)hModule + pImportDesc->FirstThunk
);
while (pThunk->u1.Function) {
// 检查是否是我们想要的函数
if (pThunk->u1.Function == (ULONG_PTR)GetProcAddress(
GetModuleHandle(L"user32.dll"), "MessageBoxW")) {
// 6. 保存原函数地址
OriginalMessageBoxW = (pMessageBoxW)pThunk->u1.Function;
// 7. 修改内存保护属性
DWORD oldProtect;
VirtualProtect(&pThunk->u1.Function, sizeof(ULONG_PTR),
PAGE_READWRITE, &oldProtect);
// 8. 写入 Hook 函数地址
pThunk->u1.Function = (ULONG_PTR)HookedMessageBoxW;
// 9. 恢复保护属性
VirtualProtect(&pThunk->u1.Function, sizeof(ULONG_PTR),
oldProtect, &oldProtect);
return TRUE;
}
pThunk++;
}
}
pImportDesc++;
}
return FALSE;
}
代码看着长,其实逻辑很清晰。我拆开讲:
- 第一步:拿到当前模块的基址。用
GetModuleHandle(NULL)就行。 - 第二步:解析 PE 头。DOS 头、NT 头,一路找下去。
- 第三步:定位导入表。在数据目录里找
IMAGE_DIRECTORY_ENTRY_IMPORT。 - 第四步:遍历每个导入的 DLL。找到
user32.dll。 - 第五步:遍历该 DLL 的 IAT。找到
MessageBoxW的条目。 - 第六步:保存原地址,写入新地址。
VirtualProtect 改内存保护属性。IAT 默认是只读的,直接写会崩。我刚开始写的时候忘了这步,程序直接挂了,查了半天才反应过来。
IAT Hook 的流程图
下面这张图展示了 IAT Hook 的完整流程:
卸载 Hook
Hook 装上去,还得能卸下来。不然程序退出时可能会出问题。
BOOL UninstallHook() {
// 找到之前保存的 IAT 条目
// 把地址写回原函数地址
// 注意:要确保原函数地址没有被释放
// 代码逻辑和 InstallHook 类似
// 只是最后一步改成:
// pThunk->u1.Function = (ULONG_PTR)OriginalMessageBoxW;
return TRUE;
}
kernel32.dll 里的函数,结果程序卸载了某个组件,把 kernel32 也卸了...嗯,那场面,惨不忍睹。
IAT Hook 的局限性
IAT Hook 虽然好用,但不是万能的。我总结了几点:
| 局限性 | 说明 | 怎么办? |
|---|---|---|
| 只能 Hook 导入函数 | 如果程序用 GetProcAddress 动态获取地址,IAT 里没有记录 |
用 Inline Hook 或 Detours |
| 每个模块独立 | Hook 了 notepad.exe 的 IAT,不影响 calc.exe | 需要 Hook 所有目标模块 |
| 容易被检测 | 安全软件会检查 IAT 是否被篡改 | 用更隐蔽的 Hook 方式 |
| 64 位兼容性 | 32 位和 64 位的 PE 结构略有不同 | 注意区分 IMAGE_NT_HEADERS32 和 IMAGE_NT_HEADERS64 |
实战中的坑
我当年做第一个 IAT Hook 时,遇到了一个特别诡异的问题:Hook 装上去之后,程序直接崩溃,连错误信息都没有。
查了半天,发现是 VirtualProtect 的参数写错了。我把 PAGE_READWRITE 写成了 PAGE_EXECUTE_READWRITE,虽然也能用,但某些模块的 IAT 在只读段里,改不了。
还有一个坑:如果你 Hook 的是 kernel32.dll 或 ntdll.dll 里的函数,要特别小心。这些 DLL 在进程里是共享的,你改了 IAT,可能会影响到其他线程。我建议新手先从 user32.dll 或 win32u.dll 练手。
!dh 模块基址,然后找 IMPORT 目录。或者直接用 dumpbin /imports exe文件 查看静态 IAT。
总结
IAT Hook 是 API Hook 的入门技术。它简单、直接,适合用来理解 Hook 的基本原理。但说实话,在实际的逆向工程中,IAT Hook 用得并不多——因为局限性太大了。
我个人习惯把 IAT Hook 用在快速验证场景。比如分析一个恶意软件,我想看看它调用了哪些 API,但又不想跑起来,就可以先 Hook 住 IAT,记录所有调用。等摸清了它的行为,再决定下一步怎么走。
下一讲,我们会深入 Inline Hook。那才是真正的大杀器。不过先把 IAT Hook 吃透,后面的路就好走了。
公众号:蓝海资料掘金营,微信deep3321