API Hook 是 Windows 系統編程核心技術,廣泛應用於安全軟體、調試器、熱補丁、行為分析。本文詳細講解基於「修改目標函數前 5 字節為 JMP 指令」的用戶態 Hook 方案,包含 DLL 注入、內存保護修改、原始代碼備份與恢復完整流程。
MessageBoxA 等系統 API,重定向到自定義函數 MyMessageBoxA。MessageBoxA 入口處前 5 字節覆蓋為 JMP MyMessageBoxA(機器碼 E9 xx xx xx xx,相對偏移)。VirtualProtectEx:修改內存頁屬性為 PAGE_READWRITE,允許寫入代碼段。WriteProcessMemory:跨進程寫入 JMP 指令 / 恢復原始字節。ReadProcessMemory:備份原始 5 字節。SetWindowsHookEx(WH_GETMESSAGE, ...) 將 Hook DLL 注入所有 GUI 進程,實現全局攔截。// 全局變量
HHOOK g_hHook = NULL;
HINSTANCE g_hInstDll = NULL;
FARPROC g_pfMessageBoxA = NULL;
BYTE g_oldBytes[5], g_newBytes[5];
DWORD g_dwTargetPid = 0;
BOOL g_bHooked = FALSE;
// DllMain:進程附著時初始化
BOOL APIENTRY DllMain(HANDLE hModule, DWORD reason, LPVOID lpReserved) {
switch (reason) {
case DLL_PROCESS_ATTACH:
if (!InitHook()) return FALSE;
break;
case DLL_PROCESS_DETACH:
if (g_bHooked) Unhook();
break;
}
return TRUE;
}
// 初始化:獲取目標函數地址、構建 JMP 指令
BOOL InitHook() {
HMODULE hUser32 = LoadLibrary("user32.dll");
g_pfMessageBoxA = GetProcAddress(hUser32, "MessageBoxA");
if (!g_pfMessageBoxA) return FALSE;
// 1. 備份原始 5 字節
memcpy(g_oldBytes, g_pfMessageBoxA, 5);
// 2. 構建 JMP 指令:E9 + 相對偏移(目標 - 源 - 5)
g_newBytes[0] = 0xE9; // JMP rel32
DWORD_PTR src = (DWORD_PTR)g_pfMessageBoxA;
DWORD_PTR dst = (DWORD_PTR)MyMessageBoxA;
*(DWORD*)&g_newBytes[1] = (DWORD)(dst - src - 5);
g_dwTargetPid = GetCurrentProcessId();
return HookOn(); // 立即啟用
}
// 啟用 Hook:修改目標進程內存
void HookOn() {
HANDLE hProc = OpenProcess(PROCESS_ALL_ACCESS, FALSE, g_dwTargetPid);
DWORD oldProtect;
// 1. 改頁屬性可寫
VirtualProtectEx(hProc, g_pfMessageBoxA, 5, PAGE_READWRITE, &oldProtect);
// 2. 寫入 JMP 指令
WriteProcessMemory(hProc, g_pfMessageBoxA, g_newBytes, 5, NULL);
// 3. 恢復頁屬性
VirtualProtectEx(hProc, g_pfMessageBoxA, 5, oldProtect, &oldProtect);
CloseHandle(hProc);
g_bHooked = TRUE;
}
// 禁用 Hook:恢復原始字節
void HookOff() {
HANDLE hProc = OpenProcess(PROCESS_ALL_ACCESS, FALSE, g_dwTargetPid);
DWORD oldProtect;
VirtualProtectEx(hProc, g_pfMessageBoxA, 5, PAGE_READWRITE, &oldProtect);
WriteProcessMemory(hProc, g_pfMessageBoxA, g_oldBytes, 5, NULL);
VirtualProtectEx(hProc, g_pfMessageBoxA, 5, oldProtect, &oldProtect);
CloseHandle(hProc);
g_bHooked = FALSE;
}
// 自定義處理函數:必須先關閉 Hook 再調用原函數,避免無限遞歸
int WINAPI MyMessageBoxA(HWND hWnd, LPCTSTR lpText, LPCTSTR lpCaption, UINT uType) {
HookOff();
int ret = MessageBoxA(hWnd, "MessageBox 已被攔截!", lpCaption, uType);
HookOn();
return ret;
}
// 導出函數:供測試程序安裝/卸載全局鉤子
extern "C" __declspec(dllexport) BOOL InstallGlobalHook() {
g_hInstDll = LoadLibrary("HookApi2.dll");
g_hHook = SetWindowsHookEx(WH_GETMESSAGE, (HOOKPROC)DummyHook, g_hInstDll, 0);
return g_hHook != NULL;
}
extern "C" __declspec(dllexport) BOOL UninstallGlobalHook() {
return UnhookWindowsHookEx(g_hHook);
}
// 空鉤子回調:僅為保持 DLL 注入
LRESULT CALLBACK DummyHook(int nCode, WPARAM wParam, LPARAM lParam) {
return CallNextHookEx(g_hHook, nCode, wParam, lParam);
}
int APIENTRY WinMain(HINSTANCE hInst, HINSTANCE hPrev, LPSTR lpCmd, int nShow) {
if (!InstallGlobalHook()) {
MessageBoxA(NULL, "Hook 安裝失敗!", "Error", MB_OK);
return 1;
}
MessageBoxA(NULL, "TEST", "TEST", MB_OK); // 彈出被攔截的內容
if (!UninstallGlobalHook()) {
MessageBoxA(NULL, "Hook 卸載失敗!", "Error", MB_OK);
return 1;
}
return 0;
}
JMP rel32 指令長度固定 5 字節(E9 + 4 字節偏移)。偏移 = 目標地址 - 源地址 - 5
// CPU 執行 JMP 時:EIP 已指向下一條指令(源+5),故需減 5
MyMessageBoxA 內部必須先 HookOff() 再調用 MessageBoxA,否則進入無限遞歸:Hook → MyMsgBox → Hook → MyMsgBox... 棧溢出崩潰。
VirtualProtectEx + WriteProcessMemory 非原子,極短窗口期內其他線程可能讀到半寫指令。SuspendThread 遍歷進程線程)、寫入、恢復線程;或使用 硬件斷點/頁保護異常 實現無損 Hook。| 項目 | x86 (32位) | x64 (64位) |
|---|---|---|
| JMP 指令 | E9 rel32 (5字節) | FF 25 [rip+rel32] (6字節,間接跳轉需地址表) |
| 地址寬度 | 4 字節 | 8 字節 |
| 相對偏移範圍 | ±2GB | ±2GB (RIP 相對) |
| 調用約定 | __stdcall (棧傳參) | Microsoft x64 (寄存器 RCX,RDX,R8,R9 + 棧) |
x64 下通常採用 Trampoline + 間接跳轉:分配可執行內存存放 MOV RA, target; JMP RA,Hook 處寫 JMP [RIP+trampoline]。



API Hook 是雙刃劍,掌握其原理既能構建強大系統工具,也能識別惡意軟體行為。工程實踐中請優先考慮 微軟官方機制(ETW、WFP、AppContainer、Detours 庫)而非手工 Patch 代碼段,以獲得穩定性與兼容性保障。