本帖最后由 shane007 于 2011-1-30 14:08 编辑
* R j5 E, M) v9 R$ m _6 }1 Z5 x: U0 t9 A0 s; g1 |' K/ h
在上文中,我们已经成功的使游戏创建了我们需要的汉字纹理,不过仅仅有这1024个汉字纹理是不行的,还得想办法让它显示出来。前面说过,游戏会调用glCallLists显示显示列表,glCallLists原型如下:2 l: M, u3 Y' M T% Y7 }$ n$ X
: s6 r* u6 @8 P
代码:7 M1 _4 K- [) P1 B8 w
void WINAPI glCallLists(
, M4 D) ?2 A% ?: C) l0 Y: V GLsizei n, y7 _, ^, O1 P1 G2 G
GLenum type,
' @1 A$ u) N( r& _1 [ const GLvoid *lists
2 w+ G, E# ?$ X" E);6 U* s. b3 f, Q& `$ q, U3 K$ R
其中n为字符串长度,type为字符串类型,*lists为字符串指针,glCallLists函数下断调试发现,游戏在调用glCallLists时第二个参数使用了0x1400,即GL_BYTE,表示单字节:
w: z" ^" P( g7 b$ q0 ?
6 K4 a+ k5 ]4 h: @" M1 J* k& v$ s代码:
9 {& g( ~4 ]: K4 L4 Q" v00439D3D |. BB 00140000 mov ebx, 1400 ; 第二个参数,GL_BYTE
' h# e. i" p+ y! J2 G" G00439D42 |. 890424 mov dword ptr [esp], eax
& u! l- ~) S8 U9 H2 S% j2 h4 g/ `00439D45 |. E8 464B0500 call <jmp.&OPENGL32.glListBase>( w9 P4 n1 z$ e+ i" y
00439D4A |. 83EC 04 sub esp, 4
( @: x! r2 D9 B00439D4D |. 893424 mov dword ptr [esp], esi ; |
* F5 v4 m" W' v# m% h00439D50 |. E8 136D0800 call <jmp.&msvcrt.strlen> ; \strlen( }$ e- v% n+ t: I" e( N
00439D55 |. 890424 mov dword ptr [esp], eax ; 将strlen结果作为第一个参数
; D' u/ E5 r# v! O! L W00439D58 |. 897424 08 mov dword ptr [esp+8], esi
( B6 \3 M: d7 O' g! p/ q9 h/ I00439D5C |. 895C24 04 mov dword ptr [esp+4], ebx: c$ b: z, M% z
00439D60 |. E8 234B0500 call <jmp.&OPENGL32.glCallLists>8 [' e/ P$ L) k$ h; N
因为游戏原有256个显示列表,字符串为单字节字符串,而现在我们要使用1024个显示列表,相应的字符串必须使用双字节编码,这里第二个参数必须修改,截取gl.h部分如下:
3 b! ]9 W o0 d5 p( q5 @- n- d4 A- B9 P% ~' x9 Y8 P) Z
代码:
! V4 ~) v. H) _( R0 Z#define GL_BYTE 0x14006 i+ B) G; i3 b
#define GL_UNSIGNED_BYTE 0x1401
* o$ N6 |; O$ g" m: I7 n r$ a#define GL_SHORT 0x1402
- d0 G+ `# y: H+ q: z" v u1 y#define GL_UNSIGNED_SHORT 0x1403! ^9 g; [% l+ i* w
#define GL_INT 0x1404
K R5 H7 s; F) e. U$ ^#define GL_UNSIGNED_INT 0x1405
8 u) `1 J4 K6 g5 T) W#define GL_FLOAT 0x1406
% g" Q. G3 ?: Z* i" b. n! E#define GL_2_BYTES 0x1407
8 d# Z3 K7 m% ^#define GL_3_BYTES 0x1408( Q! g$ Q' D# X& j
#define GL_4_BYTES 0x1409
# ], B8 ]+ K+ x( E#define GL_DOUBLE 0x140A- B+ G& [% Z3 I+ T( _
无符号双字节为GL_UNSIGNED_SHORT,所以应将0x00439D3D处指令改为mov ebx, 1403。接下来修改第一个参数,第一个参数改起来比较麻烦。原游戏使用ascii码所以可以调用strlen,而我们使用双字节字符串,那么必须使用wcslen来求字符串长度,而导入表里没有wcslen,这时候我们必须自己加入一个导入函数。这里为了省事我使用了pe工具stud_pe,wcslen位于动态链接库msvcvrt.dll中,增添该函数后如图:
/ ?& g3 @( D8 `8 k/ n4 D' n% G0 _* N' c! N8 L, 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永远得不到正确的字符串长度。
9 e4 x* N- ^. j5 k$ z- P7 } 不过这很容易解决,我们只需要在代码的缝隙中找到一块6字节的空白区,然后加入指令jmp [0xB05214B],再call到这条新加入的指令,就可以顺利解决了。我将这条指令放在了0x439c36,然后修改0x439d50处的指令为call 0x439c36,问题解决。如图:; m, E$ W5 E1 N3 y# S
; w* n$ v1 l8 _4 ?9 o' H# B5 p+ W
至此,对游戏引擎的修改已经结束。9 Y! K: g' Z, o, y {# F
剩下的工作比较简单了,将游戏的英文文本找出并逐一翻译。统计翻译后的中文文本,也就是统计使用了那些汉字。然后制作一张32格*32格的字库,其中前128个位置放置游戏原有字符并与游戏原来字符顺序保持一致,以便能够正常显示一些特殊字符,后896个位置依次放入统计出的汉字。按照字符在字库上的位置制作一张码表,然后按照码表将翻译后的文本进行编码转换,再将转换后的结果导入游戏。% ]; `: {8 S( Q7 b9 S* l
事实上,我有意无意地淡化了在分析调试过程中遇到的种种麻烦,因为事后想想也不过如此。逆向是非常痛苦的,因为下一步总是不可预料的。任何一个小小的麻烦都有可能导致前功尽弃。然而逆向的魅力也正在于此,当咬牙踏出那一步之后,回头看看自己的脚印,我想这都是值得的。
6 C* \1 ]$ o c) B6 E+ ~+ T3 } 最后附上一张中文版截图: 0 A' T/ Z% Y2 S+ X5 K h0 N' o; e7 B9 k
|