学习一下人家的破解过程,这个游戏是开源游戏引擎OGRE进行开发的喔。 5 o8 M E# u3 C) N7 o
( ] z& ]% }+ Y2 a5 s# H }+ ^
. r% l3 ?4 R/ w/ {
( e. @" M9 m3 L发信站: 饮水思源 (2007年06月22日02:36:37 星期五)
2 }; L/ D4 g# b! c+ p1 B$ X: w1 l, ^
作者:[email protected] " S) }2 W/ x! ?) {4 J$ a7 C
5 ]- q) a+ Q0 w. Q* @三月份时玩了某狐公司的网络游戏《天XXX》,感觉还是蛮有意思的,遂研究了一下。
. ^$ h# l" C: X: ]2 Z: K2 [这个游戏是利用开源游戏引擎OGRE进行开发的,看了一下目录里面的文件结构,主要的数
" E: H, j& r& Q& \据都放在Data目录下面。不过文件基本都是.AXP后缀的,每一个动辄几十兆,料想肯定是 " v% _! p% w7 B
把游戏文件打包到一起并加密过的,GOOGLE未遂。开始用UE打开看了一下这个AXP文件,发 7 k( a" T) [ G) G% [% r/ E# C
现里面居然大部分都是明文的,开始以为只是把文件罗列在一起,不过仔细看了一下,发 * _# j0 q# T3 O9 y& P! r1 X
现每个文件都有一段间隔,前面还有一个数据头,而且文件与名字也无法对应。于是打开 $ r! k* x; m, x7 G( S8 ?
OD手动分析一下,主要过程其实比较简单,CreateFile函数下断,找到文件Buffer位置,
7 q. k" z& z d$ @- z" K% h再下内存访问断点即可来到关键代码区域。略过具体跟踪细节及文件校验部分不讲,文件 8 r: ^- z8 q; J9 Y. Q+ k8 T
格式主要分析如下:
, }: N$ W4 }2 K
* f$ e4 M, C1 l2 _7 l& P& J( i& o9 O整个AXP文件可以分成四个部分:1.文件头 2.文件名索引 3.文件索引 4.文件数据
% P1 E. U) p; z g: b* {! i4 w# g1 E7 t, A+ l1 q, \( o
6 u. q$ Q x$ D& \4 R) A9 ~
1.文件头: / O( |4 b+ e# w! `# [ W
整个文件头固定为0x28字节,其中第20个字节开始的一个整数乘以12代表了第三部分
q# P' ^8 M& ~) X: R4 ~即文件索引部分的长度(因为每个索引有三个整数构成)
# s0 g5 y" d. X* s2.文件名索引:
6 i4 z/ m5 n6 I' H' X5 j 整个文件名索引固定为0x60000字节,其中包含了每个压缩文件对应的文件索引位置 ; |9 Y/ E/ a( v& d
8 X0 E! @ w! K8 L; |/ _/ C0 `9 T
3.文件索引:
. d1 ?1 V6 L7 @+ F 本部分长度由文件头相关数据决定,其中包含了每个压缩文件在.axp中的实际偏移位 8 H, _& e3 R; c( R7 n4 `$ @
置及文件大小
5 o/ s* M" E8 Q; A9 v a4.文件数据: 1 h1 U9 B! ~, F$ g: d1 m9 N
本部分包含所有压缩文件的具体数据,每个文件之间用若干零填充。
& r8 e% @# N! Z7 m2 N
, n: ?: Q* K! A+ I首先说说解压总体过程:比如我们要从A.axp中解压出一个叫file.txt的文件,那么先根据
y9 I p% D* m$ ?- p文件名file.txt到文件名索引中去找到对应的文件索引,然后再根据文件索引找到这个文
2 V# x6 @3 g+ ^, X6 ~件在axp文件中的位置和大小,最后把其解压出来。
$ T1 M2 k. b4 @# z5 N e
3 U; I+ `; M' D, U9 m, ~解压具体过程如下: 3 N* E+ T9 [6 M) _% q; t& j
将待解压的文件名转为小写(如果为英文字母),利用GetDisp(char* s,int v)函数 % g# H& R/ ?' o8 l$ F' {- o
计算相关数据,其中s代表文件名,v代表计算参数,分别计算GetDisp(fname,1),GetDisp
3 R) Z. ?: G0 N' h1 Y2 Y' ~(fname,2), : k: |2 j X. S! a1 M0 i' |3 M$ z0 r
GetDisp(fname,3),得到三个值a1,a2,a3。其中a3低位与在文件名索引中的位置有关,a3最
1 @6 `/ P% M$ a8 [高位及a1,a2用来进行校验,如果三个值不能同时满足要求,则将偏移位置顺移继续验证, $ B5 ?1 [3 x1 ]* |1 i2 b
具体细节懒得写了。
0 o4 ~& a* a/ ?9 Q, x以下为GetDisp函数具体内容,我直接将跟踪代码里面的汇编改造了一下拿出来用,其中s 7 ]" [# c, f4 y+ M# O: `
ucks为一个随机数数组,这里不列出来了。
: J# U9 q9 E6 Q; D0 munsigned int TLBBUnpacker::GetDisp(char* s,int v) " |7 ~6 q* U; ?/ V9 h- l" o
{
; C5 d1 R' v: J: a__asm # _% F7 Z1 P+ f& C; p1 q
{ 2 g9 ^# G& |2 i
push esi
( E/ g& Y1 W' zmov esi,s
* h" p _- R. n- umov cl,byte ptr ds:[esi]
1 Z+ T0 p# a; `4 A: h" ktest cl,cl
9 }* X% M/ y4 v: @3 z" g5 a4 hmov eax,0x7FED7FED * z$ q# U4 `+ q" C
mov edx,0xEEEEEEEE
' N) Y* z3 e( G- |6 ^% Qje end
% u" H; P6 G3 [/ jpush ebx & U* g3 X8 b( g z. M1 l
push ebp
! ~; g& {! n }& G# F6 B" T5 J$ C3 lpush edi
0 g5 ^9 U" K* U4 b& V4 zmov edi,v # r5 `) a4 z' @. D% w, L. c
shl edi,0x8 ' c# Q" T; _: E8 X
iter:
: o, K/ y0 V5 n7 l6 w, Radd eax,edx
0 h2 B1 Z, a& fimul edx,edx,0x21 % h# A/ j. n3 H" d
movsx ecx,cl
' v* j# Y; ]* ?2 slea ebx,dword ptr ds:[edi+ecx] ; Z: R9 r4 o8 l3 F; c+ e; |1 J
mov ebp,dword ptr ds:[ebx*4+sucks]
' Y( W% K( q; a% r- ^0 Ninc esi & |5 a) a" f8 F& {6 P
add edx,ecx
d) x! d. \# X) H+ H0 cmov cl,byte ptr ds:[esi]
6 V9 D* s1 g: X3 Pxor eax,ebp 5 m/ U f$ x. ^" W
test cl,cl
/ \8 e0 k) s- V* \! x1 j% slea edx,dword ptr ds:[edx+eax+3]
x) L9 U- h5 P: w0 l+ sjnz iter
# K, K8 L0 A" B- M G. jpop edi , }0 U5 r' v6 \" B7 x
pop ebp 0 b/ r( E' p0 a7 t9 a. f
pop ebx
. a- r% U) J1 P4 A& I/ W& P- S9 E, Dend:
4 ^, F7 F u7 Cpop esi 9 q: ^$ d; ~( }9 G
} / B# L. `$ L3 j+ W$ j% C* ? _! a
}
5 H7 b: w$ x- s2 |5 ]1 U+ U这个为解压单个文件的函数GenerateFile,用到了QT作GUI,大家就当伪代码看吧。 % m; @3 o. F, f6 Q* G
bool TLBBUnpacker::GenerateFile(QString name) ! g$ o( |# Y( ?' {9 n8 q
{ # \ J" t- {0 p* l9 c( B/ F) ?
name=name.toLower();
+ P5 G! L K5 R" Junsigned int a=GetDisp(name.toLocal8Bit().data(),3),a2=GetDisp(name.toLocal8B
- ^0 A6 h# e2 X) e* f! bit().data(),2),a1=GetDisp(name.toLocal8Bit().data(),1),b,disp,length; - }. {2 O6 W" a: \6 o. e3 @7 d. t+ L
a&=0x7FFF;
4 Z% g' Z2 B! F, Q5 S) gwhile(!((b=((int*)buffer2)[a*3+2])&0x80000000)||a1!=((int*)buffer2)[a*3]||a2!
3 W; \ `- S, A0 G=((int*)buffer2)[a*3+1]) 5 R2 M+ N4 @7 L% _- e* [
{
( H7 [5 T$ {: l4 F! H7 R* \2 Y _a++; - ]5 p3 c n3 O1 i7 Y
a&=0x7FFF;
7 n7 c/ W c2 u+ o; D4 C}
% a& p- Y7 V( _5 {0 K2 P4 jb&=0x3FFFFFFF;
9 a8 ?' i+ M: E7 Odisp=((int*)buffer3)[b*3]; 6 Q6 A1 h. L0 t) h! A. M/ ?
length=((int*)buffer3)[b*3+1];
9 f- R- c* ^0 l) X
8 |3 ^3 h; a) p9 }/ J P# x) l: ~. S. [QFile pdata(this->package_name); * F) Q) k, h) Z9 `' R: {% x
if (pdata.open(QFile::ReadOnly)&&pdata.seek(disp)) { # {0 }: z2 O, T& A3 i1 A, M- W
: j! l! \5 r, z! @) |8 _5 n' rQString wdir=QDir::currentPath()+QDir::separator()+QFileInfo(pdata).fileName 6 l. j0 A! k2 ]1 I, f2 n
()+QDir::separator(); , T$ {; Y7 g O
QDir dir;
& ?( n! V" }% P+ w" ~" ZQDataStream pfin(&pdata);
' p! R8 z0 `- Q' j( @QFile file(wdir+name); # s' H( Y8 q6 c. t
QFileInfo info(file); * m, f8 u% C1 O( v
dir.mkpath(info.absolutePath());
6 o% N, Y/ T/ j( j tif(file.open(QFile::WriteOnly))
% ^4 C7 b. I5 h7 ?{
9 V2 p9 p& f' x3 y& e3 pchar* pBuffer=new char[length];;
C: B+ k# T5 M. O9 r8 W* QQDataStream fout(&file);
. C8 H6 c: g# ~& V# \pfin.readRawData(pBuffer,length);
" b+ j# d5 v; ^! J- x7 g9 bfout.writeRawData(pBuffer,length); % J8 t& \4 `+ G( S0 Y% Y& b
delete []pBuffer; 9 ?7 j( ^* G* a, q9 V/ Z/ k8 Z
return true; * u5 g& D7 ~1 X( _4 a3 V
} - Q0 r8 U* N& d( |7 F; L0 j
} . D& P7 C4 R0 ?
return false;
7 A$ p& }" X0 W' U8 v. w3 t. ?} & q4 `/ m- q$ G- g
得到待解压文件在文件索引中的位置後就可以找出该文件在axp文件中的具体偏移量和文件 - a. m- N6 V- N8 m6 F% _$ n4 ]
大小了,然后直接fseek一下然后在弄出来就OK了。
* a- b2 k. [2 V* j最后说一下,这个AXP压缩包本身就含有一个文件列表文件叫做(list),所以每次只要先解 4 y1 \) D3 v! [% x+ y
压缩这个文件,然后按照里面的文件列表来一一解压缩就OK了。
. E' Y4 N# d* R' B1 J" g; }
6 F4 A7 q) X* T z2 u5 u$ _1 s7 l* I- D: j1 Y* B
以上就是文件大致格式,感觉还是比较简单的,也可以考虑在自己的项目中使用类似方法 6 I, T( h9 a* R2 d
进行文件压缩。 % n A" k0 C. B" Y: t5 R; X$ q3 }
+ z* S/ ? `! P0 C& Z# C& `PS:本文仅供学习,本人不负任何责任。。。。 |