本帖最后由 shane007 于 2011-1-30 14:08 编辑 , o# b! j, s( a8 S, Y5 Y
- r) o1 b" U/ E, T; Z: ~ T) `
在上文中,我们已经成功的使游戏创建了我们需要的汉字纹理,不过仅仅有这1024个汉字纹理是不行的,还得想办法让它显示出来。前面说过,游戏会调用glCallLists显示显示列表,glCallLists原型如下:
* a ^ z8 ~: E) V" n) t0 f6 s: L& U+ M% O' Q
代码:, s3 }1 s$ w/ }8 g I- v
void WINAPI glCallLists(& I1 v& Z: D+ ~4 M5 z
GLsizei n,1 T# v; {' [' T; l# }4 f
GLenum type,2 d8 C/ w9 ]! w( B _) f
const GLvoid *lists
( _; D' j1 I8 g& K/ q);3 Q* L/ z' x; A- [ n
其中n为字符串长度,type为字符串类型,*lists为字符串指针,glCallLists函数下断调试发现,游戏在调用glCallLists时第二个参数使用了0x1400,即GL_BYTE,表示单字节:2 ~$ _! H& @$ h4 X$ k1 G
, y& n/ o7 b) P" b. D代码:6 |2 L/ { Q& _
00439D3D |. BB 00140000 mov ebx, 1400 ; 第二个参数,GL_BYTE4 N1 I, n6 L( n
00439D42 |. 890424 mov dword ptr [esp], eax
( b" F' K- |3 ?+ C6 Y7 e00439D45 |. E8 464B0500 call <jmp.&OPENGL32.glListBase>
8 C5 P$ W, g& \! k" ^4 l' W00439D4A |. 83EC 04 sub esp, 4
/ \: g9 M" |+ N2 Y& ~9 q: V" o3 x6 i# V00439D4D |. 893424 mov dword ptr [esp], esi ; |
7 T8 z4 ]8 h; G. K3 e00439D50 |. E8 136D0800 call <jmp.&msvcrt.strlen> ; \strlen
/ E7 Y! `$ r# H- G9 R00439D55 |. 890424 mov dword ptr [esp], eax ; 将strlen结果作为第一个参数5 b2 N' E4 o7 c6 i
00439D58 |. 897424 08 mov dword ptr [esp+8], esi1 A7 q# T* h5 D F
00439D5C |. 895C24 04 mov dword ptr [esp+4], ebx
1 y+ o9 y; l! {/ m) D; e00439D60 |. E8 234B0500 call <jmp.&OPENGL32.glCallLists> g3 N" ?7 n0 M5 t5 U4 @! s' D) u
因为游戏原有256个显示列表,字符串为单字节字符串,而现在我们要使用1024个显示列表,相应的字符串必须使用双字节编码,这里第二个参数必须修改,截取gl.h部分如下:& w# t [5 ^2 ^1 ?
% \6 F7 d+ K6 e
代码:. {- g% a; R* d+ p" U8 y3 G
#define GL_BYTE 0x1400
5 w) @5 }6 f8 O" u" s#define GL_UNSIGNED_BYTE 0x1401
: ~" L. {1 l6 k- O9 ]) q#define GL_SHORT 0x1402
+ k2 q- c% z m& H#define GL_UNSIGNED_SHORT 0x1403
, }" q; ^& j- i$ ]- P' ]* [* k# Q#define GL_INT 0x1404
0 n& B* F+ q6 G#define GL_UNSIGNED_INT 0x1405 e4 N9 T8 f4 P. B" Z
#define GL_FLOAT 0x1406
1 S, T9 C" e7 i. f#define GL_2_BYTES 0x1407
; w0 x1 J( |! G8 z#define GL_3_BYTES 0x14081 g8 Q- N) L- P; G. \
#define GL_4_BYTES 0x1409" Y: A$ ^- f! T
#define GL_DOUBLE 0x140A. Y" K% x) ^8 A) O, o
无符号双字节为GL_UNSIGNED_SHORT,所以应将0x00439D3D处指令改为mov ebx, 1403。接下来修改第一个参数,第一个参数改起来比较麻烦。原游戏使用ascii码所以可以调用strlen,而我们使用双字节字符串,那么必须使用wcslen来求字符串长度,而导入表里没有wcslen,这时候我们必须自己加入一个导入函数。这里为了省事我使用了pe工具stud_pe,wcslen位于动态链接库msvcvrt.dll中,增添该函数后如图:
, q; j, h& R$ G7 r0 u) }3 q; X8 Q" l' h u' ]! L
图中可以看出,导入函数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永远得不到正确的字符串长度。
! _- q4 ]8 x- G6 L+ f, ?% A 不过这很容易解决,我们只需要在代码的缝隙中找到一块6字节的空白区,然后加入指令jmp [0xB05214B],再call到这条新加入的指令,就可以顺利解决了。我将这条指令放在了0x439c36,然后修改0x439d50处的指令为call 0x439c36,问题解决。如图:
6 G) F0 I8 X2 R; G3 O1 g/ O$ z: |, x% X1 j
至此,对游戏引擎的修改已经结束。- E4 L E a5 d0 `1 i: J) u" \
剩下的工作比较简单了,将游戏的英文文本找出并逐一翻译。统计翻译后的中文文本,也就是统计使用了那些汉字。然后制作一张32格*32格的字库,其中前128个位置放置游戏原有字符并与游戏原来字符顺序保持一致,以便能够正常显示一些特殊字符,后896个位置依次放入统计出的汉字。按照字符在字库上的位置制作一张码表,然后按照码表将翻译后的文本进行编码转换,再将转换后的结果导入游戏。9 C4 ?5 | Q' u
事实上,我有意无意地淡化了在分析调试过程中遇到的种种麻烦,因为事后想想也不过如此。逆向是非常痛苦的,因为下一步总是不可预料的。任何一个小小的麻烦都有可能导致前功尽弃。然而逆向的魅力也正在于此,当咬牙踏出那一步之后,回头看看自己的脚印,我想这都是值得的。
* t3 P& f" h/ p, Y+ c 最后附上一张中文版截图: % P2 E9 m: O- V
|