消息钩子
Windows操作系统向用户提供GUI(图形用户界面),以事件驱动(Event Driven)方式工作。在操作系统中借助键盘、鼠标、选择菜单、按钮、鼠标移动、改变窗口大小与位置等都是事件(Event)发生事件时,OS会把事先定义好的消息发送给相应的应用程序,应用程序分析收到的信息后执行相应操作。以键盘消息为例。
常规Windows消息流。
#发生键盘输入事件时,WM_KEYDOWN消息被添加到[OS message queue]。
#OS判断哪个应用程序中发生了事件,然后从[OS message queue]取出消息,添加到相应应用程序的 [application message queue]中。
#应用程序监视自身的[application message queue],发现新添加的WM_KEYDOWN消息后,调用相应的事件 处理程序处理。
如图所示,OS消息队列与应用程序消息队列之间存在一条“钩链”(Hook Chain),设置好键盘消息钩子之后,处于“钩链”中的键盘消息钩子会比应用程序先看到相应信息。在键盘消息钩子函数内部,除了可以查看消息之外,还可以修改消息本身,而且还能对消息实施拦截,组织消息传递。
MS Visual Studio中提供的SPY++是一个十分强大的消息钩取程序,能够查看操作系统中来往的所有消息。
SetWindowsHookEx()
使用SetWindowsHookEx()API可轻松实现消息钩子,SetWindowsHookEx()API的定义如下所示。
HHOOK SetWindowsHookEx(int idHook, //钩子类型HOOKPROC lpfn, //钩子函数HINSTANCE hMod, //包含钩子函数模块的句柄DWORD dwThreadId //想要挂钩的进程ID);
钩子过程(hook procedure)是由操作系统调用的回调函数。安装消息“钩子”时,“钩子”过程需要存在于某个DLL内部,且该DLL的示例句柄(instance handle)即是hMod。
提示:若dwThreadID参数被设置为0,则安装的钩子为“全局钩子”(Global Hook),它会影响到运行中的(以及以后要运行的)所有进程。
使用SetWindowsHookEx()设置好钩子后,在某个进程中生成指定消息时,操作系统会将相关的DLL文件强制注入(injection)相应进程,然后调用注册的“钩子”过程。注入进程时用户几乎不需要做什么,十分方便。
键盘消息钩取练习
![UC31(4MV9O~H~J$1$((]D9E.png](/uploads/projects/c3h7n3o9@cm7byl/af0e10544d7c594c0c8a7cb7630c84ac.png)
KeyHook.dll文件是一个含有钩子过程(KeyboardProc)的DLL文件。HookMain.exe是最先加载SetWindowsHookEx()安装键盘钩子(KeyboardProc)。若其他进程(explore.exe、iexplorr.exe、notepad.exe等)中发生键盘输入事件,OS就会强制将KeyHook.dll加载到相应进程的内存,然后调用KeyboardProc()函数。
需注意,OS会将KeyHook.dll强制加载到发生键盘输入事件的所有进程。换言之,消息钩取技术常常被用作一种DLL注入技术。
分析源码
WinXP/7中可用。
HookMain.cpp
//HookMain#include "stdio.h"#include "windows.h"//Console Input/Output,定义了通过控制台进行数据输入和数据输出的函数//主要是一些用户通过按键盘产生的对应操作,比如getch()函数等等#include "conio.h"//定义一些常量#define DEF_DLL_NAME "KeyHook.dll"#define DEF_HOOKSTART "HookStart"#define DEF_HOOKSTOP "HookStop"//定义两个参数为空、返回值为void即没有的函数指针typedef void (*PFN_HOOKSTART)();typedef void (*PFN_HOOKSTOP)();void main(){//定义及初始化句柄变量和函数指针HMODULE hDll = NULL;PFN_HOOKSTART HookStart = NULL;PFN_HOOKSTOP HookStop = NULL;//加载KeyHook.dllhDll = LoadLibraryA(DEF_DLL_NAME);//若加载不成功,则输出错误信息if( hDll == NULL ){printf("[-]无法加载%s [%d]\n", DEF_DLL_NAME, GetLastError());return;}//获取导出函数地址HookStart = (PFN_HOOKSTART)GetProcAddress(hDll, DEF_HOOKSTART);HookStop = (PFN_HOOKSTOP)GetProcAddress(hDll, DEF_HOOKSTOP);//开始钩取HookStart();//直至用户输入“q”退出钩取printf("[*]等待输入 'q' 来停止钩取...\n");while( _getch() != 'q' );//终止钩取HookStop();//卸载KeyHook.dllFreeLibrary(hDll);}
源码非常简单。先加载KeyHook.dll文件,然后调用HookStart()函数开始钩取,用户输入“q”时,调用HookStop()函数终止钩取。
KeyHook
//KeyHook.cpp#include "stdio.h"#include "windows.h"//定义目标进程名为notepad.exe#define DEF_PROCESS_NAME "notepad.exe"//定义全局变量HINSTANCE g_hInstance = NULL;HHOOK g_hHook = NULL;//DllMain()函数在DLL被加载到进程后会自动执行BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD dwReason, LPVOID lpvReserved){switch( dwReason ){case DLL_PROCESS_ATTACH:g_hInstance = hinstDLL;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 => press, 1 => releaseif( !(lParam & 0x80000000) ){GetModuleFileNameA(NULL, szPath, MAX_PATH);p = strrchr(szPath, '\\');//比较当前进程名称,若为notepad.exe,则消息不会传递给应用程序或下一个钩子函数//_stricmp()函数用于比较字符串,i表示不区分大小写,若两个值相等则返回0if( !_stricmp(p + 1, DEF_PROCESS_NAME) ){return 1;}}}//比较当前进程名称,若非notepad.exe,则消息传递给应用程序或下一个钩子函数return CallNextHookEx(g_hHook, nCode, wParam, lParam);}//在C++中调用C的库文件,用extern "C"告知编译器,因为C++支持函数重载而C不支持,两者的编译规则不同#ifdef __cplusplusextern "C"{#endif//__declspec,针对编译器的关键字,用于指出导出函数//当调用导出函数HookStart()时,SetWindowsHookEx()函数就会将KeyboardProc()添加到键盘钩链__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
调用导出函数HookStart()时,SetWindowsHookEx()函数就会将KeyboardProc()添加到键盘钩链。
