posts - 14,  comments - 57,  trackbacks - 0
   继续未完成的内容,声明本文仅用于学习研究,不提供解压工具和实际代码
由于时间仓促,剑3资源格式分析(仅用于学习和技术研究)(一) 大部分只是贴出了分析的结果,并没有详细的分析过程,比如:如何知道那是一个pak文件处理对象,
如何根据虚表偏移获取实际函数地址等等,这就需要读者对c++对象在内存中的layout有一定基础。
开始正文了~~,先整理下前面的分析结果:
1、剑3是通过package.ini 来管理pak文件的,最多可配置key从 0-32(0x20)的32个文件。
2、每个pak文件都用一个独立对象来管理,所有的pak对象指针存储在一个数组里(这个后面会用到)。
3、pak文件格式:[pak标记(Uint32)] + [文件数目(Uint32)]+[索引数据偏移(Uint32)]+未知内容。另外,每个文件的索引数据是16个字节。

一、路径名哈希

  剑3的pak的内部文件是通过hash值来查找的,这样有利于加快查询速度。这就需要有一个函数通过传入 路径名返回hash值。
这个函数居然是导出的。。。g_FileNameHash  这个函数代码比较少,可以逆向出来用C重写,也可以直接使用引擎函数(LoadLibrary,GetProceAddress来使用)。
熟悉剑侠系列的朋友会发现,这个函数从新剑侠情缘(可能历史更久)开始就没有变过(确实没必要变),具体细节就不逐个分析了,我是写了一个单独的命令行工具
来测试的。

二、查询过程

查询函数在sub_10010E00 里,也就是(0x10010E00)的位置,我是通过简单分析g_IsFileExist 得知这个函数功能的。下面
来分析这个函数过程:

从前文可知,pak文件对象是存储在一个数组的这个数组是类似 KPakFile* m_szPakFile[0x21];
前面0x20个存储的都是KPakFile对象指针,最后一个存储的是数组长度。
这个搜索结构比较简单,就是遍历所有的KPakFile对象,逐个查询,找到了就返回。想知道具体怎么查询的吗,
接下来要看sub_100108B0了。

这个函数稍微有点长,分几个部分来分析吧:

首先,验证下Hash值是否是0,如果是0,肯定是错了:)
然后接着开始根据这个hash值进行查找了,经过分析,我发现这个函数其实是一个二分查找,代码贴出来如下 sub_10010320 :



从上面的代码还是比较容易可以知道,每个文件的16个索引数据中,前4个字节是hash值,这个函数返回的是这个文件是pak包的第几个。

接着前面的sub_100108B0 来看吧

这一段是保存查询到的数据到对象里。分析到这里,我只知道16个索引数据前4个字节是hash值,那么剩下的12个字节呢,
剩下的数据基本可以确定是:文件偏移、文件长度。我是个懒人,接下来的分析我是通过在fseek、fread下断点来得到的,为什么不是在SetFilePointer和ReadFile呢,
这是根据前面的分析得到的,因为pak文件管理对象使用的是C标准库函数。
根据fread和fseek的结果,可以得到如下结果:
索引数据构成是:
[哈希数值(Uint32)] + [文件偏移(Uint32)]+[未知数据(Uint32)] + 2(文件长度)+2(未知数据)。
剩下的,就是看看单独内部文件的解压方式了,
在fread的缓冲区上设置内存断点,就可以找到解压函数了:
sub_10018020
这个函数不算太长,一开始我也想逆向成C语言,后来看到如此多的分支就放弃了,转而用了一个偷懒的办法解决了:
从汇编代码可知这个函数的原型:
typedef int (*PUNPACK_FUN)(void* psrcData, int nSrcLen, void* pDstData, int* pDstLen);
直接加载剑3的dll,设置函数地址:
PUNPACK_FUN pEngineUnpack = (PUNPACK_FUN)((unsigned int)hEngineModule + 0x18020);
hEngineModule 是引擎dll的基址,大家看到了吧,dll的函数即使不导出,我们也是可以调用的:)

三、尾声

到这里,已经可以写出一个pak文件的解压包了,但是,我们还是没有还原真实的文件名,
下面是我解压的script.pak的文件的部分内容:

 终于看到大侠们的签名了。当然,对着一堆hash值为名字的文件,阅读起来确实很困难,
那么有办法还原真实的文件名吗,办法还是有一些的,可以通过各种办法改写g_OpenFileInPak记录参数名,来获取游戏中用到的pak内部文件名,相信这难不倒各位了。

posted on 2010-07-16 20:47 feixuwu 阅读(4661) 评论(11)  编辑 收藏 引用 所属分类: 逆向工程

FeedBack:
# re: 剑3资源格式分析(仅用于学习和技术研究)(二)
2010-07-16 22:20 | yafare
hook他的lua.dll加载脚本的函数,文件名以及脚本缓冲区都可以在堆栈找到。这样更简单一些  回复  更多评论
  
# re: 剑3资源格式分析(仅用于学习和技术研究)(二)
2010-07-16 22:47 | feixuwu
@yafare
恩,不过这个只对lua有效,打包lua文件一般用luaL_loadBuffer加载。
而且hook了luaL_loadbuffer是得不到文件名的,对除script以外的资源也无效。  回复  更多评论
  
# re: 剑3资源格式分析(仅用于学习和技术研究)(二)
2010-07-16 23:39 | yafare
@feixuwu

之前弄过剑侠世界的脚本,在luaL_loadBuffer断下之后,文件名是在堆栈里面可以找到的,当时dump了他的一套脚本,可以用来搞外挂了
  回复  更多评论
  
# re: 剑3资源格式分析(仅用于学习和技术研究)(二)
2010-07-17 00:14 | feixuwu
@yafare
int luaL_loadbuffer (lua_State *L,
const char *buff,
size_t sz,
const char *name);
打包文件加载是从内存中加载的,理论上来说是可以没有文件名的。不过也可能是为了方便调试,主动将最后一个name设置为文件名了。
外挂其实有更简单的办法,结合协程可以做一个单独的AI脚本,可以做得比较灵活。逆向资源最初是想做游戏却没有资源。。。  回复  更多评论
  
# re: 剑3资源格式分析(仅用于学习和技术研究)(二)
2010-07-17 10:32 | yafare
@feixuwu
没试过协程,hook了他lua脚本里面的一个timer,在那里面搞的外挂逻辑
  回复  更多评论
  
# re: 剑3资源格式分析(仅用于学习和技术研究)(二)
2010-07-17 11:36 | feixuwu
@yafare
yafare是常在cloud的blog发言的那位?  回复  更多评论
  
# re: 剑3资源格式分析(仅用于学习和技术研究)(二)
2010-07-17 14:46 | yafare
@feixuwu
Cloud是啥?
  回复  更多评论
  
# re: 剑3资源格式分析(仅用于学习和技术研究)(二)
2010-07-21 19:44 | suiniannian
很强很厉害。  回复  更多评论
  
# re: 剑3资源格式分析(仅用于学习和技术研究)(二)
2010-07-21 20:46 | suiniannian
你的文章很有用。
关于索引数据,结合你的理解我有两种猜测:
1. 索引数据构成是:
[哈希数值(Uint32)] + [文件偏移(Uint32)]+[解压后文件长度] + 2byte(文件在pak中的数据长度)+2byte(0000代表此文件在pak中没压缩,0020代表有压缩)。

2.用最后1byte就可以分别文件有没有压缩,所以也有可能是这样:
[哈希数值(Uint32)] + [文件偏移(Uint32)]+[解压后文件长度] + 3byte(文件在pak中的数据长度)+1byte(00代表此文件在pak中没压缩,0x20代表有压缩)。  回复  更多评论
  
# re: 剑3资源格式分析(仅用于学习和技术研究)(二)[未登录]
2013-04-26 16:38 | albert
感觉就像做梦~浏览完了,就是天书~看懂了,就快升天了,自己搞定,就又回到地面了~等完全透析,就进入地狱~  回复  更多评论
  
# re: 剑3资源格式分析(仅用于学习和技术研究)(二)
2013-08-19 18:23 | 得一帅
PAK文件介绍
  PAK文件是使用在剑网1,2,3,剑侠世界中的用来存放资源和相关游戏客户端文件的压缩文件格式。

PAK文件格式结构
  PAK文件格式被组织为一个线性的数据流,它是由一个XPackFileHeader头部开始,接着就是文件数据区里面每个子文件的数据内容,紧接其后的就是XPackIndexInfo信息,每个文件对应一个XPackIndexInfo,具体分布请看图1:
XPackFileHeader
File_Data_1
File_Data_2
File_Data_3
.......
File_Data_N
N * XPackIndexInfo
  



















                图1

数据结构以及说明
  XPackIndexInfo
    struct XPackFileHeader
    {
     unsigned char cSignature[4]; //四个字节的文件的头标志,固定为字符串'PACK'
     unsigned int uCount; //数据的条目数
     unsigned int uIndexTableOffset; //索引的偏移量
     unsigned int uDataOffset; //数据的偏移量
     unsigned int uCrc32; //校验和(根据索引表内容数据求得)
     unsigned int uPakTime; //打包文件制作时的时间,秒为单位time()
     unsigned char cReserved[8]; //保留的字节
    };
  
  uCount:这个PAK内一共包含文件的个数。
  uIndexTableOffset:文件信息XPackIndexInfo在文件中的偏移位置。
  uDataOffset:文件数据区在文件的偏移。
  
  
  XPackIndexInfo
    
    struct XPackIndexInfo
    {
     unsigned int uId; //子文件id
     unsigned int uOffset; //子文件在包中的偏移位置
     unsigned int uSize; //子文件的原始大小
     unsigned int uCompressSizeFlag; //子文件压缩后的大小和压缩方法
    };
    
    uId:是通过HASH代码得到的HASH值,是由该文件的目录决定的,并不是对文件内容进行HASH。
    uOffset:这个偏移地址是从文件开始算起的。
    uSize:并未压缩的文件大小。
    uCompressSizeFlag:包含2个内容,1是压缩的标记,2是压缩后的实际大小。最高字节表示压缩标记,低的三个字节表示子文件压缩后的大小,对于分块压缩的文件,包含该文件全部分块数据,头信息数据,分块信息表等加起来的全部大小,压缩标记可以使用 uCompressSizeFlag & 0xF0000000 得到,大小可以 uCompressSizeFlag & 0x07FFFFFF得到。
    
    数据区是没有数据结构的,它只是每个文件内容经过压缩后组成的一个线性的区域,其中压缩方式有3中:
    不压缩,压缩标记为0x00000000 表示这个文件的内容没有被压缩,如果某文件在使用UCL压缩后比原来还大,那么就不压缩了
    UCL压缩,压缩标记为0x20000000 表示经过了UCL算法压缩,uCompressSizeFlag里的大小和uSize不相同,UCL压缩后的大小最大支持128MB。
子文件分块压缩,压缩标记为0x10000000 表示该文件数据是经过分块压缩,就是文件内容被分成了几块,内容为该文件全部分块数据,头信息数据,分块信息表组成  回复  更多评论
  

只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理


<2010年7月>
27282930123
45678910
11121314151617
18192021222324
25262728293031
1234567

文章转载请注明出处

常用链接

留言簿(11)

随笔分类

随笔档案

搜索

  •  

最新评论

阅读排行榜

评论排行榜