我們知道Windows中的窗口程序是基于消息,由事件驅(qū)動的,在某些情況下可能需要捕獲或者修改消息,從而完成一些特殊的功能(MFC框架就利用Windows鉤子對消息進行引導(dǎo))。對于捕獲消息而言,無法使用IAT或Inline Hook之類的方式去進行捕獲,這就要用到接下來要介紹的Windows提供的專門用于處理消息的鉤子函數(shù)。
1.?掛鉤原理
Windows下的應(yīng)用程序大部分都是基于消息機制的,它們都會有一個消息過程函數(shù),根據(jù)不同的消息完成不同的功能。Windows操作系統(tǒng)提供的鉤子機制的作用就是用來截獲和監(jiān)視這些系統(tǒng)中的消息。Windows鉤子琳瑯滿目,可以用來應(yīng)對各種不同的消息。
按照鉤子作用的范圍不同,又可以分為局部鉤子和全局鉤子。局部鉤子是針對某個線程的;而全局鉤子則是作用于整個系統(tǒng)中基于消息的應(yīng)用。全局鉤子需要使用DLL文件,在DLL中實現(xiàn)相應(yīng)的鉤子函數(shù)。在操作系統(tǒng)中安裝全局鉤子后,只要進程接收到可以發(fā)出鉤子的消息,全局鉤子的DLL文件就會被操作系統(tǒng)自動或強行地加載到該進程中。因此,設(shè)置消息鉤子,也可以達到DLL注入的目的。
2.?鉤子函數(shù)
[cpp] view plaincopy print?
HHOOK?SetWindowsHookEx(??????????????int?idHook,??????HOOKPROC?lpfn,??????HINSTANCE?hMod,??????DWORD?dwThreadId??);?? HHOOK SetWindowsHookEx( int idHook,HOOKPROC lpfn,HINSTANCE hMod,DWORD dwThreadId
);
該函數(shù)的返回值是一個鉤子句柄。參數(shù)介紹如下:
lpfn:指定Hook函數(shù)的地址。如果dwThreadId參數(shù)被賦值為0,或者被設(shè)置為一個其他進程中的線程ID,那么lpfn屬于DLL中的函數(shù)過程。如果dwThreadId為當(dāng)前進程中的一個線程ID,那么lpfn可以使指向當(dāng)前進程模塊中的函數(shù),當(dāng)然,也可以使DLL模塊中的函數(shù)。
hMod:該參數(shù)指定鉤子函數(shù)所在模塊的模塊句柄。即lpfn所在的模塊句柄。如果dwThreadId為當(dāng)前進程中的線程ID,且lpfn所指函數(shù)在當(dāng)前進程中,則該參數(shù)被設(shè)置為NULL。
dwThreadId:指定需要被掛鉤的線程ID號。如果設(shè)置為0,表示在所有基于消息的線程中掛鉤;如果設(shè)置了具體的線程ID,表示在指定線程中掛鉤。該參數(shù)影響上面兩個參數(shù)的取值,同時也決定了該鉤子是全局鉤子還是局部鉤子。
idHook:該參數(shù)表示鉤子的類型。常用的幾種如下:
※? WH_GETMESSAGE
按照該鉤子的作用是監(jiān)視被投遞到消息隊列中的消息。也就是當(dāng)調(diào)用GetMessage或PeekMessage函數(shù)時,函數(shù)從程序的消息隊列中獲取一個消息后調(diào)用該鉤子。
WH_GETMESSAGEG的鉤子函數(shù)如下:
[cpp] view plaincopy print?
LRESULT?CALLBACK?GetMsgProc(??????????????int?code,?????????????WPARAM?wParam,????????????LPARAM?lParam??????????);?? LRESULT CALLBACK GetMsgProc( int code, //hook codeWPARAM wParam, //removal optionLPARAM lParam //message
);
※? WH_MOUSE
該鉤子用于監(jiān)視鼠標(biāo)消息。鉤子函數(shù)如下:
[cpp] view plaincopy print?
LRESULT?CALLBACK?MouseProc(??????????????int?nCode,????????????WPARAM?wParam,????????????LPARAM?lParam?????????);?? LRESULT CALLBACK MouseProc( int nCode, //hook codeWPARAM wParam, //message identifierLPARAM lParam //mouse coordinates
);
※? WH_KEYBOARD
該鉤子用于監(jiān)視鍵盤消息。鉤子函數(shù)如下:
[cpp] view plaincopy print?
LRESULT?CALLBACK?KeyboardProc(??????????????int?code,?????????????WPARAM?wParam,????????????LPARAM?lParam?????????);?? LRESULT CALLBACK KeyboardProc( int code, //hook codeWPARAM wParam, //virtual-key codeLPARAM lParam //keystroke-message information
);
※? WH_DEBUG
用于調(diào)試其它鉤子。鉤子函數(shù)如下:
[cpp] view plaincopy print?
LRESULT?CALLBACK?DebugProc(??????????????int?nCode,????????????WPARAM?wParam,????????????LPARAM?lParam?????????);?? LRESULT CALLBACK DebugProc( int nCode, //hook codeWPARAM wParam, //hook typeLPARAM lParam //debugging information
);
對于以上鉤子函數(shù)的詳情還請各位看客老爺們自行挪步到MSDN了。
移除先前用SetWindowsHookEx安裝的鉤子:
[cpp] view plaincopy print?
BOOL?UnhookWindowsHookEx(??????????????HHOOK?hhk??);?? BOOL UnhookWindowsHookEx( HHOOK hhk
);
唯一的參數(shù)是待移除的鉤子句柄。
在實際應(yīng)用中,可以多次調(diào)用SetWindowsHookEx函數(shù)來安裝鉤子,而且可以安裝多個同樣類型的鉤子。這樣,鉤子就會形成一條鉤子鏈,最后安裝的鉤子會首先截獲到消息。當(dāng)該鉤子對消息處理完畢后,可以選擇返回或者把消息繼續(xù)傳遞下去。如果是為了屏蔽某消息,可以在安裝的鉤子函數(shù)中直接返回非零值。如果希望我們的鉤子函數(shù)處理完消息后可以繼續(xù)傳遞給目標(biāo)窗口,則必須選擇將消息繼續(xù)傳遞。繼續(xù)傳遞消息的函數(shù)定義如下:
[cpp] view plaincopy print?
LRESULT?CallNextHookEx(??????????????HHOOK?hhk,????????????int?nCode,????????????WPARAM?wParam,????????????LPARAM?lParam?????????);?? LRESULT CallNextHookEx( HHOOK hhk, //handle to current hookint nCode, //hook code passed to hook procedureWPARAM wParam, //value passed to hook procedureLPARAM lParam //value passed to hook procedure
);
第一個參數(shù)是鉤子句柄,就是調(diào)用SetWindowsHookEx函數(shù)的返回值;后面3個參數(shù)是鉤子的參數(shù),直接一次copy即可。例如:
[cpp] view plaincopy print?
HHOOK?g_Hook?=?SetWindowsHookEx(…);??LRESULT?CALLBACK?GetMsgProc(??????????????int?code,?????????????WPARAM?wParam,????????????LPARAM?lParam??????????)??{??????return?CallNextHookEx(g_Hook,?code,?wParam,?lParam);??}?? HHOOK g_Hook = SetWindowsHookEx(…);
LRESULT CALLBACK GetMsgProc( int code, //hook codeWPARAM wParam, //removal optionLPARAM lParam //message
)
{return CallNextHookEx(g_Hook, code, wParam, lParam);
}
3.?鉤子實例
Windows鉤子的使用場景比較廣泛,我們就幾種比較常見的情況做一個應(yīng)用示例。
3.1全局鍵盤鉤子
先新建一個DLL程序(這個不會可以看我以前的博客,這里就不重復(fù)了),我們在頭文件中增加兩個導(dǎo)出函數(shù)和兩個全局。
[cpp] view plaincopy print?
#define?MY_API?__declspec(dllexport)??extern?"C"?MY_API?VOID?SetHookOn();??extern?"C"?MY_API?VOID?SetHookOff();??HHOOK?g_Hook?=?NULL;??????????HINSTANCE?g_Inst?=?NULL;?????? #define MY_API __declspec(dllexport)
extern "C" MY_API VOID SetHookOn();
extern "C" MY_API VOID SetHookOff();
HHOOK g_Hook = NULL; //鉤子句柄
HINSTANCE g_Inst = NULL; //DLL模塊句柄
在DllMain中保存該DLL模塊的句柄,以方便安裝全局鉤子。
[cpp] view plaincopy print?
BOOL?APIENTRY?DllMain(?HANDLE?hModule,??????????????????????????DWORD??ul_reason_for_call,??????????????????????????LPVOID?lpReserved???????????????????????)??{??????????????g_Inst?=?(HINSTANCE)hModule;????????return?TRUE;??}?? BOOL APIENTRY DllMain( HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
{//保存DLL模塊句柄g_Inst = (HINSTANCE)hModule;return TRUE;
}
安裝與卸載鉤子的函數(shù)如下:
[cpp] view plaincopy print?
VOID?SetHookOn()??{????????????g_Hook?=?SetWindowsHookEx(WH_KEYBOARD,?KeyboardProc,?g_Inst,?0);??}????VOID?SetHookOff()??{????????????UnhookWindowsHookEx(g_Hook);??}?? VOID SetHookOn()
{//安裝鉤子g_Hook = SetWindowsHookEx(WH_KEYBOARD, KeyboardProc, g_Inst, 0);
}VOID SetHookOff()
{//卸載鉤子UnhookWindowsHookEx(g_Hook);
}
鉤子函數(shù)的實現(xiàn)如下:
[cpp] view plaincopy print?
??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)??????{??????????????????????????????char?szBuf[MAXBYTE]?=?{0};??????????GetKeyNameText(lParam,?szBuf,?MAXBYTE);??????????MessageBox(NULL,?szBuf,?"提示",?MB_OK);??????}????????return?CallNextHookEx(g_Hook,?code,?wParam,?lParam);????}?? //鉤子函數(shù)
LRESULT CALLBACK KeyboardProc(int code, WPARAM wParam, LPARAM lParam)
{if(code < 0){//如果code小于0,必須調(diào)用CallNextHookEx傳遞消息,不處理該消息,并返回CallNextHookEx的返回值。return CallNextHookEx(g_Hook, code, wParam, lParam);}if(code == HC_ACTION && lParam > 0){//code等于HC_ACTION,表示消息中包含按鍵消息//如果為WM_KEYDOWN,則顯示按鍵對應(yīng)的文本char szBuf[MAXBYTE] = {0};GetKeyNameText(lParam, szBuf, MAXBYTE);MessageBox(NULL, szBuf, "提示", MB_OK);}return CallNextHookEx(g_Hook, code, wParam, lParam);}
編譯鏈接后產(chǎn)生我們需要的.dll和.lib文件,然后新建一個項目來導(dǎo)入動態(tài)庫內(nèi)容調(diào)用相關(guān)函數(shù)。
新建項目如下:
首先導(dǎo)入庫:
[cpp] view plaincopy print?
#pragma?comment?(lib,?"全局鉤子.lib")?? #pragma comment (lib, "全局鉤子.lib")
聲明將要調(diào)用的函數(shù)(不聲明鏈接時將報錯):
[cpp] view plaincopy print?
extern?"C"?VOID?SetHookOn();??extern?"C"?VOID?SetHookOff();?? extern "C" VOID SetHookOn();
extern "C" VOID SetHookOff();
在按鈕事件中調(diào)用導(dǎo)出函數(shù):
[cpp] view plaincopy print?
void?CHookDebugDlg::OnHookon()???{??????SetHookOn();??}????void?CHookDebugDlg::OnHookoff()???{??????SetHookOff();??}?? void CHookDebugDlg::OnHookon()
{SetHookOn();
}void CHookDebugDlg::OnHookoff()
{SetHookOff();
}
執(zhí)行結(jié)果如下:
3.2低級鍵盤鉤子
數(shù)據(jù)防泄漏軟件通常會精致PrintScreen鍵,防止通過截屏將數(shù)據(jù)保存為圖片而導(dǎo)致數(shù)據(jù)泄密。下面我們也可以模仿一下,簡單的實現(xiàn)該功能。這里需要注意的是,普通的鍵盤鉤子(WH_KEYBOARD)是無法過濾一些系統(tǒng)按鍵的,得通過安裝低級鍵盤鉤子(WH_KEYBOARD_LL)來達到目的。
在低級鍵盤鉤子的回調(diào)函數(shù)中,判斷是否為PrintScreen鍵,如果是,則直接返回TRUE;如果不是,則傳遞給下一個鉤子處理。
具體DLL中的實現(xiàn)代碼如下:
[cpp] view plaincopy print?
BOOL?SetHookOn()??{??????if(g_Hook?!=?NULL)??????{??????????return?FALSE;??????}????????????g_Hook?=?SetWindowsHookEx(WH_KEYBOARD_LL,?LowLevelKeyboardProc,?g_Inst,?0);??????if(NULL?==?g_Hook)??????{??????????MessageBox(NULL,?"安裝鉤子出錯!",?"ERROR",?MB_ICONSTOP);??????????return?FALSE;??????}????????return?TRUE;??}????BOOL?SetHookOff()??{??????if(g_Hook?==?NULL)??????{??????????return?FALSE;??????}????????????UnhookWindowsHookEx(g_Hook);??????g_Hook?=?NULL;??????return?TRUE;??}??????LRESULT?CALLBACK?LowLevelKeyboardProc(int?code,?WPARAM?wParam,?LPARAM?lParam)??{??????KBDLLHOOKSTRUCT?*Key_Info?=?(KBDLLHOOKSTRUCT?*)lParam;????????if(HC_ACTION?==?code)??????{??????????if(WM_KEYDOWN?==?wParam?||?WM_SYSKEYDOWN?==?wParam)??????????{??????????????if(Key_Info->vkCode?==?VK_SNAPSHOT)??????????????{??????????????????MessageBox(NULL,?"該鍵已禁用!",?"ERROR",?MB_ICONSTOP);??????????????????return?TRUE;??????????????}??????????}??????}????????return?CallNextHookEx(g_Hook,?code,?wParam,?lParam);????}?? BOOL SetHookOn()
{if(g_Hook != NULL){return FALSE;}//安裝鉤子g_Hook = SetWindowsHookEx(WH_KEYBOARD_LL, LowLevelKeyboardProc, g_Inst, 0);if(NULL == g_Hook){MessageBox(NULL, "安裝鉤子出錯!", "ERROR", MB_ICONSTOP);return FALSE;}return TRUE;
}BOOL SetHookOff()
{if(g_Hook == NULL){return FALSE;}//卸載鉤子UnhookWindowsHookEx(g_Hook);g_Hook = NULL;return TRUE;
}//鉤子函數(shù)
LRESULT CALLBACK LowLevelKeyboardProc(int code, WPARAM wParam, LPARAM lParam)
{KBDLLHOOKSTRUCT *Key_Info = (KBDLLHOOKSTRUCT *)lParam;if(HC_ACTION == code){if(WM_KEYDOWN == wParam || WM_SYSKEYDOWN == wParam){if(Key_Info->vkCode == VK_SNAPSHOT){MessageBox(NULL, "該鍵已禁用!", "ERROR", MB_ICONSTOP);return TRUE;}}}return CallNextHookEx(g_Hook, code, wParam, lParam);}
依然利用前面的小程序,執(zhí)行后按下PrintScreen鍵,效果如下:
可能在編譯時會報錯,說WH_KEYBOARD_LL和KBDLLHOOKSTRUCT未定義,此時可以在文件開頭加上如下代碼:
[cpp] view plaincopy print?
#define?WH_KEYBOARD_LL?13????typedef?struct?tagKBDLLHOOKSTRUCT?{????DWORD?vkCode;????DWORD?scanCode;????DWORD?flags;????DWORD?time;????DWORD?dwExtraInfo;????}?KBDLLHOOKSTRUCT,?FAR?*LPKBDLLHOOKSTRUCT,?*PKBDLLHOOKSTRUCT;?? #define WH_KEYBOARD_LL 13typedef struct tagKBDLLHOOKSTRUCT {DWORD vkCode;DWORD scanCode;DWORD flags;DWORD time;DWORD dwExtraInfo;} KBDLLHOOKSTRUCT, FAR *LPKBDLLHOOKSTRUCT, *PKBDLLHOOKSTRUCT;
其實在winuser.h中已有定義,但是可能是兼容的緣故用不了。
3.3鉤子注入DLL
利用WH_GETMESSAGE鉤子,可以方便地將DLL文件注入到所有基于消息機制的程序中。因為有時候可能需要DLL文件完成一些工作,但是工作時需要DLL在目標(biāo)進程的空間中。這個時候,就可以將DLL注入目標(biāo)進程來完成相關(guān)的功能。
主要的代碼如下:
[cpp] view plaincopy print?
BOOL?APIENTRY?DllMain(?HANDLE?hModule,??????????????????????????DWORD??ul_reason_for_call,??????????????????????????LPVOID?lpReserved???????????????????????)??{????????????g_Inst?=?(HINSTANCE)hModule;????????switch?(ul_reason_for_call)??????{??????????case?DLL_PROCESS_ATTACH:??????????????{??????????????????DoSomething();??????????????????break;??????????????}??????????case?DLL_THREAD_ATTACH:??????????case?DLL_THREAD_DETACH:??????????case?DLL_PROCESS_DETACH:??????????????break;??????}??????return?TRUE;??}??VOID?SetHookOn()??{??????g_Hook?=?SetWindowsHookEx(WH_GETMESSAGE,?GetMsgProc,?g_Inst,?0);??}????VOID?SetHookOff()??{??????UnhookWindowsHookEx(g_Hook);??}????LRESULT?CALLBACK?GetMsgProc(int?code,?WPARAM?wParam,?LPARAM?lParam)??{??????return?CallNextHookEx(g_Hook,?code,?wParam,?lParam);??}????VOID?DoSomething()??{??????MessageBox(NULL,?"Hello,我被執(zhí)行了!",?"提示",?MB_OK);??}?? BOOL APIENTRY DllMain( HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
{//保存DLL模塊句柄g_Inst = (HINSTANCE)hModule;switch (ul_reason_for_call){case DLL_PROCESS_ATTACH:{DoSomething();break;}case DLL_THREAD_ATTACH:case DLL_THREAD_DETACH:case DLL_PROCESS_DETACH:break;}return TRUE;
}
VOID SetHookOn()
{g_Hook = SetWindowsHookEx(WH_GETMESSAGE, GetMsgProc, g_Inst, 0);
}VOID SetHookOff()
{UnhookWindowsHookEx(g_Hook);
}LRESULT CALLBACK GetMsgProc(int code, WPARAM wParam, LPARAM lParam)
{return CallNextHookEx(g_Hook, code, wParam, lParam);
}VOID DoSomething()
{MessageBox(NULL, "Hello,我被執(zhí)行了!", "提示", MB_OK);
}
執(zhí)行效果圖:
?需要注意的是,此處執(zhí)行的DoSomething并不是導(dǎo)出函數(shù)哦。
與50位技術(shù)專家面對面20年技術(shù)見證,附贈技術(shù)全景圖
總結(jié)
以上是生活随笔為你收集整理的Windows 钩子的使用的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。