针对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;
}