拿到样本,解包无壳,如图所示jsc格式文件就是编译过的源代码

Jsc结构

lua开发生成的luac形式上很像,猜测一样是xxtea加密来的,同时CocosCreator上提供了个构建编译时填写密钥的选项,所以先不论是否有现成的反编译工具,这个密钥应该是必须要获取的。亦或者密钥也可能被转换成别的形式存放在某个地方,然后运行时用key解密。

密钥选项

起初还以为jsc跟V8有什么关系,看了非虫前辈的文章

才知道是用的另一个JavaScript解释引擎 SpiderMonkey,为了掌握更多的信息,上Jadx稍微读一下assets下二进制源代码的加载情况,在Cocos2dxActivity入口处我们看到有一个onLoadNativeLibraries函数,跳转到其声明发现加载了一个叫android.app.lib_name的字符串资源

onLoadNativeLibraries

AndroidManifest.xml下找到其定义为cocos2djs,即加载了libcocos2djs.so

libname

明确了加载Assets目录资源的操作大概率不在java层后,研究对象随即转移到libcocos2djs.so上。
IDA打开so,导出函数名很整洁看起来没有加什么奇怪的东西,搜索xxtea / key之类的函数名得到这几个相关函数

XXTEA

稍微看了下几个相关decrypt的函数内容,在xxtea_decrypt这个函数里看到有memcpymemclr操作,很像在开内存拷贝数据,并且sub_597B9C这个函数里大量的计算很像解密算法

sub_597B9C
通过CocosCreator源代码同时验证了这一点 jsb_global.cpp

其中的第三个参数正是xxteaKey,证实了我的猜想。所以当下考虑直接在运行时Hook这个函数打印出key。熟悉Xposed的我对Native层好像没什么办法

那就剩下俩个方案:写Magisk模块或者Frida脚本来Hook,最终选了没上手过的Frida,想着借此机会实践一下其威力。

按照官网wiki,随便扒拉两下在PC安装好Frida的python实现,Server端推荐使用Magisk仓库中的模块,无需自己推到手机/data/local/tmp目录下手动运行转发端口,开机直接挂载对应文件自动运行。

这里我选用网络adb的方式链接手机进行调试,别问 问就是局域网下不用插线很方便,终端adb connect 设备IP 连接上手机

终端输入 frida-ps -U 查看当前手机上的进程 以测试是否连通

haleclipse@Haleclipses-iMac:~$ frida-ps -U
  PID  Name
-----  ---------------------------------------------------
 3640  ATFWD-daemon
  707  adbd
  728  adsprpcd
26041  android.hardware.audio@2.0-service
  741  android.hardware.biometrics.fingerprint@

接着编写一个js脚本直接hook xxtea_decrypt函数

Interceptor.attach(Module.findExportByName("libcocos2djs.so", "xxtea_decrypt"), {
        onEnter: function(args) {
            console.log(Memory.readUtf8String(args[2]));
            //send("Function called!");
            console.log("\n");
        },
        onLeave: function(retval) {
        }
    });

终端输入

frida -U -f 包名 -l key.js --no-pause

拉起对应app进行hook

结果要么没输出,要么直接闪退,大多直接提示

libcocos2djs.so Error: expected a pointer

看起来没加载到这个so,那就是加载的时机不对。遂改进脚本,先hook dlopen函数当扫描到包含 libcocos2djs.so时进行xxtea_decrypt函数的hook

function hooknative1() {
    Java.perform(function() {
        Process.enumerateModules({
            onMatch: function(exp) {
                if (exp.name == 'libcocos2djs.so') {
                    send(exp.name + "|" + exp.base + "|" + exp.size + "|" + exp.path);
                    send(exp);
                    return 'stop';
                }
            },
            onComplete: function() {
                send('stop');
            }
        });
        var soAddr = Module.findBaseAddress("libcocos2djs.so");
        send(soAddr);
        hooknative2();
    });
}

这次想着应该能够命中了,结果运行以后打印出来的so愣是没有libcocos2djs.so,屡次复查无果

没有办法,所以dlopen这个时机抓不到那只能换条路,突然想到Java层的System.loadLibrary函数也是一个等价的hook时机,再次改进添加如下代码

function startHook() {
    Java.perform(function() {
        var Cocos2dxActivity = Java.use('org.cocos2dx.lib.Cocos2dxActivity'); //要hook的类名完整路径
        Cocos2dxActivity.onLoadNativeLibraries.implementation = function() { // 重写要hook的方法getSign,当有多个重名函数时需要重载,function括号为函数的参数个数

            var onLoadNativeLibraries = this.onLoadNativeLibraries(); //调用原始的函数实现并且获得返回值,如果不写的话我们下面的代码会全部替换原函数
            console.log("File loaded hooking");
            // send("arg1:" + arg1); //打印参数值
            // send("arg2:" + arg2);
            // send("arg3:" + arg3);
            // send("result:" + Sign); //打印返回值
            // return Sign; //函数有返回值时需要返回
            hooknative2();
        };
    })
}

这下就很准确的在libcocos2djs.so刚加载的时机hook。成功拿到key
成功拿到Key

拿到key剩下解密的逻辑直接参照CocosCreator源代码xxtea_decrypt部分,抄一份对应实现即可

其实按照Frida的Hook能力是一定能够将xxtea_decrypt解密后的内容打印出来的,但多次尝试都没办法打印出来,大概是因为类型没写对 又或者 是解密后的内容太大了就打出来一堆乱码。

最最后我发现,so的十六进制文本里直接就能定位到key(此地无银三百两哈哈哈哈艹

直接用Hex Workshop打开libcocos2djs.so搜索jsb-adapater
Hex下 key

Last modification:August 20th, 2020 at 01:56 pm
如果觉得我的文章对你有用,请随意赞赏