模块 62 - API Hooking - 使用 Windows API
恶意软件开发课程 - API Hooking - 使用 Windows API
模块 62 - API Hooking - 使用 Windows API#
API Hooking - 使用 Windows API#
简介#
使用 SetWindowsHookEx ↗ WinAPI 是实现 API Hooking 的另一种方法。它主要用于监视系统中的某些事件类型。这与之前模块中讨论的技术有所不同:SetWindowsHookExW/A 并不直接修改目标函数的指令(如写入 jmp),而是在特定事件触发时,由系统自动调用一个预先定义的“回调函数”。不过,这种方法监控的事件类型受限于 Windows 系统本身提供的范围。
SetWindowsHookEx 使用方法#
SetWindowsHookExW 的函数定义如下:
HHOOK SetWindowsHookExW(
[in] int idHook, // 要安装的钩子过程类型
[in] HOOKPROC lpfn, // 钩子回调函数的指针(事件触发时执行的函数)
[in] HINSTANCE hmod, // 包含回调函数的 DLL 句柄(通常设为 NULL)
[in] DWORD dwThreadId // 与钩子管理的线程 ID(通常设为 NULL,表示全局钩子)
);cidHook:要监视的事件。例如,WH_KEYBOARD_LL用于监控低级键盘消息(常用于编写键盘记录器)。在本模块中,我们将使用WH_MOUSE_LL标志来监控鼠标点击事件。lpfn:回调函数指针,每当事件发生时系统都会调用它。
回调函数定义#
回调函数必须符合 HOOKPROC 类型定义:
typedef LRESULT (CALLBACK* HOOKPROC)(int nCode, WPARAM wParam, LPARAM lParam);c一个典型的回调函数如下所示:
LRESULT CALLBACK HookCallbackFunc(int nCode, WPARAM wParam, LPARAM lParam){
// 处理逻辑...
// 必须调用 CallNextHookEx 将消息传递给链条中的下一个钩子
return CallNextHookEx(NULL, nCode, wParam, lParam);
}c💡 初学者提示:为什么必须调用 CallNextHookEx?
系统中的钩子是以链表形式存在的。如果你拦截了消息但不向下传递(即不调用
CallNextHookEx),那么其他依赖该消息的程序(甚至是系统本身)可能无法获取输入消息,导致它们行为异常或完全卡死。
处理消息循环#
仅仅安装钩子是不够的,你必须确保你的程序(以及安装钩子的线程)保持运行,并且能够“吞吐”系统发来的消息。在 Windows 开发中,这通常需要一个 消息循环 (Message Loop)。
如果我们只用一个死循环 while(1){},鼠标会因为消息积压而变得非常卡顿,甚至无法移动。我们需要使用 GetMessageW 获取消息,并使用 DefWindowProcW(或简单的 DispatchMessageW)来处理那些我们不感兴趣的消息。
BOOL MouseClicksLogger() {
MSG Msg = { 0 };
// 1. 安装低级鼠标钩子 (WH_MOUSE_LL)
HHOOK hMouseHook = SetWindowsHookExW(WH_MOUSE_LL, (HOOKPROC)HookCallback, NULL, NULL);
// 2. 消息循环:这是维持钩子运行的关键
// 如果没有这段代码,钩子将无法接收到系统通知
while (GetMessageW(&Msg, NULL, NULL, NULL)) {
// 将消息交给默认窗口过程处理,确保鼠标能正常移动
DefWindowProcW(Msg.hwnd, Msg.message, Msg.wParam, Msg.lParam);
}
return TRUE;
}cpp移除挂钩#
使用 UnhookWindowsHookEx 函数并传入钩子句柄即可停止监控。
完整示例代码#
下面的代码演示了如何实时监控并打印用户的鼠标左键、右击和中键操作。
// 全局钩子句柄
HHOOK g_hMouseHook = NULL;
// 当用户点击鼠标时执行的回调
LRESULT CALLBACK HookCallback(int nCode, WPARAM wParam, LPARAM lParam) {
// wParam 包含了鼠标消息的具体类型
if (wParam == WM_LBUTTONDOWN) {
printf("[ # ] 检测到鼠标左键点击 \n");
}
if (wParam == WM_RBUTTONDOWN) {
printf("[ # ] 检测到鼠标右键点击 \n");
}
if (wParam == WM_MBUTTONDOWN) {
printf("[ # ] 检测到鼠标中键点击 \n");
}
// 继续传递消息
return CallNextHookEx(NULL, nCode, wParam, lParam);
}
int main() {
// 启动监控线程
HANDLE hThread = CreateThread(NULL, NULL, (LPTHREAD_START_ROUTINE)MouseClicksLogger, NULL, NULL, NULL);
if (hThread) {
printf("[*] 开始监控鼠标点击(持续 10 秒)...\n");
WaitForSingleObject(hThread, 10000); // 监控 10 秒
}
// 结束监控,卸载钩子
if (g_hMouseHook && !UnhookWindowsHookEx(g_hMouseHook)) {
printf("[!] 卸载钩子失败,错误代码: %d \n", GetLastError());
}
return 0;
}cpp📚 知识扩展:用户层钩子的局限性
SetWindowsHookEx虽然强大且“官方”,但它非常容易被安全软件监测。因为它依赖于系统的消息机制,AV/EDR 只需要简单检查系统的钩子链(Hook Chain)就能发现是否存在可疑的回调函数。此外,由于所有消息都要经过你的回调函数处理,如果你的函数逻辑过于复杂,会严重拖慢整个系统的响应速度。
🎯 总结#
在本模块中,我们学习了:
- 官方挂钩机制:掌握了使用系统 API 监控全局事件的方法。
- 回调函数模型:理解了事件驱动型 Hook 的基本逻辑。
- 消息循环的重要性:学会了如何通过
GetMessage和DefWindowProc确保系统在被 Hook 时依然保持流畅。
📚 下一步学习
下一个模块将开启全新的专题:Syscalls - Introduction。我们将深入用户态与内核态的交界处,学习如何绕过那些在导出函数上安装 Hook 的监控工具!