"陷阱"技术探秘──动态汉化Windows技术的分析 A=0{}B#
2F+"v?n=\
四通利方(RichWin)、中文之星(CStar)是大家广为熟知的汉化Windows产品,"陷阱"技术即动态修改Windows代码,一直是其对外宣称的过人技术。本文从Windows的模块调用机制与重定位概念着手,介绍了"陷阱"技术的实现,并给出了采用"陷阱"技术动态修改Windows代码的示例源程序。 {P_i5V?
!?>QN'p.b
一、发现了什么?
wz)s
笔者多年来一直从事Windows下的软件开发工作,经历了Windows 2.0 、 3.0 、3.1 ,直至Windows 95、NT的成长过程,也遍历了长青窗口、长城窗口、DBWin、CStar、RichWin等多个Windows汉化产品。从现在看来,影响最大也最为成功的,当推四通利方的RichWin;此外,中文之星CStar与RichWin师出一门,其核心技术自然也差不多。其对外宣传采用独特的"陷阱" 技术即动态修改Windows代码,一直是笔者感兴趣的地方。 r .'xqzF/
EXEHDR是Microsoft Visual C++开发工具中很有用的一个程序,它可以检查NE(New-Exe cutable)格式文件,用它来分析RichWin的WSENGINE.DLL或CStar的CHINESE.DLL,就会发现与众不同的两点(以CStar 1.20为例): Lk!m1J5
m_;fj~m
C:\CSTAR>exehdr chinese.dll /v ttdY]+Fj
.................................. ?em8nZ'
kia[d984w
6 type offset target Qd~z<U l
BASE 060a seg 2 offset 0000 +;\w'dBi,
PTR 047e imp GDI.GETCHARABCWIDTHS MhR`
PTR 059b imp GDI.ENUMFONTFAMILIES ? )h8uf4
PTR 0451 imp DISPLAY.14 ( EXTTEXTOUT ) F3qCtx*N
PTR 0415 imp KEYBOARD.4 ( TOASCII ) |gWA'O0S
PTR 04ba imp KEYBOARD.5 ( ANSITOOEM ) N,Z*d
PTR 04c9 imp KEYBOARD.6 ( OEMTOANSI ) t
V(
WhP
PTR 04d8 imp KEYBOARD.134( ANSITOOEMBUFF ) }S8aR:'
PTR 05f5 imp USER.430 ( LSTRCMP ) ,p3]`MG
PTR 04e7 imp KEYBOARD.135( OEMTOANSIBUFF ) 4#}aLP
PTR 0514 imp USER.431 ( ANSIUPPER ) OPzudO
PTR 0523 imp USER.432 ( ANSILOWER ) ;<q2
PTR 05aa imp GDI.56 ( CREATEFONT ) dOKp:|9G
PTR 056e imp USER.433 ( ISCHARALPHA ) =
PTR 05b9 imp GDI.57 ( CREATEFONTINDIRECT ) <T?-A}0uO
PTR 057d imp USER.434 ( ISCHARALPHANUMERIC ) 9tU"+
PTR 049c imp USER.179 ( GETSYSTEMMETRICS ) P
JATRJ1.
PTR 0550 imp USER.435 ( ISCHARUPPER ) zBl L98
PTR 055f imp USER.436 ( ISCHARLOWER ) cE:s\hG
PTR 0532 imp USER.437 ( ANSIUPPERBUFF ) $m)gfI]9
PTR 0541 imp USER.438 ( ANSILOWERBUFF ) #Lpw8b6
PTR 05c8 imp GDI.69 ( DELETEOBJECT ) #2s}s<Sc;
PTR 058c imp GDI.70 ( ENUMFONTS ) D{s87h
PTR 04ab imp KERNEL.ISDBCSLEADBYTE ?$K-f:?c
PTR 05d7 imp GDI.82 ( GETOBJECT ) -yB}(69
PTR 048d imp KERNEL.74 ( OPENFILE ) |,@D<
PTR 0460 imp GDI.91 ( GETTEXTEXTENT ) f*<Vq:N=\
PTR 05e6 imp GDI.92 ( GETTEXTFACE ) &BLCP d
PTR 046f imp GDI.350 ( GETCHARWIDTH ) r:$tvT*
PTR 0442 imp GDI.351 ( EXTTEXTOUT ) EYn?YiVFU
PTR 0604 imp USER.471 ( LSTRCMPI ) .n1&Jsey
PTR 04f6 imp USER.472 ( ANSINEXT ) r&m49N,d
PTR 0505 imp USER.473 ( ANSIPREV ) *djLf.I@
PTR 0424 imp USER.108 ( GETMESSAGE ) l99Lxgx=
PTR 0433 imp USER.109 ( PEEKMESSAGE )
NdRcA
@]%eL
35 relocations gy%.+!4>v`
OA*O =
(括号内为笔者加上的对应Windows API函数。) &n;*'M
第一,在数据段中,发现了重定位信息。 cN0
*<
第二,这些重定位信息提示的函数,全都与文字显示输出和键盘、字符串有关。也就是说汉化Windows,必须修改这些函数。 >U,&V%y
在这非常特殊的地方,隐藏着什么呢?毋庸置疑,这与众不同的两点,对打开"陷阱"技术之门而言,不是金钥匙,也是敲门砖。 #<< el;n
I)V=$r{
二、Windows的模块调用机制与重定位概念 ".
tW5O>
为了深入探究"陷阱"技术,我们先来介绍Windows的模块调用机制。 7H)$NG<U$
Windows的运行分实模式、标准模式和增强模式三种,虽然这几种模式各不相同,但其核心模块的调用关系却是完全一致的。 !Z tqh Xr
主要的三个模块,有如下的关系: {o5|(^l
·KERNEL是Windows系统内核,它不依赖其它模块。 tO{{ci$-T
·GDI是Windows图形设备接口模块,它依赖于KERNEL模块。 I4G0!"T+
·USER是Windows用户接口服务模块,它依赖于KERNEL、GDI模块及设备驱动程序等所有模块。 TL2E|@k1]
这三个模块,实际上就是Windows的三个动态链接库。KERNEL有三种系统存在形式:Kern el.exe(实模式)、Krnl286.exe(标准模式)、Krnl386.exe(386增强模式);GDI模块是Gdi.ex e;USER模块是User.exe。虽然文件名都以EXE为扩展名,但它们实际都是动态链接库。同时,几乎所有的API函数都隐藏在这三个模块中。用EXEHDR对这三个模块分析,就可列出一大堆大家所熟悉的Windows API函数。 Rpg g
:
以GDI模块为例,运行结果如下: .*x:
C:\WINDOWS\SYSTEM>exehdr gdi.exe M i& ;1!bg
>2znn&gZ
Exports: >lV,K1Z
0WC\uxT7
rd seg offset name EW*sTI3
............ #zTy7ZS,0
351 1 923e EXTTEXTOUT exported, shared data w2.]
3QAZ
56 3 19e1 CREATEFONT exported, shared data >t3_]n1e
............ u:f ]|Q
).;{'8Q
至此,读者已能从Windows纷繁复杂的系统中理出一些头续来。下面,再引入一个重要概念——重定位。 x|a&wC2,{
一个Windows执行程序对调用API函数或对其它动态库的调用,在程序装入内存前,都是一些不能定位的动态链接;当程序调入内存时,这些远调用都需要重新定位,重新定位的依据就是重定位表。在Windows执行程序(包括动态库)的每个段后面,通常都跟有这样一个重定位表。重定位包含调用函数所在模块、函数序列号以及定位在模块中的位置。 OW@%H;b
例如,用EXEHDR /v 分析CHINESE.DLL得到: 4W"A*A
6 type offset target ksC_F8Q+
BQ0?B*yqd
.......... xT"V9t[f
@v%Kw e1Q
PTR 0442 imp GDI.351 bI &<L O
Mwnr4$]
.......... iSCkV2
=$J(]KPv!?
就表明,在本段的0442H偏移处,调用了GDI的第351号函数。如果在0442H处是0000:FFFF ,表示本段内仅此一处调用了GDI.351函数;否则,表明了本段内还有一处调用此函数,调用的位置就是0442H处所指向的内容,实际上重定位表只含有引用位置的链表的链头。那么,GDI. 351是一个什么函数呢?用EXEHDR对GDI.EXE作一分析,就可得出,在GDI的出口(Export)函数中,第351号是ExtTextOut。 LZn'+{\`
这样,我们在EXEHDR这一简单而非常有用的工具帮助下,已经在Windows的浩瀚海洋中畅游了一会,下面让我们继续深入下去。 #@B"E2F
F,Q;sq
三、动态汉化Windows原理 Qp:I[:Lr;
我们知道,传统的汉化Windows的方法,是要直接修改Windows的显示、输入、打印等模块代码,或用DDK直接开发"中文设备"驱动模块。这样不仅工作量大,而且,系统的完备性很难保证,性能上也有很多限制(早期的长青窗口就是如此),所以只有从内核上修改Windows核心代码才是最彻底的办法。 8TFQ%jv
从Windows的模块调用机制,我们可以看到,Windows实际上是由包括在KERNEL、GDI、US ER等几个模块中的众多函数支撑的。那么,修改其中涉及语言文字处理的函数,使之能适应中文需要,不就能达到汉化目的了吗? m`-);y
因而,我们可以得出这样的结论:在自己的模块中重新编写涉及文字显示、输入的多个函数,然后,将Windows中对这些函数的引用,改向到自己的这些模块中来。修改哪些函数才能完成汉化,这需要深入分析Windows的内部结构,但CHINESE.DLL已明确无误地告诉了我们,在其数据段的重定位表中列出的引用函数,正是CStar修改了的Windows函数!为了验证这一思路, 我们利用RichWin作一核实。
X%'z
用EXEHDR分析GDI.EXE,得出ExtTextOut函数在GDI的第一代码段6139H偏移处(不同版本的Windows其所在代码段和偏移可能不一样)。然后,用HelpWalk(也是Microsoft Visual C+ +开发工具中的一个)检查GDI的Code1段,6139H处前5个字节是 B8 FF 05 45 55,经过运行Ri chWin 4.3 for Internet后,再查看同样的地方,已改为 EA 08 08 8F 3D。其实反汇编就知道,这5个字节就是 Jmp 3D8F:0808,而句柄为0x3D8F的模块,用HelpWalk能观察正是RichWin 的WSENGINE.DLL的第一代码段( 模块名为TEXTMAN)。而偏移0808H处 B8 B7 3D 45 55 8B E C 1E,正是一个函数起始的地方,这实际上就是RichWin所重改写的ExtTextOut函数。退出Ri chWin后,再用HelpWalk观察GDI的Code1代码段,一切又恢复正常!这与前面的分析结论完全吻合!那么,下一个关键点就是如何动态修改Windows的函数代码,也就是汉化Windows的核心——"陷阱"技术。 #SHeK 4
?gMx
四、"陷阱"技术 RRt(%Wm*
讨论"陷阱"技术,还要回到前面的两个发现。发现之二,已能解释为修改的Windows函数,而发现之一却仍是一个迷。 X}k;(rb
数据段存放的是变量及常量等内容,如果这里面包含有重定位信息,那么,必定要在变量说明中将函数指针赋给一个FARPROC类型的变量,于是,在变量说明中写下: .J75bX5
FARPROC FarProcFunc=ExtTextOut; syR"p,3EC
果然,在自己程序的数据段中也有了重定位信息。这样,当程序调入内存时,变量FarPro cFunc已是函数ExtTextOut的地址了。 ;\*3A22 #
要直接修改代码段的内容,还遇到一个难题,就是代码段是不可改写的。这时,需要用到一个未公开的Windows函数AllocCStoDSAlias,取得与代码段有相同基址的可写数据段别名, 其函数声明为: 0$)Q@#
WORD FAR PASCAL AllocCStoDSAlias(WORD code_sel); 49; 'K
参数是代码段的句柄,返回值是可写数据段别名句柄。 -'$ob~*
Windows中函数地址是32位,高字节是其模块的内存句柄,低字节是函数在模块内的偏移。将得到的可写数据段别名句柄锁定,再将函数偏移处的5个字节保留下来,然后将其改为转向替代函数(用 EA Jmp): L~6%Fi&n4
*(lpStr+wOffset) =0xEA; 7.h{"xOx{
四通利方(RichWin)、中文之星(CStar)是大家广为熟知的汉化Windows产品,"陷阱"技术即动态修改Windows代码,一直是其对外宣称的过人技术。本文从Windows的模块调用机制与重定位概念着手,介绍了"陷阱"技术的实现,并给出了采用"陷阱"技术动态修改Windows代码的示例源程序。 "r&,#$6W6
//源程序 relocate.c g*)K/Z0pJ$
.@-9'<K?~
#include <WINDOWS.H> Cz4)Yz
#include <dos.h> &1l=X]%
BOOL WINAPI MyExtTextOut(HDC hDC, int x, int y, UINT nInt1, const RECTFAR*lpRect,LPCSTR lpStr, UINT nInt2, int FAR* lpInt); Qu=LnGo~P
WORD FAR PASCAL AllocCStoDSAlias(WORD code_sel); 7"7rmZ
typedef struct tagFUNC )%SkJ
{ !3iGz_y
FARPROC lpFarProcReplace; //替代函数地址 zWpqJK
FARPROC lpFarProcWindows; //Windows函数地址 jztq.2-c#
BYTE bOld; //保存原函数第一字节 {7cX#1
LONG lOld; //保存原函数接后的四字节长值 6Ao%>;e*
}FUNC; n .!Ym
X4
FUNC Func={MyExtTextOut,ExtTextOut}; _A<u#.yd
//Windows主函数 5qg2Zc~
int PASCAL WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR lpCmdLine,int nCmdShow) ;fw1
{ _|72r}j
HANDLE hMemCode; //代码段句柄 OA:%lC!
WORD hMemData; //相同基址的可写数据段别名 O8|5KpXd@
WORD wOffset; //函数偏移 hS[yNwD
LPSTR lpStr; J};z85B
LPLONG lpLong; *Nyev]8
char lpNotice[96]; r'CM
hMemCode=HIWORD((LONG) Func.lpFarProcWindows ); wU#F_De)R:
wOffset=LOWORD((LONG) Func.lpFarProcWindows ); V`adWXu
wsprintf(lpNotice,"函数所在模块句柄 0x%4xH,偏移 0x%4xH",hMemCode,wOffset); _3]][a,
MessageBox(NULL,lpNotice,"提示",MB_OK); H$af/^
//取与代码段有相同基址的可写数据段别名 79\JxiSB
hMemData=AllocCStoDSAlias(hMemCode); LPG`^SA
lpStr=GlobalLock(hMemData); UgWs{y2SE.
lpLong=(lpStr+wOffset+1 ); :{NC-%4o0
//保存原函数要替换的头几个字节 h'
!imQ
Func.bOld=*(lpStr+wOffset); izKfU?2]X@
Func.lOld=*lpLong; 7?B.0>$3>V
*(lpStr+wOffset)=0xEA; @&D?e:|!U
*lpLong=Func.lpFarProcReplace; {;2vmx9
GlobalUnlock(hMemData); 1!pa;$L
MessageBox(NULL,"改为自己的函数","提示",MB_OK); }HE6aF62O
//将保留的内容改回来
^ kST
hMemData=AllocCStoDSAlias(hMemCode); {0! ~C=P
lpStr=GlobalLock(hMemData); Zo KcJA
lpLong=(lpStr+wOffset+1 ); H+
h07\?
%
*(lpStr+wOffset)=Func.bOld; 1 T130L
*lpLong=Func.lOld; pZ#ap<|>I
GlobalUnlock(hMemData); /ywD{*
MessageBox(NULL,"改回原Windows函数","提示",MB_OK); I\qYkWg7
return 1; y~IuP c
} n.hv!W0
H3{GmV8
//自己的替代函数 v^ d]rSm
9w9jpe#
BOOL WINAPI MyExtTextOut(HDC hDC, int x, int y, UINT nInt1, const RECT FAR* qS&%!
lpRect, LPCSTR lpStr, UINT nInt2, int FAR* lpInt) TpA\9N#$
{ ,@m@S^
BYTE NameDot[96]= l"n{.aL
{ S4witIK5
0x09, 0x00, 0xfd, 0x08, 0x09, 0x08, 0x09, 0x10, 0x09, 0x20, $,xnU.n
0x79, 0x40, 0x41, 0x04, 0x47, 0xfe, 0x41, 0x40, 0x79, 0x40, |^28\sm2e
0x09, 0x20, 0x09, 0x20, 0x09, 0x10, 0x09, 0x4e, 0x51, 0x84, 50dGBF
0x21, 0x00, 0x02, 0x00, 0x01, 0x04, 0xff, 0xfe, 0x00, 0x00, Av0y?oGH
0x1f, 0xf0, 0x10, 0x10, 0x10, 0x10, 0x1f, 0xf0, 0x00, 0x00, 0J.dG/I%
0x7f, 0xfc, 0x40, 0x04, 0x4f, 0xe4, 0x48, 0x24, 0x48, 0x24, :b[`
v
0x4f, 0xe4, 0x40, 0x0c, 0x10, 0x80, 0x10, 0xfc, 0x10, 0x88, )} DUMq7
0x11, 0x50, 0x56, 0x20, 0x54, 0xd8, 0x57, 0x06, 0x54, 0x20, e T'nl,e|
0x55, 0xfc, 0x54, 0x20, 0x55, 0xfc, 0x5c, 0x20, 0x67, 0xfe, #k3t3az2{
0x00, 0x20, 0x00, 0x20, 0x00, 0x20 bslrqUk_`=
}; @H !$[m3
E{HY!L[
HBITMAP hBitmap,hOldBitmap; &h*S
y
HDC hMemDC; ?=GXqbS"
BYTE far *lpDot; yGg,$WM
int i; "l={)=R
for ( i=0;i<3;i++ ) .kTG[)F0b
{ 7^}Ll@
lpDot=(LPSTR)NameDot+i*32; _ >`X]I;
hMemDC=CreateCompatibleDC(hDC); q.Nweu!jQ
hBitmap=CreateBitmap(16,16,1,1,lpDot); 61U<5:#l
SetBitmapBits(hBitmap,32L,lpDot); R~bC,`Bh
hOldBitmap=SelectObject(hMemDC,hBitmap); HaA1z}?n
BitBlt(hDC,x+i*16,y,16,16,hMemDC,0,0,SRCCOPY); uH3D{4
DeleteDC(hMemDC); P#v*TD'
DeleteObject(hBitmap); hP J4Oj1O
} .s/fhk,
return TRUE; RkFD*E$
} d@mo!zu
8\<jyJ
//模块定义文件 relocate.def dBO@6*N4c
KlK`;cr?
NAME RELOCATE Gf8s?l
EXETYPE WINDOWS c41: !u^
CODE PRELOAD MOVEABLE DISCARDABLE T5wjU*=IL
DATA PRELOAD MOVEABLE MULTIPLE s?WCnT
HEAPSIZE 1024 (Lh#`L?x
EXPORTS vUC!fIG
5CAR{|a
五、结束语 XwM611
本文从原理上分析了称为"陷阱"技术的动态汉化Windows方法,介绍了将任一Windows函数调用改向到自己指定函数处的通用方法,这种方法可以拓展到其它应用中,如多语种显示、不同内码制式的切换显示等。