对于一些调用Windows标准API来进行文本输出的AVG游戏,也许可以借鉴金山词霸之类的屏幕取词技术,HOOK API 之后,在自己的函数里把截获的英文替换为中文,然后输出到屏幕上来实现汉化,而且,这样的工具一旦制作完成,对于这类的AVG游戏将是通用的。
) y5 I& Q! b" B6 x( O! z
& J4 i1 M. G! A6 m7 w# ^3 x5 |6 U7 \! @. l/ ^/ }
金山词霸”屏幕取词技术揭密(讨论稿) ' u! \/ s- f) }
这篇文章最早是发在北极星论坛的一系列帖子,那时候闻怡洋(好像他也是MVP)也在那里混 ( o1 o4 i* l% y. g
原始的帖子我已经没有了,但不知道是谁帮我收集整理了下来(非常感谢),我用google找到了 0 ~& J. b ]1 U, n8 E+ d
? % Q) z4 M4 l- o, W* `- I% D7 L
这是我进金山之前写的,应该不算泄露公司技术秘密吧
7 _5 S+ W4 @# N7 ], c7 \8 E+ }而且这些现在看来似乎已经有些过时了 7 b6 {5 k8 i! u+ {9 e) D1 p' p* V
? " w, e% \; b% `
那时讨论的只是Win31和Win9x下的取词实现 " O$ h, j% }; H& }5 M7 j
? ; ^# U, _1 E) G( j; ]$ y& w
我到了金山之后不是负责取词模块,而是做UI,因为有个家伙比我更擅长做这种东西 4 C+ R; N2 C/ P1 ~% ^0 J
他用SoftIce调试汇编代码非常熟练,做逆向工程方面有过人的天分。 8 @* K0 e) M l/ H1 \* x' A4 E
? % R0 Z+ S9 {( t+ d$ t
? 0 K5 X( k$ m: r* ?6 E0 w
“亦东” 是我那时的笔名 5 d& j! k0 [1 l4 Q, z
? 4 Z! X# S( h4 h5 ]& }, p5 j
? ) w" {& d: y% x# ?5 u# D: g3 }
“金山词霸”屏幕取词技术揭密(讨论稿) / M( f1 `0 X& C. `% r0 U1 Y
? 2 v. a1 Y# S# y- A* ]+ I
主题 屏幕取词技术系列讲座(一) 6 A5 F3 J4 |- }
作者 亦东
! _4 j P+ g G/ d1 q很多人对这个问题感兴趣。
+ O8 y0 c" I+ H2 v, a原因是这项技术让人感觉很神奇,也很有商业价值。
% j. N8 `+ G$ J: O5 J7 j现在词典市场金山词霸占了绝对优势,所以再做字典也没什么前途了。我就是这么认为的,所以我虽然掌握了这项技术,却没去做字典软件。只做了一个和词霸相似的软件自己用,本来想拿出来做共享软件,但我的词库是“偷”来的,而且词汇不多,所以也就算了,词库太小,只能取词有什么用呢?而且词霸有共享版的。
+ T0 ]6 m9 h( W: Y+ ]2 J但既然很多人想了解这项技术,我也不会保留。我准备分多次讲述这项技术的所有细节。
; i2 I- k3 }0 t+ \7 K) R/ d大约每周一两次。想知道的人就常常来看看吧!
* t' V0 d# G. a, q一.基础知识 5 h2 L0 N3 j8 L8 k7 k1 m6 ^5 R. |) _
首先想编这种程序需要一些基础知识。
9 S: I, Z( d2 h会用Vc++,包括16/32位。
( H& U' c6 e6 C1 P* m' M精通Windows API特别是GDI,KERNEL部分。
5 P8 [) E# H3 P# X9 S" j懂汇编语言,会用softice调试程序,因为这种程序最好用softice调试。
e1 o3 |4 h6 Y: w- e8 F二.基本原理
( @3 }( y' M: p& }& Y8 U在Window 3.x时代,windows系统提供的字符输出函数只有很少的几个。
) I/ Y% N$ @) G1 D- I+ {! [TextOut / a" ?/ H4 g) S
ExtTextOut
9 g, L8 y/ n! f, p. X; m9 s* T2 y, Y! }DrawText
5 y0 e( n' q4 B: [4 T7 E w......
1 Y& f7 }" W9 q* \; t+ g2 t其中DrawText最终是用ExtTextOut实现的。 8 o) o- p/ D, x) \& X. P) e4 K3 z$ k6 \
所以Windows的所有字符输出都是由调用TextOut和ExtTextOut实现的。因此,如果你可以修改这两个函数的入口,让程序先调用你自己的一个函数再调用系统的字符输出,你就可以得到Windows所有输出的字符了。 4 q* N" X& v7 p, T2 s- p3 L
到了Windows95时代,原理基本没变,但是95比3.x要复杂。开始的时候,一些在windows3.x下编写的取词软件仍然可以是使用。但是后来出了个IE4,结果很多词典软件就因为不支持IE4而被淘汰了,但同时也给一些软件创造了机会,如金山词霸。其实IE4的问题并不复杂,只不过它的输出的是unicode字符,是用TextOutW和ExtTextOutW输出的。知道了这一点,只要也截取就可以了。不过实现方法复杂一点,以后会有详细讲解。现在又出了个IE5,结果词霸也不好用了,微软真是#^@#$%$*&^&#@#@..........
% y# b9 h4 K e' C# i* I! {$ x9 y我研究后找到了一种解决办法,但还有些问题,有时会取错,正在继续研究,希望大家共同探讨。
' y" K, U; ~7 h! o8 j5 X另外还有WindowsNT,原理也是一样,只是实现方法和95下完全不同。 8 |( x; O& b2 H6 ~% V
三.技术要点
+ w( R- W* N) v0 ?9 O( C" q要实现取词,主要要解决以下技术问题。 4 h5 z4 l3 E4 w9 [$ v: O) ?; g7 c
1.截取API入口,获得API的参数。 1 U( A5 x7 N8 G! \
2.安全地潜入Windows内部,良好地兼容Windows的各个版本 9 ~8 n2 W+ H- V
3.计算鼠标所在的单词和字母。 ) ~. B$ S8 r' B' G5 d2 x
4.如果你在Window95下,做32位程序,还涉及Windows32/16混合编程的技术。
5 s) n3 T: P$ h( F: R. d1 \今天先到这里吧!最好准备一份softice for 95/98和金山词霸,让我们先来分析一下别人是怎么做的。 ! Q0 g- i3 D9 {
欢迎与我联系 & T3 T! a% S) Y! C5 Q) j; j
E-Mail:[email protected]
. r( W8 _) Q; u1 i, P8 q0 D主题 屏幕取词技术系列讲座(二)
/ K1 H' A9 \, m, V( ]作者 亦东 " f+ t. R5 i/ V4 x- k
很抱歉让大家久等了!
& o1 F7 M' m' s1 I# m3 _我看了一些人的回帖,发现很多人对取词的原理还是不太清楚。 5 o0 t# G4 T8 M- _- f c
首先我来解释一下hook问题。词霸中的确用到了hook,而且他用了两种hook其中一种是Windows标准hook,通过SetWindowHook安装一个回调函数,它安装了一个鼠标hook,是为了可以及时响应鼠标的消息用的和取词没太大关系。 3 [5 ]$ k) X1 L" f0 q- h4 s5 ~
另一种钩子是API钩子,这才是取词的核心技术所在。他在TextOut等函数的开头写了一个jmp语句,跳转到自己的代码里。 ! `2 ~# v) E0 x! Q
你用softice看不到这个跳转语句是因为它只在取词的一瞬间才存在,平时是没有的。
3 R; i% I3 M% J6 X& G. D你可以在TextOut开头设一个读写断点
) E4 {- Z2 G& L0 w) Fbpm textout
; I4 g/ x, c/ R' ~( N! K# u* L' }再取词,就会找到词霸用来写钩子的代码了。 ' E/ j+ B" p) i5 n; L2 S
/**********************************
) c! q g& m; v$ x$ P所以我在次强调,想学这种技术一定要懂汇编语言和熟练使用softice. - c6 T6 i4 f1 Y+ w( g, F& y/ p$ _, j( g
**********************************/ 8 l* l/ a8 |; ?" V: d- \
至于从cjktl95中dump出来的未公开函数是和Windows32/16混合编程有关的,以后我会提到他们。 : e2 E/ c$ m6 F3 ^- p0 N9 r" C
我先来讲述取词的过程, $ C, e/ [6 |- O; D) T+ \. f) l
0 判断鼠标是否在一个地方停留了一段时间
/ M( @. b, f; J% N' M9 P1 取得鼠标当前位置
: T' [4 ?/ p$ P+ w' u2 以鼠标位置为中心生成一个矩形 4 L! w) { k! `+ w2 h% i
3 挂上API钩子
$ w, s- A' ~9 l' G. g. v# }/ W7 t4 让这个矩形产生重画消息
* {/ C* `- V2 c' u1 Y9 L. E( C) L/ {, }5 在钩子里等输出字符 1 W( Q! s1 T$ Z5 N( X% H8 V' B
6 计算鼠标在哪个单词上面,把这个单词保存下来 ; Z3 g6 g* S* I/ x8 F
7 如果得到单词则摘掉API钩子,在一段时间后,无论是否得到单词都摘掉API钩子
% u/ W$ G7 k1 J* o8 用单词查词库,显示解释框。 5 Z$ ]' S. K% k M) n
很多步骤实现起来都有一些难度,所以在中国可以做一个完善的取词词典的人屈指可数。 8 x) m' N* S% [
其中0,1,2,7,8比较简单就不提了。
$ r6 Z/ s p, _) [先说如何挂钩子: ( H8 E' i, g3 g
所谓钩子其实就是在WindowsAPI入口写一个JMP XXXX:XXXX语句,跳转到自己的代码里。 ; P" h, l1 m0 p3 c: W4 A' K1 G2 {
步骤如下: ! {# S Z, Z0 y0 R
1.取得Windows API入口,用GetProcAddress实现
! L9 v7 F% j# H% R2.保存API入口的前五个字节,因为JMP是0xEA,地址是4个字节
% c! D p0 B4 j; o3.写入跳转语句 $ M" `6 E3 L& V8 a* F
这步最复杂 , K1 F6 P7 i, S5 ?1 K/ k8 ^
Windows的代码段本来是不可以写的,但是Microsoft给自己留了个后门。 4 [- Y' ~4 P: x0 [. A" t
有一个未公开函数是AllocCsToDsAlias, , d+ ^4 }) E, P# y3 z8 l' h5 u
UINT WINAPI ALLOCCSTODSALIAS(UINT); ) j7 n+ I% D" {: k1 D- \4 V5 v4 h: I8 Z
你可以取到这个函数的入口,把API的代码段的选择符(要是不知道什么是选择符,就先去学学保护模式编程吧)传给他,他会返回一个可写的数据段选择符。这个选择符用完要释放的。用新选择符和API入口的偏移量合成一个指针就可以写windows的代码段了。 ) d; c) C% g5 U8 W0 ~
这就是取词技术的最核心的东东,不止取词,连外挂中文平台全屏汉化都是使用的这种技术。现在知道为什么这么简单的几句话却很少知道了吧?因为太多的产品使用他,太多的公司靠他赚钱了。
! M) \. |7 [ v; n这些公司和产品有:中文之星,四通利方,南极星,金山词霸,实达铭泰的东方快车,roboword,译典通,即时汉化专家等等等等。。。。还有至少20多家小公司。他们的具体实现虽然不同,但大致原理是相同的。
D# ~7 Q: O: v! N; o$ o9 C7 [我这些都是随手写的,也没有提纲之类的东西,以后如果有机会我会整理一下,大家先凑合着看吧!xixi... / V p! [: g% ^) Z1 z
?
* a% _& [" c/ d$ I L) q. w# d; y主题 关于屏幕取词的讨论(三) j( H3 R/ a5 b& Q* F$ r1 s9 ]
作者 亦东
9 o' l! y0 K9 w
' M9 v8 a8 i8 U8 U让大家久等,很抱歉,前些时候工作忙硬盘又坏了,太不幸了。
2 b) B v7 K$ x, ~1 t这回来点真格的。
; r5 Y9 r B2 U& `- q咱们以截取TextOut为例。
) Y. c* v+ U v) J8 B$ t' B下面是代码:
T5 p* b+ U, Q% W$ b! F! d q//截取TextOut ) A- L0 t# T7 C7 g
typedef UINT (WINAPI* ALLOCCSTODSALIAS)(UINT);
. u1 n! W5 D3 ]: ]6 o, w: B1 G6 ^) AALLOCCSTODSALIAS AllocCsToDsAlias; . ^0 {! q" w) R: _$ w
BYTE Newvalue[5];//保存新的入口代码
. [8 D5 K6 f4 H5 u2 u7 E, TBYTE Oldvalue[5];//API原来的入口代码
! x+ F9 y; ^4 _, D0 `unsigned char * Address=NULL;//可写的API入口地址 # ~( U/ O5 g+ `, X
UINT DsSelector=NULL;//指向API入口的可写的选择符 % T' `' u( b7 `) ?0 ]1 `
WORD OffSetEntry=NULL;//API的偏移量
2 R. K. u. b' d, KBOOL bHookAlready = FALSE; //是否挂钩子的标志
$ h$ T$ o( l- B; `- E- O5 IBOOL InitHook() x$ Q) G/ N! F8 Z7 s
{
4 c! n# T! E" S3 A4 s! k7 Y, ^2 Q6 kHMODULE hKernel,hGdi; " ]4 x* Z% l* g* b, h/ b2 w
hKernel = GetModuleHandle("Kernel"); * s# I5 m, n0 L9 m# R
if(hKernel==NULL) : q2 P& a# C# @; Z0 }7 M
return FALSE; : B6 `8 f. J9 S/ v
AllocCsToDsAlias = (ALLOCCSTODSALIAS)GetProcAddress(hKernel,"AllocCsToDsAlias");//这是未公开的API所以要这样取地址
9 q, z. t- p }, ]if(AllocCsToDsAlias==NULL)
( d' ?2 r" q$ @8 P! A5 k9 z$ `5 freturn FALSE; ; v, _* b2 y @" m& J
hGdi = GetModuleHandle("Gdi"); * [/ B% ^3 l$ M0 [) [" _
if(hmGdi==NULL) % K0 x Z5 K. O
return FALSE;
' a* H1 J9 R* C( K/ k( {FARPROC Entry = GetProcAddress(hGdi,"TextOut"); 8 N) L1 Y1 F5 j( v* j* G h( S( E
if(Entry==NULL)
8 Z3 J7 |( j, d8 D, greturn FALSE;
5 z# A" c+ X0 \9 jOffSetEntry = (WORD)(FP_OFF(Entry));//取得API代码段的选择符
I0 ~* P5 r" b F! Q( B' t" K1 NDsSelector = AllocCsToDsAlias(FP_SEG(Entry));//分配一个等同的可写的选择符
# m4 f! Y4 @* R8 H( ]/ ]- yAddress = (unsigned char*)MK_FP(DsSelector,OffSetEntry);//合成地址 : M- `7 w# M* K! m8 p% {2 n
Newvalue[0]=0xEA;
0 J e- _: p' V r* [2 c: ?*((DWORD*)(Newvalue+1)) = (DWORD)MyTextOut;
5 E5 n4 w1 |. S: n0 D: hOldvalue[0]=Address[0]; 8 E* _; Z: ]% x1 G' s& t+ B$ J
*((DWORD*)(Oldvalue+1)) = *((DWORD*)(Address+1)); # k9 [+ o+ ?* Z* Y1 S; ]& A
}
2 x! K3 L- Y" r H, _BOOL ClearHook() * I' I* B2 ~; P* P# R
{
8 R8 E& }& T9 N$ K8 U$ cif(bHookAlready) 3 Y$ j, U9 P+ U1 O8 N8 q: b
HookOff(); 4 O' L D' a2 X$ Z
FreeSelector(DsSelector);
9 a' q: h8 f# c% ~- y/ w/ r} 5 m6 U) }. H( H: @. e6 j
BOOL HookOn() 5 W R7 E" y6 ~) a3 j: V# X
{
' H4 r9 ]* E- Vif(!bHookAlready){
4 a) t+ l# T4 I: o" N7 w& O: j/ dfor(int i=0;i<5;i++){ 2 E' ~: w4 [. }
Address=Newvalue; * p0 W: I; P$ _( S3 f+ B
} # d3 h" J) \/ M* z5 R8 C1 R
bHookAlready=TRUE; ) C, i7 P" j' L& p4 i
}
8 L3 X9 `) m: y}
5 m7 m: j) u$ E- M" A) eBOOL HookOff() : H8 \- A9 D9 h; x
{
- q0 U8 G+ Z) i( D: ]if(bHookAlready){
, F' Y5 t0 _5 A! K& vfor(int i=0;i<5;i++){
* e& \4 B, w Q' `5 T: F* BAddress=Oldvalue;
$ T" S+ Z0 {3 N2 l7 M} 8 z3 B, `1 C9 s4 t2 z3 [( y
bHookAlready=FALSE; : y) C& ]: L9 h A6 x" m
} 3 h6 i7 M( [/ q/ I6 q3 l/ F
} ' q( Q% f, v2 B6 h& i7 G9 o
//钩子函数,一定要和API有相同的参数和声明
/ V9 t3 y, d: q" r9 eBOOL WINAPI MyTextOut(HDC hdc,int nXStart,int nYStart,LPCSTR lpszString,UINT cbString) , S/ ^) {3 E3 o
{ 4 B; I& q& m" D: q4 Z" ?& }, T" g6 K+ {
BOOL ret;
/ k9 D$ H1 |* }4 t1 rHookOff(); + \, p( O% o+ a8 F" c' U- W* B
ret = TextOut(hdc,nXStart,nYStart,lpszString,cbString);//调原来的TextOut
! f+ n; D- j' Z8 hHookOn(); / w# h9 }8 ?. V% c% J. N
return ret; % G& f/ b" a, j4 I& q
} ( I& e0 R# Y3 F" M% u
上面的代码是一个最简单的挂API钩子的例子,我要提醒大家的是,这段代码是我凭记忆写的,我以前的代码丢了,我没有编译测试过
: u. ~! V; A6 b8 f因为我没有VC++1.52.所以代码可能会有错。 - t+ S2 O% i3 u4 f5 F6 {( A6 p
建议使用Borland c++,按16位编译。
; Q, j) ]9 L3 {6 I* M/ U) F如果用VC++1.52,则要改个选项
/ p& }3 l y4 e" A; D4 V8 a在VC++1.52的Option里,有个内存模式的设置,选大模式,和"DS!=SS DS Load on Function entry.",切记,否则会系统崩溃。 ! s5 Q/ E" _ F& n: h& Q" J
有什么不明白的可以给我写信 3 U" I. ?7 y1 U& Q
[email protected]
, D. ]( U5 I1 N3 [' H6 i( ?Wednesday, June 23, 2004 7:11 PM 2 p3 @' [1 ]/ X* y) o0 C& |
4 q4 s' j6 z' r' X. i# o; t |