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

滑鼠螢幕取字技術原理與實作:API Hooking 深度解析

     編輯:關於C++

前言:什麼是「滑鼠取字」?

「滑鼠螢幕取字」廣泛應用於電子字典(如四通利方、金山詞霸),使用者將滑鼠指向螢幕文字即可即時翻譯。看似簡單,在 Windows 系統下實作卻相當複雜,主要有兩種路徑:

  1. GDI API Hooking:攔截 TextOut、TextOutA 等繪圖 API 呼叫。
  2. Device Context (DC) 複製與追蹤:對每個 DC 建立副本並追蹤所有修改操作。功能更強但相容性較差。

本文專注於第一種——API Hooking,此技術威力遠超想像,甚至可改造整個作業系統行為,許多外掛式中文平台即基於此原理。

API Hooking 兩大流派

  1. 直接修改記憶體中的 API 映像:在目標函數開頭植入組合語言跳轉指令(JMP)導向自訂函數。實作繁瑣,且 Win9x 下 32 位 API 內部常轉呼 16 位同名 API,需插入 16 位組語,難度極高。
  2. 修改 IAT (Import Address Table,匯入地址表):重新導向 PE 檔案中的 IAT 項目,使模組呼叫目標 API 時轉向自訂函數。此法在 Win95/98/NT 均穩定,相容性佳,為本文採用方案。

核心背景知識

Windows 虛擬記憶體管理

  • Win9x:每進程 4GB 位址空間,2GB~4GB 為共享區(載入共享 DLL、VxD、記憶體管理器等),所有進程可見,這也是 Win9x 不夠健壯的原因。0~4MB 保留給 16 位系統,4MB~2GB 為進程私有空間。
  • NT 系統:保留 2GB~4GB 禁止進程存取,僅核心模式可用。

要攔截其他進程的 API 呼叫,必須打破進程邊界,將 Hook 程式碼注入目標進程。此工作交由 Hook 函數 SetWindowsHookEx 完成。系統 Hook 函數必須置於 DLL 中,當進程載入該 DLL 時,DLL 程式碼即成為進程一部分,以該進程身分執行,使用其堆疊。

PE 檔案格式與 IAT

EXE/DLL 映射到虛擬記憶體時,保持磁碟上 PE (Portable Executable) 格式結構。關鍵在 .idata 區段,包含所有匯入函數資訊與 IAT (Import Address Table) 的 RVA (Relative Virtual Address)。

進程對外部 DLL 函數的呼叫,實際上是跳轉到自身 .idata 區段的 IAT,由 IAT 再跳轉到目標 DLL 真實入口。因此,修改 IAT 中對應函數的地址為自訂函數位址,即可實現攔截。

尋找目標 IAT 項目步驟

  1. 取得目標模組基地址(GetModuleHandle 或 AfxGetInstanceHandle)。
  2. 解析 PE 標頭,定位 IMAGE_IMPORT_DESCRIPTOR 陣列,尋找目標 DLL(如 gdi32.dll)。
  3. 透過 IMAGE_THUNK_DATA 陣列(OriginalFirstThunk 指向原始匯入名稱表,FirstThunk 指向已填入實際位址的 IAT)遍歷匯入函數。
  4. 比對函數名稱(如 TextOutA),找到後修改 FirstThunk 對應項目的函數位址為自訂函數位址。
  5. 修改前需呼叫 VirtualProtect 變更記憶體保護屬性為可寫,修改完恢復原保護屬性。

「滑鼠取字」完整實作流程

  1. 安裝滑鼠 Hook:SetWindowsHookEx 取得滑鼠訊息。
  2. 取得滑鼠位置與目標視窗:WindowFromPoint、ScreenToClient、InvalidateRect 發送重繪訊息,誘使目標視窗呼叫繪圖 API。
  3. 攔截 TextOutA 取得字串參數:在 Hook DLL 中實作 MyTextOutA,處理攔截到的字串,再呼叫原版 TextOutA 繪製。

關鍵代碼片段

// 自訂 TextOutA
BOOL WINAPI MyTextOutA(HDC hdc, int x, int y, LPCSTR lpString, int c)
{
    // 這裡處理 lpString(取字邏輯)
    return RealTextOutA(hdc, x, y, lpString, c); // 呼叫原函數
}

// Hook 設定
HOOKFUNCDESC hd;
PROC pOrigFunc;
hd.szFunc = "TextOutA";
hd.pProc = (PROC)MyTextOutA;
HookImportFunction(AfxGetInstanceHandle(), "gdi32.dll", &hd, &pOrigFunc);

核心 HookImportFunction 實作(節錄)

BOOL HookImportFunction(HMODULE hModule, LPCSTR szImportModule,
                        LPHOOKFUNCDESC pHookFunc, PROC* pOrigFuncs)
{
    // 參數驗證、檢查模組基地址是否在共享區(Win9x)等略...

    PIMAGE_IMPORT_DESCRIPTOR pImportDesc = GetNamedImportDescriptor(hModule, szImportModule);
    if (!pImportDesc) return FALSE; // 目標 DLL 未被匯入

    // 取得原始 Thunk (匯入名稱表) 與實際 Thunk (IAT)
    PIMAGE_THUNK_DATA pOrigThunk = MAKE_PTR(PIMAGE_THUNK_DATA, hModule, pImportDesc->OriginalFirstThunk);
    PIMAGE_THUNK_DATA pRealThunk = MAKE_PTR(PIMAGE_THUNK_DATA, hModule, pImportDesc->FirstThunk);

    while (pOrigThunk->u1.Function) {
        // 僅處理按名稱匯入的函數
        if (!(pOrigThunk->u1.Ordinal & IMAGE_ORDINAL_FLAG)) {
            PIMAGE_IMPORT_BY_NAME pByName = MAKE_PTR(PIMAGE_IMPORT_BY_NAME, hModule, pOrigThunk->u1.AddressOfData);
            if (pByName->Name[0] == '\0') { pOrigThunk++; pRealThunk++; continue; }

            // 比對目標函數名
            if (stricmp(pHookFunc->szFunc, (char*)pByName->Name) == 0) {
                // 修改記憶體保護
                MEMORY_BASIC_INFORMATION mbi;
                VirtualQuery(pRealThunk, &mbi, sizeof(mbi));
                VirtualProtect(mbi.BaseAddress, mbi.RegionSize, PAGE_READWRITE, &mbi.Protect);

                // 保存原函數位址
                if (pOrigFuncs) *pOrigFuncs = (PROC)pRealThunk->u1.Function;

                // 替換為自訂函數位址
                pRealThunk->u1.Function = (DWORD)pHookFunc->pProc;

                // 恢復保護
                DWORD dwOld;
                VirtualProtect(mbi.BaseAddress, mbi.RegionSize, mbi.Protect, &dwOld);
                return TRUE;
            }
        }
        pOrigThunk++; pRealThunk++;
    }
    return FALSE;
}

輔助函數 GetNamedImportDescriptor 解析 PE 標頭定位目標 DLL 的 Import Descriptor,IsNT 判斷是否 NT 核心系統。

結語

API Hooking 是 Windows 系統程式設計的核心技術之一,掌握 IAT 修改法可實現強大的系統擴充功能。本文從虛擬記憶體、PE 格式、Hook 原理到完整實作代碼逐層剖析,希望能幫助開發者攻克這項充滿挑戰的技術。

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