对于一些调用Windows标准API来进行文本输出的AVG游戏,也许可以借鉴金山词霸之类的屏幕取词技术,HOOK API 之后,在自己的函数里把截获的英文替换为中文,然后输出到屏幕上来实现汉化,而且,这样的工具一旦制作完成,对于这类的AVG游戏将是通用的。
9 F% t9 U. Y% g2 X, f
+ K* A; y; |5 l" o, g* W+ S# B* n+ @: |
* y+ J& _) F0 t4 J V. z" [$ P金山词霸”屏幕取词技术揭密(讨论稿) 8 _5 z5 F2 [1 B9 ]) D6 I
这篇文章最早是发在北极星论坛的一系列帖子,那时候闻怡洋(好像他也是MVP)也在那里混 # Z' m* ~# j& Y5 i
原始的帖子我已经没有了,但不知道是谁帮我收集整理了下来(非常感谢),我用google找到了
& \" G, S u" V( F9 ~?
% U1 H4 `& L- d' O+ [/ [这是我进金山之前写的,应该不算泄露公司技术秘密吧 % W/ \, m m! R
而且这些现在看来似乎已经有些过时了 2 U, Z% ?9 a, G# b
? % l0 P: A1 O. O/ z2 Z& f. e: @
那时讨论的只是Win31和Win9x下的取词实现 o! k. K O' \( ~- D
? / j, T3 u5 J: ]5 S% I
我到了金山之后不是负责取词模块,而是做UI,因为有个家伙比我更擅长做这种东西 ) s9 x: t7 R2 A& m3 K
他用SoftIce调试汇编代码非常熟练,做逆向工程方面有过人的天分。
& G( W2 H8 V! {? . `! I) f: f6 b% u' @* ?
?
4 l5 B; L6 Q1 `5 {) n* y“亦东” 是我那时的笔名 . \+ j3 ^' s( Y
?
' P' x$ i3 t$ T. \0 ?& k" T/ |7 T?
8 p% h/ D" U- @1 Q y“金山词霸”屏幕取词技术揭密(讨论稿) , `( H ~& ~% ]; w/ b/ @9 P7 r
?
, _% v# X: W( i! b4 M主题 屏幕取词技术系列讲座(一)
/ c1 ?+ V* v0 G# ^作者 亦东
+ |) I5 I" v2 y' Q% i1 ^7 Q6 o7 Z很多人对这个问题感兴趣。 + m& Z0 @6 A5 F
原因是这项技术让人感觉很神奇,也很有商业价值。 2 z. m2 k( F9 |1 I
现在词典市场金山词霸占了绝对优势,所以再做字典也没什么前途了。我就是这么认为的,所以我虽然掌握了这项技术,却没去做字典软件。只做了一个和词霸相似的软件自己用,本来想拿出来做共享软件,但我的词库是“偷”来的,而且词汇不多,所以也就算了,词库太小,只能取词有什么用呢?而且词霸有共享版的。
* U$ a% s' ~% q0 e0 @但既然很多人想了解这项技术,我也不会保留。我准备分多次讲述这项技术的所有细节。 , A! R0 N. X D; G2 a
大约每周一两次。想知道的人就常常来看看吧! ! q' t; G0 v7 V
一.基础知识
2 }4 f+ M j4 B+ ?* a8 S* Z3 q首先想编这种程序需要一些基础知识。
+ c8 @; F! p6 H* Y0 r6 R会用Vc++,包括16/32位。 5 h/ w2 n% L7 z# v# ^# n
精通Windows API特别是GDI,KERNEL部分。 1 u: I3 p* Z& s3 a# ^
懂汇编语言,会用softice调试程序,因为这种程序最好用softice调试。 # ^ }6 }3 \7 G; k' U5 s" b6 {
二.基本原理
2 C- |& E# h; M在Window 3.x时代,windows系统提供的字符输出函数只有很少的几个。
! h5 q4 H8 `1 a7 U) d8 S5 MTextOut " O, Y: S4 W5 R
ExtTextOut 5 |; L% u# m4 r7 D6 z2 z! J& _* ]
DrawText ! m0 `1 H K( p) w+ d0 R
...... - {+ T( L7 d1 s: g/ _7 x; p9 l4 _- G( `
其中DrawText最终是用ExtTextOut实现的。 8 F3 |8 d3 I2 B( i
所以Windows的所有字符输出都是由调用TextOut和ExtTextOut实现的。因此,如果你可以修改这两个函数的入口,让程序先调用你自己的一个函数再调用系统的字符输出,你就可以得到Windows所有输出的字符了。
' ?3 ~' c; v+ V$ l1 G$ o到了Windows95时代,原理基本没变,但是95比3.x要复杂。开始的时候,一些在windows3.x下编写的取词软件仍然可以是使用。但是后来出了个IE4,结果很多词典软件就因为不支持IE4而被淘汰了,但同时也给一些软件创造了机会,如金山词霸。其实IE4的问题并不复杂,只不过它的输出的是unicode字符,是用TextOutW和ExtTextOutW输出的。知道了这一点,只要也截取就可以了。不过实现方法复杂一点,以后会有详细讲解。现在又出了个IE5,结果词霸也不好用了,微软真是#^@#$%$*&^&#@#@..........
5 N/ d( w* R. U2 L) `3 P7 E我研究后找到了一种解决办法,但还有些问题,有时会取错,正在继续研究,希望大家共同探讨。 ; o- p; x. T z* k+ L
另外还有WindowsNT,原理也是一样,只是实现方法和95下完全不同。
+ }' m6 ?" b# v7 b2 n0 L三.技术要点 / V- E0 s$ L: u
要实现取词,主要要解决以下技术问题。
3 f) k- x- [' q: g1.截取API入口,获得API的参数。 3 g2 p/ d' _6 D' O$ P
2.安全地潜入Windows内部,良好地兼容Windows的各个版本
4 V1 e: f( W! G% {) j0 {3 p! i3.计算鼠标所在的单词和字母。
$ Q+ ^6 G; E2 N& H6 J( P! w0 @4.如果你在Window95下,做32位程序,还涉及Windows32/16混合编程的技术。
7 {& W7 k) d$ D0 n5 g今天先到这里吧!最好准备一份softice for 95/98和金山词霸,让我们先来分析一下别人是怎么做的。 4 M6 m) g7 S% ]# D7 v' {& O: {
欢迎与我联系
5 X; K: Z3 Q+ B: h8 `) a7 `E-Mail:[email protected] ! R& U9 ~0 e4 i
主题 屏幕取词技术系列讲座(二)
8 |. f) q5 v' f. |/ B2 J1 N, F作者 亦东 8 B1 C1 D, w* V/ A& e
很抱歉让大家久等了! t& f+ d7 C7 V
我看了一些人的回帖,发现很多人对取词的原理还是不太清楚。 ; N8 q0 g z! V. X$ `* {- B
首先我来解释一下hook问题。词霸中的确用到了hook,而且他用了两种hook其中一种是Windows标准hook,通过SetWindowHook安装一个回调函数,它安装了一个鼠标hook,是为了可以及时响应鼠标的消息用的和取词没太大关系。 0 u: z. T Y" A, E1 p# G
另一种钩子是API钩子,这才是取词的核心技术所在。他在TextOut等函数的开头写了一个jmp语句,跳转到自己的代码里。
! u/ |# G# Z( p) _( G; c2 ^. B5 y你用softice看不到这个跳转语句是因为它只在取词的一瞬间才存在,平时是没有的。 5 Y2 R0 F! W! b4 l: \5 {4 D. T
你可以在TextOut开头设一个读写断点
* r. c! n1 j" Lbpm textout
0 u+ M4 e; O' ]3 o5 b( m& _! J再取词,就会找到词霸用来写钩子的代码了。 ( _! F/ V6 b1 W, [6 T: c( i, e0 n
/**********************************
! C. d+ z9 z. a# P所以我在次强调,想学这种技术一定要懂汇编语言和熟练使用softice. ( q! ?9 }* |, ?9 O
**********************************/
2 G$ w0 ^8 y0 G& }$ T, M$ B至于从cjktl95中dump出来的未公开函数是和Windows32/16混合编程有关的,以后我会提到他们。
" k' I. G6 H/ r+ v. s" A我先来讲述取词的过程,
! g9 ~ j/ K: A6 m8 y4 M0 判断鼠标是否在一个地方停留了一段时间 5 B4 W7 O! ^- p' `- e5 d
1 取得鼠标当前位置
; Q1 P/ c- H) A2 以鼠标位置为中心生成一个矩形 # w) ~' g1 b6 g; ?, d% W( L
3 挂上API钩子
! l' N; [- a- q* e. x7 f; a4 让这个矩形产生重画消息
9 H+ w4 s& M1 e6 }& ]5 A- V4 m. U5 h5 在钩子里等输出字符
S& b- @3 _* f* e8 I# @" I6 计算鼠标在哪个单词上面,把这个单词保存下来
3 Q8 H, d( _3 G! h0 n1 O7 如果得到单词则摘掉API钩子,在一段时间后,无论是否得到单词都摘掉API钩子 9 b" Y. e) N m1 I! C
8 用单词查词库,显示解释框。
6 f7 [( u7 I0 F/ t. m9 w很多步骤实现起来都有一些难度,所以在中国可以做一个完善的取词词典的人屈指可数。 ! ]9 N* t& E9 x) _+ Q9 U
其中0,1,2,7,8比较简单就不提了。 + P- j/ _; N% q t8 A" {$ \
先说如何挂钩子: 2 x) L# Z0 b& r- Q
所谓钩子其实就是在WindowsAPI入口写一个JMP XXXX:XXXX语句,跳转到自己的代码里。 D8 t4 M' k* d$ e0 m
步骤如下: 2 E9 b' n1 Q& C9 _
1.取得Windows API入口,用GetProcAddress实现 & p/ W7 M9 |* e1 l
2.保存API入口的前五个字节,因为JMP是0xEA,地址是4个字节
$ T" G; T% O3 c3.写入跳转语句 & _- A" X( a9 i/ [+ u
这步最复杂
) f e0 s! |, r/ DWindows的代码段本来是不可以写的,但是Microsoft给自己留了个后门。 $ X; R% {5 P; d' n# A
有一个未公开函数是AllocCsToDsAlias,
1 W1 ~6 a6 }2 I( b* l: GUINT WINAPI ALLOCCSTODSALIAS(UINT);
4 Z/ R1 ^: }/ C/ y3 S' D你可以取到这个函数的入口,把API的代码段的选择符(要是不知道什么是选择符,就先去学学保护模式编程吧)传给他,他会返回一个可写的数据段选择符。这个选择符用完要释放的。用新选择符和API入口的偏移量合成一个指针就可以写windows的代码段了。 & W0 P+ k. X+ s) q! x3 L
这就是取词技术的最核心的东东,不止取词,连外挂中文平台全屏汉化都是使用的这种技术。现在知道为什么这么简单的几句话却很少知道了吧?因为太多的产品使用他,太多的公司靠他赚钱了。
% a* y, E; N& _+ s这些公司和产品有:中文之星,四通利方,南极星,金山词霸,实达铭泰的东方快车,roboword,译典通,即时汉化专家等等等等。。。。还有至少20多家小公司。他们的具体实现虽然不同,但大致原理是相同的。 - A' @ O. O* J2 I0 e! A. d
我这些都是随手写的,也没有提纲之类的东西,以后如果有机会我会整理一下,大家先凑合着看吧!xixi... , Y$ x2 X& G, D+ n+ _' Z/ u
?
7 ?# ~- D4 y/ y2 U2 G主题 关于屏幕取词的讨论(三) 4 @: i! t& D7 S+ o- {
作者 亦东 ( V0 X3 [8 d x0 o6 ~- ` L! w; j& m- ~
! A4 d9 y) m9 j
让大家久等,很抱歉,前些时候工作忙硬盘又坏了,太不幸了。 # e& m" B" d4 e2 U
这回来点真格的。
4 D6 D# H% E" c& ~咱们以截取TextOut为例。
, O P6 }' _" I8 a7 o, t下面是代码:
! ~% M, K4 I/ W7 j//截取TextOut
2 i% |) J$ ~2 C/ a7 ]% a+ d$ t/ Ztypedef UINT (WINAPI* ALLOCCSTODSALIAS)(UINT); 1 |) e) c# Z' e! A0 X/ Q
ALLOCCSTODSALIAS AllocCsToDsAlias; - w1 B- j0 Y( T0 |, Z y* E2 i
BYTE Newvalue[5];//保存新的入口代码 " S4 E! i! b/ W7 Y2 |! y
BYTE Oldvalue[5];//API原来的入口代码 7 |% W/ b7 }5 S8 ~
unsigned char * Address=NULL;//可写的API入口地址 % x$ F; d p0 L$ Y4 ?% j
UINT DsSelector=NULL;//指向API入口的可写的选择符 / K+ X& T# _! B% ]* v0 G1 ?0 c
WORD OffSetEntry=NULL;//API的偏移量
1 q9 z2 h) {2 ^! e; O% }% rBOOL bHookAlready = FALSE; //是否挂钩子的标志
; ]* a( i1 h' X* D9 R1 x0 k$ pBOOL InitHook()
! Y7 q. u* g; j: @+ U{ 9 Z( F' g) x7 Z5 d% R, C/ a* s
HMODULE hKernel,hGdi; 0 e9 Z3 T$ W+ R; \. T; s7 R
hKernel = GetModuleHandle("Kernel"); ! o; s1 [1 D, U7 B" T
if(hKernel==NULL)
& E$ N2 z6 w) v; f6 creturn FALSE;
; a( Z7 Q1 S2 _. m9 j( f: AAllocCsToDsAlias = (ALLOCCSTODSALIAS)GetProcAddress(hKernel,"AllocCsToDsAlias");//这是未公开的API所以要这样取地址
7 ]! b6 j$ B% z e k1 iif(AllocCsToDsAlias==NULL)
U* W6 P4 r& a% _) m; treturn FALSE; / V" s3 i* X4 C u3 `" }
hGdi = GetModuleHandle("Gdi"); t# g* o. ]- P) p* A
if(hmGdi==NULL) + m3 L8 C1 t9 t' |
return FALSE; 7 |: W% N( {. ?+ D) t
FARPROC Entry = GetProcAddress(hGdi,"TextOut"); ( ?/ p5 a' Q. \& c& J
if(Entry==NULL) : X/ `; i2 e8 W8 t4 J' Z: x
return FALSE;
! }% b: |9 _6 t. T+ ], y' j! ?OffSetEntry = (WORD)(FP_OFF(Entry));//取得API代码段的选择符
3 Q& B6 K. \- m' ]DsSelector = AllocCsToDsAlias(FP_SEG(Entry));//分配一个等同的可写的选择符
. M9 n7 J( f/ k" V. bAddress = (unsigned char*)MK_FP(DsSelector,OffSetEntry);//合成地址 2 ?# R/ [/ m, a& ^
Newvalue[0]=0xEA;
% W, U. Y4 O, u. S3 X! s8 F" J; ~*((DWORD*)(Newvalue+1)) = (DWORD)MyTextOut;
1 |9 S( \. i$ p5 _: ~% _Oldvalue[0]=Address[0]; 8 B3 d* ^7 m- y( C& j' h. T& t
*((DWORD*)(Oldvalue+1)) = *((DWORD*)(Address+1)); 8 V0 `8 S% H1 O4 ^+ S
} 8 r! V2 ^4 w6 O: e: V0 I6 h
BOOL ClearHook()
$ I7 g: S- C5 d+ O{ 9 G7 ~' C2 ~. \# [; G
if(bHookAlready) ( L5 M* B. G, f- g9 f" u5 E- A6 u
HookOff(); 1 A. y9 r0 e4 W) g; a
FreeSelector(DsSelector); + ]+ p. X! o1 r. n) W! V% @7 M
} ?$ s( i, N d9 O! ?
BOOL HookOn() ; g4 c* R, d B; R+ D
{ + e, ?6 B. |. L# R8 {" d; W
if(!bHookAlready){
1 @7 h- _' Q: Wfor(int i=0;i<5;i++){
- ^( J- p/ O6 ?+ N6 R8 j7 ZAddress=Newvalue;
8 t; u7 l6 A6 e3 t- t0 }}
8 N/ P1 A9 X# _bHookAlready=TRUE; % l0 j3 ~& h4 A4 d+ n* @9 ^
}
8 {: L$ H; [# y; A0 @+ W} ; E4 l7 W; n4 z% x. V+ e
BOOL HookOff()
' z" A1 z: f" `% ~{
! p; P, B3 X7 hif(bHookAlready){
6 M4 i3 O A6 u* A0 W0 R3 R! V# W6 x. Kfor(int i=0;i<5;i++){
# D, a" y( ] Y- {5 r! @6 k/ PAddress=Oldvalue; 2 O) }/ Y2 H8 o, P0 J
} ; T1 V% P1 j9 O3 E. H. S
bHookAlready=FALSE;
( L8 E( I5 P" ?) n' W2 m. N# @} ' u8 L' j3 H/ `4 ~6 U9 A$ j: n. W
} & o) M7 g8 x; e8 y( l
//钩子函数,一定要和API有相同的参数和声明 8 P$ s0 ~; \7 }- K: g% x$ D
BOOL WINAPI MyTextOut(HDC hdc,int nXStart,int nYStart,LPCSTR lpszString,UINT cbString) + N0 W t% ~6 c: N0 Y4 b2 E
{ ' z. R C( `4 w7 ?# w
BOOL ret; - S0 C* h9 P9 z( o {1 S
HookOff();
1 W* e3 a w% Nret = TextOut(hdc,nXStart,nYStart,lpszString,cbString);//调原来的TextOut ! E% Z% C0 _8 W- H; g9 N0 s
HookOn();
! K3 M. L" d& Q. s2 l% P; c) p' Kreturn ret;
9 }2 v' G1 U2 T$ ~}
0 q/ D9 E. Q8 E8 h上面的代码是一个最简单的挂API钩子的例子,我要提醒大家的是,这段代码是我凭记忆写的,我以前的代码丢了,我没有编译测试过
3 U9 |0 e5 a* J* {因为我没有VC++1.52.所以代码可能会有错。 2 o* A" K4 \7 w$ `3 P
建议使用Borland c++,按16位编译。
6 B* e" ^' q) J- M d如果用VC++1.52,则要改个选项 ( c7 z% o1 Y$ V5 I; Q
在VC++1.52的Option里,有个内存模式的设置,选大模式,和"DS!=SS DS Load on Function entry.",切记,否则会系统崩溃。
! s7 E% O1 N* o% ~0 ]: p4 n有什么不明白的可以给我写信 + D/ U% m; B4 {& Q+ E# u# V
[email protected] % n% s4 \5 x* a' ]: I
Wednesday, June 23, 2004 7:11 PM ! A8 L' ~2 J5 t. F6 b t: m$ V4 b
, J' `, C0 J0 S+ c# _% d9 ~% X" r
|