0x00 先用WinHex看看少年西游记资源包package.was
粗略的分析可以把PackageWas的结构看成为这样:
┌─PackageWas
┊ ├─文件头
┊ ├─文件1
┊ ├─ ┊
┊ ├─文件n
详细的解构分析如下:
was文件头 32字节
12字节 | 文件标识 |
4字节 | 未知 |
4字节 | 未知 |
4字节 | 未知 |
4字节 | 子文件个数 |
4字节 | 未知 |
子块文件头 32字节
4字节 | 文件块偏移 |
4字节 | 当前文件数据长度 |
4字节 | 原始文件数据长度 |
4字节 | 压缩标志 值=1为压缩 |
4字节 | 加密标志 值=1为加密 |
4字节 | 未知标志 值默认为0 |
4字节 | 文件块长度 |
4字节 | 文件名长度 |
子块文件数据
n字节 | 文件名 |
1字节 | 文件名结束标志 |
4字节 | 原始文件数据长度 |
n字节 | 文件数据 |
0x01 在IDA中反汇编\lib\armeabi\libcocos2dlua.so文件,来分析它是怎样读取资源包数据的
主要看下图片中用红框标注的地方,逻辑其实很简单,关键是在对子文件数据的处理上 先是判断有没有用xxTea加密 然后判断有没有用zlib压缩
关于xxTea的key的寻找,可在IDA中 搜索SetXXTeaKey函数,然后查看该函数的引用位置就能找到key,可参考我的另一片文章 http://blog.csdn.net/blueeffie/article/details/51208285
0x02 分析完资源包结构和文件的解析处理后 那就直接上代码 解包吧(C# 代码片段)
//读取Was资源包文件
private void ReadPackageFile(FileInfo f)
{
UpdateText("开始解析资源包" + Path.GetFileName(f.FullName));
//使用委托开启异步,防止解析数据时界面假死
Func<bool> Analysis = new Func<bool>(() =>
{
return AnalysisPackageFile(f);
});
Analysis.BeginInvoke((isAnalysis) =>
{
this.BeginInvoke(new Action(() =>
{
if (Analysis.EndInvoke(isAnalysis))
{
UpdateText("开始导出资源包...");
OutPackageFile(f);
UpdateText("资源包导出完成!");
}
else
{
UpdateText("解析资源包失败!");
}
}), null);
}, null);
}
//分析Was资源包文件
private bool AnalysisPackageFile(FileInfo f)
{
byte[] bytes;
using (FileStream inStream = new FileStream(f.FullName, FileMode.Open, FileAccess.ReadWrite))
{
bytes = new byte[inStream.Length];
inStream.Read(bytes, 0, bytes.Length);
}
if (bytes != null && PacketUtil.IsWasFile(bytes))
{
try
{
int fileDataLength = 0;
for (int i = 32; i < bytes.Length; i += fileDataLength)
{
int Deviation = BitConverter.ToInt32(bytes, i); //获取当前文件块偏移
if (i == Deviation)
{
fileDataLength = BitConverter.ToInt32(bytes, i + 24); //获取当前文件块长度
byte[] fileByte = new byte[fileDataLength];
Array.Copy(bytes, i, fileByte, 0, fileDataLength);
WasData wasData = new WasData();
FileData fileData = wasData.intData(fileByte); //解析文件块
fileDataList.Add(fileData);
}
}
return true;
}
catch (Exception e)
{
UpdateText(e.ToString());
return false;
}
}
else
{
return false;
}
}
//导出包文件
private void OutPackageFile(FileInfo f)
{
for (int i = 0; i < fileDataList.Count; i++)
{
string path = Path.Combine(f.DirectoryName, Path.GetFileNameWithoutExtension(f.FullName) + "/" + fileDataList[i].name);
OutResFile(fileDataList[i].data, path);
}
fileDataList.Clear();
}
//输出文件
private void OutResFile(byte[] bytes, string path)
{
if (!Directory.Exists(Path.GetDirectoryName(path)))
{
Directory.CreateDirectory(Path.GetDirectoryName(path));
}
using (FileStream outStream = new FileStream(path, FileMode.OpenOrCreate, FileAccess.ReadWrite))
{
outStream.Write(bytes, 0, bytes.Length);
UpdateText("生成:" + path);
}
}
public FileData intData(byte[] bytes)
{
FileData fileData = new FileData();
FileHead fileHead = (FileHead)PacketUtil.BytesToStuct(bytes, typeof(FileHead));
fileData.name = GetFileName(bytes, fileHead);
fileData.data = GetFileData(bytes, fileHead);
return fileData;
}
private string GetFileName(byte[] bytes, FileHead fileHead)
{
byte[] nameByte = new byte[fileHead.FileNameLength];
Array.Copy(bytes, Marshal.SizeOf(fileHead), nameByte, 0, nameByte.Length);
return Encoding.Default.GetString(nameByte);
}
private byte[] GetFileData(byte[] bytes, FileHead fileHead)
{
byte[] dataByte = new byte[fileHead.CurrentFileLength - 4];
Array.Copy(bytes, fileHead.FileDataLength - dataByte.Length, dataByte, 0, dataByte.Length);
if (fileHead.EncryptFlag == 1) //需要解密
{
byte[] decryptByte = DecryptData(dataByte);
if (fileHead.CompressFlag == 1) //需要解压
{
return ZlibStream.UncompressBuffer(decryptByte);
}
else
{
return decryptByte;
}
}
else
{
if (fileHead.CompressFlag == 1) //需要解压
{
return ZlibStream.UncompressBuffer(dataByte);
}
else
{
return dataByte;
}
}
}
private byte[] DecryptData(byte[] bytes)
{
byte[] keyByte = Encoding.Default.GetBytes("27efb289-bc7f-3636-ae74-e747b1bea17c");
byte[] decryptByte = XXTea.Decrypt(bytes, keyByte);
return decryptByte;
}
zilb库 我用的 http://dotnetzip.codeplex.com
0x03 注意 .\package\spine 中的PNG图片,它本身并不是png图片而是JFIF图片,可以用XnView看图软件直接打开,或是把下图红框的内容删除后可以直接打开查看
资源提取工具源码下载:
链接:http://pan.baidu.com/s/1slEQ54p 密码:6dag