冒险解谜游戏中文网 ChinaAVG
标题:
【转】基于OPENGL引擎3D游戏逆向分析及汉化修改实例(下)
[打印本页]
作者:
shane007
时间:
2011-1-30 14:06
标题:
【转】基于OPENGL引擎3D游戏逆向分析及汉化修改实例(下)
本帖最后由 shane007 于 2011-1-30 14:08 编辑
+ ?8 A6 _9 G% R6 }
7 Q, `6 a6 c: O4 ?$ _. a F
在上文中,我们已经成功的使游戏创建了我们需要的汉字纹理,不过仅仅有这1024个汉字纹理是不行的,还得想办法让它显示出来。前面说过,游戏会调用glCallLists显示显示列表,glCallLists原型如下:
% S% l6 D8 s1 A* R' R( t% D
, s& q$ z5 C8 r) Z; M, `
代码:
% f% R; a T/ ? N
void WINAPI glCallLists(
8 g3 v' w; C7 r
GLsizei n,
5 P( q% @7 E/ Y$ `. o# ^
GLenum type,
' U. \, j* w3 F* n! h, h5 E0 Q2 Z
const GLvoid *lists
* \5 i5 |" L- R
);
* J! U( ~7 q: g# {1 U+ k! h
其中n为字符串长度,type为字符串类型,*lists为字符串指针,glCallLists函数下断调试发现,游戏在调用glCallLists时第二个参数使用了0x1400,即GL_BYTE,表示单字节:
+ S6 [) ?" j3 d T) A0 i
; G6 @+ I( S6 N5 J. m- S
代码:
/ L5 C+ c- Y3 A; z
00439D3D |. BB 00140000 mov ebx, 1400 ; 第二个参数,GL_BYTE
5 v- q; j) \5 z9 D/ z C. g
00439D42 |. 890424 mov dword ptr [esp], eax
% m% Z7 B9 I5 p( `: u
00439D45 |. E8 464B0500 call <jmp.&OPENGL32.glListBase>
* d _) _9 d" ]! i
00439D4A |. 83EC 04 sub esp, 4
6 e x" [3 q {- _# Y ]! v
00439D4D |. 893424 mov dword ptr [esp], esi ; |
) J, J) q" n1 ~
00439D50 |. E8 136D0800 call <jmp.&msvcrt.strlen> ; \strlen
2 n) ?& g- \- I' o) n" b6 {
00439D55 |. 890424 mov dword ptr [esp], eax ; 将strlen结果作为第一个参数
: z' g$ [* Y" q) h4 d& B: X: v: g
00439D58 |. 897424 08 mov dword ptr [esp+8], esi
& {2 ]! ]; }- Y9 U X' x, n
00439D5C |. 895C24 04 mov dword ptr [esp+4], ebx
" Q. ]' y3 |4 O3 G* X- [( T
00439D60 |. E8 234B0500 call <jmp.&OPENGL32.glCallLists>
8 r: e* G. {" w9 Z5 u
因为游戏原有256个显示列表,字符串为单字节字符串,而现在我们要使用1024个显示列表,相应的字符串必须使用双字节编码,这里第二个参数必须修改,截取gl.h部分如下:
% h1 \' O. v7 L+ ^1 o( }
" q0 ?8 y K9 M5 C! k0 U$ k
代码:
5 } n# d6 B3 C8 ~+ S. Q- B/ V& F4 ^
#define GL_BYTE 0x1400
" V2 K, p5 T- P! ]/ q. M/ J
#define GL_UNSIGNED_BYTE 0x1401
# G: U% d( w2 O7 }& w7 F
#define GL_SHORT 0x1402
6 `) u- U9 w/ w
#define GL_UNSIGNED_SHORT 0x1403
- K" e! O9 [9 E* G
#define GL_INT 0x1404
; ?, R; J2 B0 W, A) O$ A+ | Y0 U
#define GL_UNSIGNED_INT 0x1405
8 w/ W: ?( J5 [ _
#define GL_FLOAT 0x1406
! N3 C8 N+ T" ~: z8 R& N2 i5 w
#define GL_2_BYTES 0x1407
6 O4 e& a- {3 d; i5 L) H0 x: N
#define GL_3_BYTES 0x1408
* l* o1 J K r% q c6 k
#define GL_4_BYTES 0x1409
/ y9 Y" C+ k0 X2 r# U; v
#define GL_DOUBLE 0x140A
( o! S0 y# P5 P( n0 ]3 j
无符号双字节为GL_UNSIGNED_SHORT,所以应将0x00439D3D处指令改为mov ebx, 1403。接下来修改第一个参数,第一个参数改起来比较麻烦。原游戏使用ascii码所以可以调用strlen,而我们使用双字节字符串,那么必须使用wcslen来求字符串长度,而导入表里没有wcslen,这时候我们必须自己加入一个导入函数。这里为了省事我使用了pe工具stud_pe,wcslen位于动态链接库msvcvrt.dll中,增添该函数后如图:
9 c" N V0 Z" z; l; ?' e8 J
{% W! z5 _" u0 _9 j
图中可以看出,导入函数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永远得不到正确的字符串长度。
# T# ?/ c* P/ Y: ~, u' T
不过这很容易解决,我们只需要在代码的缝隙中找到一块6字节的空白区,然后加入指令jmp [0xB05214B],再call到这条新加入的指令,就可以顺利解决了。我将这条指令放在了0x439c36,然后修改0x439d50处的指令为call 0x439c36,问题解决。如图:
- l. p- ]$ t* ]9 h, g# t& m M
[attach]18537[/attach]
0 U2 T1 d% @- B
至此,对游戏引擎的修改已经结束。
6 i% A _3 t$ Z+ `9 e
剩下的工作比较简单了,将游戏的英文文本找出并逐一翻译。统计翻译后的中文文本,也就是统计使用了那些汉字。然后制作一张32格*32格的字库,其中前128个位置放置游戏原有字符并与游戏原来字符顺序保持一致,以便能够正常显示一些特殊字符,后896个位置依次放入统计出的汉字。按照字符在字库上的位置制作一张码表,然后按照码表将翻译后的文本进行编码转换,再将转换后的结果导入游戏。
* F) T& [- P; i2 g' `% h
事实上,我有意无意地淡化了在分析调试过程中遇到的种种麻烦,因为事后想想也不过如此。逆向是非常痛苦的,因为下一步总是不可预料的。任何一个小小的麻烦都有可能导致前功尽弃。然而逆向的魅力也正在于此,当咬牙踏出那一步之后,回头看看自己的脚印,我想这都是值得的。
" Q8 z2 Y! A& V) o9 {
最后附上一张中文版截图:
. n* ^+ n+ _8 f0 z8 [
[attach]18536[/attach]
欢迎光临 冒险解谜游戏中文网 ChinaAVG (https://chinaavg.com/)
Powered by Discuz! X3.2