本帖最后由 shane007 于 2011-1-30 14:08 编辑
+ A1 M6 ^( ]% u2 T# F
* F/ o7 v" d8 w" J2 a在上文中,我们已经成功的使游戏创建了我们需要的汉字纹理,不过仅仅有这1024个汉字纹理是不行的,还得想办法让它显示出来。前面说过,游戏会调用glCallLists显示显示列表,glCallLists原型如下:
V: B+ q" {$ B$ D0 a1 P9 [+ S" _& o
代码:6 _7 I: h! v& O) l( F
void WINAPI glCallLists(7 t+ w7 Z% }6 q1 c
GLsizei n,
* n/ s5 H) D k$ E" Y2 r GLenum type,
* [$ x, S- M, o, p# W const GLvoid *lists
7 ^( T) H% O$ \8 f);
- S) Q$ n4 y ?- e7 @+ A 其中n为字符串长度,type为字符串类型,*lists为字符串指针,glCallLists函数下断调试发现,游戏在调用glCallLists时第二个参数使用了0x1400,即GL_BYTE,表示单字节:6 p1 \1 ^0 T. w+ [" }" x
, v, E2 C+ Z" X$ D9 I; ^- X
代码:
4 }1 p) M) @9 |00439D3D |. BB 00140000 mov ebx, 1400 ; 第二个参数,GL_BYTE
- f9 K; |- h8 z" @00439D42 |. 890424 mov dword ptr [esp], eax
- {2 ] v& e" k# ]/ t00439D45 |. E8 464B0500 call <jmp.&OPENGL32.glListBase>
" Z1 h! `3 g; @8 V/ m00439D4A |. 83EC 04 sub esp, 4
' M% }& b- y# i$ N: n! o) n00439D4D |. 893424 mov dword ptr [esp], esi ; |
8 f& S8 d" ]) J( y0 h$ e00439D50 |. E8 136D0800 call <jmp.&msvcrt.strlen> ; \strlen
; ^$ [/ U5 U5 Z! k$ ~% n, U# b/ n- s00439D55 |. 890424 mov dword ptr [esp], eax ; 将strlen结果作为第一个参数
5 e, V( S* Z6 P00439D58 |. 897424 08 mov dword ptr [esp+8], esi
! j" w, y+ q, [, {2 P1 X8 y00439D5C |. 895C24 04 mov dword ptr [esp+4], ebx+ x+ Y r z/ t, v2 k
00439D60 |. E8 234B0500 call <jmp.&OPENGL32.glCallLists>
$ Z3 Y* j% B2 i& {9 U 因为游戏原有256个显示列表,字符串为单字节字符串,而现在我们要使用1024个显示列表,相应的字符串必须使用双字节编码,这里第二个参数必须修改,截取gl.h部分如下:# j: y3 Q" E1 m9 Y( o# W* l
6 U5 d1 U0 g1 N* z- R' u) L% Z4 C
代码:
" U/ {7 D) m9 @4 h8 q/ w1 O#define GL_BYTE 0x1400# e* H$ M$ S; @0 _! s2 k5 X
#define GL_UNSIGNED_BYTE 0x1401% _& N: i* O4 Y0 h8 i5 Q+ h
#define GL_SHORT 0x1402
7 Y, I; d1 ]4 L* k! W9 L#define GL_UNSIGNED_SHORT 0x14036 L6 V* b1 G a6 I
#define GL_INT 0x14048 A$ u4 _; E, r
#define GL_UNSIGNED_INT 0x1405
. c( I0 ^: F: F' @) p) p#define GL_FLOAT 0x1406) D- D( H% \4 J1 K3 K; l
#define GL_2_BYTES 0x14071 w1 O. p: d3 c5 i! m
#define GL_3_BYTES 0x1408: X9 A5 `+ d" m# a" v0 }
#define GL_4_BYTES 0x1409
" w8 a2 j. b' N7 N) v6 f" U( P#define GL_DOUBLE 0x140A
; {3 r6 G% u$ E7 } S7 R0 f( k& A$ y 无符号双字节为GL_UNSIGNED_SHORT,所以应将0x00439D3D处指令改为mov ebx, 1403。接下来修改第一个参数,第一个参数改起来比较麻烦。原游戏使用ascii码所以可以调用strlen,而我们使用双字节字符串,那么必须使用wcslen来求字符串长度,而导入表里没有wcslen,这时候我们必须自己加入一个导入函数。这里为了省事我使用了pe工具stud_pe,wcslen位于动态链接库msvcvrt.dll中,增添该函数后如图:
. ]- Y8 u& e5 q h' z9 b
2 `) @7 X; O" x* L4 Z7 n5 n 图中可以看出,导入函数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永远得不到正确的字符串长度。
3 @( V* Q8 g2 k8 r 不过这很容易解决,我们只需要在代码的缝隙中找到一块6字节的空白区,然后加入指令jmp [0xB05214B],再call到这条新加入的指令,就可以顺利解决了。我将这条指令放在了0x439c36,然后修改0x439d50处的指令为call 0x439c36,问题解决。如图: ^0 N0 x' e3 W* I1 C* n
% W) B6 j3 v" k9 \8 t2 |+ x 至此,对游戏引擎的修改已经结束。' `$ z3 p% J% h
剩下的工作比较简单了,将游戏的英文文本找出并逐一翻译。统计翻译后的中文文本,也就是统计使用了那些汉字。然后制作一张32格*32格的字库,其中前128个位置放置游戏原有字符并与游戏原来字符顺序保持一致,以便能够正常显示一些特殊字符,后896个位置依次放入统计出的汉字。按照字符在字库上的位置制作一张码表,然后按照码表将翻译后的文本进行编码转换,再将转换后的结果导入游戏。' b( F5 b8 e5 t8 Y# S
事实上,我有意无意地淡化了在分析调试过程中遇到的种种麻烦,因为事后想想也不过如此。逆向是非常痛苦的,因为下一步总是不可预料的。任何一个小小的麻烦都有可能导致前功尽弃。然而逆向的魅力也正在于此,当咬牙踏出那一步之后,回头看看自己的脚印,我想这都是值得的。/ T, F: G; r3 B1 _6 h5 A
最后附上一张中文版截图: % `( p( z. ]5 w9 s& x5 l
|