Chinaunix首页 | 论坛 | 博客
  • 博客访问: 126234
  • 博文数量: 37
  • 博客积分: 1490
  • 博客等级: 上尉
  • 技术积分: 323
  • 用 户 组: 普通用户
  • 注册时间: 2009-12-01 16:38
文章分类

全部博文(37)

文章存档

2011年(1)

2010年(23)

2009年(13)

我的朋友

分类: WINDOWS

2009-12-28 14:52:11

一: Windows钩子
    在Windows下,我们已经知道保护模式下的中断描述符表是受系统保护的,在应用程序层次是不可能再通过修改中断向量来截获系统中断了,但这样也对一些应用造成了不便,当做一种变通的措施,Windows提供了钩子来完成类似的功能。钩子是什么呢?
    钩子是Windows的消息处理机制中的一个监视点,应用程序可以在这里安装一个监视子程序,这样就可以在系统中的消息流到达目的窗口过程前监控它们。”
    也就是说,钩子可以用来截获系统中的消息流,显然,钩子不是像截获中断一样用来随心所欲地截获系统底层功能的,那么钩子能够用来做什么事情呢?  如果把钩子用在后台执行的程序中,就能够偷偷检查任何程序中发生的WM_CHAR消息,这样用户输入的任何内容:账号、密码、情书——不管是什么,不管是否显示在屏幕上——都可以被记录下来。事实上,很多木马程序就是这样做的,像冰河一类的木马程序就可以在后台记录用户的击键并偷偷发送到人家的信箱中去。
二: 钩子的类型
      钩子是Windows消息机制中的监视点,应用程序可以在这里安装一个监视函数,这样就可以捕捉自己进程或者其他进程发生的事件。通过SetWindowsHookEx函数就可以做到这一点。SetWindowsHookEx函数定义了监视函数的位置和监视消息的类型,这样,每当发生我们感兴趣的消息时,Windows就会将消息发送给监视函数,监视函数是一个处理消息的回调函数,也称为“钩子函数”。
Windows安装的钩子有两种类型:局部的和远程的。它们处理消息的范围不同。局部钩子仅钩挂属于自身进程的事件;远程钩子除了可以钩挂自身进程的事件,还可以钩挂其他进程中发生的事件。远程钩子又分两种:基于线程的和系统范围的。基于线程的远程钩子用来捕获其他进程中某一特定线程的事件;而系统范围的远程钩子将捕捉系统中所有进程中发生的事件消息。
安装钩子会影响系统的性能,因为系统在处理所有的相关事件时都会调用钩子函数,特别是监视范围是整个系统范围的全局钩子。如果钩子函数中的处理代码过多的话,系统运行速度将会明显减慢,所以对于全局钩子一定要小心使用,不需要的时候应该立刻卸载。在 DOS操作系统下编写中断服务程序的时候,如果代码有错误的话会影响其他调用它的程序。同样,由于钩子函数可以预先截获其他进程的消息,所以一旦钩子函数存在问题的话,也会影响其他进程的运行。
可以把钩子想像成钓鱼钩,不同鱼钩用来钓的鱼是不同的,大钩钓大鱼,小钩钓小鱼,不同钩子钓的消息也是不同的,没有必要每次钓来所有的消息,根据监视的消息类型和时机的不同,钩子可以分为如表11.1所示的几种。
表1  钩子的类型
钩 子 名 称               监视消息的类型和时机
 
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         系统空闲钩子,当系统空闲的时候调用钩子函数,这样就可以在这里安排一些优先级很低的任务
在这些钩子中,有些只能当做局部钩子使用,如WH_MSGFILTER钩子;有些只能当做系统范围的远程钩子使用,如WH_JOURNALRECORD 和 WH_JOURNALPLAYBACK钩子;而大多数的钩子可以在任何范围内使用。
对于不同的钩子,由于它们处理的消息类型不同,所以钩子函数的参数定义也是不同的,在具体的编程中,需要查看Win32 API手册来了解各种钩子函数的参数定义。
另外,远程钩子和局部钩子的程序结构也是不同的。当安装了一个局部钩子时,每当指定的事件发生,Windows就可以调用进程中的钩子函数;但是若安装的是远程钩子,系统不能从其他进程的地址空间中调用钩子函数,因为两个进程的地址空间是隔离的,由于系统中只有DLL程序是可以插入到其他进程的地址空间中去的,所以远程钩子的钩子函数必须位于一个动态链接库中,而且必须是共享数据段的动态链接库(因为写远程钩子要用到动态链接库,所以本书中将两部分内容合在一章中介绍)。
但是也有两个例外:日志记录钩子和日志回放钩子虽然属于远程钩子,但是它们的钩子函数却可以放在安装钩子的程序中,并不需要单独放在一个动态链接库中。Microsoft并没有说明为什么有这样的例外,笔者认为其中的原因是这两个钩子是用来监控比较底层的硬件事件的,所以钩子函数的调用并不是从其他进程的地址空间中发起的,而是从Windows内部发起的,所以不存在不同进程之间地址空间隔离的问题。(猜想而已,如果读者有明确的资料请告知笔者。)
下面的以键盘钩子为例来说明系统范围远程钩子的安装和使用,局部钩子的使用步骤与之类似,只不过不必将钩子函数放在动态链接库中而已,使用起来更加简单,读者可以举一反三自己尝试一下。
三: 远程钩子的安装和使用
1. 钩子程序的结构
钩子程序一般包括3个功能模块:
(1)主程序——用来实现界面或者其他功能。
(2)钩子回调函数——用来接收系统发过来的消息。
(3)钩子的安装和卸载程序。
对于局部钩子来说,这些模块可以处在同一个可执行文件中。而对于远程钩子来说,第2部分必须放在一个动态链接库中,第3部分虽然没有要求,但一般也放在动态链接库中,这是因为钩子创建以后得到一个钩子句柄,这个句柄要在钩子回调函数中以及卸载钩子的时候用到,如果把这部分代码放在主程序中的话,还需要创建一个函数将它传回给动态链接库,所以还不如直接放到库中。
下面例子包括两部分文件:HookDll.asm和HookDll.def文件用来生成动态链接库;Main.asm和Main.rc是主程序部分。程序用一个系统范围的远程钩子来实现监视所有键盘输入的功能。由于安装钩子回调函数的动态链接库要求是共享数据段的,所以请读者注意Makefile中dll文件的链接选项,它使用了/section:.bss,S选项。
NAME = Main
DLL = Hookdll
ML_FLAG = /c /coff
LINK_FLAG = /subsystem:windows
DLL_LINK_FLAG = /subsystem:windows /section:.bss,S
$(DLL).dll $(NAME).exe:
$(DLL).dll: $(DLL).obj $(DLL).def
           Link  $(DLL_LINK_FLAG) /Def:$(DLL).def /Dll $(DLL).obj
$(NAME).exe: $(NAME).obj $(NAME).res
        Link  $(LINK_FLAG) $(NAME).obj $(NAME).res
.asm.obj:
            ml $(ML_FLAG) $<
.rc.res:
            rc $<
clean:
            del *.obj
            del *.res
            del *.exp
            del *.lib
HookDll.asm文件的内容如下:
.386
.model flat, stdcall
option casemap :none
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
需要共享的变量被放在 .data?段中,如钩子句柄和钩住的按键内容等,仅dll程序的实例句柄不需要共享,不需要共享的变量放在 .data段中。动态链接库的入口函数例行公事地返回了一个TRUE来表示允许被装入。程序中只写了3个函数,HookProc是钩子回调函数,InstallHook和 UninstallHook函数是供主程序使用的钩子安装函数和卸载函数。这3个函数是需要导出的,所以HookDll.def文件中包括了它们的名称:
EXPORTS     HookProc
                InstallHook
                UninstallHook
InstallHook子程序用来安装钩子,程序为它设计了两个参数:窗口句柄和自定义消息ID。动态链接库保存这两个参数,以便在钩子回调函数收到消息的时候将截获的按键通过自定义消息ID转发给父窗口,这样父窗口在初始化完成后只需要等待自定义消息ID就可以了。
在子程序中,通过SetWindowsHookEx函数安装钩子。SetWindowsHookEx函数的用法是:
   invoke  SetWindowsHookEx,idHook,lpHookProc,hInstance,dwThreadID
   .if     eax
           mov     hHook,eax
   .endif
idHook参数指定钩子的类型,它就是表11.1中列出的钩子名称。例子中要安装的是键盘钩子,所以使用WH_KEYBOARD。lpHookProc参数指出钩子回调函数的地址。
hInstance 指定钩子回调函数所在DLL的实例句柄。如果安装的是局部钩子的话,由于局部钩子的回调函数并不需要放在动态链接库中,这时这个参数就使用NULL。
dwThreadID是安装钩子后想监控的线程的ID号。该参数可以决定钩子是局部的还是系统范围的。如果参数指定的是自己进程中的某个线程ID号,那么该钩子是一个局部钩子;如果指定的线程ID是另一个进程中某个线程的ID,那么安装的钩子是一个局部的远程钩子;如果想要安装系统范围的全局钩子的话,可以将这个参数指定为NULL,这样钩子就会被解释成系统范围的,可以用来监控所有的进程及它们的线程。

   如果钩子安装成功,函数返回钩子句柄,否则返回NULL。钩子句柄必须被保存下来,因为在回调函数和卸载钩子的时候还要用到这个句柄。动态链接库导出的另一个函数是UninstallHook,用来供主程序卸载钩子。程序在这里使用UnhookWidowHookEx函数卸载钩子,这个函数的输入参数只有一个,就是安装钩子时返回的句柄
   
现在来看主程序。Main.rc文件的内容如下:

#include                   
#define     ICO_MAIN        1000
#define     DLG_MAIN        1000
#define     IDC_TEXT        1001
ICO_MAIN        ICON                    "Main.ico"

DLG_MAIN DIALOG 208, 130, 234, 167
STYLE DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
CAPTION "键盘钩子"
FONT 9, "宋体"
{
 EDITTEXT IDC_TEXT, 5, 5, 224, 158, ES_MULTILINE | ES_AUTOVSCROLL
        | WS_BORDER | WS_VSCROLL | WS_TABSTOP | ES_READONLY
}

资源脚本文件中的定义很简单,仅定义了一个对话框,对话框中有个多行的编辑控件,用来显示“钩住”的按键。Main.asm的内容如下:
.386
.model flat, stdcall
option casemap :none
include         windows.inc
include         user32.inc
includelib      user32.lib
include         kernel32.inc
includelib      kernel32.lib
include         Hookdll.inc
includelib      Hookdll.lib

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
为了使用动态链接库中的导出函数InstallHook和UninstallHook,在程序的开头需要用include语句和includelib语句将动态链接库的函数声明和导入库包含进来。
在对话框初始化的消息WM_INITDIALOG 中,程序调用InstallHook函数安装钩子,输入的参数是主窗口句柄和自定义的消息ID:WM_HOOK(Windows系统中ID值在WM_USER以后的值都可以由用户使用,在这里将WM_HOOK定义为WM_USER+100h),这样每当钩子回调函数得到按键消息的时候,就可以通过这个消息ID通知主窗口。接下来程序对返回值进行检查,如果返回值表示失败则直接退出程序。在关闭对话框的WM_CLOSE消息中,程序调用UninstallHook函数卸载钩子。
在平时,主程序等待自定义消息WM_HOOK,并将传递过来的按键字符串通过发送EM_REPLACESEL消息添加到编辑框中,在添加之前先检测按键是否为回车键,如果是,再人为插入一个换行符(0ah),以便将编辑框中的内容换行显示。
2. 钩子回调函数
现在回过头来看HookDll.asm程序中的钩子回调函数,回调函数的写法一般如下:
HookProc            proc        dwCode,wParam,lParam
 
                invoke  CallNextHookEx,hHook,_dwCode,_wParam,_lParam
                   ;处理消息的代码
                   mov     eax,返回值
                   ret
HookProc    endp
各种类型钩子的回调函数的参数都是这样3个,但是它们的定义各不相同,就像窗口过程在收到各种不同消息的时候,wParam和lParam的定义也各不相同。不同类型的钩子回调函数的返回值定义也是各不相同的。
对于键盘钩子来说,参数的定义如下所示。
●   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。
对于每个击键动作,钩子回调函数会在键按下和释放的时候被调用两次,只需根据 lParam的位31中的标志来记录一次,否则得到的是重复信息。
另外,回调函数收到的参数是以按键的扫描码和虚拟码表示的,在送给主窗口前需要将它转换成我们认识的ASCII码,但虚拟码或扫描码和ASCII码之间的对应关系并没有规律,必须进行查表操作才能转换。如果在程序中自己转换的话,需要一个键码对应表和查表程序。
Windows中现成的函数ToAscii可以完成这个功能并自动辨认按键的按下或释放动作。代码如下。
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
               ...
ToAscii函数的用法是:
   invoke  ToAscii,dwVirtKey,uScanCode,lpKeyState,lpBuffer,uFlags
dwVirtKey参数指定按键的虚拟码,在使用时直接用钩子回调函数的wParam参数就可以了,uScanCode指定按键的扫描码,并用位15来表示是按键按下还是按键释放,和回调函数的lParam参数对比可以看出,lParam参数的高16位就是需要的东西,所以程序将lParam右移16位后用做uScanCode参数。
lpKeyState指向一个256字节的缓冲区,其中存放键盘中所有按键的当前状态,一个字节表示一个按键,数值为1表示按下,为0表示释放,数据在缓冲区中的排列位置按照VK_xx虚拟码的顺序排列。这是为了让函数得知键盘上各种控制键的状态(如Shift,Alt和Ctrl等),因为这些键是否按下对转换结果是有影响的,比如同样是按键“1”,如果Shift键不按下,对应的就是“1”,按下的话函数必须返回“!”才是正确的结果。当然不可能自己去填写这个缓存区,使用GetKeyboardState函数就可以让系统根据当前的键盘状态填写这个缓冲区。
lpBuffer指向一个缓存区,用来接收转换后的ASCII码,最后的uFlags参数表示当前是否有一个菜单在激活状态,0表示没有,1表示有菜单正在激活。
函数的返回值表示转换后返回在lpBuffer缓冲区中的字符数量,它可能是0(如按键放开时不产生字符)、1或者是2,下面的语句根据返回字符数将缓冲区中的字符尾部加上一个NULL:
   mov     byte ptr szAscii [eax],0
对于Shift等控制键来说,GetKeyboardState函数返回的状态是区分左、右键的(分别对应VK_LSHIFT和VK_RSHIFT),而ToAscii函数检测的是VK_SHIFT,不对Shift键进行处理的话,转换结果可能是错误的,所以程序使用GetKeyState函数单独获取VK_SHIFT的状态并手工修改缓冲区中VK_SHIFT位置的状态。
转换完成后,用PostMessage函数将转换后的按键内容传递给主窗口,就大功告成了!不过要注意的还有两点:首先是在这里不要使用SendMessage函数,因为可能造成死循环;其次就是不要向主窗口传递地址,因为钩子DLL被插入到其他进程的地址空间中运行,所以将地址传回去可能是无效的。
不同类型钩子回调函数返回值的定义是不同的。对于键盘钩子,返回0表示允许Windows将消息转发给目标窗口过程,返回非0值表示让Windows将消息丢弃,这样钩子函数可以检测到按键动作,目标程序却无法收到键盘消息,相当于所有的按键都失效了。
3. 钩子链
Windows系统中可以同时存在多个同类型的钩子,多个程序同时安装同一种钩子的时候就会出现这种情况,这些钩子组成一个钩子链,最近加入的钩子放在链表的头部,Windows负责为每种钩子维护一个钩子链。当一个事件发生的时候,Windows调用最后安装的钩子,然后由当前钩子的回调函数发起调用下一个钩子的动作,Windows收到这个动作后,再从链表中取出下一个钩子的地址并将调用传递下去。
在大多数的情况下,一个钩子回调函数最好把消息事件传递下去以便其他的钩子都有获得处理这一消息的机会。调用下一个钩子函数是CallNextHookEx,该函数的用法是:
   invoke  CallNextHookEx,hHook,dwCode,wParam,lParam
hHook参数是当前钩子的句柄,dwCode,wParam和lParam参数就是当前钩子收到的参数,这个函数让Windows调用钩子链中的下一个钩子。如果调用成功,函数的返回值是下一个钩子回调函数返回的数值。
四:  日志记录钩子
日志记录钩子是一种特殊的钩子,说它特殊是因为它是远程钩子,却不用放在动态链接库中,这就为监视系统范围的消息提供了方便。本节中尝试用日志记录钩子的办法来实现键盘监视的功能,包括汇编源文件RecHook.asm和资源脚本文件RecHook.rc,其中RecHook.rc文件的内容和上一个例子的Main.rc文件是一样的。
RecHook.asm文件的内容如下:
.386
.model flat, stdcall
option casemap :none
include         windows.inc
include         user32.inc
includelib      user32.lib
include         kernel32.inc
includelib      kernel32.lib

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
参数WH_JOURNALRECORD表示安装的钩子是日志记录钩子。
由于钩子回调函数也写在主程序中,所以没有必要再通过自定义的WM_HOOK消息来通信,在回调函数中使用ToAscii函数将监测到的按键扫描码转换成ASCII码字符串以后,程序直接发送EM_REPLACESEL消息将它添加到编辑框中。
程序比较重要的一个不同点在于日志钩子回调函数的参数定义不同,在这里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时进行处理就可以了,同理,如果关心的是鼠标消息的话,使用日志记录钩子也可以完成鼠标钩子完成的工作。例子程序中的相关代码如下:
   .if     _dwCode == HC_ACTION
           mov     ebx,_lParam
           assume  ebx:ptr EVENTMSG
           .if     [ebx].message == WM_KEYDOWN
                   ;处理按键消息
           .endif
           assume  ebx:nothing
   .endif
日志记录钩子回调函数的返回值没有被定义。所以不管返回什么值对消息的传递都没有影响。
阅读(2403) | 评论(0) | 转发(0) |
0

上一篇:动态链接库

下一篇:多线程

给主人留下些什么吧!~~