对于一些调用Windows标准API来进行文本输出的AVG游戏,也许可以借鉴金山词霸之类的屏幕取词技术,HOOK API 之后,在自己的函数里把截获的英文替换为中文,然后输出到屏幕上来实现汉化,而且,这样的工具一旦制作完成,对于这类的AVG游戏将是通用的。
* b3 W X+ n2 H/ y7 S; |% ~& Z. g2 r$ P/ R( t
! {1 U- Z6 [0 w4 i, L, S5 S' Q5 A金山词霸”屏幕取词技术揭密(讨论稿)
$ [! B( l9 _+ w3 {这篇文章最早是发在北极星论坛的一系列帖子,那时候闻怡洋(好像他也是MVP)也在那里混
% |& m! g v( {0 b. V4 K4 C3 U原始的帖子我已经没有了,但不知道是谁帮我收集整理了下来(非常感谢),我用google找到了 # S { K1 a4 P) k/ M; D+ p I
?
, ]) v6 {4 K+ P- E4 j这是我进金山之前写的,应该不算泄露公司技术秘密吧 " z: w+ r7 _0 Y d. U G
而且这些现在看来似乎已经有些过时了 : Y& N- W8 { D; k8 a
?
- e( T3 `% f5 }那时讨论的只是Win31和Win9x下的取词实现
- u+ J$ i! B5 j? 3 R: c1 ~/ v4 T9 @/ L2 I, p
我到了金山之后不是负责取词模块,而是做UI,因为有个家伙比我更擅长做这种东西 ) \, o: R/ i+ z
他用SoftIce调试汇编代码非常熟练,做逆向工程方面有过人的天分。 1 Q) F2 k9 F$ F% R! ^- _, W
?
2 [7 z" u" M" o4 f; i% p?
; q( F' |% F7 j z" \/ ?“亦东” 是我那时的笔名
" p. O8 c! S8 o( y8 M?
8 W1 r) g$ d: y# P& d [?
$ A/ J. D& ], G3 K2 ~“金山词霸”屏幕取词技术揭密(讨论稿)
5 \4 w- o, i9 ^+ `?
9 Q2 E: e* R! i; D d9 \7 N9 W' v主题 屏幕取词技术系列讲座(一)
$ }" U! K/ D+ `作者 亦东 ~/ }$ l' g) N; U* ?
很多人对这个问题感兴趣。
6 X$ q% [$ [7 {! Y8 ?原因是这项技术让人感觉很神奇,也很有商业价值。 : l( b- I/ S- `$ u
现在词典市场金山词霸占了绝对优势,所以再做字典也没什么前途了。我就是这么认为的,所以我虽然掌握了这项技术,却没去做字典软件。只做了一个和词霸相似的软件自己用,本来想拿出来做共享软件,但我的词库是“偷”来的,而且词汇不多,所以也就算了,词库太小,只能取词有什么用呢?而且词霸有共享版的。 2 {; f0 A: N- M* y- @. m0 f
但既然很多人想了解这项技术,我也不会保留。我准备分多次讲述这项技术的所有细节。
8 G: L9 Z' B" F& M大约每周一两次。想知道的人就常常来看看吧! 3 C7 S! A/ }6 _* Q4 U
一.基础知识 9 S2 ? P( D* J
首先想编这种程序需要一些基础知识。
+ }) `) c. D' v+ n8 A+ F会用Vc++,包括16/32位。
& T" u. X# k, X! v. N4 j7 D精通Windows API特别是GDI,KERNEL部分。
' s: `3 m/ O1 M0 u5 T. L懂汇编语言,会用softice调试程序,因为这种程序最好用softice调试。
! G- X Q5 y+ G3 Y3 L二.基本原理 " p' u) v( j! N& i) T
在Window 3.x时代,windows系统提供的字符输出函数只有很少的几个。 , d6 |+ f' Z" p! }4 }% P
TextOut
' e$ O A4 B5 c. n- c/ FExtTextOut
6 s9 i( o* _5 g! SDrawText - F( \% S* G! I1 O* t; P
......
5 Q; Q2 _2 Z/ _3 U% y: Z n3 @其中DrawText最终是用ExtTextOut实现的。 a$ t4 w* e+ _. S! L, |5 D' z
所以Windows的所有字符输出都是由调用TextOut和ExtTextOut实现的。因此,如果你可以修改这两个函数的入口,让程序先调用你自己的一个函数再调用系统的字符输出,你就可以得到Windows所有输出的字符了。 7 a( O/ e0 B* b. G5 D) r: Z9 \
到了Windows95时代,原理基本没变,但是95比3.x要复杂。开始的时候,一些在windows3.x下编写的取词软件仍然可以是使用。但是后来出了个IE4,结果很多词典软件就因为不支持IE4而被淘汰了,但同时也给一些软件创造了机会,如金山词霸。其实IE4的问题并不复杂,只不过它的输出的是unicode字符,是用TextOutW和ExtTextOutW输出的。知道了这一点,只要也截取就可以了。不过实现方法复杂一点,以后会有详细讲解。现在又出了个IE5,结果词霸也不好用了,微软真是#^@#$%$*&^&#@#@..........
6 r9 W3 t" X: s7 M! ^) v" r我研究后找到了一种解决办法,但还有些问题,有时会取错,正在继续研究,希望大家共同探讨。 ( s/ K8 Z; I1 u6 j1 j* j
另外还有WindowsNT,原理也是一样,只是实现方法和95下完全不同。
5 \' ~; F4 t% g$ L" I3 V5 E三.技术要点
6 `- D# U5 z$ {要实现取词,主要要解决以下技术问题。 7 t" ]. `% H0 C% ?) ?2 @. l4 |
1.截取API入口,获得API的参数。 7 E- Z5 | u# c d/ C
2.安全地潜入Windows内部,良好地兼容Windows的各个版本
( l- e% K6 F# D& g& N3.计算鼠标所在的单词和字母。
H b3 e; `% c; J. L# X( p7 t3 r4.如果你在Window95下,做32位程序,还涉及Windows32/16混合编程的技术。
# T3 K# u/ F5 {; \今天先到这里吧!最好准备一份softice for 95/98和金山词霸,让我们先来分析一下别人是怎么做的。
3 s. w1 H9 A1 g5 M欢迎与我联系
' M9 p# b' L! [, g" F" ^E-Mail:[email protected]
: {: c5 V$ _5 [- s3 u; N6 L/ m主题 屏幕取词技术系列讲座(二)
2 u. S q1 @# _3 c S; p作者 亦东
) \* m" p, Q/ p: D很抱歉让大家久等了! 8 D9 I+ ^( ~! D1 @9 h
我看了一些人的回帖,发现很多人对取词的原理还是不太清楚。
9 i) V* ]) q8 T- Y" t首先我来解释一下hook问题。词霸中的确用到了hook,而且他用了两种hook其中一种是Windows标准hook,通过SetWindowHook安装一个回调函数,它安装了一个鼠标hook,是为了可以及时响应鼠标的消息用的和取词没太大关系。
- p" M# L8 p0 P- s! L5 S; R& ^另一种钩子是API钩子,这才是取词的核心技术所在。他在TextOut等函数的开头写了一个jmp语句,跳转到自己的代码里。
/ W, l$ s7 D6 }. i你用softice看不到这个跳转语句是因为它只在取词的一瞬间才存在,平时是没有的。
% S" d4 m9 B. r你可以在TextOut开头设一个读写断点
+ ?- n$ r, W5 b" d' J3 |3 o. J( x$ D0 Mbpm textout * s4 S r9 Y4 ^9 o) |2 O* H8 [
再取词,就会找到词霸用来写钩子的代码了。 ! ]! U* w& t8 x: u0 N8 ?
/********************************** ]0 N# ^7 V: I) r* [% t. w( y8 K6 w
所以我在次强调,想学这种技术一定要懂汇编语言和熟练使用softice.
' b0 r: `. v4 s* l' t$ l, [**********************************/
b; n' h }, o [3 x6 Y+ y至于从cjktl95中dump出来的未公开函数是和Windows32/16混合编程有关的,以后我会提到他们。 ( R+ x; Z8 h D2 a. s/ W# y
我先来讲述取词的过程,
* r: D% Y& I) N5 L7 p+ W0 Y0 判断鼠标是否在一个地方停留了一段时间
$ j0 G ~+ m/ N6 v/ Q1 取得鼠标当前位置
7 [2 y: O$ |0 }! Z2 以鼠标位置为中心生成一个矩形
* N. ]' M: U" i2 o3 L6 Y3 挂上API钩子 ) _" x: h2 z+ ?0 N+ i
4 让这个矩形产生重画消息 9 ]3 Y E9 S2 U5 L3 t
5 在钩子里等输出字符 " @$ `7 |' K1 C
6 计算鼠标在哪个单词上面,把这个单词保存下来
7 U8 E4 Y% x! G; V3 f7 如果得到单词则摘掉API钩子,在一段时间后,无论是否得到单词都摘掉API钩子
0 l* _) j B0 U1 e: d4 c: w8 用单词查词库,显示解释框。
/ b# r' x& E; }: a1 ?' U# ~很多步骤实现起来都有一些难度,所以在中国可以做一个完善的取词词典的人屈指可数。 * ?& ?5 J/ X2 Z3 W
其中0,1,2,7,8比较简单就不提了。
, ^: n/ k0 R! W先说如何挂钩子: % U& _0 @, F$ Q9 d+ Q
所谓钩子其实就是在WindowsAPI入口写一个JMP XXXX:XXXX语句,跳转到自己的代码里。 $ ~* r! e& a, n9 `1 }* \
步骤如下: 3 D5 q6 V- P3 @. A/ V
1.取得Windows API入口,用GetProcAddress实现
0 W- } q8 [- j' q" \2.保存API入口的前五个字节,因为JMP是0xEA,地址是4个字节
0 m0 A$ N) j x4 O7 C3.写入跳转语句 5 I' ~' S; ~& b4 q
这步最复杂
# F' T. \+ [3 y8 h8 b) `Windows的代码段本来是不可以写的,但是Microsoft给自己留了个后门。
' W' Y% u9 |. M7 v. I有一个未公开函数是AllocCsToDsAlias,
, f+ H/ y0 v: a! h7 ?UINT WINAPI ALLOCCSTODSALIAS(UINT); 5 b, x# p/ _" A; w" z5 ]
你可以取到这个函数的入口,把API的代码段的选择符(要是不知道什么是选择符,就先去学学保护模式编程吧)传给他,他会返回一个可写的数据段选择符。这个选择符用完要释放的。用新选择符和API入口的偏移量合成一个指针就可以写windows的代码段了。
& T% ?; C; [1 z' l7 N这就是取词技术的最核心的东东,不止取词,连外挂中文平台全屏汉化都是使用的这种技术。现在知道为什么这么简单的几句话却很少知道了吧?因为太多的产品使用他,太多的公司靠他赚钱了。
) N4 o, G) B1 \! {这些公司和产品有:中文之星,四通利方,南极星,金山词霸,实达铭泰的东方快车,roboword,译典通,即时汉化专家等等等等。。。。还有至少20多家小公司。他们的具体实现虽然不同,但大致原理是相同的。
5 b/ v! Z4 O2 F5 u我这些都是随手写的,也没有提纲之类的东西,以后如果有机会我会整理一下,大家先凑合着看吧!xixi... 4 W+ L' }( H9 j- X* |8 G* Z; K: M
?
. ^# y% c: I2 y* W* _; ]主题 关于屏幕取词的讨论(三) 7 }/ T6 K5 L# X4 S6 t# V7 b0 x2 H- h, J
作者 亦东
8 r. y; @7 O2 U! o
, t" {* t* n: n! C- ^* F4 i让大家久等,很抱歉,前些时候工作忙硬盘又坏了,太不幸了。 6 X$ j6 I7 R0 d( p3 h( B. m6 W
这回来点真格的。 ( ^5 N: U$ m' m& N* z( L( R
咱们以截取TextOut为例。 ; \1 ]$ F0 r5 P8 y7 j8 h0 P7 G
下面是代码:
' T) h* j" I2 ]9 G0 a//截取TextOut
* C7 Q7 V' K2 b. X7 Rtypedef UINT (WINAPI* ALLOCCSTODSALIAS)(UINT);
) p' i5 c N% TALLOCCSTODSALIAS AllocCsToDsAlias;
( P( q3 k( x& p, j' QBYTE Newvalue[5];//保存新的入口代码 ; v: K0 e! y U: r
BYTE Oldvalue[5];//API原来的入口代码 1 ~1 z( P3 \8 h2 l
unsigned char * Address=NULL;//可写的API入口地址 ! j3 t( E4 ~! L9 ]6 V0 e7 _
UINT DsSelector=NULL;//指向API入口的可写的选择符 6 b" F6 x0 g; Q) }# v( p b7 C
WORD OffSetEntry=NULL;//API的偏移量
+ X$ K( q0 z0 j% cBOOL bHookAlready = FALSE; //是否挂钩子的标志 . `6 C) i' n4 _. l
BOOL InitHook() 3 x s/ x' F0 T- f: _- t w8 T
{ ) K: p/ b5 G) P/ N6 r9 c
HMODULE hKernel,hGdi;
; h+ T% X e3 d% C9 \hKernel = GetModuleHandle("Kernel"); / B& n- r. x- V. ^7 d4 H4 z
if(hKernel==NULL) 5 S: ~& i6 E0 x5 B ?
return FALSE;
4 K3 F3 D! z7 n; Q) |3 c5 |AllocCsToDsAlias = (ALLOCCSTODSALIAS)GetProcAddress(hKernel,"AllocCsToDsAlias");//这是未公开的API所以要这样取地址
P+ w& I2 O2 }6 O, _' sif(AllocCsToDsAlias==NULL)
. h. ]! R( q6 K! [6 o/ lreturn FALSE;
# s0 X) i$ g1 B3 t2 ahGdi = GetModuleHandle("Gdi");
6 H7 V% g2 I$ K k! r8 wif(hmGdi==NULL)
( c$ @2 t1 t8 c% Xreturn FALSE; : h7 P3 d. K9 R; q7 z! I
FARPROC Entry = GetProcAddress(hGdi,"TextOut");
1 H6 u3 f+ D( s. o6 u! Aif(Entry==NULL) ! I0 W9 F! C2 S7 j5 i. N
return FALSE; 3 p6 K0 ^; ]' i# {5 ~
OffSetEntry = (WORD)(FP_OFF(Entry));//取得API代码段的选择符
7 f1 ^ Z0 }/ }DsSelector = AllocCsToDsAlias(FP_SEG(Entry));//分配一个等同的可写的选择符
/ J$ _+ S( ^. Y/ C4 ZAddress = (unsigned char*)MK_FP(DsSelector,OffSetEntry);//合成地址
( ^" _3 D/ g& C& A5 h E. qNewvalue[0]=0xEA; 4 j, T9 p6 Z! N7 r8 G) d" z
*((DWORD*)(Newvalue+1)) = (DWORD)MyTextOut; * V) g& z3 P0 ~+ b3 g9 C
Oldvalue[0]=Address[0]; 6 g. i% @$ r5 N- @" {9 f( D
*((DWORD*)(Oldvalue+1)) = *((DWORD*)(Address+1)); 3 E: P4 }" `0 ?) @6 q' }9 c! M
}
: O9 a [: ~( @8 U4 O! vBOOL ClearHook() d, A) x, x& W. i) ^; W9 o
{
" v9 O- B5 [9 g0 [+ C Oif(bHookAlready) - A: g9 \4 R' s
HookOff(); : m8 g* K _# d1 P, O+ h6 F3 [# O
FreeSelector(DsSelector); # \ T" K" E9 D9 W
} $ E8 ]' o9 y( {6 k1 l7 v
BOOL HookOn()
6 Z! K1 q8 M' e{ 8 b/ x, V( c1 w# y6 i/ r! r6 j
if(!bHookAlready){ 8 S( ^6 o1 K; H: i' D' C% S5 p
for(int i=0;i<5;i++){ 3 O- ?/ _, Q- k) U2 P/ ~9 I0 T
Address=Newvalue; 9 i0 @$ v) B& V( j4 c7 v- }
} . O2 ?* |9 e6 D9 ]
bHookAlready=TRUE;
' H- F$ ]& i! R H. s} ) F4 G4 m( a5 o$ O! o( z8 Y5 A
}
) W# ?% M; Y+ }# }BOOL HookOff()
/ m5 `! n, \3 Q{ ; {4 |! C' R; {7 D) [. z6 y
if(bHookAlready){
( E5 n) a) j; w7 v% l/ zfor(int i=0;i<5;i++){ ; V+ `: L0 ^! }/ J" q7 p
Address=Oldvalue;
: x9 @9 m5 S" R/ @& E}
. ^0 D: Q5 @& k) ^; Z0 e% x* k8 cbHookAlready=FALSE; - @% ~+ A& G( {8 M) X
}
; e3 h) a& w$ W3 K, o' i' z}
$ M9 V: R) Y3 _7 s+ w//钩子函数,一定要和API有相同的参数和声明
, S- a( t+ ~; w% H; m$ ^0 c( {7 FBOOL WINAPI MyTextOut(HDC hdc,int nXStart,int nYStart,LPCSTR lpszString,UINT cbString) * L& _; ~5 p+ F
{
; G& D1 P) x6 `6 Y; z5 \BOOL ret;
& V1 j) E+ K+ X3 N+ P fHookOff(); * z2 S8 G. P/ t" e% O
ret = TextOut(hdc,nXStart,nYStart,lpszString,cbString);//调原来的TextOut
/ m# m0 L, l/ _7 k- w% RHookOn(); b9 O. t+ n+ B7 P' }% {. B6 K; K- Q
return ret; $ \0 p. x8 G1 Q
}
! `" U K$ d( g( Q( v, ~上面的代码是一个最简单的挂API钩子的例子,我要提醒大家的是,这段代码是我凭记忆写的,我以前的代码丢了,我没有编译测试过
: ?8 e5 v# I8 y0 H5 u# U7 I因为我没有VC++1.52.所以代码可能会有错。 $ z- {3 g& e# q2 u- @( k" _
建议使用Borland c++,按16位编译。
0 g/ `* j7 {' I( c; o' \5 q+ U如果用VC++1.52,则要改个选项
6 L2 F4 t$ |: y在VC++1.52的Option里,有个内存模式的设置,选大模式,和"DS!=SS DS Load on Function entry.",切记,否则会系统崩溃。
# I8 @# C: ]& ]有什么不明白的可以给我写信 . B. U# H# g+ t
[email protected]
+ l" D4 i7 T$ s3 uWednesday, June 23, 2004 7:11 PM 0 S& h5 q5 w7 n3 y$ U& u
! C( L/ f5 P) _% H8 F) Z |