Mas0n
mailto:MasonShi@88.com
翻车鱼

cocos2djsApp dump与hook,成功与失败

cocos2djsApp dump与hook,成功与失败

没错,又是cocos2djs,先不要关,先看我道明事情原委。

某好友问我:Cocos2d有办法动态调试嘛

并且给我发了张图

https://cdn.shi1011.cn/2021/05/67b3447ae020f687cd1349f165625b36.png?imageMogr2/format/webp/interlace/0/quality/90|watermark/2/text/wqlNYXMwbg/font/bXN5aGJkLnR0Zg/fontsize/14/fill/IzMzMzMzMw/dissolve/80/gravity/southeast/dx/5/dy/5
某某:遇到这种随机key和iv的没法解密明文咋整啊

我一看,想当然的,查看引用追回去不就行了。不过嘛,毕竟我比较懒,不想每次都去分析上下文。于是踏上了探索之路。

动态调试?(挨打)

先说结论:不行。除非App本身就是debug编译的,因为cocos打包的过程中不会把debug的js打包进去,虽然编译后的libcocos2djs.so里面包含了enableDebugger这个函数,然并软。

结论说完了,说说我的失败之路:自己建环境编译了个样本,想着用frida在适当的时机主动调用下enableDebugger….(此处省略一大波吐槽and废话)

贴贴Hook的代码:

function enableDebugger(){
    
    var v8Start = Module.findExportByName("libcocos2djs.so", "_ZN13ScriptingCore5startEv")
    var basic_string = Module.findExportByName("libcocos2djs.so", "_ZNSt6__ndk112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEE6__initEPKcj")
    var enableDebuggerPtrs = Module.findExportByName("libcocos2djs.so", "_ZN2se12ScriptEngine14enableDebuggerERKNSt6__ndk112basic_stringIcNS1_11char_traitsIcEENS1_9allocatorIcEEEEjb")
    var jsb_enable_debuggerPtrs = Module.findExportByName("libcocos2djs.so", "_Z19jsb_enable_debuggerRKNSt6__ndk112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEEEjb")
    
    if(v8Start == null) {
        // console.log("None evalString ptr");
        setTimeout(enableDebugger, 5);
        return;
    }
    var newstr = new NativeFunction(basic_string, "pointer", ["pointer","pointer", "int"]);
    var jsb_enable_debugger = new NativeFunction(jsb_enable_debuggerPtrs, "pointer", ["pointer","pointer", "int"]);
    var stdString = Memory.alloc(0x100);
    

    var strs = Memory.allocUtf8String("0.0.0.0");
    newstr(stdString, strs, 0x7);

    Interceptor.attach(v8Start, {
        onEnter: function(args){
            console.log("ssss");
            jsb_enable_debugger(stdString, new NativePointer(5123),0x0);

        },
        onLeave: function(arg){
            
        }
    })
}

setImmediate(function(){
    setTimeout(enableDebugger, 5);
})

我选择了ScriptingCorestart执行前的时机,主动调用了jsb_enable_debugger,在开启了debug模式的样本中能够成功的将端口从默认的6086转到了5123,然而到了未开启debug的样本中….翻车了

插桩

俗话讲得好,退一步海阔天空(明明是被逼无奈)。我从Android中的smali插桩技术得到启发:是不是可以在jsc文件解密后运行前的时机进行Hook替换?

思路有了,下面动手干。

首先当然是找时机。翻阅了一下Cocos2djs的文档JSB 2.0 使用指南 · Cocos Creator ,jsc文件解密加载的过程就在AppDelegate::applicationDidFinishLaunching中,IDA查看伪代码

https://cdn.shi1011.cn/2021/05/2c92fccb40f246a0c93b34265fee087d.png?imageMogr2/format/webp/interlace/0/quality/90|watermark/2/text/wqlNYXMwbg/font/bXN5aGJkLnR0Zg/fontsize/14/fill/IzMzMzMzMw/dissolve/80/gravity/southeast/dx/5/dy/5

很明显,甚至xxtea的key都已经外露。解密后,可以看到加载的时机就在jsb_run_script,一步步跟进去,最终进行加载的时机在se::ScriptEngine::evalString

// attributes: thunk
int __fastcall se::ScriptEngine::evalString(int a1, int a2, signed int a3, int a4, const char *a5)
{
  return _ZN2se12ScriptEngine10evalStringEPKciPNS_5ValueES2_(a1, (const char *)a2, a3, a4, a5);
}

翻找了下源代码

// ScriptingCore.h
/**
 * will eval the specified string
 * @param string The string with the javascript code to be evaluated
 * @param outVal The jsval that will hold the return value of the evaluation.
 * Can be NULL.
 */
bool evalString(const char *string, jsval *outVal, const char *filename = NULL, JSContext* cx = NULL, JSObject* global = NULL);


// ScriptingCore.cpp
bool ScriptingCore::evalString(const char *string, jsval *outVal, const char *filename, JSContext* cx, JSObject* global)
{
    if (cx == NULL)
        cx = _cx;
    if (global == NULL)
        global = _global.ref().get();

    JSAutoCompartment ac(cx, global);
    return JS_EvaluateScript(cx, JS::RootedObject(cx, global), string, strlen(string), "ScriptingCore::evalString", 1);
}

string参数就是要执行的js代码,Hook这一函数就能得到相应的代码或进行替换。

function hook() {
    Java.perform(function(){
        var evalString = Module.findExportByName("libcocos2djs.so", "_ZN2se12ScriptEngine10evalStringEPKciPNS_5ValueES2_")
        if(evalString == null) {
            setTimeout(hook, 100);
            return;
        }
        Interceptor.attach(evalString, {
            onEnter: function(args){
                var codeData = args[1].readCString();
                var codeSize = args[2];
                var pathName = args[4].readCString();
                send({Status:"hookOn", Data:codeData, Size:codeSize, Path:pathName});
            }
        })
    })
}

setImmediate(function(){
    setTimeout(hook, 10);
})

jscHookR

借助frida赋予python与js强大的交互能力,我写了一份脚本。

但是,此脚本也有未曾解决的问题:

替换时,不能够申请新的内存空间存放修改的js代码,只能在原来的内存地址上进行修改,导致修改后的文件大小不能超过原文件。这是此脚本最大的缺陷,目前并没有较好的办法解决,如果你有什么建议和思考,欢迎提出issue或联系我。

结语

失败乃成功之母!(无可奈何)

结语Plus

感觉最近写的文章都好水,开始不知道写什么了…让我想想下个月什么时候再写博客(逃了)

本文链接:https://blog.shi1011.cn/re/android/1422
本文采用 CC BY-NC-SA 3.0 Unported 协议进行许可

Mas0n

文章作者

推荐文章

发表评论

textsms
account_circle
email

翻车鱼

cocos2djsApp dump与hook,成功与失败
没错,又是cocos2djs,先不要关,先看我道明事情原委。 某好友问我:Cocos2d有办法动态调试嘛 并且给我发了张图 某某:遇到这种随机key和iv的没法解密明文咋整啊 我一看,想…
扫描二维码继续阅读
2021-05-02