Hook入门
2014-07-24
基本概念[1]
Windows消息机制[5]
Windows操作系统是建立在事件驱动机制之上的,系统各部分之间的沟通也都是通过消息的相互传递而实现的。但在通常情况下,应用程序只能处理来自进程内部的消息或是从其他进程发过来的消息(借助进程间通信技术,如剪贴板,管道等)。
图1 Windows应用程序消息处理机制
(1)操作系统接收到应用程序的窗口消息,将消息投递到该应用程序的消息队列中。[5]
(2)应用程序在消息循环中调用GetMessage 函数从消息队列中取出一条一条的消息。取出消息后,应用程序可以对消息进行一些预处理,例如,放弃对某些消息的响应,或者调用TranslateMessage 产生新的消息。
(3)应用程序调用DispatchMessage,将消息回传给操作系统。消息是由MSG 结构体对象来表示的,其中就包含了接收消息的窗口的句柄。因此,DispatchMessage 函数总能进行正确的传递。DispatchMessage 函数分派一个消息到窗口过程,由窗口过程函数对消息进行处理。DispachMessage 实际上是将消息回传给操作系统,由操作系统调用窗口过程函数对消息进行处理(响应)。”
(4)系统利用WNDCLASS 结构体的lpfnWndProc 成员保存的窗口过程函数的指针调用窗口过程,对消息进行处理(即“系统给应用程序发送了消息”)。”
Hook(钩子)
如果需要对在进程外传递的消息进行拦截处理就必须采取一种称为HOOK的技术。HOOK作为Windows操作系统中非常重要的一种系统接口,用它可以轻松截获并处理在其他应用程序之间传递的消息,并由此可以完成一些普通应用程序难以实现的特殊功能。
Hook就是在图1中的第(2)步和第(4)步之间进行的额外工作(个人猜想,有待考证)。
运行机制[1]
钩子实际上是一个处理消息的程序段,通过系统调用,把它挂入系统。
每一个HOOK实质上都由系统维护着一个指针列表,其指针指向HOOK的各个处理函数,我们称之为HOOK子程。当与指定的Hook类型关联的消息发生时,系统就把这个消息传递到Hook子程。一些Hook子程可以只监视消息,或者修改消息,或者停止消息的前进,避免这些消息传递到下一个Hook子程或者目的窗口。
Hook子程是一个应用程序定义的回调函数(CALLBACK Function),不能定义成某个类的成员函数,只能定义为普通的C函数。用以监视系统或某一特定类型的事件,这些事件可以是与某一特定线程关联的,也可以是系统中所有线程的事件。
最近安装的Hook放在链的开始,而最早安装的钩子放在最后,也就是后加入的先获得控制权。
Windows 并不要求Hook子程的卸载顺序一定得和安装顺序相反。每当有一个钩子被卸载,Windows 便释放其占用的内存,并更新整个Hook链表。如果程序安装了钩子,但是在尚未卸载钩子之前就结束了,那么系统会自动为它做卸载钩子的操作。
在使用钩子时根据其监视范围的不同可以将其分为全局钩子和线程钩子两大类,其中线程钩子指定某个线程ID(可以是当前线程),只能监视该线程;全局钩子可以对同一个窗口下的所有线程进行监视。这里的全局HOOK的本质还是由触发HOOK机制的线程调用自身进程空间中的代码进行处理,所以我们的HOOK子程代码必须映射进该线程所在的进程的地址空间,即通过DLL的方式实现。下面描述实现过程:
首先我们编写HOOK驱动器,将HOOK.dll映射进内存中,安装好HOOK后,进行HOOK监视:
核心函数[1]
使用Windows HOOK所需要的核心函数不多,只有四个:
- SetWindowsHookEx():安装一个HOOK
- HOOK 子程:HOOK的处理函数,如GetMsgProc, KeyboardProc等
- CallNextHookEx():调用HOOK链的下一个HOOK子程
- UnhookWindowsHookEx():卸载一个HOOK
函数SetWindowsHookEx:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 HHOOK SetWindowsHookEx( 2 //idHook用来标识HOOK类型,比如鼠标信息用WH_MOUSE,键盘消息用WH_KEYBOARD等 3 int idHook, 4 //lpfn指向一个具体的HOOK子程,用于实际处理拦截的消息 5 HOOKPROC lpfn, 6 //hMod用来标识HOOK子程所在的模块, 7 //如果是一个全局HOOK,则是一个载入内存的DLL句柄(使用GetModuleHandle得到);如果是一个内部线程HOOK,则为NULL即可。 8 HINSTANCE hMod, 9 //dwThread指明HOOK的范围, 10 //如果是0则表示监视运行在同一个窗口下的所有线程,否则指定一个具体的线程ID即可。 11 DWORD dwThreadId 12 );
函数UnhookWindowsHookEx:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 BOOL UnhookWindowsHookEx( 2 //参数为SetWindowsHookEx()的返回值 3 HHOOK hhk 4 );
函数CallNextHookEx
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 LRESULT CallNextHookEx( 2 //参数为SetWindowsHookEx()的返回值 3 HHOOK hhk, 4 //NCode为传给钩子过程的事件代码。 5 int nCode, 6 //wParam和lParam 分别是传给钩子子程的wParam值,其具体含义与钩子类型有关。 7 WPARAM wParam, 8 LPARAM lParam 9 );
HOOK 子程,函数KeyboardProc:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 LRESULT CALLBACK KeyboardProc( 2 int code, 3 WPARAM wParam, 4 LPARAM lParam 5 );
C# hook示例
step 1:创建一个WinForm应用程序,把与hook相关的方法放在一个文件‘GlobalHook.cs’中:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 using System; 2 3 using System.Runtime.InteropServices; 4 using System.Reflection; 5 using System.Windows.Forms; 6 7 namespace Test 8 { 9 //Declare wrapper managed KeyboardHookStruct class. 10 [StructLayout(LayoutKind.Sequential)] 11 public class KeyboardHookStruct 12 { 13 public int vkCode; //Specifies a virtual-key code. The code must be a value in the range 1 to 254. 14 public int scanCode; // Specifies a hardware scan code for the key. 15 public int flags; // Specifies the extended-key flag, event-injected flag, context code, and transition-state flag. 16 public int time; // Specifies the time stamp for this message. 17 public int dwExtraInfo; // Specifies extra information associated with the message. 18 } 19 20 public class GlobalHook 21 { 22 #region native methods 23 [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)] 24 public static extern int SetWindowsHookEx(int idHook, GlobalHookProc lpfn, IntPtr hInstance, int threadId); 25 26 [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)] 27 public static extern bool UnhookWindowsHookEx(int idHook); 28 29 [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)] 30 public static extern int CallNextHookEx(int idHook, int nCode, Int32 wParam, IntPtr lParam); 31 #endregion 32 33 #region fileds 34 private const int WM_KEYDOWN = 0x100; 35 private const int WM_SYSKEYDOWN = 0x104; 36 37 static int hKeyboardHook = 0; //Declare keyboard hook handle as int. 38 //values from Winuser.h in Microsoft SDK. 39 public const int WH_KEYBOARD_LL = 13; //keyboard hook constant 40 #endregion 41 42 #region delegates and events 43 public event KeyEventHandler KeyDown; 44 45 public delegate int GlobalHookProc(int nCode, Int32 wParam, IntPtr lParam); 46 GlobalHookProc KeyboardHookProcedure; //Declare KeyboardHookProcedure as HookProc type. 47 #endregion 48 49 #region methods 50 public void Start() 51 { 52 // install Keyboard hook 53 if (hKeyboardHook == 0) 54 { 55 KeyboardHookProcedure = new GlobalHookProc(KeyboardHookProc); 56 57 hKeyboardHook = SetWindowsHookEx(WH_KEYBOARD_LL, 58 KeyboardHookProcedure, 59 //IntPtr.Zero, 60 Marshal.GetHINSTANCE(Assembly.GetExecutingAssembly().GetModules()[0]), 61 0); 62 63 //If SetWindowsHookEx fails. 64 if (hKeyboardHook == 0) 65 { 66 throw new Exception("SetWindowsHookEx ist failed."); 67 } 68 } 69 } 70 71 public void Stop() 72 { 73 bool retKeyboard = true; 74 75 if (hKeyboardHook != 0) 76 { 77 retKeyboard = UnhookWindowsHookEx(hKeyboardHook); 78 hKeyboardHook = 0; 79 } 80 81 //If UnhookWindowsHookEx fails. 82 if (!retKeyboard) 83 throw new Exception("UnhookWindowsHookEx failed."); 84 } 85 86 private int KeyboardHookProc(int nCode, Int32 wParam, IntPtr lParam) 87 { 88 // it was ok and someone listens to events 89 if ((nCode >= 0) && (KeyDown != null)) 90 { 91 KeyboardHookStruct MyKeyboardHookStruct = 92 (KeyboardHookStruct)Marshal.PtrToStructure(lParam, 93 typeof(KeyboardHookStruct)); 94 95 // raise KeyDown 96 if (KeyDown != null && (wParam == WM_KEYDOWN || wParam == WM_SYSKEYDOWN)) 97 { 98 Keys keyData = (Keys)MyKeyboardHookStruct.vkCode; 99 KeyEventArgs e = new KeyEventArgs(keyData); 100 KeyDown(this, e); 101 } 102 } 103 //消息不再传递给下一个HOOK子程,也不会再发送给目的窗口 104 return 1; 105 //消息传递给下一个HOOK子程 106 //return CallNextHookEx(hKeyboardHook, nCode, wParam, lParam); 107 } 108 #endregion 109 } 110 111 }
Step 2:在现有个Form1.cs[Design]界面中拖入一个控件‘Lable’,它的代码文件‘Form1.cs’内容如下:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
1 using System; 2 using System.Text; 3 using System.Windows.Forms; 4 5 namespace Test 6 { 7 public partial class Form1 : Form 8 { 9 private GlobalHook hook; 10 11 public Form1() 12 { 13 InitializeComponent(); 14 } 15 private void MyKeyDown(object sender, System.Windows.Forms.KeyEventArgs e) 16 { 17 this.label1.Text = e.KeyCode.ToString(); 18 } 19 20 private void FormMain_Load(object sender, EventArgs e) 21 { 22 //全局钩子 23 hook = new GlobalHook(); 24 hook.KeyDown += new KeyEventHandler(this.MyKeyDown); 25 hook.Start(); 26 } 27 28 private void FormMain_FormClosing(object sender, FormClosingEventArgs e) 29 { 30 hook.Stop(); 31 } 32 } 33 }
Step 3:为Form1界面把Step 2 加入处理方法注册到事件:FormClosing = FormMainClosing,Load=FormMain_Load。
Step 4:Build project,打开生成的Test.exe,当我们操作键盘时,无论Test.exe当前是active状态,或notepad当前是active状态,Test.exe的label控件都会显示出你键盘输入的字符。
注意:
- 当debug时,会在step 1代码66行抛错。
- 由于在HOOK子程KeyboardHookProc最后return 1,即消息不再传递给下一个HOOK子程,也不会再发送给目的窗口,因此,在若想notepad用键盘输入信息,它不会显示。
参考
[2] HOOK专题[来自微软中国社区]
[3] HOOK技术的一些简单总结
[4] windows钩子程序入门(一)