本帖最后由 shane007 于 2011-1-30 14:08 编辑
1 b- y/ K% z! K& s0 f6 Q' M, I! o( x1 e5 E; ^8 S
在上文中,我们已经成功的使游戏创建了我们需要的汉字纹理,不过仅仅有这1024个汉字纹理是不行的,还得想办法让它显示出来。前面说过,游戏会调用glCallLists显示显示列表,glCallLists原型如下:3 Y# ?% d( [9 p8 A
/ @2 A& d# s6 A: A/ ~0 Y% t+ G代码:
# u/ C# H: }, `4 H" H% M2 qvoid WINAPI glCallLists(9 O) n" o. a0 ~0 P4 z) i) P: g J
GLsizei n,
; g) A7 X: `, q* {, i6 r GLenum type,
) \! i1 F" f* y9 }, E const GLvoid *lists8 d7 U/ w$ V U [# [$ _7 B
);% c* Z" x/ ^' G3 u/ Y9 ~
其中n为字符串长度,type为字符串类型,*lists为字符串指针,glCallLists函数下断调试发现,游戏在调用glCallLists时第二个参数使用了0x1400,即GL_BYTE,表示单字节:
' i1 v; |6 u( o: r4 J
5 `: V7 l: K) l) _; l" [代码:5 n. L- A# m D3 I
00439D3D |. BB 00140000 mov ebx, 1400 ; 第二个参数,GL_BYTE4 X: ^6 K, k0 D1 V+ C; n
00439D42 |. 890424 mov dword ptr [esp], eax( b- k( Q; H& P- X i
00439D45 |. E8 464B0500 call <jmp.&OPENGL32.glListBase>0 b, Z: `' \' S9 E6 p$ b# V2 u6 s
00439D4A |. 83EC 04 sub esp, 40 G7 S- o& b; Q5 F: ]' t R7 W
00439D4D |. 893424 mov dword ptr [esp], esi ; |
: e+ \* y- L n9 h) q1 Q00439D50 |. E8 136D0800 call <jmp.&msvcrt.strlen> ; \strlen" Z# Z* {% i6 x7 U6 a0 R- C
00439D55 |. 890424 mov dword ptr [esp], eax ; 将strlen结果作为第一个参数
: e2 E3 ^) n! E2 A0 }' n) P00439D58 |. 897424 08 mov dword ptr [esp+8], esi
% f- E/ H" y" B8 E7 k5 u7 G+ h3 p00439D5C |. 895C24 04 mov dword ptr [esp+4], ebx
# `' L# \" }3 l5 U00439D60 |. E8 234B0500 call <jmp.&OPENGL32.glCallLists>/ K# o! x: i8 W, @$ u# v& ?# }" W
因为游戏原有256个显示列表,字符串为单字节字符串,而现在我们要使用1024个显示列表,相应的字符串必须使用双字节编码,这里第二个参数必须修改,截取gl.h部分如下:
# I' L& O& ^- |5 Y( v
, x' Z) w: t4 \" o+ u代码:$ S+ H0 M0 s* a2 r2 C. r
#define GL_BYTE 0x1400
8 T. i) {3 h" G& D( W6 @2 b#define GL_UNSIGNED_BYTE 0x14010 f5 V5 r# ~/ Q& @* w1 _5 p4 _
#define GL_SHORT 0x1402 w1 t4 C1 A/ K- g/ X+ N& f
#define GL_UNSIGNED_SHORT 0x1403
( y3 U5 u* h8 ]) H#define GL_INT 0x1404% g6 \8 m# I# F# M/ U
#define GL_UNSIGNED_INT 0x1405
& `+ x' q/ w7 B+ w0 n I2 r#define GL_FLOAT 0x1406
) e+ K* W1 G4 J. m( K+ o. }' O#define GL_2_BYTES 0x1407
5 J7 ]3 x9 u4 Q3 ~: E5 E#define GL_3_BYTES 0x1408. N6 Y5 B" J1 s# G
#define GL_4_BYTES 0x1409
& W) l' |+ ?( M0 Q#define GL_DOUBLE 0x140A
/ U/ V9 p- o. [4 v6 w& }8 O2 q# a 无符号双字节为GL_UNSIGNED_SHORT,所以应将0x00439D3D处指令改为mov ebx, 1403。接下来修改第一个参数,第一个参数改起来比较麻烦。原游戏使用ascii码所以可以调用strlen,而我们使用双字节字符串,那么必须使用wcslen来求字符串长度,而导入表里没有wcslen,这时候我们必须自己加入一个导入函数。这里为了省事我使用了pe工具stud_pe,wcslen位于动态链接库msvcvrt.dll中,增添该函数后如图:
: h% T* m1 ]7 p" \0 L" Z6 H* G! Q- @% A- E+ E
图中可以看出,导入函数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永远得不到正确的字符串长度。
9 v3 U/ D( g. }" @* t7 l# f 不过这很容易解决,我们只需要在代码的缝隙中找到一块6字节的空白区,然后加入指令jmp [0xB05214B],再call到这条新加入的指令,就可以顺利解决了。我将这条指令放在了0x439c36,然后修改0x439d50处的指令为call 0x439c36,问题解决。如图:' S z+ N. o5 \5 Q7 L
9 k. X+ ^* _: F
至此,对游戏引擎的修改已经结束。+ R7 X- f7 T. c" a- c: h5 @5 [
剩下的工作比较简单了,将游戏的英文文本找出并逐一翻译。统计翻译后的中文文本,也就是统计使用了那些汉字。然后制作一张32格*32格的字库,其中前128个位置放置游戏原有字符并与游戏原来字符顺序保持一致,以便能够正常显示一些特殊字符,后896个位置依次放入统计出的汉字。按照字符在字库上的位置制作一张码表,然后按照码表将翻译后的文本进行编码转换,再将转换后的结果导入游戏。
& Y ]7 D8 G8 a6 T- K 事实上,我有意无意地淡化了在分析调试过程中遇到的种种麻烦,因为事后想想也不过如此。逆向是非常痛苦的,因为下一步总是不可预料的。任何一个小小的麻烦都有可能导致前功尽弃。然而逆向的魅力也正在于此,当咬牙踏出那一步之后,回头看看自己的脚印,我想这都是值得的。
5 l4 j6 H( _" ], D& |: v2 ^ 最后附上一张中文版截图: 9 F: Z( @$ e% D' [
|