学习一下人家的破解过程,这个游戏是开源游戏引擎OGRE进行开发的喔。
; d1 q: Q Y* @3 h
5 f+ [$ w1 w# i( C8 F
, a' n9 ?' n: E2 C: y- M$ P4 }% \+ y
发信站: 饮水思源 (2007年06月22日02:36:37 星期五)
% b2 k! U$ E, Z/ R* ]7 r$ c I b* t; ?: ~+ j
作者:[email protected]
4 [" g6 D7 A: P) c4 X/ i- a
. n3 @6 {% g! T' e2 Y# J1 q三月份时玩了某狐公司的网络游戏《天XXX》,感觉还是蛮有意思的,遂研究了一下。
9 ~. v' T9 W0 O8 ~这个游戏是利用开源游戏引擎OGRE进行开发的,看了一下目录里面的文件结构,主要的数 8 m6 E7 T# B0 l% I: e& e" X
据都放在Data目录下面。不过文件基本都是.AXP后缀的,每一个动辄几十兆,料想肯定是
* |( a1 F- ?$ K2 N' g9 c. C把游戏文件打包到一起并加密过的,GOOGLE未遂。开始用UE打开看了一下这个AXP文件,发 8 P0 {3 z; ~. z6 L6 j& ?& e
现里面居然大部分都是明文的,开始以为只是把文件罗列在一起,不过仔细看了一下,发
% _9 s& d7 p9 \0 @1 U% V- m" P) u现每个文件都有一段间隔,前面还有一个数据头,而且文件与名字也无法对应。于是打开
1 G; [/ Z9 A0 w& o6 GOD手动分析一下,主要过程其实比较简单,CreateFile函数下断,找到文件Buffer位置,
/ }# W' `7 }% W6 t6 Q再下内存访问断点即可来到关键代码区域。略过具体跟踪细节及文件校验部分不讲,文件
9 D) z; z6 A; g7 u; }( C6 S2 j格式主要分析如下: " L1 W" t% I, q5 Y
: ]9 j# d7 F! `8 _* u
整个AXP文件可以分成四个部分:1.文件头 2.文件名索引 3.文件索引 4.文件数据 ! ]- U7 k6 Q- {0 C! N% ?
; H! a3 ?+ [6 O8 S+ m0 l$ ]' c$ B. \5 j
1.文件头: - v; D8 W6 k# n: e# i% @ K# L: `
整个文件头固定为0x28字节,其中第20个字节开始的一个整数乘以12代表了第三部分
; Y* K+ H$ y" g9 q: J" u9 y7 U即文件索引部分的长度(因为每个索引有三个整数构成) ! m' W3 J8 K1 T# |
2.文件名索引: ' z) Y5 }* | C5 r( J6 S6 g
整个文件名索引固定为0x60000字节,其中包含了每个压缩文件对应的文件索引位置 0 Y6 I% T x: n9 y
3 S" Y: e. V9 K9 ^/ Q
3.文件索引: & `" X4 I9 {4 l3 c H5 ], f* @
本部分长度由文件头相关数据决定,其中包含了每个压缩文件在.axp中的实际偏移位
@' e. n- Q! T置及文件大小
! x9 w. D- Y, r( u) M4.文件数据: ( S- T; [2 S# p! j- |) M* }
本部分包含所有压缩文件的具体数据,每个文件之间用若干零填充。
- | d6 X- x5 P$ ?; _9 C- K
! `% G* w! p4 ~ ]$ G0 }首先说说解压总体过程:比如我们要从A.axp中解压出一个叫file.txt的文件,那么先根据
: \, ~/ J/ y# f3 O' y6 p文件名file.txt到文件名索引中去找到对应的文件索引,然后再根据文件索引找到这个文 4 {4 f3 L& _7 F( x& j. h
件在axp文件中的位置和大小,最后把其解压出来。 + G$ M! b, i: _/ m
# F W; U. L' f# J1 ]
解压具体过程如下: " @! ?4 D/ T6 ?( {' o
将待解压的文件名转为小写(如果为英文字母),利用GetDisp(char* s,int v)函数
; ~& \" B' q- T! A/ \* ]计算相关数据,其中s代表文件名,v代表计算参数,分别计算GetDisp(fname,1),GetDisp 6 i! K1 ^1 u6 p- z. Q! C
(fname,2),
- w( q, Z1 [" _" q5 k+ tGetDisp(fname,3),得到三个值a1,a2,a3。其中a3低位与在文件名索引中的位置有关,a3最
0 e1 }9 v" J4 j. U3 d! `. k高位及a1,a2用来进行校验,如果三个值不能同时满足要求,则将偏移位置顺移继续验证, % w6 ]" x7 y1 {$ t! e0 ^! ^& M( E
具体细节懒得写了。 1 V5 x0 { }3 H8 k" W3 \
以下为GetDisp函数具体内容,我直接将跟踪代码里面的汇编改造了一下拿出来用,其中s
, u" r2 `, Z; I* Oucks为一个随机数数组,这里不列出来了。
) H2 t1 y% r( ]/ C2 f9 Runsigned int TLBBUnpacker::GetDisp(char* s,int v)
% W( k0 Y1 L1 C1 l{ / l; |/ z3 _, C# y5 R
__asm
7 `4 y# L$ D9 R, E9 G4 X- ?{ ' R3 p1 B0 ?* o; M/ v
push esi
9 b( N- C7 f: `" Bmov esi,s 9 A3 [7 B) P8 M ?
mov cl,byte ptr ds:[esi] - j- i' j& B9 h, D" @1 x% h( W W
test cl,cl , W( p* B0 f' @2 ] s
mov eax,0x7FED7FED : q; K' w, v/ r
mov edx,0xEEEEEEEE
! `2 e( j( [" f0 ^. }. Sje end ) \9 c7 \: o, a& Z
push ebx
* x2 @$ h; S4 R8 N* ^) f# A2 Spush ebp
$ v- u' e5 M3 F! V% H) }. `. ]& qpush edi 6 i; ~/ z o3 c4 Y0 e* p% q
mov edi,v
- _% o( ~4 x) L5 u+ Y. Pshl edi,0x8
2 P1 c# q7 J5 I; }iter:
8 \1 }! i3 V9 @# ]. J: I. \$ M0 ~, ~* @add eax,edx 3 V: e8 v" N0 k2 z" ]! U* _
imul edx,edx,0x21 1 u$ q- {# f `! J: t8 u
movsx ecx,cl
& u. |. W* i0 x; {; a ~lea ebx,dword ptr ds:[edi+ecx]
2 g1 F. G: D) w/ ?mov ebp,dword ptr ds:[ebx*4+sucks] " ~+ M& k3 h% r+ b* v( X
inc esi 8 x' U$ M! l4 f" O% w
add edx,ecx 9 w& o* f6 z: Y7 P* E; J
mov cl,byte ptr ds:[esi]
+ d! n; F4 P; X Lxor eax,ebp & }3 T0 O- x/ {+ x5 s
test cl,cl
9 a! }* k# D( Q7 D" xlea edx,dword ptr ds:[edx+eax+3] 3 e9 f: l( B" U* }# v6 @" W
jnz iter : ?8 q* {6 }3 Z1 Q: f
pop edi
' H5 f! D8 [3 I- Xpop ebp 4 E* p) w! B0 F8 r: w. _8 g" f
pop ebx
4 L( R# P5 G v' o+ Eend:
* X4 O) t5 d# K4 Mpop esi 4 ?0 r5 y) J6 c# G+ B
} 2 s( p* C9 G8 ~
} / L, h9 u- Q( d
这个为解压单个文件的函数GenerateFile,用到了QT作GUI,大家就当伪代码看吧。
; I+ H8 M* Y0 U) }! _; Zbool TLBBUnpacker::GenerateFile(QString name) ; X H; L, W; H* x$ s1 _
{ 4 N) T+ {/ W7 O, d) Q2 C9 q
name=name.toLower(); ( z$ ~( r1 B" j6 P( {; N8 w L% O
unsigned int a=GetDisp(name.toLocal8Bit().data(),3),a2=GetDisp(name.toLocal8B
% R6 c8 Y/ }$ ~( y- _it().data(),2),a1=GetDisp(name.toLocal8Bit().data(),1),b,disp,length;
1 e' j- a" T( b) n0 Q9 ^* c. Fa&=0x7FFF; % H6 j! F- n2 \$ r- ~ ]. u7 \8 q7 D
while(!((b=((int*)buffer2)[a*3+2])&0x80000000)||a1!=((int*)buffer2)[a*3]||a2!
. |' ^6 B( G: R t# v% w=((int*)buffer2)[a*3+1]) $ u3 x% z5 c& j! _- ^
{ . z! ?% }9 b* U! t
a++; 0 q& m# _' K1 y. Y( Z
a&=0x7FFF; 1 {" b, \+ a6 o. Y4 @0 F* h5 R
} . v5 X4 ~6 c; I
b&=0x3FFFFFFF;
; _, G8 a7 f) c" F4 }0 e. o7 mdisp=((int*)buffer3)[b*3];
4 S, S4 ?: A' N( V0 D o" Wlength=((int*)buffer3)[b*3+1];
% ~* j4 Q( `$ x1 m) Y# C- }) d. ^& n' K7 I5 K. F1 H2 k
QFile pdata(this->package_name);
; a4 V( A' P, B7 F0 w3 tif (pdata.open(QFile::ReadOnly)&&pdata.seek(disp)) { 0 x# p4 J) B1 g! t, C% ^
7 p1 q5 X: `$ |9 N1 T. N* E
QString wdir=QDir::currentPath()+QDir::separator()+QFileInfo(pdata).fileName ' B3 u3 o/ _7 X1 z6 f$ W
()+QDir::separator();
4 u' b6 y# T# v% H( |" e( Z( dQDir dir;
6 f) F0 F1 p' c4 T YQDataStream pfin(&pdata);
; P# r; F% Z$ c* FQFile file(wdir+name);
" U$ A1 q' V" p( v# V* sQFileInfo info(file); ' D8 P3 t8 }& ^) U0 d
dir.mkpath(info.absolutePath()); 9 l$ T" {5 ?6 G8 J( _8 l
if(file.open(QFile::WriteOnly)) + t( l' g3 ~9 `9 b. N( G
{
9 h6 n& o) Y! p" zchar* pBuffer=new char[length];; 5 \# Q5 x) q& q% L7 O
QDataStream fout(&file);
7 B& l7 o) }2 c& cpfin.readRawData(pBuffer,length);
( i s9 i# Z0 P4 ?- P. {fout.writeRawData(pBuffer,length); 7 Y( }3 J, p- E% g
delete []pBuffer; 3 i: p, ?8 G. A
return true;
6 K, \& b" R8 @3 }- Z* v+ M}
5 h3 ^2 I0 y4 W3 b5 L( u} ) l$ ]# D/ D# X& p# A! @- V
return false;
5 e5 t5 L0 t' B( U2 [& Y0 b, C! s, Z} & d4 r2 {' l$ l6 C+ y0 _2 w$ V
得到待解压文件在文件索引中的位置後就可以找出该文件在axp文件中的具体偏移量和文件 8 d0 \' M7 {/ Z/ u* w9 Q/ q, A
大小了,然后直接fseek一下然后在弄出来就OK了。
1 A/ B! x! M, A3 a4 e- [0 L最后说一下,这个AXP压缩包本身就含有一个文件列表文件叫做(list),所以每次只要先解
2 n$ ]5 C0 H/ u0 M压缩这个文件,然后按照里面的文件列表来一一解压缩就OK了。
- v" ~- J3 m! X: x9 B2 W
' a9 m3 S" M% \. l9 ^7 f6 s
' e7 }. _$ t8 L, F! D8 D8 w! \以上就是文件大致格式,感觉还是比较简单的,也可以考虑在自己的项目中使用类似方法
$ L( U3 Y! H) ^; y进行文件压缩。 - p' E0 A# U6 X! L) U
o/ r% i1 m8 m
PS:本文仅供学习,本人不负任何责任。。。。 |