有关部门抽查审核反馈,安卓包内有明文的js文件,有高危风险,文件有以下三处:
assets/main.js
assets/jsb-adapter/jsb-builtin.js
assets/jsb-adapter/jsb-engine.js
解包发现,这三个文件其实是cocos的引擎源码。如何将其加密或者混淆,不以明文出现呢?
很容易能想到的一个方案是,将js文件手动加密,然后找到加载这几个js的地方,hook一个加密的过程即可。
在AppDelegate.cpp中找到主入口: applicationDidFinishLaunching
bool AppDelegate::applicationDidFinishLaunching()
{
se::ScriptEngine* se = se::ScriptEngine::getInstance();
jsb_set_xxtea_key("88881890-xxxx");
jsb_init_file_operation_delegate();
#if defined(COCOS2D_DEBUG) && (COCOS2D_DEBUG > 0)
// Enable debugger here
jsb_enable_debugger("0.0.0.0", 6086, false);
#endif
se->setExceptionCallback([](const char* location, const char* message, const char* stack){
// Send exception information to server like Tencent Bugly.
cocos2d::log("\nUncaught Exception:\n - location : %s\n - msg : %s\n - detail : \n %s\n", location, message, stack);
});
jsb_register_all_modules();
se->start();
se::AutoHandleScope hs;
jsb_run_script("jsb-adapter/jsb-builtin.js");
jsb_run_script("main.js");
se->addAfterCleanupHook([](){
JSBClassType::destroy();
});
return true;
}
其中23,24行就是在加载js脚本,跳进去jsb_run_script,会调到ScriptEngine.cpp的runScript,里面加载文件的代码在这行
std::string scriptBuffer = _fileOperationDelegate.onGetStringFromFile(path);
这是一个回调,具体实现在jsb_global.cpp
delegate.onGetStringFromFile = [](const std::string& path) -> std::string{
assert(!path.empty());
std::string byteCodePath = removeFileExt(path) + BYTE_CODE_FILE_EXT;
if (FileUtils::getInstance()->isFileExist(byteCodePath)) {
Data fileData = FileUtils::getInstance()->getDataFromFile(byteCodePath);
uint32_t dataLen;
uint8_t* data = xxtea_decrypt((uint8_t*)fileData.getBytes(), (uint32_t)fileData.getSize(), (uint8_t*)xxteaKey.c_str(), (uint32_t)xxteaKey.size(), &dataLen);
if (data == nullptr) {
SE_REPORT_ERROR("Can't decrypt code for %s", byteCodePath.c_str());
return "";
}
if (ZipUtils::isGZipBuffer(data,dataLen)) {
//解压逻辑 ...
return ret;
}
else {
std::string ret(reinterpret_cast<const char*>(data), dataLen);
free(data);
return ret;
}
}
if (FileUtils::getInstance()->isFileExist(path)) {
return FileUtils::getInstance()->getStringFromFile(path);
}
else {
SE_LOGE("ScriptEngine::onGetStringFromFile %s not found, possible missing file.\n", path.c_str());
}
return "";
};
分析代码可知:从文件读取字符串的逻辑为:先将js文件名转成.jsc后缀,先判断是否存在这个jsc文件,如果存在则读取文件内容,然后通过xxtea_decrypt进行解密,解密成功再判断是否进行了压缩,有则解压,否则转成字符串返回;如果不存在jsc文件,则直接从文件获取字符串(getStringFromFile).
看到这里就有了两个方案:
-
自己实现加解密算法,在onGetStringFromFile中hook这三个js文件,注入自己的读文件和解密方法
-
直接复用cocos本身的加密算法,将这三个js文件替换为对应的jsc,这样就不需要改加载代码
2的方案思路更简单直接,cocos在构建安卓工程的时候会有选项是否加密脚本,并且会自动生成一个密钥,那么问题来了,怎么使用cocos本身的脚本加密算法进行手动加密呢?

从论坛找到cocos有自带的jscompile工具,可以将js编译成jsc,工具在cocos的安装目录下:

cocos jscompile -s xxxgame\src -d xxxgame\src
但是这个工具只是进行了编译,并没有参数可以输入密钥进行xxtea加密,工具不可行.
由于xxtea是一个开源的算法,那网上找到这个算法,拿着密钥去加密文件就行了。省时间,直接网上找了一个工具
android cocoscreator jsc js 间加解密(六)_jsc 解密-CSDN博客
使用:
1:解密
执行文件跟jsc 文件放同目录下
不需要解压
decodeencodejsc -s 0 -k 密钥 -f 1.jsc
需要解压
decodeencodejsc -s 2 -k 密钥 -f 1.jsc
同目录下生成1.js
2:加密
执行文件跟js 文件放同目录下
不需要压缩
decodeencodejsc -s 1 -k 密钥 -f 1.js
需要压缩
decodeencodejsc -s 3 -k 密钥 -f 1.js
同目录下生成1.jsc
还有开源的仓库:
https://github.com/luckyaibin/cocoscreatorjscdecrypt
注意 :如果引擎升级,万一xxtea或zip 版本跟引擎的不一致怎么办?
其实在安卓工程下,会包含相关库,完全可以通过c++去手动做加解密

目录位置:jsb-default\frameworks\cocos2d-x\external\sources
加密替换成jsc后发现,其他两个文件没问题,但是main.js就是加载不到。解包发现,jsc竟然没有打到包里面去!那肯定是构建apk的时候,对文件做了处理了
查项目的gradle,发现,构建merge的时候会拷贝文件
variant.mergeAssets.doLast {
def sourceDir = "${buildDir}/../../../../.."
copy {
from "${sourceDir}"
include "assets/**"
include "src/**"
include "jsb-adapter/**"
into outputDir
}
copy {
from "${sourceDir}/main.js"
from "${sourceDir}/project.json"
into outputDir
}
}
main.js会单独拷贝,这里 改成main.jsc 就行了
完毕