本帖最后由 shane007 于 2011-1-30 14:08 编辑 3 {, ^0 }' M: \, M% O( q* D
. e8 W# d& m. i( z) o) \' d在上文中,我们已经成功的使游戏创建了我们需要的汉字纹理,不过仅仅有这1024个汉字纹理是不行的,还得想办法让它显示出来。前面说过,游戏会调用glCallLists显示显示列表,glCallLists原型如下:
" j8 Q% L0 m6 J3 e% g
. a! i f0 f6 a6 {$ U代码:
/ a& ^5 d1 ^% d7 s* xvoid WINAPI glCallLists(
I7 f2 H( f4 v: t" u$ \# B( J GLsizei n,
0 t5 b7 D! J3 v0 Y f- @ GLenum type,/ F4 W& C1 \% [1 H Q. G- m/ f' y+ e
const GLvoid *lists5 n/ i$ Y# G) C) }
);
: ^" J f/ O) X0 T2 _% u7 _ 其中n为字符串长度,type为字符串类型,*lists为字符串指针,glCallLists函数下断调试发现,游戏在调用glCallLists时第二个参数使用了0x1400,即GL_BYTE,表示单字节:
' {9 F8 ^# u4 f$ z$ V# g
; g: d$ r+ E Z# n! R- h( x代码: J% p1 d2 K8 F
00439D3D |. BB 00140000 mov ebx, 1400 ; 第二个参数,GL_BYTE
2 i& r$ O6 `$ A00439D42 |. 890424 mov dword ptr [esp], eax
$ f0 w( _& A$ {' y& j00439D45 |. E8 464B0500 call <jmp.&OPENGL32.glListBase>( o2 F, ^' d1 G) ^& W
00439D4A |. 83EC 04 sub esp, 40 v4 Y/ l/ N: `. |4 f
00439D4D |. 893424 mov dword ptr [esp], esi ; |
~* _" f b# Q; R/ K0 H00439D50 |. E8 136D0800 call <jmp.&msvcrt.strlen> ; \strlen
+ t: T$ h J; l- `; j9 q7 _00439D55 |. 890424 mov dword ptr [esp], eax ; 将strlen结果作为第一个参数& Y A( r' t6 M# h- k3 Y
00439D58 |. 897424 08 mov dword ptr [esp+8], esi1 x8 i8 r5 j" G
00439D5C |. 895C24 04 mov dword ptr [esp+4], ebx3 v3 ?6 _& Q% s# {2 |
00439D60 |. E8 234B0500 call <jmp.&OPENGL32.glCallLists>
# R2 N! r# z' q: U$ O5 h9 J 因为游戏原有256个显示列表,字符串为单字节字符串,而现在我们要使用1024个显示列表,相应的字符串必须使用双字节编码,这里第二个参数必须修改,截取gl.h部分如下:0 C$ \# w0 B0 [& t4 M
5 `- g' q$ S6 e. r
代码:
7 {' B3 r; s. d8 k$ o# q. h7 g9 l#define GL_BYTE 0x1400- ~; w! Z5 R) U) E# `+ P1 x
#define GL_UNSIGNED_BYTE 0x1401 m. W6 }0 }, T
#define GL_SHORT 0x1402
6 z) Y% z; v7 Y4 n- Q1 z) \" |#define GL_UNSIGNED_SHORT 0x1403: U; N) A1 Y$ B# e5 |
#define GL_INT 0x1404% q! s9 h4 {% p N4 y4 g
#define GL_UNSIGNED_INT 0x14053 ~4 [6 L. P# f4 l ~
#define GL_FLOAT 0x1406* h4 [' J" G1 w/ H3 g- |
#define GL_2_BYTES 0x1407
; m$ [2 t* G* E( i4 J( h( p#define GL_3_BYTES 0x1408; F3 F5 v. {8 D
#define GL_4_BYTES 0x1409
0 R. b0 ^6 k9 }1 Y/ ]! I1 \* a#define GL_DOUBLE 0x140A8 h+ b+ h/ J/ e# O5 R
无符号双字节为GL_UNSIGNED_SHORT,所以应将0x00439D3D处指令改为mov ebx, 1403。接下来修改第一个参数,第一个参数改起来比较麻烦。原游戏使用ascii码所以可以调用strlen,而我们使用双字节字符串,那么必须使用wcslen来求字符串长度,而导入表里没有wcslen,这时候我们必须自己加入一个导入函数。这里为了省事我使用了pe工具stud_pe,wcslen位于动态链接库msvcvrt.dll中,增添该函数后如图:
0 q6 U* Z+ J9 V- Z6 l9 M& X: v/ u5 [4 j" v; q/ Y0 n' k
图中可以看出,导入函数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永远得不到正确的字符串长度。$ H2 P D: K7 d' T! h/ F& E1 s' L
不过这很容易解决,我们只需要在代码的缝隙中找到一块6字节的空白区,然后加入指令jmp [0xB05214B],再call到这条新加入的指令,就可以顺利解决了。我将这条指令放在了0x439c36,然后修改0x439d50处的指令为call 0x439c36,问题解决。如图:+ o) C5 V& L: d4 |& l, A. B
5 J# F7 |5 g! E# J9 M
至此,对游戏引擎的修改已经结束。
. h* W" n& h% c! G9 B 剩下的工作比较简单了,将游戏的英文文本找出并逐一翻译。统计翻译后的中文文本,也就是统计使用了那些汉字。然后制作一张32格*32格的字库,其中前128个位置放置游戏原有字符并与游戏原来字符顺序保持一致,以便能够正常显示一些特殊字符,后896个位置依次放入统计出的汉字。按照字符在字库上的位置制作一张码表,然后按照码表将翻译后的文本进行编码转换,再将转换后的结果导入游戏。
' R. y: f' ]' z$ d0 t6 M 事实上,我有意无意地淡化了在分析调试过程中遇到的种种麻烦,因为事后想想也不过如此。逆向是非常痛苦的,因为下一步总是不可预料的。任何一个小小的麻烦都有可能导致前功尽弃。然而逆向的魅力也正在于此,当咬牙踏出那一步之后,回头看看自己的脚印,我想这都是值得的。4 P) {5 \: ^" X0 ^$ r% N* N
最后附上一张中文版截图:
8 h1 W- i2 N7 L4 i: \* v; f1 A |