本帖最后由 shane007 于 2011-1-30 14:08 编辑 / Y8 x9 ?1 O7 I# A+ v5 Y
0 ` y E0 r; k# I; i在上文中,我们已经成功的使游戏创建了我们需要的汉字纹理,不过仅仅有这1024个汉字纹理是不行的,还得想办法让它显示出来。前面说过,游戏会调用glCallLists显示显示列表,glCallLists原型如下:7 |4 B: x9 ?1 C$ \. e
# p6 w* x5 _- Z- r
代码:4 f6 f1 v0 X! a" F
void WINAPI glCallLists(
) y, r: r2 M |$ L2 v3 D; f GLsizei n,
3 D8 a, f+ F/ R# J6 Z6 t3 Y GLenum type,8 S N* B( h7 F$ l+ K
const GLvoid *lists, Q6 S2 ^4 j4 M
);* D7 M$ u- H3 n6 V0 Y2 h
其中n为字符串长度,type为字符串类型,*lists为字符串指针,glCallLists函数下断调试发现,游戏在调用glCallLists时第二个参数使用了0x1400,即GL_BYTE,表示单字节:' ~- t: {$ e- [3 @; Z: n
% N1 f0 u% T+ F, T, K! e! I
代码:
! D1 I# s2 ~5 C00439D3D |. BB 00140000 mov ebx, 1400 ; 第二个参数,GL_BYTE
2 N( R) u$ ~$ B/ Y4 |( H2 \6 L6 r+ f00439D42 |. 890424 mov dword ptr [esp], eax3 G4 W) g% f+ j. U% Z. I
00439D45 |. E8 464B0500 call <jmp.&OPENGL32.glListBase>
' V0 c+ m' t( ]& H: E% K# ~00439D4A |. 83EC 04 sub esp, 44 O) k5 }4 L7 `% E5 q
00439D4D |. 893424 mov dword ptr [esp], esi ; |& w8 Z0 D; }: L2 y) O7 v' D2 F
00439D50 |. E8 136D0800 call <jmp.&msvcrt.strlen> ; \strlen
d% I# W$ ]: z$ N( N! [6 k8 f2 C00439D55 |. 890424 mov dword ptr [esp], eax ; 将strlen结果作为第一个参数
" G- x9 e" G# h6 b. l; T" H% m9 b8 r00439D58 |. 897424 08 mov dword ptr [esp+8], esi" s- C9 m3 g- e. s$ r3 ^& f% s+ m
00439D5C |. 895C24 04 mov dword ptr [esp+4], ebx, s+ r/ v9 T! q+ E( k% `" H
00439D60 |. E8 234B0500 call <jmp.&OPENGL32.glCallLists>: Q/ i- s: a' o) F" E6 l8 n3 E
因为游戏原有256个显示列表,字符串为单字节字符串,而现在我们要使用1024个显示列表,相应的字符串必须使用双字节编码,这里第二个参数必须修改,截取gl.h部分如下:
7 ?; _( \# l4 `! `. r; U
6 t' g0 ~6 N5 X z# S代码:
4 `. n: v" ^0 g8 w, h& I: |#define GL_BYTE 0x1400
* Y- ]5 D3 f2 ?. ]#define GL_UNSIGNED_BYTE 0x1401- L3 E: E& F! r
#define GL_SHORT 0x1402% x. S0 ^( u6 }" A
#define GL_UNSIGNED_SHORT 0x1403
* _. K. A8 u# ^" ]& g#define GL_INT 0x1404, P' p8 m \# T) W( m6 d
#define GL_UNSIGNED_INT 0x1405
, W/ }+ |! d: w3 K# X; X2 b#define GL_FLOAT 0x1406$ q2 S0 c. v I2 O
#define GL_2_BYTES 0x1407" J9 t4 N- q1 t7 b* C- J8 H
#define GL_3_BYTES 0x1408: ^% R% `/ @, ^. c2 M2 G
#define GL_4_BYTES 0x1409
& j; c& \" [/ l$ b#define GL_DOUBLE 0x140A6 A, m0 x) L, L% _! t, z
无符号双字节为GL_UNSIGNED_SHORT,所以应将0x00439D3D处指令改为mov ebx, 1403。接下来修改第一个参数,第一个参数改起来比较麻烦。原游戏使用ascii码所以可以调用strlen,而我们使用双字节字符串,那么必须使用wcslen来求字符串长度,而导入表里没有wcslen,这时候我们必须自己加入一个导入函数。这里为了省事我使用了pe工具stud_pe,wcslen位于动态链接库msvcvrt.dll中,增添该函数后如图:# W @& \' n6 |
( N6 B# N0 Z9 Z! ^! f% e5 T+ g7 @ 图中可以看出,导入函数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永远得不到正确的字符串长度。 x. _/ P1 W3 S
不过这很容易解决,我们只需要在代码的缝隙中找到一块6字节的空白区,然后加入指令jmp [0xB05214B],再call到这条新加入的指令,就可以顺利解决了。我将这条指令放在了0x439c36,然后修改0x439d50处的指令为call 0x439c36,问题解决。如图:
2 ^0 n3 r7 @/ K* B" y$ P& }: y6 g
至此,对游戏引擎的修改已经结束。
$ U" @" F( r( n% i 剩下的工作比较简单了,将游戏的英文文本找出并逐一翻译。统计翻译后的中文文本,也就是统计使用了那些汉字。然后制作一张32格*32格的字库,其中前128个位置放置游戏原有字符并与游戏原来字符顺序保持一致,以便能够正常显示一些特殊字符,后896个位置依次放入统计出的汉字。按照字符在字库上的位置制作一张码表,然后按照码表将翻译后的文本进行编码转换,再将转换后的结果导入游戏。- ]. {9 G3 L6 v5 k6 r
事实上,我有意无意地淡化了在分析调试过程中遇到的种种麻烦,因为事后想想也不过如此。逆向是非常痛苦的,因为下一步总是不可预料的。任何一个小小的麻烦都有可能导致前功尽弃。然而逆向的魅力也正在于此,当咬牙踏出那一步之后,回头看看自己的脚印,我想这都是值得的。. u1 T# U7 h3 I( e+ Z
最后附上一张中文版截图: % f( P( L" X+ f, }* x `2 n/ p3 }
|