学习一下人家的破解过程,这个游戏是开源游戏引擎OGRE进行开发的喔。 . i4 f' g% K. }! \
& a( x; J# C5 n" r" Q" C+ U4 ]# z u7 s U3 ?: u
: [/ E: d h6 n; @. w发信站: 饮水思源 (2007年06月22日02:36:37 星期五)
* i7 A9 [( I& \& F
8 F! |5 K0 C5 d2 c作者:[email protected]
6 U8 ~7 o; D3 Z, g
: O- n8 D# P. q' o6 W8 w ?5 j8 \三月份时玩了某狐公司的网络游戏《天XXX》,感觉还是蛮有意思的,遂研究了一下。
0 I* a- s, i) p这个游戏是利用开源游戏引擎OGRE进行开发的,看了一下目录里面的文件结构,主要的数 1 s5 P. L, h- i6 J
据都放在Data目录下面。不过文件基本都是.AXP后缀的,每一个动辄几十兆,料想肯定是 ! |" o! H( n- ^; h0 e m0 w7 _/ _
把游戏文件打包到一起并加密过的,GOOGLE未遂。开始用UE打开看了一下这个AXP文件,发 : o+ D e; e( E8 w
现里面居然大部分都是明文的,开始以为只是把文件罗列在一起,不过仔细看了一下,发
# U2 ?0 w2 o5 \现每个文件都有一段间隔,前面还有一个数据头,而且文件与名字也无法对应。于是打开 , F+ g+ z' X* N. K$ M) @ D) C4 \
OD手动分析一下,主要过程其实比较简单,CreateFile函数下断,找到文件Buffer位置, B+ t$ g- N8 Y; W9 {# ` L
再下内存访问断点即可来到关键代码区域。略过具体跟踪细节及文件校验部分不讲,文件
; w0 J$ I1 G% ?' S2 q6 z6 X格式主要分析如下:
- N- z( z- O l- h$ | s
. x, T! T% i+ J整个AXP文件可以分成四个部分:1.文件头 2.文件名索引 3.文件索引 4.文件数据 ' T* |8 X/ r. }' e2 c6 `* B1 g7 I% U
' [! X, a9 Y/ g" h- s( C
$ D8 c$ K: g7 K3 H- c$ a+ }8 k: d1.文件头: 1 P- X1 J% h+ z9 Q: ~ b/ ]1 d
整个文件头固定为0x28字节,其中第20个字节开始的一个整数乘以12代表了第三部分
% G2 x3 H' t* ]) |7 P* K8 q; | W即文件索引部分的长度(因为每个索引有三个整数构成) $ a; z& k0 b9 F0 T- R8 i& V6 G
2.文件名索引:
8 ^0 E2 `; r8 L! ` 整个文件名索引固定为0x60000字节,其中包含了每个压缩文件对应的文件索引位置 9 c$ r: \* D& F8 T2 i
- F& D/ x( u6 A1 X3.文件索引: 5 k5 {/ U: o2 j7 _
本部分长度由文件头相关数据决定,其中包含了每个压缩文件在.axp中的实际偏移位
: x* C% k& _5 M) Q2 {置及文件大小 : t6 P+ M: h0 L& q& }# x
4.文件数据:
0 U6 j' V% D) J% \ 本部分包含所有压缩文件的具体数据,每个文件之间用若干零填充。 0 d+ r' q: d$ ]" _+ L
/ g5 D! [: T& U
首先说说解压总体过程:比如我们要从A.axp中解压出一个叫file.txt的文件,那么先根据 6 G W' I) S: V6 u. ?8 d2 l
文件名file.txt到文件名索引中去找到对应的文件索引,然后再根据文件索引找到这个文 8 G! @+ \( Y- L+ F
件在axp文件中的位置和大小,最后把其解压出来。 6 S! J' [9 ~9 l$ k4 t! T. D
1 o& x' A5 D2 P' Z3 \( H
解压具体过程如下:
& [2 t3 f! w" l 将待解压的文件名转为小写(如果为英文字母),利用GetDisp(char* s,int v)函数
7 D2 a }0 _! h9 k计算相关数据,其中s代表文件名,v代表计算参数,分别计算GetDisp(fname,1),GetDisp
7 E0 v' q `4 R- a& P9 |* i(fname,2),
4 c* i \$ y) k, Y1 [GetDisp(fname,3),得到三个值a1,a2,a3。其中a3低位与在文件名索引中的位置有关,a3最
/ @! S$ _# f: X高位及a1,a2用来进行校验,如果三个值不能同时满足要求,则将偏移位置顺移继续验证, ' B- D1 T% y! w
具体细节懒得写了。
- g- |6 L, ?, Q5 L以下为GetDisp函数具体内容,我直接将跟踪代码里面的汇编改造了一下拿出来用,其中s ) H+ M/ |. R* ], F! \' C
ucks为一个随机数数组,这里不列出来了。
/ h( J$ r, p' c$ r) B0 Sunsigned int TLBBUnpacker::GetDisp(char* s,int v) 9 ^+ z$ ~3 r6 v3 c$ c9 I
{
# x* n g2 j. P3 [& {__asm
% ]2 f3 Z0 z$ J t{
5 \; e3 x' ], S6 E3 x1 I2 e- [4 ?: vpush esi
0 l# @$ g! `. M! h; l" hmov esi,s $ g3 A+ Y5 Q1 ~9 Z$ ~; e& m! [: L
mov cl,byte ptr ds:[esi]
G9 v$ s, t# r& Xtest cl,cl
# o' z# q. s% N" V6 umov eax,0x7FED7FED
) H+ N: E" U, l1 Z, P! G/ `/ e. ymov edx,0xEEEEEEEE
4 w. W) M9 r* Q9 bje end
! m3 { @# [+ A7 z) opush ebx : X$ _2 n6 ^' Z |" K, _1 O" I' R
push ebp ' K# r1 r# O3 T# g
push edi * s7 P4 V; [+ F& n( V3 f/ N
mov edi,v 4 e2 ?$ F4 B& S" Q& p
shl edi,0x8
) v" {7 A2 q7 B: e. p4 @3 ^iter:
- `% W. q8 W; o- k) nadd eax,edx
) v+ d; A7 E+ V7 \ p9 W ?imul edx,edx,0x21 % ^1 J7 ?) u4 r: @4 h. M
movsx ecx,cl % ~7 \3 B" y6 [, ?1 @6 \" i( c6 i
lea ebx,dword ptr ds:[edi+ecx] ) M% i t( X. R) C
mov ebp,dword ptr ds:[ebx*4+sucks] 3 O4 b2 ]( N9 Y: r! x3 ]' |* K
inc esi
- j: |7 c+ w! S' w5 L& Jadd edx,ecx 9 S2 |# e% E9 Q& \' `
mov cl,byte ptr ds:[esi]
1 A* e% e, g: vxor eax,ebp
5 T! _2 l( b. ^( stest cl,cl
/ k. N/ `% T* D4 d) {9 R; Hlea edx,dword ptr ds:[edx+eax+3]
/ {* i% ?/ ^2 K8 ^4 Q3 Ujnz iter
0 P. k; Z( L: L$ L) @5 I7 gpop edi
4 n- N! f* _/ L# j# Z/ j: Jpop ebp
) N! `. _+ S& M Jpop ebx 4 C* e' H5 B$ D; D% D3 n& Y
end: 9 B% u$ X; Z. ~, W
pop esi
" c. P9 @2 t" K( Z+ ?7 `2 E, N1 t}
+ n3 a( S4 i, ]8 ~# o} 6 z2 v' C, R }% h( E
这个为解压单个文件的函数GenerateFile,用到了QT作GUI,大家就当伪代码看吧。
4 S9 [; D; e, Y& a. a$ Ibool TLBBUnpacker::GenerateFile(QString name) # _; x5 p- v( w4 D( }
{
+ |9 J: A5 X# m0 T% _. P' w$ ]" f zname=name.toLower(); 9 E9 o! L5 i0 v- E6 R0 f" V% l
unsigned int a=GetDisp(name.toLocal8Bit().data(),3),a2=GetDisp(name.toLocal8B
, C& @9 H; O* W5 f& X# Q- I3 cit().data(),2),a1=GetDisp(name.toLocal8Bit().data(),1),b,disp,length;
- O) K6 V' h' ~1 Ma&=0x7FFF;
( O7 N% G; N4 n5 p) l9 t: d$ C4 Owhile(!((b=((int*)buffer2)[a*3+2])&0x80000000)||a1!=((int*)buffer2)[a*3]||a2!
0 C6 Q. b9 w7 K=((int*)buffer2)[a*3+1])
/ B- Z. S) d. W, \{
h' w( w" Q* q2 e& m& |) v* Fa++; $ q9 o/ ^# K8 }# _ v
a&=0x7FFF; $ l6 V( q* P# I; w# {/ G
} 9 V7 w, `% {$ b
b&=0x3FFFFFFF; - W# X O6 C; K: }5 g) d! S5 l2 h
disp=((int*)buffer3)[b*3];
. j" Z! x! T; O9 K& Z" z1 ?! Slength=((int*)buffer3)[b*3+1];
& t1 {* ^6 N" s e
5 E9 E* X1 [" [, Q0 \QFile pdata(this->package_name); 0 N- s3 H& ?: X; i. _/ \
if (pdata.open(QFile::ReadOnly)&&pdata.seek(disp)) { 7 {( t* A, J, r5 _: \
5 g6 Z+ M7 G( L6 n) H+ Z# ]% q
QString wdir=QDir::currentPath()+QDir::separator()+QFileInfo(pdata).fileName
3 g3 _5 d+ F0 |. Z- d6 I()+QDir::separator();
/ p- K9 j4 W& r1 T: r5 i- Z! vQDir dir; / r, e& b0 D) A& ?/ P+ d( K+ t
QDataStream pfin(&pdata);
p; S) c9 s2 O# \! FQFile file(wdir+name); ) V5 g, G% p6 V2 b$ I
QFileInfo info(file);
' S3 Y+ M0 n0 S; Wdir.mkpath(info.absolutePath()); * l6 [8 P* z6 j' L
if(file.open(QFile::WriteOnly)) ' V E5 v* W3 j
{
2 S6 r6 Z* D) N4 \+ G2 P! ^char* pBuffer=new char[length];; + F; V. O8 @) Y) |1 Y
QDataStream fout(&file);
6 F; {* G' |3 X7 s: X# E; @pfin.readRawData(pBuffer,length);
. k; _+ _% J! G B* Gfout.writeRawData(pBuffer,length); ' ~# w# m% a: D# k" M" X" Q: g
delete []pBuffer;
. R/ a, Y2 C* _+ Q: B: s2 hreturn true; * _5 U7 i, {5 N1 k: ]; e. f' S
} ( Z1 s* V' S. p |2 m( v
}
4 s/ e# {/ b+ z; D2 r8 Kreturn false;
x5 e# R" @* F8 N} % I7 D4 Q+ U# [' \
得到待解压文件在文件索引中的位置後就可以找出该文件在axp文件中的具体偏移量和文件 - Y- m* n; W$ g
大小了,然后直接fseek一下然后在弄出来就OK了。 & D5 F3 \* @/ q2 w8 [9 c n
最后说一下,这个AXP压缩包本身就含有一个文件列表文件叫做(list),所以每次只要先解 1 ^. \% r) C) h
压缩这个文件,然后按照里面的文件列表来一一解压缩就OK了。 6 o9 H1 [6 \* O$ q! V. h9 {2 ]
3 H7 q# B. ]8 D7 \ i- u' l3 n5 g" _9 c8 R h
以上就是文件大致格式,感觉还是比较简单的,也可以考虑在自己的项目中使用类似方法
/ J k0 ?0 o/ p. a8 m4 k6 N& J1 j( g* x& G进行文件压缩。 , {! V+ H, `- Q/ v( r/ r) k8 {$ V
+ t9 p( M: q& \1 uPS:本文仅供学习,本人不负任何责任。。。。 |