首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Windows API Hook 技術:修改前 5 字節 JMP 跳轉法實現全局攔截

     編輯:Java 相關知識

API Hook 是 Windows 系統編程核心技術,廣泛應用於安全軟體、調試器、熱補丁、行為分析。本文詳細講解基於「修改目標函數前 5 字節為 JMP 指令」的用戶態 Hook 方案,包含 DLL 注入、內存保護修改、原始代碼備份與恢復完整流程。

一、核心原理

  1. 目標:攔截 MessageBoxA 等系統 API,重定向到自定義函數 MyMessageBoxA。
  2. 手段:在目標進程內存中,將 MessageBoxA 入口處前 5 字節覆蓋為 JMP MyMessageBoxA(機器碼 E9 xx xx xx xx,相對偏移)。
  3. 關鍵 API:
    • VirtualProtectEx:修改內存頁屬性為 PAGE_READWRITE,允許寫入代碼段。
    • WriteProcessMemory:跨進程寫入 JMP 指令 / 恢復原始字節。
    • ReadProcessMemory:備份原始 5 字節。
  4. DLL 注入載體:通過 SetWindowsHookEx(WH_GETMESSAGE, ...) 將 Hook DLL 注入所有 GUI 進程,實現全局攔截。

二、Hook DLL 核心代碼結構

// 全局變量
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;
}

四、關鍵技術細節

1. 為什麼是 5 字節?

  • x86 JMP rel32 指令長度固定 5 字節(E9 + 4 字節偏移)。
  • 若目標函數前 5 字節不是完整指令邊界,會導致執行流錯位崩潰。實踐中需反彙編確認前 5 字節為完整指令,或使用更長的 Trampoline 方案。

2. 相對偏移計算

偏移 = 目標地址 - 源地址 - 5
// CPU 執行 JMP 時:EIP 已指向下一條指令(源+5),故需減 5

3. 遞歸調用防護

MyMessageBoxA 內部必須先 HookOff() 再調用 MessageBoxA,否則進入無限遞歸:Hook → MyMsgBox → Hook → MyMsgBox... 棧溢出崩潰。

4. 多線程安全

  • VirtualProtectEx + WriteProcessMemory 非原子,極短窗口期內其他線程可能讀到半寫指令。
  • 生產級方案:暫停所有線程(SuspendThread 遍歷進程線程)、寫入、恢復線程;或使用 硬件斷點/頁保護異常 實現無損 Hook。

五、64 位系統遷移要點

項目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]。

六、效果演示

掛接前正常 MessageBox

Hook 程序界面

被攔截後的 MessageBox

七、法律與道德邊界

  • 合法用途:調試器、熱補丁、兼容性修復、安全監控(殺毒/EDR)、自動化測試。
  • 非法風險:竊取密碼、繞過授權、注入惡意代碼、隱藏進程/文件(Rootkit 行為)。
  • 簽名與審計:企業級 Hook 組件必須簽名,行為納入 SIEM 審計,防止濫用。

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

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved