学习一下人家的破解过程,这个游戏是开源游戏引擎OGRE进行开发的喔。
4 R; ]" z @ @' }* G/ f$ @2 \8 B/ x. b- j3 P7 m' P, b
* c7 |9 i u/ S# u* y- n8 d
|" h" \ m3 [
发信站: 饮水思源 (2007年06月22日02:36:37 星期五) 6 _9 y5 y2 F; D/ J
" I9 b3 K0 }, {# _/ v/ @
作者:[email protected]
5 j2 |1 i3 [9 b$ t1 F9 C; |. l9 K
/ e% Z5 J$ w8 h- Z8 P三月份时玩了某狐公司的网络游戏《天XXX》,感觉还是蛮有意思的,遂研究了一下。
' b: W H( }) E8 t5 d这个游戏是利用开源游戏引擎OGRE进行开发的,看了一下目录里面的文件结构,主要的数
/ U4 l/ d$ J" f8 o% o# ?据都放在Data目录下面。不过文件基本都是.AXP后缀的,每一个动辄几十兆,料想肯定是
6 e7 J3 H) ~- I) @( ?7 k把游戏文件打包到一起并加密过的,GOOGLE未遂。开始用UE打开看了一下这个AXP文件,发 8 a: ~8 |' @: t0 z# J
现里面居然大部分都是明文的,开始以为只是把文件罗列在一起,不过仔细看了一下,发
7 b* m: e- a8 z8 P$ e$ t' \现每个文件都有一段间隔,前面还有一个数据头,而且文件与名字也无法对应。于是打开 + F" H5 [4 i6 R" y, T/ V# R, w) g
OD手动分析一下,主要过程其实比较简单,CreateFile函数下断,找到文件Buffer位置,
. x# e6 T" N5 H' Z再下内存访问断点即可来到关键代码区域。略过具体跟踪细节及文件校验部分不讲,文件 ; \" `' }, R( Y2 l
格式主要分析如下: " I& x3 w) t9 M: b$ q
/ O/ ^: @6 R" u0 L0 V3 J E
整个AXP文件可以分成四个部分:1.文件头 2.文件名索引 3.文件索引 4.文件数据 1 `* p' W$ n' l/ ~5 N
3 M! S* X1 O+ ^2 P3 Z H
; g. u+ B% f5 W2 o8 p( W# F1.文件头:
+ \3 h/ ~, T: m1 Y4 e 整个文件头固定为0x28字节,其中第20个字节开始的一个整数乘以12代表了第三部分 ; Z C j$ E2 [( A' [9 r. W+ B! l0 g
即文件索引部分的长度(因为每个索引有三个整数构成) ( e1 Q5 W7 Z+ O
2.文件名索引:
0 |4 k) x4 t3 Z# R j( U' F" G 整个文件名索引固定为0x60000字节,其中包含了每个压缩文件对应的文件索引位置 : D6 m2 |: w& ~
, w- ?5 o: a- h) ^2 y _9 h: _3 I3.文件索引:
+ u4 r% ^" [6 D' z/ R+ M 本部分长度由文件头相关数据决定,其中包含了每个压缩文件在.axp中的实际偏移位
9 x7 \/ ?" y* C" r置及文件大小 ' d' u' d+ d# t
4.文件数据: 7 W: B3 c( F2 Y5 D; R! l( C
本部分包含所有压缩文件的具体数据,每个文件之间用若干零填充。 9 l) f9 y: O' E3 l
# s- ?9 v, T$ f; E; a' A首先说说解压总体过程:比如我们要从A.axp中解压出一个叫file.txt的文件,那么先根据
- y) s$ n8 T4 K9 H6 H文件名file.txt到文件名索引中去找到对应的文件索引,然后再根据文件索引找到这个文
# |5 U+ Q- ^- H9 N# d; O- t2 W件在axp文件中的位置和大小,最后把其解压出来。
$ L6 Y: N, F5 D1 c. W2 ?% Z3 g" I* K1 S
解压具体过程如下: $ y: S0 {+ w1 T
将待解压的文件名转为小写(如果为英文字母),利用GetDisp(char* s,int v)函数
' }: z* ?2 Y. x2 W* T4 X" v' ^计算相关数据,其中s代表文件名,v代表计算参数,分别计算GetDisp(fname,1),GetDisp * o/ @1 s7 q4 n9 ?( N% h
(fname,2),
# p1 z8 c0 e' J' w4 z V! DGetDisp(fname,3),得到三个值a1,a2,a3。其中a3低位与在文件名索引中的位置有关,a3最 $ _0 g+ I3 Y3 @) h9 j# w# {' ]5 l
高位及a1,a2用来进行校验,如果三个值不能同时满足要求,则将偏移位置顺移继续验证, 6 U7 @8 p8 T4 L; s
具体细节懒得写了。
4 l4 z3 U; K# a- ^" T8 R6 \以下为GetDisp函数具体内容,我直接将跟踪代码里面的汇编改造了一下拿出来用,其中s # Y- B' v; Y6 l( U: N, t, j* E
ucks为一个随机数数组,这里不列出来了。
( Q/ \9 y3 V7 Z7 A% i7 gunsigned int TLBBUnpacker::GetDisp(char* s,int v) ( ]- c' ~ e k6 A/ D, f+ Y8 b
{
" T! W0 j% P1 f; P' p6 t__asm
6 G' |5 _3 J, f2 k6 Q# j{ # s- v: r% F S9 r! y% K6 g! v
push esi
+ J& J9 B. y' ?, c/ Nmov esi,s : v& O% O" A9 @% \, V0 s) V7 v
mov cl,byte ptr ds:[esi] 4 ^9 v7 E2 J. \/ y
test cl,cl
) q- [) O, z- p, O! O$ hmov eax,0x7FED7FED
' t2 E7 M7 [* Hmov edx,0xEEEEEEEE
. \/ N8 @% F1 C* Ije end
' B; s- T% Z8 W' ~; X3 |push ebx 3 k# B, L/ r/ i/ b: s
push ebp
; t% N# T0 i" Q3 Q" Upush edi
+ T$ ^; T" d1 u8 Z" g" O- U' X0 lmov edi,v
( i2 [+ W/ v: pshl edi,0x8
# r- H2 o4 Q+ f0 S" Miter: % ?' V7 l! l8 h3 O, Y- A
add eax,edx 2 \, k$ R9 i( H, ]
imul edx,edx,0x21
7 {. N: c' V. y- {6 bmovsx ecx,cl
2 u$ l7 | d S- flea ebx,dword ptr ds:[edi+ecx] 6 [! m; A0 S" K8 K8 O; @& [
mov ebp,dword ptr ds:[ebx*4+sucks]
6 R) ]" [ _. W# A" N, l$ Finc esi
4 ^: Y8 M+ C; v% badd edx,ecx
j+ J0 f4 `4 \& `* Hmov cl,byte ptr ds:[esi] 3 n/ V/ b0 `( B5 m- y* M# h
xor eax,ebp
4 L4 C! w9 ~4 h& v* A7 rtest cl,cl
% W8 N0 U8 v3 Y8 N/ llea edx,dword ptr ds:[edx+eax+3]
' R3 N* ]- c0 fjnz iter
9 P% E& v, q1 N3 Q0 [% O) Apop edi
& ?4 p( d9 {: d( `$ apop ebp
/ B8 z, a. x) e' O0 U, j4 B# C; w9 jpop ebx 6 Q Y: P3 @& D: n8 a2 M
end:
& n3 B& z/ O$ N$ Fpop esi 3 X$ [6 A: E2 Z& z
}
8 j& W2 s: X7 o+ U& ~- Y} 4 {3 F, v5 z" D! T2 a1 ~
这个为解压单个文件的函数GenerateFile,用到了QT作GUI,大家就当伪代码看吧。 8 C s8 `) U9 w q* o+ o& ]
bool TLBBUnpacker::GenerateFile(QString name) / u6 L+ E/ E1 O. |& }, Z5 G
{
+ w+ W) T6 C0 B3 x3 Z' kname=name.toLower();
3 U4 R: j1 M5 p7 s' r8 z) |unsigned int a=GetDisp(name.toLocal8Bit().data(),3),a2=GetDisp(name.toLocal8B 8 S g* \; V+ t2 H% n$ Y
it().data(),2),a1=GetDisp(name.toLocal8Bit().data(),1),b,disp,length; % d0 E- p3 z) r1 J0 C$ e; S
a&=0x7FFF; ; i' p8 v5 ~5 A% [
while(!((b=((int*)buffer2)[a*3+2])&0x80000000)||a1!=((int*)buffer2)[a*3]||a2! 3 }3 q6 ?8 B B
=((int*)buffer2)[a*3+1])
7 ]" h$ {: V# H' S4 R{ 2 _9 H* ]6 n+ R5 G# \+ [" n
a++;
% _9 j Z8 v/ b wa&=0x7FFF; ) T! q( I9 D2 a5 W6 S2 q* M/ j
}
2 n! `. u) E% v6 h. Ab&=0x3FFFFFFF;
9 l* ~$ L: f. m& r1 D# O9 Jdisp=((int*)buffer3)[b*3]; , V3 x, V8 ?! m% z
length=((int*)buffer3)[b*3+1];
' x9 ?2 l M( F0 U+ s, d' @2 \0 O' w) w0 f9 _5 q" M' C, P
QFile pdata(this->package_name);
5 T9 b/ |; P& d% o7 s5 \: i+ ?7 Rif (pdata.open(QFile::ReadOnly)&&pdata.seek(disp)) {
. f. O( f Z. H+ M$ {1 |* Z' d6 i) i6 d9 T
QString wdir=QDir::currentPath()+QDir::separator()+QFileInfo(pdata).fileName
5 P6 L0 r% S9 |5 F+ ^()+QDir::separator();
, m" [, j7 ]+ [ l8 a4 HQDir dir;
2 u: z! k/ ]& T7 I5 oQDataStream pfin(&pdata); 8 |7 n: c1 @0 f9 M
QFile file(wdir+name);
& s, Y4 ?% @ G# X+ c. Y( S ]QFileInfo info(file); - ?' {- A( F& E" O
dir.mkpath(info.absolutePath()); ; O+ u! v; {9 O- e* H6 q: S
if(file.open(QFile::WriteOnly)) $ j/ T/ S) S* ~, E1 v2 Q6 O
{ R4 R [) u Z3 b; _
char* pBuffer=new char[length];;
' D1 Q, g3 m9 T2 e9 UQDataStream fout(&file);
" J* C6 o. v$ O9 c1 k1 ?pfin.readRawData(pBuffer,length);
2 u1 N8 `3 U, ^4 _& n7 M& b/ Ifout.writeRawData(pBuffer,length); ' t: \8 }3 Z7 D) t/ @
delete []pBuffer;
. R) w1 o* h7 H, F/ xreturn true; 3 d# D( i2 }) Z$ F* Y; i! E, Z
}
: x$ x y- g3 X} " K2 y1 X4 `: f# B$ E `
return false;
o0 A- u( B) a( }3 m$ D7 I} . L( @2 _# r4 C
得到待解压文件在文件索引中的位置後就可以找出该文件在axp文件中的具体偏移量和文件
3 g+ [( u7 f T# p) g大小了,然后直接fseek一下然后在弄出来就OK了。 " d9 R9 j- C& O* W" o# H
最后说一下,这个AXP压缩包本身就含有一个文件列表文件叫做(list),所以每次只要先解
6 c" C: T' W1 }压缩这个文件,然后按照里面的文件列表来一一解压缩就OK了。 8 d# a- V$ V% ], y& l
1 ?& L$ T% B2 X5 n! e+ ]
' M5 Z! e& S/ J P: Q2 R6 i% U
以上就是文件大致格式,感觉还是比较简单的,也可以考虑在自己的项目中使用类似方法 $ m" ]* N, r! v0 r2 s- q3 M$ @8 o
进行文件压缩。 8 b: ~/ p3 v& v2 E# a' A$ P
6 D$ u6 O; C! h; Z; sPS:本文仅供学习,本人不负任何责任。。。。 |