学习一下人家的破解过程,这个游戏是开源游戏引擎OGRE进行开发的喔。 I( z3 x; O+ \
% z0 v+ F2 _6 k7 O+ t5 V/ n& X& ^) @) J
/ E8 ~7 b/ ~6 a; t! c
发信站: 饮水思源 (2007年06月22日02:36:37 星期五)
& B3 r$ ?/ K( g( @ S1 l
2 {8 v& B% F$ Q- ^: v- ^作者:[email protected] $ u# W/ j& r/ Q8 l R
6 O0 T7 `) Z/ _$ v, }3 ?8 e三月份时玩了某狐公司的网络游戏《天XXX》,感觉还是蛮有意思的,遂研究了一下。 q! r* n# c" k$ A% h, e
这个游戏是利用开源游戏引擎OGRE进行开发的,看了一下目录里面的文件结构,主要的数 , p; K0 W, L9 |
据都放在Data目录下面。不过文件基本都是.AXP后缀的,每一个动辄几十兆,料想肯定是 1 z1 ]: L/ B2 B, \: y0 r
把游戏文件打包到一起并加密过的,GOOGLE未遂。开始用UE打开看了一下这个AXP文件,发
$ T7 D; Y/ j. l, i! Y; @现里面居然大部分都是明文的,开始以为只是把文件罗列在一起,不过仔细看了一下,发
; f0 S! o' h: n) d4 S5 S; L. Z. W现每个文件都有一段间隔,前面还有一个数据头,而且文件与名字也无法对应。于是打开
( K" v$ ?! g2 t& }) o% @OD手动分析一下,主要过程其实比较简单,CreateFile函数下断,找到文件Buffer位置,
; t y( t C) S9 q再下内存访问断点即可来到关键代码区域。略过具体跟踪细节及文件校验部分不讲,文件 0 w9 Y! L/ [$ B% L }8 M
格式主要分析如下: , o/ l9 O* |; t" s
4 y' L* ~0 J, H d i整个AXP文件可以分成四个部分:1.文件头 2.文件名索引 3.文件索引 4.文件数据 - @. w! g& u2 P. w, j! ~
- k& l; G# @- S7 v4 U: \& I
) c7 ]: Z! T' a' Q
1.文件头:
& i0 _# F$ d5 E4 J! |" F% z 整个文件头固定为0x28字节,其中第20个字节开始的一个整数乘以12代表了第三部分 y& P7 L9 X( m4 ^
即文件索引部分的长度(因为每个索引有三个整数构成)
& l. U7 n. y* e* S" Y2.文件名索引:
3 c0 Q$ s/ C' |& | 整个文件名索引固定为0x60000字节,其中包含了每个压缩文件对应的文件索引位置 9 f. F: I6 X0 C9 N9 x( t$ E8 x9 x
2 X: q* S& s7 z8 a9 {9 h- f
3.文件索引:
3 l! }6 ^# Y: h p: W! i1 a8 P 本部分长度由文件头相关数据决定,其中包含了每个压缩文件在.axp中的实际偏移位
$ ?# Y* f7 f# V2 [5 r% \8 Q置及文件大小
0 J) c$ m+ c% }5 A& G4.文件数据:
9 z6 t. F0 }& @ 本部分包含所有压缩文件的具体数据,每个文件之间用若干零填充。 ! o8 E. d: X! M; c+ U
( r$ Y! E; A: L4 N首先说说解压总体过程:比如我们要从A.axp中解压出一个叫file.txt的文件,那么先根据
' X1 _/ q/ l) c文件名file.txt到文件名索引中去找到对应的文件索引,然后再根据文件索引找到这个文 - _. N! E: {* _; w+ U
件在axp文件中的位置和大小,最后把其解压出来。 ' n" Q) y' i( Q( \
* t, n% G. n! P0 a7 @4 g
解压具体过程如下:
1 u# i! M) Q& [/ U& a q 将待解压的文件名转为小写(如果为英文字母),利用GetDisp(char* s,int v)函数 ) O7 l2 g! D/ J: X: x
计算相关数据,其中s代表文件名,v代表计算参数,分别计算GetDisp(fname,1),GetDisp 3 O7 M2 W5 g' `* \7 F
(fname,2), ' t5 B* D2 O) N
GetDisp(fname,3),得到三个值a1,a2,a3。其中a3低位与在文件名索引中的位置有关,a3最 & h& O0 @7 b% B' |+ o4 G, r! @
高位及a1,a2用来进行校验,如果三个值不能同时满足要求,则将偏移位置顺移继续验证,
' o. E, p. ~7 g2 n* s* N" D具体细节懒得写了。
) ?" O1 t; k: e& N; m7 |$ B* b$ E以下为GetDisp函数具体内容,我直接将跟踪代码里面的汇编改造了一下拿出来用,其中s
* ]; Z" r! |- D% U' e7 V3 q, v Vucks为一个随机数数组,这里不列出来了。 , W$ D& K6 g: l% y% x
unsigned int TLBBUnpacker::GetDisp(char* s,int v)
- i. b- G8 h( u1 @5 K- @/ x{ ( |9 s9 ^5 r! B1 \
__asm 6 T' s- H, V6 k0 |8 X" I( z
{
" f5 G0 m9 l& \. l: Y+ |: z( gpush esi
0 t' Z8 A3 _! D3 ?( j) |mov esi,s , I2 r& T) z0 t, E) |
mov cl,byte ptr ds:[esi]
( C7 c% C( e$ a3 x# Utest cl,cl 9 B& M, r: h( V+ A2 c9 H
mov eax,0x7FED7FED
" O8 p6 r, J B/ y2 e G! dmov edx,0xEEEEEEEE ( _# h" Q. l& V2 ]# W6 C4 ?
je end
9 ^3 B( `& B ppush ebx / X- t! K( U" x* z4 z
push ebp
5 ~' }; ?4 A4 U9 i, q* ipush edi
1 K' }: h, V1 |8 \0 O& xmov edi,v
K n* A" L. |4 Gshl edi,0x8 , R! K( s6 D2 F/ w0 T3 S; |
iter: ! L$ [! ^0 M2 @! k( W+ z" Y* K
add eax,edx ! W, S) G4 J) A5 Z7 S5 h
imul edx,edx,0x21 5 A% O+ I' V8 n* ~
movsx ecx,cl 1 M. W+ R/ I+ i. B& W/ C
lea ebx,dword ptr ds:[edi+ecx] & @& _9 U' X2 q5 M
mov ebp,dword ptr ds:[ebx*4+sucks] * i; h! p) s8 o# r) R0 b" P' n
inc esi
" F* m/ I3 N7 a, Qadd edx,ecx 2 G9 m2 e9 {; S: e
mov cl,byte ptr ds:[esi]
' \, P+ ~6 z0 Q {- X% s: Bxor eax,ebp $ ~% k. |4 u7 ~* H% x6 b# T- N& H
test cl,cl + v7 q! y( e/ L" S" i
lea edx,dword ptr ds:[edx+eax+3]
6 ^1 ]* q) ?6 H" y" e& O/ Gjnz iter - `" v3 p: p$ ^
pop edi
: ]6 n0 d% c- A" ipop ebp ) a$ B5 D( X' }% r2 A( v7 b
pop ebx 8 C9 e4 n( t0 ^3 n' \6 k
end:
& Y$ N# \4 {3 ^- L* a' p) q gpop esi
# I; ~" ^/ A7 z}
1 z: Q9 Z' V( [( v9 b* s( m1 X} + \ n9 u, @- u8 ^4 p
这个为解压单个文件的函数GenerateFile,用到了QT作GUI,大家就当伪代码看吧。 * j' q1 W2 S! B/ l& [3 M
bool TLBBUnpacker::GenerateFile(QString name)
# W/ n7 `* j( b, {- ~& N: P{
) @ a! p6 z- k3 `/ Y) X6 Wname=name.toLower();
# |- f" a8 Q, A: _unsigned int a=GetDisp(name.toLocal8Bit().data(),3),a2=GetDisp(name.toLocal8B 2 l+ f# y) e: A
it().data(),2),a1=GetDisp(name.toLocal8Bit().data(),1),b,disp,length; ; d5 p+ v+ c+ B% k7 H' W! s0 D
a&=0x7FFF; 5 F# Q) z$ P9 T- d
while(!((b=((int*)buffer2)[a*3+2])&0x80000000)||a1!=((int*)buffer2)[a*3]||a2!
- ]7 D/ D$ c* G' R' ?=((int*)buffer2)[a*3+1])
1 r j1 E! R) d7 N5 X5 z4 R8 w{
u- d9 h& q: K- Sa++;
: Y, B- E2 R- Y t; wa&=0x7FFF;
& j! ]% m, q3 I: P} , r1 m2 e6 ]* ]0 U$ |0 N# r# C
b&=0x3FFFFFFF;
9 b e* c) Y0 [disp=((int*)buffer3)[b*3]; 1 q; ]4 C9 v% G# R
length=((int*)buffer3)[b*3+1]; ) f# v' ]0 h- `
3 h% ?! |5 ^' V- L8 F* L1 h) \QFile pdata(this->package_name); ) u3 d0 ]3 W. ?# ^ A: k
if (pdata.open(QFile::ReadOnly)&&pdata.seek(disp)) {
5 H( |; ]$ ~. X6 o
1 d3 V: k9 a' ]QString wdir=QDir::currentPath()+QDir::separator()+QFileInfo(pdata).fileName ( e* i6 G' r" t0 f" O. H5 |
()+QDir::separator();
t$ W6 u+ g% e. `6 _QDir dir; - F6 \/ x; C* p4 n
QDataStream pfin(&pdata);
3 J! n* s3 R, jQFile file(wdir+name); : I5 {- M& o! }1 ~0 X
QFileInfo info(file);
, h. g* N3 m n, K' ~5 ?dir.mkpath(info.absolutePath()); ' I4 m7 l: }( W# n, s9 y
if(file.open(QFile::WriteOnly))
$ a% A: w4 h5 M{ " T0 l$ {1 H/ y, q$ L# F% M" i
char* pBuffer=new char[length];;
2 O' t4 H3 M$ F3 P4 cQDataStream fout(&file);
* d) @9 u* G+ I8 Y3 W$ `/ B- Apfin.readRawData(pBuffer,length); + Z* Y3 v( R0 L: |( J m
fout.writeRawData(pBuffer,length); + i' Y! b$ e4 q, V+ M' o
delete []pBuffer; - \6 U4 L' ]6 f" ?
return true;
6 G7 p$ D8 [) I. _$ z, q, c} 4 }. B# E* \3 } s4 _0 ~
}
' \# X- q8 k7 D+ e8 N: F0 `return false; / [% a8 W% V9 {9 X f
}
I2 u/ v1 A) H得到待解压文件在文件索引中的位置後就可以找出该文件在axp文件中的具体偏移量和文件 6 Y/ a- q! A+ Z1 N& M/ q
大小了,然后直接fseek一下然后在弄出来就OK了。 3 W3 Q, `6 t) ~ m
最后说一下,这个AXP压缩包本身就含有一个文件列表文件叫做(list),所以每次只要先解
' Y. M# \- G4 j O压缩这个文件,然后按照里面的文件列表来一一解压缩就OK了。
3 R4 u6 W* L0 }3 E$ [' T. K9 T) V# H/ ?& X* a4 s% Z
9 V: d, U. h- [) W6 r/ Y( c
以上就是文件大致格式,感觉还是比较简单的,也可以考虑在自己的项目中使用类似方法 $ d. v' }' F$ I3 V" ?0 j
进行文件压缩。 u* x1 b8 s. p
3 E0 r2 D6 g* U: r& M
PS:本文仅供学习,本人不负任何责任。。。。 |