Windows 下的函數 Hook 技術是系統編程、調試工具、安全軟件及熱補丁的核心手段。本文系統梳理了三種主流 Hook 方案:導入地址表 Hook、按序數 Hook、內聯 Hook,並給出一個完整的 TextOutA Hook 實例,演示如何通過 CreateRemoteThread 將 Hook DLL 注入目標進程。
調用外部 DLL 導出函數時,編譯器生成 CALL [IAT槽] 指令。PE 文件的 IMAGE_IMPORT_DESCRIPTOR 結構指向兩個數組:
Hook 思路:定位目標模塊的 IMAGE_IMPORT_DESCRIPTOR,遍歷 原始 IAT 找到目標函數的索引,然後修改 真實 IAT 對應項為自定義 Hook 函數地址。
IMAGE_IMPORT_DESCRIPTOR 找到目標 DLL(如 GDI32.dll)。OriginalFirstThunk(原始 IAT)查找目標函數名,記錄索引 index。FirstThunk(真實 IAT)中 index 處的地址為 Hook 函數地址。原理相同,只是在原始 IAT 中通過 IMAGE_ORDINAL_FLAG 判斷是否為序數導入,並比對序數值定位索引。
當目標函數位於通過 LoadLibrary 動態加載的 DLL 中,IAT Hook 無效。內聯 Hook 直接修改目標函數機器碼:
JMP HookFunc 指令(通常需 5 字節:E9 xx xx xx xx)。JMP 回目標函數之後繼續執行。關鍵點: 需處理指令長度變化、線程安全(VirtualProtect 修改頁屬性)、多處理器同步。
// 全局變量
BYTE g_abOriCode[5]; // 保存 TextOutA 前 5 字節
BYTE g_abJmpCode[5] = {0xE9}; // JMP 指令模板
PROC g_oriTextout = NULL; // 原始地址
BOOL g_blHooked = FALSE;
// 安裝 Hook
LRESULT WINAPI InstallTextoutHook() {
if (g_blHooked) return TRUE;
HMODULE hGdi32 = LoadLibrary(_T("Gdi32.dll"));
g_oriTextout = GetProcAddress(hGdi32, "TextOutA");
if (!g_oriTextout) return FALSE;
// 計算 Hook 函數相對偏移(假設 DLL 基址 0x10000000)
HMODULE hSelf = GetModuleHandle(_T("HookDLL.dll"));
DWORD dwHookAddr;
__asm {
mov esi, offset HookLabel
mov edi, 0x10000000
sub esi, edi
add esi, hSelf
mov [dwHookAddr], esi
}
// 保存原始代碼
__asm {
mov esi, g_oriTextout
lea edi, g_abOriCode
cld
movsd
movsb
}
// 生成 JMP 指令
__asm {
mov eax, dwHookAddr
mov ebx, g_oriTextout
add ebx, 5
sub eax, ebx
mov dword ptr[g_abJmpCode+1], eax
}
// 寫入目標進程內存
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, GetCurrentProcessId());
DWORD dwOld;
VirtualProtectEx(hProcess, g_oriTextout, 5, PAGE_READWRITE, &dwOld);
WriteProcessMemory(hProcess, g_oriTextout, g_abJmpCode, 5, NULL);
VirtualProtectEx(hProcess, g_oriTextout, 5, dwOld, &dwOld);
g_blHooked = TRUE;
return TRUE;
}
// Hook 處理函數(裸函數,手動保存/恢復上下文)
__declspec(naked) void HookLabel() {
__asm {
// 保存寄存器、建立堆棧幀
PUSHAD
// 此處可添加日誌、參數檢查等邏輯
// 調用原始函數:需先恢復原始指令,調用後再重寫 JMP
// 簡化示例:直接 JMP 回原函數+5
POPAD
JMP g_oriTextout + 5
}
}
// 目標進程 PID
DWORD dwPID = ...;
HANDLE hProcess = OpenProcess(PROCESS_CREATE_THREAD | PROCESS_VM_OPERATION | PROCESS_VM_WRITE, FALSE, dwPID);
// 在目標進程分配內存寫入 DLL 路徑
LPVOID pRemotePath = VirtualAllocEx(hProcess, NULL, MAX_PATH, MEM_COMMIT, PAGE_READWRITE);
WriteProcessMemory(hProcess, pRemotePath, dllPath, MAX_PATH, NULL);
// 創建遠程線程調用 LoadLibrary
HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)LoadLibrary, pRemotePath, 0, NULL);
WaitForSingleObject(hThread, INFINITE);
// 清理
JMP rel32 固定 5 字節;x64 需用 MOV RAX, addr / JMP RAX(14 字節)或 JMP [RIP+offset]。InterlockedExchange64(x64)保證寫入原子性。IsDebuggerPresent、時間檢測、校驗和碼完整性(CRC/Hash)以防分析。IAT Hook 適合針對特定模塊的導入函數,實現簡潔;內聯 Hook 通用性強,可 Hook 任意地址,但實現複雜且風險高。結合 CreateRemoteThread 注入技術,可實現跨進程的 API 監控、參數攔截、熱補丁等高級功能。實際項目中建議成熟庫(如 Detours、MinHook、EasyHook)以減少錯誤。