Windows消息钩子
API介绍
SetWindowsHookEx
是 Windows API 中用于安装挂钩(Hook)的函数,它允许一个应用程序在特定事件发生时拦截系统消息或输入(例如键盘、鼠标输入),并对其进行处理。通常用于捕获、监控或修改应用程序中的消息流。
1 2 3 4 5 6
| HHOOK SetWindowsHookEx( int idHook, HOOKPROC lpfn, HINSTANCE hMod, DWORD dwThreadId );
|
参数解释:
idHook
(挂钩类型): 表示要安装的挂钩类型,它决定了系统在什么事件发生时调用钩子函数。常见的挂钩类型有:
WH_KEYBOARD
: 捕获键盘消息(不区分按键的目标窗口)。
WH_KEYBOARD_LL
: 捕获低级别的全局键盘消息,可以用于记录按键等。
WH_MOUSE
: 捕获鼠标消息。
WH_MOUSE_LL
: 捕获低级别的全局鼠标消息。
WH_CALLWNDPROC
: 监控发送给目标窗口的消息。
WH_GETMESSAGE
: 监控从消息队列中获取消息的操作。
WH_CBT
: 监控系统中一些特定的事件(如窗口的创建、销毁、最小化、最大化等)。
lpfn
(挂钩回调函数): 挂钩处理程序的指针,定义了挂钩触发时要执行的逻辑。这个回调函数将由系统在挂钩事件发生时调用。其签名通常是这样的:
1 2 3 4 5
| LRESULT CALLBACK HookProc( int nCode, WPARAM wParam, LPARAM lParam );
|
挂钩函数可以对系统消息进行修改或传递给下一个挂钩函数。
hMod
(模块句柄): 指向包含 lpfn
钩子函数的模块句柄(即DLL或EXE的实例句柄)。如果挂钩是在一个动态链接库(DLL)中实现的,就需要提供这个 DLL 的 HINSTANCE
,否则可以传入 NULL
表示当前进程的实例句柄。
- 如果
dwThreadId
参数指定了 0,则 hMod
必须为挂钩所在的 DLL 模块句柄。
- 如果是线程挂钩,则可以传递
NULL
。
dwThreadId
(线程标识符): 指定要挂钩的线程的标识符(Thread ID
)。有以下两种用法:
- 如果指定一个线程 ID,则挂钩只在该线程的上下文中生效,这是一个线程级挂钩。
- 如果设置为 0,则挂钩将在全局范围内生效,处理系统中的所有消息。
返回值:
成功时,返回挂钩的句柄(HHOOK
);失败时,返回 NULL
,可以使用 GetLastError
获取错误信息。
代码demo:
设置Hook的Exe代码:
首先输入一个线程的ID号,因为如果SetWindowsHookEx
的最后一个参数不是0(全局挂钩),那么就是挂钩指定的线程,在这里我们加载我们写的Dll,获取dll里面导出的函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| #include "Main.h" #include "iostream" DWORD TestHook(DWORD Thread_Id) { HMODULE hMessageDll = LoadLibraryA("MessageDll.dll"); if (!hMessageDll) { printf("MessageDll模块Load失败\n"); return -1; } typedef void(*PSETHOOK)(DWORD Thread_Id); PSETHOOK SetHook = (PSETHOOK)GetProcAddress(hMessageDll, "SetHook"); if (!SetHook) { printf("SetHook函数获取失败\n"); return -1; } typedef void(*PUNHOOK)(); PUNHOOK UnHook = (PUNHOOK)GetProcAddress(hMessageDll, "UnHook"); if (!UnHook) { printf("UnHook函数获取失败\n"); return -1; }
HWND hwnd = FindWindow(NULL, TEXT("test1")); if (!hwnd) { printf("窗口获取失败\n"); return -1; }
SetHook(Thread_Id); SendMessage(hwnd, WM_USER + 0x1, (WPARAM)100, (LPARAM)200); UnHook(); return 0; }
int main() { DWORD Thread_Id; std::cin >> Thread_Id; TestHook(Thread_Id); getchar(); }
|
Dll的代码:
SetWindowsHookEx
和UnhookWindowsHook
和MessageHookProc
必须写在dll里面
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
| #define _CRT_SECURE_NO_WARNINGS #include <Windows.h> #include <stdio.h>
HHOOK g_HookProc;
LRESULT CALLBACK MessageHookProc(int nCode, WPARAM wParam, LPARAM lParam); extern "C" void __declspec(dllexport) SetHook(); extern "C" void __declspec(dllexport) UnHook();
BOOL APIENTRY DllMain( HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved ) { switch (ul_reason_for_call) { case DLL_PROCESS_ATTACH: case DLL_THREAD_ATTACH: case DLL_THREAD_DETACH: case DLL_PROCESS_DETACH: break; } return TRUE; }
extern "C" void __declspec(dllexport) SetHook(DWORD Thread_Id) { g_HookProc = SetWindowsHookExA(WH_CALLWNDPROC, MessageHookProc, GetModuleHandle(TEXT("MessageDll.dll")), Thread_Id); }
extern "C" void __declspec(dllexport) UnHook() { if (g_HookProc) UnhookWindowsHookEx(g_HookProc); }
LRESULT CALLBACK MessageHookProc(int nCode, WPARAM wParam, LPARAM lParam) { if (nCode == HC_ACTION) { PCWPSTRUCT pcw = (PCWPSTRUCT)lParam; if (pcw->message == WM_USER + 0x1) { char szBuf[200] = { 0 }; sprintf(szBuf, "wParam: %d\n,lParam: %d\nPID: %d\n", pcw->wParam, pcw->lParam, GetCurrentProcessId()); MessageBoxA(NULL, szBuf, "[contorl]", MB_OK); } } return CallNextHookEx(g_HookProc, nCode, wParam, lParam); }
|
如何调试设置的HookProc
当在 Visual Studio 中启动调试,或附加到一个进程时,调试器会查找与加载的 DLL 文件匹配的调试符号(PDB 文件)。PDB 文件包含了源码的位置信息、函数名称、变量等调试信息。
调试器会检查目标程序加载的所有模块(EXE 和 DLL),并将这些模块与相应的 PDB 文件进行匹配。
如果是无模块注入,那就不太懂了,感觉vs不能识别到
例如想要在test1.exe下钩子
附加到test1.exe上
但是我发现,挂完钩子,但是断点好像没有马上被断下来
而是等我用SendMessage
给test1.exe发完消息之后,才会断下来,这就有点奇怪了,但是总之是断下来了