本帖最后由 shane007 于 2011-1-30 14:08 编辑 0 P' z' ~$ r7 S% ?$ n( b
# e4 F! i) V9 B% Y0 ^- z
在上文中,我们已经成功的使游戏创建了我们需要的汉字纹理,不过仅仅有这1024个汉字纹理是不行的,还得想办法让它显示出来。前面说过,游戏会调用glCallLists显示显示列表,glCallLists原型如下:
$ D1 v% K: D @& t; P1 c+ l# `: L
2 c+ _6 W; ]. }0 b& C代码:
% W' a; q- ~* G6 X% Bvoid WINAPI glCallLists(
* I3 d- P" w% D! X: u+ s GLsizei n,
$ Y8 h1 F( B9 J4 j GLenum type,% d: R0 h5 p. ?0 \0 q6 |' d7 a9 i* c
const GLvoid *lists
: K4 T- ?$ v' _) a4 F);$ `$ w0 ^+ b. _0 K3 O
其中n为字符串长度,type为字符串类型,*lists为字符串指针,glCallLists函数下断调试发现,游戏在调用glCallLists时第二个参数使用了0x1400,即GL_BYTE,表示单字节:
, W3 H" O/ }1 Z$ M6 G
8 s4 @ j, U3 `# K代码:
2 {7 j: R1 \, s: I3 j# E00439D3D |. BB 00140000 mov ebx, 1400 ; 第二个参数,GL_BYTE
. a% M3 \6 m7 d$ R- x3 I! _: p) x& M+ w00439D42 |. 890424 mov dword ptr [esp], eax% v. c: K+ ]) N8 _, f
00439D45 |. E8 464B0500 call <jmp.&OPENGL32.glListBase>
. i5 k2 m! _) ?00439D4A |. 83EC 04 sub esp, 4
6 _1 }( u2 a: [00439D4D |. 893424 mov dword ptr [esp], esi ; |! k1 I0 p$ e% `8 Z$ g. ^
00439D50 |. E8 136D0800 call <jmp.&msvcrt.strlen> ; \strlen/ R3 \% B% P$ m! t& e$ f* ?
00439D55 |. 890424 mov dword ptr [esp], eax ; 将strlen结果作为第一个参数- c" C) r) K% _' v D
00439D58 |. 897424 08 mov dword ptr [esp+8], esi! j* ?( m+ V8 z- E3 h
00439D5C |. 895C24 04 mov dword ptr [esp+4], ebx
: j8 I9 S3 r, Y& C( T4 S00439D60 |. E8 234B0500 call <jmp.&OPENGL32.glCallLists> K7 a: P. _& ]2 V! H: ?
因为游戏原有256个显示列表,字符串为单字节字符串,而现在我们要使用1024个显示列表,相应的字符串必须使用双字节编码,这里第二个参数必须修改,截取gl.h部分如下:
) M) b3 E% B( }- S
- M0 ]# w: m5 P代码:
7 O7 C; i. k' ^) X" D#define GL_BYTE 0x1400
$ @6 J. A6 b( N! B' X#define GL_UNSIGNED_BYTE 0x1401, y* X5 w% j6 U' I3 Y7 X- P- v
#define GL_SHORT 0x14027 A# z: s7 P: o3 S" E5 H7 Z
#define GL_UNSIGNED_SHORT 0x1403* C- s& V/ b7 w0 p, T
#define GL_INT 0x1404+ q8 k0 R4 G# U6 S
#define GL_UNSIGNED_INT 0x1405# J& E- h: T0 a2 T5 R9 @+ [3 m
#define GL_FLOAT 0x14063 c, y$ l5 Q" w, ~1 Y8 r7 M
#define GL_2_BYTES 0x1407
& ?. D! u; t/ U#define GL_3_BYTES 0x1408" l9 o' \; \- m) Y" d
#define GL_4_BYTES 0x1409
% h4 l. r& H1 R0 E! j#define GL_DOUBLE 0x140A
% U: A: V2 R/ K. ^. _ 无符号双字节为GL_UNSIGNED_SHORT,所以应将0x00439D3D处指令改为mov ebx, 1403。接下来修改第一个参数,第一个参数改起来比较麻烦。原游戏使用ascii码所以可以调用strlen,而我们使用双字节字符串,那么必须使用wcslen来求字符串长度,而导入表里没有wcslen,这时候我们必须自己加入一个导入函数。这里为了省事我使用了pe工具stud_pe,wcslen位于动态链接库msvcvrt.dll中,增添该函数后如图:9 N u4 h& M- R( n1 ~
# {* g: c2 L( p7 [* y
图中可以看出,导入函数wcslen的RVA为0xB05214B,理论上只要我将call <jmp.&msvcrt.strlen>改为call [0xB05214B],便可以达到目的。但是事情总是比之前想象的复杂一点。这里如果直接改为call dword ptr [0xB05214B],目的地址与指令地址偏移量大,call为远call,指令长度为6字节,而原来的call是近call,指令长度为5字节,这将覆盖掉下面的mov dword ptr [esp], eax指令。后果将是glCallLists永远得不到正确的字符串长度。; F" N* K$ N4 t3 R4 I, n0 l9 f
不过这很容易解决,我们只需要在代码的缝隙中找到一块6字节的空白区,然后加入指令jmp [0xB05214B],再call到这条新加入的指令,就可以顺利解决了。我将这条指令放在了0x439c36,然后修改0x439d50处的指令为call 0x439c36,问题解决。如图:
. Q ?- Y" Q- V& c& D" Q; j0 l& I0 h2 l! s0 W
至此,对游戏引擎的修改已经结束。
) {0 l1 V2 [) v$ Y% R% K. U5 H R4 @; F 剩下的工作比较简单了,将游戏的英文文本找出并逐一翻译。统计翻译后的中文文本,也就是统计使用了那些汉字。然后制作一张32格*32格的字库,其中前128个位置放置游戏原有字符并与游戏原来字符顺序保持一致,以便能够正常显示一些特殊字符,后896个位置依次放入统计出的汉字。按照字符在字库上的位置制作一张码表,然后按照码表将翻译后的文本进行编码转换,再将转换后的结果导入游戏。
. d4 D- T; H5 D1 S4 F& e# ?: |. A 事实上,我有意无意地淡化了在分析调试过程中遇到的种种麻烦,因为事后想想也不过如此。逆向是非常痛苦的,因为下一步总是不可预料的。任何一个小小的麻烦都有可能导致前功尽弃。然而逆向的魅力也正在于此,当咬牙踏出那一步之后,回头看看自己的脚印,我想这都是值得的。% Z6 n/ H0 J& y6 Y
最后附上一张中文版截图: ! h" |2 q" M1 F1 H" L
|