Loading... **消息钩子** Windows操作系统向用户提供GUI(Graphic User Interface,图形用户界面),它以事件驱动(Event Driven)方式工作。在操作系统中借助键盘、鼠标、选择菜单、按钮、以及移动鼠标、改变窗口大小与位置等都是事件(Event)。发生这样的事件时,OS会把事先定义好的消息发送给相应的应用程序,应用程序分析收到的信息后执行相应的操作。也就是说,敲击键盘时,消息会从OS移动到应用程序。所谓的“消息钩子”就在此间偷看这些信息。 如下图,OS消息队列与应用程序消息队列之间存在一条“钩链”(Hook Chain),如果设置好键盘消息钩子之后,处于“钩链”中的键盘消息钩子会比应用程序先看到相应信息。**在键盘消息钩子函数的内部,除了可以查看消息之外,还可以修改消息本身,而且还能对消息事实拦截,阻止消息传递。** ![image.png](http://47.117.131.13/usr/uploads/2021/08/3727960515.png) **SetWindowsHookEx()** 使用SetWindowsHookEx()API可轻松实现消息钩子,SetWindowsHookEx()API的定义如下: ``` HHOOK SetWindowsHookEx( int idHook, //hook type HOOKPROC lpfn, //hook procedure HINSTANCE hMnd, //hook procedure所属的DLL句柄(Handle) DWORD dwThreadId //想要挂钩的线程ID;若设置为0,则安装的钩子为'全局钩子'(Global hook),它会影响到运行中的(以及以后要运行的)所有进程 ); ``` 钩子过程(hook procedure)是由操作系统调用的回调函数。安装消息“钩子”时,“钩子”过程需要存在于某个DLL内部,且该DLL的示例句柄(instance handle)即是hMod。 像这样,使用SetWindowsEx()设置好钩子函数之后,在某个进程中生成指定消息时,操作系统会将相关的DLL文件强制注入(injection)相应进程,然后调用注册的“钩子”过程。 以键盘消息钩取为例: ![image.png](http://47.117.131.13/usr/uploads/2021/08/3886252079.png) KeyHook.dll文件是一个含有钩子过程(KeyboardProc)的DLL文件。HookMain.exe是最先加载KeyHook.dll并安装键盘钩子的程序。HookMain.exe加载KeyHook.dll文件后使用SetWindowsHookEx()安装键盘钩子(KerboardProc)。***若进程(explorer.exe、iexplore.exe、notepad.exe等)发生键盘输入事件,OS就会强制将KeyHook.dll加载到相应进程的内存,然后调用KeyboardProc()函数*。** KeyboardProc()函数中发生键盘输入事件时,就会比较当前进程的名称与“notepad++.exe”字符串,若相同,则返回1,终止KeyboardProc()函数,这意味着截获且删除消息。这样,键盘消息就不会传递到notepad++.exe程序的消息队列。因notepad++.exe未能收获到任何键盘消息,故无法输出。 若当前进程名称非“notepad++.exe”,执行`return CallNextHookEx(g_hHook, nCode, wParam, lParam);`语句,消息会被传递到另一个应用程序或钩链的另一个“钩子”函数。 HookMain.exe文件的源代码(HookMain.cpp)。 ``` #include<stdio.h> #include<conio.h> #include<windows.h> #define DEF_DLL_NAME "KeyHook.dll" #define DEF_HOOKSTART "HookStart" #define DEF_HOOKSTOP "HookStop" typedef void(*PFN_HOOKSTART)(); typedef void(*PFN_HOOKSTOP)(); int main() { HMODULE hDll = NULL; PFN_HOOKSTART HookStart = NULL; PFN_HOOKSTART HookStop = NULL; char ch = 0; //加载KeyHook.dll hDll = LoadLibraryA(DEF_DLL_NAME); //获取导出函数地址 HookStart = (PFN_HOOKSTART)GetProcAddress(hDll, DEF_HOOKSTART); HookStop = (PFN_HOOKSTOP)GetProcAddress(hDll, DEF_HOOKSTOP); //开始钩取 HookStart(); //等待直到用户输入'q' printf("press 'q' to quit!\n"); while (_getch() != 'q'); //终止钩取 HookStop(); //卸载KeyHook.dll FreeLibrary(hDll); return 0; } ``` KeyHook.dll文件的源代码(KeyHook.cpp)。 ``` // dllmain.cpp : 定义 DLL 应用程序的入口点。 #include "pch.h" #include "stdio.h" #include "windows.h" #include "tchar.h" #define DEF_PROCESS_NAME "notepad++.exe" HINSTANCE g_hInstance = NULL; HHOOK g_hHook = NULL; HWND g_hWnd = NULL; BOOL APIENTRY DllMain( HMODULE hModule, //指向DLL本身的实例句柄 DWORD ul_reason_for_call, //指明了DLL被调用的原因 LPVOID lpReserved ) { switch (ul_reason_for_call) { case DLL_PROCESS_ATTACH: {//当DLL被进程<<第一次>>调用时,导致DllMain函数被调用 g_hInstance = hModule; break; } case DLL_PROCESS_DETACH:break; } return TRUE; } LRESULT CALLBACK KeyboardProc(int nCode, WPARAM wParam, LPARAM lParam) { char szPath[MAX_PATH] = { 0, }; char* p = NULL; if (nCode == 0) { //bit 31: 0 = key press,1 = key release if (!(lParam & 0x80000000)) //释放键盘按键时 { GetModuleFileNameA(NULL, szPath, MAX_PATH); p = strrchr(szPath, '\\'); //比较当前进程名称,若为notepad.exe,则消息不会传递给应用程序(或下一个“钩子”) if (!_stricmp(p + 1, DEF_PROCESS_NAME)) return 1; } } //若非notapad.exe,则调用CallNextHookEx()函数,将消息传递给应用程序(或下一个“钩子”) return CallNextHookEx(g_hHook, nCode, wParam, lParam); } #ifdef __cplusplus extern "C" { #endif __declspec(dllexport) void HookStart() { g_hHook = SetWindowsHookEx(WH_KEYBOARD, KeyboardProc, g_hInstance, 0); } __declspec(dllexport) void HookStop() { if (g_hHook) { UnhookWindowsHookEx(g_hHook); g_hHook = NULL; } } #ifdef __cplusplus } #endif ``` 在process Explorer中检索注入KeyHook.dll的所有进程,一个进程开始运行并发生键盘事件时,KeyHook.dll就会注入其中(但根据之前的分析我们知道,此程序忽视键盘事件的仅有notepad.exe进程,其它进程会正常处理键盘事件)。 ![image.png](http://47.117.131.13/usr/uploads/2021/08/2861285952.png) 拆除键盘钩子后,相关进程就会将KeyHook.dll文件全部卸载(Unloading),notepad++.exe能正常处理键盘消息事件。 ![image.png](http://47.117.131.13/usr/uploads/2021/08/421992346.png) 参考:《逆向工程核心原理》 最后修改:2021 年 08 月 09 日 11 : 03 PM © 允许规范转载