Windows钩子简介
Windows钩子技术是一种Windows操作系统提供的机制,允许程序在特定事件发生时拦截并处理操作系统消息、事件或其他程序的输入和输出。钩子可以被用来监视和记录用户的操作、增加或修改某些功能,或者对某些事件作出反应,以此来实现各种自动化和定制化的需求。
钩子技术可以通过两种方式实现:全局钩子和局部钩子。全局钩子会拦截所有应用程序的消息和事件,而局部钩子则只拦截指定的应用程序或线程的消息和事件。
如果是全局钩子或者跨进程钩子,钩子的回调函数需要写在.dll
动态链接库文件中,在使用SetWindowsHookEx
函数为进程挂载钩子后,被挂载钩子的进程会去加载内存中的.dll
文件,这时.dll
文件中的函数可以共享该进程中的所有资源(dll注入原理)。
钩子是依附于线程的,而消息循环是线程处理消息的机制,因此必须在安装钩子的主程序所在的线程中创建消息循环。在安装钩子后,当Windows操作系统检测到需要被钩子处理的事件时,将会把事件信息封装成消息发送到安装钩子的主程序的消息队列中,安装钩子的主程序需要在消息循环中接收并处理这些消息,这样才能够完成钩子的处理过程。
此外,安装钩子的主程序需要运行在一个独立的线程中,这样可以防止钩子处理逻辑阻塞其他线程的执行。在这种情况下,线程的主循环必须调用 GetMessage
或 PeekMessage
函数以等待消息的到来。因此,必须在主程序中创建一个消息循环以便及时接收和处理钩子的消息,从而完成钩子的功能。
下面是一个跨进程钩子的举例
dll文件
#include <windows.h>
#include <iostream>
#define MY_API __declspec(dllexport)
extern "C" MY_API HOOKPROC getDllHookProc(VOID);
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;
}
LRESULT CALLBACK HookProc(int nCode, WPARAM wParam, LPARAM lParam)
{
// 所有小于零的是系统消息不做处理如果不放行系统消息会卡死
if (nCode >= 0)
{
std::cout << "nCode:" << nCode << std::endl;
/*
return 1;
如果返回一个非零的数则该消息无法被捕获
*/
}
// 调用下一个钩子一定要调用下一个钩子否则会卡死
return CallNextHookEx(NULL, nCode, wParam, lParam);
}
HOOKPROC getDllHookProc(VOID) {
// 返回钩子回调函数
return HookProc;
}
钩子回调函数通常有三个参数:
nCode:表示钩子代码,是一个整数值。如果小于0,则是系统消息,需要将钩子信息传递给下一个钩子,如果大于等于0,则可拦截。
wParam:表示一个消息或一个系统事件的参数,通常是一个虚拟键码或者一个句柄等。
lParam:表示一个消息或一个系统事件的参数,通常是一个指向 MSG 或 KBDLLHOOKSTRUCT 等结构体的指针。
这三个参数中,最重要的是 lParam 参数,因为它包含了所有的消息和事件信息,通常需要对其进行解析,以便获取具体的信息,lParam指向的结构体不确定,和SetWindowsHookEx
设置的监听类型有关。
.exe文件
#include<Windows.h>
#include<iostream>
using namespace std;
typedef HOOKPROC(*func)(void);
int main() {
HINSTANCE hDll; //DLL句柄
// 加载dll
hDll = LoadLibrary(L"testDll.dll");
if (hDll != NULL)
{
// 这里通过窗口标题获取窗口句柄,注意FindWindow只能获取顶级窗口
HWND hwnd = FindWindow(NULL, L"记事本");
// 获取钩子回调函数
func proc = (func)GetProcAddress(hDll, "getDllHookProc");
// 安装钩子
HHOOK hook = SetWindowsHookEx(WH_KEYBOARD, proc(), hDll,
GetWindowThreadProcessId(hwnd, NULL));
// 一定要有消息循环不然消息会卡死
MSG msg;
while (GetMessage(&msg, NULL, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
// 卸载钩子
UnhookWindowsHookEx(hook);
}
return 0;
}
SetWindowsHookEx函数
参数解释如下:
idHook: 要安装的钩子的类型。可以是下列值之一:
WH_CALLWNDPROC: 监听全局或局部的窗口消息,每次消息被发送到窗口过程时触发。
WH_CALLWNDPROCRET: 监听全局或局部的窗口消息,每次窗口过程处理完消息后触发。
WH_CBT: 监听全局或局部的窗口事件,如窗口创建、销毁、移动、大小调整等。
WH_KEYBOARD: 监听全局或局部的键盘输入事件。
WH_MOUSE: 监听全局或局部的鼠标事件。
WH_MSGFILTER: 监听全局或局部的窗口消息,包括按键、鼠标、定时器等。
WH_SHELL: 监听全局的 shell 事件。
WH_SYSMSGFILTER: 监听全局的窗口消息,包括键盘、鼠标、定时器等。
WH_FOREGROUNDIDLE: 监听全局的空闲时间。
WH_JOURNALRECORD: 监听全局的系统事件记录。
WH_JOURNALPLAYBACK: 监听全局的系统事件回放。
WH_DEBUG: 监听全局的调试事件。
WH_MOUSE_LL: 监听全局的鼠标事件,使用鼠标低级别钩子。
WH_KEYBOARD_LL: 监听全局的键盘事件,使用键盘低级别钩子。
等等
lpfn: 指向钩子处理函数的指针。钩子过程的参数和返回值的类型取决于 idHook 的值。
hMod: 包含钩子处理函数的 DLL 的句柄。如果钩子过程在钩子处理函数所在的进程中,则为 NULL。
dwThreadId: 线程标识符,标识接收钩子信息的线程。如果为 0,则钩子过程与所有线程共享。
SetWindowsHookEx 函数成功后返回钩子的句柄。可以使用返回的句柄来卸载钩子。
需要注意的是,钩子处理函数应该尽快完成处理,以免拖慢系统性能。此外,由于全局钩子会监视所有进程的事件,可能会产生安全风险,因此应该谨慎使用。
消息循环
GetMessage()
:从消息队列中取得一个消息,如果队列为空,则等待直到有消息到来。
TranslateMessage()
:将一个虚拟键消息翻译为字符消息,并将其放回消息队列中。
DispatchMessage()
:将一个消息派发给相应的窗口过程函数或者钩子函数。
这三个函数通常一起使用,构成了一个消息循环。当应用程序启动后,会创建一个消息队列,然后调用 GetMessage() 函数等待消息到来,一旦消息到来,就将其交给 TranslateMessage() 函数进行翻译,最后交给 DispatchMessage() 函数进行处理。这样循环进行,直到 GetMessage() 函数返回 0,表示退出消息循环。在消息循环中,每个消息都会依次被处理,直到消息队列为空。
说在最后
安全申明:本人才疏学浅,若有任何谬误,欢迎指正