学习一下人家的破解过程,这个游戏是开源游戏引擎OGRE进行开发的喔。 " V$ r( r" h, a" c' g3 o
# N3 n& D3 H9 u ]0 c
' x5 C2 f* M5 V+ g2 `% {# b% H6 I; S3 g! _1 K$ ]8 v
发信站: 饮水思源 (2007年06月22日02:36:37 星期五)
' O6 Y) R4 K- E( h1 a2 Q
+ `: f, h' j& w/ \$ i# J作者:[email protected] $ L- S5 Z, ?2 C
% t) W/ ^ y4 N- u三月份时玩了某狐公司的网络游戏《天XXX》,感觉还是蛮有意思的,遂研究了一下。
# L4 ?' _9 y, j, I' I这个游戏是利用开源游戏引擎OGRE进行开发的,看了一下目录里面的文件结构,主要的数
4 {1 U4 C/ a1 Y1 U1 ^据都放在Data目录下面。不过文件基本都是.AXP后缀的,每一个动辄几十兆,料想肯定是 8 a9 @1 d+ D" S- A$ X
把游戏文件打包到一起并加密过的,GOOGLE未遂。开始用UE打开看了一下这个AXP文件,发
3 J0 L K, z/ |$ s M1 f% l现里面居然大部分都是明文的,开始以为只是把文件罗列在一起,不过仔细看了一下,发
. Q \4 Y$ q9 n, n现每个文件都有一段间隔,前面还有一个数据头,而且文件与名字也无法对应。于是打开 2 O& @1 O. w1 F1 r) i& G" e' G
OD手动分析一下,主要过程其实比较简单,CreateFile函数下断,找到文件Buffer位置,
9 v6 j6 m% z0 ^9 |8 P+ E再下内存访问断点即可来到关键代码区域。略过具体跟踪细节及文件校验部分不讲,文件 / ]: C: j2 M2 J0 q
格式主要分析如下: * q: e( z+ V: c- q0 E' G& g
( N+ ]( a$ M% E; e! _3 B整个AXP文件可以分成四个部分:1.文件头 2.文件名索引 3.文件索引 4.文件数据
) q/ F5 | x# K! C, m9 v! B! d; U; {4 c: x J$ J' h
8 o7 q+ K8 n% G9 g" N
1.文件头:
3 l$ x* I4 q p. }2 F! R0 C 整个文件头固定为0x28字节,其中第20个字节开始的一个整数乘以12代表了第三部分 9 }, ?! f5 ~4 [- v/ M8 D! z
即文件索引部分的长度(因为每个索引有三个整数构成)
3 }# ?0 `7 D' Q& v, k, l2.文件名索引:
1 M# J4 G: v" o. g4 ~8 |$ i& r 整个文件名索引固定为0x60000字节,其中包含了每个压缩文件对应的文件索引位置 2 J5 L1 Q+ g3 c0 {+ q' h% H
2 a+ c" W3 q, M7 T4 {; M. a/ l; h. S3.文件索引:
" R( m6 i4 w5 N2 V; x( I 本部分长度由文件头相关数据决定,其中包含了每个压缩文件在.axp中的实际偏移位 # [: d- ^$ y2 Z8 W8 k
置及文件大小
- G0 a8 N4 n! i; K' {4 _4.文件数据: 0 K3 q, t) b5 v7 z- |
本部分包含所有压缩文件的具体数据,每个文件之间用若干零填充。
4 G* [; G# l9 d1 B/ k8 i, r
# O, ^$ y% ]) [. \" d, w6 Z' b首先说说解压总体过程:比如我们要从A.axp中解压出一个叫file.txt的文件,那么先根据 # w$ t9 m0 D5 }3 p3 ~2 @
文件名file.txt到文件名索引中去找到对应的文件索引,然后再根据文件索引找到这个文
% ~+ x/ p, A3 [5 \. u$ K1 q件在axp文件中的位置和大小,最后把其解压出来。 " f2 l3 g' `& C' ^' s' e. E2 c
: I! h! Z4 [" j1 S+ `: m
解压具体过程如下: 1 g/ x4 Q, j5 n
将待解压的文件名转为小写(如果为英文字母),利用GetDisp(char* s,int v)函数
$ b6 |1 d$ I* z- _$ D3 @& H4 k计算相关数据,其中s代表文件名,v代表计算参数,分别计算GetDisp(fname,1),GetDisp $ G( G; F, z) X/ m/ G- u0 v
(fname,2), 2 M$ ~; u7 v# B9 |: a9 T* b' q6 N# u
GetDisp(fname,3),得到三个值a1,a2,a3。其中a3低位与在文件名索引中的位置有关,a3最
& y0 e) c# D( m( o1 Q: @高位及a1,a2用来进行校验,如果三个值不能同时满足要求,则将偏移位置顺移继续验证,
) X* O7 d4 `2 {; b9 f/ K* q; r具体细节懒得写了。 7 ^: L" A1 v; G% u
以下为GetDisp函数具体内容,我直接将跟踪代码里面的汇编改造了一下拿出来用,其中s ( U$ f6 D3 ~* n3 F7 F
ucks为一个随机数数组,这里不列出来了。
( c$ a2 S6 _6 P! d8 ^unsigned int TLBBUnpacker::GetDisp(char* s,int v) / m; [5 |. M# V9 X) I c# w! }
{
* w% H& T5 T. ^& d- G# X9 |( v1 Z__asm
; z4 e4 P6 i! E* m. L3 c& v) g{ 4 ^1 X& R6 I/ E2 J; ]/ ]. o: C
push esi
( Y- W- `" r& l5 {* p7 }mov esi,s 8 L* B0 ~3 B- \0 }7 k
mov cl,byte ptr ds:[esi]
1 Q4 O: ~9 B- s b( b8 T* o7 wtest cl,cl
* x1 g$ j( _) R7 [mov eax,0x7FED7FED - C4 S! ` l1 `% ~ B
mov edx,0xEEEEEEEE ; @+ m6 S( s$ q+ o! M7 Q1 T
je end & v. `/ G3 U2 t, g$ n2 j* N3 B
push ebx * T4 z( e. E8 I' ^+ w7 z
push ebp
; l' ?0 c4 \/ o8 B' cpush edi : U$ C- _9 v* N8 v, R
mov edi,v
8 _% w# y, ^( ]6 N: [: Oshl edi,0x8
, v7 h" N( M. a, Fiter:
* ?# g. u5 ~3 N3 cadd eax,edx
6 c& @1 L! I# c Q, ~. V' Y: E$ Bimul edx,edx,0x21
. P) n3 @0 [, ^- B2 \/ ^' u' j* rmovsx ecx,cl
, Q6 g3 F0 u( ]; Olea ebx,dword ptr ds:[edi+ecx] + X3 ~$ x# a0 B5 |) }1 U
mov ebp,dword ptr ds:[ebx*4+sucks] ; u9 b- ]0 I+ {5 K+ X
inc esi 7 n d% q! B p
add edx,ecx 0 X* ?4 O6 m# t7 n0 C" ~# R2 A7 E$ h q
mov cl,byte ptr ds:[esi]
1 P* e: |: d' C3 R5 Q0 b3 I7 kxor eax,ebp
% A, R5 M2 g$ M2 ?& z2 Ltest cl,cl
" I* R! s' H, @2 Clea edx,dword ptr ds:[edx+eax+3]
6 {0 I# L/ s' Q% I' T% _# q7 t0 x5 Kjnz iter 4 P1 B d9 C; E( V, Z6 ]
pop edi
# T, V3 k8 n) f. |! ~. w- }pop ebp " w6 U4 o8 y1 B: K% {0 l6 D8 n6 A: t/ q0 b; |
pop ebx
2 W, H( I8 e9 G0 r! v! L& \end:
* g7 L! H$ d( l5 c3 b3 Wpop esi ( V. `3 d) C u1 i
}
6 W( v- W' n5 M" A* C9 k}
. Q* Z4 ^: h5 f9 Q( `$ e1 _2 ^这个为解压单个文件的函数GenerateFile,用到了QT作GUI,大家就当伪代码看吧。 6 B+ g( ^. u/ B; X" W
bool TLBBUnpacker::GenerateFile(QString name) 8 W7 T# z% q9 b) S' E
{ 4 x! Y1 a2 Z" C c/ p8 t7 g% k
name=name.toLower();
( k& L2 f! N m( Nunsigned int a=GetDisp(name.toLocal8Bit().data(),3),a2=GetDisp(name.toLocal8B - {3 V9 v9 x$ [6 G$ h# X
it().data(),2),a1=GetDisp(name.toLocal8Bit().data(),1),b,disp,length;
7 f l V o5 M( R6 H7 B( h! ja&=0x7FFF; ' m7 J$ u& I5 p& ]
while(!((b=((int*)buffer2)[a*3+2])&0x80000000)||a1!=((int*)buffer2)[a*3]||a2! 9 K" r" d- _& G1 y o' j1 d
=((int*)buffer2)[a*3+1])
7 l# H( W; S3 k- g2 n{ : e H8 c1 `9 J
a++; 5 G: l6 Y# ^% M; c
a&=0x7FFF;
. \, T0 m. z1 z}
5 p5 M ?1 l# {b&=0x3FFFFFFF;
+ j: x/ l9 J5 C% X+ A1 F- V& i5 tdisp=((int*)buffer3)[b*3];
0 ]- f: n6 ~# y: s/ Q, Mlength=((int*)buffer3)[b*3+1];
# \3 p! S% W; o2 V5 a# x9 q
2 W+ B/ f6 Q7 r% ?' U" h$ `% ?QFile pdata(this->package_name); ( [ r, b: B i" H7 f# _8 V
if (pdata.open(QFile::ReadOnly)&&pdata.seek(disp)) { 8 {4 B: o1 b! c$ V
* A7 ^/ ^8 t, f, D& l$ YQString wdir=QDir::currentPath()+QDir::separator()+QFileInfo(pdata).fileName . }- }8 n. w/ f6 M( R
()+QDir::separator(); 3 h! R' @1 H `5 U- N' V* f: [
QDir dir;
% T; G2 Y# _* h3 rQDataStream pfin(&pdata);
! ^/ U5 g! o8 u5 FQFile file(wdir+name); 7 F' Y* C0 a O$ O# {% `$ Y$ K, @
QFileInfo info(file);
3 @, L+ K( z9 t gdir.mkpath(info.absolutePath());
! n0 {2 h- `& Vif(file.open(QFile::WriteOnly)) 6 ~* e" y8 d3 r8 D) `
{
0 ^; x- o, p( jchar* pBuffer=new char[length];;
" b2 e$ m# @# _* e Z) R, z1 @- |QDataStream fout(&file); 5 e8 I- }! i) K2 w5 Z1 b
pfin.readRawData(pBuffer,length); + e( p5 O7 T, A, l" C) V3 p
fout.writeRawData(pBuffer,length); 9 Z6 p$ N, H" j7 V4 C! q
delete []pBuffer;
. d5 V9 O. J: e2 D) q* c" h4 Freturn true;
1 D" w$ i; w- \4 |$ z} 3 t: {% w" {* |& ]9 W S
}
: e4 r0 p' E1 I, D5 H- vreturn false; , A" r1 K% o, W9 N8 E
}
6 j8 j* `/ u q. r( E2 C# M, L得到待解压文件在文件索引中的位置後就可以找出该文件在axp文件中的具体偏移量和文件 ! Y: ~% f% x2 h$ g& x& K
大小了,然后直接fseek一下然后在弄出来就OK了。
- o/ Q3 B/ O$ c2 D) V最后说一下,这个AXP压缩包本身就含有一个文件列表文件叫做(list),所以每次只要先解 9 \% a8 Z6 m# x3 z. A6 j
压缩这个文件,然后按照里面的文件列表来一一解压缩就OK了。 + J6 l0 F1 Z- l. K* M, X1 M& l% R
2 t1 }5 T& Z: F3 w7 w8 o
v1 Z& R! U& D0 `+ ^
以上就是文件大致格式,感觉还是比较简单的,也可以考虑在自己的项目中使用类似方法 * _1 `& d1 b$ `8 A
进行文件压缩。 ' w6 C- J7 {% x! U# }* h& w
/ \4 E# E! ]1 z/ E" ~+ S. U
PS:本文仅供学习,本人不负任何责任。。。。 |