C++ windows 平臺的 Hook

 

From:https://www.jianshu.com/p/1cbde2276752

Windows Hook(鉤子)函數詳解:https://wenku.baidu.com/view/fd9088aaf46527d3250ce059.html

 

環境:vs 2019,添加Windows.h頭文件。

核心函數:SetWindowsHookEx()UnhookWindowsHookEx()CallNextHookEx()
你可能在其他api文檔看到SetWindowsHookExA、SetWindowsHookExW這兩個函數,他們是編碼上的區別,A指ASCII,W指wide-char也就是unicode編碼,但系統已經幫我們用宏處理好,只需調用 SetWindowsHookEx 就行了,自動選擇對應編碼那個函數。

Hook:建議先百度自行稍微瞭解

HINSTANCE、HMODULE、HANDLE、HWND是一樣的東西,都是各種 typedef 繞來繞去,區別只在於人類使用時附加的語義,實際上都是一串數字。下面就混着用了。
下面僅以最基本的控制檯應用示例。

 

非常簡單的一個例子,運行後鼠標移動即可看到效果:

LRESULT CALLBACK mymouse(int nCode, WPARAM wParam, LPARAM lParam)
{
    cout<<"yes"<<endl;
    return CallNextHookEx(NULL, nCode, wParam, lParam);
}

int main()
{
    HHOOK mouseHook = SetWindowsHookEx(WH_MOUSE_LL, mymouse, 0, 0);
    MSG msg;
    while (GetMessage(&msg, NULL, NULL, NULL))
    {
    }
    UnhookWindowsHookEx(mouseHook);
    return 0;
}

 

1.mymouse函數:
LRESULT:long result,實際就是個宏,當long(長整數)就行。
CALLBACK:也是宏,實際上是__stdcall,函數修飾關鍵字,詳細自行可百度。
WPARAM、LPARAM:還是宏,當整數就行。不同的值有不同含義,具體看msdn。

爲什麼這樣定義?
因爲SetWindowsHookEx()的第二個參數就是接受這樣的一個函數指針。簡單瞭解使用函數指針
該函數返回0時,消息繼續往下傳遞;返回1時消息不再往下傳遞。

爲什麼是return CallNextHookEx()而不是直接return 0或1?
SetWindowsHookEx()把這個鉤子放在Hook鏈頭,不調用這個方法其他鏈節點的Hook不執行。另外該函數的第一個參數沒用,直接null就行。

2.HHOOK類型:
代表加進去的鉤子,後面解除鉤子的函數UnhookWindowsHookEx(),參數就是這個的對象。

3.MSG、while:
爲了不讓程序結束,用一個while卡住它。

爲什麼不用while(true)?
看第5點

4.SetWindowsHookEx函數:
原型:

HHOOK WINAPI SetWindowsHookEx(
  _In_ int       idHook,
  _In_ HOOKPROC  lpfn,
  _In_ HINSTANCE hMod,
  _In_ DWORD     dwThreadId
);

看到陌生的不用頭大,都是關鍵字和宏罷了,直接講4個參數。
idHook:一個int值,不同的值對應不同的鉤子(鼠標or鍵盤之類的)。vs中打出WH_後,看自動補全顯示的名字,你就能瞭解了,不用死記硬背。WH就是window hook的意思。
lpfn:函數指針,類型就是mymouse函數那個類型。每當鉤子獲取到消息,就會調用該函數指針。
hMod:用於幫助找到真正的地址,下面會講。
dwThreadId:DWORD也是一個宏,當整數就行。這裏指你要把鉤子掛到哪個線程中,所有進程的所有線程都可以選擇,只不過其他進程的線程不一定掛的上,需要其他手段,不作細說。填0就是嘗試把所有線程都給掛上,成了所謂的全局鉤子(只是嘗試,拒絕掛鉤子的線程還是掛不上)。

當掛鉤子失敗時,該函數返回null。另外msdn講解參數這段一定要看:
lpfn [in]
Type: HOOKPROC

A pointer to the hook procedure. If the dwThreadId parameter is zero or specifies the identifier of a thread created by a different process, the lpfn parameter must point to a hook procedure in a DLL. Otherwise, lpfn can point to a hook procedure in the code associated with the current process.
如果dwThreadId指定的線程並不是由當前進程創建的,那麼子程(就是那個函數,如示例的mymouse)一定要寫在dll裏(動態鏈接庫)。
原因:由於lpfn是指針和邏輯地址的機制,當前進程的函數地址不適用於其他進程。系統一般需要藉助dll模塊以確定函數入口地址(low-level除外,只有此時可以不寫dll)。

hMod [in]
Type: HINSTANCE

A handle to the DLL containing the hook procedure pointed to by the lpfn parameter. The hMod parameter must be set to NULL if the dwThreadId parameter specifies a thread created by the current process and if the hook procedure is within the code associated with the current process.
當 dwThreadId指定的線程是當前進程創建的線程 子程的代碼是在當前進程(非dll)寫的,那麼這個hMod必須填0(也就是null)。
值得一提,在dll中時,這個參數要填爲該dll的句柄,可以有兩種方法獲取來填:
(1)把入口DllMain的參數hModule(HMODULE和HINSTANCE一樣的)保存下來,填進去(推薦)
(2)通過函數GetModuleHandle(L"dll name")來獲取(加L代表爲unicode編碼,不用加dll後綴)(不推薦)

絕大多數情況下會寫成動態鏈接庫(建議)。上面示例能算上特例,不寫在dll中就要:首先是鉤子類型不是WH_MOUSE而是WH_MOUSE_LL(LL代表low-level,另一種回調機制,寫在dll中則兩種都可以),不然沒反應,其次第三個參數hMod寫成0。

5.GetMessage函數:從線程消息隊列獲取消息,沒有消息將進入等待態直至有消息被插入隊列
(1)系統獲取消息-----系統分發消息---(Hook鏈)-----消息抵達線程消息隊列。(這下搞懂第一點說的消息是否繼續傳遞和CallNextHookEx了吧)
(2)我們示例的控制檯程序創建時默認是沒有消息隊列的,但調用相關函數時就自動創建消息隊列,如GetMessage函數
(3)子程是由系統通過回調機制,把執行SetWindowsHookEx函數的線程叫回來去執行的,也就是示例中的mymouse函數是由主線程去執行的。因此,如果寫成while(true),那麼線程將無限運行,系統沒有機制能夠中斷它的執行來把它叫過來執行子程。但是,調用GetMessage函數後,線程有了消息隊列,而且我們沒有往裏面插入消息,線程就進入等待態,此時系統便能夠控制該線程,讓它去執行子程。也就是說,我們需要把這個線程弄成等待態去讓系統獲取控制權,所以你還可以使用while(true){ Sleep(100); }達到相同目的。
(4)由於上述原因,對於掛Hook的線程來說,這樣一個循環查詢消息隊列是有必要的。
(5)如果系統發現hook的子程長期得不到執行(你的線程正在繁忙),那麼系統會自動刪除掉這個hook。這裏就要注意了,不要安排耗時任務在該線程上。

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章