Windows Hook钩子机制及键盘钩子实例(转)

本文将试图以下面的顺序讲解HOOK的大部分内容: 

1、 WINDOWS的消息机制 
2、 HOOK介绍 
3、 HOOK链 
4、 HOOK钩子的作用范围 
5、 HOOK类型 
6、 回调函数 
7、 HOOK钩子的安装与卸载 


+++++++++++++++++++ 
WINDOWS的消息机制 
+++++++++++++++++++ 
Windows系统是以消息处理为其控制机制,系统通过消息为窗口过程(windows  
procedure)传递输入。系统和应用两者都可以产生消息。对于每个输入事件,例如用 
户按下了键盘上的某个键、移动了鼠标、单击了一个控件上的滚动条,等等,系统都 
将产生一系列消息。此外,对于应用带给系统的变化,如字体资源的改变、应用本身 
窗口的改变,系统都将通过消息以响应这种变化。应用通过产生消息指示应用的窗口 
完成特定的任务,或与其他应用的窗口进行通信。 
每个窗口都有一个处理Windows系统发送消息的处理程序,称为窗口程序。它是 
隐含在窗口背后的一段程序脚本,其中包含对事件进行处理的代码。 
Windows系统为每条消息指定了一个消息编号,例如当一个窗口变为活动窗口时,它事 
实上是收到一条来自Windows系统的WM_ACTIVATE消息,该消息的编号为6,它对 
应于VB窗口的Activate事件。对于窗口来说,诸如Open、Activate、MouseDown、Resize 
等事件,实际上对应的是窗口内部的消息处理程序,这些程序对于用户来讲是不可见的。 
类似地,命令按钮也有消息处理程序,它的处理程序响应诸如WM_LBUTTONDOWN 
和WM_RBUTTONDOWN之类的消息,即激活命令按钮的MouseDown事件。 

WINDOWS的消息处理机制为了能在应用程序中监控系统的各种事件消息,提供 
了挂接各种回调函数(HOOK)的功能。这种挂钩函数(HOOK)类似扩充中断驱动程序, 
挂钩上 可以挂接多个反调函数构成一个挂接函数链。系统产生的各种消息首先被送 
到各种挂接函数,挂接函数根据各自的功能对消息进行监视、修改和控制等,然后交 
还控 制权或将消息传递给下一个挂接函数以致最终达到窗口函数。WINDOW系统的 
这种反调函数挂接方法虽然会略加影响到系统的运行效率,但在很多场合下是非常有 
用的,通过合理有效地利用键盘事件的挂钩函数监控机制可以达到预想不到的良好效 
果。 

+++++++++++ 
hook介绍 
+++++++++++ 

  Hook(钩子)是WINDOWS提供的一种消息处理机制平台,是指在程序正常运 
行中接受信息之前预先启动的函数,用来检查和修改传给该程序的信息,(钩子)实 
际上是一个处理消息的程序段,通过系统调用,把它挂入系统。每当特定的消息发出, 
在没有到达目的窗口前,钩子程序就先捕获该消息,亦即钩子函数先得到控制权。这 
时钩子函数即可以加工处理(改变)该消息,也可以不作处理而继续传递该消息,还 
可以强制结束消息的传递。 
注意:安装钩子函数将会影响系统的性能。监测“系统范围事件”的系统钩子特 
别明显。因为系统在处理所有的相关事件时都将调用您的钩子函数,这样您的系统将 
会明显的减慢。所以应谨慎使用,用完后立即卸载。还有,由于您可以预先截获其它 
进程的消息,所以一旦您的钩子函数出了问题的话必将影响其它的进程。记住:功能 
强大也意味着使用时要负责任。 

+++++++++++++ 
HOOK链 
+++++++++++++ 

WINDOWS提供了14种不同类型的HOOKS;不同的HOOK可以处理不同的消 
息。例如,WH_MOUSE HOOK用来监视鼠标消息。 
WINDOWS为这几种HOOKS维护着各自的HOOK链表。HOOK链表是一串由 
应用程序定义的回调函数(CALLBACK Function)队列,当某种类型的消息发生时,

WINDOWS向此种类型的HOOK链的第一个函数(HOOK链的顶部)发送该消息, 
在第一函数处理完该消息后由该函数向链表中的下一个函数传递消息,依次向下。如 
果链中某个函数没有向下传送该消息,那么链表中后面的函数将得不到此消息。(对 
于某些类型的HOOK,不管HOOK链中的函数是否向下传递消息,与此类型HOOK 
联系的所有HOOK函数都会收到系统发送的消息)一些Hook子过程可以只监视消息, 
或者修改消息,或者停止消息的前进,避免这些消息传递到下一个Hook子过程或者 
目的窗口。最近安装的钩子放在链的开始,而最早安装的钩子放在最后,也就是后加 
入的先获得控制权。 



+++++++++++++++++ 
钩子的作用范围 
++++++++++++++++ 

 一共有两种范围(类型)的钩子:局部的和远程的。 
一、局部钩子仅钩挂您自己进程的事件。 
二、远程的钩子还可以将钩挂其它进程发生的事件。 
远程的钩子又有两种: 
1、基于线程的 它将捕获其它进程中某一特定线程的事件。简言之,就是可 
以用来观察其它进程中的某一特定线程将发生的事件。 
2、系统范围的 将捕捉系统中所有进程将发生的事件消息。 

+++++++++++++ 
HOOK类型 
+++++++++++++ 

 Windows共有14种HOOKS,每一种类型的Hook可以使应用程序能够监视不同 
类型的系统消息处理机制。下面描述所有可以利用的Hook类型的发生时机。(这些常 
数值均可以API浏览器里查到) 

1、WH_CALLWNDPROC和WH_CALLWNDPROCRET Hooks 

 WH_CALLWNDPROC和WH_CALLWNDPROCRET Hooks使你可以监视发送到 
窗口过程的消息。系统在消息发送到接收窗口过程之前调用WH_CALLWNDPROC 
Hook子过程,并且在窗口过程处理完消息之后调用WH_CALLWNDPROCRET Hook 
子过程。 

 WH_CALLWNDPROCRET Hook传递指针到CWPRETSTRUCT结构,再传递到 
Hook子过程。CWPRETSTRUCT结构包含了来自处理消息的窗口过程的返回值,同 
样也包括了与这个消息关联的消息参数。 

2、WH_CBT Hook 

 在以下事件之前,系统都会调用WH_CBT Hook子过程,这些事件包括: 
 1. 激活,建立,销毁,最小化,最大化,移动,改变尺寸等窗口事件; 
 2. 完成系统指令; 
 3. 来自系统消息队列中的移动鼠标,键盘事件; 
 4. 设置输入焦点事件; 
 5. 同步系统消息队列事件。 
 
 Hook子过程的返回值确定系统是否允许或者防止这些操作中的一个。 

3、WH_DEBUG Hook 

 在系统调用系统中与其它Hook关联的Hook子过程之前,系统会调用 
WH_DEBUG Hook子过程。你可以使用这个Hook来决定是否允许系统调用与其它 
Hook关联的Hook子过程。 

4、WH_FOREGROUNDIDLE Hook 

 当应用程序的前台线程处于空闲状态时,可以使用WH_FOREGROUNDIDLE 
Hook执行低优先级的任务。当应用程序的前台线程大概要变成空闲状态时,系统就 
会调用WH_FOREGROUNDIDLE Hook子过程。 

5、WH_GETMESSAGE Hook 

 应用程序使用WH_GETMESSAGE Hook来监视从GetMessage or PeekMessage函 
数返回的消息。你可以使用WH_GETMESSAGE Hook去监视鼠标和键盘输入,以及 
其它发送到消息队列中的消息。 

6、WH_JOURNALPLAYBACK Hook 

 WH_JOURNALPLAYBACK Hook使应用程序可以插入消息到系统消息队列。可 
以使用这个Hook回放通过使用WH_JOURNALRECORD Hook记录下来的连续的鼠 
标和键盘事件。只要WH_JOURNALPLAYBACK Hook已经安装,正常的鼠标和键盘 
事件就是无效的。WH_JOURNALPLAYBACK Hook是全局Hook,它不能象线程特定 
Hook一样使用。WH_JOURNALPLAYBACK Hook返回超时值,这个值告诉系统在处 
理来自回放Hook当前消息之前需要等待多长时间(毫秒)。这就使Hook可以控制实 
时事件的回放。WH_JOURNALPLAYBACK是system-wide local hooks,它们不会被 
注射到任何行程地址空间。 

7、WH_JOURNALRECORD Hook

WH_JOURNALRECORD Hook用来监视和记录输入事件。典型的,可以使用这 
个Hook记录连续的鼠标和键盘事件,然后通过使用WH_JOURNALPLAYBACK Hook 
来回放。WH_JOURNALRECORD Hook是全局Hook,它不能象线程特定Hook一样 
使用。WH_JOURNALRECORD是system-wide local hooks,它们不会被注射到任何行 
程地址空间。 

8、WH_KEYBOARD Hook 

 在应用程序中,WH_KEYBOARD Hook用来监视WM_KEYDOWN and 
WM_KEYUP消息,这些消息通过GetMessage or PeekMessage function返回。可以使 
用这个Hook来监视输入到消息队列中的键盘消息。 

9、WH_KEYBOARD_LL Hook 

 WH_KEYBOARD_LL Hook监视输入到线程消息队列中的键盘消息。 

10、WH_MOUSE Hook 

 WH_MOUSE Hook监视从GetMessage 或者 PeekMessage 函数返回的鼠标消息。 
使用这个Hook监视输入到消息队列中的鼠标消息。 

11、WH_MOUSE_LL Hook 

 WH_MOUSE_LL Hook监视输入到线程消息队列中的鼠标消息。 

12、WH_MSGFILTER 和 WH_SYSMSGFILTER Hooks 

 WH_MSGFILTER 和 WH_SYSMSGFILTER Hooks使我们可以监视菜单,滚动 
条,消息框,对话框消息并且发现用户使用ALT+TAB or ALT+ESC 组合键切换窗口。 
WH_MSGFILTER Hook只能监视传递到菜单,滚动条,消息框的消息,以及传递到通 
过安装了Hook子过程的应用程序建立的对话框的消息。WH_SYSMSGFILTER Hook 
监视所有应用程序消息。 
 
 WH_MSGFILTER 和 WH_SYSMSGFILTER Hooks使我们可以在模式循环期间 
过滤消息,这等价于在主消息循环中过滤消息。 
 
通过调用CallMsgFilter function可以直接的调用WH_MSGFILTER Hook。通过使用这 
个函数,应用程序能够在模式循环期间使用相同的代码去过滤消息,如同在主消息循 
环里一样。 

13、WH_SHELL Hook 

 外壳应用程序可以使用WH_SHELL Hook去接收重要的通知。当外壳应用程序是 
激活的并且当顶层窗口建立或者销毁时,系统调用WH_SHELL Hook子过程。 
 WH_SHELL 共有5钟情况: 
1. 只要有个top-level、unowned 窗口被产生、起作用、或是被摧毁; 
2. 当Taskbar需要重画某个按钮; 
3. 当系统需要显示关于Taskbar的一个程序的最小化形式; 
4. 当目前的键盘布局状态改变; 
5. 当使用者按Ctrl+Esc去执行Task Manager(或相同级别的程序)。 

 按照惯例,外壳应用程序都不接收WH_SHELL消息。所以,在应用程序能够接 
收WH_SHELL消息之前,应用程序必须调用SystemParametersInfo function注册它自 
己。 


++++++++++++++++++++++++++ 
回调函数(HOOK处理子过程) 
++++++++++++++++++++++++++ 

为了拦截和处理特定的消息,你可以使用SetWindowsHookEx函数(下面将具体 
说明这些函数的声明及各种参数)在该类型的HOOK链中安装你自己的处理HOOK 
的子过程(回调函数)。只要您安装的钩子的消息事件类型发生,WINDOWS就将调 
用钩子函数。譬如您安装的钩子是WH_MOUSE类型,那么只要有一个鼠标事件发生 
时,该钩子函数就会被调用。不管您安装的是那一类型钩子,钩子函数的原型都时是 
一样的,语法如下: 

public function MyHook(ByVal nCode As Long, ByVal wParam As Long, ByVal lParam 
As Long) as long 

‘处理代码 

end function 

其中MyHook可以随便命名,其它不能变。该函数必须放在模块段。 
参数说明: 
nCode指定HOOK传入的信息类型。Hook子过程使用这个参数来确定任务。这个 
参数的值依赖于Hook类型,每一种Hook都有自己的Hook代码特征字符集。 
wParam:短整型参数。 
lParam:长整型参数。 
wParam,iParam的取值随nCode不同而不同,它代表了某种类型的HOOK的某个特 
定的动作。它们的典型值是包含了关于发送或者接收消息的信息。 
至于以上的几个参数及返回值的具体含义,各种类型的钩子都不相同,所以您必

须查询WIN32 API 指南来得到不同类型钩子参数的详细定义以及它们返回值的意 
义。(如果哪位朋友有时间和兴趣的话,请整理一下贴出来,顺便发给我一份,我的邮箱: 
wlclass163.com) 
譬如: 
WH_CALLWNDPROC 
nCode 只能是HC_ACTION,它代表有一个消息发送给了一个窗口 
wParam 如果非0,代表正被发送的消息 
lParam 指向CWPSTRUCT型结构体变量的指针 
return value: 未使用,返回0 
WH_MOUSE 
nCode 为HC_ACTION 或 HC_NOREMOVE 
wParam 包含鼠标的事件消息 
lParam 指向MOUSEHOOKSTRUCT型结构体变量的指针 
return value: 如果不处理返回0,否则返回非0值 

++++++++++++++++++++++++++ 
钩子的安装/卸载 
++++++++++++++++++++++++++ 

现在我们知道了一些基本的理论,接下来开始讲解如何安装和卸载一个钩子。 


◆安装钩子 

使用SetWindowsHookEx函数(API函数),指定一个HOOK类型、自己的HOOK 
过程是全局还是局部HOOK,同时给出HOOK过程的进入点,就可以轻松的安装你 
自己的HOOK过程。 
SetWindowsHookEx总是将你的HOOK函数放置在HOOK链的顶端。你可以使用 
CallNextHookEx函数将系统消息传递给HOOK链中的下一个函数。 
[注意]对于某些类型的HOOK,系统将向该类的所有HOOK函数发送消息,这时, 
HOOK函数中的CallNextHookEx语句将被忽略。 

全局(远程钩子)HOOK函数可以拦截系统中所有线程的某个特定的消息,为了 
安装一个全局HOOK过程,必须在应用程序外建立一个DLL,并将该HOOK函数封 
装到其中,应用程序在安装全局HOOK过程时必须先得到该DLL模块的句柄。将DLL 
名传递给LoadLibrary 函数,就会得到该DLL模块的句柄;得到该句柄 后,使用 
GetProcAddress函数可以得到HOOK过程的地址。最后,使用SetWindowsHookEx将 
HOOK过程的首址嵌入相应的HOOK链中,SetWindowsHookEx传递一个模块句柄, 
它为HOOK过程的进入点,线程标识符置为0,指出:该HOOK过程同系统中的所 
有线程关联。如果是安装局部HOOK此时该HOOK函数可以放置在DLL中,也可以 
放置在应用程序的模块段。 

 建议只在调试时使用全局HOOK函数。全局HOOK函数将降低系统效率,并且 
会同其它使用该类HOOK的应用程序产生冲突。 


SetWindowHookEx函数参数说明: 
idHook:代表是何种Hook(也就是上面讲的14种Hook) 

lpfn:代表处理Hook的过程所在的Address,这是一个CallBack Fucnction(也就是上 
面讲的回调函数),当挂上某个Hook时,我们便得定义一个Function来当作某个信 
息产生时,来处理它的Function。因这个参数是一个 Function的Address所以我们 
固定将Hook Function放在.Bas中,并以AddressOf HookFunc传入 

hmod:代表.DLL的hInstance,如果是Local Hook,该值可以是Null(VB中可传0 
进去),而如果是Remote Hook,则可以使用GetModuleHandle(".dll名称")来传入。 

dwThreadId:代表执行这个Hook的ThreadId(线程ID),如果不设定是那个Thread 
(线程)来做,则传0,而VB的Local Hook一般可传App.ThreadId进去ThreadID 
是您安装该钩子函数后想监控的线程的ID号。该参数可以决定该钩子是局部的还 
是系统范围的。如果该值为NULL,那么该钩子将被解释成系统范围内的,那它就 
可以监控所有的进程及它们的线程。如果您指定了您自己进程中的某个线程ID 号, 
那该钩子是一个局部的钩子。如果该线程ID是另一个进程中某个线程的ID,那该 
钩子是一个全局的远程钩子。这里有两个特殊情况:WH_JOURNALRECORD 和 
WH_JOURNALPLAYBACK总是代表局部的系统范围的钩子,之所以说是局部,是 
因为它们没有必要放到一个DLL中。WH_SYSMSGFILTER 总是一个系统范围内 
的远程钩子。其实它和WH_MSGFILTER钩子类似,如果把参数ThreadID设成0 
的话,它们就完全一样了。 

SetWindowHookEx函数回值: 如果SetWindowsHookEx()成功,它会传回一个值, 
代表目前的Hook的Handle,否则返回NULL。您必须保存该句柄,因为后面我们 
还要它来卸载钩子。 


hHook值是SetWindowsHookEx()的传回值,nCode, wParam, lParam则是回调函数 
中的三个参数。  


CallNextHookEx函数:
在钩子子程中调用得到控制权的钩子函数在完成对消息的处理后,如果想要该消 
息继续传递,那么它必须调用另外一个API函数CallNextHookEx来传递它,以执行 
钩子链表所指的下一个钩子子过程。这个函数成功时返回钩子链中下一个钩子过程的 
返回值,返回值的类型依赖于钩子的类型。 



◆ 卸载钩子 
 
要卸载一个钩子非常简单,只需要使用UnhookWindowsHookEx函数来卸载创建 
的钩子。 
函数声明: 

下面是一个键盘钩子的简单实例:

DllImport的相关使用说明: http://blog.csdn.net/x___v/article/details/8585415

/// <summary>
        /// 声明回调函数委托
        /// </summary>
        /// <param name="nCode"></param>
        /// <param name="wParam"></param>
        /// <param name="lParam"></param>
        /// <returns></returns>
        public delegate int HookProc(int nCode, Int32 wParam, IntPtr lParam);

        /// <summary>
        /// 委托实例
        /// </summary>
        HookProc KeyboardHookProcedure;

        /// <summary>
        /// 键盘钩子句柄
        /// </summary>
        static int hKeyboardHook = 0;
        
        //装置钩子的函数 
        [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
        public static extern int SetWindowsHookEx(int idHook, HookProc lpfn, IntPtr hInstance, int threadId);

        //卸下钩子的函数 
        [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
        public static extern bool UnhookWindowsHookEx(int idHook);

        //获取某个进程的句柄函数
        [DllImport("kernel32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
        private static extern IntPtr GetModuleHandle(string lpModuleName);

        /// <summary>
        /// 普通按键消息
        /// </summary>
        private const int WM_KEYDOWN = 0x100;
        /// <summary>
        /// 系统按键消息
        /// </summary>
        private const int WM_SYSKEYDOWN = 0x104;

        //鼠标常量 
        public const int WH_KEYBOARD_LL = 13;

        //声明键盘钩子的封送结构类型 
        [StructLayout(LayoutKind.Sequential)]
        public class KeyboardHookStruct
        {
            public int vkCode; //表示一个在1到254间的虚似键盘码 
            public int scanCode; //表示硬件扫描码 
            public int flags;
            public int time;
            public int dwExtraInfo;
        }

        /// <summary>
        /// 启动键盘钩子
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void BtnStart_Click(object sender, EventArgs e)
        {
            //启动键盘钩子 
            if (hKeyboardHook == 0)
            {
                //实例化委托
                KeyboardHookProcedure = new HookProc(KeyboardHookProc);
                Process curProcess = Process.GetCurrentProcess();
                ProcessModule curModule = curProcess.MainModule;
                hKeyboardHook = SetWindowsHookEx(WH_KEYBOARD_LL, KeyboardHookProcedure, GetModuleHandle(curModule.ModuleName), 0);
            }
        }

        /// <summary>
        /// 截取全局按键,发送新按键,返回
        /// </summary>
        /// <param name="nCode"></param>
        /// <param name="wParam"></param>
        /// <param name="lParam"></param>
        /// <returns></returns>
        private int KeyboardHookProc(int nCode, Int32 wParam, IntPtr lParam)
        {
            if (nCode >= 0 && wParam == WM_KEYDOWN || wParam == WM_SYSKEYDOWN)
            {
                KeyboardHookStruct MyKeyboardHookStruct = (KeyboardHookStruct)Marshal.PtrToStructure(lParam, typeof(KeyboardHookStruct));
                Keys keyData = (Keys)MyKeyboardHookStruct.vkCode;

                if (keyData.ToString() == "Enter")
                {
                    MessageBox.Show("你按下了回车键");
                    //return为了屏蔽原来的按键,如果去掉,则原来的按键和新的按键都会模拟按。
                    return 1;
                }
            }
            return 0;
        }

        /// <summary>
        /// 停止键盘钩子
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void BtnStop_Click(object sender, EventArgs e)
        {
            bool retKeyboard = true;

            if (hKeyboardHook != 0)
            {
                retKeyboard = UnhookWindowsHookEx(hKeyboardHook);
                hKeyboardHook = 0;
            }
            //如果卸下钩子失败 
            if (!(retKeyboard)) throw new Exception("卸下钩子失败!");
        }



原文地址:https://www.cnblogs.com/fornet/p/2976175.html