繼續(xù)未完成的內(nèi)容,聲明
本文僅用于學(xué)習(xí)研究,不提供解壓工具和實(shí)際代碼。
由于時(shí)間倉(cāng)促,劍3資源格式分析(僅用于學(xué)習(xí)和技術(shù)研究)(一)
大部分只是貼出了分析的結(jié)果,并沒(méi)有詳細(xì)的分析過(guò)程,比如:如何知道那是一個(gè)pak文件處理對(duì)象,
如何根據(jù)虛表偏移獲取實(shí)際函數(shù)地址等等,這就需要讀者對(duì)c++對(duì)象在內(nèi)存中的layout有一定基礎(chǔ)。
開(kāi)始正文了~~,先整理下前面的分析結(jié)果:
1、劍3是通過(guò)package.ini 來(lái)管理pak文件的,最多可配置key從 0-32(0x20)的32個(gè)文件。
2、每個(gè)pak文件都用一個(gè)獨(dú)立對(duì)象來(lái)管理,所有的pak對(duì)象指針存儲(chǔ)在一個(gè)數(shù)組里(這個(gè)后面會(huì)用到)。
3、pak文件格式:[pak標(biāo)記(Uint32)] + [文件數(shù)目(Uint32)]+[索引數(shù)據(jù)偏移(Uint32)]+未知內(nèi)容。另外,每個(gè)文件的索引數(shù)據(jù)是16個(gè)字節(jié)。
一、路徑名哈希
劍3的pak的內(nèi)部文件是通過(guò)hash值來(lái)查找的,這樣有利于加快查詢速度。這就需要有一個(gè)函數(shù)通過(guò)傳入 路徑名返回hash值。
這個(gè)函數(shù)居然是導(dǎo)出的。。。g_FileNameHash
這個(gè)函數(shù)代碼比較少,可以逆向出來(lái)用C重寫(xiě),也可以直接使用引擎函數(shù)(LoadLibrary,GetProceAddress來(lái)使用)。
熟悉劍俠系列的朋友會(huì)發(fā)現(xiàn),這個(gè)函數(shù)從新劍俠情緣(可能歷史更久)開(kāi)始就沒(méi)有變過(guò)(確實(shí)沒(méi)必要變),具體細(xì)節(jié)就不逐個(gè)分析了,我是寫(xiě)了一個(gè)單獨(dú)的命令行工具
來(lái)測(cè)試的。
二、查詢過(guò)程
查詢函數(shù)在sub_10010E00
里,也就是(0x10010E00)的位置,我是通過(guò)簡(jiǎn)單分析g_IsFileExist 得知這個(gè)函數(shù)功能的。下面
來(lái)分析這個(gè)函數(shù)過(guò)程:

從前文可知,pak文件對(duì)象是存儲(chǔ)在一個(gè)數(shù)組的這個(gè)數(shù)組是類似 KPakFile* m_szPakFile[0x21];
前面0x20個(gè)存儲(chǔ)的都是KPakFile對(duì)象指針,最后一個(gè)存儲(chǔ)的是數(shù)組長(zhǎng)度。
這個(gè)搜索結(jié)構(gòu)比較簡(jiǎn)單,就是遍歷所有的KPakFile對(duì)象,逐個(gè)查詢,找到了就返回。想知道具體怎么查詢的嗎,
接下來(lái)要看sub_100108B0了。
這個(gè)函數(shù)稍微有點(diǎn)長(zhǎng),分幾個(gè)部分來(lái)分析吧:

首先,驗(yàn)證下Hash值是否是0,如果是0,肯定是錯(cuò)了:)
然后接著開(kāi)始根據(jù)這個(gè)hash值進(jìn)行查找了,經(jīng)過(guò)分析,我發(fā)現(xiàn)這個(gè)函數(shù)其實(shí)是一個(gè)二分查找,代碼貼出來(lái)如下 sub_10010320
:


從上面的代碼還是比較容易可以知道,每個(gè)文件的16個(gè)索引數(shù)據(jù)中,前4個(gè)字節(jié)是hash值,這個(gè)函數(shù)返回的是這個(gè)文件是pak包的第幾個(gè)。
接著前面的sub_100108B0
來(lái)看吧

這一段是保存查詢到的數(shù)據(jù)到對(duì)象里。分析到這里,我只知道16個(gè)索引數(shù)據(jù)前4個(gè)字節(jié)是hash值,那么剩下的12個(gè)字節(jié)呢,
剩下的數(shù)據(jù)基本可以確定是:文件偏移、文件長(zhǎng)度。我是個(gè)懶人,接下來(lái)的分析我是通過(guò)在fseek、fread下斷點(diǎn)來(lái)得到的,為什么不是在SetFilePointer和ReadFile呢,
這是根據(jù)前面的分析得到的,因?yàn)閜ak文件管理對(duì)象使用的是C標(biāo)準(zhǔn)庫(kù)函數(shù)。
根據(jù)fread和fseek的結(jié)果,可以得到如下結(jié)果:
索引數(shù)據(jù)構(gòu)成是:
[哈希數(shù)值(Uint32)] + [文件偏移(Uint32)]+[未知數(shù)據(jù)(Uint32)] + 2(文件長(zhǎng)度)+2(未知數(shù)據(jù))。
剩下的,就是看看單獨(dú)內(nèi)部文件的解壓方式了,
在fread的緩沖區(qū)上設(shè)置內(nèi)存斷點(diǎn),就可以找到解壓函數(shù)了:
sub_10018020
這個(gè)函數(shù)不算太長(zhǎng),一開(kāi)始我也想逆向成C語(yǔ)言,后來(lái)看到如此多的分支就放棄了,轉(zhuǎn)而用了一個(gè)偷懶的辦法解決了:
從匯編代碼可知這個(gè)函數(shù)的原型:
typedef int (*PUNPACK_FUN)(void* psrcData, int nSrcLen, void* pDstData, int* pDstLen);
直接加載劍3的dll,設(shè)置函數(shù)地址:
PUNPACK_FUN pEngineUnpack = (PUNPACK_FUN)((unsigned int)hEngineModule + 0x18020);
hEngineModule
是引擎dll的基址,大家看到了吧,dll的函數(shù)即使不導(dǎo)出,我們也是可以調(diào)用的:)
三、尾聲
到這里,已經(jīng)可以寫(xiě)出一個(gè)pak文件的解壓包了,但是,我們還是沒(méi)有還原真實(shí)的文件名,
下面是我解壓的script.pak的文件的部分內(nèi)容:

終于看到大俠們的簽名了。當(dāng)然,對(duì)著一堆hash值為名字的文件,閱讀起來(lái)確實(shí)很困難,
那么有辦法還原真實(shí)的文件名嗎,辦法還是有一些的,可以通過(guò)各種辦法改寫(xiě)g_OpenFileInPak記錄參數(shù)名,來(lái)獲取游戲中用到的pak內(nèi)部文件名,相信這難不倒各位了。
posted on 2010-07-16 20:47
feixuwu 閱讀(4740)
評(píng)論(11) 編輯 收藏 引用 所屬分類:
逆向工程