zoukankan      html  css  js  c++  java
  • 学习之路三十八:Hook(钩子)的学习

    好久没写文章了,还记得年前面试了一家公司,为了检测一下我的学习能力,给了我一个任务,做一个自动登录并自动操作菜单的程序。

    花了几天的时间研究了Hook以及使用WindowsAPI操作程序的知识,现在记录一下,也算是一次温习。

    一丶Hook

      在我看来Hook就是监测用户操作键盘(或虚拟键盘)以及鼠标的行为,对于Hook的理解我也不是很深入,也只是一点皮毛。

      1. 实现Hook的步骤

        ①安装钩子

        ②监测键盘和鼠标的操作,用来实现相应的逻辑

        ③卸载钩子

      2.安装钩子

        钩子分两种:键盘钩子和鼠标钩子,而每一种钩子又可以分为全局钩子或局部勾子。

        下面是安装钩子需要的Windows Message常量(网上找的)。

      1     public enum HookType : int
      2     {
      3         /// <summary>
      4         /// WH_MSGFILTER 和 WH_SYSMSGFILTER Hooks使我们可以监视菜单,滚动 
      5         ///条,消息框,对话框消息并且发现用户使用ALT+TAB or ALT+ESC 组合键切换窗口。 
      6         ///WH_MSGFILTER Hook只能监视传递到菜单,滚动条,消息框的消息,以及传递到通 
      7         ///过安装了Hook子过程的应用程序建立的对话框的消息。WH_SYSMSGFILTER Hook 
      8         ///监视所有应用程序消息。 
      9         /// 
     10         ///WH_MSGFILTER 和 WH_SYSMSGFILTER Hooks使我们可以在模式循环期间 
     11         ///过滤消息,这等价于在主消息循环中过滤消息。 
     12         ///    
     13         ///通过调用CallMsgFilter function可以直接的调用WH_MSGFILTER Hook。通过使用这 
     14         ///个函数,应用程序能够在模式循环期间使用相同的代码去过滤消息,如同在主消息循 
     15         ///环里一样
     16         /// </summary>
     17         WH_MSGFILTER = -1,
     18         /// <summary>
     19         /// WH_JOURNALRECORD Hook用来监视和记录输入事件。典型的,可以使用这 
     20         ///个Hook记录连续的鼠标和键盘事件,然后通过使用WH_JOURNALPLAYBACK Hook 
     21         ///来回放。WH_JOURNALRECORD Hook是全局Hook,它不能象线程特定Hook一样 
     22         ///使用。WH_JOURNALRECORD是system-wide local hooks,它们不会被注射到任何行 
     23         ///程地址空间
     24         /// </summary>
     25         WH_JOURNALRECORD = 0,
     26         /// <summary>
     27         /// WH_JOURNALPLAYBACK Hook使应用程序可以插入消息到系统消息队列。可 
     28         ///以使用这个Hook回放通过使用WH_JOURNALRECORD Hook记录下来的连续的鼠 
     29         ///标和键盘事件。只要WH_JOURNALPLAYBACK Hook已经安装,正常的鼠标和键盘 
     30         ///事件就是无效的。WH_JOURNALPLAYBACK Hook是全局Hook,它不能象线程特定 
     31         ///Hook一样使用。WH_JOURNALPLAYBACK Hook返回超时值,这个值告诉系统在处 
     32         ///理来自回放Hook当前消息之前需要等待多长时间(毫秒)。这就使Hook可以控制实 
     33         ///时事件的回放。WH_JOURNALPLAYBACK是system-wide local hooks,它们不会被 
     34         ///注射到任何行程地址空间
     35         /// </summary>
     36         WH_JOURNALPLAYBACK = 1,
     37         /// <summary>
     38         /// 在应用程序中,WH_KEYBOARD Hook用来监视WM_KEYDOWN and  
     39         ///WM_KEYUP消息,这些消息通过GetMessage or PeekMessage function返回。可以使 
     40         ///用这个Hook来监视输入到消息队列中的键盘消息
     41         /// </summary>
     42         WH_KEYBOARD = 2,
     43         /// <summary>
     44         /// 应用程序使用WH_GETMESSAGE Hook来监视从GetMessage or PeekMessage函 
     45         ///数返回的消息。你可以使用WH_GETMESSAGE Hook去监视鼠标和键盘输入,以及 
     46         ///其它发送到消息队列中的消息
     47         /// </summary>
     48         WH_GETMESSAGE = 3,
     49         /// <summary>
     50         /// 监视发送到窗口过程的消息,系统在消息发送到接收窗口过程之前调用
     51         /// </summary>
     52         WH_CALLWNDPROC = 4,
     53         /// <summary>
     54         /// 在以下事件之前,系统都会调用WH_CBT Hook子过程,这些事件包括: 
     55         ///1. 激活,建立,销毁,最小化,最大化,移动,改变尺寸等窗口事件; 
     56         ///2. 完成系统指令; 
     57         ///3. 来自系统消息队列中的移动鼠标,键盘事件; 
     58         ///4. 设置输入焦点事件; 
     59         ///5. 同步系统消息队列事件。
     60         ///Hook子过程的返回值确定系统是否允许或者防止这些操作中的一个
     61         /// </summary>
     62         WH_CBT = 5,
     63         /// <summary>
     64         /// WH_MSGFILTER 和 WH_SYSMSGFILTER Hooks使我们可以监视菜单,滚动 
     65         ///条,消息框,对话框消息并且发现用户使用ALT+TAB or ALT+ESC 组合键切换窗口。 
     66         ///WH_MSGFILTER Hook只能监视传递到菜单,滚动条,消息框的消息,以及传递到通 
     67         ///过安装了Hook子过程的应用程序建立的对话框的消息。WH_SYSMSGFILTER Hook 
     68         ///监视所有应用程序消息。 
     69         /// 
     70         ///WH_MSGFILTER 和 WH_SYSMSGFILTER Hooks使我们可以在模式循环期间 
     71         ///过滤消息,这等价于在主消息循环中过滤消息。 
     72         ///    
     73         ///通过调用CallMsgFilter function可以直接的调用WH_MSGFILTER Hook。通过使用这 
     74         ///个函数,应用程序能够在模式循环期间使用相同的代码去过滤消息,如同在主消息循 
     75         ///环里一样
     76         /// </summary>
     77         WH_SYSMSGFILTER = 6,
     78         /// <summary>
     79         /// WH_MOUSE Hook监视从GetMessage 或者 PeekMessage 函数返回的鼠标消息。 
     80         ///使用这个Hook监视输入到消息队列中的鼠标消息
     81         /// </summary>
     82         WH_MOUSE = 7,
     83         /// <summary>
     84         /// 当调用GetMessage 或 PeekMessage 来从消息队列种查询非鼠标、键盘消息时
     85         /// </summary>
     86         WH_HARDWARE = 8,
     87         /// <summary>
     88         /// 在系统调用系统中与其它Hook关联的Hook子过程之前,系统会调用 
     89         ///WH_DEBUG Hook子过程。你可以使用这个Hook来决定是否允许系统调用与其它 
     90         ///Hook关联的Hook子过程
     91         /// </summary>
     92         WH_DEBUG = 9,
     93         /// <summary>
     94         /// 外壳应用程序可以使用WH_SHELL Hook去接收重要的通知。当外壳应用程序是 
     95         ///激活的并且当顶层窗口建立或者销毁时,系统调用WH_SHELL Hook子过程。 
     96         ///WH_SHELL 共有5钟情况: 
     97         ///1. 只要有个top-level、unowned 窗口被产生、起作用、或是被摧毁; 
     98         ///2. 当Taskbar需要重画某个按钮; 
     99         ///3. 当系统需要显示关于Taskbar的一个程序的最小化形式; 
    100         ///4. 当目前的键盘布局状态改变; 
    101         ///5. 当使用者按Ctrl+Esc去执行Task Manager(或相同级别的程序)。 
    102         ///
    103         ///按照惯例,外壳应用程序都不接收WH_SHELL消息。所以,在应用程序能够接 
    104         ///收WH_SHELL消息之前,应用程序必须调用SystemParametersInfo function注册它自 
    105         ///106         /// </summary>
    107         WH_SHELL = 10,
    108         /// <summary>
    109         /// 当应用程序的前台线程处于空闲状态时,可以使用WH_FOREGROUNDIDLE  
    110         ///Hook执行低优先级的任务。当应用程序的前台线程大概要变成空闲状态时,系统就 
    111         ///会调用WH_FOREGROUNDIDLE Hook子过程
    112         /// </summary>
    113         WH_FOREGROUNDIDLE = 11,
    114         /// <summary>
    115         /// 监视发送到窗口过程的消息,系统在消息发送到接收窗口过程之后调用
    116         /// </summary>
    117         WH_CALLWNDPROCRET = 12,
    118         /// <summary>
    119         /// 监视输入到线程消息队列中的键盘消息
    120         /// </summary>
    121         WH_KEYBOARD_LL = 13,
    122         /// <summary>
    123         /// 监视输入到线程消息队列中的鼠标消息
    124         /// </summary>
    125         WH_MOUSE_LL = 14
    126     }
    View Code

         而用的最多的也就是:WH_KEYBOARD,WH_MOUSE, WH_KEYBOARD_LL,WH_MOUSE_LL。

         WH_KEYBOARD和WH_MOUSE是全局钩子,而WH_KEYBOARD_LL和WH_MOUSE_LL是针对某个线程的。

         所以说安装全局还是局部钩子取决于传入的消息常量。

         安装钩子需要调用的API:

     1         /// <summary>
     2         /// 安装勾子
     3         /// </summary>
     4         /// <param name="idHook">钩子类型,此处用整形的枚举表示</param>
     5         /// <param name="hookCallBack">钩子发挥作用时的回调函数</param>
     6         /// <param name="moudleHandle">应用程序实例的模块句柄(一般来说是你钩子回调函数所在的应用程序实例模块句柄)</param>
     7         /// <param name="threadID">与安装的钩子子程相关联的线程的标识符
     8         /// <remarks>如果线程ID是0则针对系统级别的,否则是针对当前线程</remarks>
     9         /// </param>
    10         /// <returns>返回钩子句柄</returns>
    11         [DllImport("user32.dll")]
    12         public static extern int SetWindowsHookEx(int idHook, HookProcCallBack hookCallBack, IntPtr moudleHandle, int threadID);
    13 
    14         public delegate int HookProcCallBack(int nCode, int wParam, IntPtr lParam);

         ☆:上面方法的第二个需要是个委托参数,必须把它设置为静态变量,因为监测钩子相当于一个定时器一直在跑,如果委托变量不是静态的话,会被GC给回收掉的。

      3.监测键盘和鼠标行为

        键盘操作分为:keyDown,keyPress,keyUp;鼠标操作分为:rightClick,leftClick,doubleClick,wheel,move。

        所以为了要监测上面的所有行为,需要使用事件来实现。

      4.卸载钩子

        主要还是调用API就可以了。

    1         /// <summary>
    2         /// 卸载勾子
    3         /// </summary>
    4         /// <param name="handle">要取消的钩子的句柄</param>
    5         /// <returns>卸载钩子是否成功</returns>
    6         [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
    7         public static extern bool UnhookWindowsHookEx(int handle); 

      5.HookManager

        发现写不下去,不知道该讲什么了,很多细节上都没有讲到,比如每个参数的含义,怎么调用等等,算是给出个思路,我也是下载了很多源码摸索过来的。

        给出主要实现代码:

      1 using System;
      2 using System.ComponentModel;
      3 using System.Diagnostics;
      4 using System.Runtime.InteropServices;
      5 using System.Windows.Forms;
      6 
      7 using SharpCommon.Windows;
      8 
      9 /*
     10  
     11  * 2014-1-28 完善第一个版本
     12  * 
     13  *  1.关于KeyPress的解释
     14  *      在控件有焦点的情况下按下键时发生。
     15  *      键事件按下列顺序发生: 
     16             KeyDown
     17             KeyPress
     18             KeyUp
     19 
     20             非字符键不会引发 KeyPress 事件;但非字符键却可以引发 KeyDown 和 KeyUp 事件。
     21             使用 KeyChar 属性在运行时对键击进行取样,并且使用或修改公共键击的子集。
     22             若要仅在窗体级别处理键盘事件而不允许其他控件接收键盘事件,
     23  *         请将窗体的 KeyPress 事件处理方法中的 KeyPressEventArgs.Handled 属性设置为 true。
     24  *         
     25  *      摘自MSDN上的说明
     26  *      KeyPressEventArgs 指定在用户按键时撰写的字符。例如,当用户按 Shift + K 时,KeyChar 属性返回一个大写字母 K。
     27          当用户按下任意键时,发生 KeyPress 事件。与 KeyPress 事件紧密相关的两个事件为 KeyUp 和 KeyDown。
     28  *      当用户按下某个键时,KeyDown 事件先于每个 KeyPress 事件发生;当用户释放某个键时发生 KeyUp 事件。
     29  *      当用户按住某个键时,每次字符重复时,KeyDown 和 KeyPress 事件也都重复发生。一个 KeyUp 事件在释放按键时生成。
     30 
     31          KeyPressEventArgs 随着 KeyPress 事件的每次发生而被传递。
     32  *      KeyEventArgs 随着 KeyDown 和 KeyUp 事件的每次发生而被传递。
     33  *      KeyEventArgs 指定是否有任一个组合键(Ctrl、Shift 或 Alt)在另一个键按下的同时也曾按下。
     34  *      此修饰符信息也可以通过 Control 类的 ModifierKeys 属性获得。
     35 
     36          将 Handled 设置为 true,以取消 KeyPress 事件。这可防止控件处理按键。
     37 
     38          注意注意: 
     39          有些控件将会在 KeyDown 上处理某些击键。
     40  *      例如,RichTextBox 在调用 KeyPress 前处理 Enter 键。
     41  *      在这种情况下,您无法取消 KeyPress 事件,而是必须从 KeyDown 取消击键。
     42  *      
     43  * 
     44  *  2014-1-28 1:00 PM
     45  *      1. 完成了对组合键的监测代码,通过获取KeyState来判断是否按了组合键
     46  
     47  */
     48 
     49 namespace SharpCommon.Hook
     50 {
     51     public sealed class HookManager
     52     {
     53         #region Event And Field
     54         public event CustomKeyEventHandler KeyUp;
     55         public event CustomKeyEventHandler KeyDown;
     56         public event CustomKeyEventHandler KeyPress;
     57 
     58         public event MouseEventHandler MouseMove;
     59         public event MouseEventHandler MouseWheel;
     60         public event MouseEventHandler LeftMouseClickUp;
     61         public event MouseEventHandler RightMouseClickUp;
     62         public event MouseEventHandler LeftMouseClickDown;
     63         public event MouseEventHandler RightMouseClickDown;
     64         public event MouseEventHandler LeftMouseDoubleClick;
     65         public event MouseEventHandler RightMouseDoubleClick;
     66 
     67         private static int _mouseHookHandle;
     68         private static int _keyboardHookHandlel;
     69 
     70         private static HookProcCallBack _mouseHookCallBack;
     71         private static HookProcCallBack _keyboardHookCallBack;
     72 
     73         private static readonly HookManager _instance = new HookManager();
     74 
     75         private static readonly int _currentThreadID = AppDomain.GetCurrentThreadId();
     76         private static readonly IntPtr _currentMoudleHandle = WindowsAPI.GetModuleHandle(Process.GetCurrentProcess().MainModule.ModuleName);
     77         
     78         #endregion
     79 
     80         #region Instance
     81 
     82         private HookManager()
     83         { }
     84 
     85         public static HookManager Instance
     86         {
     87             get { return _instance; }
     88         } 
     89 
     90         #endregion
     91 
     92         #region Install Hook
     93 
     94         /// <summary>
     95         /// Install the hook.
     96         /// </summary>
     97         /// <param name="installType">Select the hook install type.</param>
     98         public void InstallHook(HookInstallType installType = HookInstallType.MouseAndKeyBoard)
     99         {
    100             switch (installType)
    101             {
    102                 case HookInstallType.Mouse:
    103                     this.InstallMouseHook();
    104                     break;
    105                 case HookInstallType.KeyBoard:
    106                     this.InstallKeyBoardHook();
    107                     break;
    108                 case HookInstallType.MouseAndKeyBoard:
    109                     this.InstallMouseHook();
    110                     this.InstallKeyBoardHook();
    111                     break;
    112             }
    113         } 
    114 
    115         #endregion
    116 
    117         #region Mouse Hook Monitor
    118 
    119         /// <summary>
    120         /// Install the mouse hook.
    121         /// Default install mouse global hook - [14];
    122         /// </summary>
    123         /// <param name="hookType">Select mouse hook type.</param>
    124         public void InstallMouseHook(HookType hookType = HookType.WH_MOUSE_LL)
    125         {
    126             if (_mouseHookHandle == default(int))
    127             {
    128                 _mouseHookCallBack = new HookProcCallBack(this.MouseHookCallBack);
    129                 if (hookType == HookType.WH_MOUSE)
    130                 {
    131                     _mouseHookHandle = HookAPI.SetWindowsHookEx((int)hookType, _mouseHookCallBack, IntPtr.Zero, _currentThreadID);
    132                 }
    133                 else
    134                 {
    135                     _mouseHookHandle = HookAPI.SetWindowsHookEx((int)hookType, _mouseHookCallBack, _currentMoudleHandle, 0);
    136                 }
    137                 this.CheckHandleIsZero(_mouseHookHandle);
    138             }
    139         }
    140 
    141         private int MouseHookCallBack(int nCode, int wParam, IntPtr lParam)
    142         {
    143             MouseButtons mouseOperation = MouseButtons.None;
    144             Point mousePoint = (Point)Marshal.PtrToStructure(lParam, typeof(Point));
    145 
    146             switch (wParam)
    147             {
    148                 case (int)WindowsMessage.WM_LBUTTONDOWN:
    149                     mouseOperation = MouseButtons.Left;
    150                     this.InvokeMouseEvent(this.LeftMouseClickDown, mouseOperation, mousePoint);
    151                     break;
    152                 case (int)WindowsMessage.WM_LBUTTONUP:
    153                     mouseOperation = MouseButtons.Left;
    154                     this.InvokeMouseEvent(this.LeftMouseClickUp, mouseOperation, mousePoint);
    155                     break;
    156                 case (int)WindowsMessage.WM_LBUTTONDBLCLK:
    157                     mouseOperation = MouseButtons.Left;
    158                     this.InvokeMouseEvent(this.LeftMouseDoubleClick, mouseOperation, mousePoint);
    159                     break;
    160                 case (int)WindowsMessage.WM_RBUTTONDOWN:
    161                     mouseOperation = MouseButtons.Right;
    162                     this.InvokeMouseEvent(this.RightMouseClickDown, mouseOperation, mousePoint);
    163                     break;
    164                 case (int)WindowsMessage.WM_RBUTTONUP:
    165                     mouseOperation = MouseButtons.Right;
    166                     this.InvokeMouseEvent(this.RightMouseClickUp, mouseOperation, mousePoint);
    167                     break;
    168                 case (int)WindowsMessage.WM_RBUTTONDBLCLK:
    169                     mouseOperation = MouseButtons.Right;
    170                     this.InvokeMouseEvent(this.RightMouseDoubleClick, mouseOperation, mousePoint);
    171                     break;
    172                 case (int)WindowsMessage.WM_MOUSEMOVE:
    173                     this.InvokeMouseEvent(this.MouseMove, mouseOperation, mousePoint);
    174                     break;
    175                 case (int)WindowsMessage.WM_MOUSEWHEEL:
    176                     this.InvokeMouseEvent(this.MouseWheel, mouseOperation, mousePoint);
    177                     break;
    178             }
    179 
    180             return HookAPI.CallNextHookEx(_mouseHookHandle, nCode, wParam, lParam);
    181         }
    182 
    183         private void InvokeMouseEvent(MouseEventHandler mouseEvent, MouseButtons mouseButton, Point point)
    184         {
    185             if (mouseEvent != null)
    186             {
    187                 MouseEventArgs mouseArgs = new MouseEventArgs(mouseButton, 0, point.X, point.Y, 0);
    188                 mouseEvent(this, mouseArgs);
    189             }
    190         } 
    191 
    192         #endregion
    193 
    194         #region KeyBoaed Hook Monitor
    195 
    196         /// <summary>
    197         /// Install the keyboard hook.
    198         /// Default install keyboard global hook - [13].
    199         /// </summary>
    200         /// <param name="hookType">Select keyboard hook type.</param>
    201         public void InstallKeyBoardHook(HookType hookType = HookType.WH_KEYBOARD_LL)
    202         {
    203             if (_keyboardHookHandlel == default(int))
    204             {
    205                 _keyboardHookCallBack = new HookProcCallBack(this.KeyBoradHookCallBack);
    206                 if (hookType == HookType.WH_KEYBOARD)
    207                 {
    208                     _keyboardHookHandlel = HookAPI.SetWindowsHookEx((int)hookType, _keyboardHookCallBack, IntPtr.Zero, _currentThreadID);
    209                 }
    210                 else
    211                 {
    212                     _keyboardHookHandlel = HookAPI.SetWindowsHookEx((int)hookType, _keyboardHookCallBack, _currentMoudleHandle, 0);
    213                 }
    214                 this.CheckHandleIsZero(_keyboardHookHandlel);
    215             }
    216         }
    217 
    218         private int KeyBoradHookCallBack(int nCode, int wParam, IntPtr lParam)
    219         {
    220             if (nCode >= 0)
    221             {
    222                 CustomKeyBoard keyInfo = (CustomKeyBoard)Marshal.PtrToStructure(lParam, typeof(CustomKeyBoard));
    223 
    224                 if (this.KeyDown != null
    225                     && (wParam == (int)WindowsMessage.WM_KEYDOWN || wParam == (int)WindowsMessage.WM_SYSKEYDOWN))
    226                 {
    227                     this.InvokeKeyBoardEvent(this.KeyDown, (Keys)keyInfo.VirtualKeyCode);
    228                 }
    229 
    230                 if (this.KeyPress != null && wParam == (int)WindowsMessage.WM_KEYDOWN)
    231                 {
    232                     this.InvokeKeyBoardEvent(this.KeyPress, (Keys)keyInfo.VirtualKeyCode);
    233                 }
    234 
    235                 if (this.KeyUp != null
    236                     && (wParam == (int)WindowsMessage.WM_KEYUP || wParam == (int)WindowsMessage.WM_SYSKEYUP))
    237                 {
    238                     this.InvokeKeyBoardEvent(this.KeyUp, (Keys)keyInfo.VirtualKeyCode);
    239                 }
    240             }
    241 
    242             return HookAPI.CallNextHookEx(_keyboardHookHandlel, nCode, wParam, lParam);
    243         }
    244 
    245         private void InvokeKeyBoardEvent(CustomKeyEventHandler keyEvent, Keys keyData)
    246         {
    247             CustomKeyEventArgs customKeyArgs = new CustomKeyEventArgs(keyData);
    248             keyEvent(this, customKeyArgs);
    249         } 
    250 
    251         #endregion
    252 
    253         #region Common
    254 
    255         private void CheckHandleIsZero(int handle)
    256         {
    257             if (handle == 0)
    258             {
    259                 int errorID = Marshal.GetLastWin32Error();
    260                 throw new Win32Exception(errorID);
    261             }
    262         }
    263 
    264         public void UninstallHook()
    265         {
    266             if (_mouseHookHandle != default(int))
    267             {
    268                 if (HookAPI.UnhookWindowsHookEx(_mouseHookHandle))
    269                 {
    270                     _mouseHookHandle = default(int);
    271                 }
    272             }
    273             if (_keyboardHookHandlel != default(int))
    274             {
    275                 if (HookAPI.UnhookWindowsHookEx(_keyboardHookHandlel))
    276                 {
    277                     _keyboardHookHandlel = default(int);
    278                 }
    279             }
    280         } 
    281 
    282         #endregion
    283     }
    284 }
    View Code

      

      全部代码:下载

    好了就这么多了,已同步至:个人文章目录索引

  • 相关阅读:
    XML相关资源
    【翻译】Windows下文件的命名
    显示文件的16进制编码(C++)
    函数模板的匹配
    最新的flex4.1和as3.0的帮助文档
    Flash/Flex 框架简介—PureMVC
    textfield的诡异
    灵异的bug
    互联网公司的发展都在于专注和坚持。
    python内置数据类型
  • 原文地址:https://www.cnblogs.com/yangcaogui/p/3560012.html
Copyright © 2011-2022 走看看