学习一下人家的破解过程,这个游戏是开源游戏引擎OGRE进行开发的喔。 8 a8 _( X w6 Z/ K
' M, x/ e0 `# u
" A C# f4 ^7 \2 [7 [9 ?$ k7 k; K1 j z- N V
发信站: 饮水思源 (2007年06月22日02:36:37 星期五) 5 s% y* ?3 d \2 X
+ J6 [+ z9 o2 E& E作者:[email protected] 4 a3 R, e2 r o5 ]: Z, u$ i
3 a+ y; _" H! c9 U: _三月份时玩了某狐公司的网络游戏《天XXX》,感觉还是蛮有意思的,遂研究了一下。
1 F G. r( Y4 ]3 f4 \这个游戏是利用开源游戏引擎OGRE进行开发的,看了一下目录里面的文件结构,主要的数
5 X4 [% @8 i! X6 j; X8 p据都放在Data目录下面。不过文件基本都是.AXP后缀的,每一个动辄几十兆,料想肯定是
8 i, ~, e# [6 g8 z+ {把游戏文件打包到一起并加密过的,GOOGLE未遂。开始用UE打开看了一下这个AXP文件,发 - h( l+ |$ g. F
现里面居然大部分都是明文的,开始以为只是把文件罗列在一起,不过仔细看了一下,发
" W4 X: o& i. j6 P0 i) t现每个文件都有一段间隔,前面还有一个数据头,而且文件与名字也无法对应。于是打开 , ]. y: F( X+ U& C
OD手动分析一下,主要过程其实比较简单,CreateFile函数下断,找到文件Buffer位置,
# B( c; ~$ o7 c9 r再下内存访问断点即可来到关键代码区域。略过具体跟踪细节及文件校验部分不讲,文件 ' |# y1 _; C( c( _$ Y9 F. R
格式主要分析如下:
i: f6 U7 I _" r6 I6 b/ P4 G! I2 A( K8 j8 l! J; A; @) T4 E) t
整个AXP文件可以分成四个部分:1.文件头 2.文件名索引 3.文件索引 4.文件数据
1 _# c8 k5 k; S( H% C9 M! T2 J7 b. ]# T' `8 Y& u# ~
8 T# Y+ }6 r1 ]' D: l! M7 w
1.文件头:
+ ~8 |( e5 h& `& O 整个文件头固定为0x28字节,其中第20个字节开始的一个整数乘以12代表了第三部分
' T5 p8 h: [" H' `即文件索引部分的长度(因为每个索引有三个整数构成) ; B0 u& |, H" b. d! p/ j* X
2.文件名索引:
" M: s; y; C) @0 \. L' Y& o 整个文件名索引固定为0x60000字节,其中包含了每个压缩文件对应的文件索引位置 + ]; }, G O6 W; U9 q( O
+ X* B; C! @0 v, A# s a6 S. v6 U3.文件索引:
1 H% g" N0 r x1 @ O 本部分长度由文件头相关数据决定,其中包含了每个压缩文件在.axp中的实际偏移位
$ I' O$ t/ K3 C9 n8 T2 B置及文件大小
. }" C4 ~: i6 J/ Z/ B3 V% ]4.文件数据: 2 c3 l0 V: ^1 L4 E2 M5 W
本部分包含所有压缩文件的具体数据,每个文件之间用若干零填充。 ) d9 S2 O7 Y& C& j. l1 |! _# O
" U7 }2 b9 g8 D Z a
首先说说解压总体过程:比如我们要从A.axp中解压出一个叫file.txt的文件,那么先根据
/ W8 P2 {* @7 _5 P" M$ L文件名file.txt到文件名索引中去找到对应的文件索引,然后再根据文件索引找到这个文 0 t( c% w f5 a# V0 ~' Z
件在axp文件中的位置和大小,最后把其解压出来。 $ i" Y1 _* _! ]. ^7 ?- i
! L* V% s k. ?! N+ N# B# g解压具体过程如下:
, l2 Y; P! E: H2 h7 A 将待解压的文件名转为小写(如果为英文字母),利用GetDisp(char* s,int v)函数 " q- a. B% T$ {; Q {& j
计算相关数据,其中s代表文件名,v代表计算参数,分别计算GetDisp(fname,1),GetDisp
& e# Z( D" Y- n3 W T(fname,2),
. U0 N) j; }" @- sGetDisp(fname,3),得到三个值a1,a2,a3。其中a3低位与在文件名索引中的位置有关,a3最 ; y, w1 J% F" U/ ^) e9 \
高位及a1,a2用来进行校验,如果三个值不能同时满足要求,则将偏移位置顺移继续验证, 5 V5 P) ]. i5 `! V& U# h
具体细节懒得写了。
' w# Q# v/ l; N( \+ W9 G6 R以下为GetDisp函数具体内容,我直接将跟踪代码里面的汇编改造了一下拿出来用,其中s
5 O) h" h5 | _5 R; h% Cucks为一个随机数数组,这里不列出来了。 1 D7 t- U+ |5 x3 o, [' R
unsigned int TLBBUnpacker::GetDisp(char* s,int v)
1 j6 F2 R: g( [4 e4 t8 n/ q: Q{
4 Z9 v' h0 |, Y" _) J! |__asm j; h$ a1 K1 @# S5 W
{
# ]4 k- O! E* C* z, bpush esi . r6 r. Y `4 K- N1 H& d2 K
mov esi,s
$ h& r8 X* x1 c- @mov cl,byte ptr ds:[esi] " b6 R0 x1 r2 i7 ~3 g
test cl,cl 3 h! d; v0 L/ e" G/ C) h/ s" m& X
mov eax,0x7FED7FED $ ?! d9 r7 q8 u% W, u; q* n$ S
mov edx,0xEEEEEEEE ( C+ X( i+ f% V7 C, K
je end & R u) h2 o/ h8 F+ d% g2 t
push ebx
1 F- t& a; w5 Q/ \push ebp - ~ `! C$ D5 e' q2 j" K
push edi
, |7 }# o" a, Qmov edi,v
6 J2 \- v3 g5 {/ dshl edi,0x8 ( }, S; U- b" s# K' f
iter: 1 w2 Q2 @3 D6 @/ n( p3 s
add eax,edx 2 R0 h! f' U4 ]7 [' \9 L
imul edx,edx,0x21 ( ^6 M# c! M$ v; x' G7 o& S
movsx ecx,cl 3 P& T: a+ G, W; k8 D6 l, c0 r" p
lea ebx,dword ptr ds:[edi+ecx] + N5 d7 M! u( {7 B5 d
mov ebp,dword ptr ds:[ebx*4+sucks] 1 L/ D& e/ X' J2 T4 l' x+ K
inc esi
' ?; Q1 K- l' k, i! E; fadd edx,ecx
4 b& J% _/ S2 @6 Mmov cl,byte ptr ds:[esi] 8 _+ M: D. }! L5 N
xor eax,ebp
4 g# Q! O# f W/ K1 _test cl,cl : W9 Z4 g, Y7 G0 H2 ?+ F3 a2 V1 G
lea edx,dword ptr ds:[edx+eax+3] % X' e+ |8 h8 w2 y
jnz iter 7 `+ M8 o! K; Z$ l# h' ? i
pop edi & d) u" k4 `6 p, R+ Y, _6 ^4 P
pop ebp
) c0 b0 v; l- E% W( R! k/ spop ebx C; f! U X" D* h" A0 ?
end:
* |5 L$ O% K" Z9 ^; ]6 S. E" Tpop esi
[- o/ T( m: i6 _. ^} 3 s/ u& D& {' }. J+ ~3 V5 H+ v' X( ?
}
( K$ r* r! m4 `3 q7 Q这个为解压单个文件的函数GenerateFile,用到了QT作GUI,大家就当伪代码看吧。
! I$ V( E/ V% [/ A3 n1 _2 U' ^bool TLBBUnpacker::GenerateFile(QString name) ; g+ f+ m# S1 P/ Z
{
7 V4 j/ V; C1 o! r1 Q6 tname=name.toLower(); ( P9 A" N! R! F u* t2 A
unsigned int a=GetDisp(name.toLocal8Bit().data(),3),a2=GetDisp(name.toLocal8B % _4 j. O0 `) V# F
it().data(),2),a1=GetDisp(name.toLocal8Bit().data(),1),b,disp,length; + s# ^& x$ G2 a H
a&=0x7FFF;
( b% w7 J8 I2 swhile(!((b=((int*)buffer2)[a*3+2])&0x80000000)||a1!=((int*)buffer2)[a*3]||a2!
- Z5 o; c# K& h4 i! A=((int*)buffer2)[a*3+1])
* d) y* T) C) Y4 h% Y& \{
) e2 }; i' \( `. { ?# \. k% T5 va++; 5 A( h( T R7 V" g
a&=0x7FFF; 9 g$ {* U0 L0 ]4 e+ X9 `$ r! |
}
. Q8 t2 ]" B2 J% |6 h! ob&=0x3FFFFFFF;
$ R# o7 X. v; w0 Rdisp=((int*)buffer3)[b*3]; 8 `" P6 I1 X$ Y& X! _7 I2 j/ N- S( S
length=((int*)buffer3)[b*3+1];
" G/ E% [6 r0 \5 C7 X+ i; {; V4 q$ n4 G0 Y
QFile pdata(this->package_name);
5 e2 W( g- d3 T* f- ?& Pif (pdata.open(QFile::ReadOnly)&&pdata.seek(disp)) { ) b* P6 {$ G2 B7 U. c7 W2 C
, |# |9 p3 z5 U0 p2 EQString wdir=QDir::currentPath()+QDir::separator()+QFileInfo(pdata).fileName
' F6 S- f' |" ?1 z6 m()+QDir::separator();
& a4 E$ V; f" y4 Q a9 ^/ IQDir dir; ' E0 u0 A; |4 x, Q" o) h
QDataStream pfin(&pdata); 4 Z- T/ ]" y/ Q; }
QFile file(wdir+name);
, h m6 k! S& k. p" K8 }) CQFileInfo info(file);
2 i9 M( f! T; Q3 Bdir.mkpath(info.absolutePath());
/ h- [6 r* A' Q. s+ P9 A- kif(file.open(QFile::WriteOnly)) / d& T7 y0 d0 \1 U( o7 |. ?) H
{
! T4 c" t. w8 l5 P8 a) l( ~1 Ychar* pBuffer=new char[length];;
. Q4 |$ x) A$ c1 u$ V" |6 sQDataStream fout(&file);
! P$ n. e+ n6 E5 }# D0 @! `4 zpfin.readRawData(pBuffer,length); 6 v- C3 j2 ~' K
fout.writeRawData(pBuffer,length);
8 u; _0 E( j, s# F4 z! b M# hdelete []pBuffer; ! h9 ~7 T- {9 _. y3 B! {+ X
return true;
n/ i. w$ a& X0 a" j4 T! Y9 o} $ i/ v) ~/ ~( x6 @+ V
}
* I8 s' d# ]- ireturn false; $ M! _( ~ O: k! J6 t
} 5 }- P2 t8 F2 | @6 y* h, }
得到待解压文件在文件索引中的位置後就可以找出该文件在axp文件中的具体偏移量和文件
0 H8 c3 t# l0 F! `大小了,然后直接fseek一下然后在弄出来就OK了。
) e/ T" _- I/ n最后说一下,这个AXP压缩包本身就含有一个文件列表文件叫做(list),所以每次只要先解
" V% A* R4 Y2 M压缩这个文件,然后按照里面的文件列表来一一解压缩就OK了。 : D6 @0 t [$ I0 d$ r
% ~% T! m6 z9 b3 J
0 J9 B ]. G* N( A
以上就是文件大致格式,感觉还是比较简单的,也可以考虑在自己的项目中使用类似方法
) P+ E8 w' K; y& p8 v进行文件压缩。
' X3 R! c( o/ W: I2 Y3 T
1 k$ z$ ?+ L& [; c! o1 J: m3 vPS:本文仅供学习,本人不负任何责任。。。。 |