Windows訊息鉤取
Windows訊息鉤取
簡單地說,訊息鉤取就是偷看、擷取資訊。
常規Windows訊息流:
1、發生鍵盤輸入事件時,WM_KEYDOWN訊息被新增到[OS message queue];
2、OS判斷哪個應用程式中發生了事件,然後從[OS message queue]中取出訊息,新增到相應應用程式的[application message queue]中;
3、應用程式監視自身的[application message queue],發現新新增的WM_KEYDOWN訊息後,呼叫相應的事件處理程式處理。
訊息鉤子:
Windows OS向使用者提供GUI,其是以事件驅動的方式進行工作的。每當發生這樣的事件時,OS會將事先定義好的訊息傳送給相應的應用程式,應用程式分析收到的資訊後執行相應動作。即在敲擊鍵盤時,訊息從OS傳遞到應用程式,此過程中訊息鉤子可以偷看其中的資訊。
訊息鉤子是Windows OS提供的基本功能,其中最具代表性的是VS Visual Studio中提供的SPY++,其是一個功能強大的訊息鉤取程式,能夠檢視OS中來往的所有訊息。
如下圖,OS訊息佇列和應用程式訊息佇列之間存在一條鉤鏈(Hook Chain),設定好鍵盤訊息鉤子後,處於鉤鏈中的鍵盤訊息鉤子會比應用程式先一步看到相應資訊。在鍵盤訊息鉤子函式的內部,除了可以檢視訊息之外,還可以修改訊息本身,而且還能對訊息實施攔截,阻止訊息傳遞。可以同時設定多個相同的鍵盤訊息鉤子,按照設定的順序依次呼叫,從而組成的鏈條稱為鉤鏈。
Windows訊息鉤取的實現——SetWindowsHookEx():
在Windows程式設計中,使用SetWindowsHookEx() API可以簡便地實現訊息鉤子,其用於將指定的鉤子註冊到鉤鏈中,無論在DLL內部或外部都可呼叫。
SetWindowsHookEx() API定義如下:
HHOOK SetWindowsHookEx(
int idHook, //hook type
HOOKPROC lpfn, //hook procedure
HINSTANCE hMod, //hook procedure所屬的DLL控制程式碼
DWORD dwThreadId //將要掛鉤的目標執行緒ID
);
HHOOK:返回值,鉤子控制程式碼,需要保留,等不使用鉤子時通過UnhookWindowsHookEx函式解除安裝鉤子。
idHook:鉤子的攔截訊息型別,選擇鉤子程式的攔截範圍,具體值參考文章結尾的訊息型別。
Lpfn:訊息的回撥函式地址,一般是填函式名。
hMod:鉤子函式所在的例項的控制程式碼。對於執行緒鉤子,該引數為NULL;對於系統鉤子,該引數為鉤子函式所在的DLL控制程式碼。在dll中可通過AfxInitExtensionModule(MousehookDLL, hInstance)獲得DLL控制程式碼。
dwThreadId:鉤子所監視的執行緒的執行緒號,可通過GetCurrentThreadId()獲得執行緒號。對於全域性鉤子,該引數為NULL(或0)。
鉤子過程(hook procedure)是由OS呼叫的回撥函式。安裝訊息鉤子時,鉤子過程需要存在於某個DLL內部,並且該DLL的例項控制程式碼即是hMod。
使用SetWindowsHookEx()設定好鉤子後,在某個程式中生成指定訊息時,OS會將相關的DLL檔案強制注入相應的程式,然後呼叫註冊的鉤子過程。
鍵盤訊息鉤子練習:
KeyHook.dll檔案是含有鉤子過程的DLL檔案,HookMain.exe是最先載入KeyHook.dll並安裝鍵盤鉤子的程式。HookMain.exe載入KeyHook.dll檔案後使用SetWindowsHookEx()安裝鍵盤鉤子。若其他程式中發生鍵盤輸入事件,則OS會強制將KeyHook.dll載入到相應程式的記憶體,然後呼叫KeyboardProc()函式。注意的是,OS會將KeyHook.dll載入到發生鍵盤輸入事件的所有程式,即訊息鉤取技術是一種DLL注入技術。
現在開始編寫程式碼。KeyHook.cpp
//KeyHook.cpp
#include "stdio.h"
#include "windows.h"
//定義目標程式名為notepad.exe
#define DEF_PROCESS_NAME "notepad.exe"
//定義全域性變數
HINSTANCE g_hInstance = NULL;
HHOOK g_hHook = NULL;
//DllMain()函式在DLL被載入到程式後會自動執行
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD dwReason, LPVOID lpvReserved){
switch( dwReason ){
case DLL_PROCESS_ATTACH:
g_hInstance = hinstDLL;
break;
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
//
LRESULT CALLBACK KeyboardProc(int nCode, WPARAM wParam, LPARAM lParam){
char szPath[MAX_PATH] = {0,};
char *p = NULL;
if( nCode >= 0 ){
//釋放鍵盤按鍵時,bit 31 : 0 => press, 1 => release
if( !(lParam & 0x80000000) ){
GetModuleFileNameA(NULL, szPath, MAX_PATH);
p = strrchr(szPath, '\\');
//比較當前程式名稱,若為notepad.exe,則訊息不會傳遞給應用程式或下一個鉤子函式
//_stricmp()函式用於比較字串,i表示不區分大小寫,若兩個值相等則返回0
if( !_stricmp(p + 1, DEF_PROCESS_NAME) ){
return 1;
}
}
}
//比較當前程式名稱,若非notepad.exe,則訊息傳遞給應用程式或下一個鉤子函式
return CallNextHookEx(g_hHook, nCode, wParam, lParam);
}
//在C++中呼叫C的庫檔案,用extern "C"告知編譯器,因為C++支援函式過載而C不支援,兩者的編譯規則不同
#ifdef __cplusplus
extern "C"{
#endif
//__declspec,針對編譯器的關鍵字,用於指出匯出函式
//當呼叫匯出函式HookStart()時,SetWindowsHookEx()函式就會將KeyboardProc()新增到鍵盤鉤鏈
__declspec(dllexport) void HookStart(){
g_hHook = SetWindowsHookEx(WH_KEYBOARD, KeyboardProc, g_hInstance, 0);
}
__declspec(dllexport) void HookStop(){
if(g_hHook){
UnhookWindowsHookEx(g_hHook);
g_hHook = NULL;
}
}
#ifdef __cplusplus
}
#endif
因為要生成的是KeyHook.dll檔案,因而在開始建立專案時應先選擇Win 32控制檯應用程式,再到應用程式型別中勾選DLL,編寫好cpp檔案後選擇Release再生成檔案即可得到DLL檔案。
當呼叫匯出函式HookStart()時,SetWindowsHookEx()函式就會將KeyboardProc()新增到鍵盤鉤鏈。安裝好鍵盤鉤子後,無論哪個程式,只要發生鍵盤輸入事件,OS都會強制將KeyHook.dll注入相應的程式中。
KeyboardProc()函式中發生鍵盤輸入事件時,會比較當前程式名稱和“notepad.exe”是否一致,若一致則返回1,終止KeyboardProc()函式,即截獲並刪除訊息,從而實現對notepad.exe程式的鍵盤輸入事件進行鉤取並截獲刪除、鍵盤訊息不能傳遞到notepad.exe的訊息佇列中。
KeyboardProc()函式定義如下:
LRESULT CALLBACK KeyboardProc(
int code, //HC_ACTION(0), HC_NOREMOVE(3)
WPARAM wParam, //virtual-key code
LPARAM lParam //extra information
);
其中wParam指使用者按下的鍵盤按鍵的虛擬鍵值。
HookMain.cpp
//HookMain
#include "stdio.h"
#include "windows.h"
//Console Input/Output,定義了通過控制檯進行資料輸入和資料輸出的函式
//主要是一些使用者通過按鍵盤產生的對應操作,比如getch()函式等等
#include "conio.h"
//定義一些常量
#define DEF_DLL_NAME "KeyHook.dll"
#define DEF_HOOKSTART "HookStart"
#define DEF_HOOKSTOP "HookStop"
//定義兩個引數為空、返回值為void即沒有的函式指標
typedef void (*PFN_HOOKSTART)();
typedef void (*PFN_HOOKSTOP)();
void main(){
//定義及初始化控制程式碼變數和函式指標
HMODULE hDll = NULL;
PFN_HOOKSTART HookStart = NULL;
PFN_HOOKSTOP HookStop = NULL;
//載入KeyHook.dll
hDll = LoadLibraryA(DEF_DLL_NAME);
//若載入不成功,則輸出錯誤資訊
if( hDll == NULL ){
printf("[-]無法載入%s [%d]\n", DEF_DLL_NAME, GetLastError());
return;
}
//獲取匯出函式地址
HookStart = (PFN_HOOKSTART)GetProcAddress(hDll, DEF_HOOKSTART);
HookStop = (PFN_HOOKSTOP)GetProcAddress(hDll, DEF_HOOKSTOP);
//開始鉤取
HookStart();
//直至使用者輸入“q”退出鉤取
printf("[*]等待輸入 'q' 來停止鉤取...\n");
while( _getch() != 'q' );
//終止鉤取
HookStop();
//解除安裝KeyHook.dll
FreeLibrary(hDll);
}
先載入KeyHook.dll,再呼叫HookStart()函式開始鉤取,當獲取到使用者輸入“q”後呼叫HookStop()函式終止鉤取。
由於檔案頭包含了“conio.h”庫檔案進來,一開始使得程式無法生成exe檔案:
conio即console input/output,顧名思義,可知其是需要Console進行操作的而非Windows,需要將連結器中的系統的子系統從Windows修改為Console:
執行除錯
將HookMain.exe和KeyHook.dll放在同一目錄,在cmd中執行HookMain.exe:
開啟notepad.exe,輸入內容,發現並沒有輸入進去,到Process Explorer中檢視notepad.exe程式,可以看到KeyHook.dll已經載入到其中,最後在cmd視窗中輸入q退出鉤取即可:
再開啟notepad++.exe檔案進行輸入,發現可以輸入內容,且也強制載入了KeyHook.dll檔案:
除錯Windows訊息鉤取
1、除錯HookMain.exe:
使用Ollydbg開啟,可以看到是典型的VC++啟動函式:
接著使用字串檢索法檢視核心程式碼,由於輸出顯示的內容是中文,因而找不到輸出內容字串(如果改為英文就方便多了),但是可以看到有定義變數時用到的字串如“HookStart”:
點選“HookStart”部分進入程式碼即是HookMain.exe的main()函式:
程式先在011A1006地址處呼叫LoadLibraryA(KeyHook.dll),然後在011A104E地址處的CALLEBX指令呼叫KeyHook.HookStart()函式。跟蹤進入該函式檢視:
在74C210EF地址處可以看到,呼叫了SetWindowsHookExW()函式,其上方兩條PUSH指令將該函式的第一、第二兩個引數壓入棧。其中第一個引數idHook值為2,即WH_KEYBOARD,第二個引數lpfn值為74C21020,該值即為鉤子過程的地址。
返回main()函式之後的程式碼即為接收使用者輸入的q後即終止鉤取。
2、除錯notepad.exe程式內的KeyHook.dll:
使用Ollydbg開啟notepad.exe,再F9使其執行起來。當然也可以先執行notepad.exe再開啟Ollydbg來attach該程式。
在Ollydbg中的Options>Debugging options>Events中勾選如下選項,即當新的DLL載入被除錯程式時會自動暫停除錯:
執行HookMain.exe,到notepad.exe中進行鍵盤輸入,此時Ollydbg暫停除錯並彈出Executable modules視窗:
但是卻沒有發現有KeyHook.dll。
根據系統環境不同,有時不會先顯示KeyHook.dll,而是先載入其他DLL庫。此時可以F9直至KeyHook.dll載入完成。但可能有的系統不能正常顯示,這時可以使用Ollydbg 2.0來檢視。
再次F9執行,發現了KeyHook:
可以看到,鉤子過程的地址為74C21498。
取消之前的Break on new module(DLL)設定,轉到該地址檢視,向起始地址處設定斷點:
可以發現,當每次notepad.exe程式中發生鍵盤輸入事件時,除錯都會停止在該斷點處。
相關文章
- 7- Windows訊息鉤取Windows
- Windows訊息鉤取(簡單DLL注入)Windows
- windows 訊息斷點Windows斷點
- RocketMQ -- 訊息拉取MQ
- 【RocketMQ】訊息的拉取MQ
- 再談Windows訊息迴圈Windows
- 簡析Windows訊息機制Windows
- windows 字元訊息——WM_CHARWindows字元
- 7-RocketMQ拉取訊息MQ
- 深入理解windows 訊息機制Windows
- RocketMQ -- 寫在訊息拉取前MQ
- 利用localstorage實現本地訊息快取快取
- 在 Windows 上搭建 MQTT 訊息伺服器WindowsMQQT伺服器
- 3-Windows程式設計 -視窗與訊息Windows程式設計
- WIN7環境使用 windows訊息列隊Win7Windows
- 好訊息 OR 壞訊息
- RocketMQ中PullConsumer的訊息拉取原始碼分析MQ原始碼
- 【原始碼】RocketMQ如何實現獲取指定訊息原始碼MQ
- 淺談熱補丁的鉤取方式
- 匯入地址表鉤取技術解析
- RocketMQ 訊息整合:多型別業務訊息-普通訊息MQ多型型別
- 訊息機制篇——初識訊息與訊息佇列佇列
- 關於 appium 獲取不到 toast 訊息的討論APPAST
- RocketMQ 訊息整合:多型別業務訊息——定時訊息MQ多型型別
- vmi:獲取 windows 系統硬體資訊Windows
- 淺談內聯鉤取原理與實現
- 利用redis的hash結構搭建訊息服務(發訊息,訂閱訊息,消費訊息,退訂)Redis
- RocketMQ 原理:訊息儲存、高可用、訊息重試、訊息冪等性MQ
- 訊息中介軟體—RocketMQ訊息消費(三)(訊息消費重試)MQ
- C#實現聊天訊息渲染、圖文混排(支援Windows、Linux)C#WindowsLinux
- MQTT-保留訊息和遺囑訊息MQQT
- 解析 RocketMQ 業務訊息——“事務訊息”MQ
- 解析 RocketMQ 業務訊息--“順序訊息”MQ
- 訊息中介軟體—RocketMQ訊息傳送MQ
- RabbitMQ訊息佇列(五):Routing 訊息路由MQ佇列路由
- vue---元件間傳遞訊息(父子傳遞訊息,兄弟傳遞訊息)Vue元件
- Flash 訊息
- 深入研究RocketMQ消費者是如何獲取訊息的MQ