技术心得记录:深入分析C#键盘勾子(Hook)拦截器,屏蔽键盘活动的详解

简介: 技术心得记录:深入分析C#键盘勾子(Hook)拦截器,屏蔽键盘活动的详解

钩子(Hook),是Windows消息处理机制的一个平台,应用程序可以在上面设置子程以监视指定窗口的某种消息,而且所监视的窗口可以是其他进程所创建的。当消息到达后,在目标窗口处理函数之前处理它。钩子机制允许应用程序截获处理window消息或特定事件。


钩子实际上是一个处理消息的程序段,通过系统调用,把它挂入系统。每当特定的消息发出,在没有到达目的窗口前,钩子程序就先捕获该消息,亦即钩子函数先得到控制权。这时钩子函数即可以加工处理(改变)该消息,也可以不作处理而继续传递该消息,还可以强制结束消息的传递。


运行机制


1、钩子链表和钩子子程:


每一个Hook都有一个与之相关联的指针列表,称之为钩子链表,由系统来维护。这个列表的指针指向指定的,应用程 序定义的,被Hook子程调用的回调函数,也就是该钩子的各个处理子程。当与指定的Hook类型关联的消息发生时,系统就把这个消息传递到Hook子程。 一些Hook子程可以只监视消息,或者修改消息,或者停止消息的前进,避免这些消息传递到下一个Hook子程或者目的窗口。最近安装的钩子放在链的开始, 而最早安装的钩子放在最后,也就是后加入的先获得控制权。


Windows 并不要求钩子子程的卸载顺序一定得和安装顺序相反。每当有一个钩子被卸载,Windows 便释放其占用的内存,并更新整个Hook链表。如果程序安装了钩子,但是在尚未卸载钩子之前就结束了,那么系统会自动为它做卸载钩子的操作。


钩子子程是一个应用程序定义的回调函数(CALLBACK Function),不能定义成某个类的成员函数,只能定义为普通的C函数。用以监视系统或某一特定类型的事件,这些事件可以是与某一特定线程关联的,也可以是系统中所有线程的事件。


钩子子程必须按照以下的语法:


复制代码代码如下:


LRESULT CALLBACKHookProc


(


int nCode,


WPARAM wParam,


LPARAM lParam


);


HookProc是应用程序定义的名字。


nCode参数是Hook代码,Hook子程使用这个参数来确定任务。这个参数的值依赖于Hook类型,每一种Hook都有自己的Hook代码特征字符集。


wParam和lParam参数的值依赖于Hook代码,但是它们的典型值是包含了关于发送或者接收消息的信息。


2、钩子的安装与释放:


使用API函数SetWindowsHookEx()把一个应用程序定义的钩子子程安装到钩子链表中。 SetWindowsHookEx函数总是在Hook链的开头安装Hook子程。当指定类型的Hook监视的事件发生时,系统就调用与这个Hook关联的 Hook链的开头的Hook子程。每一个Hook链中的Hook子程都决定是否把这个事件传递到下一个Hook子程。Hook子程传递事件到下一个 Hook子程需要调用CallNextHookEx函数。


复制代码代码如下:


HHOOKSetWindowsHookEx(


int idHook, // 钩子的类型,即它处理的消息类型


HOOKPROC lpfn, // 钩子子程的地址指针。如果dwThreadId参数为0


// 或是一个由别的进程创建的线程的标识,


// lpfn必须指向DLL中的钩子子程。


// 除此以外,lpfn可以指向当前进程的一段钩子子程代码。


// 钩子函数的入口地址,当钩子钩到任何消息后便调用这个函数。


HINSTANCE hMod, // 应用程序实例的句柄。标识包含lpfn所指的子程的


DLL。


// 如果dwThreadId 标识当前进程创建的一个线程,


// 而且子程代码位于当前进程,hMod必须为NULL。


// 可以很简单的设定其为本应用程序的实例句柄。


DWORD dwThreadId // 与安装的钩子子程相关联的线程的标识符。


// 如果为0,钩子子程与所有的线程关联,即为全局钩子。


);


函数成功则返回钩子子程的句柄,失败返回NULL。


以上所说的钩子子程与线程相关联是指在一钩子链表中发给该线程的消息同时发送给钩子子程,且被钩子子程先处理。


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


复制代码代码如下:


LRESULTCallNextHookEx


(


HHOOK hhk;


int nCode;


WPARAM wParam;


LPARAM lParam;


);


hhk为当前钩子的句柄,由SetWindowsHookEx()函数返回。


NCode为传给钩子过程的事件代码。


wParam和lParam 分别是传给钩子子程的wParam值,其具体含义与钩子类型有关。


钩子函数也可以通过直接返回TRUE来丢弃该消息,并阻止该消息的传递。否则的话,其他安装了钩子的应用程序将不会接收到钩子的通知而且还有可能产生不正确的结果。


钩子在使用完之后需要用UnHookWindowsHookEx()卸载,否则会造成麻烦。释放钩子比较简单,UnHookWindowsHookEx()只有一个参数。函数原型如下:


复制代码代码如下:


UnHookWindowsHookEx


(


HHOOK hhk;


);


函数成功返回TRUE,否则返回FALSE。


3、一些运行机制:


在Win16环境中,DLL的全局数据对每个载入它的进程来说都是相同的;而在Win32环境中,情况却发生了变化,DLL函数中的代码所创建的任何对象(包括变量)都归调用它的线程或进程所有。当进程在载入DLL时,操作系统自动把DLL地址映射到该进程的私有空间,也就是进程的虚拟地址空间,而且也复制该DLL的全局数据的一份拷贝到该进程空间。也就是说每个进程所拥有的相同的DLL的全局数据,它们的名称相同,但其值却并不一定是相同的,而且是互不干涉的。


因此,在Win32环境下要想在多个进程中共享数据,就必须进行必要的设置。在访问同一个Dll的各进程 之间共享存储器是通过存储器映射文件技术实现的。也可以把这些需要共享的数据分离出来,放置在一个独立的数据段里,并把该段的属性设置为共享。必须给这些 变量赋初值,否则编译器会把没有赋初始值的变量放在一个叫未被初始化的数据段中。


#pragma data_seg预处理指令用于设置共享数据段。例如:


复制代码代码如下:


#pragmadata_seg("SharedDataName")


HHOOK hHook=NULL;


#pragma data_seg()


在#pragma data_seg("SharedDataName")和#pragma data_seg()之间的所有变量将被访问该Dll的所有进程看到和共享。再加上一条指令#pragmacomment(linker,"/section:.SharedDataName,rws"),那么这个数据节中的数据可以在所有DLL的实例之间共 享。所有对这些数据的操作都针对同一个实例的,而不是在每个进程的地址空间中都有一份。


当进程隐式或显式调用一个动态库里的函数时,系统都要把这个动态库映射到这个进程的虚拟地址空间里(以下简称"地址空间")。这使得DLL成为进程的一部分,以这个进程的身份执行,使用这个进程的堆栈。


4、系统钩子与线程钩子:


SetWindowsHookEx()函数的最后一个参数决定了此钩子是系统钩子还是线程钩子。


线程勾子用于监视指定线程的事件消息。线程勾子一般在当前线程或者当前线程派生的线程内。


系统勾子监视系统中的所有线程的事件消息。因为系统勾子会影响系统中所有的应用程序,所以勾子函数必须放在独立的动态链接库(DLL) 中。系统自动将包含"钩子回调函数"的DLL映射到受钩子函数影响的所有进程的地址空间中,即将这个DLL注入了那些进程。


几点说明:


(1)如果对于同一事件(如鼠标消息)既安装了线程勾子又安装了系统勾子,那么系统会自动先调用线程勾子,然后调用系统勾子。


(2)对同一事件消息可安装多个勾子处理过程,这些勾子处理过程形成了勾子链。当前勾子处理结束后应把勾子信息传递给下一个勾子函数。


(3)勾子特别是系统勾子会消耗消息处理时间,降低系统性能。只有在必要的时候才安装勾子,在使用完毕后要及时卸载


C#键盘勾子(Hook)拦截器,屏蔽键盘活动源代码:


复制代码代码如下:


using System;


using System.Collections.Generic;


using System.ComponentModel;


using System.Data;


using System.Drawing;


using System.Text;


using System.Windows.Forms;


using System.Runtime.InteropServices;


using System.Reflection;


using System.Diagnostics;


using Microsoft.Win32;


namespace Vjsdn.Tech.KeyboardHook


{


public partial class frmKeyboardHook : Form


{


//勾子管理类


private KeyboardHookLib _keyboardHook = null;


public frmKeyboardHook()


{


InitializeComponent();


}


private void button1_Click(object sender, EventArgs e)


{


//安装勾子


_keyboardHook = new KeyboardHookLib();


_keyboardHook.InstallHook(this.OnKeyPress);


}


private void button2_Click(object sender, EventArgs e)


{


//取消勾子


if (_keyboardHook != null) _keyboardHook.UninstallHook();


}


///


/// 客户端键盘捕捉事件.


///


///


由Hook程序发送的按键信息


///


是否拦截


public void OnKeyPress(KeyboardHookLib.HookStruct hookStruct, out bool handle)


{


handle = false; //预设不拦截任何键


if (hookStruct.vkCode == 91) // 截获左win(开始菜单键)


{


handle = true;


}


if (hookStruct.vkCode == 92)// 截获右win


{


handle = true;


}


//截获Ctrl+Esc


if (hookStruct.vkCode == (int)Keys.Escape && (int)Control.ModifierKeys == (int)Keys.Control)


{


handle = true;


}


//截获alt+f4


if (hookStruct.vkCode == (int)Keys.F4 && (int)Control.ModifierKeys == (int)Keys.Alt)


{


handle = true;


}


//截获alt+tab


if (hookStruct.vkCode == (int)Keys.Tab && (int)Control.ModifierKeys == (int)Keys.Alt)


{


handle = true;


}


//截获F1


if (hookStruct.vkCode == (int)Keys.F1)


{


handle = true;


}


//截获Ctrl+Alt+Delete


if ((int)Control.ModifierKeys == (int)Keys.Control + (int)Keys.Alt + (int)Keys.Delete)


{


handle = true;


}


//如果键A~Z


if (hookStruct.vkCode >= (int)Keys.A && hookStruct.vkCode <= (int)Keys.Z)


{


//挡掉B键


if (hookStruct.vkCode == (int)Keys.B)


hookStruct.vkCode = (int)Keys.None; //设键为0


handle = true;


}


Keys key = (Keys)hookStruct.vkCode;


label1.Text = "你按下:" + (key == Keys.None ? "" : key.ToString());


}


}


}


KeyboardHookLib.cs


键盘管理类


复制代码代码如下:


using System;


using System.Collections.Generic;


using System.Text;


using System.Runtime.InteropServices;


using System.Reflection;


using System.Diagnostics;


using Microsoft.Win32;


using System.Windows.Forms;


namespace Vjsdn.Tech.KeyboardHook


{


///


/// 键盘Hook管理类, by www.vjsdn.com 易学原创


///


public class KeyboardHookLib


{


private const int WH_KEYBOARD_LL = 13; //键盘


//键盘处理//代码效果参考:http://hnjlyzjd.com/xl/wz_24930.html

事件委托 ,当捕获键盘输入时调用定义该委托的方法.

private delegate int HookHandle(int nCode, int wParam, IntPtr lParam);


//客户端键盘处理事件


public delegate void ProcessKeyHandle(HookStruct param, out bool handle);


//接收SetWindowsHookEx返回值


private static int _hHookValue = 0;


//勾子程序处理事件


private HookHandle _KeyBoardHookProcedure;


//Hook结构


【StructLayout(LayoutKind.Sequential)】


public class HookStruct


{


public int vkCode;


public int scanCode;


public int flags;


public int time;


public int dwExtraInfo;


}


//设置钩子


【DllImport("user32.dll")】


private static extern int SetWindowsHookEx(int idHook, HookHandle lpfn, IntPtr hInstance, int threadId);


//取消钩子


【DllImport("user32.dll",//代码效果参考:http://hnjlyzjd.com/xl/wz_24928.html

CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)】

private static extern bool UnhookWindowsHookEx(int idHook);


//调用下一个钩子


【DllImport("user32.dll")】


private static extern int CallNextHookEx(int idHook, int nCode, int wParam, IntPtr lParam);


//获取当前线程ID


【DllImport("kernel32.dll")】


private static extern int GetCurrentThreadId();


//Gets the main module for the associated process.


【DllImport("kernel32.dll")】


private static extern IntPtr GetModuleHandle(string name);


private IntPtr _hookWindowPtr = IntPtr.Zero;


//构造器


public KeyboardHookLib() { }


//外部调用的键盘处理事件


private static ProcessKeyHandle _clientMethod = null;


///


/// 安装勾子


///


///


外部调用的键盘处理事件


public void InstallHook(ProcessKeyHandle clientMethod)


{


_clientMethod = clientMethod;


// 安装键盘钩子


if (_hHookValue == 0)


{


_KeyBoardHookProcedure = new HookHandle(OnHookProc);


_hookWindowPtr = GetModuleHandle(Process.GetCurrentProcess().MainModule.ModuleName);


_hHookValue = SetWindowsHookEx(


WH_KEYBOARD_LL,


_KeyBoardHookProcedure,


_hookWindowPtr,


0);


//如果设置钩子失败.


if (_hHookValue == 0) UninstallHook();


}


}


//取消钩子事件


public void UninstallHook()


{


if (_hHookValue != 0)


{


bool ret = UnhookWindowsHookEx(_hHookValue);


if (ret) _hHookValue = 0;


}


}


//钩子事件内部调用,调用_clientMethod方法转发到客户端应用。


private static int OnHookProc(int nCode, int wParam, IntPtr lParam)


{


if (nCode >= 0)


{


//转换结构


HookStruct hookStruct = (HookStruct)Marshal.PtrToStructure(lParam, typeof(HookStruct));


if (_clientMethod != null)


{


bool handle = false;


//调用客户提供的事件处理程序。


_clientMethod(hookStruct, out handle);


if (handle) return 1; //1:表示拦截键盘,return 退出


}


}


return CallNextHookEx(_hHookValue, nCode, wParam, lParam);


}


}


}

相关文章
|
3天前
|
关系型数据库 C# 数据库
技术笔记:MSCL超级工具类(C#),开发人员必备,开发利器
技术笔记:MSCL超级工具类(C#),开发人员必备,开发利器
|
4天前
|
C#
技术经验分享:C#DUID的用法及取得整数的几个方法
技术经验分享:C#DUID的用法及取得整数的几个方法
11 1
|
4天前
|
C#
技术经验分享:c#拆分字符串英文和数字(包括国外所以文字)
技术经验分享:c#拆分字符串英文和数字(包括国外所以文字)
技术经验分享:c#拆分字符串英文和数字(包括国外所以文字)
|
4天前
|
C#
技术经验分享:C#之打印乘法表
技术经验分享:C#之打印乘法表
技术经验分享:C#之打印乘法表
|
27天前
|
C# Windows
C# 串口关闭时主界面卡死原因分析
串口程序关闭导致界面卡死的原因是主线程与辅助线程间的死锁。问题出在`SerialPort.Close()`方法与`DataReceived`事件处理程序。`DataReceived`事件在`lock (stream)`块中执行,而`Close()`方法会关闭`SerialStream`并锁定自身。当辅助线程处理数据并尝试更新UI时,UI线程因调用`Close()`被阻塞,造成死锁。解决办法是让`DataReceived`事件处理程序使用`this.BeginInvoke()`异步更新界面,避免等待UI线程,从而防止死锁。
|
3天前
|
数据采集 XML 存储
技术经验分享:C#构造蜘蛛爬虫程序
技术经验分享:C#构造蜘蛛爬虫程序
|
3天前
|
存储 C# 索引
技术经验分享:C#入门详解(8)
技术经验分享:C#入门详解(8)
|
3天前
|
Java BI C#
技术笔记:SM4加密算法实现Java和C#相互加密解密
技术笔记:SM4加密算法实现Java和C#相互加密解密
|
3天前
|
监控 C#
技术经验解读:【转】c#实现魔兽(warIII)中显血和改键功能(附源码)(不影响聊天打字)
技术经验解读:【转】c#实现魔兽(warIII)中显血和改键功能(附源码)(不影响聊天打字)
|
3天前
|
机器学习/深度学习 开发框架 .NET
技术经验解读:【c#】反射真的很可怕吗?
技术经验解读:【c#】反射真的很可怕吗?