windows下钩子的使用
我們知道Windows中的窗口程序是基于消息,由事件驅(qū)動(dòng)的,在某些情況下可能需要捕獲或者修改消息,從而完成一些特殊的功能(MFC框架就利用Windows鉤子對(duì)消息進(jìn)行引導(dǎo))。對(duì)于捕獲消息而言,無(wú)法使用IAT或Inline Hook之類的方式去進(jìn)行捕獲,這就要用到接下來(lái)要介紹的Windows提供的專門用于處理消息的鉤子函數(shù)。
1.?掛鉤原理
Windows下的應(yīng)用程序大部分都是基于消息機(jī)制的,它們都會(huì)有一個(gè)消息過(guò)程函數(shù),根據(jù)不同的消息完成不同的功能。Windows操作系統(tǒng)提供的鉤子機(jī)制的作用就是用來(lái)截獲和監(jiān)視這些系統(tǒng)中的消息。Windows鉤子琳瑯滿目,可以用來(lái)應(yīng)對(duì)各種不同的消息。
按照鉤子作用的范圍不同,又可以分為局部鉤子和全局鉤子。其中,全局鉤子具有相當(dāng)大的功能,幾乎可以實(shí)現(xiàn)對(duì)所有Windows消息的攔截、處理和監(jiān)控。局部鉤子是針對(duì)某個(gè)線程的;而全局鉤子則是作用于整個(gè)系統(tǒng)中基于消息的應(yīng)用。全局鉤子需要使用DLL文件,在DLL中實(shí)現(xiàn)相應(yīng)的鉤子函數(shù)。系統(tǒng)鉤子,系統(tǒng)就必須把鉤子函數(shù)插入到其它進(jìn)程的地址空間,要做到這一點(diǎn)要求鉤子函數(shù)必須在一個(gè)動(dòng)態(tài)鏈接庫(kù)中,所以如果您想要使用系統(tǒng)鉤子,就必須把該鉤子函數(shù)放到動(dòng)態(tài)鏈接庫(kù)中去。在操作系統(tǒng)中安裝全局鉤子后,只要進(jìn)程接收到可以發(fā)出鉤子的消息,全局鉤子的DLL文件就會(huì)被操作系統(tǒng)自動(dòng)或強(qiáng)行地加載到該進(jìn)程中。因此,設(shè)置消息鉤子,也可以達(dá)到DLL注入的目的。
般來(lái)說(shuō),HOOK API由兩個(gè)組成部分,即實(shí)現(xiàn)HOOK API的DLL文件,和啟動(dòng)注入的主調(diào)程序。本文采用HOOK API 技術(shù)對(duì)剪切板相關(guān)的API 函數(shù)進(jìn)行攔截,從而實(shí)現(xiàn)對(duì)剪切板內(nèi)容的監(jiān)控功能,同樣使用該技術(shù)實(shí)現(xiàn)進(jìn)程防終止功能。其中DLL文件支持HOOK API的實(shí)現(xiàn),而主調(diào)客戶端程序?qū)⒃诔跏蓟瘯r(shí)把帶有HOOK API功能的DLL隨著鼠標(biāo)鉤子的加載注入到目標(biāo)進(jìn)程中,這里的鼠標(biāo)鉤子屬于系統(tǒng)鉤子。
幾點(diǎn)需要說(shuō)明的地方:
(1) 如果對(duì)于同一事件(如鼠標(biāo)消息)既安裝了線程鉤子又安裝了系統(tǒng)鉤子,那么系統(tǒng)會(huì)自動(dòng)先調(diào)用線程鉤子,然后調(diào)用系統(tǒng)鉤子。
(2) 對(duì)同一事件消息可安裝多個(gè)鉤子處理過(guò)程,這些鉤子處理過(guò)程形成了鉤子鏈。當(dāng)前鉤子處理結(jié)束后應(yīng)把鉤子信息傳遞給下一個(gè)鉤子函數(shù)。而且最近安裝的鉤子放在鏈的開(kāi)始,而最早安裝的鉤子放在最后,也就是后加入的先獲得控制權(quán)。
(3) 鉤子特別是系統(tǒng)鉤子會(huì)消耗消息處理時(shí)間,降低系統(tǒng)性能。只有在必要的時(shí)候才安裝鉤子,在使用完畢后要及時(shí)卸載。
?
鉤子的類型
1、按事件分類
有如下的幾種常用類型
(1) 鍵盤鉤子和低級(jí)鍵盤鉤子可以監(jiān)視各種鍵盤消息。
(2) 鼠標(biāo)鉤子和低級(jí)鼠標(biāo)鉤子可以監(jiān)視各種鼠標(biāo)消息。
(3) 外殼鉤子可以監(jiān)視各種Shell事件消息。比如啟動(dòng)和關(guān)閉應(yīng)用程序。
(4) 日志鉤子可以記錄從系統(tǒng)消息隊(duì)列中取出的各種事件消息。
(5) 窗口過(guò)程鉤子監(jiān)視所有從系統(tǒng)消息隊(duì)列發(fā)往目標(biāo)窗口的消息。
此外,還有一些特定事件的鉤子提供給我們使用,不一一列舉。
2、按使用范圍分類
主要有線程鉤子和系統(tǒng)鉤子:
(1) 線程鉤子監(jiān)視指定線程的事件消息。
(2) 系統(tǒng)鉤子監(jiān)視系統(tǒng)中的所有線程的事件消息。因?yàn)橄到y(tǒng)鉤子會(huì)影響系統(tǒng)中所有的應(yīng)用程序,所以鉤子函數(shù)必須放在獨(dú)立的動(dòng)態(tài)鏈接庫(kù)(DLL)中。這是系統(tǒng)鉤子和線程鉤子很大的不同之處。
2.?鉤子函數(shù)
HHOOK SetWindowsHookEx(
int idHook,
HOOKPROC lpfn,
HINSTANCE hMod,
DWORD dwThreadId
);
?
該函數(shù)的返回值是一個(gè)鉤子句柄。參數(shù)介紹如下:
lpfn:指定Hook函數(shù)的地址。如果dwThreadId參數(shù)被賦值為0,或者被設(shè)置為一個(gè)其他進(jìn)程中的線程ID(遠(yuǎn)程鉤子/全局鉤子),那么lpfn屬于DLL中的函數(shù)過(guò)程。如果dwThreadId為當(dāng)前進(jìn)程中的一個(gè)線程ID,那么lpfn可以使指向當(dāng)前進(jìn)程模塊中的函數(shù),當(dāng)然,也可以使DLL模塊中的函數(shù)(局部鉤子/本地鉤子)。
hMod:該參數(shù)指定鉤子函數(shù)所在模塊的模塊句柄。即lpfn所在的模塊句柄。如果dwThreadId為當(dāng)前進(jìn)程中的線程ID,若lpfn所指函數(shù)在當(dāng)前進(jìn)程中,則該參數(shù)被設(shè)置為NULL。
dwThreadId:指定需要被掛鉤的線程ID號(hào)。如果設(shè)置為0,表示在所有基于所有的線程中掛鉤;如果設(shè)置了具體的線程ID,表示在指定線程中掛鉤。該參數(shù)影響上面兩個(gè)參數(shù)的取值,同時(shí)也決定了該鉤子是全局鉤子還是局部鉤子。注意是否全局鉤子還是局部鉤子與下面具體的鉤子種類無(wú)關(guān)僅由本參數(shù)控制。
idHook:該參數(shù)表示鉤子的類型。常用的幾種如下:
- WH_GETMESSAGE
按照該鉤子的作用是監(jiān)視被投遞到消息隊(duì)列中的消息。也就是當(dāng)調(diào)用GetMessage或PeekMessage函數(shù)時(shí),函數(shù)從程序的消息隊(duì)列中獲取一個(gè)消息后調(diào)用該鉤子。
WH_GETMESSAGEG的鉤子函數(shù)如下:
LRESULT CALLBACK GetMsgProc( ?????
????int code, //hook code
????WPARAM wParam, //removal option
????LPARAM lParam ???????//message
);
?
- WH_MOUSE
該鉤子用于監(jiān)視鼠標(biāo)消息。鉤子函數(shù)如下:
LRESULT CALLBACK MouseProc( ?????
????int nCode, //hook code
????WPARAM wParam, //message identifier
????LPARAM lParam //mouse coordinates
);
?
- WH_KEYBOARD
該鉤子用于監(jiān)視鍵盤消息。鉤子函數(shù)如下:
LRESULT CALLBACK KeyboardProc( ?????
?
????int code, //hook code
????WPARAM wParam, //virtual-key code
LPARAM lParam //keystroke-message information
}
- WH_DEBUG
用于調(diào)試其它鉤子。鉤子函數(shù)如下:
LRESULT CALLBACK DebugProc( ?????
?
????int nCode, //hook code
????WPARAM wParam, //hook type
????LPARAM lParam //debugging information
);
對(duì)于以上鉤子函數(shù)的詳情還請(qǐng)各位看客老爺們自行挪步到MSDN了。
移除先前用SetWindowsHookEx安裝的鉤子:
BOOL UnhookWindowsHookEx( ?????
????HHOOK hhk
);
唯一的參數(shù)是待移除的鉤子句柄。
在實(shí)際應(yīng)用中,可以多次調(diào)用SetWindowsHookEx函數(shù)來(lái)安裝鉤子,而且可以安裝多個(gè)同樣類型的鉤子。這樣,鉤子就會(huì)形成一條鉤子鏈,最后安裝的鉤子會(huì)首先截獲到消息。當(dāng)該鉤子對(duì)消息處理完畢后,可以選擇返回或者把消息繼續(xù)傳遞下去。如果是為了屏蔽某消息,可以在安裝的鉤子函數(shù)中直接返回非零值。如果希望我們的鉤子函數(shù)處理完消息后可以繼續(xù)傳遞給目標(biāo)窗口,則必須選擇將消息繼續(xù)傳遞。繼續(xù)傳遞消息的函數(shù)定義如下:
LRESULT CallNextHookEx( ?????
????HHOOK hhk, //handle to current hook
????int nCode, //hook code passed to hook procedure
????WPARAM wParam, //value passed to hook procedure
????LPARAM lParam //value passed to hook procedure
);
第一個(gè)參數(shù)是鉤子句柄,就是調(diào)用SetWindowsHookEx函數(shù)的返回值;后面3個(gè)參數(shù)是鉤子的參數(shù),直接一次copy即可。例如:
HHOOK g_Hook = SetWindowsHookEx(…);
LRESULT CALLBACK GetMsgProc( ?????
????int code, //hook code
????WPARAM wParam, //removal option
????LPARAM lParam ???????//message
)
{
return CallNextHookEx(g_Hook, code, wParam, lParam);
}
?
3.?鉤子實(shí)例
Windows鉤子的使用場(chǎng)景比較廣泛,我們就幾種比較常見(jiàn)的情況做一個(gè)應(yīng)用示例。
參看上一小結(jié)可知,編寫鉤子程序的三個(gè)步奏是:
定義鉤子函數(shù):
LRESULT CALLBACK HookProc(int nCode ,WPARAM wParam,LPARAM lParam)
安裝鉤子:
HHOOK SetWindowsHookEx( int idHook,HOOKPROC lpfn, INSTANCE hMod,DWORD dwThreadId )
卸載鉤子:
BOOL UnhookWindowsHookEx( HHOOK hhk)
還需要注意一點(diǎn):系統(tǒng)鉤子必須放在獨(dú)立的動(dòng)態(tài)鏈接庫(kù)中。由此,程序分為兩個(gè)部分:一個(gè)是鉤子程序動(dòng)態(tài)鏈接庫(kù),實(shí)現(xiàn)了鼠標(biāo)鉤子程序;另一個(gè)是MFC操作窗體,對(duì)DLL進(jìn)行加載和卸載,即對(duì)DLL進(jìn)行測(cè)試。//我在win7下測(cè)試沒(méi)有使用dll也成功了why?不想在DLL實(shí)現(xiàn)SetWindowsHookEx時(shí)第三個(gè)參數(shù)傳入GetModuleHandle(NULL)即可?
?
3.1全局鍵盤鉤子
先新建一個(gè)DLL程序(這個(gè)不會(huì)可以看我以前的博客,這里就不重復(fù)了),我們?cè)陬^文件中增加兩個(gè)導(dǎo)出函數(shù)和兩個(gè)全局。
#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模塊的句柄,以方便安裝全局鉤子。
BOOL APIENTRY DllMain( HANDLE hModule,
???????????????????????DWORD ?ul_reason_for_call,
???????????????????????LPVOID lpReserved
????)
{
//保存DLL模塊句柄
g_Inst = (HINSTANCE)hModule;
????return TRUE;
}
?
安裝與卸載鉤子的函數(shù)如下:
VOID SetHookOn()
{
//安裝鉤子
g_Hook = SetWindowsHookEx(WH_KEYBOARD, KeyboardProc, g_Inst, 0);
}
?
VOID SetHookOff()
{
//卸載鉤子
UnhookWindowsHookEx(g_Hook);
}
?
鉤子函數(shù)的實(shí)現(xiàn)如下:
?
//鉤子函數(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,則顯示按鍵對(duì)應(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文件,然后新建一個(gè)項(xiàng)目來(lái)導(dǎo)入動(dòng)態(tài)庫(kù)內(nèi)容調(diào)用相關(guān)函數(shù)。
新建項(xiàng)目如下:
首先導(dǎo)入庫(kù):
?
#pragma comment (lib, "全局鉤子.lib")
聲明將要調(diào)用的函數(shù)(不聲明鏈接時(shí)將報(bào)錯(cuò)):
?
extern "C" VOID SetHookOn();
extern "C" VOID SetHookOff();
在按鈕事件中調(diào)用導(dǎo)出函數(shù):
?
void CHookDebugDlg::OnHookon()
{
SetHookOn();
}
?
void CHookDebugDlg::OnHookoff()
{
SetHookOff();
}
?
?
3.2低級(jí)鍵盤鉤子
數(shù)據(jù)防泄漏軟件通常會(huì)精致PrintScreen鍵,防止通過(guò)截屏將數(shù)據(jù)保存為圖片而導(dǎo)致數(shù)據(jù)泄密。下面我們也可以模仿一下,簡(jiǎn)單的實(shí)現(xiàn)該功能。這里需要注意的是,普通的鍵盤鉤子(WH_KEYBOARD)是無(wú)法過(guò)濾一些系統(tǒng)按鍵的,得通過(guò)安裝低級(jí)鍵盤鉤子(WH_KEYBOARD_LL)來(lái)達(dá)到目的。
在低級(jí)鍵盤鉤子的回調(diào)函數(shù)中,判斷是否為PrintScreen鍵,如果是,則直接返回TRUE;如果不是,則傳遞給下一個(gè)鉤子處理。
實(shí)現(xiàn)過(guò)程與上面相同。
可能在編譯時(shí)會(huì)報(bào)錯(cuò),說(shuō)WH_KEYBOARD_LL和KBDLLHOOKSTRUCT未定義,此時(shí)可以在文件開(kāi)頭加上如下代碼:
?
#define WH_KEYBOARD_LL 13
?typedef struct tagKBDLLHOOKSTRUCT {
?DWORD vkCode;
?DWORD scanCode;
?DWORD flags;
?DWORD time;
?DWORD dwExtraInfo;
?} KBDLLHOOKSTRUCT, FAR *LPKBDLLHOOKSTRUCT, *PKBDLLHOOKSTRUCT;
其實(shí)在winuser.h中已有定義,但是可能是兼容的緣故用不了。
3.3鉤子注入DLL
利用WH_GETMESSAGE鉤子,可以方便地將DLL文件注入到所有基于消息機(jī)制的程序中。因?yàn)橛袝r(shí)候可能需要DLL文件完成一些工作,但是工作時(shí)需要DLL在目標(biāo)進(jìn)程的空間中。這個(gè)時(shí)候,就可以將DLL注入目標(biāo)進(jìn)程來(lái)完成相關(guān)的功能。
主要的代碼如下:
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);
}
?
總結(jié)
以上是生活随笔為你收集整理的windows下钩子的使用的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: win32创建控件的一些问题
- 下一篇: VS2017控制台工程日志输出到log文