对于一些调用Windows标准API来进行文本输出的AVG游戏,也许可以借鉴金山词霸之类的屏幕取词技术,HOOK API 之后,在自己的函数里把截获的英文替换为中文,然后输出到屏幕上来实现汉化,而且,这样的工具一旦制作完成,对于这类的AVG游戏将是通用的。
# Q b8 @+ Y/ {& T! v6 P, J/ t1 J( g- Q& K( ^
6 i4 n: i4 D0 s; j. D
金山词霸”屏幕取词技术揭密(讨论稿) : l& C/ @; b, @
这篇文章最早是发在北极星论坛的一系列帖子,那时候闻怡洋(好像他也是MVP)也在那里混 : _& j2 h# q, |% n8 i# W
原始的帖子我已经没有了,但不知道是谁帮我收集整理了下来(非常感谢),我用google找到了 : y/ v: G& S" S2 g
? & ~0 t1 X2 y1 U8 H7 d9 F
这是我进金山之前写的,应该不算泄露公司技术秘密吧
1 N8 r) y4 W1 d [. r8 v而且这些现在看来似乎已经有些过时了 ) J: y4 N/ w* \& S, j
?
8 g+ e9 a# a8 w3 O' }那时讨论的只是Win31和Win9x下的取词实现 1 M+ U" r- w, Z; [
? ; I* R: Z) ~' L! K3 E9 M, v7 A
我到了金山之后不是负责取词模块,而是做UI,因为有个家伙比我更擅长做这种东西
9 o5 R8 @8 a! u# l/ j, u- w他用SoftIce调试汇编代码非常熟练,做逆向工程方面有过人的天分。
6 r/ Z2 b/ |% B5 I: R( w8 m?
! a" b& `3 x. E3 T? ; ]5 J. ?5 x9 o! }& z
“亦东” 是我那时的笔名 0 G# L& y* o+ q- I
? 0 e' O- ?, p1 `$ f: w
? O3 i* z' b5 X' B0 k) o
“金山词霸”屏幕取词技术揭密(讨论稿)
. v4 _3 m3 n- v c?
+ F. B) |. H% D- f5 b" I) u8 k! |主题 屏幕取词技术系列讲座(一) 0 f5 z* ~9 C- R/ h3 H
作者 亦东 / _/ Y/ _2 Z1 ?9 ^5 H
很多人对这个问题感兴趣。
5 K6 ^" t4 l4 r2 U原因是这项技术让人感觉很神奇,也很有商业价值。 . [, ~$ E" d" W6 T: Q( u
现在词典市场金山词霸占了绝对优势,所以再做字典也没什么前途了。我就是这么认为的,所以我虽然掌握了这项技术,却没去做字典软件。只做了一个和词霸相似的软件自己用,本来想拿出来做共享软件,但我的词库是“偷”来的,而且词汇不多,所以也就算了,词库太小,只能取词有什么用呢?而且词霸有共享版的。 + Z$ H1 s! [, C; T* j
但既然很多人想了解这项技术,我也不会保留。我准备分多次讲述这项技术的所有细节。 , t) K, x6 T2 n
大约每周一两次。想知道的人就常常来看看吧! - f; g- a% w* f
一.基础知识
: u ^( V! ?/ j0 K# f/ v首先想编这种程序需要一些基础知识。 ( Q& H9 e7 m* x2 A
会用Vc++,包括16/32位。 * f' r5 R$ D c g
精通Windows API特别是GDI,KERNEL部分。
4 f1 v( }+ L) n$ `懂汇编语言,会用softice调试程序,因为这种程序最好用softice调试。
. ~. i" n2 s, d; y二.基本原理 3 |. A5 j. i- y0 H$ o. W }
在Window 3.x时代,windows系统提供的字符输出函数只有很少的几个。
9 k4 W, S/ h+ ^% `TextOut
n7 I9 a$ ~ ]0 V5 g& I4 B0 fExtTextOut
( e/ v- {) a, W7 h! ^DrawText 5 @$ ] E2 e# o: x+ l& Q$ q4 B) P
......
) l& P0 i/ Y# p' O2 E! ^其中DrawText最终是用ExtTextOut实现的。 & Q2 q* p! R ~1 n" j' H6 U* _' ?
所以Windows的所有字符输出都是由调用TextOut和ExtTextOut实现的。因此,如果你可以修改这两个函数的入口,让程序先调用你自己的一个函数再调用系统的字符输出,你就可以得到Windows所有输出的字符了。 5 G( c3 D+ b8 N+ Y; z, s
到了Windows95时代,原理基本没变,但是95比3.x要复杂。开始的时候,一些在windows3.x下编写的取词软件仍然可以是使用。但是后来出了个IE4,结果很多词典软件就因为不支持IE4而被淘汰了,但同时也给一些软件创造了机会,如金山词霸。其实IE4的问题并不复杂,只不过它的输出的是unicode字符,是用TextOutW和ExtTextOutW输出的。知道了这一点,只要也截取就可以了。不过实现方法复杂一点,以后会有详细讲解。现在又出了个IE5,结果词霸也不好用了,微软真是#^@#$%$*&^&#@#@.......... # K; _! Q/ _- e! V- z
我研究后找到了一种解决办法,但还有些问题,有时会取错,正在继续研究,希望大家共同探讨。 + `! C" z: F- n/ z, W
另外还有WindowsNT,原理也是一样,只是实现方法和95下完全不同。
5 F( P" r9 S! @$ G$ q3 ], ^三.技术要点
) P, _# B! W2 N4 _要实现取词,主要要解决以下技术问题。
% J0 m% T/ m* h, x$ g1.截取API入口,获得API的参数。 6 m7 y' w2 F% ~" R& q: u
2.安全地潜入Windows内部,良好地兼容Windows的各个版本 ' o# Y- n' R3 Y
3.计算鼠标所在的单词和字母。 . y; ~" a/ l& u$ _& L* @% o
4.如果你在Window95下,做32位程序,还涉及Windows32/16混合编程的技术。
$ S9 B$ l. q( C$ A: r今天先到这里吧!最好准备一份softice for 95/98和金山词霸,让我们先来分析一下别人是怎么做的。
2 f, }( k% H" z欢迎与我联系 ' D1 n, d( |8 C
E-Mail:[email protected]
$ V+ [5 T8 B6 q+ C( J7 G3 A主题 屏幕取词技术系列讲座(二) ' |9 J! Z8 z B' y$ ?! ?+ m5 E
作者 亦东 8 P" e/ R! h( B- V
很抱歉让大家久等了!
0 F) ]. h% N- M: W. w我看了一些人的回帖,发现很多人对取词的原理还是不太清楚。 7 u' o7 _' D" r5 A7 z+ P
首先我来解释一下hook问题。词霸中的确用到了hook,而且他用了两种hook其中一种是Windows标准hook,通过SetWindowHook安装一个回调函数,它安装了一个鼠标hook,是为了可以及时响应鼠标的消息用的和取词没太大关系。 z6 ~6 @' F% j: ~1 y
另一种钩子是API钩子,这才是取词的核心技术所在。他在TextOut等函数的开头写了一个jmp语句,跳转到自己的代码里。
/ J1 U6 N4 X2 Q3 [* {4 _: Z你用softice看不到这个跳转语句是因为它只在取词的一瞬间才存在,平时是没有的。
4 M! ?- j: ~3 \) ]# Z N4 t你可以在TextOut开头设一个读写断点 ; ^. o9 L5 o7 }* W+ {$ b
bpm textout / R. e ` L7 ^0 U! b6 g
再取词,就会找到词霸用来写钩子的代码了。 $ n* R# }; a) Z q. }, d! O6 X
/********************************** / d6 D1 f0 g8 c9 Q/ S
所以我在次强调,想学这种技术一定要懂汇编语言和熟练使用softice.
; d* a9 f! l8 J**********************************/ / P/ d5 g2 |) l# ]9 k1 ] N
至于从cjktl95中dump出来的未公开函数是和Windows32/16混合编程有关的,以后我会提到他们。
, \3 m) K0 q) {我先来讲述取词的过程,
8 Q6 D k$ [; R0 @6 K0 判断鼠标是否在一个地方停留了一段时间
" V2 m( m& \1 H- c% r- ^" a1 取得鼠标当前位置
6 d) x) X0 l2 H' Q9 v. i2 以鼠标位置为中心生成一个矩形 4 ]/ ~ ]9 J J
3 挂上API钩子 . V* A. b8 N k- X: z: \! p2 f7 P
4 让这个矩形产生重画消息
+ [# h4 E+ V& {* S2 z0 Y7 X2 z5 在钩子里等输出字符
8 _) y# D6 t2 b# m7 i7 F6 计算鼠标在哪个单词上面,把这个单词保存下来 0 |$ s5 _% f- M R7 D
7 如果得到单词则摘掉API钩子,在一段时间后,无论是否得到单词都摘掉API钩子
' e! s2 F) n1 p" F8 用单词查词库,显示解释框。 + M J) O% a9 t5 m1 q
很多步骤实现起来都有一些难度,所以在中国可以做一个完善的取词词典的人屈指可数。 + p$ ^8 c; ?3 y( u2 g/ L1 d2 o
其中0,1,2,7,8比较简单就不提了。
7 I3 L1 Z& {: Y7 V: i( u先说如何挂钩子: 8 @+ }, R* J- L* Z+ ?
所谓钩子其实就是在WindowsAPI入口写一个JMP XXXX:XXXX语句,跳转到自己的代码里。
$ b' m) {' n3 K5 n步骤如下: ! s9 x: g6 ]0 u d' t" c6 P
1.取得Windows API入口,用GetProcAddress实现 ' V2 p0 @& |% g0 i% N
2.保存API入口的前五个字节,因为JMP是0xEA,地址是4个字节 ( z1 P8 C$ V% V' f
3.写入跳转语句 # E% { e% e+ ^7 B W- E; y; K
这步最复杂
5 U, I' A, F* i0 J/ P, AWindows的代码段本来是不可以写的,但是Microsoft给自己留了个后门。
8 {# e5 M0 B% i7 t有一个未公开函数是AllocCsToDsAlias, ; T! Y, P5 v0 e& [) n( k1 e. _+ n
UINT WINAPI ALLOCCSTODSALIAS(UINT);
0 L$ R; f: E( _1 V5 S5 _. A9 M. m你可以取到这个函数的入口,把API的代码段的选择符(要是不知道什么是选择符,就先去学学保护模式编程吧)传给他,他会返回一个可写的数据段选择符。这个选择符用完要释放的。用新选择符和API入口的偏移量合成一个指针就可以写windows的代码段了。 ! Z/ M. ^3 P1 L$ p" Y
这就是取词技术的最核心的东东,不止取词,连外挂中文平台全屏汉化都是使用的这种技术。现在知道为什么这么简单的几句话却很少知道了吧?因为太多的产品使用他,太多的公司靠他赚钱了。 & W& [, F5 u1 G
这些公司和产品有:中文之星,四通利方,南极星,金山词霸,实达铭泰的东方快车,roboword,译典通,即时汉化专家等等等等。。。。还有至少20多家小公司。他们的具体实现虽然不同,但大致原理是相同的。
/ [# ^0 K8 {2 A3 V6 |' \我这些都是随手写的,也没有提纲之类的东西,以后如果有机会我会整理一下,大家先凑合着看吧!xixi...
" S2 K1 I0 A- N: E? 1 p. a0 z: M! c% Q5 r, d
主题 关于屏幕取词的讨论(三) * B. O U1 [5 t! r, _1 D9 t3 R
作者 亦东 , r+ k* r7 }! c8 r- |2 o; O
' d: _( G4 w' _+ L; h% o
让大家久等,很抱歉,前些时候工作忙硬盘又坏了,太不幸了。 ) R& [- w) m4 U5 U& X! S1 @
这回来点真格的。
' X5 d8 ?9 w) x7 L: z- d- N: F+ V! y咱们以截取TextOut为例。
" H5 {/ Z8 m9 F下面是代码: . {% J$ b# w; C5 u2 z$ Y6 n
//截取TextOut ' |. J0 H. A$ r' [' W
typedef UINT (WINAPI* ALLOCCSTODSALIAS)(UINT);
! B2 `5 P3 @, e3 {" Z6 JALLOCCSTODSALIAS AllocCsToDsAlias;
7 \' S9 l2 A# S" O% ^4 z+ DBYTE Newvalue[5];//保存新的入口代码
1 k1 O" _5 \+ |" b9 ]7 e" HBYTE Oldvalue[5];//API原来的入口代码 + F, S" i0 d1 d6 l/ `
unsigned char * Address=NULL;//可写的API入口地址 0 ~: ~5 _" Q- F
UINT DsSelector=NULL;//指向API入口的可写的选择符
: F% P F6 \9 D) q; j6 N$ f8 bWORD OffSetEntry=NULL;//API的偏移量 0 h0 d8 `+ R# [% @: ?/ Y2 }" ]
BOOL bHookAlready = FALSE; //是否挂钩子的标志
( [" k/ w- [9 Y4 p. F' qBOOL InitHook() 9 y/ I5 X" T4 q. ]2 N
{ + R4 L% N. ^0 c6 v1 ]& S/ [! n
HMODULE hKernel,hGdi; ; i* w; D+ Q/ P( A( Y
hKernel = GetModuleHandle("Kernel"); , t& r" X% @% J& H# E
if(hKernel==NULL) & r# A6 R, @7 u- X" n8 R
return FALSE; 7 K/ h4 F. j( U H7 M$ t
AllocCsToDsAlias = (ALLOCCSTODSALIAS)GetProcAddress(hKernel,"AllocCsToDsAlias");//这是未公开的API所以要这样取地址
* K8 |. ~( f9 a& c. [1 Y: L! j7 l; I6 j. @8 oif(AllocCsToDsAlias==NULL) 2 ]* C7 x4 E6 R7 A. W+ C( A( t: A
return FALSE;
. X" s8 b. S# K; k! `hGdi = GetModuleHandle("Gdi");
# L% V( Z( I J" H: N8 r* D4 Iif(hmGdi==NULL) # `+ K: Z) ?: Y/ Y
return FALSE;
; Z& y( \. U- `1 g* _FARPROC Entry = GetProcAddress(hGdi,"TextOut");
; m2 v6 K' w \1 j$ iif(Entry==NULL)
' Q, g5 i3 A8 Vreturn FALSE; $ o5 E! R" ~+ C( M5 x$ [
OffSetEntry = (WORD)(FP_OFF(Entry));//取得API代码段的选择符
& ?3 c9 e* h# S p& JDsSelector = AllocCsToDsAlias(FP_SEG(Entry));//分配一个等同的可写的选择符
5 A) I* q) ~% J7 `' tAddress = (unsigned char*)MK_FP(DsSelector,OffSetEntry);//合成地址 $ z1 e) a8 [8 R# d8 |$ B
Newvalue[0]=0xEA; 9 T2 T1 G* O6 q S9 `
*((DWORD*)(Newvalue+1)) = (DWORD)MyTextOut; # n2 [# Y! U9 n0 z# @0 d
Oldvalue[0]=Address[0]; 3 q, y5 Y1 d( V2 {: A p
*((DWORD*)(Oldvalue+1)) = *((DWORD*)(Address+1));
/ b$ i5 L0 l4 C} $ ~6 R& _) e/ r; c9 C
BOOL ClearHook() 9 O' g" M+ J; ~( v3 e
{ - \/ B8 @# E, o7 U/ U8 A0 z
if(bHookAlready)
6 A0 _# f( ^7 HHookOff();
( W) | V0 ^& r" a5 W, i( k& bFreeSelector(DsSelector); 5 k: C: C2 Q# w* ~; z( M
}
6 e( p9 x+ ?* F; ]BOOL HookOn() ( L+ B( }1 ? @. p# h9 ~ b3 S- s
{ - G* \) x* c/ c7 d, G u
if(!bHookAlready){
+ f+ h/ s: }7 Lfor(int i=0;i<5;i++){
( b. f; h; n3 [! K# `1 b* H$ AAddress=Newvalue; ! n9 N. o6 j! b% A2 }. Z
}
7 v+ y9 s( a9 _9 b* _' |7 |6 _# j0 cbHookAlready=TRUE; : r1 i: m3 \/ U. z
}
( K7 c! ?6 B; Q* k) q! T$ i0 s0 h- k} ' P0 O, D2 R3 @* m0 L4 Z5 h: a( q
BOOL HookOff() + V/ t+ }) H( }+ z
{ , ?9 A8 W8 Q/ |, l
if(bHookAlready){ . T( {6 }! ]$ W3 H9 p" M
for(int i=0;i<5;i++){
3 h7 [6 M, ]: G- d; S1 y& P# oAddress=Oldvalue;
1 d6 d! _9 }9 l8 ?- \} - k) I# Z9 v! T: E
bHookAlready=FALSE;
4 v8 k5 }, P! O& Q' r}
* r. t, T1 y" |% I} $ v7 B2 M' H, V% r: q' t l+ [
//钩子函数,一定要和API有相同的参数和声明
, q$ q1 ?9 T, I9 wBOOL WINAPI MyTextOut(HDC hdc,int nXStart,int nYStart,LPCSTR lpszString,UINT cbString) + D. t" y0 K+ }$ d5 K( a
{ 3 J1 c* x- `( A1 q- w
BOOL ret; ! _) g' }) V( ?8 ]& z ^/ t& _
HookOff();
4 b; z2 c3 R8 [ T' {ret = TextOut(hdc,nXStart,nYStart,lpszString,cbString);//调原来的TextOut
2 i2 }* o1 `$ \% ]0 IHookOn(); ) A4 q, d7 `/ h) x, |/ [
return ret; 4 I$ f" W4 q6 k/ G
}
+ b# P O% u4 @' C& L1 @上面的代码是一个最简单的挂API钩子的例子,我要提醒大家的是,这段代码是我凭记忆写的,我以前的代码丢了,我没有编译测试过 $ s5 ?) ^, \! X8 o7 I0 h) d- u: j$ j' Z B
因为我没有VC++1.52.所以代码可能会有错。 ( k/ T8 C1 j+ Y; x, _* v7 q+ N J
建议使用Borland c++,按16位编译。 . ~, t! L/ `! x% H
如果用VC++1.52,则要改个选项
* ~' j2 p, H/ |% ^- I8 v0 p; W& z在VC++1.52的Option里,有个内存模式的设置,选大模式,和"DS!=SS DS Load on Function entry.",切记,否则会系统崩溃。 7 r/ [& n+ W( Q8 Q
有什么不明白的可以给我写信
5 k6 {$ i1 d" D& F4 x* S[email protected] : O3 x7 X9 a) _6 O/ _3 V* w
Wednesday, June 23, 2004 7:11 PM
0 c0 ~8 M/ F: |0 B+ m! Y2 A7 P; X, H S' X' y
|