一、什么是HOOK(鈎子) API
Windows消息傳遞機制,當在應用程序進行相關操作,例如點擊鼠標、按下鍵盤,操作窗口等,操作系統能夠感知這一事件,接着把此消息放到系統消息隊列,然后到應用程序的消息序列中,應用程序通過Getmessage函數取出消息,然后調用DispatchMessage函數將這條消息調度給操作系統,操作系統會調用在設計窗口類時指定的應用程序窗口對這一消息進行處理,處理過程如圖所示:

在《VC深入詳解》一書將鈎子過程比喻為警察為了抓逃犯而設置的檢查站,基本原理也確實與此類似。就是在應用程序將信息傳遞給操作系統時(圖中③ 的),對消息進行捕獲和過濾,從而阻止消息發送到指定的窗口過程,最終完成對某些消息的屏蔽功能。
HOOK(鈎子,掛鈎)是一種實現Windows平台下類似於中斷的機制。HOOK機制允許應用程序攔截並處理Windows消息或指定事件,當指定的消息發出后,HOOK程序就可以在消息到達目標窗口之前將其捕獲,從而得到對消息的控制權,進而可以對該消息進行處理或修改,加入我們所需的功能。鈎子按使用范圍分,可分為線程鈎子和系統鈎子,其中,系統鈎子具有相當大的功能,幾乎可以實現對所有Windows消息的攔截、處理和監控。這項技術涉及到兩個重要的API,一個是SetWindowsHookEx,安裝鈎子;另一個是UnHookWindowsHookEx,卸載鈎子。
對於Windows系統,它是建立在事件驅動機制上的,說白了就是整個系統都是通過消息傳遞實現的。hook(鈎子)是一種特殊的消息處理機制,它可以監視系統或者進程中的各種事件消息,截獲發往目標窗口的消息並進行處理。所以說,我們可以在系統中自定義鈎子,用來監視系統中特定事件的發生,完成特定功能,如屏幕取詞,監視日志,截獲鍵盤、鼠標輸入等等。
程序員在討論時也常說“可以先鈎住再處理”,即執行某操作之前,優先處理一下,再決定后面的執行走向。
鈎子的種類很多,每種鈎子可以截獲相應的消息,如鍵盤鈎子可以截獲鍵盤消息,外殼鈎子可以截取、啟動和關閉應用程序的消息等。鈎子可以分為線程鈎子和系統鈎子,線程鈎子可以監視指定線程的事件消息,系統鈎子監視系統中的所有線程的事件消息。因為系統鈎子會影響系統中所有的應用程序,所以鈎子函數必須放在獨立的動態鏈接庫(DLL) 中。
所以說,hook(鈎子)就是一個Windows消息的攔截機制,可以攔截單個進程的消息(線程鈎子),也可以攔截所有進程的消息(系統鈎子),也可以對攔截的消息進行自定義的處理。Windows消息帶了一些程序有用的信息,比如Mouse類信息,就帶有鼠標所在窗體句柄、鼠標位置等信息,攔截了這些消息,就可以做出例如金山詞霸一類的屏幕取詞功能。
二、Hook 分類
(1) 線程鈎子監視指定線程的事件消息。
(2) 系統鈎子監視系統中的所有線程的事件消息。因為系統鈎子會影響系統中所有的應用程序,所以鈎子函數必須放在獨立的動態鏈接庫(DLL)中。這是系統鈎子和線程鈎子很大的不同之處。
三、HOOK(鈎子)的工作原理
在正確使用鈎子函數前,我們先講解鈎子函數的工作原理。當您創建一個鈎子時,WINDOWS會先在內存中創建一個數據結構,該數據結構包含了鈎子的相關信息,然后把該結構體加到已經存在的鈎子鏈表中去。新的鈎子將加到老的前面。當一個事件發生時,如果您安裝的是一個線程鈎子,您進程中的鈎子函數將被調用。如果是一個系統鈎子,系統就必須把鈎子函數插入到其它進程的地址空間,要做到這一點要求鈎子函數必須在一個動態鏈接庫中,所以如果您想要使用系統鈎子,就必須把該鈎子函數放到動態鏈接庫中去。
當然有兩個例外:工作日志鈎子和工作日志回放鈎子。這兩個鈎子的鈎子函數必須在安裝鈎子的線程中。原因是:這兩個鈎子是用來監控比較底層的硬件事件的,既然是記錄和回放,所有的事件就當然都是有先后次序的。所以如果把回調函數放在DLL中,輸入的事件被放在幾個線程中記錄,所以我們無法保證得到正確的次序。故解決的辦法是:把鈎子函數放到單個的線程中,譬如安裝鈎子的線程。
幾點需要說明的地方:
(1) 如果對於同一事件(如鼠標消息)既安裝了線程鈎子又安裝了系統鈎子,那么系統會自動先調用線程鈎子,然后調用系統鈎子。
(2) 對同一事件消息可安裝多個鈎子處理過程,這些鈎子處理過程形成了鈎子鏈。當前鈎子處理結束后應把鈎子信息傳遞給下一個鈎子函數。而且最近安裝的鈎子放在鏈的開始,而最早安裝的鈎子放在最后,也就是后加入的先獲得控制權。
(3) 鈎子特別是系統鈎子會消耗消息處理時間,降低系統性能。只有在必要的時候才安裝鈎子,在使用完畢后要及時卸載。
