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

Windows 函數 Hook 技術全解析:從 IAT 到內聯 Hook 及 DLL 注入實戰

     編輯:關於C語言

概述

Windows 下的函數 Hook 技術是系統編程、調試工具、安全軟件及熱補丁的核心手段。本文系統梳理了三種主流 Hook 方案:導入地址表 Hook、按序數 Hook、內聯 Hook,並給出一個完整的 TextOutA Hook 實例,演示如何通過 CreateRemoteThread 將 Hook DLL 注入目標進程。

一、Hook 導入函數

原理背景

調用外部 DLL 導出函數時,編譯器生成 CALL [IAT槽] 指令。PE 文件的 IMAGE_IMPORT_DESCRIPTOR 結構指向兩個數組:

  • 原始導入地址表:未修改,按名稱/序數列出導入函數。
  • 真實導入地址表:加載時由載入器填寫真實函數地址,程式實際跳轉使用此表。

Hook 思路:定位目標模塊的 IMAGE_IMPORT_DESCRIPTOR,遍歷 原始 IAT 找到目標函數的索引,然後修改 真實 IAT 對應項為自定義 Hook 函數地址。

實現步驟

  1. 獲取模塊基址,遍歷節區找到導入表目錄。
  2. 遍歷 IMAGE_IMPORT_DESCRIPTOR 找到目標 DLL(如 GDI32.dll)。
  3. 遍歷 OriginalFirstThunk(原始 IAT)查找目標函數名,記錄索引 index。
  4. 修改 FirstThunk(真實 IAT)中 index 處的地址為 Hook 函數地址。
  5. 保存原始地址,以便在 Hook 函數中調用原函數。

按序數 Hook

原理相同,只是在原始 IAT 中通過 IMAGE_ORDINAL_FLAG 判斷是否為序數導入,並比對序數值定位索引。

二、Hook 本模塊內的函數(內聯 Hook)

當目標函數位於通過 LoadLibrary 動態加載的 DLL 中,IAT Hook 無效。內聯 Hook 直接修改目標函數機器碼:

  1. 在目標函數入口處寫入 JMP HookFunc 指令(通常需 5 字節:E9 xx xx xx xx)。
  2. 保存被覆蓋的原始指令(需完整指令對齊),放入 Hook 函數尾部(Trampoline)。
  3. Hook 函數執行完自定義邏輯後,執行保存的原始指令,再 JMP 回目標函數之後繼續執行。

關鍵點: 需處理指令長度變化、線程安全(VirtualProtect 修改頁屬性)、多處理器同步。

三、完整實例:Hook TextOutA 並注入 DLL

Hook DLL 核心代碼

// 全局變量
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);
// 清理

四、工程實踐注意事項

  • 指令長度:x86 下 JMP rel32 固定 5 字節;x64 需用 MOV RAX, addr / JMP RAX(14 字節)或 JMP [RIP+offset]。
  • 原子性:多線程環境下修改代碼需暫停其他線程或使用 InterlockedExchange64(x64)保證寫入原子性。
  • 調試檢測:檢查 IsDebuggerPresent、時間檢測、校驗和碼完整性(CRC/Hash)以防分析。
  • 兼容性:系統 DLL 版本更新可能導致偏移失效,建議動態獲取地址而非硬編碼。

總結

IAT Hook 適合針對特定模塊的導入函數,實現簡潔;內聯 Hook 通用性強,可 Hook 任意地址,但實現複雜且風險高。結合 CreateRemoteThread 注入技術,可實現跨進程的 API 監控、參數攔截、熱補丁等高級功能。實際項目中建議成熟庫(如 Detours、MinHook、EasyHook)以減少錯誤。

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