钩子可以用来截获系统中的消息流 通过SetWindowsHookEx函数定义了监视函数的位置和监视消息的类型,这样,每当发生我们感兴趣的消息时,Windows就会将消息发送给监视函数,监视函数是一个处理消息的回调函数,也称为“钩子函数”。(会影响系统的性能)
局部钩子仅钩挂属于自身进程的事件;(SetWindowsHook)
远程钩子分两种:基于线程的和系统范围的(包括自身)。
1 基于线程的远程钩子用来捕获其他进程中某一特定线程的事件;
2 系统范围的远程钩子将捕捉系统中所有进程中发生的事件消息。
钩 子 名 称 |
监视消息的类型和时机 |
WH_CALLWNDPROC |
每当调用SendMessage函数时,函数将消息发送给目标窗口过程前首先调用钩子函数 |
WH_CALLWNDPROCRET |
每当调用SendMessage函数时,函数将消息发送给目标窗口过程后再调用钩子函数 |
WH_GETMESSAGE |
每当调用GetMessage或PeekMessage函数时,函数从程序的消息队列中获取一个消息后调用钩子函数 |
WH_KEYBOARD |
每当调用GetMessage或PeekMessage函数时,如果从消息队列中得到的是WM_KEYUP或WM_KEYDOWN消息,则调用钩子函数(键盘消息) |
WH_MOUSE |
每当调用GetMessage或PeekMessage函数时,如果从消息队列中得到的是鼠标消息,则调用钩子函数(鼠标消息) |
WH_HARDWARE |
每当调用GetMessage或PeekMessage函数时,如果从消息队列中得到的是非鼠标和键盘消息,则调用钩子函数 |
WH_MSGFILTER |
当用户对对话框、菜单和滚动条有所操作时,系统在发送对应的消息之前调用钩子函数,这种钩子只能是局部的 |
WH_SYSMSGFILTER |
同WH_MSGFILTER,不过是系统范围的 |
WH_SHELL |
当Windows shell程序准备接收一些通知事件前调用钩子函数,如shell被激活和重画等 |
WH_DEBUG |
用来给其他钩子函数除错(调试) |
WH_CBT |
当基于计算机的训练(CBT)事件发生时调用钩子函数 |
WH_JOURNALRECORD |
日志记录钩子,用来记录发送给系统消息队列的所有消息 只能全局 |
WH_JOURNALPLAYBACK |
日志回放钩子,用来回放日志记录钩子记录的系统事件 只能全局 |
WH_FOREGROUNDIDLE |
系统空闲钩子,当系统空闲的时候调用钩子函数,这样就可以在这里安排一些优先级很低的任务 |
远程钩子的钩子函数必须位于一个动态链接库中,而且必须是共享数据段的动态链接库
钩子程序一般包括3个功能模块:
(1)主程序——用来实现界面或者其他功能。
(2)钩子回调函数——用来接收系统发过来的消息。
(3)钩子的安装和卸载程序。
键盘钩子示例 -(链接库)
<HookDll.asm>
.386 .model flat, stdcall option casemap :none ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ; Include 文件定义 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> include windows.inc include user32.inc includelib user32.lib include kernel32.inc includelib kernel32.lib ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> .data hInstance dd ? .data? hWnd dd ? hHook dd ? dwMessage dd ? szAscii db 4 dup (?) ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> .code ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> DllEntry proc _hInstance,_dwReason,_dwReserved Push _hInstance pop hInstance mov eax,TRUE ret DllEntry Endp ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ; 键盘钩子回调函数 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> HookProc proc _dwCode,_wParam,_lParam local @szKeyState[256]:byte invoke CallNextHookEx,hHook,_dwCode,_wParam,_lParam invoke GetKeyboardState,addr @szKeyState invoke GetKeyState,VK_SHIFT mov @szKeyState + VK_SHIFT,al mov ecx,_lParam shr ecx,16 invoke ToAscii,_wParam,ecx,addr @szKeyState,addr szAscii,0 mov byte ptr szAscii [eax],0 invoke SendMessage,hWnd,dwMessage,dword ptr szAscii,NULL xor eax,eax ret HookProc endp ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ; 安装钩子 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> InstallHook proc _hWnd,_dwMessage push _hWnd pop hWnd push _dwMessage pop dwMessage invoke SetWindowsHookEx,WH_KEYBOARD,addr HookProc, hInstance,NULL mov hHook,eax ret InstallHook endp ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ; 卸载钩子 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> UninstallHook proc invoke UnhookWindowsHookEx,hHook ret UninstallHook endp ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> End DllEntry
<HookDll.def>文件中包括了它们的名称:
EXPORTS HookProc
InstallHook
UninstallHook
invoke SetWindowsHookEx,idHook,lpHookProc,hInstance,dwThreadID .if eax mov hHook,eax .endif
idHook参数指定钩子的类型
hInstance 指定钩子回调函数所在DLL的实例句柄
dwThreadID是安装钩子后想监控的线程的ID号 指定为NULL会被解释成系统范围的
<Main.asm>
.386 .model flat, stdcall option casemap :none ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ; Include 文件定义 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> include windows.inc include user32.inc includelib user32.lib include kernel32.inc includelib kernel32.lib include Hookdll.inc includelib Hookdll.lib ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ; Equ 等值定义 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ICO_MAIN equ 1000 DLG_MAIN equ 1000 IDC_TEXT equ 1001 WM_HOOK equ WM_USER + 100h ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ; 代码段 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> .code ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> _ProcDlgMain proc uses ebx edi esi hWnd,wMsg,wParam,lParam local @dwTemp mov eax,wMsg ;******************************************************************** .if eax == WM_CLOSE invoke UninstallHook invoke EndDialog,hWnd,NULL ;******************************************************************** .elseif eax == WM_INITDIALOG invoke InstallHook,hWnd,WM_HOOK .if ! eax invoke EndDialog,hWnd,NULL .endif ;******************************************************************** .elseif eax == WM_HOOK mov eax,wParam .if al == 0dh mov eax,0a0dh .endif mov @dwTemp,eax invoke SendDlgItemMessage,hWnd,IDC_TEXT, EM_REPLACESEL,0,addr @dwTemp .else mov eax,FALSE ret .endif mov eax,TRUE ret _ProcDlgMain endp ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> start: invoke GetModuleHandle,NULL invoke DialogBoxParam,eax,DLG_MAIN,NULL, offset _ProcDlgMain,NULL invoke ExitProcess,NULL ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> end start
2. 钩子回调函数
现在回过头来看HookDll.asm程序中的钩子回调函数,回调函数的写法一般如下:
HookProc proc dwCode,wParam,lParam
invoke CallNextHookEx,hHook,_dwCode,_wParam,_lParam
;处理消息的代码
mov eax,返回值
ret
HookProc endp
对于键盘钩子来说,参数的定义如下所示。
● dwCode——键盘消息的处理方式。如果是HC_ACTION,表示收到一个正常的击键消息;如果是HC_NOREMOVE,表示对应消息并没有从消息队列中移去(当某个进程用指定PM_NOREMOVE 标志的PeekMessage函数获取消息时就是如此)。
● wParam——按键的虚拟码(即Windows.inc中定义的VK_xxx值)。
● lParam——按键的重复次数、扫描码和标志等数据,不同数据位的定义如下:
■ 位0~15:按键的重复次数。
■ 位16~23:按键的扫描码。
■ 位24:按键是否是扩展键(F1与F2等Fx键,小键盘数字键等),如果此位是1表示按键是扩展键。
■ 位25~28:未定义。
■ 位29:如果Alt键在按下状态,此位置1,否则置0。
■ 位30:按键的原先状态,消息发送前按键原来是按下的,此位被设置为1,否则置0。
■ 位31:按键的当前动作,如果是按键按下,那么此位被设置为0;按键释放的话被设置为1
日志记录钩子
日志记录钩子是一种特殊的钩子,说它特殊是因为它是远程钩子,却不用放在动态链接库中,这就为监视系统范围的消息提供了方便。
日志钩子HOOK键盘<RecHook.asm>
.386 .model flat, stdcall option casemap :none ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ; Include 文件定义 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> include windows.inc include user32.inc includelib user32.lib include kernel32.inc includelib kernel32.lib ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ; Equ 等值定义 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ICO_MAIN equ 1000 DLG_MAIN equ 1000 IDC_TEXT equ 1001 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ; 数据段 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> .data? hInstance dd ? hWinMain dd ? hHook dd ? szAscii db 32 dup (?) ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ; 代码段 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> .code ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ; 钩子回调函数 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> HookProc proc _dwCode,_wParam,_lParam local @szKeyState[256]:byte invoke CallNextHookEx,hHook,_dwCode,_wParam,_lParam pushad .if _dwCode == HC_ACTION mov ebx,_lParam assume ebx:ptr EVENTMSG .if [ebx].message == WM_KEYDOWN invoke GetKeyboardState,addr @szKeyState invoke GetKeyState,VK_SHIFT mov @szKeyState + VK_SHIFT,al mov ecx,[ebx].paramH shr ecx,16 invoke ToAscii,[ebx].paramL,ecx, addr @szKeyState,addr szAscii,0 mov byte ptr szAscii [eax],0 .if szAscii == 0dh mov word ptr szAscii+1,0ah .endif invoke SendDlgItemMessage,hWinMain,IDC_TEXT, EM_REPLACESEL,0,addr szAscii .endif assume ebx:nothing .endif popad ret HookProc endp ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> _ProcDlgMain proc uses ebx edi esi hWnd,wMsg,wParam,lParam mov eax,wMsg ;******************************************************************** .if eax == WM_CLOSE invoke UnhookWindowsHookEx,hHook invoke EndDialog,hWnd,NULL ;******************************************************************** .elseif eax == WM_INITDIALOG push hWnd pop hWinMain invoke SetWindowsHookEx,WH_JOURNALRECORD, addr HookProc,hInstance,NULL .if eax mov hHook,eax .else invoke EndDialog,hWnd,NULL .endif ;******************************************************************** .else mov eax,FALSE ret .endif mov eax,TRUE ret _ProcDlgMain endp ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> start: invoke GetModuleHandle,NULL mov hInstance,eax invoke DialogBoxParam,eax,DLG_MAIN,NULL, offset _ProcDlgMain,NULL invoke ExitProcess,NULL ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> end start
由于不再需要动态链接库了,钩子回调函数HookProc被移到了主程序中,也取消了InstallHook和UninstallHook两个子程序,相应的内容直接放在WM_INITDIALOG和WM_CLOSE消息中完成。在WM_INITDIALOG消息中用下面的语句完成对钩子的安装:
invoke SetWindowsHookEx,WH_JOURNALRECORD,addr HookProc,hInstance,NULL
程序比较重要的一个不同点在于日志钩子回调函数的参数定义不同,在这里dwCode的参数定义如下:
● HC_ACTION——系统准备从消息队列中移去一条消息,消息的具体信息由lParam参数中指定的EVENTMSG结构定义。
● HC_SYSMODALOFF——某个系统模态对话框准备被关闭。
● HC_SYSMODALON——某个系统模态对话框准备被建立
我们关心的是HC_ACTION标志,这时lParam参数指向一个EVENTMSG结构,其定义为:
EVENTMSG STRUCT message DWORD ? ;消息队列中将要移去的消息ID paramL DWORD ? ;消息的wParam参数 paramH DWORD ? ;消息的lParam参数 time DWORD ? ;消息发生的事件 hwnd DWORD ? ;消息对应的窗口句柄 EVENTMSG ENDS
由于日志记录钩子可以截获的不仅是键盘消息,也有鼠标等其他消息,所以需要有个地方指定消息类型,通过检测EVENTMSG结构中的消息ID字段就可以得知截获的究竟是什么消息。(如按键消息ID WM_KEYDOWN)