钩子原理 Windows下的应用程序大部分是基于消息模式机制的,一些CUI程序不基于消息。Windows下应用程序都有一个消息函数,根据不同的消息来完成不同的功能。Windows提供的钩子机制是用来截获监视系统中的消息。不同的钩子可以处理不同信息。
钩子分为局部钩子和全局钩子。局部钩子是针对一个线程的,而全局钩子是针对整个操作系统内基于消息机制的应用程序的。全局钩子需要使用DLL文件,DLL文件里存放了钩子函数的代码。
安装全局钩子后,只要进程接收到可以发出钩子的消息后,全局钩子的DLL文件会被操作系统自动或强行的加载到该进程中。由此可见,设置消息钩子也是一种可以进行DLL注入的方法。
钩子函数 钩子函数在系统消息触发时被系统调用 。在某个事件触发后,钩子函数捕获它并完成一些操作,是一段用以处理系统消息的程序
相关API函数 1 2 3 4 5 6 LRESULT CALLBACK HookProc ( int nCode, WPARAM wParam, LPARAM lParam, );
参数nCode:钩子代码,钩子子程通过该代码来决定执行什么动作。该值取决于钩子的类型,每种类型都拥有自己特有的钩子代码集合。
参数wParam和lParam 的值,都取决于钩子代码。但是一般都包含发送或者传递的消息的信息。
SetWindowsHookEx 返回一个钩子句柄
1 2 3 4 5 6 HHOOK WINAPI SetWindowsHookEx ( _In_ int idHook, _In_ HOOKPROC lpfn, _In_ HINSTANCE hMod, _In_ DWORD dwThreadId ) ;
lpfn:指定HOOK函数的地址。如果dwThread参数被设置为0或者被设置为一个进程中的线程ID,则该回调HOOK函数只能在DLL文件中。如果dwThread为当前进程中的线程ID,则这个回调函数可以在当前进程中也可以在DLL中。
hMod:钩子函数所在模块的句柄。lpfn所在的模块的句柄,如果dwThreadId为当前进程中的线程ID,而且lpfn所指向的函数在当前进程中,那么hMod被设置为NULL
dwThreadId:需要被挂钩的线程ID号(指定的话为局部钩子),如果设置为0表示在基于消息机制的所有的线程挂钩(全局钩子),如果指定为具体ID好,那么表示要在指定的线程中进行挂钩。这个参数影响上边两个参数的取值,决定了该钩子属于全局钩子还是局部钩子。
idHook:钩子的类型
UnhookWindowsEx 移除先前用SetWindowsHookEx安装的钩子,
1 2 3 BOOL UnhookWindowsHookEx ( HHOOK hhk ) ;
可以多次反复安装钩子,而且可以安装多个同样类型的钩子。这样会形成一条钩子链,最后安装的钩子会首先截获到消息,当该钩子对消息处理完毕以后会选择返回,或者继续传递消息。通常情况下,为了消息可以传达到目标窗口,我们会选择将消息继续传递
CallNextHookEx 使消息继续传递,第一个参数为钩子句柄,后面三个为钩子函数的参数
1 2 3 4 5 6 LRESULT CallNextHookEx ( HHOOK hhk, int nCode, WPARAM wParam, LPARAM lParam ) ;
GetKeyNameText GetKeyNameText函数检索表示键的名称的字符串。
1 2 3 4 5 int GetKeyNameTextA ( LONG lParam, LPSTR lpString, int cchSize ) ;
键盘钩子实例 功能为用Messagebox显示按下键的字符。既然要截获键盘消息,那么肯定是截获系统范围内的键盘消息,因此需要安装全局钩子,这样就需要DLL文件支持。
首先新建DLL文件,定义两个导出函数和两个全局变量
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 #include "pch.h" #include <Windows.h> extern "C" __declspec(dllexport) void SetHookOn () ;extern "C" __declspec(dllexport) void SetHookOff () ; HHOOK g_Hook = NULL ; HINSTANCE g_Inst = NULL ;LRESULT CALLBACK KeyboardProc ( int code, WPARAM wParam, LPARAM lParam ) { if (code < 0 ) { return CallNextHookEx(g_Hook, code, wParam, lParam); } if (code == HC_ACTION && lParam > 0 ) { TCHAR szBuf[MAXBYTE] = { 0 }; GetKeyNameText(lParam, szBuf, MAXBYTE); MessageBox(NULL , szBuf, NULL , MB_OK); } return CallNextHookEx(g_Hook, code, wParam, lParam); }void SetHookOn () { g_Hook = SetWindowsHookEx(WH_KEYBOARD, KeyboardProc, g_Inst, 0 ); }void SetHookOff () { UnhookWindowsHookEx(g_Hook); }BOOL APIENTRY DllMain ( HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved ) { g_Inst = (HMODULE)hModule; switch (ul_reason_for_call) { case DLL_PROCESS_ATTACH: case DLL_THREAD_ATTACH: case DLL_THREAD_DETACH: case DLL_PROCESS_DETACH: break ; } return TRUE; }
在DllMain函数中,需要保存该DLL模块的句柄,以方便安装全局钩子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 #include "pch.h" #include "framework.h" #include "MFCKeyboardHook.h" #include "MFCKeyboardHookDlg.h" #include "afxdialogex.h" #pragma comment(lib,"KeyboardHook" ) extern "C" void SetHookOn () ;extern "C" void SetHookOff () ;#ifdef _DEBUG #define new DEBUG_NEW #endif CMFCKeyboardHookDlg::CMFCKeyboardHookDlg(CWnd* pParent ) : CDialog(IDD_MFCKEYBOARDHOOK_DIALOG, pParent) { m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME); }void CMFCKeyboardHookDlg::DoDataExchange (CDataExchange* pDX) { CDialog::DoDataExchange(pDX); } BEGIN_MESSAGE_MAP(CMFCKeyboardHookDlg, CDialog) ON_WM_PAINT() ON_WM_QUERYDRAGICON() ON_BN_CLICKED(IDC_BUTTON2, &CMFCKeyboardHookDlg::OnBnClickedButton2) ON_BN_CLICKED(IDC_BUTTON1, &CMFCKeyboardHookDlg::OnBnClickedButton1) END_MESSAGE_MAP()BOOL CMFCKeyboardHookDlg::OnInitDialog () { CDialog::OnInitDialog(); SetIcon(m_hIcon, TRUE); SetIcon(m_hIcon, FALSE); return TRUE; }void CMFCKeyboardHookDlg::OnPaint () { if (IsIconic()) { CPaintDC dc (this ) ; SendMessage(WM_ICONERASEBKGND, reinterpret_cast <WPARAM>(dc.GetSafeHdc()), 0 ); int cxIcon = GetSystemMetrics(SM_CXICON); int cyIcon = GetSystemMetrics(SM_CYICON); CRect rect; GetClientRect(&rect); int x = (rect.Width() - cxIcon + 1 ) / 2 ; int y = (rect.Height() - cyIcon + 1 ) / 2 ; dc.DrawIcon(x, y, m_hIcon); } else { CDialog::OnPaint(); } }HCURSOR CMFCKeyboardHookDlg::OnQueryDragIcon () { return static_cast <HCURSOR>(m_hIcon); }void CMFCKeyboardHookDlg::OnBnClickedButton2 () { SetHookOn(); }void CMFCKeyboardHookDlg::OnBnClickedButton1 () { SetHookOff(); }
全局钩子DLL注入 WH_GETMESSAGE:该钩子作用是监视被投递到消息队列的消息。也就是在调用GetMessage()或PeekMessage()函数时,函数从消息队列中获取一个消息后调用该钩子。
利用WH_GETMESSAGE可以将DLL文件注入到所有的基于消息机制的程序中,在需要DLL大范围注入到基于消息的进程中时可以使用这种方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 #include "pch.h" #include <Windows.h> #include <tchar.h> #pragma warning (disable:4996) #pragma data_seg("mydata" ) HHOOK g_Hook = NULL ; #pragma data_seg() #pragma comment(linker,"/SECTION:mydata,RWS" ) extern "C" __declspec(dllexport) void SetHookOn () ;extern "C" __declspec(dllexport) void SetHookOff () ; HINSTANCE g_hInst;LRESULT CALLBACK GetMessageProc ( int nCode, WPARAM wParam, LPARAM lParam ) { MessageBox(NULL , L"Hooked" , L"提示" , MB_ICONWARNING | MB_OKCANCEL); return CallNextHookEx(g_Hook, nCode, wParam, lParam); }void SetHookOn () { g_Hook=SetWindowsHookEx(WH_GETMESSAGE, GetMessageProc, g_hInst, 0 ); }void SetHookOff () { UnhookWindowsHookEx(g_Hook); }BOOL APIENTRY DllMain ( HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved ) { switch (ul_reason_for_call) { case DLL_PROCESS_ATTACH: { g_hInst = hModule; } case DLL_THREAD_ATTACH: case DLL_THREAD_DETACH: case DLL_PROCESS_DETACH: break ; } return TRUE; }