WIN32汇编语言教程:第11章 动态链接库和钩子 · 11.2 Windows钩子(1)
11.2.1 什么是Windows钩子
1. Windows钩子简介
在DOS操作系统下编程的时候,如果想截获某种系统功能,可以采取截获中断的办法,比如要获取击键的动作可以截获9号中断,要获取应用程序对文件操作功能的调用可以截获21h号DOS中断,由于DOS是单任务系统,所以这些操作几乎全部是内存驻留程序做的。DOS下截获中断的方法是这样的简单和随意,不管在驱动程序层次还是在应用程序层次都可以完成,以至于到最后大半的截获操作是由病毒使用的。
在Windows下就不同了,我们已经知道保护模式下的中断描述符表是受系统保护的,在应用程序层次是不可能再通过修改中断向量来截获系统中断了,但这样也对一些应用造成了不便,当做一种变通的措施,Windows提供了钩子来完成类似的功能。那么,钩子是什么呢?Win32 API手册中是这样描述的:
“A hook is a point in the Microsoft Windows message-handling mechanism where an application can install a subroutine to monitor the message traffic in the system and process certain types of messages before they reach the target window procedure.”
翻译过来就是:“钩子是Windows的消息处理机制中的一个监视点,应用程序可以在这里安装一个监视子程序,这样就可以在系统中的消息流到达目的窗口过程前监控它们。”
也就是说,钩子可以用来截获系统中的消息流,显然,钩子不是像截获中断一样用来随心所欲地截获系统底层功能的,那么钩子能够用来做什么事情呢?(我仿佛听到了一些阴险的笑声......)不用笑得这么阴险嘛!大家想得没错,如果把钩子用在后台执行的程序中,就能够偷偷检查任何程序中发生的WM_CHAR消息,这样用户输入的任何内容:账号、密码、情书——不管是什么,不管是否显示在屏幕上——都可以被记录下来。事实上,很多木马程序就是这样做的,像冰河一类的木马程序就可以在后台记录用户的击键并偷偷发送到人家的信箱中去。
2. 钩子的类型
钩子是Windows消息机制中的监视点,应用程序可以在这里安装一个监视函数,这样就可以捕捉自己进程或者其他进程发生的事件。通过SetWindowsHookEx函数就可以做到这一点。SetWindowsHookEx函数定义了监视函数的位置和监视消息的类型,这样,每当发生我们感兴趣的消息时,Windows就会将消息发送给监视函数,监视函数是一个处理消息的回调函数,也称为“钩子函数”。
Windows安装的钩子有两种类型:局部的和远程的。它们处理消息的范围不同。局部钩子仅钩挂属于自身进程的事件;远程钩子除了可以钩挂自身进程的事件,还可以钩挂其他进程中发生的事件。远程钩子又分两种:基于线程的和系统范围的。基于线程的远程钩子用来捕获其他进程中某一特定线程的事件;而系统范围的远程钩子将捕捉系统中所有进程中发生的事件消息。
安装钩子会影响系统的性能,因为系统在处理所有的相关事件时都会调用钩子函数,特别是监视范围是整个系统范围的全局钩子。如果钩子函数中的处理代码过多的话,系统运行速度将会明显减慢,所以对于全局钩子一定要小心使用,不需要的时候应该立刻卸载。在 DOS操作系统下编写中断服务程序的时候,如果代码有错误的话会影响其他调用它的程序。同样,由于钩子函数可以预先截获其他进程的消息,所以一旦钩子函数存在问题的话,也会影响其他进程的运行。
可以把钩子想像成钓鱼钩,不同鱼钩用来钓的鱼是不同的,大钩钓大鱼,小钩钓小鱼,不同钩子钓的消息也是不同的,没有必要每次钓来所有的消息,根据监视的消息类型和时机的不同,钩子可以分为如表11.1所示的几种。
表11.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内部发起的,所以不存在不同进程之间地址空间隔离的问题。(猜想而已,如果读者有明确的资料请告知笔者。)
下面的11.2.2节以键盘钩子为例来说明系统范围远程钩子的安装和使用,局部钩子的使用步骤与之类似,只不过不必将钩子函数放在动态链接库中而已,使用起来更加简单,读者可以举一反三自己尝试一下。11.2.3节中演示日志钩子的使用方法。
11.2.2 远程钩子的安装和使用
1. 钩子程序的结构
钩子程序一般包括3个功能模块:
(1)主程序——用来实现界面或者其他功能。
(2)钩子回调函数——用来接收系统发过来的消息。
(3)钩子的安装和卸载程序。
对于局部钩子来说,这些模块可以处在同一个可执行文件中。而对于远程钩子来说,第2部分必须放在一个动态链接库中,第3部分虽然没有要求,但一般也放在动态链接库中,这是因为钩子创建以后得到一个钩子句柄,这个句柄要在钩子回调函数中以及卸载钩子的时候用到,如果把这部分代码放在主程序中的话,还需要创建一个函数将它传回给动态链接库,所以还不如直接放到库中。
所附光盘的Chapter11\KeyHook目录中的例子采用的就是这样的结构。目录中包括两部分文件:HookDll.asm和HookDll.def文件用来生成动态链接库;Main.asm和Main.rc是主程序部分。程序用一个系统范围的远程钩子来实现监视所有键盘输入的功能。由于安装钩子回调函数的动态链接库要求是共享数据段的,所以请读者注意Makefile中dll文件的链接选项,它使用了/section:.bss,S选项。
上页:第11章 动态链接库和钩子 · 11.1 动态链接库(6) 下页:第11章 动态链接库和钩子 · 11.2 Windows钩子(2)