四通利方 RichWin、中文之星 CStar 等經典中文化產品,其核心技術被稱為「陷阱」技術——即動態修改 Windows 核心程式碼以實現中文顯示與輸入。本文從 Windows 模組呼叫機制與重定位概念切入,深度剖析此技術原理,並提供示例原始碼。
使用 Visual C++ 工具 EXEHDR 分析 CStar 的 CHINESE.DLL,發現兩大異常:
ExtTextOut)、字型建立(CreateFont)、鍵盤轉換(ToAscii、AnsiToOem)、字串比較(lstrcmp、CharUpper)等文字處理 API。這揭示了中文化必須攔截修改的關鍵函數集合。
Kernel.exe、Krnl286.exe、Krnl386.exe)。Gdi.exe)。User.exe)。這三者實為動態連結庫,幾乎所有 API 函數皆藏身其中。
程式載入記憶體前,對 API 的呼叫均為動態連結,無法定址。載入時依據重定位表修正位址。重定位項記錄:目標模組、函數序號、呼叫位置偏移。EXEHDR /v 輸出範例:
PTR 0442 imp GDI.351 ; 本區段 0442h 處呼叫 GDI 第 351 號函數
查詢 GDI 匯出表可知第 351 號為 ExtTextOut。
MyExtTextOut)。分析 RichWin 運行前後 GDI.EXE 程式碼區段:ExtTextOut 入口處原為 B8 FF 05 45 55,運行 RichWin 後變為 EA 08 08 8F 3D(反組譯為 JMP 3D8F:0808),目標正是 RichWin WSENGINE.DLL 內的重寫函數。退出 RichWin 後自動恢復原碼。
在資料區段定義函數指標,編譯器自動產生重定位項:
FARPROC FarProcFunc = ExtTextOut; // 載入後 FarProcFunc 即為真實位址
使用未公開 API AllocCStoDSAlias 取得與程式碼區段基址相同的可寫資料區段別名:
WORD FAR PASCAL AllocCStoDSAlias(WORD code_sel);
*(lpStr + wOffset) = 0xEA; // JMP FAR 指令碼
*((LPLONG)(lpStr + wOffset + 1)) = (LONG)MyExtTextOut; // 目標位址
卸載時將保存的原始 5 位元組寫回。
// relocate.c - 動態修改 ExtTextOut 示例
#include <windows.h>
#include <dos.h>
BOOL WINAPI MyExtTextOut(HDC hDC, int x, int y, UINT nInt1, const RECT FAR* lpRect,
LPCSTR lpStr, UINT nInt2, int FAR* lpInt);
WORD FAR PASCAL AllocCStoDSAlias(WORD code_sel);
typedef struct {
FARPROC lpFarProcReplace; // 替代函數位址
FARPROC lpFarProcWindows; // Windows 原函數位址
BYTE bOld; // 原第 1 位元組
LONG lOld; // 原後 4 位元組
} FUNC;
FUNC Func = {MyExtTextOut, ExtTextOut};
int PASCAL WinMain(HINSTANCE hInst, HINSTANCE hPrev, LPSTR lpCmd, int nShow) {
HANDLE hMemCode = HIWORD((LONG)Func.lpFarProcWindows);
WORD wOffset = LOWORD((LONG)Func.lpFarProcWindows);
WORD hMemData = AllocCStoDSAlias(hMemCode);
LPSTR lpStr = (LPSTR)GlobalLock(hMemData);
LPLONG lpLong = (LPLONG)(lpStr + wOffset + 1);
// 保存原始碼
Func.bOld = *(lpStr + wOffset);
Func.lOld = *lpLong;
// 修改為 JMP MyExtTextOut
*(lpStr + wOffset) = 0xEA;
*lpLong = (LONG)Func.lpFarProcReplace;
GlobalUnlock(hMemData);
MessageBox(NULL, "已改為自訂函數", "提示", MB_OK);
// 恢復原始碼
hMemData = AllocCStoDSAlias(hMemCode);
lpStr = (LPSTR)GlobalLock(hMemData);
lpLong = (LPLONG)(lpStr + wOffset + 1);
*(lpStr + wOffset) = Func.bOld;
*lpLong = Func.lOld;
GlobalUnlock(hMemData);
MessageBox(NULL, "已恢復 Windows 原函數", "提示", MB_OK);
return 1;
}
// 自訂 ExtTextOut:在螢幕繪製點陣字元示意
BOOL WINAPI MyExtTextOut(HDC hDC, int x, int y, UINT nInt1, const RECT FAR* lpRect,
LPCSTR lpStr, UINT nInt2, int FAR* lpInt) {
// ... 繪製簡單點陣圖形略 ...
return TRUE;
}
NAME RELOCATE
EXETYPE WINDOWS
CODE PRELOAD MOVEABLE DISCARDABLE
DATA PRELOAD MOVEABLE MULTIPLE
HEAPSIZE 1024
EXPORTS
「陷阱」技術實現了任意 Windows 函數呼叫的動態重導向,不僅是中文化的基石,亦可延伸至:多語系動態切換、不同內碼制式(Big5/GB/Unicode)即時轉換顯示、API 監控與攔截工具開發等領域。雖然現代 Windows (NT 核心) 已透過 Unicode 原生支援多語言,但此技術在系統級 Hook、熱修補、安全防護等領域仍具參考價值。