对于一些调用Windows标准API来进行文本输出的AVG游戏,也许可以借鉴金山词霸之类的屏幕取词技术,HOOK API 之后,在自己的函数里把截获的英文替换为中文,然后输出到屏幕上来实现汉化,而且,这样的工具一旦制作完成,对于这类的AVG游戏将是通用的。+ p2 j. \$ E2 t" E2 g0 `: a2 f
6 y. P$ p) g6 e- N8 t/ Z- r
# J8 {" @* y8 K, i: X) _! G# v$ j7 C
金山词霸”屏幕取词技术揭密(讨论稿) * G/ z: s; {. [7 {0 ]0 j
这篇文章最早是发在北极星论坛的一系列帖子,那时候闻怡洋(好像他也是MVP)也在那里混 5 r' O4 T' x! a) {
原始的帖子我已经没有了,但不知道是谁帮我收集整理了下来(非常感谢),我用google找到了
' ^, C) I6 V2 l0 i. J7 b?
' n9 N% k5 `# w. G3 [. n* L* \- j: k" U这是我进金山之前写的,应该不算泄露公司技术秘密吧
& F3 X; S* M8 M* B而且这些现在看来似乎已经有些过时了
7 m+ o: A: I! w* _+ R4 P& f9 d?
, E, o8 b+ x. W$ _# T% h( l那时讨论的只是Win31和Win9x下的取词实现 0 z+ R' q3 P7 t9 k/ l( l5 n7 r
?
& s2 u1 ]: i" u: r我到了金山之后不是负责取词模块,而是做UI,因为有个家伙比我更擅长做这种东西
~* ]9 L/ i0 T3 i4 B! Z他用SoftIce调试汇编代码非常熟练,做逆向工程方面有过人的天分。 8 ?! ^& _ A) X
? ' E( E* h- ^$ L( r7 f- @6 ?' G
? {6 B9 }- U5 o1 @7 ~+ |
“亦东” 是我那时的笔名 ) c3 u4 b' G' ~
? 3 J9 O [0 O9 r7 W% ]
? 3 M5 z7 j( j f
“金山词霸”屏幕取词技术揭密(讨论稿)
s& K1 z( U+ ~" X: }?
`) _0 ~5 u7 i' f- k( J主题 屏幕取词技术系列讲座(一) $ R5 B, m% ?6 w ]/ E
作者 亦东
6 ]/ N1 T* v9 [# l很多人对这个问题感兴趣。
* Z8 E. `8 n& O原因是这项技术让人感觉很神奇,也很有商业价值。
7 T9 E5 n" A. N现在词典市场金山词霸占了绝对优势,所以再做字典也没什么前途了。我就是这么认为的,所以我虽然掌握了这项技术,却没去做字典软件。只做了一个和词霸相似的软件自己用,本来想拿出来做共享软件,但我的词库是“偷”来的,而且词汇不多,所以也就算了,词库太小,只能取词有什么用呢?而且词霸有共享版的。 / i* z, g4 F. I0 g
但既然很多人想了解这项技术,我也不会保留。我准备分多次讲述这项技术的所有细节。
) C0 L X8 l( P9 z% ^大约每周一两次。想知道的人就常常来看看吧! & a6 ?) ?3 r: @* \7 E a
一.基础知识 4 s; q% o& B9 U
首先想编这种程序需要一些基础知识。 , Q# h3 l5 B4 v
会用Vc++,包括16/32位。
% a# {+ v# i5 |精通Windows API特别是GDI,KERNEL部分。
3 q: j' V5 M/ D7 f懂汇编语言,会用softice调试程序,因为这种程序最好用softice调试。
3 d' G. t# `( z! C6 I二.基本原理
/ r0 p# h: @, L1 i在Window 3.x时代,windows系统提供的字符输出函数只有很少的几个。 ; k& m; k4 h8 o! A
TextOut
+ Q4 P- o% o" }' L0 YExtTextOut . W& `: ?, J) h2 O8 N2 x4 m1 H9 N
DrawText
S7 b- V/ `; l9 y/ R# _( ]( s...... * ~- e, y" l; N/ [9 N8 c8 A
其中DrawText最终是用ExtTextOut实现的。 6 N, Q; G+ R: z* f: M
所以Windows的所有字符输出都是由调用TextOut和ExtTextOut实现的。因此,如果你可以修改这两个函数的入口,让程序先调用你自己的一个函数再调用系统的字符输出,你就可以得到Windows所有输出的字符了。 % Q, o. u y) g7 g6 x7 |
到了Windows95时代,原理基本没变,但是95比3.x要复杂。开始的时候,一些在windows3.x下编写的取词软件仍然可以是使用。但是后来出了个IE4,结果很多词典软件就因为不支持IE4而被淘汰了,但同时也给一些软件创造了机会,如金山词霸。其实IE4的问题并不复杂,只不过它的输出的是unicode字符,是用TextOutW和ExtTextOutW输出的。知道了这一点,只要也截取就可以了。不过实现方法复杂一点,以后会有详细讲解。现在又出了个IE5,结果词霸也不好用了,微软真是#^@#$%$*&^&#@#@.......... 0 @. k. R5 \ ~. @1 H' y S. r1 z
我研究后找到了一种解决办法,但还有些问题,有时会取错,正在继续研究,希望大家共同探讨。
' ^' w+ p" ^1 r& c6 \0 U另外还有WindowsNT,原理也是一样,只是实现方法和95下完全不同。
$ B9 u6 a; C2 ]5 [6 d三.技术要点
* t$ g# v! ~ D2 P- t! V# G要实现取词,主要要解决以下技术问题。
. d% t# F( X' k' g" M1 U/ Q7 P9 T/ c" R1.截取API入口,获得API的参数。
, r9 e7 {7 n- p3 T2.安全地潜入Windows内部,良好地兼容Windows的各个版本 * y" S3 c$ q9 ^! W3 v7 Z
3.计算鼠标所在的单词和字母。 3 `! S) q0 Q' A! X/ i
4.如果你在Window95下,做32位程序,还涉及Windows32/16混合编程的技术。 % Q9 E1 ]6 c) Q7 y( o0 x) v
今天先到这里吧!最好准备一份softice for 95/98和金山词霸,让我们先来分析一下别人是怎么做的。
! R E! F$ i5 v3 W5 i; {; h* R欢迎与我联系
7 I5 O! S! z U$ f8 kE-Mail:[email protected]
* Z- J7 P' t$ R7 R* W5 V) X主题 屏幕取词技术系列讲座(二)
! j& j6 I9 n* W6 Y+ b作者 亦东 5 U2 k3 c6 ~ s; e5 J
很抱歉让大家久等了!
- T- b( A( X/ h/ I( T6 A4 m我看了一些人的回帖,发现很多人对取词的原理还是不太清楚。 5 x. ]9 m( i- v8 r) z, L
首先我来解释一下hook问题。词霸中的确用到了hook,而且他用了两种hook其中一种是Windows标准hook,通过SetWindowHook安装一个回调函数,它安装了一个鼠标hook,是为了可以及时响应鼠标的消息用的和取词没太大关系。 0 Z6 Y+ c9 Z6 C M
另一种钩子是API钩子,这才是取词的核心技术所在。他在TextOut等函数的开头写了一个jmp语句,跳转到自己的代码里。
( ^6 l d. B- S3 }- [1 l- l你用softice看不到这个跳转语句是因为它只在取词的一瞬间才存在,平时是没有的。
0 H! d3 A l7 {, u# d# W& p) ~* S& X你可以在TextOut开头设一个读写断点
) ]! [& t- W: Ebpm textout / s+ P; n; T6 o2 A; H _& ^
再取词,就会找到词霸用来写钩子的代码了。 2 t( }: v T& I5 j
/**********************************
/ [) \/ O2 V* A* N7 c4 n1 @所以我在次强调,想学这种技术一定要懂汇编语言和熟练使用softice.
o9 M8 R1 @7 s& u! U**********************************/
* B+ o3 A0 i( `' \3 [2 l7 m至于从cjktl95中dump出来的未公开函数是和Windows32/16混合编程有关的,以后我会提到他们。
! c' X# K, r0 \6 N, t, b O0 \我先来讲述取词的过程,
! D6 M$ x4 M4 |; `. J( P0 判断鼠标是否在一个地方停留了一段时间 8 Q- _' s" I' M3 n1 i, j9 x
1 取得鼠标当前位置 1 {! h, F. I7 s% C* Y. Q( h/ R
2 以鼠标位置为中心生成一个矩形 8 i5 P5 q1 t! i! u% K
3 挂上API钩子
) k+ Q8 d, p) i2 W4 让这个矩形产生重画消息
/ Z/ g4 \6 ^, O" @5 在钩子里等输出字符
9 y' w3 j5 |9 W$ ~7 Z3 e1 `6 计算鼠标在哪个单词上面,把这个单词保存下来 k4 I( v d$ ~
7 如果得到单词则摘掉API钩子,在一段时间后,无论是否得到单词都摘掉API钩子
' ? ^" {5 ]% F% @4 I8 用单词查词库,显示解释框。 # E. m6 f: z; u6 t* ?. A1 a, J
很多步骤实现起来都有一些难度,所以在中国可以做一个完善的取词词典的人屈指可数。 0 I+ |( l1 a& G/ S6 V$ X' {
其中0,1,2,7,8比较简单就不提了。 # {( v6 Q4 Q& D1 W! C
先说如何挂钩子:
5 L C( e0 m# V. l所谓钩子其实就是在WindowsAPI入口写一个JMP XXXX:XXXX语句,跳转到自己的代码里。 c p6 ?- u8 W7 D- Z
步骤如下: , e) Q5 g- m4 O2 h
1.取得Windows API入口,用GetProcAddress实现 K) Q+ v# q" l# P- P
2.保存API入口的前五个字节,因为JMP是0xEA,地址是4个字节
' j8 f; Q6 ?/ d8 Z' I$ h3.写入跳转语句 9 Q! ?4 H4 B0 v& k% F3 z8 i
这步最复杂
& H0 w0 F8 ~$ z6 P% f5 n2 Z3 E/ U) mWindows的代码段本来是不可以写的,但是Microsoft给自己留了个后门。
- K3 O8 h |* y4 M9 S有一个未公开函数是AllocCsToDsAlias, ) |9 N) Y, |' N; r6 d4 [! b& `
UINT WINAPI ALLOCCSTODSALIAS(UINT); & D+ T' {: a0 w# A1 E) F
你可以取到这个函数的入口,把API的代码段的选择符(要是不知道什么是选择符,就先去学学保护模式编程吧)传给他,他会返回一个可写的数据段选择符。这个选择符用完要释放的。用新选择符和API入口的偏移量合成一个指针就可以写windows的代码段了。 ) G. z2 t$ |: \( A. j+ t! p- @' W, @
这就是取词技术的最核心的东东,不止取词,连外挂中文平台全屏汉化都是使用的这种技术。现在知道为什么这么简单的几句话却很少知道了吧?因为太多的产品使用他,太多的公司靠他赚钱了。 2 M# x8 E3 M# S- w
这些公司和产品有:中文之星,四通利方,南极星,金山词霸,实达铭泰的东方快车,roboword,译典通,即时汉化专家等等等等。。。。还有至少20多家小公司。他们的具体实现虽然不同,但大致原理是相同的。
! J g/ h1 q+ {/ @* f我这些都是随手写的,也没有提纲之类的东西,以后如果有机会我会整理一下,大家先凑合着看吧!xixi...
U4 \- Y ?$ u. y* ?: M. W0 n8 d? ! b& O& o# l6 l% q* ?
主题 关于屏幕取词的讨论(三) 0 h8 s% l- r5 D$ k o7 D0 n; l: g
作者 亦东
r0 F, B# ]9 u" l- Z! \; a+ \; D: k6 t! b Z/ K- _2 a5 B2 U
让大家久等,很抱歉,前些时候工作忙硬盘又坏了,太不幸了。
* e: n5 q' D; u; V这回来点真格的。
# I# N# |3 Z" j" h6 g. D咱们以截取TextOut为例。 2 x3 {( L- G& U4 f2 }
下面是代码: & a% S+ f0 \" K1 ~5 C8 T
//截取TextOut
i3 C/ m/ E) s5 p# }typedef UINT (WINAPI* ALLOCCSTODSALIAS)(UINT);
% N+ ~9 C8 m( R0 W, L7 d3 ^ALLOCCSTODSALIAS AllocCsToDsAlias;
9 c# O* e# s4 p \% ]; TBYTE Newvalue[5];//保存新的入口代码 3 Q6 k- A$ @4 ] H
BYTE Oldvalue[5];//API原来的入口代码
6 h: g+ j$ N6 Junsigned char * Address=NULL;//可写的API入口地址
& M$ U4 S4 Y; U) S/ \% {. rUINT DsSelector=NULL;//指向API入口的可写的选择符 $ r/ N0 h2 F& q% L8 h @
WORD OffSetEntry=NULL;//API的偏移量 3 p! j8 T" L7 t8 Z& n% V
BOOL bHookAlready = FALSE; //是否挂钩子的标志 ! s. T; O+ I6 J" E+ ?
BOOL InitHook()
0 e& ]1 P8 ]9 g8 s% Z }% s: E{
# f# X* w5 T9 v1 t6 I( | XHMODULE hKernel,hGdi;
2 g t3 M1 g0 x* T2 s0 rhKernel = GetModuleHandle("Kernel"); " O. b" t" Q8 L9 f
if(hKernel==NULL)
' D6 g2 L; o5 jreturn FALSE; ; e* C# J0 h8 m6 ^: |$ I
AllocCsToDsAlias = (ALLOCCSTODSALIAS)GetProcAddress(hKernel,"AllocCsToDsAlias");//这是未公开的API所以要这样取地址
2 G7 T9 ~6 f# ?% ]5 j p: jif(AllocCsToDsAlias==NULL) / W/ Y1 r7 P% @, q& B7 L
return FALSE; 6 z* ^3 h. X# B8 G: c* Y' N! P0 f
hGdi = GetModuleHandle("Gdi"); 5 S/ p0 {, m4 j$ b0 D
if(hmGdi==NULL)
2 }; ~. H. K( Ireturn FALSE;
# p& t) w5 ^0 K& k# b; QFARPROC Entry = GetProcAddress(hGdi,"TextOut");
k) S# p1 Y \. p3 g4 o1 ~9 Lif(Entry==NULL) H* x8 a$ \5 ^6 ^6 }$ V4 b
return FALSE;
# Y0 n, X# ]6 ]2 G0 {6 q6 P. @; y+ n5 {OffSetEntry = (WORD)(FP_OFF(Entry));//取得API代码段的选择符
* v5 p8 d8 _- F/ o* v; {DsSelector = AllocCsToDsAlias(FP_SEG(Entry));//分配一个等同的可写的选择符
- s) L: t6 V2 n# N9 D/ lAddress = (unsigned char*)MK_FP(DsSelector,OffSetEntry);//合成地址
7 L; s# }" h# l2 }: D1 sNewvalue[0]=0xEA; - U) ]$ T' u/ L$ E# d
*((DWORD*)(Newvalue+1)) = (DWORD)MyTextOut; " X! Y8 J# n$ X" S+ j: a
Oldvalue[0]=Address[0];
# V i, S- k% t" O& X*((DWORD*)(Oldvalue+1)) = *((DWORD*)(Address+1)); ) u) r! p* a- z) I# C) j. Y
} ! _# w/ h( C" _% }/ N
BOOL ClearHook()
" {1 u: I( q7 \( f1 d6 D2 n7 Y% [7 ~) E{
! ?- v. ~/ T8 {$ S) Wif(bHookAlready) ; ^ X. T8 w# V) V2 u
HookOff();
2 @6 v9 p+ x) U' C, cFreeSelector(DsSelector); 1 [2 x$ t9 X- q+ V! `' M
}
/ O, p" B. D* b: o$ EBOOL HookOn()
0 ?- `! F9 J/ x) m% b1 n{
7 L" @4 C: V) Z8 B& Tif(!bHookAlready){
/ L/ \: |5 E! y5 q9 Xfor(int i=0;i<5;i++){
7 S' m4 Y5 m7 Y# W: F" @! yAddress=Newvalue;
8 k; n) D) C4 I) p8 u7 M} : x5 c/ m" e( N+ |
bHookAlready=TRUE; `- v1 J) r' b/ h' B
} ) m- S3 \& g2 i: d3 E2 I [
} / q* E% I3 {. {/ Q$ s- u& z. e
BOOL HookOff() : I/ p6 Y4 J+ c" E
{
( `9 d+ R6 `" ^/ V2 }7 {* I0 M8 x: Z5 oif(bHookAlready){ ' }, l/ B! k0 C8 g
for(int i=0;i<5;i++){ # s, p# @) l) ~/ n" F# E
Address=Oldvalue;
" G% \* M7 ]4 L P5 ^) {2 A} 3 y. H% X- o7 ?; v7 D
bHookAlready=FALSE;
0 z- q) l$ A( |; P# @0 R( [$ A}
" U: T! R" ^0 U; W' g( n}
! `' Q1 m: f# ^/ s8 u" w! g//钩子函数,一定要和API有相同的参数和声明 6 k+ o) Z3 o7 t% C$ X* @3 s! B
BOOL WINAPI MyTextOut(HDC hdc,int nXStart,int nYStart,LPCSTR lpszString,UINT cbString) / f) a: Y/ {# w; O
{
" j* Y$ y2 P) Z& a6 J& |BOOL ret; : B' h3 L/ C6 k( C6 P: C d( c
HookOff();
4 u; d) A! H% w" }1 ]2 `* g4 r- Bret = TextOut(hdc,nXStart,nYStart,lpszString,cbString);//调原来的TextOut 5 ~9 l7 y: j+ W2 e3 A* n0 X$ k# |* B
HookOn();
$ n4 s! h* j' Zreturn ret; $ Y0 o9 X' Y4 ^, ^
} % y' {+ y7 t# f1 C; g7 d) M0 t5 R& f/ ?
上面的代码是一个最简单的挂API钩子的例子,我要提醒大家的是,这段代码是我凭记忆写的,我以前的代码丢了,我没有编译测试过 ! m' B" b4 Y6 O; j: }
因为我没有VC++1.52.所以代码可能会有错。
6 N( o4 T# B1 \$ }9 T% C建议使用Borland c++,按16位编译。
1 T9 E% }9 e* `2 C7 U1 O: V如果用VC++1.52,则要改个选项 ) C$ h: p. u1 ~; R- ?
在VC++1.52的Option里,有个内存模式的设置,选大模式,和"DS!=SS DS Load on Function entry.",切记,否则会系统崩溃。
2 Z1 X/ |; g* ^1 W4 x8 i9 X. }有什么不明白的可以给我写信
* `! K; I" _' b5 o9 t[email protected]
8 u$ ^3 t. ^4 lWednesday, June 23, 2004 7:11 PM
0 V6 D3 u* G( z: ^$ ^3 v. w5 [, g1 F$ K, D
|