冒险解谜游戏中文网 ChinaAVG

标题: 【转】基于OPENGL引擎3D游戏逆向分析及汉化修改实例(下) [打印本页]

作者: shane007    时间: 2011-1-30 14:06
标题: 【转】基于OPENGL引擎3D游戏逆向分析及汉化修改实例(下)
本帖最后由 shane007 于 2011-1-30 14:08 编辑 ; a0 q  [  D6 |" E  z9 k, A

4 [' `1 a5 d' G% F+ \在上文中,我们已经成功的使游戏创建了我们需要的汉字纹理,不过仅仅有这1024个汉字纹理是不行的,还得想办法让它显示出来。前面说过,游戏会调用glCallLists显示显示列表,glCallLists原型如下:
8 g$ W4 @! _+ S% X, u- o: g# D
代码:
- W- e* `& _9 P' fvoid WINAPI glCallLists(
# O; O5 O2 S3 Q2 P  h    GLsizei n,
. V) H  E+ y) E2 N# ^4 K$ D' i    GLenum type,
( Y0 t; s4 D: @  ^8 O    const GLvoid *lists7 I4 Q, P" y0 T% l$ \8 E% m
);! Y6 L6 ~" ]# b& Z( F2 @
  其中n为字符串长度,type为字符串类型,*lists为字符串指针,glCallLists函数下断调试发现,游戏在调用glCallLists时第二个参数使用了0x1400,即GL_BYTE,表示单字节:; o& O: m! l6 b

, i1 u' N4 x8 V代码:  L% @: l% d" t$ W/ ?0 O+ q* ?
00439D3D  |.  BB 00140000   mov     ebx, 1400                        ;  第二个参数,GL_BYTE; F- a) n+ ]" k& k
00439D42  |.  890424        mov     dword ptr [esp], eax
( z- g' I9 ]) A6 K) s- M6 n00439D45  |.  E8 464B0500   call    <jmp.&OPENGL32.glListBase>' f/ D& a5 a$ y9 y7 t+ i" F8 V
00439D4A  |.  83EC 04       sub     esp, 46 A6 ]5 }1 y; Y% j4 @
00439D4D  |.  893424        mov     dword ptr [esp], esi             ; |
( B+ A- a8 {8 K7 t00439D50  |.  E8 136D0800   call    <jmp.&msvcrt.strlen>             ; \strlen+ S+ J2 Q8 M4 _& p% L3 X( p
00439D55  |.  890424        mov     dword ptr [esp], eax             ; 将strlen结果作为第一个参数
0 r4 n  k4 Q7 t- m( }! J00439D58  |.  897424 08     mov     dword ptr [esp+8], esi& y' y9 x6 _% O$ r$ n8 b( `! k
00439D5C  |.  895C24 04     mov     dword ptr [esp+4], ebx7 d6 G  V- X+ q3 A7 T1 s
00439D60  |.  E8 234B0500   call    <jmp.&OPENGL32.glCallLists>" O% @* C. X) [/ F1 T, S
  因为游戏原有256个显示列表,字符串为单字节字符串,而现在我们要使用1024个显示列表,相应的字符串必须使用双字节编码,这里第二个参数必须修改,截取gl.h部分如下:$ C, {* }8 N- ?3 O. r2 ~* Z8 O$ o

( D5 k$ s, j7 D9 x9 t" N8 }代码:
. J+ w1 R: r- S, A6 ?#define GL_BYTE                           0x1400
: L% o% r; x5 L- h$ G" a#define GL_UNSIGNED_BYTE                  0x1401
5 C  B; R7 T# O. S#define GL_SHORT                          0x1402! k8 F' P3 _9 A# ?3 }( X, S, v) ^% s. O
#define GL_UNSIGNED_SHORT                 0x14032 I. @& n/ k/ M. }3 `3 S: W  q
#define GL_INT                            0x14049 S/ Y; T2 T7 K; v. X$ {9 V4 f5 N2 T
#define GL_UNSIGNED_INT                   0x14056 H2 I4 _3 S3 q6 M7 Q
#define GL_FLOAT                          0x14062 X- Y8 }. ?$ L' s& K/ R
#define GL_2_BYTES                        0x1407
2 R/ A- f( ^* _+ E" F8 w  v#define GL_3_BYTES                        0x1408
! @8 _* i  L) n7 D& Z4 e1 |#define GL_4_BYTES                        0x1409
8 t: J7 _9 ]% H# S  ]#define GL_DOUBLE                         0x140A4 w) |$ w- i3 H* ?0 b! K$ \
  无符号双字节为GL_UNSIGNED_SHORT,所以应将0x00439D3D处指令改为mov     ebx, 1403。接下来修改第一个参数,第一个参数改起来比较麻烦。原游戏使用ascii码所以可以调用strlen,而我们使用双字节字符串,那么必须使用wcslen来求字符串长度,而导入表里没有wcslen,这时候我们必须自己加入一个导入函数。这里为了省事我使用了pe工具stud_pe,wcslen位于动态链接库msvcvrt.dll中,增添该函数后如图:4 j, j7 u) t, l; x1 T( h

! N) d, g5 v# J4 [  图中可以看出,导入函数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永远得不到正确的字符串长度。
7 }& e5 l6 D/ `# f3 ~3 K# t  不过这很容易解决,我们只需要在代码的缝隙中找到一块6字节的空白区,然后加入指令jmp [0xB05214B],再call到这条新加入的指令,就可以顺利解决了。我将这条指令放在了0x439c36,然后修改0x439d50处的指令为call 0x439c36,问题解决。如图:
8 Z6 @9 h8 t! l5 U6 i/ r0 x[attach]18537[/attach]
* I! y5 d+ q* T2 P4 D8 @2 L  至此,对游戏引擎的修改已经结束。2 e7 _% T% T; w; U/ S
  剩下的工作比较简单了,将游戏的英文文本找出并逐一翻译。统计翻译后的中文文本,也就是统计使用了那些汉字。然后制作一张32格*32格的字库,其中前128个位置放置游戏原有字符并与游戏原来字符顺序保持一致,以便能够正常显示一些特殊字符,后896个位置依次放入统计出的汉字。按照字符在字库上的位置制作一张码表,然后按照码表将翻译后的文本进行编码转换,再将转换后的结果导入游戏。8 [: w# d; _& f4 t+ l+ [/ N
  事实上,我有意无意地淡化了在分析调试过程中遇到的种种麻烦,因为事后想想也不过如此。逆向是非常痛苦的,因为下一步总是不可预料的。任何一个小小的麻烦都有可能导致前功尽弃。然而逆向的魅力也正在于此,当咬牙踏出那一步之后,回头看看自己的脚印,我想这都是值得的。
' p: x$ t# I0 h2 f$ a  最后附上一张中文版截图:
1 ~- q4 O' ~9 t5 F% R[attach]18536[/attach]




欢迎光临 冒险解谜游戏中文网 ChinaAVG (https://chinaavg.com/) Powered by Discuz! X3.2