“钩子是Windows的消息监视点,应用程序可以在这里安装一个监视子程序,这样就可以在系统中的消息流到达目的窗口过程前监控它们“
上面就是WIN32API手册中对钩子的描述。大概就是说钩子可以用来截获系统的消息。那么,要写一个钩子程序,肯定要先确定钩子的类型,钩子是有很多类型的,不同类型的钩子可以监视不同类型的消息。
钩子分为局部钩子和远程钩子,局部钩子只能属于自身进程的事件,而远程钩子的除了可以钩到自己的还可以钩到其他进程里面的消息,远程的钩子又分为两种,基于线程的和系统范围的。基于线程的远程钩子用来捕获其他进程中某一特定的事件,而系统范围的远程钩子可以捕捉系统中所有进程中发生的事件消息。
局部的和远程的钩子还有一个不同的地方就是局部的钩子的钩子函数可以安装在进程中,而远程的钩子的钩子函数自能安装在DLL中。因为远程钩子要获得其他进程的消息,如果安装在本进程中,又因为进程之间的地址空间是隔离开来的,这样钩子就无法监视其他的进程的消息了。而DLL是可以插入到其他进程的地址空间中去的,所以远程钩子一般放在DLL中。但是也有两个例外,分别是日志记录钩子和日志回放钩子。这两个明明是远程钩子,但是它们的钩子函数可以安装在进程里面,并且不碍事。可能是因为这两个钩子函数要监视一些比较底层的消息,所以函数的调用可能是从Windows内部发起的。
那么,了解了一些钩子的基本概念之后,我们就可以开始准备写钩子程序了。所以来看一下一般的钩子程序的结构:
主程序:用来实现界面或者其他功能钩子回调函数:用来接收系统发过来的消息钩子的安装和卸载模块
对于局部钩子来说,这三个模块可以都在一个可执行文件中。但是对于远程钩子来说,第二部分必须放在DLL中。
先来看一下DLL的汇编源代码:
.386.modelflat,stdcalloption casemap:none;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>;Include;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>includewindows.incincludeuser32.incincludelibuser32.libincludekernel32.incincludelibkernel32.lib;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>;数据段;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>.datahInstancedd?;动态链接库模块实例句柄.data?hWnddd?;接收消息的窗口的句柄 hHookdd?;钩子句柄dwMessagedd?;要发送的消息的类型szAsciidb4dup (?);按键的ASCII码;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>;代码段;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>.codeDllEntryproc_hInstance,_dwReason,_dwReserved;DLL入口push_hInstancepophInstancemoveax,TRUEretDllEntryendp;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>;键盘钩子回调函数;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>HookProcproc_dwCode,_wParam,_lParam;消息的处理方式,按键的虚拟码,按键的其他信息local@szKeyState[256]:byteinvokeCallNextHookEx,hHook,_dwCode,_wParam,_lParam;调用下一个钩子的函数invokeGetKeyboardState,addr @szKeyState;获取键盘所有按键的当前状态invokeGetKeyState,VK_SHIFT;获取SHIFT键的当前状态mov@szKeyState + VK_SHIFT,almovecx,_lParamshrecx,16invokeToAscii,_wParam,ecx,addr @szKeyState,addr szAscii,0;将按键信息转换为ASCII码,并返回szAscii缓冲区中的字符的数量movbyte ptr szAscii [eax],0;在缓冲区末尾加上字符个数invokeSendMessage,hWnd,dwMessage,dword ptr szAscii,NULLxoreax,eax;返回0,表示将消息转发给目标窗口(不是我们自己的窗口)retHookProcendp;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>;安装钩子;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>InstallHookproc_hWnd,_dwMessage;自己的窗口的句柄,自定义的消息类型push_hWndpophWndpush_dwMessagepopdwMessageinvokeSetWindowsHookEx,WH_KEYBOARD,addr HookProc,\;安装钩子函数,钩子类型,回调函数,DLL句柄,监控所有进程hInstance,NULLmovhHook,eaxretInstallHookendp;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>;卸载钩子;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>UninstallHookprocinvokeUnhookWindowsHookEx,hHookretUninstallHookendp;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>EndDllEntry
可以看到我们把安装和卸载钩子的模块也放到了钩子回调函数的模块里面。因为钩子句柄在钩子回调函数和卸载钩子的时候都要用到,而钩子句柄是在安装钩子的时候得到的,为了方便就把它们放一个模块了。
SetWindowsHookEx:
invoke SetWindowsHookEx,idHook,lpHookProc,hInstance,dwThreadID.if eaxmov hHook,eax.endif
该函数安装一个钩子。第一个参数指出钩子类型,第二个参数指出钩子回调函数的地址,第三个参数指出DLL的实例句柄,第四个参数指出钩子想监视的线程ID,ID可以指定为本进程的线程ID和其他进程的线程ID。如果这个参数为NULL,那么这个钩子会被解释成系统范围的,可以用来监视所有的进程以及它们的线程。
钩子安装成功返回一个钩子的句柄。
UnhookWindowsHookEx:
invoke UnhookWindowsHookEx,hHook
该函数卸载钩子,参数是钩子的句柄。
接下来就是最重要的钩子回调函数了。键盘钩子回调函数的一般结构是这样写的:
HookProc proc dwCode,wParam,lParaminvoke CallNextHookEx,hHook,_dwCode,_wParam,_lParam;处理消息的代码moveax,返回值retHookProc endp
一般是这么写的,当然只要能实现功能,不这么写也行。但是函数的格式是有要求的的,必须得是三个参数。三个参数的含义如下:
dwCode:键盘消息的处理方式。如果是HC_ACTION,表示收到一个正常的击键消息,如果是HC_MOREMOVE,表示对应消息并没有从消息队列中移去。wParam:按键的虚拟码lParam:按键的重复次数,扫描码和标志等数据,不同数据位的定义如下: 0~15位:按键的重复次数。16~23位:按键的扫描码。24位:按键是否是扩展键(如:F1和F2等,还有小数字键盘等),如果此位是1表示按键时扩展键。25~28:未定义。29位:如果Alt键在按下状态,此位置为1,否则为0。30位:按键的原先状态,消息发送前按键原来是按下的,此键为1,否则为0。31位:按键当前的动作,如果是按键按下,那么此位被设置为0,按键释放的话被设置为1。
然后这里还要介绍一个很重要的概念就是:钩子链。
WIndows中可以存在多个同种类型的钩子。这些钩子组成一个钩子链,最近加入的钩子放在链表的头部,Windows负责为每种钩子维护一个钩子链。当一个事件发生的时候,WIndows调用最后安装的钩子,然后由这个钩子的回调函数发起调用下一个钩子的动作,Windows收到这个动作后后,再从链表中取出下一个钩子的地址并将调用传递下去。
大概就是说同种类型的钩子在一条链子上,然后消息会依次从最后面那个钩子开始向前传,但这个动作需要我们在回调函数中完成。因为你不知道你的钩子是什么时候被放到钩子链里面的,所以我们最好还是把消息传递下去。
CallNextHookEx:
invoke CallNextHookEx,hHook,dwCode,wParam,lParam
这个函数就是调用下一个钩子的函数,第一个参数是当前钩子的句柄,后面三个参数就是当前钩子回调函数的三个参数。
我们写的这个是一个键盘钩子,所以我们要对获取的键盘消息先进行一个处理,然后再发给我们的主程序。先调用GetKeyboardState函数获取当前键盘的状态,然后因为我们获取的消息是按键的虚拟码和扫描码,我们要显示字符就得先把这些转换为ASCII码。ToAscii函数可以完成这个工作。最后我们再调用SendMessage函数将转换后的按键信息发送给我们的主窗口,主窗口的句柄是在主窗口调用DLL中的钩子安装程序的时候传递到DLL中的。消息类型是我们自己定义的。
然后钩子的DLL就写完了,但是我们在编译链接DLL的时候要注意一个地方就是,链接选项要指定是共享数据段的。所以链接的选项要使用/section:.bss,S
做完这些后就只剩下主窗口的程序了。主窗口程序的结构就比较简单了,初始化的时候调用一下钩子安装函数,然后等待钩子消息来,有消息就显示到窗口,退出的时候就调用钩子卸载函数。
下面是主窗口程序的资源文件:
//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>#include<resource.h>//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>#defineICO_MAIN0x1000#defineDLG_MAIN0x1000#defineIDC_TEXT0x1001//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>ICO_MAINICON"Main.ico"//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>DLG_MAINDIALOG208,130,234,167STYLEDS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENUCAPTION"键盘钩子"FONT 9,"宋体"BEGINEDITTEXT IDC_TEXT,5,5,224,158,ES_MULTILINE | ES_AUTOVSCROLL | WS_BORDER | WS_VSCROLL | WS_TABSTOP | ES_READONLYEND
下面是汇编源代码:
.386.modelflat,stdcalloptioncasemap:none;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>;Include??t?¨?;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>includewindows.incincludeuser32.incincludelibuser32.libincludekernel32.incincludelibkernel32.libincludeHookdll.incincludelibHookdll.lib;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>;Equ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>ICO_MAINequ1000hDLG_MAINequ1000hIDC_TEXTequ1001hWM_HOOKequWM_USER + 100h;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>;′ú???;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>.code_ProcDlgMainprocuses ebx edi esi hWnd,wMsg,wParam,lParamlocal@dwTempmoveax,wMsg.ifeax == WM_CLOSEinvokeUninstallHookinvokeEndDialog,hWnd,NULL.elseifeax == WM_INITDIALOGinvokeInstallHook,hWnd,WM_HOOK.if! eaxinvokeEndDialog,hWnd,NULL.endif.elseifeax == WM_HOOKmoveax,wParam.ifal == 0dhmoveax,0a0dh.endifmov@dwTemp,eaxinvokeSendDlgItemMessage,hWnd,IDC_TEXT,\EM_REPLACESEL,0,addr @dwTemp.else moveax,FALSEret.endifmoveax,TRUEret_ProcDlgMainendp;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>start:invokeGetModuleHandle,NULLinvokeDialogBoxParam,eax,DLG_MAIN,NULL,\offset _ProcDlgMain,NULLinvokeExitProcess,NULLend start
编译链接出来后运行:
然后我们随便在什么地方输入键盘:
除了一些特殊的字符显示不出来,勉强还是能看的。
如果觉得《自己写一个键盘钩子程序来监视键盘输入》对你有帮助,请点赞、收藏,并留下你的观点哦!