0x01 簡介
有人稱它為“鈎子”,有人稱它為“掛鈎”技術。談到鈎子,很容易讓人聯想到在釣東西,比如魚鈎就用於釣魚。編程技術的鈎子也是在等待捕獲系統中的某個消息或者動作。鈎子的應用范圍非常廣泛,比如輸入監控、API攔截、消息捕獲、改變程序執行流程等方面。殺毒軟件會用Hook技術鈎住一些API函數,比如鈎住注冊表讀寫函數,從而防止病毒對注冊表進行寫入;病毒使用Hook技術有針對性的捕獲鍵盤的輸入,從而記錄用戶的密碼等敏感信息;文件加密系統通過Hook技術在不改變用戶操作的情況下對用戶的文件進行透明加密,這些都屬於Hook范疇的知識。
在windows系統下,Hook技術的方法比較多,使用比較靈活,常見的Hook方法有Inline Hook,IAT Hook,Windows鈎子。。。Hook技術涉及DLL相關的知識。Hook技術也涉及注入的知識,想要把完成Hook功能的DLL文件加載到目標進程空間中,就要使用注入的知識。
0x02 常見Hook技術介紹
- Inline Hook
API函數都保存在操作系統提供的DLL文件中,當在程序中調用某個API函數並運行程序后,程序會隱式地將API函數所在的DLL文件加載入內存中,這樣,程序就會像調用自己的函數一樣調用API。Inline Hook這種方法是在程序流程中直接進行嵌入jmp指令來改變流程的。
Inline Hook流程
- 構造跳轉指令。
- 在內存中找到欲Hook函數地址,並保存欲Hook位置處的前5字節。
- 將構造的跳轉指令寫入需Hook的位置處。
- 當被Hook位置被執行時會轉到自己的流程執行。
- 如果要執行原來的流程,那么取消Hook,也就是還原被修改的字節。
- 執行原來的流程。
- 繼續Hook住原來的位置
這就是Inline Hook的大概流程。
- 導入地址表鈎子-IAT HOOK
導入地址表是PE文件結構中的一個表結構。在可執行文件中使用其他DLL可執行文件的代碼或數據,成為導入或者輸入。當PE文件需要運行時,將被系統加載至內存中,此時windows加載器會定位所有的導入的函數或者數據將定位到的內容填寫至可執行文件的某個位置供其使用。這個地位是需要借助於可執行文件的導入表來完成的。導入表中存放了所使用的DLL的模塊名稱及導入的函數名稱或函數序號。
在加殼和脫殼的研究中,導入表是非常關鍵的部分。加殼要盡可能地隱藏或破壞原始的導入表。脫殼一定要找到或者還原或者重建原始的導入表,如果無法還原或修過脫殼后的導入表的話,那么可執行文件仍然是無法運行的。
- windows鈎子函數
windows下的窗口應用程序是基於消息驅動的,但是在某種情況下需要捕獲或者修改消息,從而完成一些特殊的功能。對於捕獲消息而言,無法使用IAT或Inline Hook之類的方式去進行捕獲,不過windows提供了專門用於處理消息的鈎子函數。
windows系統提供的鈎子按照掛鈎范圍分為局部鈎子和全局鈎子。局部鈎子是針對一個線程的,而全局鈎子這是針對這個操作系統內基於消息機制的應用程序的。全局鈎子需要使用DLL文件,DLL文件里面存放了鈎子函數的代碼。
在操作系統中安裝全局鈎子以后,只要進程接收到可以發出鈎子的消息后,全局鈎子的DLL文件會被操作系統自動或強行加載到該進程中,由此可見,設置消息鈎子也是一種可以進行DLL注入的方法。
windows下的鈎子函數,主要用3個,分別是SetWindowsHookEx() 、CallNextHookEx()和UnhookWindowsHookEx()
0x03 實例
Inline Hook實例

首先通過Process Explorer可以查看程序的父進程,可以看出,大部分普通的應用程序都是由explorer.exe進程創建的(像360、QQProtect以及一些驅動程序除外),那么知道把Explorer.exe 進程中CreateProcessW()函數Hook住,就可以針對要完成的工作做很多事情了,比如,可以記錄哪個應用程序被啟動,也可以對應用程序進行攔截。


關鍵代碼如下:
ILHook.cpp
#include "ILHook.h" CILHook::CILHook() { // 對成員變量的初始化 m_pfnOrig = NULL; ZeroMemory(m_bOldBytes, 5); ZeroMemory(m_bNewBytes, 5); } CILHook::~CILHook() { // 取消HOOK UnHook(); m_pfnOrig = NULL; ZeroMemory(m_bOldBytes, 5); ZeroMemory(m_bNewBytes, 5); } /* 函數名稱:Hook 函數功能:對指定模塊中的函數進行掛鈎 參數說明: pszModuleName:模塊名稱 pszFuncName: 函數名稱 pfnHookFunc: 鈎子函數 */ BOOL CILHook::Hook(LPSTR pszModuleName, LPSTR pszFuncName, PROC pfnHookFunc) { BOOL bRet = FALSE; // 獲取指定模塊中函數的地址 m_pfnOrig = (PROC)GetProcAddress(GetModuleHandle(pszModuleName), pszFuncName); if ( m_pfnOrig != NULL ) { // 保存該地址處5個字節的內容 DWORD dwNum = 0; ReadProcessMemory(GetCurrentProcess(), m_pfnOrig, m_bOldBytes, 5, &dwNum); // 構造JMP指令 m_bNewBytes[0] = '\xe9'; // jmp Opcode // pfnHookFunc是我們HOOK后的目標地址 // m_pfnOrig是原來的地址 // 5是指令長度 *(DWORD *)(m_bNewBytes + 1) = (DWORD)pfnHookFunc - (DWORD)m_pfnOrig - 5; // 將構造好的地址寫入該地址處 WriteProcessMemory(GetCurrentProcess(), m_pfnOrig, m_bNewBytes, 5, &dwNum); bRet = TRUE; } return bRet; } /* 函數名稱:UnHook 函數功能:取消函數的掛鈎 */ VOID CILHook::UnHook() { if ( m_pfnOrig != 0 ) { DWORD dwNum = 0; WriteProcessMemory(GetCurrentProcess(), m_pfnOrig, m_bOldBytes, 5, &dwNum); } } /* 函數名稱:ReHook 函數功能:重新對函數進行掛鈎 */ BOOL CILHook::ReHook() { BOOL bRet = FALSE; if ( m_pfnOrig != 0 ) { DWORD dwNum = 0; WriteProcessMemory(GetCurrentProcess(), m_pfnOrig, m_bNewBytes, 5, &dwNum); bRet = TRUE; } return bRet; }
HookCreateProcess.cpp
#include "ILHook.h" CILHook CreateProcessHook; // 我們實現的Hook函數 BOOL WINAPI MyCreateProcessW( LPCWSTR lpApplicationName, LPWSTR lpCommandLine, LPSECURITY_ATTRIBUTES lpProcessAttributes, LPSECURITY_ATTRIBUTES lpThreadAttributes, BOOL bInheritHandles, DWORD dwCreationFlags, LPVOID lpEnvironment, LPCWSTR lpCurrentDirectory, LPSTARTUPINFOW lpStartupInfo, LPPROCESS_INFORMATION lpProcessInformation ) { BOOL bRet = FALSE; if ( MessageBoxW(NULL, lpApplicationName, lpCommandLine, MB_YESNO) == IDYES ) { CreateProcessHook.UnHook(); bRet = CreateProcessW(lpApplicationName, lpCommandLine, lpProcessAttributes, lpThreadAttributes, bInheritHandles, dwCreationFlags, lpEnvironment, lpCurrentDirectory, lpStartupInfo, lpProcessInformation); CreateProcessHook.ReHook(); } else { MessageBox(NULL, "您啟動的程序被攔截", "提示", MB_OK); } // CreateProcessHook.UnHook(); // // 彈出被創建進程的進程名 // MessageBoxW(NULL, lpApplicationName, lpCommandLine, MB_OK); // // // 創建進程 // bRet = CreateProcessW(lpApplicationName, // lpCommandLine, // lpProcessAttributes, // lpThreadAttributes, // bInheritHandles, // dwCreationFlags, // lpEnvironment, // lpCurrentDirectory, // lpStartupInfo, // lpProcessInformation); // // CreateProcessHook.ReHook(); return bRet; } BOOL APIENTRY DllMain( HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved ) { switch ( ul_reason_for_call ) { case DLL_PROCESS_ATTACH: { // Hook CreateProcessW()函數 CreateProcessHook.Hook("kernel32.dll", "CreateProcessW", (PROC)MyCreateProcessW); break; } case DLL_PROCESS_DETACH: { CreateProcessHook.UnHook(); break; } } return TRUE; }
鈎子實例:
這里實現一個簡單的鍵盤記錄工具。

關鍵代碼如下:
KeyBoradHookTest.cpp 主要來生成dll文件,在該dll文件中需要定義兩個導出函數和兩個全局變量。
// KeyBoradHookTest.cpp : Defines the entry point for the DLL application. // #include "stdafx.h" extern "C" __declspec(dllexport) VOID SetHookOn(); extern "C" __declspec(dllexport) VOID SetHookOff(); // 鈎子句柄 HHOOK g_Hook = NULL; // DLL模塊句柄 HINSTANCE g_Inst = NULL; BOOL APIENTRY DllMain( HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved ) { // 保存DLL的模塊句柄 g_Inst = (HINSTANCE)hModule; return TRUE; } // 鈎子函數 LRESULT CALLBACK KeyboardProc( int code, // hook code WPARAM wParam, // virtual-key code LPARAM lParam // keystroke-message information ) { if ( code < 0 ) { return CallNextHookEx(g_Hook, code, wParam, lParam); } if ( code == HC_ACTION && lParam > 0 ) { char szBuf[MAXBYTE] = { 0 }; GetKeyNameText(lParam, szBuf, MAXBYTE); MessageBox(NULL, szBuf, NULL, MB_OK); } return CallNextHookEx(g_Hook, code, wParam, lParam); } VOID SetHookOn() { // 安裝鈎子 g_Hook = SetWindowsHookEx(WH_KEYBOARD, KeyboardProc, g_Inst, 0); } VOID SetHookOff() { // 卸載鈎子 UnhookWindowsHookEx(g_Hook); }
0x04Reference
1.http://blog.csdn.net/u013761036/article/details/52268500 DLL劫持(HiJack)原理以及實現細節
