轉(zhuǎn)自:http://bbs.sjtu.edu.cn/bbsgcon?board=Crack&file=G.1182450997.A
發(fā)信人: aSB (Go), 信區(qū): Crack
標(biāo) 題: 淺談網(wǎng)絡(luò)游戲《天龍X部》的文件加密格式
發(fā)信站: 飲水思源 (2007年06月22日02:36:37 星期五)
作者:aSB@bbs.sjtu
三月份時(shí)玩了某狐公司的網(wǎng)絡(luò)游戲《天龍X部》,感覺還是蠻有意思的,遂研究了一下。
這個(gè)游戲是利用開源游戲引擎OGRE進(jìn)行開發(fā)的,看了一下目錄里面的文件結(jié)構(gòu),主要的數(shù)
據(jù)都放在Data目錄下面。不過文件基本都是.AXP后綴的,每一個(gè)動(dòng)輒幾十兆,料想肯定是
把游戲文件打包到一起并加密過的,GOOGLE未遂。開始用UE打開看了一下這個(gè)AXP文件,發(fā)
現(xiàn)里面居然大部分都是明文的,開始以為只是把文件羅列在一起,不過仔細(xì)看了一下,發(fā)
現(xiàn)每個(gè)文件都有一段間隔,前面還有一個(gè)數(shù)據(jù)頭,而且文件與名字也無法對(duì)應(yīng)。于是打開
OD手動(dòng)分析一下,主要過程其實(shí)比較簡(jiǎn)單,CreateFile函數(shù)下斷,找到文件Buffer位置,
再下內(nèi)存訪問斷點(diǎn)即可來到關(guān)鍵代碼區(qū)域。略過具體跟蹤細(xì)節(jié)及文件校驗(yàn)部分不講,文件
格式主要分析如下:
整個(gè)AXP文件可以分成四個(gè)部分:1.文件頭 2.文件名索引 3.文件索引 4.文件數(shù)據(jù)
1.文件頭:
整個(gè)文件頭固定為0x28字節(jié),其中第20個(gè)字節(jié)開始的一個(gè)整數(shù)乘以12代表了第三部分
即文件索引部分的長(zhǎng)度(因?yàn)槊總€(gè)索引有三個(gè)整數(shù)構(gòu)成)
2.文件名索引:
整個(gè)文件名索引固定為0x60000字節(jié),其中包含了每個(gè)壓縮文件對(duì)應(yīng)的文件索引位置
3.文件索引:
本部分長(zhǎng)度由文件頭相關(guān)數(shù)據(jù)決定,其中包含了每個(gè)壓縮文件在.axp中的實(shí)際偏移位
置及文件大小
4.文件數(shù)據(jù):
本部分包含所有壓縮文件的具體數(shù)據(jù),每個(gè)文件之間用若干零填充。
首先說說解壓總體過程:比如我們要從A.axp中解壓出一個(gè)叫file.txt的文件,那么先根據(jù)
文件名file.txt到文件名索引中去找到對(duì)應(yīng)的文件索引,然后再根據(jù)文件索引找到這個(gè)文
件在axp文件中的位置和大小,最后把其解壓出來。
解壓具體過程如下:
將待解壓的文件名轉(zhuǎn)為小寫(如果為英文字母),利用GetDisp(char* s,int v)函數(shù)
計(jì)算相關(guān)數(shù)據(jù),其中s代表文件名,v代表計(jì)算參數(shù),分別計(jì)算GetDisp(fname,1),GetDisp
(fname,2),
GetDisp(fname,3),得到三個(gè)值a1,a2,a3。其中a3低位與在文件名索引中的位置有關(guān),a3最
高位及a1,a2用來進(jìn)行校驗(yàn),如果三個(gè)值不能同時(shí)滿足要求,則將偏移位置順移繼續(xù)驗(yàn)證,
具體細(xì)節(jié)懶得寫了。
以下為GetDisp函數(shù)具體內(nèi)容,我直接將跟蹤代碼里面的匯編改造了一下拿出來用,其中s
ucks為一個(gè)隨機(jī)數(shù)數(shù)組,這里不列出來了。
unsigned int TLBBUnpacker::GetDisp(char* s,int v)


{
__asm

{
push esi
mov esi,s
mov cl,byte ptr ds:[esi]
test cl,cl
mov eax,0x7FED7FED
mov edx,0xEEEEEEEE
je end
push ebx
push ebp
push edi
mov edi,v
shl edi,0x8
iter:
add eax,edx
imul edx,edx,0x21
movsx ecx,cl
lea ebx,dword ptr ds:[edi+ecx]
mov ebp,dword ptr ds:[ebx*4+sucks]
inc esi
add edx,ecx
mov cl,byte ptr ds:[esi]
xor eax,ebp
test cl,cl
lea edx,dword ptr ds:[edx+eax+3]
jnz iter
pop edi
pop ebp
pop ebx
end:
pop esi
}
}

這個(gè)為解壓?jiǎn)蝹€(gè)文件的函數(shù)GenerateFile,用到了QT作GUI,大家就當(dāng)偽代碼看吧。
bool TLBBUnpacker::GenerateFile(QString name)


{
name=name.toLower();
unsigned int a=GetDisp(name.toLocal8Bit().data(),3),a2=GetDisp(name.toLocal8B
it().data(),2),a1=GetDisp(name.toLocal8Bit().data(),1),b,disp,length;
a&=0x7FFF;
while(!((b=((int*)buffer2)[a*3+2])&0x80000000)||a1!=((int*)buffer2)[a*3]||a2!
=((int*)buffer2)[a*3+1])

{
a++;
a&=0x7FFF;
}
b&=0x3FFFFFFF;
disp=((int*)buffer3)[b*3];
length=((int*)buffer3)[b*3+1];

QFile pdata(this->package_name);

if (pdata.open(QFile::ReadOnly)&&pdata.seek(disp))
{

QString wdir=QDir::currentPath()+QDir::separator()+QFileInfo(pdata).fileName
()+QDir::separator();
QDir dir;
QDataStream pfin(&pdata);
QFile file(wdir+name);
QFileInfo info(file);
dir.mkpath(info.absolutePath());
if(file.open(QFile::WriteOnly))

{
char* pBuffer=new char[length];;
QDataStream fout(&file);
pfin.readRawData(pBuffer,length);
fout.writeRawData(pBuffer,length);
delete []pBuffer;
return true;
}
}
return false;
}

得到待解壓文件在文件索引中的位置後就可以找出該文件在axp文件中的具體偏移量和文件
大小了,然后直接fseek一下然后在弄出來就OK了。
最后說一下,這個(gè)AXP壓縮包本身就含有一個(gè)文件列表文件叫做(list),所以每次只要先解
壓縮這個(gè)文件,然后按照里面的文件列表來一一解壓縮就OK了。
以上就是文件大致格式,感覺還是比較簡(jiǎn)單的,也可以考慮在自己的項(xiàng)目中使用類似方法
進(jìn)行文件壓縮。
PS:本文僅供學(xué)習(xí),本人不負(fù)任何責(zé)任。。。。
附上一首解壓出來的游戲音樂,大理城山歌。。。
http://bbs.sjtu.edu.cn/file/Crack/1182450894261753.mp3