学习一下人家的破解过程,这个游戏是开源游戏引擎OGRE进行开发的喔。 ) ]3 j) } t- m# b* y+ @3 W8 _
& b& ~( W9 m) W8 l6 u# }
# u9 Z' R0 ~1 t: ^! f& f0 H
2 T" E' S; c& m% _9 H发信站: 饮水思源 (2007年06月22日02:36:37 星期五)
. b; E5 t7 i- \9 K
; O9 H: O% ~0 q' c0 w- R5 l9 A0 V作者:[email protected]
) O' W* p; W9 A# R
9 D) `& j/ j7 @$ N$ L p* ^1 Y三月份时玩了某狐公司的网络游戏《天XXX》,感觉还是蛮有意思的,遂研究了一下。
; q# T$ h/ w- T K0 }+ q- W$ c这个游戏是利用开源游戏引擎OGRE进行开发的,看了一下目录里面的文件结构,主要的数
r8 {4 Q, _0 m( N' j- n据都放在Data目录下面。不过文件基本都是.AXP后缀的,每一个动辄几十兆,料想肯定是
1 j. P1 X4 S8 n$ c把游戏文件打包到一起并加密过的,GOOGLE未遂。开始用UE打开看了一下这个AXP文件,发 : F1 B2 H- }6 ]: o2 g" N
现里面居然大部分都是明文的,开始以为只是把文件罗列在一起,不过仔细看了一下,发 , _, F `! @ ?2 z
现每个文件都有一段间隔,前面还有一个数据头,而且文件与名字也无法对应。于是打开 7 f" w9 f9 i5 i: ^# N
OD手动分析一下,主要过程其实比较简单,CreateFile函数下断,找到文件Buffer位置,
4 t1 F: K" a @+ I$ U再下内存访问断点即可来到关键代码区域。略过具体跟踪细节及文件校验部分不讲,文件 1 w/ W4 a( q6 `7 u3 k X& s
格式主要分析如下:
2 d1 ]$ k9 V. d- L5 p# W5 Z" e
# G/ P0 u2 G% Q5 T整个AXP文件可以分成四个部分:1.文件头 2.文件名索引 3.文件索引 4.文件数据
& M! ?7 I6 t' a- j6 L6 A8 \6 P- Q1 e+ a1 H- y6 p
6 A, l* V4 |' g- ?* m. D! j9 y" U
1.文件头: 0 u9 l/ s: m9 ?! o. Z
整个文件头固定为0x28字节,其中第20个字节开始的一个整数乘以12代表了第三部分
5 U0 q6 { j7 C即文件索引部分的长度(因为每个索引有三个整数构成) 4 j# j% x. `* q- y0 C
2.文件名索引: 6 l2 m! @5 e$ ?5 Z3 q
整个文件名索引固定为0x60000字节,其中包含了每个压缩文件对应的文件索引位置
. I- b' h8 t N0 M
: h4 H( M& T- W. K' q4 H. B9 s3.文件索引: & T3 z9 Q h4 Z' c2 p3 R1 L0 ~
本部分长度由文件头相关数据决定,其中包含了每个压缩文件在.axp中的实际偏移位 " g9 G& S8 f- w" s/ z5 G: [
置及文件大小 . p* ~) R' d* z
4.文件数据:
: L+ C- o' n3 K5 S- e 本部分包含所有压缩文件的具体数据,每个文件之间用若干零填充。
7 y# k+ ~' u6 P" {+ v0 U8 T: u3 i) ^7 e/ F; \
首先说说解压总体过程:比如我们要从A.axp中解压出一个叫file.txt的文件,那么先根据 6 \1 I) i* n. ]
文件名file.txt到文件名索引中去找到对应的文件索引,然后再根据文件索引找到这个文
+ m) `8 e w: c6 F' F件在axp文件中的位置和大小,最后把其解压出来。 F; L' G6 J& V B2 Q: o3 K) X1 y. i& Y
( F4 j- F! ]' [8 X0 i
解压具体过程如下: 8 _4 q5 _0 M/ a! g
将待解压的文件名转为小写(如果为英文字母),利用GetDisp(char* s,int v)函数 $ D9 t" @2 Z0 E% x9 p/ ?
计算相关数据,其中s代表文件名,v代表计算参数,分别计算GetDisp(fname,1),GetDisp 0 _5 Z9 f5 t N' M! l8 F
(fname,2),
" a% l: t- N7 Y$ s7 R; L, UGetDisp(fname,3),得到三个值a1,a2,a3。其中a3低位与在文件名索引中的位置有关,a3最 0 f5 w# k: F% j6 E+ O
高位及a1,a2用来进行校验,如果三个值不能同时满足要求,则将偏移位置顺移继续验证,
: V9 \- ] Y8 R, R具体细节懒得写了。
# c8 q, y3 @4 P% s以下为GetDisp函数具体内容,我直接将跟踪代码里面的汇编改造了一下拿出来用,其中s
5 {# l1 M( L+ jucks为一个随机数数组,这里不列出来了。 7 r6 i4 v$ S7 m1 m$ N2 J+ L7 p
unsigned int TLBBUnpacker::GetDisp(char* s,int v)
8 k, \& [$ u# S" B2 U: R7 P% [" x5 o$ {& P{
5 D: D* d: }5 L4 o5 {7 n7 Q__asm
4 w8 {; L- A' `+ R7 c" `6 s0 d) r{ & m" q9 c; v& e% Z
push esi / ~# w6 \ G8 y0 C3 [
mov esi,s
: `& a9 s+ _( M# fmov cl,byte ptr ds:[esi] ( |6 [9 Y W8 H: z
test cl,cl
$ m, |2 O' P: [6 W$ H9 x3 T Vmov eax,0x7FED7FED
9 K/ n, B8 s8 B# j& Bmov edx,0xEEEEEEEE ) J& W" z* u4 j- [; q& d6 Y- a
je end 3 x& L2 X# S0 o% c: n
push ebx
$ Q/ f# q8 z. I5 gpush ebp
: h7 {' }2 t2 h& U& l( Opush edi 1 |3 b d X0 @4 e; R; i
mov edi,v , W+ W) o, z6 K3 d# N9 R& o
shl edi,0x8
, r* ^+ U4 x- ?' Kiter:
2 O/ z: E- p5 `- sadd eax,edx
) f) \3 R( Q3 |6 h. w v0 Limul edx,edx,0x21 % J# u) W0 z4 q5 N, E: G
movsx ecx,cl ; G6 }3 c ~! z3 g4 W& I2 d
lea ebx,dword ptr ds:[edi+ecx]
Y* d: Q# ]$ z/ P( P3 kmov ebp,dword ptr ds:[ebx*4+sucks]
) L6 r; N9 F5 ~$ p0 G$ L' T" vinc esi
5 m: l) a. B6 b. ~( _, e! j$ q, Tadd edx,ecx
0 E/ m1 W* J0 `) n8 ^mov cl,byte ptr ds:[esi] . G# f8 D/ }5 @& ^; e
xor eax,ebp 0 a# F; p. y7 ?7 k
test cl,cl ! u6 s3 x9 Z5 x& B2 S* I
lea edx,dword ptr ds:[edx+eax+3]
5 g4 R2 ]4 D! l5 ~; R6 T: tjnz iter
1 K0 d. o- ?' v# S0 upop edi
4 V. t4 ?3 ~1 z2 N6 epop ebp ! g0 @( n/ F) d' e0 P
pop ebx
1 e5 l8 F0 B5 Q* x( gend:
6 Q7 C6 C8 ?2 i( G8 O, Dpop esi ' S4 B# m! V6 j0 P/ T! p
}
8 f7 C6 ~3 b& M}
4 d1 U; |6 ?- y; n& Z! ]这个为解压单个文件的函数GenerateFile,用到了QT作GUI,大家就当伪代码看吧。 7 r5 Q2 p8 T, j! I9 {6 I E. y' \
bool TLBBUnpacker::GenerateFile(QString name)
2 f. s- b, a/ ` ^. Q$ v( j5 c{
8 h: G$ t1 @9 f, M* i+ q( q8 Z! wname=name.toLower(); ; K$ D* y4 T' i
unsigned int a=GetDisp(name.toLocal8Bit().data(),3),a2=GetDisp(name.toLocal8B
" x7 S4 x+ z4 Q; Lit().data(),2),a1=GetDisp(name.toLocal8Bit().data(),1),b,disp,length;
: G6 v/ G" q: m. C; Ka&=0x7FFF; : @0 f6 @# I/ Q g
while(!((b=((int*)buffer2)[a*3+2])&0x80000000)||a1!=((int*)buffer2)[a*3]||a2!
* s: P% \% v, p/ G! l! p4 g9 `6 ]=((int*)buffer2)[a*3+1])
1 D& X" n9 L, I$ F/ d{
3 @9 M, v6 b- j: W& F. G( oa++;
* ~5 O4 ?* m, i% h g7 _' c2 ja&=0x7FFF; 1 }, z! N" e/ s4 T* |& e g* d; J
}
/ @# g4 R0 |- a- S& n% {b&=0x3FFFFFFF;
% F6 O5 A& i$ y6 U- tdisp=((int*)buffer3)[b*3]; . X/ x! c: [- U0 `* C# q
length=((int*)buffer3)[b*3+1]; 8 }% u4 r2 Q1 }4 q) V+ g# r3 u" b
$ \7 R6 ~. q5 w8 t6 |QFile pdata(this->package_name); " u8 l$ [. D1 \# x* d6 S0 z
if (pdata.open(QFile::ReadOnly)&&pdata.seek(disp)) { % D6 U5 t8 b2 y T; e$ I8 n& p
% ?/ x3 j7 |1 w. w9 i ?2 p5 R( vQString wdir=QDir::currentPath()+QDir::separator()+QFileInfo(pdata).fileName
- @) e( C% E2 V7 S+ c: n! w) @()+QDir::separator(); $ t f/ H6 v u% o$ v
QDir dir; + P+ \3 t# N9 j, n; L/ G' b- t0 m
QDataStream pfin(&pdata);
5 \1 I9 Z% Z8 [5 M% R& d2 G' ^$ U. CQFile file(wdir+name);
" x2 N: i+ W# y' S) F. s/ JQFileInfo info(file); 4 k* E7 D, e8 d, @; F
dir.mkpath(info.absolutePath()); % z9 q- y, L1 J9 h3 D! e+ g8 o8 I
if(file.open(QFile::WriteOnly))
1 \4 m$ m7 L6 J" z! r8 g& F{ ; L8 G% k- S A: k! b8 P
char* pBuffer=new char[length];; 6 A5 J2 h F3 _8 V0 k
QDataStream fout(&file);
8 w9 P* X* e8 p$ [% ypfin.readRawData(pBuffer,length); # W2 l) z* q3 T4 p3 K5 `
fout.writeRawData(pBuffer,length); , N& Z- |% f6 f, | P
delete []pBuffer; ) L4 f: m8 q4 T* g7 ^
return true; ! y# M% s; D4 X# u9 S
} 5 Z9 O+ \# T: ]2 K2 j9 ^
}
I! ?0 E+ j) B. P4 V Areturn false;
1 C, u2 p! }* [' E# o} ' b' K, H% F5 U5 R2 t
得到待解压文件在文件索引中的位置後就可以找出该文件在axp文件中的具体偏移量和文件
2 z/ E6 V1 q. v" J% k7 R大小了,然后直接fseek一下然后在弄出来就OK了。 ( s& i! j( ~2 J
最后说一下,这个AXP压缩包本身就含有一个文件列表文件叫做(list),所以每次只要先解
9 q! H+ N8 N3 z& C3 ~" W4 R7 G7 v压缩这个文件,然后按照里面的文件列表来一一解压缩就OK了。
* F' b8 V% Z7 M J9 @6 O1 |- J# i a9 ^" ^
0 I' g/ G5 [$ @8 y+ [
以上就是文件大致格式,感觉还是比较简单的,也可以考虑在自己的项目中使用类似方法 $ P+ s5 _1 f: h% @' _; a( x- `
进行文件压缩。
9 D0 [$ _4 a' ?
9 R6 \* h. \( P! YPS:本文仅供学习,本人不负任何责任。。。。 |