对于一些调用Windows标准API来进行文本输出的AVG游戏,也许可以借鉴金山词霸之类的屏幕取词技术,HOOK API 之后,在自己的函数里把截获的英文替换为中文,然后输出到屏幕上来实现汉化,而且,这样的工具一旦制作完成,对于这类的AVG游戏将是通用的。
9 ~5 ]& c& X' o, V6 P( D. F" |0 T/ {/ C w0 T5 t
* E& a( l$ |: h
金山词霸”屏幕取词技术揭密(讨论稿)
% K0 k8 }$ n- V% Y) s这篇文章最早是发在北极星论坛的一系列帖子,那时候闻怡洋(好像他也是MVP)也在那里混 $ K3 `! p+ x# j I
原始的帖子我已经没有了,但不知道是谁帮我收集整理了下来(非常感谢),我用google找到了
" \, ^* V9 g# K. |" N& [2 A?
+ v; D1 ?$ ]5 }! c3 l2 |0 P这是我进金山之前写的,应该不算泄露公司技术秘密吧
* y7 z+ X8 h5 E( ^* c而且这些现在看来似乎已经有些过时了
+ e2 i) o1 Q- ^4 y' U?
* s- d+ N. Y. ^6 x3 _那时讨论的只是Win31和Win9x下的取词实现 0 j+ M, u9 F4 l& j" P% B! P
?
8 O7 _1 v' z; X7 ?# f0 E$ Y我到了金山之后不是负责取词模块,而是做UI,因为有个家伙比我更擅长做这种东西
0 ^1 [& B, z" l8 I' T! H1 S他用SoftIce调试汇编代码非常熟练,做逆向工程方面有过人的天分。
2 T3 W8 O5 A. U2 B' _?
# h* z$ d+ y( s( _9 Q- @7 v* A, ? c? 3 @; C+ j7 V) W# r
“亦东” 是我那时的笔名 . l! h0 D) ^, ^" s1 H
? $ C2 }/ ?* L$ @& k) \
? * ?1 S1 O8 J$ D- ?
“金山词霸”屏幕取词技术揭密(讨论稿) 1 T, A4 T2 ]6 i% u! c
?
' r9 R! h" p' r主题 屏幕取词技术系列讲座(一) 8 W% J1 l. v2 }, S! c
作者 亦东 ' g2 z' `5 V7 b2 L5 g" H+ A
很多人对这个问题感兴趣。
( T, C R, }; U; C! H, u! @; G# W- p原因是这项技术让人感觉很神奇,也很有商业价值。
% J7 V8 P; w, ?9 U现在词典市场金山词霸占了绝对优势,所以再做字典也没什么前途了。我就是这么认为的,所以我虽然掌握了这项技术,却没去做字典软件。只做了一个和词霸相似的软件自己用,本来想拿出来做共享软件,但我的词库是“偷”来的,而且词汇不多,所以也就算了,词库太小,只能取词有什么用呢?而且词霸有共享版的。 . @2 P h/ ^) P* C" K4 t
但既然很多人想了解这项技术,我也不会保留。我准备分多次讲述这项技术的所有细节。 : b* t4 g$ ?! k1 T" X% p
大约每周一两次。想知道的人就常常来看看吧!
0 Q2 v1 Q% G P. m$ C一.基础知识
9 L0 R S5 ^ E) Z5 C6 a- C' X首先想编这种程序需要一些基础知识。
3 L- |1 t/ @4 A8 b, ^, N) j+ d' U1 v会用Vc++,包括16/32位。
; R! _; a/ c ?2 k8 V9 A精通Windows API特别是GDI,KERNEL部分。
; |5 @; v9 i% \2 x/ |0 R0 [, L! v懂汇编语言,会用softice调试程序,因为这种程序最好用softice调试。 ' ]; I# p) d7 s3 W
二.基本原理 6 H3 ?6 I$ ~- H% \. t
在Window 3.x时代,windows系统提供的字符输出函数只有很少的几个。 5 ^5 w0 n' d1 u% d* p
TextOut ) v- U) X& p. ?$ A5 V+ b1 c
ExtTextOut 8 T1 D3 R w7 U; E8 o" a3 }
DrawText
# L8 P5 Q5 C+ w7 v3 m" K( K2 [$ _...... 1 w& U0 T- x9 A! F# I7 e5 d. C5 s0 V2 x
其中DrawText最终是用ExtTextOut实现的。 3 P* H7 [/ H, p! Q2 m3 b/ k1 u1 j7 G4 z
所以Windows的所有字符输出都是由调用TextOut和ExtTextOut实现的。因此,如果你可以修改这两个函数的入口,让程序先调用你自己的一个函数再调用系统的字符输出,你就可以得到Windows所有输出的字符了。
) | S' @- W6 _0 }: W, N$ ~+ T到了Windows95时代,原理基本没变,但是95比3.x要复杂。开始的时候,一些在windows3.x下编写的取词软件仍然可以是使用。但是后来出了个IE4,结果很多词典软件就因为不支持IE4而被淘汰了,但同时也给一些软件创造了机会,如金山词霸。其实IE4的问题并不复杂,只不过它的输出的是unicode字符,是用TextOutW和ExtTextOutW输出的。知道了这一点,只要也截取就可以了。不过实现方法复杂一点,以后会有详细讲解。现在又出了个IE5,结果词霸也不好用了,微软真是#^@#$%$*&^&#@#@..........
? A7 T& C. b! I我研究后找到了一种解决办法,但还有些问题,有时会取错,正在继续研究,希望大家共同探讨。
O6 v( y4 O+ k/ Z另外还有WindowsNT,原理也是一样,只是实现方法和95下完全不同。
0 c7 m- j/ U" G7 G. r- U! w三.技术要点 . S J7 ?6 S m: v' x+ l
要实现取词,主要要解决以下技术问题。
% T9 ^) d: `* l2 y: L1.截取API入口,获得API的参数。 - A+ ^: o+ }9 B3 Q8 |! \- m
2.安全地潜入Windows内部,良好地兼容Windows的各个版本 ( V; T3 {/ K, u" N2 E
3.计算鼠标所在的单词和字母。 # o5 a$ T# z7 Z/ k% l# [
4.如果你在Window95下,做32位程序,还涉及Windows32/16混合编程的技术。
8 E, X, `% E$ ?6 L2 W今天先到这里吧!最好准备一份softice for 95/98和金山词霸,让我们先来分析一下别人是怎么做的。 $ f" K$ Z! f* k/ \% y
欢迎与我联系
: A3 p6 K8 r* F: qE-Mail:[email protected]
: l) M j: Y- i- l9 Z' }主题 屏幕取词技术系列讲座(二)
: K* i& v! w, @1 I. b作者 亦东
0 c g% m0 y. n很抱歉让大家久等了! 9 E# V+ b1 F& e/ D7 D* S: r! k
我看了一些人的回帖,发现很多人对取词的原理还是不太清楚。 ) ^8 H7 m G: M
首先我来解释一下hook问题。词霸中的确用到了hook,而且他用了两种hook其中一种是Windows标准hook,通过SetWindowHook安装一个回调函数,它安装了一个鼠标hook,是为了可以及时响应鼠标的消息用的和取词没太大关系。 ; }. H, n2 Q, A7 V% L- l
另一种钩子是API钩子,这才是取词的核心技术所在。他在TextOut等函数的开头写了一个jmp语句,跳转到自己的代码里。 $ O3 T5 C j; Y7 D8 x/ G# K0 K
你用softice看不到这个跳转语句是因为它只在取词的一瞬间才存在,平时是没有的。
0 e+ ~) b4 {1 v/ z' _6 m你可以在TextOut开头设一个读写断点
) M# m2 N# ~# T! B: Tbpm textout
9 [% z) S' T- a1 c- V& M+ J再取词,就会找到词霸用来写钩子的代码了。
w9 v! { ^' ~6 J0 p. g8 i/********************************** & y4 o3 m4 ^& ^8 l ^* p4 B
所以我在次强调,想学这种技术一定要懂汇编语言和熟练使用softice. 5 M0 Q7 }# f) k& R U9 O. F4 A
**********************************/
) `$ A4 Y* E1 p' v. I1 _* I1 o至于从cjktl95中dump出来的未公开函数是和Windows32/16混合编程有关的,以后我会提到他们。
; X& K4 j* R" k- i0 Q0 ?3 |我先来讲述取词的过程,
4 {3 ], e2 X/ V- T. B4 v0 判断鼠标是否在一个地方停留了一段时间 : ]# @+ G4 p$ D. Y+ F
1 取得鼠标当前位置
# g8 {! o, r/ b) z- C( u3 Y2 以鼠标位置为中心生成一个矩形
4 Y0 M; G4 o- y& _& a3 挂上API钩子 " S$ x& ~0 D" ^* X! T( h
4 让这个矩形产生重画消息 1 \1 J+ r+ N4 x: q6 B: d5 ^
5 在钩子里等输出字符
3 t" G+ Y t5 m. k6 B% \6 计算鼠标在哪个单词上面,把这个单词保存下来 " V! W6 A+ J8 G
7 如果得到单词则摘掉API钩子,在一段时间后,无论是否得到单词都摘掉API钩子 ) o. Q S9 e" J
8 用单词查词库,显示解释框。 7 r. W: S# k# T; P ~/ B5 F/ H
很多步骤实现起来都有一些难度,所以在中国可以做一个完善的取词词典的人屈指可数。 % k: H) z7 @3 m/ i2 C& b; E
其中0,1,2,7,8比较简单就不提了。
1 ]# w$ W" n7 S0 C: @% Y4 ^先说如何挂钩子: " w7 R% Z) m+ {2 h
所谓钩子其实就是在WindowsAPI入口写一个JMP XXXX:XXXX语句,跳转到自己的代码里。
~& N) r6 w; B/ N% l步骤如下:
% K8 Z' g8 F: c! m2 p) N, B1.取得Windows API入口,用GetProcAddress实现 : t' x" Q6 m" c) b- ~- p! S) }
2.保存API入口的前五个字节,因为JMP是0xEA,地址是4个字节
+ { B& v9 Y$ A q# h: Z( N3.写入跳转语句 5 S% k+ H; c; K4 C! @ h
这步最复杂 3 M6 @6 j' @2 [" c" M2 J5 d/ i" E( ?
Windows的代码段本来是不可以写的,但是Microsoft给自己留了个后门。
* u' h% e6 Q% I# K+ i- v' b有一个未公开函数是AllocCsToDsAlias,
8 S8 N5 X0 [! u' t, M0 Q2 `% QUINT WINAPI ALLOCCSTODSALIAS(UINT); $ J5 h, @) L9 ~& A/ p5 e# D; R- P* ?
你可以取到这个函数的入口,把API的代码段的选择符(要是不知道什么是选择符,就先去学学保护模式编程吧)传给他,他会返回一个可写的数据段选择符。这个选择符用完要释放的。用新选择符和API入口的偏移量合成一个指针就可以写windows的代码段了。 & ?$ c5 e* O6 o- T" x3 C
这就是取词技术的最核心的东东,不止取词,连外挂中文平台全屏汉化都是使用的这种技术。现在知道为什么这么简单的几句话却很少知道了吧?因为太多的产品使用他,太多的公司靠他赚钱了。 5 R4 p6 Q: f% T! w% H
这些公司和产品有:中文之星,四通利方,南极星,金山词霸,实达铭泰的东方快车,roboword,译典通,即时汉化专家等等等等。。。。还有至少20多家小公司。他们的具体实现虽然不同,但大致原理是相同的。 / I" J1 p9 a6 T* x6 \/ g" Q8 V
我这些都是随手写的,也没有提纲之类的东西,以后如果有机会我会整理一下,大家先凑合着看吧!xixi...
A: T: H6 ~) N% z7 i( r# H# |0 R?
) K5 E6 `6 i# f1 t主题 关于屏幕取词的讨论(三) + z8 W% F' n2 }+ K W. U
作者 亦东 - i+ E' G: z F& y
3 ]$ |0 T5 M( L+ V6 Y5 s
让大家久等,很抱歉,前些时候工作忙硬盘又坏了,太不幸了。
2 C; l" d3 f- r' M这回来点真格的。
, Q p& j9 w" _咱们以截取TextOut为例。 ! Y5 b9 w2 k" z' V, F8 S
下面是代码: " E+ T& p2 k* A. D) V
//截取TextOut % b- ?& @6 ^# l/ W: L
typedef UINT (WINAPI* ALLOCCSTODSALIAS)(UINT);
' [+ N8 q% [9 ^5 b# n: ?' pALLOCCSTODSALIAS AllocCsToDsAlias; ! p$ g" B4 K/ R2 r1 ^8 E
BYTE Newvalue[5];//保存新的入口代码
( E/ G" ~3 P/ K9 RBYTE Oldvalue[5];//API原来的入口代码 4 K6 x4 e. g5 d3 ~. V6 U& `
unsigned char * Address=NULL;//可写的API入口地址 % X/ i9 a J, ?# f6 `! z
UINT DsSelector=NULL;//指向API入口的可写的选择符 # R. _2 Z# f& Q" @4 O/ Q# t- K
WORD OffSetEntry=NULL;//API的偏移量 : c2 T3 z+ \: e, s
BOOL bHookAlready = FALSE; //是否挂钩子的标志
+ M, b" `/ _! yBOOL InitHook() 1 X& p7 d/ a& T2 K
{
5 \. n b4 \0 ~, h0 c+ ]1 MHMODULE hKernel,hGdi; ; ?. s- S* F% Y" k& v$ C/ r
hKernel = GetModuleHandle("Kernel"); 5 u u' v* i: A3 A
if(hKernel==NULL) ( U% g% | a7 t2 u. G! B
return FALSE; & i# J0 b+ f) ]) V5 ]4 s
AllocCsToDsAlias = (ALLOCCSTODSALIAS)GetProcAddress(hKernel,"AllocCsToDsAlias");//这是未公开的API所以要这样取地址 - R t7 A/ s8 d. ^% \! ^! D
if(AllocCsToDsAlias==NULL)
0 @- `# j" j; [+ nreturn FALSE;
% o$ A; D& B2 MhGdi = GetModuleHandle("Gdi"); , Q! o, N% B! c- R9 p
if(hmGdi==NULL) 1 J7 u9 i, V7 v# |9 @+ Y% \
return FALSE; : A* k6 _: [" w5 K; J! F
FARPROC Entry = GetProcAddress(hGdi,"TextOut"); % w: U* a5 n- S! O0 j/ A3 n
if(Entry==NULL)
, N. t5 Q+ E8 M2 m' f- ureturn FALSE; 6 O. B: ~4 w$ F4 k: W0 E
OffSetEntry = (WORD)(FP_OFF(Entry));//取得API代码段的选择符 ' A8 i7 x; b/ b) g5 c, U0 [8 Y
DsSelector = AllocCsToDsAlias(FP_SEG(Entry));//分配一个等同的可写的选择符
$ m; c" J3 B0 v0 E: t7 R: q- NAddress = (unsigned char*)MK_FP(DsSelector,OffSetEntry);//合成地址 ; S6 O, f! B3 W" n- e; N
Newvalue[0]=0xEA;
8 {9 r B' R& H& g*((DWORD*)(Newvalue+1)) = (DWORD)MyTextOut; ; `4 c7 A* R% D" @& t
Oldvalue[0]=Address[0];
) U. ]9 ?/ D5 J4 C! j*((DWORD*)(Oldvalue+1)) = *((DWORD*)(Address+1));
1 h, p" P m5 q6 b} " z! _8 v0 v' J+ }" c
BOOL ClearHook() 1 c* |+ h4 O2 ?( c9 A
{ * A& P( j" R1 }0 G
if(bHookAlready)
) n( E/ i& X4 n [" I4 T1 rHookOff(); 4 K* O: D- W7 o* E2 b4 U1 p! v% e
FreeSelector(DsSelector);
! S# U7 w( r6 s) X$ v. T, E}
+ l; k+ ?, `: k2 V/ B) QBOOL HookOn() - Z9 e" P& S" f0 W( W
{ ) N5 C1 y: f2 F9 L$ o
if(!bHookAlready){ , z9 l5 i% y& `. I( n
for(int i=0;i<5;i++){
" [. L* D' P2 u$ M5 R( I$ A/ h' vAddress=Newvalue; 9 `. M- U2 K" d9 M W
} 4 L/ X& Z! |1 I
bHookAlready=TRUE; ' q& j+ P0 y7 N! x+ e* b/ k
}
7 w& |, q9 t( L" d, [* B3 A}
% c- q- `* q. H! ~& c& C$ U! SBOOL HookOff() , i, h5 x7 q: ^) j8 b# `
{
D5 y4 u9 v: b9 S. }6 kif(bHookAlready){
2 \0 Z. [1 F$ G! W$ Zfor(int i=0;i<5;i++){ & C% k) `; u9 g' v
Address=Oldvalue;
$ z x- o- D2 I/ z& ]} % Z' d G z. F: R' @
bHookAlready=FALSE; ' p) n! R9 v# D, `5 V& }+ C
} , D0 _9 r/ u- L% U4 y
}
9 R8 {4 u& u% @* U% S+ w//钩子函数,一定要和API有相同的参数和声明 0 S' s( b' N- K% G+ `
BOOL WINAPI MyTextOut(HDC hdc,int nXStart,int nYStart,LPCSTR lpszString,UINT cbString)
8 T: I2 v- t6 G; m( a/ h{ 6 @, l+ @7 N b( |( s
BOOL ret; 8 P/ b6 v$ _' ?" s
HookOff();
6 V' u# o# C; D$ B) a* Q- w' Qret = TextOut(hdc,nXStart,nYStart,lpszString,cbString);//调原来的TextOut
, R5 b: J& R7 QHookOn(); $ {/ V( y0 S7 P9 E1 P
return ret;
! \0 A; K! d P7 s# A( ?} $ q v. L# L: e" _/ H' L. Z
上面的代码是一个最简单的挂API钩子的例子,我要提醒大家的是,这段代码是我凭记忆写的,我以前的代码丢了,我没有编译测试过
" \5 s+ l! s( l因为我没有VC++1.52.所以代码可能会有错。
$ \0 W5 | }' _+ e0 }" k- `建议使用Borland c++,按16位编译。 $ F; |% J5 L; U
如果用VC++1.52,则要改个选项
0 m: H( f6 e! R8 m7 L( q) Y; f/ u1 ]在VC++1.52的Option里,有个内存模式的设置,选大模式,和"DS!=SS DS Load on Function entry.",切记,否则会系统崩溃。
- F: p8 V) ?8 q' V" J+ S) b有什么不明白的可以给我写信
9 ]2 [' n4 q" r[email protected]
5 I0 R7 O( e" |0 v- }! I0 x3 aWednesday, June 23, 2004 7:11 PM % O( q. t6 E3 `6 Z1 s+ L& @# ?
9 h* b/ Y* k2 Q4 [4 |
|