22 4月 2020 ### 起因   前段时间在某宝上买了一款折叠蓝牙键盘,各方面都感觉不错,但是就有一个致命缺陷,F1~F11全部要配合Fn按键才起作用,单独按下F1~F11是功能按键,比如最常用到的F10是音量增加,F11锁屏。shit!对于一个VS开发人员来讲,无法顺畅的使用F10和F11,这也太难受了!于是在网上学习一番,发现可以使用HOOK来截获键盘点击事件,并阻止其传递下去。于是想到了自己写一个程序,来模拟F10和F11按键。 ### 什么是HOOK?   HOOK英文直译为钩子,从字面上理解,钩子就是想钩住些东西,在程序里可以利用钩子提前处理些Windows消息。   要理解钩子,首先要理解Windows的消息机制,Windows平台是基于事件驱动机制的,整个系统都是通过消息的传递来实现的。当进程有响应时(包括响应鼠标和键盘事件),则Windows会向应用程序发送一个消息给应用程序的消息队列,应用程序进而从消息队列中取出消息并发送给相应窗口进行处理。   Hook则是Windows消息处理机制的一个平台,应用程序可以在上面设置子程以监视指定窗口的某种消息,而且所监视的窗口可以是其他进程所创建的。当消息到达后,在目标窗口处理函数之前处理它。钩子机制允许应用程序截获处理window消息或特定事件。   所以Hook就可以实现在键盘/鼠标响应后,窗口处理消息之前,就对此消息进行处理,比如监听键盘输入,鼠标点击坐标等等。某些盗号木马就是Hook了指定的进程,从而监听键盘输入了什么内容,进而盗取账户密码。 ### C#实现HOOK   我们知道C#是运行在.NET平台之上,而且是基于CLR动态运行的,所以只能操作封装好的函数,且无法直接操作内存数据。而且在C#常用的功能中,并未封装Hook相关的类与方法,所以如果用C#实现Hook,必须采用调用WindowsAPI的方式进行实现。 WindowsAPI函数属于非托管类型的函数,我们在调用时必须遵循以下几步: 1、查找包含调用函数的DLL,如User32.dll,Kernel32.dll等。 2、将该DLL加载到内存中,并注明入口 3、将所需参数转化为C#存在的类型,如指针对应Intptr,句柄对应int类型等等 4、调用函数 我们本篇需要使用的函数有以下几个: SetWindowsHookEx 用于安装钩子 UnhookWindowsHookEx 用于卸载钩子 CallNextHookEx 执行下一个钩子 keybd_event 模拟一个按键消息 详细API介绍请参考MSDN官方声明 ###### 第一步:声明API函数 //使用此功能,安装了一个钩子 [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("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)] public static extern int CallNextHookEx(int idHook, int nCode, Int32 wParam, IntPtr lParam); //使用此功能,模拟一个按键消息的发出 [DllImport("user32.dll")] public static extern void keybd_event(byte bVk, byte bScan, int dwFlags, int dwExtraInfo); ###### 第二步:声明、定义。 public delegate int HookProc(int nCode, Int32 wParam, IntPtr lParam); static int hKeyboardHook = 0; //声明键盘钩子处理的初始值 //值在Microsoft SDK的Winuser.h里查询 public const int WH_KEYBOARD_LL = 13; //线程键盘钩子监听消息设为2,全局键盘监听消息设为13 HookProc KeyboardHookProcedure; //声明KeyboardHookProcedure作为HookProc类型 //键盘结构 [StructLayout(LayoutKind.Sequential)] public class KeyboardHookStruct { public int vkCode; //定一个虚拟键码。该代码必须有一个价值的范围1至254 public int scanCode; // 指定的硬件扫描码的关键 public int flags; // 键标志 public int time; // 指定的时间戳记的这个讯息 public int dwExtraInfo; // 指定额外信息相关的信息 }   这里说一下,HOOK分为线程钩子和全局钩子,线程钩子是指只截获当前线程的消息,而全局钩子则截获所有线程的消息。区别就在于,当最小化当前程序或者焦点聚焦到其他应用程序之后,钩子将截取不到键盘/鼠标的点击消息了,而全局钩子则仍能够截取到。所以我这边需要使用全局钩子。 ###### 第三步:写钩子子程   钩子子程就是钩子截取到键盘消息之后,我们要处理的事情。我这边需要处理的是阻止F10对应的功能按键(即音量增加)的消息传递,并发送真正F10的按键的消息下去。 private int KeyboardHookProc(int nCode, Int32 wParam, IntPtr lParam) { KeyboardHookStruct MyKeyboardHookStruct = (KeyboardHookStruct)Marshal.PtrToStructure(lParam, typeof(KeyboardHookStruct)); if (MyKeyboardHookStruct.vkCode == 175 && (wParam == WM_KEYDOWN || wParam == WM_SYSKEYDOWN)) { keybd_event(121, 0, 0, 0);//发送F10按键消息 return 1;//阻止功能按键消息传递 } if (MyKeyboardHookStruct.vkCode == 174 && (wParam == WM_KEYDOWN || wParam == WM_SYSKEYDOWN)) { keybd_event(122, 0, 0, 0);//发送F11按键消息 return 1;//阻止功能按键消息传递 } //如果返回1,则结束消息,这个消息到此为止,不再传递。 //如果返回0或调用CallNextHookEx函数则消息出了这个钩子继续往下传递,也就是传给消息真正的接受者 return CallNextHookEx(hKeyboardHook, nCode, wParam, lParam); }   通过一些方法,获取到了音量增加键(即F10对应功能键)的CODE是175,音量减小键(F9)的CODE是174,真正的F10按键CODE是121,F11是122。所以在截获到175的消息时时,发送121(即F10),然而此时又冒出一个缺陷 :sob:,F11键对应的功能按键不知坏了还是咋地,不发出消息,(可能需要和其他按键一起组合才能使用吧),最后没办法,只能使用F9的功能按键,来代替F11。 ###### 第四步:安装钩子、卸载钩子   新建一个winform程序,用来挂载钩子    启动钩子与停止钩子代码 KeyboardHook k_hook = new KeyboardHook(); bool hookstart = true; private void Button1_Click(object sender, EventArgs e) { try { k_hook.Start();//安装键盘钩子 hookstart = true; button1.Enabled = false; button2.Enabled = true; } catch(Exception ex) { MessageBox.Show(ex.ToString()); } } private void Button2_Click(object sender, EventArgs e) { try { k_hook.Stop(); hookstart = false; button1.Enabled = true; button2.Enabled = false; } catch (Exception ex) { MessageBox.Show(ex.ToString()); } }   最后程序还是有个不足之处,本想着将HOOK挂载到windows服务上面,可是不知道为啥不起作用,似乎也没有人这样做过,猜测可能是由于windows服务无法与桌面沟通??总之最后还是写了一个winform来挂载。 ### 最后有个最最最重要的地方,程序一定要使用管理员运行!!!被这个坑了我好长时间!!! ### 5/12更新(增加鼠标消息监听,超级实用)   现在大多数鼠标都有6~7个按键:左右按键、滚轮键、开关灯关键、切换速率键,以及左侧的前进后退键。对于大多数VS开发人员来讲,左侧的后退键还是经常用到的,前进键也用,不过很少。所以我突发奇想,何不把前进键改为F12键,这样子看代码时,充分解放了左手需要频繁去按F12键的苦恼。   操作步骤和安装键盘钩子几乎一样,只不过注册鼠标钩子时,编码为14(键盘的是13)。   附上注册键盘和鼠标hook代码: ```csharp public void Start() { // 安装键盘钩子 if (hKeyboardHook == 0) { KeyboardHookProcedure = new HookProc(KeyboardHookProc); hKeyboardHook = SetWindowsHookEx(WH_KEYBOARD_LL, KeyboardHookProcedure, GetModuleHandle(System.Diagnostics.Process.GetCurrentProcess().MainModule.ModuleName), 0); if (hKeyboardHook == 0) { Stop(); throw new Exception("安装键盘钩子失败"); } } // 安装鼠标钩子 if (hMouseHook == 0) { MouseHookProcedure = new HookProc(KeyboardHookProc); hMouseHook = SetWindowsHookEx(WH_MOUSE_LL, MouseHookProc, GetModuleHandle(System.Diagnostics.Process.GetCurrentProcess().MainModule.ModuleName), 0); if (hMouseHook == 0) { Stop(); throw new Exception("安装键盘钩子失败"); } } } ``` [点击下载最新源码](https://www.seghart.com/content/BOWKeyBoardHook.rar "点我下载") 非特殊说明,本文版权归 强强 所有,转载请注明出处. 本文标题: C#使用HOOK截获键盘消息 本文网址: https://seghart.com/Article/Read/93 延伸阅读 发表评论 提交留言