学习一下人家的破解过程,这个游戏是开源游戏引擎OGRE进行开发的喔。 ; }4 }, X% E; {. d: X
) q6 ?6 A( J, g- x" b5 }0 n6 S. K& t) f( t: f
$ B9 {" V+ L% f" o! w" O5 c2 @
发信站: 饮水思源 (2007年06月22日02:36:37 星期五) " x, A6 \6 f) o9 b S# e
& G w/ h( ?1 I作者:[email protected]
( _! B+ G; w4 z" G8 ^5 Z
' N" d' V- ]/ ~三月份时玩了某狐公司的网络游戏《天XXX》,感觉还是蛮有意思的,遂研究了一下。
- w/ A* Y. M( R- R这个游戏是利用开源游戏引擎OGRE进行开发的,看了一下目录里面的文件结构,主要的数 , d; F' I+ ]/ e* `! o A. W9 x
据都放在Data目录下面。不过文件基本都是.AXP后缀的,每一个动辄几十兆,料想肯定是
# d* y3 C* T1 m! f' b8 Z4 V把游戏文件打包到一起并加密过的,GOOGLE未遂。开始用UE打开看了一下这个AXP文件,发 : D: F |, Y) s! N' [0 ]8 Z! a
现里面居然大部分都是明文的,开始以为只是把文件罗列在一起,不过仔细看了一下,发
8 t% a* C8 m5 H8 l4 C现每个文件都有一段间隔,前面还有一个数据头,而且文件与名字也无法对应。于是打开
' v O9 `! Z( {2 \* j* |3 z$ MOD手动分析一下,主要过程其实比较简单,CreateFile函数下断,找到文件Buffer位置, 1 l9 y; r7 [. ?: t5 [
再下内存访问断点即可来到关键代码区域。略过具体跟踪细节及文件校验部分不讲,文件 . x T& y! q6 p/ r
格式主要分析如下:
& j6 n6 }) x6 `* C% V+ K
) L4 k) M0 k8 `整个AXP文件可以分成四个部分:1.文件头 2.文件名索引 3.文件索引 4.文件数据
' H9 K- L5 m, W1 S, W5 l" Y9 q7 f$ h' r3 `3 \2 Y; S
6 w i! m( f% K2 L) h
1.文件头: 4 e' w+ ?$ ?: x& \; I/ j4 c R
整个文件头固定为0x28字节,其中第20个字节开始的一个整数乘以12代表了第三部分
* O; _. h8 x7 I+ V: p4 {$ p即文件索引部分的长度(因为每个索引有三个整数构成)
5 Q. u2 _6 z: r* e4 t9 S2.文件名索引: 4 Q9 E; ]! q' }8 }% Y' U8 K
整个文件名索引固定为0x60000字节,其中包含了每个压缩文件对应的文件索引位置
- A3 A, q+ z1 ^- ^# P; k8 X4 t0 L$ H, L& J
3.文件索引:
' e4 J$ x; _8 y0 J2 [; P 本部分长度由文件头相关数据决定,其中包含了每个压缩文件在.axp中的实际偏移位
$ G$ J% O8 p: t- j6 ?; U置及文件大小 - k. x' D5 O! ^% X ~. B: T
4.文件数据:
7 y( U7 _1 U+ i: }; ~ 本部分包含所有压缩文件的具体数据,每个文件之间用若干零填充。 % _2 }+ A* O4 U% h7 |3 _1 e( i
9 P8 ^' Z# @" n1 F2 Y* D2 I7 ?首先说说解压总体过程:比如我们要从A.axp中解压出一个叫file.txt的文件,那么先根据
) y1 w' C& I3 f' L7 w/ I文件名file.txt到文件名索引中去找到对应的文件索引,然后再根据文件索引找到这个文
; Z4 y- H. R% q件在axp文件中的位置和大小,最后把其解压出来。 2 u6 g$ S& X* L7 I
* e1 _6 E8 w0 ]1 f解压具体过程如下:
* H, D* |6 Q. C: o4 j: e3 @ 将待解压的文件名转为小写(如果为英文字母),利用GetDisp(char* s,int v)函数 * @+ O* C- [% m# H% W& P
计算相关数据,其中s代表文件名,v代表计算参数,分别计算GetDisp(fname,1),GetDisp % b' o" X/ w# I, [! [+ L
(fname,2), 4 {! @/ N; G" t5 ?( S# M' y; o
GetDisp(fname,3),得到三个值a1,a2,a3。其中a3低位与在文件名索引中的位置有关,a3最
- Q2 _" W* x2 F! e" d) ^0 T高位及a1,a2用来进行校验,如果三个值不能同时满足要求,则将偏移位置顺移继续验证,
% R1 W$ x: l# W) A1 i' l具体细节懒得写了。 3 Q; M# A4 j* X5 e% x; F. G$ z& Z+ ^" r# F
以下为GetDisp函数具体内容,我直接将跟踪代码里面的汇编改造了一下拿出来用,其中s
/ D$ V0 [: Q: Y% W. Gucks为一个随机数数组,这里不列出来了。
" Y- o0 N" z3 P5 N, d2 t" V, Gunsigned int TLBBUnpacker::GetDisp(char* s,int v) {9 v) x( v( k/ `
{
" s) g/ B) v# H. ~% @__asm , N+ Y; z# e; B2 @3 p
{ 8 d+ d0 Y: ^# O0 J# @% K
push esi 5 \5 k5 \" E( a/ J
mov esi,s 5 `1 L; o4 e8 K6 c( r0 b( x
mov cl,byte ptr ds:[esi]
8 ^8 X2 |) W# _test cl,cl
4 F# M! r r& U! _mov eax,0x7FED7FED
: r y* T7 K# b) B) e4 D/ J( fmov edx,0xEEEEEEEE / n$ k$ M/ c" t9 S6 w+ @1 ^1 j
je end b6 K0 S9 v2 A8 C: ?4 h
push ebx 2 J( G" N9 G) @
push ebp 4 C1 u; U3 q6 g# G
push edi
9 f! d& w( ?; }7 {4 a/ lmov edi,v
# s2 p Q: Z/ {" h1 R* b8 G# K6 ^shl edi,0x8
) @) G7 ]# `- D% w' Xiter:
( T; t/ l. t8 W) ]! r' ?8 ^3 K" Kadd eax,edx & q. B4 h# A2 ]7 `! b: P% d- v! ?
imul edx,edx,0x21
5 A- j5 }$ m, q: j$ U5 Nmovsx ecx,cl
: D Z7 @# S. X- b* qlea ebx,dword ptr ds:[edi+ecx] 5 Z! B1 l3 C! Q2 p8 ^& @$ [
mov ebp,dword ptr ds:[ebx*4+sucks]
9 N9 u8 L2 Y4 V4 d/ u# V; Z8 oinc esi
1 I# ~4 U4 `$ w' k* Vadd edx,ecx 7 X! v' ]5 q. Z0 h
mov cl,byte ptr ds:[esi]
9 X0 U$ E# v1 b5 X8 Exor eax,ebp
+ A, B2 A. n! Y: jtest cl,cl 7 Q( {" d# \% y1 Y" H3 I! Z
lea edx,dword ptr ds:[edx+eax+3]
Y) f1 L0 j/ Pjnz iter 6 {0 W8 w" c3 d( `, s
pop edi
: g/ W& T. o( G% Z$ Vpop ebp
; o0 S/ P: B$ l" _pop ebx
( U \4 u1 J5 b8 {) w4 hend: 8 A" Q+ U% r2 t4 a! r3 G0 c
pop esi % D1 f* t" s/ ~& x, b" a
} , o7 [! ]# g. \
} 1 m `* G6 X6 p- E T
这个为解压单个文件的函数GenerateFile,用到了QT作GUI,大家就当伪代码看吧。 " [0 J0 i% D: m% w
bool TLBBUnpacker::GenerateFile(QString name)
: F: l" K. ^6 @- y; M2 Z3 L{
& ] y; t( x7 T# T9 K1 m% F0 xname=name.toLower();
- p; B. [0 v0 u9 r; |unsigned int a=GetDisp(name.toLocal8Bit().data(),3),a2=GetDisp(name.toLocal8B * E+ Z0 ]' w! j) _0 ~8 i& ^
it().data(),2),a1=GetDisp(name.toLocal8Bit().data(),1),b,disp,length; & x( W0 X% z. \: l/ P6 N" Z
a&=0x7FFF; ( y/ y3 E3 O9 E2 \/ v2 W
while(!((b=((int*)buffer2)[a*3+2])&0x80000000)||a1!=((int*)buffer2)[a*3]||a2!
# G0 r$ J% D5 Y$ Q3 g=((int*)buffer2)[a*3+1])
( t4 q! u1 Y; A; c3 n3 X1 c{
1 }$ F; [- M; s" ra++; + \# K+ P3 y+ f3 p( a7 |2 m
a&=0x7FFF; / G/ P- Z/ \! ~. O: |( C
}
4 L( m- _* d: [b&=0x3FFFFFFF; % Y! E) O8 K! }4 x5 F5 a
disp=((int*)buffer3)[b*3]; - l& q1 S+ B# \. r- ?1 S
length=((int*)buffer3)[b*3+1]; 6 P# _' p9 k4 K" o
/ z8 \1 D* m) A# A
QFile pdata(this->package_name); % S) F9 B3 {. Z' l+ D1 Y' s
if (pdata.open(QFile::ReadOnly)&&pdata.seek(disp)) {
1 Z" C. H( V- A- Z( I- F7 }) X# `( k* ^- b8 [
QString wdir=QDir::currentPath()+QDir::separator()+QFileInfo(pdata).fileName
( s" q2 {( t1 r1 H()+QDir::separator();
9 w) C7 @- e& l9 MQDir dir;
9 }. b) O2 X$ |QDataStream pfin(&pdata);
1 ~6 x! T) H! _7 n) e8 P3 rQFile file(wdir+name);
3 O4 z" b3 T0 S+ C6 S. ?9 YQFileInfo info(file);
[: \0 q/ p: A4 K" vdir.mkpath(info.absolutePath()); 2 K+ Y5 U1 m- K% P% X- ]) b! [
if(file.open(QFile::WriteOnly))
# l+ N% k$ p. Y: g{ 5 z9 ?, L' x& k# t) W! K
char* pBuffer=new char[length];;
( d% W* T" y" b& q! s% O+ pQDataStream fout(&file); ! b/ I! g# O4 w) i* u$ B, }
pfin.readRawData(pBuffer,length);
. _8 V/ S% t) M6 gfout.writeRawData(pBuffer,length);
; |- C0 `; Z' [7 u' ~: Idelete []pBuffer;
: }2 m2 R8 N( @( K7 |* lreturn true;
/ X S, G2 x6 @* n* K# M) k8 [4 G} 5 b& c* {$ P! l }- m, v1 z; Z
} 7 V0 b- K' v0 R* o, x
return false; 6 V0 A) }4 P1 c& @4 _7 B- F
}
, N5 N! x1 w0 T- | Z _9 X* |得到待解压文件在文件索引中的位置後就可以找出该文件在axp文件中的具体偏移量和文件 r0 Q7 T8 D$ x
大小了,然后直接fseek一下然后在弄出来就OK了。 q7 u( E6 H! @
最后说一下,这个AXP压缩包本身就含有一个文件列表文件叫做(list),所以每次只要先解
3 \: b+ Z. {* q5 f L; z8 X8 p0 A压缩这个文件,然后按照里面的文件列表来一一解压缩就OK了。
/ j0 [+ D& U" P
: `: u, f( p0 v' K; a6 A5 m
/ w0 ^- M) C$ `3 S$ u以上就是文件大致格式,感觉还是比较简单的,也可以考虑在自己的项目中使用类似方法
, B4 y( d \; k: M; ^2 O进行文件压缩。 / d% i i# f2 |) W, X
. R0 w: H: `5 i: i7 d* x
PS:本文仅供学习,本人不负任何责任。。。。 |