众所周知,很多游戏的图片文本影音等资源,都是经过打包、加密或者压缩的,那么为了获取这些资源,只能对算法进行分析,写出相应的破解程序,从而达到资源提取的目的。 - [" D7 H' Y9 d* g' _# g
m, {! L& R1 i3 N
但目前来说,使用公开的已知算法来加密或压缩的游戏,毕竟是少数,大多游戏厂商都会对已知算法进行一定的修改或者干脆自己发明一种算法,这时想要破解就没那么容易了。还好为了速度上的考量,这样的算法一般都不会很复杂,我们还是可以通过对程序进行dasm来进行分析破解。 5 l. }# G7 ^9 B' Z* h i
) V5 [3 P9 @: `) M7 w$ z4 O" G; n/ P
昨天受朋友之托,让帮忙破解pc版ys2的文本文件"libre.ys2",花了10来分钟搞定,虽然无论从分析还是算法的角度来说,都属于非常简单的级别,但是对于从未接触过dasm破解的朋友来说,却无疑是一个很好的试刀例子。本文通过描述此次的破解过程,讲述了dasm破解游戏资源的一般思路,希望能达到抛砖引玉的效果。 # N f7 t+ q2 j: W2 P
3 Q8 a6 Y, a5 A$ O9 m. s8 h
& ], P( E. X: v1 `, @: n--------------------------------------------------------------------------------
, [3 r N6 s9 y4 r9 p! U/ I( n$ z1 A3 b5 C, L) j3 f. i( n, |
本人比较懒,既然说是简易教程,就一切从简。。。
1 ?$ @+ g7 @6 h, [! c" m: d0 Z: g9 \8 @ H* B- c
拿到文件首先分析文件头:
+ q$ @' X- ~8 O4 t6 q7 ]; M* q23 C8 14 00 AE 63 06 00 AC 07 97 3E 02 4F 9C 71 ; y) f, C& _+ n# Q0 Q
; ?! p3 U% L/ m K4 D+ n4 }" M
这是文件前16个字节的内容,很明显,前面8个字节,也就是2个dword马上引起了我们的注意。因为这个文件的大小是0x663B6,而0x000663AE = 0x663B6 - 0x8,那么可以推断,0x000663AE就是数据部分的大小,而前8个字节就是文件头。那么还剩一个0x0014C823是什么呢?因为文件里搜不到明文文本,说明不是被加密就是被压缩过,做过破解的可能都会根据两个数值之间的关系,推断0x0014C832就是解压后的数据大小,看起来也是很合理的,我起初也是这么判断的,目前就先放一边吧。 8 ~: q8 y5 W- E8 l
: }2 l* S9 r w: H i
再让我们看看数据部分,杂乱无章,没有任何已知算法的压缩头,也不像是lz系列的压缩,看来再看下去也是徒劳,只能祭出dasm了。 ; X# c/ b) ]( p( k' h
U; G- W- A5 Q, W% E" E$ t由于pc上有众多调试器,所以最好的做法是进行动态跟踪来找出算法,不过前面说了我比较懒,抱着侥幸心里先用ida试了试静态分析,看看能不能有所斩获。关于ida的下载安装使用等本文概不提及,有兴趣的朋友可以自行查阅相关教程。
8 t6 f: `; v9 a: v4 ]: V- m( }/ d4 u; d2 [
由于存在数据大小,因此可以判断这个文件是单独使用的,那么首先想到的就是到字符串列表里查找文件名,然后看哪些程序用到了它。 4 B4 Z( |0 B6 }* T: j$ k/ L; L
/ M( {" c ~. \3 d+ ?7 P
打开ida,加载"ys2_win.exe",在strings里搜索"libre.ys2",果然找到如下字符串: / a+ F& e. B! \* S+ t
_rdata:004B8798 0000000F C DATA\\LIBRE.YS2
7 _; j$ X8 Y- ~6 B
( M; g" X3 L3 o5 P) Y" l双击来到其地址处:
5 k+ ?0 e* i5 A0 i. ]_rdata:004B8798 aDataLibre_ys2 db 'DATA\LIBRE.YS2',0 ; DATA XREF: sub_0_443030+39o
( K! d! g( E+ E, z w# v
3 a. S7 u7 M% p0 j) q% b4 W可以看到,很幸运,只有一处进行了引用,下面转到sub_0_443030+39处:
. V; r8 b7 L1 }9 `
% a9 A1 v/ Q- ^C++代码
2 C* E$ \' M( c5 z2 @2 b2 N_unknwn:00443065 push 0
$ p! n; [, e# R_unknwn:00443067 push 0
/ j7 t$ M5 E) N n0 ?# K_unknwn:00443069 push offset DataLibre_ys2 ; "DATA\\LIBRE.YS2" $ y$ R6 c5 R+ z
_unknwn:0044306E push 80000000h 3 H0 P4 B }! T9 @. N; Z
_unknwn:00443073 lea ecx, [esp+140h+var_124] ' O3 |, A8 c, Q) A0 @
_unknwn:00443077 mov [esp+140h+var_4], 0
( t% i3 a# n) t9 Z_unknwn:00443082 call dword ptr [eax+10h] % n9 f; F- g: t4 c5 g
_unknwn:00443085 lea ecx, [esp+140h+var_134]
% t$ M5 ^2 c# p! v, }' j/ b. ~0 I_unknwn:00443089 call sub_0_44B780
S+ h% m7 I* ^( _% W% W$ R% a_unknwn:0044308E mov esi, eax 5 U8 q+ H1 d- _2 @0 j, U# _) U K
_unknwn:00443090 push esi ) G, \4 o2 U! A% r: s U
_unknwn:00443091 call ??2@YAPAXI@Z ; operator new(uint) " B) N7 t9 p: ~: Q
_unknwn:00443096 add esp, 4
$ Y) r" [" h3 e. N+ n/ E% d& i7 B& A_unknwn:00443099 push esi ; nNumberOfBytesToRead ' l. K6 Q; W- u/ o0 x: X
_unknwn:0044309A push eax ; lpBuffer ) ]5 Z- T2 k" i
_unknwn:0044309B lea ecx, [esp+148h+var_134] & V0 B W% P: N6 `* A0 J
_unknwn:0044309F mov dword_0_5FA6B4, eax 9 Y J6 M1 Z# Y& k1 p0 i
_unknwn:004430A4 call sub_0_44B6B0 " a" y: J r2 V% k) q" s G
_unknwn:004430A9 mov eax, dword_0_5FA6B4
' B, @; C# c9 R- p2 q) r* f" A& b_unknwn:004430AE mov edx, [eax+4] / a' J4 y& ^0 H3 _
_unknwn:004430B1 mov ecx, [eax] 6 ]2 l# }) r, R2 \9 h& }
_unknwn:004430B3 lea esi, [eax+8]
- p' |$ I& G5 l3 v_unknwn:004430B6 add edx, esi
0 T+ t4 t) R# L' y) K1 W_unknwn:004430B8 push edx / o) m: ?1 @6 ?3 x' i: g$ \
_unknwn:004430B9 mov edx, esi H& t0 D) v. }! r- h2 K1 @: x
_unknwn:004430BB call sub_0_492580
) r" z6 N( V0 I' m" r
k* P. _3 e7 r9 L9 B9 Z对于asm的分析,没有什么捷径可走,只能是耐心+经验+运气。 5 o% T3 H9 e. A1 y. o4 M; \
( L( W/ o7 l3 s) K
下面简单分析一下这段程序: " i0 g; \. c( a1 F& ?5 e
7 r/ _5 B1 u& B( G# J& a( h8 I' R
第3行,对"DATA\\LIBRE.YS2"这个文件名进行了压栈,作为后面函数的参数之一。 + u6 ~( C! O* X
. V4 D5 P2 `) ]/ L, f- O6 U: t
第7行,"call"调用目的函数目前弄不明白,跳过。
9 d! k; e" E3 R4 C8 h k0 c' t( S3 h0 _& D8 p8 b% w& Y- ~
第9行,"call sub_0_44B780",追到sub_0_44B780里,发现调用了系统API GetFileSize,返回值保存在eax里,所以eax里现在就是文件的大小。 * a4 b8 ~( x0 G+ W
9 d$ p; j; f; S i3 Z! _第10-12行,用"new"进行内存分配,分配空间大小就是刚才获得的文件大小,说明这里是分配一个缓冲,用来加载文件。 . T0 ~& E9 q0 C
9 ]) c. ~" E# T8 `/ V第13-18行,压栈一些数据,为函数"sub_0_44B6B0"传递参数,追到这个函数里,发现调用了系统API ReadFile,说明此处是把文件读取到内存的缓冲区中。此处注意,第15行很明确的说明了此时eax就是缓冲区的指针lpBuffer,如果之后要进行解压或者解密,基本上肯定要从这里取得原始数据,因此必需时刻跟踪这个指针。第17行,eax被传到了一个地址上,可以看成是一个临时变量用来暂存lpBuffer。
: g. _5 {: K1 p( _8 c; [/ b9 u: |: h- K- M+ {; C5 s
第19行,把刚才暂存的缓冲区指针lpBuffer传回eax。
! }: N1 l( |2 M/ I" Y5 Q9 _2 s' \4 [; I
第20行,有意思的来了,想起来了么,[eax+4]就是文件第2个dword,也就是数据部分的大小,传到了edx,看来我们接近目的地了。
' H+ K2 \% B9 R/ _# ~. b# W% F7 N/ o$ p6 k! A4 C6 u) P
第21行,第一个dword传给ecx,之前推测是解压后的大小。 2 \6 ^0 ^! `/ A7 Q
7 S. h. Q( z! G
第22行,注意这里lea和前面mov的区别,这里是传地址,esi现在实际上是个指针,指向缓冲区中的实际数据部分。 * T. J' x6 y B+ m
* t8 A3 z' Y% F4 a+ W
第23-25行,这里的edx=edx+esi,实际上是得到一个指向数据末尾的指针,猜想是用于循环计数的结束标志,并且将这个指针压栈,作为参数传递给后面的函数sub_0_492580。然后再把lpBuffer送edx。一切准备就绪。 % O% p9 y$ ^- S, Y V/ }
- P: y9 U3 b* H现在应该可以判定,sub_0_492580就是用来解压或者解密的函数。不用犹豫,追进去,实在是太熟悉的程序模式了,如下:
, l! K2 ~& n( c3 G7 c+ O# ^# s$ ]
0 X- }& e3 \' S8 a" sC++代码
- W: H+ ?* R' v) a- |; h4 e_unknwn:00492580 sub_0_492580 proc near
! B5 Z' P( n* i& K6 Y. d H_unknwn:00492580
- E) V* i1 R# ` j4 r& L2 l% P_unknwn:00492580
- e. ]) q: g; U+ v4 x5 t# V1 u_unknwn:00492580 arg_0 = dword ptr 4 ) f$ A* J/ |( G1 z) d/ z0 j- f+ s
_unknwn:00492580
9 `6 `9 A* c: B9 @_unknwn:00492580 test ecx, ecx
( z' y, n; C" S: R, \( g0 c0 B_unknwn:00492582 push esi ; u8 {6 r( D2 m
_unknwn:00492583 mov eax, edx
+ ?0 N) Y/ c2 c& U* }_unknwn:00492585 jz short loc_0_4925B3 ( A$ m# [7 _7 C, m
_unknwn:00492587 mov esi, [esp+4+arg_0] 0 Y. d' E) q; y) ]
_unknwn:0049258B cmp eax, esi
$ \5 V9 W2 s8 Z' v_unknwn:0049258D jnb short loc_0_4925B3 % W2 Q0 q5 L- p+ {+ m0 c* z h; u
_unknwn:0049258F push ebx 6 {# J/ |0 `: N, Z$ m/ k, @9 f( V
_unknwn:00492590 - w1 `* H! ^" i$ b( Z* U: d$ j t
_unknwn:00492590 loc_0_492590: ; CODE XREF: sub_0_492580+30j
* \/ u! X- y# L; }1 e8 }) s_unknwn:00492590 mov bl, [eax] 8 Y0 t3 i2 C7 G; p7 a" O# ]5 H
_unknwn:00492592 lea ecx, [ecx+ecx*4]
, N6 d0 {- H& h! M+ t2 n; d+ x" N_unknwn:00492595 lea ecx, [ecx+ecx*4] " T7 m4 [( W4 E2 B! ^7 H9 ^8 ^4 s
_unknwn:00492598 lea ecx, [ecx+ecx*4] / \' C1 g8 \5 v$ E j8 k) H! L J2 C
_unknwn:0049259B lea ecx, [ecx+ecx*4] + Z2 j1 M% s- c0 j9 W
_unknwn:0049259E lea ecx, [ecx+ecx*4]
3 R! |1 o) s" K7 e O' i_unknwn:004925A1 lea ecx, [ecx+ecx*4] . a. `+ s$ s: Y% C v8 l e" m
_unknwn:004925A4 mov edx, ecx K. V4 i e! e4 e- y- V
_unknwn:004925A6 shr edx, 10h
( K. P, @, C) |5 W_unknwn:004925A9 sub bl, dl 0 t; h+ B3 g3 N. K% v4 g
_unknwn:004925AB mov [eax], bl 9 n2 Q* i& U% u2 H5 C9 i' X1 G
_unknwn:004925AD inc eax ; O: \1 m1 q8 ]' `* ?
_unknwn:004925AE cmp eax, esi 9 r o6 L2 Y& i1 q4 z0 h& k
_unknwn:004925B0 jb short loc_0_492590
% B1 W) H* ^8 B: }" y/ _, w; O" Q_unknwn:004925B2 pop ebx / f9 B: `" F% v2 p+ H+ G k3 T
_unknwn:004925B3
0 |2 E% k: x- E# M_unknwn:004925B3 loc_0_4925B3: ; CODE XREF: sub_0_492580+5j
: j% g; K8 Z3 |1 e/ ]_unknwn:004925B3 sub_0_492580+Dj ( s, D8 g3 l( G
_unknwn:004925B3 pop esi
8 ~$ P: y5 y T( y! n/ \_unknwn:004925B4 retn 4 1 F$ A! T! e: U* X! n
_unknwn:004925B4 sub_0_492580 endp
4 B' l# O5 m7 j5 V. R1 ^" z0 L q4 e: ^
这里的"mov bl, [eax] ....... mov [eax], bl"这个程序段实在是太引人注目了,以至于第一眼看到就几乎可以断定这是个解密程序。下面做简要分析: % c" f3 r0 \$ P3 N5 r- ~& H
, P+ B5 w( M; X6 @: N9 H第1-9行,一些合法性判断。第8行很重要,edx送到了eax,根据前面的分析,eax此时就是缓冲区中数据部分的起始地址指针!同时也是作为移动游标使用。 % e& z9 s( r" M. D
- x- N6 h- G' }4 B9 T# a" h第10行,获得传入函数的参数,即前面提到的数据末尾指针。送esi。 * x! X/ q @/ V3 x
1 d: T# S' E$ s. C
第11-12行,做了个比较,如果游标大于数据末指针,那么就返回。到这里就很清楚了,eax作为一个游动的指针,指向数据部分的单个字节数据,esi作为一个结束标识,两者结合作为循环结束的判断依据。 " Y3 T+ L! F' n1 L" c. e2 H
, H* \! x+ O# ?4 T$ x) Z7 x第16-29行,不用说了,明显是解密。第16行取出一个缓冲区中的数据,第29行则是把解密过的数据放回原缓冲区。中间的就是解密过程了。还记得么,在函数调用之前,ecx的值就是文件第一个dword的值,这里终于弄清楚它的作用,就是作为初始密钥!这里17-22行相当于"ecx = ecx * 5 * 5 * 5 * 5 * 5 * 5"(注意这里是lea),第23-24行是把得到的结果右移16位,并传入edx。第25行的dl,就是edx的低8位,即"dl = edx & 0xFF",这里用原始的数据来减dl,然后26行把结果写回缓冲区,这样就完成了一个字节的解密工作。第27-29行则是递增游标,然后判断数据是否解密完,做循环。
7 q! Y S7 a# m2 U s% l( o. S; m3 ^8 h& W8 G9 N* R. i+ L6 B
至此整个解密算法分析结束,如果这样你还写不出解密程序来,我也无话可说了。。。 1 h5 A8 T/ n0 i' }$ d
% ]4 Q; _) R j# ?. j
总结起来,dasm破解游戏资源的关键,就是密切跟踪用于加载文件的缓存区,以及对于该缓冲区内数据的读写操作,如果是取出了数据然后经过一系列处理又放回去的,一般就是解密了;如果只是取出数据,经过一系列处理以后,放到了另一个缓冲区的,很可能就是解压。总之,dasm是一件非常需要耐心的工作,asm代码总是枯燥的,要弄清各个寄存器的值也得花一番功夫,但是破解以后的成就感,也只有你亲自动手并完成以后,才能体会得到了。。。
0 V- |) I& |/ t: l* F1 ]) S
7 q( O5 ~" K7 Q0 J原贴9 m0 V# n9 ~6 l+ K" F+ d2 s
http://www.xinyuonline.net/blog/?action=show&id=37 |