对于一些调用Windows标准API来进行文本输出的AVG游戏,也许可以借鉴金山词霸之类的屏幕取词技术,HOOK API 之后,在自己的函数里把截获的英文替换为中文,然后输出到屏幕上来实现汉化,而且,这样的工具一旦制作完成,对于这类的AVG游戏将是通用的。
2 M8 l" C& S5 P& r8 I6 V; m1 p. O) o# u9 @
& C( Y% I8 D) g2 x8 W% K! U% I
金山词霸”屏幕取词技术揭密(讨论稿) # F1 F: s M/ I" ^' c! i
这篇文章最早是发在北极星论坛的一系列帖子,那时候闻怡洋(好像他也是MVP)也在那里混
$ t! j5 t. V7 L+ F s8 a a原始的帖子我已经没有了,但不知道是谁帮我收集整理了下来(非常感谢),我用google找到了 " g8 N5 ?4 \/ o1 S& F% V
? 2 l$ p9 T/ d, y# B0 E
这是我进金山之前写的,应该不算泄露公司技术秘密吧 ! _3 H, P: i1 Z0 F& v8 ?9 c: G/ P+ w3 Z
而且这些现在看来似乎已经有些过时了
0 q* R. P& A# t/ d7 T/ J? % E- J7 _5 W1 G( n. M9 H0 X) o. p! d8 J
那时讨论的只是Win31和Win9x下的取词实现 ) h( N$ }; V! {! n1 S( t
? 4 L, F0 V# ?% H i3 Y2 N4 `
我到了金山之后不是负责取词模块,而是做UI,因为有个家伙比我更擅长做这种东西
! e/ C* T& D* @) b1 v) \他用SoftIce调试汇编代码非常熟练,做逆向工程方面有过人的天分。
N2 ?1 l3 M# z [2 Q?
# J" B4 R) Y$ Q? , P( s; n& a# ?
“亦东” 是我那时的笔名 - o" C+ [ V" b. |- a8 Q& ]" M
?
: y2 [& A' S$ R?
6 {! M3 h _3 d/ {“金山词霸”屏幕取词技术揭密(讨论稿)
. \# T8 U Y8 Y H1 r$ H? . u% g$ s8 s o0 i$ {, Q( R2 `
主题 屏幕取词技术系列讲座(一) " c$ J6 s) J) l0 O6 x* h
作者 亦东
7 a7 c. Z I' u% T5 Y很多人对这个问题感兴趣。
: Z8 j- j* f! H1 ~* h h原因是这项技术让人感觉很神奇,也很有商业价值。
" H* {1 C7 [ [# s现在词典市场金山词霸占了绝对优势,所以再做字典也没什么前途了。我就是这么认为的,所以我虽然掌握了这项技术,却没去做字典软件。只做了一个和词霸相似的软件自己用,本来想拿出来做共享软件,但我的词库是“偷”来的,而且词汇不多,所以也就算了,词库太小,只能取词有什么用呢?而且词霸有共享版的。 . ^/ k0 C% |* [, T0 `
但既然很多人想了解这项技术,我也不会保留。我准备分多次讲述这项技术的所有细节。 - T H- Q4 O6 P" G
大约每周一两次。想知道的人就常常来看看吧! Z3 D# r1 J) @1 q& P6 i
一.基础知识
. Z* U5 ^2 g/ p3 j9 i7 p3 {5 V首先想编这种程序需要一些基础知识。 5 L6 \& j6 z. K i& R
会用Vc++,包括16/32位。
, x3 g* h. |5 o5 S+ e2 `精通Windows API特别是GDI,KERNEL部分。
: ]! e$ {% Z" e1 O懂汇编语言,会用softice调试程序,因为这种程序最好用softice调试。
$ X* k& S6 `: ]( d: I6 d! ]二.基本原理
( s5 z5 l7 [% h0 c [, E1 }在Window 3.x时代,windows系统提供的字符输出函数只有很少的几个。 1 z' ]% A! L0 L `1 k; J' ~
TextOut
; f: ?: K8 a6 |8 {# a$ C* Q8 B4 yExtTextOut
9 S# I4 F& j2 A( _' @DrawText
( K6 ^' W8 @1 ]/ G/ |...... 9 a/ Q) T2 K% ?# N" h; B
其中DrawText最终是用ExtTextOut实现的。
6 w+ n7 X: `3 F5 Z W所以Windows的所有字符输出都是由调用TextOut和ExtTextOut实现的。因此,如果你可以修改这两个函数的入口,让程序先调用你自己的一个函数再调用系统的字符输出,你就可以得到Windows所有输出的字符了。
9 [6 w5 l: I, d3 Z2 J到了Windows95时代,原理基本没变,但是95比3.x要复杂。开始的时候,一些在windows3.x下编写的取词软件仍然可以是使用。但是后来出了个IE4,结果很多词典软件就因为不支持IE4而被淘汰了,但同时也给一些软件创造了机会,如金山词霸。其实IE4的问题并不复杂,只不过它的输出的是unicode字符,是用TextOutW和ExtTextOutW输出的。知道了这一点,只要也截取就可以了。不过实现方法复杂一点,以后会有详细讲解。现在又出了个IE5,结果词霸也不好用了,微软真是#^@#$%$*&^&#@#@.......... 7 n- m/ L) L- [4 F+ W+ I& [: G
我研究后找到了一种解决办法,但还有些问题,有时会取错,正在继续研究,希望大家共同探讨。
5 B& G: z- Q& ]另外还有WindowsNT,原理也是一样,只是实现方法和95下完全不同。
2 w/ t7 E: ^6 N- R" I$ M! y3 c三.技术要点 4 r8 V+ m8 Z0 V& e9 D0 g% \+ G
要实现取词,主要要解决以下技术问题。
9 y" s' o! M$ i$ a% H; k1.截取API入口,获得API的参数。
6 l' B- ~. |" |2.安全地潜入Windows内部,良好地兼容Windows的各个版本
9 l- i Z; K2 }! ?: G- k3.计算鼠标所在的单词和字母。 $ Z# v( P; ^) d* r- E
4.如果你在Window95下,做32位程序,还涉及Windows32/16混合编程的技术。
1 y7 ]9 A: y, R) v今天先到这里吧!最好准备一份softice for 95/98和金山词霸,让我们先来分析一下别人是怎么做的。 + F& C4 `% ]' Q" D
欢迎与我联系
+ }6 r* U% r( m7 uE-Mail:[email protected] * Q3 C% T- |" I- E2 b
主题 屏幕取词技术系列讲座(二) 8 y4 \8 v9 A" G* ?' X6 }1 S7 C
作者 亦东
% B9 B* @8 X9 I- U' |. s& g5 w很抱歉让大家久等了! 2 s# F, }2 W: g* i+ |; v9 w& X9 A
我看了一些人的回帖,发现很多人对取词的原理还是不太清楚。 / Y: ^. F3 R( i0 z
首先我来解释一下hook问题。词霸中的确用到了hook,而且他用了两种hook其中一种是Windows标准hook,通过SetWindowHook安装一个回调函数,它安装了一个鼠标hook,是为了可以及时响应鼠标的消息用的和取词没太大关系。
: C2 C+ q/ X- e' J6 `. @% s8 E另一种钩子是API钩子,这才是取词的核心技术所在。他在TextOut等函数的开头写了一个jmp语句,跳转到自己的代码里。 0 f3 t3 {9 f8 Y0 T4 E7 S% Y
你用softice看不到这个跳转语句是因为它只在取词的一瞬间才存在,平时是没有的。
2 ^0 k3 H- o- |& k你可以在TextOut开头设一个读写断点
5 L2 ]3 h$ h% l4 A" _$ r3 Bbpm textout 6 z1 H1 @5 c8 ~ }2 p
再取词,就会找到词霸用来写钩子的代码了。
+ q8 X# E3 w' V/**********************************
5 k; q9 R2 z3 P( K: K所以我在次强调,想学这种技术一定要懂汇编语言和熟练使用softice.
8 M( Q( a! Q3 }2 v: G8 E2 C' `**********************************/
3 Z0 D: ^' ?9 m$ L至于从cjktl95中dump出来的未公开函数是和Windows32/16混合编程有关的,以后我会提到他们。
/ G2 _" `' R! N1 c; [* u我先来讲述取词的过程,
# k) w5 |& f1 z% r0 判断鼠标是否在一个地方停留了一段时间
$ f3 z( O6 m* z5 e$ _: u! b/ v1 取得鼠标当前位置
, V8 F* [7 N% T( F {& B: D& l& h f2 以鼠标位置为中心生成一个矩形
, o- {! c6 M. G& f. ?3 挂上API钩子 . l' q8 h6 v9 A
4 让这个矩形产生重画消息 7 F0 z% R' l1 Q: F
5 在钩子里等输出字符 - ?1 L6 _* v% d8 m
6 计算鼠标在哪个单词上面,把这个单词保存下来
7 g" q- A' l n7 如果得到单词则摘掉API钩子,在一段时间后,无论是否得到单词都摘掉API钩子 : K- U4 ]" W' N" Q
8 用单词查词库,显示解释框。
) F$ D# [% A2 Z5 R: ]) I很多步骤实现起来都有一些难度,所以在中国可以做一个完善的取词词典的人屈指可数。
# E" S5 f& T* N, M* p' ?9 o其中0,1,2,7,8比较简单就不提了。 ; ?" r- u0 l5 l' h
先说如何挂钩子:
5 t# O G5 ^$ \4 [3 }所谓钩子其实就是在WindowsAPI入口写一个JMP XXXX:XXXX语句,跳转到自己的代码里。
* z' U. K" l' K! B步骤如下: & y9 b1 a. U" a$ W3 n# L5 h/ V g
1.取得Windows API入口,用GetProcAddress实现
' N/ k* C8 j! s# ^) f2.保存API入口的前五个字节,因为JMP是0xEA,地址是4个字节 # p# {2 l6 C8 S/ d* I
3.写入跳转语句
' }' F- t% r H+ W3 S; d0 S8 }! E这步最复杂 ; D- K8 Q6 x% @. B
Windows的代码段本来是不可以写的,但是Microsoft给自己留了个后门。 ; L0 J0 t/ ~6 o2 e' E: [4 t
有一个未公开函数是AllocCsToDsAlias,
+ I2 N% ]. t1 C% w* wUINT WINAPI ALLOCCSTODSALIAS(UINT); # g. g1 N: J4 |4 t9 ?; S8 @
你可以取到这个函数的入口,把API的代码段的选择符(要是不知道什么是选择符,就先去学学保护模式编程吧)传给他,他会返回一个可写的数据段选择符。这个选择符用完要释放的。用新选择符和API入口的偏移量合成一个指针就可以写windows的代码段了。
/ ^# l3 _6 R+ S5 i& w/ G& ^这就是取词技术的最核心的东东,不止取词,连外挂中文平台全屏汉化都是使用的这种技术。现在知道为什么这么简单的几句话却很少知道了吧?因为太多的产品使用他,太多的公司靠他赚钱了。 . `8 U9 h6 `) m/ E
这些公司和产品有:中文之星,四通利方,南极星,金山词霸,实达铭泰的东方快车,roboword,译典通,即时汉化专家等等等等。。。。还有至少20多家小公司。他们的具体实现虽然不同,但大致原理是相同的。 ' s* W; n2 ^/ h6 ~
我这些都是随手写的,也没有提纲之类的东西,以后如果有机会我会整理一下,大家先凑合着看吧!xixi... 4 w6 t* ~6 ?4 f* j
? # y7 B2 H V/ J8 p( [+ r- Z0 ^" q+ c; p
主题 关于屏幕取词的讨论(三)
; H$ U; e) u$ _: D1 t4 J作者 亦东 5 Z0 J! X" b" O
6 v2 [" ]8 K* h/ I3 H) Z0 W& ]
让大家久等,很抱歉,前些时候工作忙硬盘又坏了,太不幸了。
( z0 u7 G- u- O这回来点真格的。
; ~( u3 O! H& `( \5 k! B咱们以截取TextOut为例。 2 N$ I f2 k \# i2 c: j) n
下面是代码:
: Y! a1 g6 I. A/ _8 X' n7 B& J% Q//截取TextOut
" o/ N2 x4 G+ Y, {$ U1 Ltypedef UINT (WINAPI* ALLOCCSTODSALIAS)(UINT);
8 J: L- C: J4 j# S4 |0 W" E# SALLOCCSTODSALIAS AllocCsToDsAlias; - p2 t* @ W F
BYTE Newvalue[5];//保存新的入口代码
" V0 q1 N4 _8 P) bBYTE Oldvalue[5];//API原来的入口代码
& ~3 e" c) i% @+ J* }! m7 hunsigned char * Address=NULL;//可写的API入口地址 4 f, ^7 B9 I ~! x
UINT DsSelector=NULL;//指向API入口的可写的选择符 . t' W. b! @6 v5 l% \5 }# \0 I
WORD OffSetEntry=NULL;//API的偏移量 ( {4 Z: h2 d$ F4 R' \# H" @
BOOL bHookAlready = FALSE; //是否挂钩子的标志
8 j- r$ C: |# eBOOL InitHook()
' ?4 t2 B8 p# s% E$ B* c5 @1 w; B{ . o+ }/ r3 w8 M8 g1 l( f
HMODULE hKernel,hGdi;
7 s; I1 q: z' whKernel = GetModuleHandle("Kernel");
$ M0 ]0 K9 }/ P2 \if(hKernel==NULL) , n1 f* n/ p$ Z( a8 g6 C$ ]9 L
return FALSE;
: Q# `. J6 w- S6 ?4 L* s7 i% GAllocCsToDsAlias = (ALLOCCSTODSALIAS)GetProcAddress(hKernel,"AllocCsToDsAlias");//这是未公开的API所以要这样取地址
! W A5 B+ B: s- Aif(AllocCsToDsAlias==NULL) 1 N0 t* r& K- t4 v% X
return FALSE;
, \! f5 W0 z6 D. fhGdi = GetModuleHandle("Gdi");
! S4 T/ w. T8 D# W zif(hmGdi==NULL) ' f3 S: ^ o3 H: k; y
return FALSE; 9 d7 M, c) }) U6 q, g b
FARPROC Entry = GetProcAddress(hGdi,"TextOut"); & A* z# _- P! Z' v3 V g
if(Entry==NULL) 0 F: a6 c+ a6 E) b
return FALSE; ) N* H! a! o$ A$ n
OffSetEntry = (WORD)(FP_OFF(Entry));//取得API代码段的选择符 0 Q% V. f5 e' U$ H2 i. s1 p/ B, d
DsSelector = AllocCsToDsAlias(FP_SEG(Entry));//分配一个等同的可写的选择符
8 m+ { M/ h% y$ ]Address = (unsigned char*)MK_FP(DsSelector,OffSetEntry);//合成地址 * e' ~0 [# |: t$ A# u: x1 M
Newvalue[0]=0xEA;
" Y+ {9 y+ D& K*((DWORD*)(Newvalue+1)) = (DWORD)MyTextOut;
4 z$ Z5 n2 Z* d3 Y1 ^1 Z7 S8 n) e! EOldvalue[0]=Address[0]; $ k( \( k) @0 p+ U
*((DWORD*)(Oldvalue+1)) = *((DWORD*)(Address+1)); # m9 a- M Q0 l% v6 Z8 t
} # y5 @6 u5 K8 \
BOOL ClearHook() 3 o: Y4 b6 E7 T- _/ j/ Q8 [! B
{
% O8 M6 k" A0 ]$ S+ v6 m2 K- Dif(bHookAlready)
) M4 W- q7 ]% f7 B0 kHookOff(); / I( w) @/ o% i
FreeSelector(DsSelector);
" T% }- [6 J0 T, `. w}
; r1 m& C, v6 FBOOL HookOn() . l9 o! q1 ~: [ a: F, z
{
. {5 E' ^; j8 k% qif(!bHookAlready){ 7 a1 p5 g: h: c( m! d" S4 ^: C
for(int i=0;i<5;i++){
4 i" q" z# e2 J4 I @/ PAddress=Newvalue;
B/ S$ Q* R6 |}
) b" z' R7 X9 j/ t7 |6 T; r5 A: `+ X @bHookAlready=TRUE;
+ c* |: d6 i" f, i} 5 h' F$ z% F1 c- U( [
}
( c) {/ C, |- o6 s5 YBOOL HookOff() 3 e9 v1 U+ e0 w1 j( Y
{
: z$ E" c q& a; D2 Eif(bHookAlready){
% H8 x V9 x, i1 B( J7 Nfor(int i=0;i<5;i++){ % L# g0 {% t {
Address=Oldvalue; " |/ e: m) A2 F9 z
}
4 s( l$ g1 f. D- `; i( E$ p$ ybHookAlready=FALSE;
0 H; P- o3 E9 I9 q5 R}
G$ n' X3 b2 E% N( z} 5 r% P. K! M( Q# e2 l
//钩子函数,一定要和API有相同的参数和声明
# o4 T. H, R7 ~ ]* b$ ]" dBOOL WINAPI MyTextOut(HDC hdc,int nXStart,int nYStart,LPCSTR lpszString,UINT cbString)
! A/ ~5 g$ F& b% S{ 5 ` }6 s$ F% U% \0 C6 o
BOOL ret; ' S$ ~# ]/ @1 N% v9 o8 d+ _
HookOff(); : H/ w- a( {- S J, P% i! L/ t7 t- s4 D
ret = TextOut(hdc,nXStart,nYStart,lpszString,cbString);//调原来的TextOut / c+ o5 L- r9 M9 y* C
HookOn(); ! J5 }% h2 I" g A0 s0 `
return ret; , {4 \; J5 n6 l0 s( ?* Q. y S0 l
} 4 t, o# _* T& k/ B# V6 M" {
上面的代码是一个最简单的挂API钩子的例子,我要提醒大家的是,这段代码是我凭记忆写的,我以前的代码丢了,我没有编译测试过
4 E4 H/ Z" @, m3 U& b* `9 n因为我没有VC++1.52.所以代码可能会有错。
2 M# {8 x6 w6 _/ t4 M建议使用Borland c++,按16位编译。 1 F x8 @- |% `( D+ E( G5 m/ s
如果用VC++1.52,则要改个选项
6 j- P$ f8 w' N: o7 q* a在VC++1.52的Option里,有个内存模式的设置,选大模式,和"DS!=SS DS Load on Function entry.",切记,否则会系统崩溃。
3 @; ^) Y8 v& u& S5 m有什么不明白的可以给我写信 # E1 t$ H/ N0 Z7 a
[email protected]
" P% c; B# K7 H5 V' K- dWednesday, June 23, 2004 7:11 PM 5 r" L% ]* e& t9 F# ~' d1 n& b
3 B1 A. b5 x2 a# @) p& H
|