学习一下人家的破解过程,这个游戏是开源游戏引擎OGRE进行开发的喔。
0 v+ C1 l- \1 M* q/ _5 R5 m% f
; d' f, I$ b9 W: Q S6 d$ w' a
' }* \+ J& O' {" q/ L3 `- A7 b6 }( q$ n C0 V. n
发信站: 饮水思源 (2007年06月22日02:36:37 星期五)
. U4 G8 M0 ]$ H" S2 k# F; j! ^- B5 j+ g" n2 _6 o3 t
作者:[email protected]
6 @9 u9 e& G: L& A% `$ G( O# I
三月份时玩了某狐公司的网络游戏《天XXX》,感觉还是蛮有意思的,遂研究了一下。
m$ V, F, U, d5 N; d9 J8 {$ Q这个游戏是利用开源游戏引擎OGRE进行开发的,看了一下目录里面的文件结构,主要的数
+ g1 o( J5 i1 F- r# S据都放在Data目录下面。不过文件基本都是.AXP后缀的,每一个动辄几十兆,料想肯定是
3 }4 |- `) m$ K6 |5 {1 Z: ]把游戏文件打包到一起并加密过的,GOOGLE未遂。开始用UE打开看了一下这个AXP文件,发 : X6 }/ R! X# d5 F8 r5 y
现里面居然大部分都是明文的,开始以为只是把文件罗列在一起,不过仔细看了一下,发
. @& k9 i* S( [6 L4 h- ]) R& U现每个文件都有一段间隔,前面还有一个数据头,而且文件与名字也无法对应。于是打开 $ [+ L0 r8 ?( x7 X2 T
OD手动分析一下,主要过程其实比较简单,CreateFile函数下断,找到文件Buffer位置, + x* T# |0 ^" h) K+ \7 o& k7 _7 Y2 I
再下内存访问断点即可来到关键代码区域。略过具体跟踪细节及文件校验部分不讲,文件
5 r7 c1 {2 V/ @* F格式主要分析如下:
0 b7 ?( }# _" @3 m+ |! X3 K
* I( U! Z3 _+ m% f: \6 }整个AXP文件可以分成四个部分:1.文件头 2.文件名索引 3.文件索引 4.文件数据
. ^! R/ |9 ^7 U' G7 A( C
5 k9 J( L$ p- P! l# h
& j' \1 ^& q% p1 \- r7 ]9 T% k1.文件头: $ d/ D, j& O0 u, u, f8 `! C
整个文件头固定为0x28字节,其中第20个字节开始的一个整数乘以12代表了第三部分
5 i- o* L9 E# s& M即文件索引部分的长度(因为每个索引有三个整数构成)
! k5 i" o; u* P5 P3 `7 ^) R0 K( |2.文件名索引: / \4 w3 {& f% Z& j F+ Y! v3 X
整个文件名索引固定为0x60000字节,其中包含了每个压缩文件对应的文件索引位置 8 i8 [% a7 L3 t7 X$ ]' A F
; X: w4 }! h/ X" U [
3.文件索引: $ X2 w$ d/ }. w3 b! u
本部分长度由文件头相关数据决定,其中包含了每个压缩文件在.axp中的实际偏移位 7 b$ I+ l2 K1 O, {; i
置及文件大小 . E' a8 z2 B2 V4 U5 X5 d8 |
4.文件数据: / l5 D$ u& F# i# O5 C
本部分包含所有压缩文件的具体数据,每个文件之间用若干零填充。
9 \: p/ V* G! r
* ^* W" [, b( g' W# E" Q6 y' f% ~% f首先说说解压总体过程:比如我们要从A.axp中解压出一个叫file.txt的文件,那么先根据
2 W4 y7 G, q: K文件名file.txt到文件名索引中去找到对应的文件索引,然后再根据文件索引找到这个文
7 R* @' f, z3 @1 y* m! x1 U件在axp文件中的位置和大小,最后把其解压出来。
( [2 Q; p- X7 Z5 E1 U" C8 y* x
/ W5 h' I2 a- {; A解压具体过程如下:
' M8 g/ w( ]0 Y" i7 o, ?3 Z! F 将待解压的文件名转为小写(如果为英文字母),利用GetDisp(char* s,int v)函数 5 g f ?' K, x& w: F+ n. ^
计算相关数据,其中s代表文件名,v代表计算参数,分别计算GetDisp(fname,1),GetDisp * j( Q6 b7 V. @# b( ~# j
(fname,2),
E1 B! F5 B; o" _& C9 @/ sGetDisp(fname,3),得到三个值a1,a2,a3。其中a3低位与在文件名索引中的位置有关,a3最
; Q# _7 ~7 p5 v$ m高位及a1,a2用来进行校验,如果三个值不能同时满足要求,则将偏移位置顺移继续验证,
. T+ [6 z: S s" l5 a* y4 v具体细节懒得写了。 # B" @: a$ \$ J! h
以下为GetDisp函数具体内容,我直接将跟踪代码里面的汇编改造了一下拿出来用,其中s
/ Z1 Y( q# A: V! b6 k; {. p I7 aucks为一个随机数数组,这里不列出来了。
. V; J4 X" Q! @" X4 F% l+ L9 Zunsigned int TLBBUnpacker::GetDisp(char* s,int v) - [. E0 B/ r0 ]# @; h6 O# u+ S
{
" A$ D8 ~3 D- m+ r7 I% y9 F3 F__asm / [8 l9 |" d+ F- {& a- w- @
{ : L2 \! X' P3 h* ~6 N8 H0 I1 d
push esi 0 R# w- b" @% j5 m1 ]- s" n
mov esi,s E1 K2 F% z, _" F, f2 H7 V
mov cl,byte ptr ds:[esi]
# U% f: M& r0 [! f, S; otest cl,cl
; p" R; c. C- Q e/ w5 Q; W& Cmov eax,0x7FED7FED 4 i3 F& n( J1 M& J$ @9 e
mov edx,0xEEEEEEEE 7 k0 z! s! f! A3 z
je end
& y Y1 I. {4 j( L' S' xpush ebx / I* I5 k. U7 `8 i* l
push ebp ! F0 C! m5 V, {0 w) ~8 V
push edi
5 E% e: k+ S$ M, nmov edi,v ' x: B0 P) @0 O. |7 }# m9 x2 k
shl edi,0x8 [6 R M7 w" G; ^2 i- E9 r8 r
iter: 9 N9 c! I& W. h" V7 c3 j# ?- Y
add eax,edx + Q8 ]" R2 Z1 K7 S+ }# l" u7 n
imul edx,edx,0x21 & R' l; |$ e2 l2 m% \' I" A& |; {
movsx ecx,cl W V, }1 z- T
lea ebx,dword ptr ds:[edi+ecx] 5 ?. t( w5 ~6 F$ m2 }) e& S" J
mov ebp,dword ptr ds:[ebx*4+sucks]
& j, @/ @, X6 n. |: y6 oinc esi
: @, |% N8 U6 W6 n, v) Eadd edx,ecx 4 [! K4 H( l) a
mov cl,byte ptr ds:[esi] % ?- O5 m' j( W' y7 u# B
xor eax,ebp
/ x4 [. f% v' s f P& z; p" @+ n1 K: }test cl,cl
0 ^' {* n" ^% c* y* _lea edx,dword ptr ds:[edx+eax+3]
: ~( g% K& a3 k/ ?jnz iter + X1 l! z# j4 H' `' v2 O# `1 ~
pop edi 2 F- g$ t# q$ W+ v
pop ebp 9 k- K/ i1 F3 W5 m3 y3 m
pop ebx
( b5 l* A P6 |$ ]3 G1 _" |end:
3 A; _$ X/ z5 l; i7 Fpop esi
( l8 o( B- w+ j7 H, |& R8 z} 7 A; X. Z; d; C: j8 l
} 5 | h$ _. H) V% r9 C( I
这个为解压单个文件的函数GenerateFile,用到了QT作GUI,大家就当伪代码看吧。 " B2 g" W- U2 _5 x/ d8 |
bool TLBBUnpacker::GenerateFile(QString name) " u1 o$ ]$ A% X
{ ! E1 G Z! ]- m; R
name=name.toLower();
3 e; W+ B! u. Z/ C. Junsigned int a=GetDisp(name.toLocal8Bit().data(),3),a2=GetDisp(name.toLocal8B
5 A8 ?1 _* v1 Z4 `1 j& V6 rit().data(),2),a1=GetDisp(name.toLocal8Bit().data(),1),b,disp,length;
# B3 S; n* l! u- G6 sa&=0x7FFF; % w' U, S& e+ K( S
while(!((b=((int*)buffer2)[a*3+2])&0x80000000)||a1!=((int*)buffer2)[a*3]||a2! . f$ K' Y9 w7 E; B9 S4 C
=((int*)buffer2)[a*3+1]) 6 L( Z& f& N( R1 s: N: M2 l( T0 e8 ~* F
{
' ~+ b" a' w$ aa++;
2 p) c- P. L9 t3 ~a&=0x7FFF; 8 ?0 ^8 R9 c, Y- H/ F8 S
}
* j0 O4 x3 Q. C0 a, [b&=0x3FFFFFFF;
8 @- Q8 ]) T3 D( `) C& zdisp=((int*)buffer3)[b*3]; . V0 e1 l9 f) ~/ N$ e
length=((int*)buffer3)[b*3+1];
8 Q0 d5 X- Q4 q* h/ L5 t, s0 {5 h# t: {
QFile pdata(this->package_name); , n5 R; B$ K+ Q2 M: X5 v
if (pdata.open(QFile::ReadOnly)&&pdata.seek(disp)) { + X% @5 u5 N% K* s% G! h g
9 u4 t1 A/ E6 Q0 h7 ]3 l5 [! C
QString wdir=QDir::currentPath()+QDir::separator()+QFileInfo(pdata).fileName 6 m2 E$ u; E" s5 L4 h; ?
()+QDir::separator(); 2 b& J2 \4 R$ h( e- G5 \2 [" P9 y
QDir dir;
( R% b0 s& z' G- T% ]( G7 }QDataStream pfin(&pdata); ! l/ b1 t) i8 n9 c, T- ~
QFile file(wdir+name);
! m' ~. H8 j* Q; T0 w7 h$ R% [QFileInfo info(file);
% K3 c j% G* R" Kdir.mkpath(info.absolutePath());
* G7 P& q9 T% Z mif(file.open(QFile::WriteOnly)) # _. @5 z' W' U0 i4 o5 z5 K
{ : ^; V$ C+ C! a. ]6 K
char* pBuffer=new char[length];;
% t4 [$ Z+ }: z% R2 |QDataStream fout(&file); 9 A0 A/ W: m. v5 c
pfin.readRawData(pBuffer,length);
% L/ k( z) l7 R! m: [; T3 Y- Mfout.writeRawData(pBuffer,length); / Y* D0 Z1 T2 m- z6 b" {
delete []pBuffer;
/ ~2 p7 i9 T4 Q! g* @* ^, `return true; : {" v5 n, B# i; s& K
} ) F( c+ M4 l/ k
} , k9 j4 f% J2 p/ {- Y; Q) [: Z2 X
return false; + ^0 n6 c% c' h8 ~6 k
} / B& e. F! s7 z" n$ p* r- p
得到待解压文件在文件索引中的位置後就可以找出该文件在axp文件中的具体偏移量和文件 ' m& F# f: j9 ~8 O# Q
大小了,然后直接fseek一下然后在弄出来就OK了。 $ o& @1 ~9 O4 g5 r6 q- U) i
最后说一下,这个AXP压缩包本身就含有一个文件列表文件叫做(list),所以每次只要先解 , a3 T3 B' }( M% J1 @; ]. {& f7 d; G$ P
压缩这个文件,然后按照里面的文件列表来一一解压缩就OK了。 6 K. g- w' m2 y8 t6 _. ]0 U
r2 P3 ~% o$ t$ f# V J1 p; f
6 g' p% c4 R' E' R8 A. K
以上就是文件大致格式,感觉还是比较简单的,也可以考虑在自己的项目中使用类似方法
( l( f* a& I1 M: Z进行文件压缩。 G2 a& u" m( t8 Y
4 ~& F3 ~( e+ K0 hPS:本文仅供学习,本人不负任何责任。。。。 |