针对cocos2d图片资源自定义加密的解密
发现某游戏其assets目录的图片打开是雪花, 而且用二进制打开查看文件, 找魔数啊什么的, 都没发定义, 是新的文件格式? 还是被加密了?
google到这篇文章 破解TexturePacker加密资源 - 使用IDA才想起来, 可以使用ida进行反编译.(调试后发现, 该游戏并不是使用TexturePacker提供的加密方式)
###在哪里解密的?
虽然游戏没有源码, 但是cocos2dx是有源码的, 参考进行调试, 还是蛮简单的, 而且记得一句话, 不管你怎么加密, 最后你在内存里, 肯定有一份解密之后的数据.
好, 按这个思路, 那我首先看, 这个解密之后的数据到底放在哪里?
根据以前的经验, cocos2dx的图片资源加载, 是在Image::initWithImageData(const unsigned char * data, ssize_t dataLen)
函数, 按照函数名称顾名思义:用数据初始化Image, 好了, 在ida 给这个函数下个断点, 先看看这个传进来的data
可以看到, data这个内存地址是debug148:797363E8
, 而且这个地址的内容跟上面打开看到的完全不一样, 再再而且, 前4个字节就是png文件的魔数!.png
那也就是说, 图片文件是在被读入内存之后, 在被初始化之前就解密了;
那图片在哪里被读入?
cocos2dx的图片资源加载, 一般都是从TextureCache::addImage(const std::string &path)
这个函数开始的, 参考源码
Texture2D * TextureCache::addImage(const std::string &path)
{
//cache中是否已经有这个图片
auto it = _textures.find(fullpath);
if( it != _textures.end() )
texture = it->second;
//没有
if (! texture)
{
do
{
image = new (std::nothrow) Image();
CC_BREAK_IF(nullptr == image);
//初始化image
bool bRet = image->initWithImageFile(fullpath);
CC_BREAK_IF(!bRet);
texture = new (std::nothrow) Texture2D();
//再用image初始化texture
if( texture && texture->initWithImage(image) )
{
//hold 住
_textures.insert( std::make_pair(fullpath, texture) );
}
else
{
CCLOG("cocos2d: Couldn't create texture for file:%s in TextureCache", path.c_str());
}
} while (0);
}
CC_SAFE_RELEASE(image);
return texture;
}
然后追到
bool Image::initWithImageFile(const std::string& path)
{
...
//从文件读入数据
Data data = FileUtils::getInstance()->getDataFromFile(_filePath);
...
}
其实也就是CCFileUtils.cpp
中的Data FileUtils::getDataFromFile(const std::string& filename)
这个CCFileUtils.cpp
是平台相关的, 每个平台有每个平台的具体实现. android的版本是CCFileUtils-android.cpp
文件
Data FileUtilsAndroid::getData(const std::string& filename, bool forString)
{
...
//上面一大堆, 主要就是先判断这个filename是绝对目录还是assets目录, //如果是assets目录则用AssetManager 来打开并读取,再做些分配内存什么的事情
//
int bytesread = AAsset_read(asset, (void*)data, fileSize);
size = bytesread;
AAsset_close(asset);
...
}
在ida中找到相应的代码查看
发现在AAsset_read
之后多了一个FileUtils::xxxxxxxxx
函数的调用, 并且,v10
这个指针指向的就是文件读入后的内存, 传给了这个函数.
进到函数看一眼:
哦? 内存每4个字节位与一个数再存回去? 得了, 基本是解密函数无疑. 验证下
在115行也就是AAsset_read
之后下个断
看到文件被读入内存, 并且内存中的数据跟文件是一致的
再在FileUtils::xxxxxxxxx
之后下个断
看到, v10 这个内存变化了, 出现了png的魔数.png
那么FileUtils::xxxxxxxxx
函数就是解密函数无疑
###知道解密算法了, 怎么把图还原出来?
很简单, 照着这个函数写一个c函数, 读文件进内存, 调这个函数解密, 就可以了
#include <stdio.h>
#include <stdlib.h>
void xxxxx(unsigned char* fileData, int fileSize)
{
int v5,v6,v7,v8,v9,i;
v5=0x00000000;
v6=0;
while(1)
{
v7=v6+v5;
if( v6 >= fileSize / 4)
break;
v8= 4 * v6;
v9 = *(int *)&fileData[4 * v6++];
*(int *)&fileData[v8] = v7 ^ v9;
}
for ( i = 0; ; *(&fileData[fileSize] + i) = *(&fileData[fileSize] + i) ^ 0xCC )
{
--i;
if ( ~i >= fileSize % 4 )
break;
}
}
int main(int argc,char** args)
{
if(argc <= 2){
printf("paramters wrong\n");
return 1;
}
char* filePath = args[1];
char* savePath = args[2];
printf("%s => %s\n",filePath,savePath);
FILE *fp = fopen(filePath, "r");
if(!fp)
{
printf("read file %s fail !!\n",filePath);
return 1;
}
fseek(fp,0,SEEK_END);
size_t size = ftell(fp);
fseek(fp,0,SEEK_SET);
unsigned char* buffer = (unsigned char*)malloc(sizeof(unsigned char) * size);
size_t readsize = fread(buffer, sizeof(unsigned char), size, fp);
fclose(fp);
xxxxx(buffer,readsize);
FILE *pFile = fopen(savePath, "w");
if(!pFile)
{
printf("write file %s fail !!\n",savePath);
return 1;
}
fwrite (buffer , sizeof(char), readsize, pFile);
fclose (pFile);
return 0;
}