在 Windows 早期發展歷程中(從 2.0 到 95/NT),國產漢化系統如長城、長青、DBWin、中文之星(CStar)與四通利方(RichWin)層出不窮。其中以 RichWin 影響力最大、技術最成熟,而 CStar 與其同源,核心技術高度相似。兩者對外宣稱的核心優勢在於獨特的「陷阱」技術——即動態修改 Windows 核心代碼以實現漢化支援。本文將從模組調用機制與重定位概念切入,深入剖析這一技術的實現原理,並提供示例源碼說明如何動態攔截並修改系統 API 行為。
使用 Visual C++ 附帶的 EXEHDR 工具檢視 NE 格式的核心漢化模組(如 WSENGINE.DLL 或 CHINESE.DLL),以 CStar 1.20 為例,會發現兩個顯著異常點:
ExtTextOut、CreateFont、GetCharWidth、GetMessage、IsDBCSLeadByte 等,幾乎覆蓋了所有文字輸出、字體管理、鍵盤輸入與系統訊息處理的關鍵節點。35 relocations,遠低於同等規模的正常 DLL。這意味著該模組幾乎不依賴系統載入器進行地址修正,而是採用了自行管理代碼地址的機制——這正是「陷阱」技術的核心跡象。在 16 位 Windows(及 9x 兼容層)中,可執行文件採用 NE 格式。模組間調用通過「輸入表」實現:調用者不直接包含被調用代碼的絕對地址,而是通過一個索引查找輸入表,由系統載入器在載入時填入目標函數的實際入口地址。這一機制為運行時動態修改調用目標提供了可能。
所謂「陷阱」,本質上是在內存中修改目標模組的輸入地址表(IAT)或直接修改代碼段的調用指令,將原本指向系統原生 API(如 GDI.ExtTextOut)的調用重定向到漢化模組自定義的處理函數。具體步驟如下:
GDI.EXE、USER.EXE 等核心模組的基地址。VirtualProtect 修改頁屬性)。系統核心模組代碼段通常屬性為只讀/執行。修改 IAT 需臨時修改頁屬性為可寫(PAGE_EXECUTE_READWRITE),修改後恢復。在 16 位環境下可通過 PrestoChangoSelector 或直接操作描述符表實現;32 位下使用 VirtualProtect。
Windows 9x 內部大量 32 位 API 通過 Thunk 層調用 16 位實現。漢化模組需同時攔截兩層入口,或直接攔截 16 位核心入口以覆蓋所有上層調用。這要求處理函數需支持混合調用約定(如 PASCAL / CDECL)與參數轉換。
正常 DLL 載入時需大量重定位修正內部絕對地址引用。漢化模組通過純相對寻址、避免全局變量、關鍵地址運行時動態計算等手法,將重定位需求降至最低,確保在任意基地址載入時無需系統修正即可正確運行,這對於注入至任意進程空間至關重要。
ExtTextOut 入口// 核心邏輯示意(簡化版,針對 16 位 NE 格式)
typedef struct _IMPORT_ENTRY {
WORD offset; // 模組內調用指令偏移
WORD module; // 目標模組索引
WORD proc; // 目標函數序號/名稱索引
} IMPORT_ENTRY;
BOOL InstallTrap(HMODULE hTargetMod, LPCSTR lpApiName, FARPROC pNewProc) {
// 1. 獲取目標模組 NE 頭部
NE_HEADER* pNe = (NE_HEADER*)hTargetMod;
IMPORT_ENTRY* pImportTable = (IMPORT_ENTRY*)((BYTE*)hTargetMod + pNe->ne_enttab);
int nEntries = pNe->ne_centtab / sizeof(IMPORT_ENTRY); // 簡化計算
// 2. 遍歷導入表查找目標 API
for (int i = 0; i < nEntries; i++) {
if (MatchImportName(pImportTable[i], lpApiName)) {
// 3. 修改頁屬性可寫
DWORD oldProtect;
VirtualProtect(&pImportTable[i].proc, sizeof(WORD), PAGE_READWRITE, &oldProtect);
// 4. 保存原地址(可存入全局表供鏈式調用)
g_OriginalProcs[lpApiName] = (FARPROC)pImportTable[i].proc;
// 5. 寫入新函數地址(需轉換為目標模組上下文的調用門地址)
pImportTable[i].proc = (WORD)GetThunkAddress(pNewProc);
// 6. 恢復頁屬性
VirtualProtect(&pImportTable[i].proc, sizeof(WORD), oldProtect, &oldProtect);
return TRUE;
}
}
return FALSE;
}
// 漢化處理函數示例
int FAR PASCAL HookedExtTextOut(HDC hdc, int x, int y, UINT fuOptions,
CONST RECT* lprc, LPCSTR lpString, int cbCount, CONST INT* lpDx) {
// 1. 雙字節字符串檢測與分割
// 2. 字體關聯與替換邏輯
// 3. 調用原始 ExtTextOut 繪製
return g_OriginalExtTextOut(hdc, x, y, fuOptions, lprc, lpString, cbCount, lpDx);
}
「陷阱」技術展示了早期 Windows 系統開放性極高、邊界模糊的特點:應用層代碼可直接重塑系統核心行為。雖然現代 Windows(NT 內核)通過內核模式簽名、PatchGuard、CFG 等機制徹底封堵了此類用戶態修改,但其核心思想——運行時二進制重寫、熱補丁、API Hooking——仍廣泛應用於調試器、性能分析器、安全防護軟件及兼容層(如 Wine)中。理解這一歷史技術,有助於掌握現代 Hook 框架(Detours, MinHook, Frida)的底層邏輯與演變脈絡。