螢幕文字繪製最終皆經由 GDI 函數:
TextOutA/WExtTextOutA/W實現取字關鍵在於 攔截這些函數呼叫,取得傳入的字串參數。
// 安裝滑鼠 Hook 取得全域座標
HHOOK hMouseHook = SetWindowsHookEx(WH_MOUSE, MouseHookProc, hDll, 0);
// Hook 回呼中
POINT pt;
GetCursorPos(&pt);
HWND hTarget = WindowFromPoint(pt); // 取得座標下視窗
ScreenToClient(hTarget, &pt); // 轉為用戶區座標
InvalidateRect(hTarget, NULL, TRUE); // 標記整個用戶區無效
UpdateWindow(hTarget); // 立即發送 WM_PAINT,觸發重繪
// 原始函數指標
FARPROC pfnTextOutA = GetProcAddress(GetModuleHandle("gdi32.dll"), "TextOutA");
// 自訂函數 (簽名必須完全一致)
BOOL WINAPI MyTextOutA(HDC hdc, int x, int y, LPCSTR lpString, int c) {
// 1. 處理攔截到的字串 (如儲存、顯示、翻譯等)
if (lpString && c > 0) {
// 此處處理捕獲的文字...
}
// 2. 呼叫原始函數完成實際繪製
return ((BOOL(WINAPI*)(HDC,int,int,LPCSTR,int))pfnTextOutA)(hdc, x, y, lpString, c);
}
void HookTargetProcessModules() {
MEMORY_BASIC_INFORMATION mbi;
LPVOID base = 0;
// 遍歷虛擬記憶體區域
while (VirtualQuery(base, &mbi, sizeof(mbi))) {
// 僅處理映射映像 (EXE/DLL)
if (mbi.Type == MEM_IMAGE) {
HookModuleIAT((HMODULE)mbi.BaseAddress);
}
base = (LPVOID)((DWORD_PTR)mbi.BaseAddress + mbi.RegionSize);
}
}
BOOL HookModuleIAT(HMODULE hMod) {
PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)hMod;
if (pDos->e_magic != IMAGE_DOS_SIGNATURE) return FALSE;
PIMAGE_NT_HEADERS pNT = (PIMAGE_NT_HEADERS)((DWORD_PTR)pDos + pDos->e_lfanew);
if (pNT->Signature != IMAGE_NT_SIGNATURE) return FALSE;
// 匯入表
DWORD importRVA = pNT->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress;
if (!importRVA) return FALSE;
PIMAGE_IMPORT_DESCRIPTOR pImport = (PIMAGE_IMPORT_DESCRIPTOR)((DWORD_PTR)hMod + importRVA);
while (pImport->Name) {
CHAR* szDllName = (CHAR*)((DWORD_PTR)hMod + pImport->Name);
// 僅處理 gdi32.dll 匯入
if (lstrcmpiA(szDllName, "gdi32.dll") == 0) {
PIMAGE_THUNK_DATA pThunk = (PIMAGE_THUNK_DATA)((DWORD_PTR)hMod + pImport->FirstThunk);
while (pThunk->u1.Function) {
if (pThunk->u1.Function == (DWORD_PTR)pfnTextOutA) {
// 修改保護並替換
DWORD oldProtect;
VirtualProtect(&pThunk->u1.Function, sizeof(DWORD_PTR),
PAGE_READWRITE, &oldProtect);
pThunk->u1.Function = (DWORD_PTR)MyTextOutA;
VirtualProtect(&pThunk->u1.Function, sizeof(DWORD_PTR),
oldProtect, &oldProtect);
return TRUE;
}
pThunk++;
}
}
pImport++;
}
return FALSE;
}
| 技術點 | 說明 |
|---|---|
| 為何用滑鼠 Hook | 系統自動將 Hook DLL 注入所有 GUI 進程,免去手動注入 |
| VirtualQuery 遍歷 | 找出進程載入的所有模組基地址,無需 Toolhelp32 API |
| IAT 修改範圍 | 僅影響該模組對 gdi32.dll 的匯入,不影響其他模組或系統全域 |
| Unicode 支援 | 需同時 Hook TextOutW, ExtTextOutA, ExtTextOutW |
| 效能考量 | 每次文字繪製皆經攔截函數,複雜邏輯會拖慢繪製,建議極簡處理 |
| 現代替代方案 | UI Automation, OCR, DirectWrite 分析等更穩健 |
// 必須同時攔截以下四個函數以覆蓋 ANSI/Unicode
TextOutA, TextOutW, ExtTextOutA, ExtTextOutW