记一次安卓js加密过程

有关部门抽查审核反馈,安卓包内有明文的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).

看到这里就有了两个方案:

  1. 自己实现加解密算法,在onGetStringFromFile中hook这三个js文件,注入自己的读文件和解密方法

  2. 直接复用cocos本身的加密算法,将这三个js文件替换为对应的jsc,这样就不需要改加载代码

2的方案思路更简单直接,cocos在构建安卓工程的时候会有选项是否加密脚本,并且会自动生成一个密钥,那么问题来了,怎么使用cocos本身的脚本加密算法进行手动加密呢?
111

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

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++去手动做加解密
333

目录位置: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 就行了

完毕

8赞

有用,感觉会用到,现在他是我的了!

想问下,这个有关部门抽查是指什么部门?国内的?

国内的,这个不好多说

mark…