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

鼠標劃詞取詞技術深度解析:從 GDI Hook 到 IAT 重定向的演進

     編輯:VC 教學

前言:劃詞翻譯背後的「黑科技」

無論是有道、金山詞霸,還是瀏覽器內建翻譯,「鼠標劃詞/懸停取詞」早已成為標配。但這看似簡單的功能,在 Windows 底層實現卻暗藏玄機。本文將系統梳理兩大主流技術路線——GDI API Hook 與 設備上下文 (DC) 鏡像追蹤——的原理、優劣與工程落地細節,並重點講解現代主流的 IAT (Import Address Table) 重定向 Hook 實現全流程。

技術路線對比:兩條路,一個目標

維度GDI API Hook (攔截 TextOut 等)DC 鏡像追蹤 (複製/監控每個 DC)
實現複雜度中等:需 Hook 多個 GDI 導出函數極高:需管理全系統 DC 生命週期、繪圖指令重放
兼容性優:僅依賴標準 API 入口差:易受驅動層、DirectX、GPU 加速繞過 GDI 影響
取詞精度高:直接獲取字符、字體、位置、顏色中:需自行解析繪圖指令流,錯誤率高
性能開銷低:僅在文字繪製時觸發高:所有繪圖操作均需記錄/重放
適用場景傳統 GDI 應用、Office、瀏覽器早期版本早期嘗試、特殊繪圖引擎研究

結論:業界主流均採用 GDI API Hook + IAT 重定向 方案,兼顧穩定性與精度。

核心技術:Windows API Hook 的兩種形態

1. 內存代碼修改 (Inline Hook / Trampoline)

  • 直接在目標函數入口寫入 JMP 指令跳轉到自定義處理函數。
  • 優點:全局生效,不依賴模組載入順序。
  • 缺點:需處理指令長度、對齊、多線程競態、代碼簽名校驗 (PatchGuard/CFG);9x 系統還需同時處理 16/32 位 Thunk 層,極其繁瑣。

2. IAT 重定向 (Import Address Table Hook) —— 推薦方案

  • 修改**調用者模組**的導入表,將特定 API 入口地址替換為自定義函數地址。
  • 優點:
  1. 無需修改目標代碼段,繞過 DEP/CFG 保護。
  2. 精確控制影響範圍:僅 Hook 特定進程/模組,不影響系統其他部分。
  3. 實現相對簡單,穩定性高,支援 9x/NT/現代 Windows 統一代碼庫。
  4. 天然支援鏈式調用:保存原地址,Hook 函數內可選擇調用原 API。

IAT Hook 實戰:核心知識鏈與關鍵代碼

前置知識清單

  1. PE 文件格式:Dos Header → NT Headers → Optional Header → Section Table → Data Directory[1] = Import Directory。
  2. 虛擬記憶體管理:VirtualProtect 修改頁屬性 (PAGE_READWRITE)、VirtualAllocEx 跨進程分配記憶體。
  3. 進程邊界打破:OpenProcess (PROCESS_VM_READ/WRITE/OPERATION)、WriteProcessMemory、CreateRemoteThread 注入 DLL。
  4. DLL 注入入口:DllMain (DLL_PROCESS_ATTACH) 執行 Hook 初始化。

關鍵步驟與核心代碼 (C++)

// 1. 遍歷目標模組的導入表,定位特定 DLL (如 GDI32.dll) 的特定函數 (如 TextOutW)
BOOL HookIAT(HMODULE hCallerMod, LPCSTR lpTargetDll, LPCSTR lpApiName, PROC pNewFunc, PROC* ppOldFunc) {
    PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)hCallerMod;
    PIMAGE_NT_HEADERS pNt = (PIMAGE_NT_HEADERS)((BYTE*)hCallerMod + pDos->e_lfanew);
    
    // 導入表 RVA -> VA
    PIMAGE_IMPORT_DESCRIPTOR pImportDesc = (PIMAGE_IMPORT_DESCRIPTOR)
        ((BYTE*)hCallerMod + pNt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress);
    
    for (; pImportDesc->Name; pImportDesc++) {
        LPCSTR dllName = (LPCSTR)((BYTE*)hCallerMod + pImportDesc->Name);
        if (_stricmp(dllName, lpTargetDll) != 0) continue; // 找到目標 DLL
        
        // 遍歷 INT (OriginalFirstThunk) 與 IAT (FirstThunk)
        PIMAGE_THUNK_DATA pInt = (PIMAGE_THUNK_DATA)((BYTE*)hCallerMod + pImportDesc->OriginalFirstThunk);
        PIMAGE_THUNK_DATA pIat = (PIMAGE_THUNK_DATA)((BYTE*)hCallerMod + pImportDesc->FirstThunk);
        
        for (; pInt->u1.AddressOfData; pInt++, pIat++) {
            if (IMAGE_SNAP_BY_ORDINAL(pInt->u1.Ordinal)) continue; // 按序號導入,略過
            
            PIMAGE_IMPORT_BY_NAME pImportName = (PIMAGE_IMPORT_BY_NAME)((BYTE*)hCallerMod + pInt->u1.AddressOfData);
            if (strcmp((char*)pImportName->Name, lpApiName) == 0) {
                // 找到目標 API,修改 IAT 項
                DWORD oldProtect;
                VirtualProtect(&pIat->u1.Function, sizeof(ULONG_PTR), PAGE_READWRITE, &oldProtect);
                *ppOldFunc = (PROC)pIat->u1.Function; // 保存原地址
                pIat->u1.Function = (ULONG_PTR)pNewFunc; // 寫入新地址
                VirtualProtect(&pIat->u1.Function, sizeof(ULONG_PTR), oldProtect, &oldProtect);
                return TRUE;
            }
        }
    }
    return FALSE;
}

// 2. Hook 處理函數範例:攔截 TextOutW 記錄文字內容與位置
BOOL WINAPI MyTextOutW(HDC hdc, int x, int y, LPCWSTR lpString, int c) {
    // 1. 獲取字符串、字體、顏色、矩形區域等上下文
    // 2. 發送給主程式 (IPC: 共享記憶體/管道/WM_COPYDATA) 供取詞引擎使用
    // 3. 調用原始 API 保證正常繪製
    return g_pfnOldTextOutW(hdc, x, y, lpString, c);
}

// 3. DLL 入口初始化
BOOL APIENTRY DllMain(HMODULE hMod, DWORD reason, LPVOID) {
    if (reason == DLL_PROCESS_ATTACH) {
        DisableThreadLibraryCalls(hMod);
        // Hook 當前進程的 user32.dll!TextOutW / gdi32.dll!ExtTextOutW 等
        HookIAT(GetModuleHandle(NULL), "GDI32.dll", "TextOutW", (PROC)MyTextOutW, (PROC*)&g_pfnOldTextOutW);
        HookIAT(GetModuleHandle(NULL), "GDI32.dll", "ExtTextOutW", (PROC)MyExtTextOutW, (PROC*)&g_pfnOldExtTextOutW);
        // ... 同理 Hook DrawTextW, PolyTextOutW 等
    }
    return TRUE;
}

工程落地關鍵點總結

  • 注入方式:全局 Hook 用 SetWindowsHookEx(WH_GETMESSAGE/WH_CBT...) + DLL;指定進程用 CreateRemoteThread + LoadLibrary 或 AppInit_DLLs (需簽名) / Image File Execution Options (IFEO)。
  • 多進程數據傳遞:建議用 共享記憶體 (CreateFileMapping + MapViewOfFile) + 無鎖環形隊列 傳遞文字事件,主程式輪詢處理,避免 Hook 內阻塞導致 UI 卡頓。
  • 兼容性填坑:
  1. Chrome/Electron/現代瀏覽器啟用 GPU 加速 (Skia/DirectWrite) 繞過 GDI,**需補充 DirectWrite/D2D Hook 或採用 UI Automation / OCR 兜底**。
  2. 64 位進程需編譯 64 位 Hook DLL,指針大小、調用約定 (x64 calling convention) 全不同。
  3. 系統關鍵進程 (csrss, lsass, services.exe) 拒絕注入,需排除。
  4. 簽名驅動級 Hook (Minifilter/Keyboard Filter) 為終極方案,但門檻極高。

結語

從早期的「改寫內存代碼」到成熟的「IAT 重定向」,鼠標取詞技術見證了 Windows 應用生態從 GDI 到 DirectWrite、從單進程到多進程沙箱的演變。掌握 IAT Hook 不僅能實現取詞,更是理解 Windows 應用間攔截、熱補丁、安全防護 (EDR/HIPS) 核心原理的入門鑰匙。

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