没错,又是cocos2djs,先不要关,先看我道明事情原委。
某好友问我:Cocos2d有办法动态调试嘛
并且给我发了张图
我一看,想当然的,查看引用追回去不就行了。不过嘛,毕竟我比较懒,不想每次都去分析上下文。于是踏上了探索之路。
动态调试?(挨打)
先说结论:不行。除非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查看伪代码
很明显,甚至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
感觉最近写的文章都好水,开始不知道写什么了…让我想想下个月什么时候再写博客(逃了)
发表回复