对于一些调用Windows标准API来进行文本输出的AVG游戏,也许可以借鉴金山词霸之类的屏幕取词技术,HOOK API 之后,在自己的函数里把截获的英文替换为中文,然后输出到屏幕上来实现汉化,而且,这样的工具一旦制作完成,对于这类的AVG游戏将是通用的。
/ b1 S% c; h! V+ }$ x7 a# ]8 n+ L. v+ l7 u/ D( }' o
6 E5 s, ?" B) s% i& n金山词霸”屏幕取词技术揭密(讨论稿)
& ?9 ^$ X# [ W; x2 _这篇文章最早是发在北极星论坛的一系列帖子,那时候闻怡洋(好像他也是MVP)也在那里混
# [+ N7 }) L! H4 o( l+ {: I; J: \原始的帖子我已经没有了,但不知道是谁帮我收集整理了下来(非常感谢),我用google找到了 - ^ I* V6 p& a9 o# }/ T: v2 }1 [
?
+ ~- s* }) w& a% ?( x& b3 c1 p这是我进金山之前写的,应该不算泄露公司技术秘密吧 & u y; V+ M$ K9 K' `3 n2 i [& O
而且这些现在看来似乎已经有些过时了
8 R4 t( L$ }. J% w8 V) \?
0 a, r6 [5 J% n2 S( q( I+ x, d那时讨论的只是Win31和Win9x下的取词实现 3 c6 X/ l( v7 [$ X/ q
? ' O) i; l+ r0 b0 G D
我到了金山之后不是负责取词模块,而是做UI,因为有个家伙比我更擅长做这种东西 + @ G7 V. U i: P' N) t( l
他用SoftIce调试汇编代码非常熟练,做逆向工程方面有过人的天分。 ! ]/ s3 n, \! w$ p# D! i- L
? * E b6 |/ C8 V1 X; e9 y
?
4 K7 C/ Z3 v" ]6 r“亦东” 是我那时的笔名 , W# D9 q- t8 z" h; e! y
?
0 X; P! s" g& g) f R?
t9 D+ p Y5 ^. u5 R! s“金山词霸”屏幕取词技术揭密(讨论稿) ! k c" s+ T! L7 c# b, V5 z
? $ ], e, r6 F: W9 s1 ]
主题 屏幕取词技术系列讲座(一)
6 _; o4 ~: g! d8 m' c2 w作者 亦东
5 j# v* c5 h3 f( X( p4 A4 C# }很多人对这个问题感兴趣。
]9 E9 H, _2 C7 C6 l原因是这项技术让人感觉很神奇,也很有商业价值。
2 S" b# n& n) } C现在词典市场金山词霸占了绝对优势,所以再做字典也没什么前途了。我就是这么认为的,所以我虽然掌握了这项技术,却没去做字典软件。只做了一个和词霸相似的软件自己用,本来想拿出来做共享软件,但我的词库是“偷”来的,而且词汇不多,所以也就算了,词库太小,只能取词有什么用呢?而且词霸有共享版的。
) \6 A4 j; S" I4 A但既然很多人想了解这项技术,我也不会保留。我准备分多次讲述这项技术的所有细节。 ' \. f& h s9 }8 \" T' g
大约每周一两次。想知道的人就常常来看看吧!
9 _/ O5 U/ w, l, e( S; b0 ?( ?一.基础知识
) m- M& f$ ^) Z- C首先想编这种程序需要一些基础知识。
" g& J/ P# Z3 l0 q( y( W会用Vc++,包括16/32位。 . o) k: X. V+ h( Q% l) J& X
精通Windows API特别是GDI,KERNEL部分。
% {5 C& K9 i3 P; n2 J n$ A懂汇编语言,会用softice调试程序,因为这种程序最好用softice调试。
! r" v- e: |* ]; w+ K二.基本原理
. p& Y% \ z& i) x在Window 3.x时代,windows系统提供的字符输出函数只有很少的几个。
5 `, {& v& o5 [# y ]7 oTextOut
) f- }3 X7 ]3 bExtTextOut
2 h* j5 Q9 a7 i% A- W/ }DrawText 5 }: m: M0 v7 e/ g1 V3 z9 c q" f
...... 8 z' g8 A) ]. s& l
其中DrawText最终是用ExtTextOut实现的。 ! A/ ?* |6 ^1 S# T- m, q+ ~
所以Windows的所有字符输出都是由调用TextOut和ExtTextOut实现的。因此,如果你可以修改这两个函数的入口,让程序先调用你自己的一个函数再调用系统的字符输出,你就可以得到Windows所有输出的字符了。
& K7 E- r8 ^ f3 @0 D h; s) A4 B到了Windows95时代,原理基本没变,但是95比3.x要复杂。开始的时候,一些在windows3.x下编写的取词软件仍然可以是使用。但是后来出了个IE4,结果很多词典软件就因为不支持IE4而被淘汰了,但同时也给一些软件创造了机会,如金山词霸。其实IE4的问题并不复杂,只不过它的输出的是unicode字符,是用TextOutW和ExtTextOutW输出的。知道了这一点,只要也截取就可以了。不过实现方法复杂一点,以后会有详细讲解。现在又出了个IE5,结果词霸也不好用了,微软真是#^@#$%$*&^&#@#@..........
- J5 A) ~! P& q6 n# g3 w# P我研究后找到了一种解决办法,但还有些问题,有时会取错,正在继续研究,希望大家共同探讨。 - L! p2 Y- t4 R& C5 ~
另外还有WindowsNT,原理也是一样,只是实现方法和95下完全不同。
4 D7 c& f. ]' R三.技术要点
* v+ n! B8 p' K6 a# _9 i要实现取词,主要要解决以下技术问题。
* T, a, u E m/ E# v1.截取API入口,获得API的参数。
6 c" }, S5 q2 I( S( Z2.安全地潜入Windows内部,良好地兼容Windows的各个版本 / @) t( d& x+ h z, }
3.计算鼠标所在的单词和字母。 $ r& v3 t9 k: }# Z% ?5 o
4.如果你在Window95下,做32位程序,还涉及Windows32/16混合编程的技术。
- \! ^ E& j2 p今天先到这里吧!最好准备一份softice for 95/98和金山词霸,让我们先来分析一下别人是怎么做的。
- U# E7 y( ^& Y3 }9 l- E6 d欢迎与我联系 * r% P" J1 Q$ {
E-Mail:[email protected]
1 o$ ?1 ]1 ]9 b& r; r9 t6 t" r主题 屏幕取词技术系列讲座(二)
# C* C! w* _' _4 L作者 亦东 4 A: T0 H9 z0 v9 m# F# k+ a( e
很抱歉让大家久等了!
! x( a% O. l: s1 m4 ^" E我看了一些人的回帖,发现很多人对取词的原理还是不太清楚。
/ |# i% Q$ a1 t0 Y首先我来解释一下hook问题。词霸中的确用到了hook,而且他用了两种hook其中一种是Windows标准hook,通过SetWindowHook安装一个回调函数,它安装了一个鼠标hook,是为了可以及时响应鼠标的消息用的和取词没太大关系。
. R, |0 q' J( H4 ^! o另一种钩子是API钩子,这才是取词的核心技术所在。他在TextOut等函数的开头写了一个jmp语句,跳转到自己的代码里。 9 q2 M& w* C) e$ J, F" B! _
你用softice看不到这个跳转语句是因为它只在取词的一瞬间才存在,平时是没有的。
# }; X; ^: m7 f! u& r你可以在TextOut开头设一个读写断点 1 I) A5 R, f3 ~
bpm textout
: q% l8 y1 b) `再取词,就会找到词霸用来写钩子的代码了。 3 u1 _4 B8 m) t) x& s7 E3 r
/**********************************
8 k) g8 Z, R' `' K8 l: C0 U所以我在次强调,想学这种技术一定要懂汇编语言和熟练使用softice.
! [, `. R6 z7 I* }8 P**********************************/
; n7 y R& c( U* b. C$ B+ o+ j至于从cjktl95中dump出来的未公开函数是和Windows32/16混合编程有关的,以后我会提到他们。
- M0 L$ v* Q l, m9 B5 F U6 t$ X, _$ g$ j我先来讲述取词的过程,
2 a# m! N* t$ f- Z+ s0 判断鼠标是否在一个地方停留了一段时间 7 }2 ^$ V5 B( b9 O
1 取得鼠标当前位置
" J( R* e* T( Z$ R& M* ~2 以鼠标位置为中心生成一个矩形 ! M9 g8 E+ x G- s" ?
3 挂上API钩子 : ~+ n# g- ?$ w& b3 s( Q6 {
4 让这个矩形产生重画消息 T1 y0 h# ~ X6 m5 g6 h
5 在钩子里等输出字符
& g0 P& b" O. J; p/ n6 计算鼠标在哪个单词上面,把这个单词保存下来
3 O; j* G' ^6 z' J1 j" s: ^4 V7 如果得到单词则摘掉API钩子,在一段时间后,无论是否得到单词都摘掉API钩子 4 {( g* W2 `- M5 ` y5 b/ C. Q
8 用单词查词库,显示解释框。
7 j5 \8 Z5 ]# E9 I$ q% Y$ ]很多步骤实现起来都有一些难度,所以在中国可以做一个完善的取词词典的人屈指可数。
* a7 i; K' P" j# g1 ~8 J9 ^ w- ^: @其中0,1,2,7,8比较简单就不提了。 b c) E; ]5 \
先说如何挂钩子:
$ @- v G& D/ p, w x G0 y2 q所谓钩子其实就是在WindowsAPI入口写一个JMP XXXX:XXXX语句,跳转到自己的代码里。
/ t- f' z z7 [% I! D% X+ P步骤如下:
" K5 H Y# n5 X+ s$ @0 s+ d+ t1.取得Windows API入口,用GetProcAddress实现 , v+ v: {, a+ E! _
2.保存API入口的前五个字节,因为JMP是0xEA,地址是4个字节 % V: k* f; r) T* ]/ v. x+ K. I
3.写入跳转语句 ' a0 _& F+ t' d- C( g, L- f
这步最复杂
6 y, a2 O9 L+ N2 c8 Z8 v0 ~Windows的代码段本来是不可以写的,但是Microsoft给自己留了个后门。
+ I s, v7 n: |9 U8 V, S/ M8 w" q有一个未公开函数是AllocCsToDsAlias, 8 F% ]! O8 G: b
UINT WINAPI ALLOCCSTODSALIAS(UINT); 1 ?1 T+ l1 {- t. k. Q% q( F
你可以取到这个函数的入口,把API的代码段的选择符(要是不知道什么是选择符,就先去学学保护模式编程吧)传给他,他会返回一个可写的数据段选择符。这个选择符用完要释放的。用新选择符和API入口的偏移量合成一个指针就可以写windows的代码段了。
/ |( j Q* b' r) z) Q6 }% {9 H: C这就是取词技术的最核心的东东,不止取词,连外挂中文平台全屏汉化都是使用的这种技术。现在知道为什么这么简单的几句话却很少知道了吧?因为太多的产品使用他,太多的公司靠他赚钱了。
/ \" y7 B4 Z' _5 i这些公司和产品有:中文之星,四通利方,南极星,金山词霸,实达铭泰的东方快车,roboword,译典通,即时汉化专家等等等等。。。。还有至少20多家小公司。他们的具体实现虽然不同,但大致原理是相同的。 $ K4 E, ^2 s* X7 [- u U8 N
我这些都是随手写的,也没有提纲之类的东西,以后如果有机会我会整理一下,大家先凑合着看吧!xixi... 5 r) {5 }2 T/ J
?
& ?, A9 |5 |; [% o% W. v$ @$ V& u主题 关于屏幕取词的讨论(三) 1 n: q6 U; e# U' R: `
作者 亦东 1 ~) \* e8 m2 Y0 z- O: |3 Q) {
( V3 K! V" O! k1 [0 I让大家久等,很抱歉,前些时候工作忙硬盘又坏了,太不幸了。
; K$ U6 w( M3 d9 k这回来点真格的。
% t* `# s! z, N# ~( n咱们以截取TextOut为例。 8 h5 h. g+ F6 r6 v* e$ a
下面是代码:
. b; G2 p; F8 l& O+ h& b0 X//截取TextOut ' [) _% p+ ~% x5 }
typedef UINT (WINAPI* ALLOCCSTODSALIAS)(UINT);
4 C" D1 X b/ D) w9 G" UALLOCCSTODSALIAS AllocCsToDsAlias;
/ z7 v8 H6 l! v3 W9 iBYTE Newvalue[5];//保存新的入口代码
7 P5 x. ~. e. HBYTE Oldvalue[5];//API原来的入口代码 / R- E6 |6 u5 N1 V
unsigned char * Address=NULL;//可写的API入口地址 , b/ }- O4 L1 a1 X* ~; z( M" `
UINT DsSelector=NULL;//指向API入口的可写的选择符 8 h/ O3 Z) v& R' k8 h4 i+ f
WORD OffSetEntry=NULL;//API的偏移量
: f0 Z1 r! r0 p3 mBOOL bHookAlready = FALSE; //是否挂钩子的标志
1 x5 L7 V" U5 Z3 aBOOL InitHook() 3 c. t( @1 n d6 m L- f
{
* X, l( H+ O1 f4 CHMODULE hKernel,hGdi;
H: K Q: X, i+ K, VhKernel = GetModuleHandle("Kernel");
- S J8 _8 W. x1 a8 O7 Z1 Cif(hKernel==NULL)
. r; J$ r/ D: H; j/ Vreturn FALSE;
* X, ^0 ?7 e. @& h$ _" UAllocCsToDsAlias = (ALLOCCSTODSALIAS)GetProcAddress(hKernel,"AllocCsToDsAlias");//这是未公开的API所以要这样取地址 - ]0 I/ j9 a7 ]9 z
if(AllocCsToDsAlias==NULL)
# {# q& O; ?( I& M! H6 ]+ Zreturn FALSE; + m) \( K7 o- u& G7 Z
hGdi = GetModuleHandle("Gdi");
/ z; d7 }) x& m rif(hmGdi==NULL)
: ]. O! s4 q: m, ], p. greturn FALSE;
0 v, J2 D$ X! \ }2 f9 S; N. oFARPROC Entry = GetProcAddress(hGdi,"TextOut");
0 I* r7 W1 C) a) g5 Fif(Entry==NULL)
3 w' @0 r) |5 D" z9 |% Q' Treturn FALSE; ) \$ S/ N B# C. N$ v
OffSetEntry = (WORD)(FP_OFF(Entry));//取得API代码段的选择符 " `$ c& J$ Z& Q9 C9 p7 f8 b
DsSelector = AllocCsToDsAlias(FP_SEG(Entry));//分配一个等同的可写的选择符
# \/ Y: u I# |: L& }7 LAddress = (unsigned char*)MK_FP(DsSelector,OffSetEntry);//合成地址 + N% `6 A$ S- {7 i c
Newvalue[0]=0xEA; ( g0 ^8 z) r+ E! h: W
*((DWORD*)(Newvalue+1)) = (DWORD)MyTextOut;
9 \ h1 F% r- k: {7 i0 K) COldvalue[0]=Address[0]; 9 J( Q, j, ]; u
*((DWORD*)(Oldvalue+1)) = *((DWORD*)(Address+1)); ) P7 Y6 ] i: ~/ b, z; m6 h ]4 I% y7 x
} , k! c1 ?* Q6 ]
BOOL ClearHook()
1 }# C: s: s9 e7 V8 w- _- h/ j{ . {- ?. v, R& F0 @. a
if(bHookAlready) 7 w5 j$ m8 W: X" c! x$ m
HookOff(); : m; y1 A* q9 N, l/ e4 E
FreeSelector(DsSelector); ) ? O6 N1 F) j5 B: T; c3 y
} * x& z+ R/ B$ l4 V/ i/ U
BOOL HookOn()
0 b8 ?7 r' Y8 R* R, F9 I3 K$ s' N{ # L1 r7 R7 A4 a% r& k2 X# Z
if(!bHookAlready){ 2 p' r" o- u! U+ O; n! @" M) E
for(int i=0;i<5;i++){
- H3 |" s9 t. w* j! ]Address=Newvalue; - u: G- k" Z3 N* D F, y& [7 q. r
}
& @8 |% _% J- ?4 D# Q3 C+ FbHookAlready=TRUE;
6 `7 g3 X/ G4 c) s8 D% b}
; v* c5 \" `! e4 N} : a' M9 T7 j5 m% Y3 z) A4 \9 \$ k, B
BOOL HookOff()
& e9 \4 p3 [$ ^! W" R# Z& i: c{
' i6 G. x9 H V8 G6 }if(bHookAlready){
; g: Y9 W' a1 n! W( S# Z. D. mfor(int i=0;i<5;i++){
5 h' p+ A" A b8 K! g0 n0 uAddress=Oldvalue; 6 I: B5 O/ }0 C) F6 Z
}
2 ?2 k @# F$ P4 W# _& s: NbHookAlready=FALSE;
" j" @, {( O( \} " r7 X% g* H9 W. [& Q' h2 X
}
1 m) E8 x% T# A' w f' f) P//钩子函数,一定要和API有相同的参数和声明 6 s3 Y+ g+ h, B4 z
BOOL WINAPI MyTextOut(HDC hdc,int nXStart,int nYStart,LPCSTR lpszString,UINT cbString) * d! z$ A# J* T; }, M: y
{ " u3 `+ g+ P* U
BOOL ret;
6 n% S$ d* ]( h0 W/ EHookOff(); . l% C- l' I: G. f4 g) U
ret = TextOut(hdc,nXStart,nYStart,lpszString,cbString);//调原来的TextOut : Q7 ^# O. K2 Y- h5 L* K- f
HookOn(); + {) C( ?( m# V) q2 N& B, t
return ret; . _ X! H, X1 d0 z- l: Y' E
} % ~% M" ?% j' e7 R* l+ S" Z- N
上面的代码是一个最简单的挂API钩子的例子,我要提醒大家的是,这段代码是我凭记忆写的,我以前的代码丢了,我没有编译测试过
; D- B1 S9 ]# ]+ ]) k* U/ J* x2 z因为我没有VC++1.52.所以代码可能会有错。 2 [3 L4 b& s0 L
建议使用Borland c++,按16位编译。
4 G* e [* [6 Y7 g, p; O6 {如果用VC++1.52,则要改个选项
! e! r+ @5 x2 `' [2 T在VC++1.52的Option里,有个内存模式的设置,选大模式,和"DS!=SS DS Load on Function entry.",切记,否则会系统崩溃。 ) E) d& Z5 I& O" S
有什么不明白的可以给我写信 7 S2 e& g7 B6 b% r0 a2 U
[email protected] ' g8 q7 o Y7 O! W" X& X" g
Wednesday, June 23, 2004 7:11 PM
2 U: I) A; k5 M X- P) p+ Z Z; ~$ m9 ?5 ~: }$ A4 m* T
|