"陷阱"技术探秘──动态汉化Windows技术的分析 qzbpLV|
a"6AZT"8
四通利方(RichWin)、中文之星(CStar)是大家广为熟知的汉化Windows产品,"陷阱"技术即动态修改Windows代码,一直是其对外宣称的过人技术。本文从Windows的模块调用机制与重定位概念着手,介绍了"陷阱"技术的实现,并给出了采用"陷阱"技术动态修改Windows代码的示例源程序。 YrRD3P.P
XoZPz
一、发现了什么? 40ZHDtIu<
笔者多年来一直从事Windows下的软件开发工作,经历了Windows 2.0 、 3.0 、3.1 ,直至Windows 95、NT的成长过程,也遍历了长青窗口、长城窗口、DBWin、CStar、RichWin等多个Windows汉化产品。从现在看来,影响最大也最为成功的,当推四通利方的RichWin;此外,中文之星CStar与RichWin师出一门,其核心技术自然也差不多。其对外宣传采用独特的"陷阱" 技术即动态修改Windows代码,一直是笔者感兴趣的地方。 P_{jZ}y(
EXEHDR是Microsoft Visual C++开发工具中很有用的一个程序,它可以检查NE(New-Exe cutable)格式文件,用它来分析RichWin的WSENGINE.DLL或CStar的CHINESE.DLL,就会发现与众不同的两点(以CStar 1.20为例): M`jqUg
F!yr};@^p
C:\CSTAR>exehdr chinese.dll /v pA|Z%aL
.................................. 4x;vn8yh
9f/RD?(1O
6 type offset target gqCDF H
BASE 060a seg 2 offset 0000 %DqPRl.Gu
PTR 047e imp GDI.GETCHARABCWIDTHS n>|7 k3
PTR 059b imp GDI.ENUMFONTFAMILIES R qnT*
PTR 0451 imp DISPLAY.14 ( EXTTEXTOUT ) Vy6A]U\%
PTR 0415 imp KEYBOARD.4 ( TOASCII ) wE*jN~
PTR 04ba imp KEYBOARD.5 ( ANSITOOEM ) ^P|
K2at
PTR 04c9 imp KEYBOARD.6 ( OEMTOANSI ) H)u<$y!8
PTR 04d8 imp KEYBOARD.134( ANSITOOEMBUFF ) Uw:gJ9
PTR 05f5 imp USER.430 ( LSTRCMP ) XC NM
PTR 04e7 imp KEYBOARD.135( OEMTOANSIBUFF ) nS`DI92I
PTR 0514 imp USER.431 ( ANSIUPPER ) |5(<
Vk=
PTR 0523 imp USER.432 ( ANSILOWER ) Ivdg1X
PTR 05aa imp GDI.56 ( CREATEFONT ) ?oKY"C8/
PTR 056e imp USER.433 ( ISCHARALPHA ) V< ]l=JOd
PTR 05b9 imp GDI.57 ( CREATEFONTINDIRECT ) fsr0E=nV
PTR 057d imp USER.434 ( ISCHARALPHANUMERIC ) Pn;Tg7oz
PTR 049c imp USER.179 ( GETSYSTEMMETRICS ) U:[#n5g
PTR 0550 imp USER.435 ( ISCHARUPPER ) vxmz3ht,Q
PTR 055f imp USER.436 ( ISCHARLOWER ) }RT#V8oc
PTR 0532 imp USER.437 ( ANSIUPPERBUFF ) K.{:H4_
PTR 0541 imp USER.438 ( ANSILOWERBUFF ) {Al}a`da
PTR 05c8 imp GDI.69 ( DELETEOBJECT ) 5*#!w1X
PTR 058c imp GDI.70 ( ENUMFONTS ) "N|gU;~W
PTR 04ab imp KERNEL.ISDBCSLEADBYTE b}eBy
PTR 05d7 imp GDI.82 ( GETOBJECT ) 5m e|dvk
PTR 048d imp KERNEL.74 ( OPENFILE ) H%AF,
PTR 0460 imp GDI.91 ( GETTEXTEXTENT ) :Jz@` s1n
PTR 05e6 imp GDI.92 ( GETTEXTFACE ) Ig*qn# Dd
PTR 046f imp GDI.350 ( GETCHARWIDTH ) H$j`75#u?-
PTR 0442 imp GDI.351 ( EXTTEXTOUT ) }/{G
PTR 0604 imp USER.471 ( LSTRCMPI ) P%- @AmO^_
PTR 04f6 imp USER.472 ( ANSINEXT ) .^Z^L F
PTR 0505 imp USER.473 ( ANSIPREV ) %#^)hX,+Q
PTR 0424 imp USER.108 ( GETMESSAGE ) 0i4X,oHjG
PTR 0433 imp USER.109 ( PEEKMESSAGE ) b<N962 q$q
%^r}$mfy:0
35 relocations ?[|T"bE5[
aeP
6JHj
(括号内为笔者加上的对应Windows API函数。) h)vRvfcmY
第一,在数据段中,发现了重定位信息。 H /kSFf{
第二,这些重定位信息提示的函数,全都与文字显示输出和键盘、字符串有关。也就是说汉化Windows,必须修改这些函数。 |6mDooTy
在这非常特殊的地方,隐藏着什么呢?毋庸置疑,这与众不同的两点,对打开"陷阱"技术之门而言,不是金钥匙,也是敲门砖。 [^U#ic>cT
3*DwXH +
二、Windows的模块调用机制与重定位概念 QnWM<6xK"
为了深入探究"陷阱"技术,我们先来介绍Windows的模块调用机制。 b)5z'zQu
Windows的运行分实模式、标准模式和增强模式三种,虽然这几种模式各不相同,但其核心模块的调用关系却是完全一致的。 JMnk~8O
主要的三个模块,有如下的关系: iyRB}[y
·KERNEL是Windows系统内核,它不依赖其它模块。 @y#QHJ.j
·GDI是Windows图形设备接口模块,它依赖于KERNEL模块。 c2,1d`
·USER是Windows用户接口服务模块,它依赖于KERNEL、GDI模块及设备驱动程序等所有模块。 d_0r
这三个模块,实际上就是Windows的三个动态链接库。KERNEL有三种系统存在形式:Kern el.exe(实模式)、Krnl286.exe(标准模式)、Krnl386.exe(386增强模式);GDI模块是Gdi.ex e;USER模块是User.exe。虽然文件名都以EXE为扩展名,但它们实际都是动态链接库。同时,几乎所有的API函数都隐藏在这三个模块中。用EXEHDR对这三个模块分析,就可列出一大堆大家所熟悉的Windows API函数。 axRzn:f
以GDI模块为例,运行结果如下: |]<eJ|\=
C:\WINDOWS\SYSTEM>exehdr gdi.exe 8, >YB+Hb
]lKQwpX3
Exports: 7d7"^M
e2SU)Tr%b
rd seg offset name o9l =Q
............ q66+x)
351 1 923e EXTTEXTOUT exported, shared data OYWW<N+R2
56 3 19e1 CREATEFONT exported, shared data '}`|QJ
............ !_EaF`oh(
Y[A`r0
至此,读者已能从Windows纷繁复杂的系统中理出一些头续来。下面,再引入一个重要概念——重定位。 HlkG^:)
一个Windows执行程序对调用API函数或对其它动态库的调用,在程序装入内存前,都是一些不能定位的动态链接;当程序调入内存时,这些远调用都需要重新定位,重新定位的依据就是重定位表。在Windows执行程序(包括动态库)的每个段后面,通常都跟有这样一个重定位表。重定位包含调用函数所在模块、函数序列号以及定位在模块中的位置。 31w9$H N
例如,用EXEHDR /v 分析CHINESE.DLL得到: J|orvnkK
6 type offset target UeG$lMV
3lyk/',
.......... M>mk=-l
P(_wT:8C?
PTR 0442 imp GDI.351 zS@"ITy
*3yeMxa
.......... `Z3Qx~fx
I[~EQ{Iz
就表明,在本段的0442H偏移处,调用了GDI的第351号函数。如果在0442H处是0000:FFFF ,表示本段内仅此一处调用了GDI.351函数;否则,表明了本段内还有一处调用此函数,调用的位置就是0442H处所指向的内容,实际上重定位表只含有引用位置的链表的链头。那么,GDI. 351是一个什么函数呢?用EXEHDR对GDI.EXE作一分析,就可得出,在GDI的出口(Export)函数中,第351号是ExtTextOut。 AnbY<&OC1
这样,我们在EXEHDR这一简单而非常有用的工具帮助下,已经在Windows的浩瀚海洋中畅游了一会,下面让我们继续深入下去。 He)<S?X-6
Ek [V A\G
三、动态汉化Windows原理 <&7KcvBn"4
我们知道,传统的汉化Windows的方法,是要直接修改Windows的显示、输入、打印等模块代码,或用DDK直接开发"中文设备"驱动模块。这样不仅工作量大,而且,系统的完备性很难保证,性能上也有很多限制(早期的长青窗口就是如此),所以只有从内核上修改Windows核心代码才是最彻底的办法。 ;CU<\
从Windows的模块调用机制,我们可以看到,Windows实际上是由包括在KERNEL、GDI、US ER等几个模块中的众多函数支撑的。那么,修改其中涉及语言文字处理的函数,使之能适应中文需要,不就能达到汉化目的了吗? @quNVx(y
因而,我们可以得出这样的结论:在自己的模块中重新编写涉及文字显示、输入的多个函数,然后,将Windows中对这些函数的引用,改向到自己的这些模块中来。修改哪些函数才能完成汉化,这需要深入分析Windows的内部结构,但CHINESE.DLL已明确无误地告诉了我们,在其数据段的重定位表中列出的引用函数,正是CStar修改了的Windows函数!为了验证这一思路, 我们利用RichWin作一核实。 \haJe~
用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的核心——"陷阱"技术。 nt,tM/
hcw)qB,s
四、"陷阱"技术 ~RQ6DG^
讨论"陷阱"技术,还要回到前面的两个发现。发现之二,已能解释为修改的Windows函数,而发现之一却仍是一个迷。 c2}?[\U]
数据段存放的是变量及常量等内容,如果这里面包含有重定位信息,那么,必定要在变量说明中将函数指针赋给一个FARPROC类型的变量,于是,在变量说明中写下: &^ sgR$m
FARPROC FarProcFunc=ExtTextOut; `?Pk~7
果然,在自己程序的数据段中也有了重定位信息。这样,当程序调入内存时,变量FarPro cFunc已是函数ExtTextOut的地址了。 Lm kv.XF
要直接修改代码段的内容,还遇到一个难题,就是代码段是不可改写的。这时,需要用到一个未公开的Windows函数AllocCStoDSAlias,取得与代码段有相同基址的可写数据段别名, 其函数声明为: Ni$WI{e9
WORD FAR PASCAL AllocCStoDSAlias(WORD code_sel); m6aq_u{W
参数是代码段的句柄,返回值是可写数据段别名句柄。 1cE3uA7
Windows中函数地址是32位,高字节是其模块的内存句柄,低字节是函数在模块内的偏移。将得到的可写数据段别名句柄锁定,再将函数偏移处的5个字节保留下来,然后将其改为转向替代函数(用 EA Jmp): |kvom 4 T
*(lpStr+wOffset) =0xEA; Y[AL!h
四通利方(RichWin)、中文之星(CStar)是大家广为熟知的汉化Windows产品,"陷阱"技术即动态修改Windows代码,一直是其对外宣称的过人技术。本文从Windows的模块调用机制与重定位概念着手,介绍了"陷阱"技术的实现,并给出了采用"陷阱"技术动态修改Windows代码的示例源程序。 m6BIQ(l
//源程序 relocate.c ~q]+\qty4
]Dm'J%P0}
#include <WINDOWS.H> r\y~
:
#include <dos.h> vUGEzC M
BOOL WINAPI MyExtTextOut(HDC hDC, int x, int y, UINT nInt1, const RECTFAR*lpRect,LPCSTR lpStr, UINT nInt2, int FAR* lpInt);
* P_
3A:_
WORD FAR PASCAL AllocCStoDSAlias(WORD code_sel); $_-f}E
typedef struct tagFUNC kji*7a?y
{ AL/q6PWi
FARPROC lpFarProcReplace; //替代函数地址 .6%-Il
FARPROC lpFarProcWindows; //Windows函数地址 @[0zZX2EE
BYTE bOld; //保存原函数第一字节 Zx(VwB2
LONG lOld; //保存原函数接后的四字节长值 g{@q
}FUNC; uBLI!N-G
FUNC Func={MyExtTextOut,ExtTextOut}; 4P@Ak7iL(V
//Windows主函数 -LL49P6
int PASCAL WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR lpCmdLine,int nCmdShow) ,K Ebnk|i
{ ]KfjZ!Qh
HANDLE hMemCode; //代码段句柄
'AN3{
WORD hMemData; //相同基址的可写数据段别名 ^)&d7cSc
WORD wOffset; //函数偏移 `7qZ6Z3z@
LPSTR lpStr; n?*Fr sZ
LPLONG lpLong; WJ$D]7
char lpNotice[96]; YC#N],#
hMemCode=HIWORD((LONG) Func.lpFarProcWindows ); nwh7DUi
wOffset=LOWORD((LONG) Func.lpFarProcWindows ); Hu|;cbK
wsprintf(lpNotice,"函数所在模块句柄 0x%4xH,偏移 0x%4xH",hMemCode,wOffset); $:V'+s4o
MessageBox(NULL,lpNotice,"提示",MB_OK); Bk&ry)`gD
//取与代码段有相同基址的可写数据段别名 q/,>UtRr
hMemData=AllocCStoDSAlias(hMemCode); Jrd:6Z
lpStr=GlobalLock(hMemData); [psW+3{bG
lpLong=(lpStr+wOffset+1 ); H:
Rd4dl,
//保存原函数要替换的头几个字节 )Xxu-/-
Func.bOld=*(lpStr+wOffset); P.WEu<$
Func.lOld=*lpLong; lz.ta!6
*(lpStr+wOffset)=0xEA; tgy*!B6a~
*lpLong=Func.lpFarProcReplace; X>^St&B}fC
GlobalUnlock(hMemData); ( /{Wu:e
MessageBox(NULL,"改为自己的函数","提示",MB_OK); E7-il;`cKn
//将保留的内容改回来 A{mv[x-XN
hMemData=AllocCStoDSAlias(hMemCode); v'(p."g
lpStr=GlobalLock(hMemData); 6m_
fEkS[
lpLong=(lpStr+wOffset+1 ); wP.b2X_V
*(lpStr+wOffset)=Func.bOld; 2Z
4Ekq0@
*lpLong=Func.lOld; BwwOaO@L
GlobalUnlock(hMemData); ~;nh|v/e
MessageBox(NULL,"改回原Windows函数","提示",MB_OK); /?<o?IR~6
return 1; $8gj}0}eH
} 4 {JoeIRyz
Q[EpE,
//自己的替代函数 >ENZ['F
5s3!{zT{
BOOL WINAPI MyExtTextOut(HDC hDC, int x, int y, UINT nInt1, const RECT FAR* - Te+{
lpRect, LPCSTR lpStr, UINT nInt2, int FAR* lpInt) @&(0]kZ6
{
bK:mt `
BYTE NameDot[96]= Y=+pz^/"
{ Z _W.iBF
0x09, 0x00, 0xfd, 0x08, 0x09, 0x08, 0x09, 0x10, 0x09, 0x20, U^iNOMs?
0x79, 0x40, 0x41, 0x04, 0x47, 0xfe, 0x41, 0x40, 0x79, 0x40, 7 lc -
0x09, 0x20, 0x09, 0x20, 0x09, 0x10, 0x09, 0x4e, 0x51, 0x84, JgQ,,p_V?
0x21, 0x00, 0x02, 0x00, 0x01, 0x04, 0xff, 0xfe, 0x00, 0x00, qyzmjV6J2
0x1f, 0xf0, 0x10, 0x10, 0x10, 0x10, 0x1f, 0xf0, 0x00, 0x00, Fd!Np7xw
0x7f, 0xfc, 0x40, 0x04, 0x4f, 0xe4, 0x48, 0x24, 0x48, 0x24, o?zA'5q
0x4f, 0xe4, 0x40, 0x0c, 0x10, 0x80, 0x10, 0xfc, 0x10, 0x88, 8D3|}z?
0x11, 0x50, 0x56, 0x20, 0x54, 0xd8, 0x57, 0x06, 0x54, 0x20, .}
al s
0x55, 0xfc, 0x54, 0x20, 0x55, 0xfc, 0x5c, 0x20, 0x67, 0xfe, ~>vv9-_
0x00, 0x20, 0x00, 0x20, 0x00, 0x20 w1tWyKq
}; v4c*6(m
F
uYjrzmx
HBITMAP hBitmap,hOldBitmap; KQGdV{VFs
HDC hMemDC; aQzDOeTi
BYTE far *lpDot; jpijnz{M
int i; 3%gn:.9N
for ( i=0;i<3;i++ ) aX
CVC<l
{ >@?!-Fy5
lpDot=(LPSTR)NameDot+i*32; F/33#
U
hMemDC=CreateCompatibleDC(hDC); WbF[4x
hBitmap=CreateBitmap(16,16,1,1,lpDot); BMaw]D
SetBitmapBits(hBitmap,32L,lpDot); IZ?+c@t
hOldBitmap=SelectObject(hMemDC,hBitmap); _{$eOwB
BitBlt(hDC,x+i*16,y,16,16,hMemDC,0,0,SRCCOPY); 44@yQ?
DeleteDC(hMemDC); YU>NGC]}d
DeleteObject(hBitmap); q|N,?f9
} p1}umDb%
return TRUE; FFC"rG
} );*:UzsC_
4WspPHj
//模块定义文件 relocate.def 2h=QJgpCG
_#;UXAi
NAME RELOCATE Qdt4h$~V"
EXETYPE WINDOWS z[vHMJ
0
CODE PRELOAD MOVEABLE DISCARDABLE o/dj1a~U
DATA PRELOAD MOVEABLE MULTIPLE C[X2]zr
HEAPSIZE 1024 Xg<R+o
EXPORTS MgpjC`
g/3t@7*<
五、结束语
(zIWJJw
本文从原理上分析了称为"陷阱"技术的动态汉化Windows方法,介绍了将任一Windows函数调用改向到自己指定函数处的通用方法,这种方法可以拓展到其它应用中,如多语种显示、不同内码制式的切换显示等。