对于一些调用Windows标准API来进行文本输出的AVG游戏,也许可以借鉴金山词霸之类的屏幕取词技术,HOOK API 之后,在自己的函数里把截获的英文替换为中文,然后输出到屏幕上来实现汉化,而且,这样的工具一旦制作完成,对于这类的AVG游戏将是通用的。
7 i/ j3 [* v+ w4 C' b+ ?( b% ?3 ?; u5 [7 M3 A; I: h$ \
4 M" a0 d! P. v2 m1 e: ]! X金山词霸”屏幕取词技术揭密(讨论稿) X2 Y) \ V9 g0 H7 L% c
这篇文章最早是发在北极星论坛的一系列帖子,那时候闻怡洋(好像他也是MVP)也在那里混
- R7 Z: j7 y; K6 D& r原始的帖子我已经没有了,但不知道是谁帮我收集整理了下来(非常感谢),我用google找到了 1 G* |$ u% A C. M
?
; g! ?/ n. i" i4 c/ R, a这是我进金山之前写的,应该不算泄露公司技术秘密吧
* M7 K2 f; R( Y8 I: k* W G) x而且这些现在看来似乎已经有些过时了 # R6 k- @: F! Q! P5 U) @; w% ^9 f
?
+ _% a& T, ?2 T1 k5 }- P那时讨论的只是Win31和Win9x下的取词实现
0 n |- j6 [; e+ ]- h8 b? 9 l/ M& O9 S/ n6 V5 V# }, u7 k
我到了金山之后不是负责取词模块,而是做UI,因为有个家伙比我更擅长做这种东西
$ y& ]2 C0 r5 \8 s9 i2 a1 j他用SoftIce调试汇编代码非常熟练,做逆向工程方面有过人的天分。
; v5 [/ ~/ M% t5 g& w g) \? n: q/ o' _8 H
? 5 K5 S5 t# p2 L1 F
“亦东” 是我那时的笔名 * |' @* |# I! [" H9 }
? 9 z; {3 g" @0 R1 I" j# G' m
? 2 d; V9 ~( I: i
“金山词霸”屏幕取词技术揭密(讨论稿)
; l3 z9 e* A5 O' k2 T! [0 [?
. K+ A; t7 [" G% r2 _* [. H$ q* n( O主题 屏幕取词技术系列讲座(一) / @4 J) `+ K" Y' `" H4 B% M
作者 亦东 2 E2 N- Y$ o! {% K8 y" S
很多人对这个问题感兴趣。 * P0 p/ m: n# S0 O
原因是这项技术让人感觉很神奇,也很有商业价值。
. l1 d( q/ @) g+ {: C' A% i( ^# j现在词典市场金山词霸占了绝对优势,所以再做字典也没什么前途了。我就是这么认为的,所以我虽然掌握了这项技术,却没去做字典软件。只做了一个和词霸相似的软件自己用,本来想拿出来做共享软件,但我的词库是“偷”来的,而且词汇不多,所以也就算了,词库太小,只能取词有什么用呢?而且词霸有共享版的。
4 K6 e1 `$ r( \+ q* P, `' i% r( g但既然很多人想了解这项技术,我也不会保留。我准备分多次讲述这项技术的所有细节。
0 j* D7 b! h9 w+ I$ G6 Z大约每周一两次。想知道的人就常常来看看吧! - b: u4 \5 k( J: Y) P+ T0 m0 W9 [
一.基础知识 $ ^8 v. B' [7 S- l9 ?
首先想编这种程序需要一些基础知识。
* O y- |6 n1 t. R+ r% W4 E会用Vc++,包括16/32位。 9 j1 v1 S9 [# B- \: ~+ R* @8 h3 a
精通Windows API特别是GDI,KERNEL部分。 ! ^+ Y5 o5 g' E
懂汇编语言,会用softice调试程序,因为这种程序最好用softice调试。 9 p* ?* H# L# P, b
二.基本原理
4 v9 a. v+ q% i" ~$ t# K在Window 3.x时代,windows系统提供的字符输出函数只有很少的几个。 ( y* F5 s A( i# l, k* Z! \* G8 O( A
TextOut
8 _) w2 M+ ]1 j2 ?ExtTextOut
$ B0 |2 B, C8 r& cDrawText
( O! h. `1 | t5 |...... $ D' \$ w" S/ n
其中DrawText最终是用ExtTextOut实现的。
1 O) f' m0 o7 `6 S; G t5 |% g6 l所以Windows的所有字符输出都是由调用TextOut和ExtTextOut实现的。因此,如果你可以修改这两个函数的入口,让程序先调用你自己的一个函数再调用系统的字符输出,你就可以得到Windows所有输出的字符了。
, R9 `2 W% {+ c F; F到了Windows95时代,原理基本没变,但是95比3.x要复杂。开始的时候,一些在windows3.x下编写的取词软件仍然可以是使用。但是后来出了个IE4,结果很多词典软件就因为不支持IE4而被淘汰了,但同时也给一些软件创造了机会,如金山词霸。其实IE4的问题并不复杂,只不过它的输出的是unicode字符,是用TextOutW和ExtTextOutW输出的。知道了这一点,只要也截取就可以了。不过实现方法复杂一点,以后会有详细讲解。现在又出了个IE5,结果词霸也不好用了,微软真是#^@#$%$*&^&#@#@.......... ' U! l8 \5 u5 Z* a
我研究后找到了一种解决办法,但还有些问题,有时会取错,正在继续研究,希望大家共同探讨。 ( }4 m' P1 U# X9 R5 G
另外还有WindowsNT,原理也是一样,只是实现方法和95下完全不同。 ' c: t- l( s( B4 \# a
三.技术要点 L, P: ~4 ~7 o; M+ O/ B
要实现取词,主要要解决以下技术问题。
) c. W: y0 ?/ d& s9 B1.截取API入口,获得API的参数。 ! |( Q ]* P/ l9 Q9 T7 A+ D# n r% @
2.安全地潜入Windows内部,良好地兼容Windows的各个版本
9 g, P3 a0 G3 l4 r; } T3.计算鼠标所在的单词和字母。 7 s& X+ W! Y0 q1 K e
4.如果你在Window95下,做32位程序,还涉及Windows32/16混合编程的技术。 8 [0 V6 N1 N5 [. l" A. L# {- u
今天先到这里吧!最好准备一份softice for 95/98和金山词霸,让我们先来分析一下别人是怎么做的。 $ ]5 Q+ R+ r. O4 b& ~ I9 C& C& P" H
欢迎与我联系
+ @$ V" t- O" j) B$ |" vE-Mail:[email protected] $ b6 B- b9 `5 B2 ?
主题 屏幕取词技术系列讲座(二)
9 a2 X/ \: H) L: v作者 亦东
2 [1 P6 z7 y0 V: W. |& l很抱歉让大家久等了! 5 I5 J8 _/ n8 W
我看了一些人的回帖,发现很多人对取词的原理还是不太清楚。 / y7 l6 [1 [2 y( j% D
首先我来解释一下hook问题。词霸中的确用到了hook,而且他用了两种hook其中一种是Windows标准hook,通过SetWindowHook安装一个回调函数,它安装了一个鼠标hook,是为了可以及时响应鼠标的消息用的和取词没太大关系。 ' n8 U' L j. M5 y3 B9 ^4 d# G
另一种钩子是API钩子,这才是取词的核心技术所在。他在TextOut等函数的开头写了一个jmp语句,跳转到自己的代码里。 - @4 z% ^- d8 n: l: i
你用softice看不到这个跳转语句是因为它只在取词的一瞬间才存在,平时是没有的。
/ A1 c/ m% g4 M& f& v你可以在TextOut开头设一个读写断点 ! o* X9 W! b2 r; i5 }
bpm textout
9 k7 V" G0 K e6 _4 c" l再取词,就会找到词霸用来写钩子的代码了。 0 l$ Z* \" L, ?2 n! T5 ^* G, l
/**********************************
7 E$ f* w( Z, u- H8 v所以我在次强调,想学这种技术一定要懂汇编语言和熟练使用softice.
& `, Q; M" G- Q**********************************/
% }' b& U; O l- ]' H R至于从cjktl95中dump出来的未公开函数是和Windows32/16混合编程有关的,以后我会提到他们。
: Y" F' B" A8 E; f. t7 p0 L$ W我先来讲述取词的过程, ! r3 J* I2 ~1 i) K5 K
0 判断鼠标是否在一个地方停留了一段时间
9 O$ q4 D0 b4 @ l) i1 取得鼠标当前位置
0 x/ H. J7 `$ t J' \1 A! P2 以鼠标位置为中心生成一个矩形
/ Q; \: _1 I! f- y7 H! \3 挂上API钩子
# l8 R* a# l# {. \9 G( h: o% K4 让这个矩形产生重画消息
' I) p. D$ f% i5 在钩子里等输出字符 / W) x* T% t- h1 O8 }" d3 r
6 计算鼠标在哪个单词上面,把这个单词保存下来 ! n" x: r8 d3 N
7 如果得到单词则摘掉API钩子,在一段时间后,无论是否得到单词都摘掉API钩子
7 `7 _4 N/ @6 `) j: a3 z5 @# Y/ S. A8 用单词查词库,显示解释框。 + c0 t+ o3 n8 ]6 o0 W8 D0 r! o1 {# S; [
很多步骤实现起来都有一些难度,所以在中国可以做一个完善的取词词典的人屈指可数。 3 ^ l0 W% J: A+ j5 z( x1 j
其中0,1,2,7,8比较简单就不提了。
q9 L6 ~" {" u6 C先说如何挂钩子: - }; x% d; D% m
所谓钩子其实就是在WindowsAPI入口写一个JMP XXXX:XXXX语句,跳转到自己的代码里。 ; m$ T/ r4 t, Q6 x# X1 U( @
步骤如下:
+ r) `& t) ]1 Z# m0 ] a9 |0 S1.取得Windows API入口,用GetProcAddress实现 2 r0 K! D* R# W- l8 B& x+ v
2.保存API入口的前五个字节,因为JMP是0xEA,地址是4个字节
, Z4 K r/ f! {( d3.写入跳转语句 . B8 m. i2 h( `
这步最复杂
& c4 b0 C5 A/ [3 hWindows的代码段本来是不可以写的,但是Microsoft给自己留了个后门。
1 ~1 a8 @6 H0 |! E: d有一个未公开函数是AllocCsToDsAlias, # }+ M% T. N* Z0 m! L* \7 Y
UINT WINAPI ALLOCCSTODSALIAS(UINT);
( A: q/ `1 Q/ z你可以取到这个函数的入口,把API的代码段的选择符(要是不知道什么是选择符,就先去学学保护模式编程吧)传给他,他会返回一个可写的数据段选择符。这个选择符用完要释放的。用新选择符和API入口的偏移量合成一个指针就可以写windows的代码段了。
" U! Y) F) M; p* P3 \这就是取词技术的最核心的东东,不止取词,连外挂中文平台全屏汉化都是使用的这种技术。现在知道为什么这么简单的几句话却很少知道了吧?因为太多的产品使用他,太多的公司靠他赚钱了。
/ E& [5 D! m9 w3 |这些公司和产品有:中文之星,四通利方,南极星,金山词霸,实达铭泰的东方快车,roboword,译典通,即时汉化专家等等等等。。。。还有至少20多家小公司。他们的具体实现虽然不同,但大致原理是相同的。
5 g! m! q) e) J. j/ Q* }: `我这些都是随手写的,也没有提纲之类的东西,以后如果有机会我会整理一下,大家先凑合着看吧!xixi... - J0 K& x6 f7 y3 n" W8 r7 k
?
9 `, I3 l: C; a8 s& g1 t! b主题 关于屏幕取词的讨论(三) , I" R, Q5 i* T. B0 [- S, d
作者 亦东
3 E$ `" d4 c. x! o5 q9 A& I5 v; i7 A& H
让大家久等,很抱歉,前些时候工作忙硬盘又坏了,太不幸了。 ) J, c- S+ W* B( b1 K, Z% h( A
这回来点真格的。
]$ s4 Y! C/ T8 Z咱们以截取TextOut为例。 5 e" f8 Y* q4 g9 j+ x& j
下面是代码: ; r4 j% c, X. w( x# N
//截取TextOut a4 C$ X- c! H
typedef UINT (WINAPI* ALLOCCSTODSALIAS)(UINT);
r( n0 b$ b8 y$ |0 a( d/ u6 }ALLOCCSTODSALIAS AllocCsToDsAlias;
0 v2 h5 a5 e$ A, A$ kBYTE Newvalue[5];//保存新的入口代码
) j3 [0 \9 Q' v F2 ^" Y+ VBYTE Oldvalue[5];//API原来的入口代码 . _/ b, e+ C' o0 Z, g1 \
unsigned char * Address=NULL;//可写的API入口地址 1 x; }! V- w& T6 M9 z
UINT DsSelector=NULL;//指向API入口的可写的选择符 & y. s& r2 x3 \
WORD OffSetEntry=NULL;//API的偏移量
2 L8 D; I, {& O# s0 {; XBOOL bHookAlready = FALSE; //是否挂钩子的标志
0 v t0 g3 j0 w& X$ o" C( ]% TBOOL InitHook()
t0 z( m& ]/ q. }{
2 z6 N+ `- R0 p/ jHMODULE hKernel,hGdi;
2 x$ ]; W/ l) Z8 a& n2 L; ahKernel = GetModuleHandle("Kernel"); 5 H3 f1 V# Q$ o& v- T. ?2 ]5 ^; I
if(hKernel==NULL)
; O* k& H. q9 U5 qreturn FALSE; 2 K+ t4 X% e( _4 [/ U& y- m0 i" a
AllocCsToDsAlias = (ALLOCCSTODSALIAS)GetProcAddress(hKernel,"AllocCsToDsAlias");//这是未公开的API所以要这样取地址 - Q( w% T& J0 \! w4 i
if(AllocCsToDsAlias==NULL) , e! x; X) k- h D4 R3 k5 I
return FALSE;
0 m/ I! S4 a3 e; IhGdi = GetModuleHandle("Gdi");
; b. [" [, H& r6 h* E3 Vif(hmGdi==NULL) 2 x$ s4 c7 I6 U
return FALSE; # [- w' N1 F2 P% c
FARPROC Entry = GetProcAddress(hGdi,"TextOut");
: ^8 y5 S& x* m1 S5 `8 b6 q, Sif(Entry==NULL) $ [) v% z- m8 S: W$ r9 T
return FALSE;
6 n7 q. |* ?2 K a: `' DOffSetEntry = (WORD)(FP_OFF(Entry));//取得API代码段的选择符
9 @$ m7 {# [2 N1 {) |7 h, L4 ZDsSelector = AllocCsToDsAlias(FP_SEG(Entry));//分配一个等同的可写的选择符 & y# B( l. O& o4 X; E
Address = (unsigned char*)MK_FP(DsSelector,OffSetEntry);//合成地址
. W- H. c* |* s, Y# g8 ANewvalue[0]=0xEA;
* @- t1 ]! Z+ x+ `* @* i& E*((DWORD*)(Newvalue+1)) = (DWORD)MyTextOut;
' q* j, D- n* p `Oldvalue[0]=Address[0]; " t6 S- C3 q, R" T2 E
*((DWORD*)(Oldvalue+1)) = *((DWORD*)(Address+1)); " `2 u0 b2 v* \
} ! L0 r9 V- v% u
BOOL ClearHook() 7 {- G! t- K% `
{
$ h ~8 Y' d/ Gif(bHookAlready)
! D! }; H8 j4 L E6 J: P5 fHookOff();
( ?+ ]( \& {: H: N; qFreeSelector(DsSelector);
3 ^' {& K& P; }8 S} # |7 p- `5 B4 O
BOOL HookOn() - Q; I; c* s3 Q/ G
{ + J6 V- t6 |9 v0 |" ?) F& V
if(!bHookAlready){
/ ^8 p7 S9 k2 [$ Qfor(int i=0;i<5;i++){
$ D+ h. w5 x+ uAddress=Newvalue;
+ q: q8 K1 t1 ~8 v w+ S0 H+ Y}
$ C m& G* ~! I0 b& P: E" q! z( x" AbHookAlready=TRUE;
2 |. [; H5 Y' T7 C* U} . A b5 v* z9 H% ]4 J
} ' m/ _, }7 R5 @4 z7 |8 y
BOOL HookOff()
& X: q: h/ C$ _4 r |3 y5 O8 v3 [{ 8 `/ @1 a0 L& \" _4 i
if(bHookAlready){
5 m% r9 E4 y$ E/ X3 lfor(int i=0;i<5;i++){
( A* n- o1 F( z0 A& q+ I% hAddress=Oldvalue;
9 m5 m, W$ `" r- q4 _}
2 P+ z/ W p7 v! Y+ BbHookAlready=FALSE; m: B6 \& i2 }: ]% Y8 T# p- H& M- q
}
: ?! V5 X' D0 h7 Y}
+ `7 h( \, d6 g* n% U8 W//钩子函数,一定要和API有相同的参数和声明
0 ?) w" J4 h) i3 f2 U4 vBOOL WINAPI MyTextOut(HDC hdc,int nXStart,int nYStart,LPCSTR lpszString,UINT cbString) / Z7 L! T# x F& q
{
9 Y, w/ v5 w8 c0 ^+ QBOOL ret;
! I f/ ]% Y9 P3 D$ P. ]/ w; A! p, oHookOff(); , {5 }8 S9 c! ^3 W5 G* [- O: ~
ret = TextOut(hdc,nXStart,nYStart,lpszString,cbString);//调原来的TextOut
8 [7 D+ I$ g9 @9 U) c3 \& c+ [HookOn();
9 l0 s( a$ I6 |) m( H' [% \- m# ~return ret; 8 K; U! M( Z7 `* u3 W. y6 x/ d+ ~
}
) t* [3 D- \: n, [上面的代码是一个最简单的挂API钩子的例子,我要提醒大家的是,这段代码是我凭记忆写的,我以前的代码丢了,我没有编译测试过
- x7 e& u' P% i# e: N因为我没有VC++1.52.所以代码可能会有错。 $ i( u* I) S$ L1 _; X
建议使用Borland c++,按16位编译。
5 c' i2 r a W% }$ B如果用VC++1.52,则要改个选项 8 s! E3 g" T, y) R' Y
在VC++1.52的Option里,有个内存模式的设置,选大模式,和"DS!=SS DS Load on Function entry.",切记,否则会系统崩溃。 % R+ ^5 B7 c. N E8 N5 `
有什么不明白的可以给我写信
0 J$ D2 j$ Q+ s[email protected] / h) }: W4 M3 z5 I- v
Wednesday, June 23, 2004 7:11 PM 9 {9 H6 `% E' A" i- o% G$ M
8 @) ?0 X( W1 [3 d$ U3 Z+ C+ M9 ~1 \
|