一、鉤子介紹鉤子的實現機制
鉤子英文名叫Hook,是一種截獲windows系統中某應用程式或者所有程序的訊息的一種技術。下圖是windows應用程式傳遞訊息的過程:
如在鍵盤中按下一鍵,作業系統將收到鍵按下訊息,把訊息放入訊息佇列,然後訊息佇列對訊息進行派發,發給相應的應用程式,經過應用程式處理後發給作業系統,作業系統再呼叫相應的應用程式的建立的視窗過程。
我們可能透過鉤子截獲這些訊息,讓訊息不再往下傳遞,或者說截獲到感興趣的訊息後做點什麼。
1.2鉤子分類與實現鉤子分程序內鉤子與全域性鉤子,程序內鉤子是擷取某一指定的程序的訊息,直接在程序內建立與消除鉤子即可。全域性鉤子是擷取所有程序的訊息,得以動態庫的方式實現。
實現一個鉤子一般有三個步驟,首先建立鉤子,有專門的API:SetWindowsHookEx,建立成功後,訊息將會傳給SetWindowsHookEx的形參指定的處理函式。然後在訊息處理函式中分析收到的訊息,做相應的處理。最後,鉤子用完後,用API(UnhookWindowsHookEx)消毀鉤子。
二、建立鉤子2.1鉤子的建立SetWindowsHookEx安裝一個應用程式定義的鉤子過程,並把建立的鉤子過程放在鉤子鏈中,可以安裝多個鉤子,多個鉤子就形成了鉤子鏈,最後安裝的鉤子總是在最前面。建立鉤子的函式如下:
建立鉤子,返回鉤子控制代碼,否則返回NULL。形參定義如下:
idHook:鉤子過程型別,如:滑鼠訊息鉤子、鍵盤訊息鉤子、訊息佇列監控鉤子等等。具體取值如下:
lpfn:相應的鉤子過程,也就是一個處理訊息的回撥函式名而已,如果引數dwThreadId為0,或者dwThreadId指向的是其他程序建立的執行緒標誌符,那麼lpfn必須指向一個位於某一動態庫中的鉤子過程。其他情況下,lpfn可以指向本程序內的某一鉤子過程。
hMod:指向鉤子過程所在的應用程式例項控制代碼,如:鉤子過程所在的DLL的控制代碼。如果dwThreadId指定的執行緒是由當前程序建立,並且鉤子過程在當時程序中,那麼hMod必須設定為NULL。
dwThreadId:指定與鉤子相關的執行緒標誌。如果為0,那麼鉤子將與桌面上執行的所有執行緒相關。
鉤子過程可以與特定執行緒相關也可以與所有執行緒相關,取決於dwThreadId的取值。
2.2鉤子過程分析鉤子過程,是處理鉤子擷取的訊息的一個回撥函式,即SetWindowsHookEx函式中的第二個形參。格式如下:
LRESULT CALLBACK MouseProc(int nCode, WPARAM wParam, LPARAM lParam);形參含義並不是都一樣的,不同鉤子過程形參表示的意義不一樣,我們以滑鼠鉤子為例說明,引數含義如下:
nCode:指示鉤子過程如何處理當前的訊息,如果鉤子是滑鼠訊息鉤子的話,有兩種含義:
wParam:指示滑鼠訊息標誌。
lParam:指向MOUSEHOOKSTRUCT結構體指標。以下是MOUSEHOOKSTRUCT結構體,包含著滑鼠訊息。
typedef struct {
POINT pt;//游標包含x,y,是螢幕座標。
HWND hwnd;//目標視窗控制代碼
UINT wHitTestCode;
ULONG_PTR dwExtraInfo;
} MOUSEHOOKSTRUCT, *PMOUSEHOOKSTRUCT;
透過這三個引數,可對訊息進行分析處理。
其他鉤子過程引數的含義可以透過MSDN文件檢視詳細說明,如KeyboardProc、MessageProc等。
2.3消毀鉤子BOOL UnhookWindowsHookEx(HHOOK hhk);此API的功能是把SetWindowsHookEx建立的鉤子從鉤子鏈中移除。形參是SetWindowsHookEx返回的鉤子控制代碼。
三、程序內鉤子程序內鉤子一般是為了截獲當前應用程式的訊息,一般會可以在應用程式初始化的時候建立,當然也可以在自己想建立的時候建立,只是建立之前的訊息無法截獲。
3.1滑鼠鉤子過程函式以下為滑鼠鉤子過程程式碼:
HWND g_DestWndH = NULL;HHOOK g_hHookDm = NULL;LRESULT CALLBACK MouseProc(int nCode, WPARAM wParam, LPARAM lParam){ //此區間內的訊息都是滑鼠訊息 if (WM_MOUSEMOVE <= wParam && wParam <= WM_MOUSEWHEEL) { if(g_DestWndH != NULL) { ::SendMessage(g_DestWndH, wParam, wParam, lParam); //滑鼠鉤子,lParam是MOUSEHOOKSTRUCT結構指標 PMOUSEHOOKSTRUCT lpMsg = (PMOUSEHOOKSTRUCT)lParam; lpMsg->hwnd = NULL; //把目的視窗置NULL lpMsg->dwExtraInfo = 0L; lpMsg->pt = CPoint(0, 0); lpMsg->wHitTestCode = 0L; } return 1;//如果想讓此訊息不再往下傳,返回非0 } else//不感興趣的訊息往下傳 return ::CallNextHookEx(g_hHookDm, nCode, wParam, lParam);}
3.2建立鉤子過程以下為建立鉤子過程的程式碼:
if(g_hHookDm == NULL) { //如果dwThreadId指定的執行緒是由當前程序建立,並且鉤子過程在當時程序中,那麼hMod必須設定為NULL。 g_hHookDm = ::SetWindowsHookEx(WH_MOUSE,MouseProc, NULL, GetCurrentThreadId()); if (NULL != g_hHookDm)//建立成功 g_DestWndH = m_hWnd;//儲存目的視窗控制代碼}
3.3消毀程序內鉤子
把鉤子過程從鉤子鏈中拿下來,呼叫一個API即可:
if (NULL != g_hHookDm) { UnhookWindowsHookEx(g_hHookDm); g_DestWndH = NULL;}
到此程序內鉤子的建立與執行到消毀整個過程都完成了。
四、全域性鉤子4.1鍵盤鉤子過程函式以上程序內鉤子只是截獲本程序的訊息,如果要截獲桌面全部執行緒的訊息則要透過全域性鉤子。全域性鉤子必須在DLL上實現,鉤子過程不能在本程序程式碼中實現,所以先得寫一個DLL,程式碼見”HookDll”,以鍵盤鉤子為例:
nCode:與滑鼠鉤子是一樣的含義,有HC_ACTION與HC_NOREMOVE兩個值。
Param:代表具體的虛擬鍵,如:VK_TAB、VK_RETURN分別代表Tab鍵、Enter鍵按下,其他虛擬鍵訊息可檢視MSDN的”virtual-key code”.
lParam:存放一些擴充套件資訊,如:按鍵重複資料、29位表示ALT鍵的按下情況。
引數的具體含義可查MSDN。關於組合鍵的問題,參看以下鍵盤鉤子函式的示例程式碼:
HHOOK g_HookKeyBoard = NULL;LRESULT CALLBACK KeyboardProc(int nCode, WPARAM wParam, LPARAM lParam){ //wParam表示虛擬鍵,lparam第29位表示ALT鍵按下狀態(按下為1,否則為0), //GetKeyState可以獲得對應鍵的狀態,所以收下表示Control + ALT + Z組合鍵按下 if ('Z' == wParam && GetKeyState(VK_CONTROL) < 0 && (lParam >> 29 & 1)) { ::SendMessage(g_DestWnd, WM_KEYDOWN, wParam, lParam); return 1; } else return CallNextHookEx(g_HookKeyBoard, nCode, wParam, lParam);}
注:全域性鉤子在某版本的系統中,除錯狀態下,如果除錯程式視窗沒有獲得焦點,在設定斷點的方不會停留,如果獲得焦點才會停留,也就是說,在除錯狀態下,不屬於本程序的訊息斷點處不會停留。
4.2建立全域性鉤子介面建立全域性鉤子的方法在DLL中,所以得引出一個介面,程式碼如下:
BOOL SetHook(HWND hWnd){ if (NULL == hWnd) return FALSE; g_DestWnd = hWnd; //第三個引數可透過些方法獲得:GetModuleHandle("MouseHook.dll") return (NULL != (g_HookKeyBoard = ::SetWindowsHookEx(WH_KEYBOARD, KeyboardProc, g_hInst, 0)));}
其形參是一視窗控制代碼,指示截獲的興趣訊息需傳向的視窗的控制代碼,其中MetWindowsHookE的第三個引數是DLL的應用程式例項控制代碼,可透過DllMain的傳入引數得到,亦可透過GetModuleHandle方法得到,第四個引數必須為0,才能獲得全部桌面執行緒的訊息。成功將返回TRUE否則FALSE。
4.3消毀全域性鉤子介面在DLL中也得引出一介面消毀全域性鉤子,程式碼如下:
void DestroyHook(){ if (NULL != g_HookKeyBoard) { UnhookWindowsHookEx(g_HookKeyBoard); g_DestWnd = NULL; }}
4.4使用DLL建立全域性鉤子以下為建立與消毀全域性鉤子的實現,程式碼如下:
HINSTANCE hIst = NULL;void CHookTestDlg::OnCreateDllHook(){ hIst = ::LoadLibrary("..\\HookDll\\Debug\\HookDll.dll"); if (NULL != hIst) { typedef BOOL (*pFunSetHook)(HWND); pFunSetHook pSetHook = (pFunSetHook)GetProcAddress(hIst, "SetHook"); if (NULL != pSetHook) { if(pSetHook(m_hWnd)) AfxMessageBox("建立全域性鉤子成功..."); } }}void CHookTestDlg::OnDestroyDllHook(){ if (NULL != hIst) { typedef void (*pFunDestroyHook)(); pFunDestroyHook pDestryHook = (pFunDestroyHook)GetProcAddress(hIst, "DestroyHook"); if (NULL != pDestryHook) { pDestryHook(); ::FreeLibrary(hIst); hIst = NULL; } }}
鉤子的型別(1) 鍵盤鉤子和低階鍵盤鉤子可以監視各種鍵盤訊息。
(2) 滑鼠鉤子和低階滑鼠鉤子可以監視各種滑鼠訊息。
(3) 外殼鉤子可以監視各種Shell事件訊息。比如啟動和關閉應用程式。
(4) 日誌鉤子可以記錄從系統訊息佇列中取出的各種事件訊息。
(5) 視窗過程鉤子監視所有從系統訊息佇列發往目標視窗的訊息。
13種常用的Hook型別:WH_CALLWNDPROC 和 WH_CALLWNDPROCRET Hooks:監視傳送到視窗過程的訊息。
WH_CBT Hook:在以下事件之前,系統都會呼叫WH_CBT Hook子程,這些事件包括:
1. 啟用,建立,銷燬,最小化,最大化,移動,改變尺寸等視窗事件;
2. 完成系統指令;
3. 來自系統訊息佇列中的移動滑鼠,鍵盤事件;
4. 設定輸入焦點事件;
5. 同步系統訊息佇列事件。
Hook子程的返回值確定系統是否允許或者防止這些操作中的一個。
WH_DEBUG Hook
在系統呼叫系統中與其他Hook關聯的Hook子程之前,系統會呼叫WH_DEBUG Hook子程。你可以使用這個Hook來決定是否允許系統呼叫與其他Hook關聯的Hook子程。
WH_FOREGROUNDIDLE Hook
當應用程式的前臺執行緒處於空閒狀態時,可以使用WH_FOREGROUNDIDLE Hook執行低優先順序的任務。當應用程式的前臺執行緒大概要變成空閒狀態時,系統就會呼叫WH_FOREGROUNDIDLE Hook子程。
WH_GETMESSAGE Hook
應用程式使用WH_GETMESSAGE Hook來監視從GetMessage or PeekMessage函式返回的訊息。你可以使用WH_GETMESSAGE Hook去監視滑鼠和鍵盤輸入,以及其他傳送到訊息佇列中的訊息。
WH_JOURNALPLAYBACK Hook:使應用程式可以插入訊息到系統訊息佇列。
WH_JOURNALRECORD Hook:監視和記錄輸入事件,記錄連續的滑鼠和鍵盤事件並回放。
WH_KEYBOARD Hook:在應用程式中監視WM_KEYDOWN 和 WM_KEYUP訊息;
WH_KEYBOARD_LL Hook:監視輸入到執行緒訊息佇列中的鍵盤訊息。
WH_MOUSE Hook:監視從GetMessage 或者 PeekMessage 函式返回的滑鼠訊息。
WH_MOUSE_LL Hook:監視輸入到執行緒訊息佇列中的滑鼠訊息。
WH_MSGFILTER 和 WH_SYSMSGFILTER Hooks:監視選單,捲軸,訊息框,對話方塊訊息並且發現使用者使用ALT+TAB or ALT+ESC 組合鍵切換視窗。
WH_SHELL Hook:外殼應用程式可以使用WH_SHELL Hook去接收重要的通知。當外殼應用程式是啟用的並且當頂層視窗建立或者銷燬時,系統呼叫WH_SHELL Hook子程。
WH_SHELL 共有5種情況:
只要有個top-level、unowned 視窗被產生、起作用、或是被摧毀;
當系統需要顯示關於Taskbar的一個程式的最小化形式;
當目前的鍵盤佈局狀態改變;
當使用者按Ctrl+Esc去執行Task Manager(或相同級別的程式)。
按照慣例,外殼應用程式都不接收WH_SHELL訊息。
所以,在應用程式能夠接收WH_SHELL訊息之前,應用程式必須呼叫SystemParametersInfo function進行註冊。
鉤子很厲害,用法需謹慎~