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

API Hook 技術:透過 IAT 修改攔截 MessageBoxW 完整實作

     編輯:關於C++

核心原理

利用 PE 格式的匯入位址表 (IAT, Import Address Table) 修改,將目標模組對特定 DLL 函數 (如 MessageBoxW) 的呼叫重導向至自訂函數。配合全域 Hook 將 DLL 注入目標進程,實現 API 攔截。

架構概覽

  1. 建立 DLL 專案,包含 Hook 回呼函數與 IAT 修改邏輯
  2. EXE 載入 DLL 並安裝全域 Hook (如 WH_GETMESSAGE),強制系統將 DLL 映射至目標進程位址空間
  3. DLL 載入時 (DLL_PROCESS_ATTACH) 執行 IAT 修改,替換目標函數入口
  4. 自訂函數執行自訂邏輯後,可選擇呼叫原函數或完全取代

DLL 核心程式碼 (關鍵片段)

自訂攔截函數

static int WINAPI MyMessageBoxW(HWND hWnd, LPCWSTR lpText,
                                LPCWSTR lpCaption, UINT uType) {
    // 完全取代訊息內容
    return MessageBox(hWnd, L"TNT", L"TNT", uType);
}

IAT 定位與修改核心邏輯


// 取得指定模組的指定匯入 DLL 描述子
PIMAGE_IMPORT_DESCRIPTOR GetNamedImportDescriptor(HMODULE hModule, LPCSTR szImportMod) {
    PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)hModule;
    if (pDos->e_magic != IMAGE_DOS_SIGNATURE) return NULL;

    PIMAGE_NT_HEADERS pNT = (PIMAGE_NT_HEADERS)((DWORD)pDos + pDos->e_lfanew);
    if (pNT->Signature != IMAGE_NT_SIGNATURE) return NULL;

    // 匯入表 RVA
    DWORD importRVA = pNT->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress;
    if (!importRVA) return NULL;

    PIMAGE_IMPORT_DESCRIPTOR pImport = (PIMAGE_IMPORT_DESCRIPTOR)((DWORD)pDos + importRVA);
    while (pImport->Name) {
        PSTR szCurr = (PSTR)((DWORD)pDos + pImport->Name);
        if (stricmp(szCurr, szImportMod) == 0) break;  // 找到目標 DLL (如 USER32.DLL)
        pImport++;
    }
    return (pImport->Name) ? pImport : NULL;
}

// 依函數名稱 Hook API
BOOL HookAPIByName(HMODULE hModule, LPCSTR szImportMod, LPHOOKAPI pHookApi) {
    PIMAGE_IMPORT_DESCRIPTOR pImport = GetNamedImportDescriptor(hModule, szImportMod);
    if (!pImport) return FALSE;

    PIMAGE_THUNK_DATA pOrigThunk = (PIMAGE_THUNK_DATA)((DWORD)hModule + pImport->OriginalFirstThunk);
    PIMAGE_THUNK_DATA pRealThunk = (PIMAGE_THUNK_DATA)((DWORD)hModule + pImport->FirstThunk);

    while (pOrigThunk->u1.Function) {
        // 依名稱匯入 (非序數)
        if (!(pOrigThunk->u1.Ordinal & IMAGE_ORDINAL_FLAG)) {
            PIMAGE_IMPORT_BY_NAME pByName = (PIMAGE_IMPORT_BY_NAME)((DWORD)hModule + pOrigThunk->u1.AddressOfData);
            if (strcmpi(pHookApi->szFunc, (char*)pByName->Name) == 0) {
                // 修改頁面保護為可寫
                MEMORY_BASIC_INFORMATION mbi;
                VirtualQuery(pRealThunk, &mbi, sizeof(mbi));
                DWORD oldProtect;
                VirtualProtect(mbi.BaseAddress, mbi.RegionSize, PAGE_READWRITE, &oldProtect);

                // 保存原函數指標並替換
                if (!pHookApi->pOldProc) pHookApi->pOldProc = (PROC)pRealThunk->u1.Function;
                pRealThunk->u1.Function = (PDWORD)pHookApi->pNewProc;

                // 還原保護屬性
                VirtualProtect(mbi.BaseAddress, mbi.RegionSize, oldProtect, &oldProtect);
                return TRUE;
            }
        }
        pOrigThunk++; pRealThunk++;
    }
    return FALSE;
}

Hook 啟用/停用控制


BYTE OldCode[5], NewCode[5];  // 儲存原始/新指令 (JMP 相對跳轉 5 字節)
FARPROC fpTarget;             // 目標 API 位址

void HookOn() {
    HANDLE hProc = OpenProcess(PROCESS_ALL_ACCESS, FALSE, GetCurrentProcessId());
    VirtualProtectEx(hProc, fpTarget, 5, PAGE_READWRITE, &oldProtect);
    WriteProcessMemory(hProc, fpTarget, NewCode, 5, NULL);  // 寫入 JMP MyFunc
    VirtualProtectEx(hProc, fpTarget, 5, oldProtect, &oldProtect);
}

void HookOff() {
    HANDLE hProc = OpenProcess(PROCESS_ALL_ACCESS, FALSE, GetCurrentProcessId());
    VirtualProtectEx(hProc, fpTarget, 5, PAGE_READWRITE, &oldProtect);
    WriteProcessMemory(hProc, fpTarget, OldCode, 5, NULL);  // 還原原始指令
    VirtualProtectEx(hProc, fpTarget, 5, oldProtect, &oldProtect);
}

EXE 測試程式


// 動態載入 DLL 並安裝 Hook
extern "C" __declspec(dllimport) BOOL __stdcall InstallHook();
extern "C" __declspec(dllimport) BOOL __stdcall UninstallHook();

void __fastcall TForm1::Button1Click(TObject *Sender) {
    if (InstallHook()) {
        MessageBox(NULL, "內容", "標題", MB_OK);  // 實際彈出 "TNT"
    }
    UninstallHook();
}

關鍵技術點總結

  • IAT 修改:僅影響特定模組對特定 DLL 的匯入,不影響其他模組,相對安全
  • 指令長度:x86 下 JMP rel32 指令佔 5 字節 (0xE9 + 4 位元組相對位移)
  • 記憶體保護:修改前必須 VirtualProtect/VirtualProtectEx 設為 PAGE_READWRITE,修改後還原
  • 執行緒安全:多執行緒環境下修改 IAT 需考慮同步,範例為簡化省略
  • 64 位元注意:x64 下指令長度不同,需使用 JMP [RIP+offset] 或絕對跳轉,IAT 結構亦不同

參考資源

  • Matt Pietrek, "Under The Hood" 系列文章 (MSJ/MSDN)
  • 《Windows 核心程式設計》第 5 版 - DLL 注入與 Hook 章節
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved