没错,又是cocos2djs,先不要关,先看我道明事情原委。
某好友问我:Cocos2d有办法动态调试嘛
并且给我发了张图
我一看,想当然的,查看引用追回去不就行了。不过嘛,毕竟我比较懒,不想每次都去分析上下文。于是踏上了探索之路。
动态调试?(挨打) 先说结论:不行。除非App本身就是debug编译的,因为cocos打包的过程中不会把debug的js打包进去,虽然编译后的libcocos2djs.so里面包含了enableDebugger这个函数,然并软。
结论说完了,说说我的失败之路:自己建环境编译了个样本,想着用frida在适当的时机主动调用下enableDebugger….(此处省略一大波吐槽and废话)
贴贴Hook的代码:
[color=rgba(0, 0, 0, 0.87)][size=1.25em]function [size=1.25em] [size=1.25em]enableDebugger [size=1.25em]( [size=1.25em]) [size=1.25em]{ [size=1.25em]
[color=rgba(0, 0, 0, 0.87)][size=1.25em]
[color=rgba(0, 0, 0, 0.87)][size=1.25em] [size=1.25em]var [size=1.25em] v8Start = Module. [size=1.25em]findExportByName [size=1.25em]( [size=1.25em]"libcocos2djs.so" [size=1.25em], [size=1.25em]"_ZN13ScriptingCore5startEv" [size=1.25em]) [size=1.25em]
[color=rgba(0, 0, 0, 0.87)][size=1.25em] [size=1.25em]var [size=1.25em] basic_string = Module. [size=1.25em]findExportByName [size=1.25em]( [size=1.25em]"libcocos2djs.so" [size=1.25em], [size=1.25em]"_ZNSt6__ndk112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEE6__initEPKcj" [size=1.25em]) [size=1.25em]
[color=rgba(0, 0, 0, 0.87)][size=1.25em] [size=1.25em]var [size=1.25em] enableDebuggerPtrs = Module. [size=1.25em]findExportByName [size=1.25em]( [size=1.25em]"libcocos2djs.so" [size=1.25em], [size=1.25em]"_ZN2se12ScriptEngine14enableDebuggerERKNSt6__ndk112basic_stringIcNS1_11char_traitsIcEENS1_9allocatorIcEEEEjb" [size=1.25em]) [size=1.25em]
[color=rgba(0, 0, 0, 0.87)][size=1.25em] [size=1.25em]var [size=1.25em] jsb_enable_debuggerPtrs = Module. [size=1.25em]findExportByName [size=1.25em]( [size=1.25em]"libcocos2djs.so" [size=1.25em], [size=1.25em]"_Z19jsb_enable_debuggerRKNSt6__ndk112basic_stringIcNS_11char_traitsIcEENS_9allocatorIcEEEEjb" [size=1.25em]) [size=1.25em]
[color=rgba(0, 0, 0, 0.87)][size=1.25em]
[color=rgba(0, 0, 0, 0.87)][size=1.25em] [size=1.25em]if [size=1.25em]( [size=1.25em]v8Start == [size=1.25em]null [size=1.25em]) [size=1.25em] [size=1.25em]{ [size=1.25em]
[color=rgba(0, 0, 0, 0.87)][size=1.25em] [size=1.25em]// console.log("None evalString ptr"); [size=1.25em]
[color=rgba(0, 0, 0, 0.87)][size=1.25em] [size=1.25em]setTimeout [size=1.25em]( [size=1.25em]enableDebugger, [size=1.25em]5 [size=1.25em]) [size=1.25em];
[color=rgba(0, 0, 0, 0.87)][size=1.25em] [size=1.25em]return [size=1.25em];
[color=rgba(0, 0, 0, 0.87)][size=1.25em] [size=1.25em]} [size=1.25em]
[color=rgba(0, 0, 0, 0.87)][size=1.25em] [size=1.25em]var [size=1.25em] newstr = [size=1.25em]new [size=1.25em] [size=1.25em]NativeFunction [size=1.25em]( [size=1.25em]basic_string, [size=1.25em]"pointer" [size=1.25em], [size=1.25em][ [size=1.25em]"pointer" [size=1.25em], [size=1.25em]"pointer" [size=1.25em], [size=1.25em]"int" [size=1.25em]] [size=1.25em]) [size=1.25em];
[color=rgba(0, 0, 0, 0.87)][size=1.25em] [size=1.25em]var [size=1.25em] jsb_enable_debugger = [size=1.25em]new [size=1.25em] [size=1.25em]NativeFunction [size=1.25em]( [size=1.25em]jsb_enable_debuggerPtrs, [size=1.25em]"pointer" [size=1.25em], [size=1.25em][ [size=1.25em]"pointer" [size=1.25em], [size=1.25em]"pointer" [size=1.25em], [size=1.25em]"int" [size=1.25em]] [size=1.25em]) [size=1.25em];
[color=rgba(0, 0, 0, 0.87)][size=1.25em] [size=1.25em]var [size=1.25em] stdString = Memory. [size=1.25em]alloc [size=1.25em]( [size=1.25em]0x100 [size=1.25em]) [size=1.25em];
[color=rgba(0, 0, 0, 0.87)][size=1.25em]
[color=rgba(0, 0, 0, 0.87)][size=1.25em]
[color=rgba(0, 0, 0, 0.87)][size=1.25em] [size=1.25em]var [size=1.25em] strs = Memory. [size=1.25em]allocUtf8String [size=1.25em]( [size=1.25em]"0.0.0.0" [size=1.25em]) [size=1.25em];
[color=rgba(0, 0, 0, 0.87)][size=1.25em] [size=1.25em]newstr [size=1.25em]( [size=1.25em]stdString, strs, [size=1.25em]0x7 [size=1.25em]) [size=1.25em];
[color=rgba(0, 0, 0, 0.87)][size=1.25em]
[color=rgba(0, 0, 0, 0.87)][size=1.25em] Interceptor. [size=1.25em]attach [size=1.25em]( [size=1.25em]v8Start, [size=1.25em]{ [size=1.25em]
[color=rgba(0, 0, 0, 0.87)][size=1.25em] onEnter: [size=1.25em]function [size=1.25em]( [size=1.25em]args [size=1.25em]) [size=1.25em]{ [size=1.25em]
[color=rgba(0, 0, 0, 0.87)][size=1.25em] [size=1.25em]console [size=1.25em]. [size=1.25em]log [size=1.25em]( [size=1.25em]"ssss" [size=1.25em]) [size=1.25em];
[color=rgba(0, 0, 0, 0.87)][size=1.25em] [size=1.25em]jsb_enable_debugger [size=1.25em]( [size=1.25em]stdString, [size=1.25em]new [size=1.25em] [size=1.25em]NativePointer [size=1.25em]( [size=1.25em]5123 [size=1.25em]) [size=1.25em], [size=1.25em]0x0 [size=1.25em]) [size=1.25em];
[color=rgba(0, 0, 0, 0.87)][size=1.25em]
[color=rgba(0, 0, 0, 0.87)][size=1.25em] [size=1.25em]} [size=1.25em],
[color=rgba(0, 0, 0, 0.87)][size=1.25em] onLeave: [size=1.25em]function [size=1.25em]( [size=1.25em]arg [size=1.25em]) [size=1.25em]{ [size=1.25em]
[color=rgba(0, 0, 0, 0.87)][size=1.25em]
[color=rgba(0, 0, 0, 0.87)][size=1.25em] [size=1.25em]} [size=1.25em]
[color=rgba(0, 0, 0, 0.87)][size=1.25em] [size=1.25em]} [size=1.25em]) [size=1.25em]
[color=rgba(0, 0, 0, 0.87)][size=1.25em] [size=1.25em]} [size=1.25em]
[color=rgba(0, 0, 0, 0.87)][size=1.25em]
[color=rgba(0, 0, 0, 0.87)][size=1.25em] [size=1.25em]setImmediate [size=1.25em]( [size=1.25em]function [size=1.25em]( [size=1.25em]) [size=1.25em]{ [size=1.25em]
[color=rgba(0, 0, 0, 0.87)][size=1.25em] [size=1.25em]setTimeout [size=1.25em]( [size=1.25em]enableDebugger, [size=1.25em]5 [size=1.25em]) [size=1.25em];
[color=rgba(0, 0, 0, 0.87)][size=1.25em] [size=1.25em]} [size=1.25em])
我选择了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
[color=rgba(0, 0, 0, 0.87)][size=1em][size=1.25em]// attributes: thunk [size=1.25em]
[size=1.25em]int __fastcall se::ScriptEngine:: [size=1.25em]evalString [size=1.25em]( [size=1.25em]int a1, int a2, signed int a3, int a4, const char *a5 [size=1.25em]) [size=1.25em]
[size=1.25em] [size=1.25em]{ [size=1.25em]
[size=1.25em] [size=1.25em]return [size=1.25em] [size=1.25em]_ZN2se12ScriptEngine10evalStringEPKciPNS_5ValueES2_ [size=1.25em]( [size=1.25em]a1, [size=1.25em]( [size=1.25em]const char * [size=1.25em]) [size=1.25em]a2, a3, a4, a5 [size=1.25em]) [size=1.25em];
[size=1.25em] [size=1.25em]}
翻找了下源代码
[color=rgba(0, 0, 0, 0.87)][size=1em][size=1.25em]// ScriptingCore.h [size=1.25em]
[size=1.25em] [size=1.25em]/**
[size=1.25em] * will eval the specified string
[size=1.25em] * @param string The string with the javascript code to be evaluated
[size=1.25em] * @param outVal The jsval that will hold the return value of the evaluation.
[size=1.25em] * Can be NULL.
[size=1.25em] */ [size=1.25em]
[size=1.25em] [size=1.25em]bool [size=1.25em] [size=1.25em]evalString [size=1.25em]( [size=1.25em]const [size=1.25em] [size=1.25em]char [size=1.25em] *string, jsval *outVal, [size=1.25em]const [size=1.25em] [size=1.25em]char [size=1.25em] *filename = [size=1.25em]NULL [size=1.25em], JSContext* cx = [size=1.25em]NULL [size=1.25em], JSObject* global = [size=1.25em]NULL [size=1.25em]) [size=1.25em];
[size=1.25em]
[size=1.25em]
[size=1.25em] [size=1.25em]// ScriptingCore.cpp [size=1.25em]
[size=1.25em] [size=1.25em]bool [size=1.25em] ScriptingCore:: [size=1.25em]evalString [size=1.25em]( [size=1.25em]const [size=1.25em] [size=1.25em]char [size=1.25em] *string, jsval *outVal, [size=1.25em]const [size=1.25em] [size=1.25em]char [size=1.25em] *filename, JSContext* cx, JSObject* global [size=1.25em]) [size=1.25em]
[size=1.25em] [size=1.25em]{ [size=1.25em]
[size=1.25em] [size=1.25em]if [size=1.25em] [size=1.25em]( [size=1.25em]cx == [size=1.25em]NULL [size=1.25em]) [size=1.25em]
[size=1.25em] cx = _cx;
[size=1.25em] [size=1.25em]if [size=1.25em] [size=1.25em]( [size=1.25em]global == [size=1.25em]NULL [size=1.25em]) [size=1.25em]
[size=1.25em] global = _global. [size=1.25em]ref [size=1.25em]() [size=1.25em]. [size=1.25em]get [size=1.25em]() [size=1.25em];
[size=1.25em]
[size=1.25em] JSAutoCompartment [size=1.25em]ac [size=1.25em]( [size=1.25em]cx, global [size=1.25em]) [size=1.25em];
[size=1.25em] [size=1.25em]return [size=1.25em] [size=1.25em]JS_EvaluateScript [size=1.25em]( [size=1.25em]cx, JS:: [size=1.25em]RootedObject [size=1.25em]( [size=1.25em]cx, global [size=1.25em]) [size=1.25em], string, [size=1.25em]strlen [size=1.25em]( [size=1.25em]string [size=1.25em]) [size=1.25em], [size=1.25em]"ScriptingCore::evalString" [size=1.25em], 1 [size=1.25em]) [size=1.25em];
[size=1.25em] [size=1.25em]}
string参数就是要执行的js代码,Hook这一函数就能得到相应的代码或进行替换。
[color=rgba(0, 0, 0, 0.87)][size=1em][size=1.25em]function [size=1.25em] [size=1.25em]hook [size=1.25em]( [size=1.25em]) [size=1.25em] [size=1.25em]{ [size=1.25em]
[size=1.25em] Java. [size=1.25em]perform [size=1.25em]( [size=1.25em]function [size=1.25em]( [size=1.25em]) [size=1.25em]{ [size=1.25em]
[size=1.25em] [size=1.25em]var [size=1.25em] evalString = Module. [size=1.25em]findExportByName [size=1.25em]( [size=1.25em]"libcocos2djs.so" [size=1.25em], [size=1.25em]"_ZN2se12ScriptEngine10evalStringEPKciPNS_5ValueES2_" [size=1.25em]) [size=1.25em]
[size=1.25em] [size=1.25em]if [size=1.25em]( [size=1.25em]evalString == [size=1.25em]null [size=1.25em]) [size=1.25em] [size=1.25em]{ [size=1.25em]
[size=1.25em] [size=1.25em]setTimeout [size=1.25em]( [size=1.25em]hook, [size=1.25em]100 [size=1.25em]) [size=1.25em];
[size=1.25em] [size=1.25em]return [size=1.25em];
[size=1.25em] [size=1.25em]} [size=1.25em]
[size=1.25em] Interceptor. [size=1.25em]attach [size=1.25em]( [size=1.25em]evalString, [size=1.25em]{ [size=1.25em]
[size=1.25em] onEnter: [size=1.25em]function [size=1.25em]( [size=1.25em]args [size=1.25em]) [size=1.25em]{ [size=1.25em]
[size=1.25em] [size=1.25em]var [size=1.25em] codeData = args [size=1.25em][ [size=1.25em]1 [size=1.25em]] [size=1.25em]. [size=1.25em]readCString [size=1.25em]( [size=1.25em]) [size=1.25em];
[size=1.25em] [size=1.25em]var [size=1.25em] codeSize = args [size=1.25em][ [size=1.25em]2 [size=1.25em]] [size=1.25em];
[size=1.25em] [size=1.25em]var [size=1.25em] pathName = args [size=1.25em][ [size=1.25em]4 [size=1.25em]] [size=1.25em]. [size=1.25em]readCString [size=1.25em]( [size=1.25em]) [size=1.25em];
[size=1.25em] [size=1.25em]send [size=1.25em]( [size=1.25em]{ [size=1.25em]Status: [size=1.25em]"hookOn" [size=1.25em], Data:codeData, Size:codeSize, Path:pathName [size=1.25em]} [size=1.25em]) [size=1.25em];
[size=1.25em] [size=1.25em]} [size=1.25em]
[size=1.25em] [size=1.25em]} [size=1.25em]) [size=1.25em]
[size=1.25em] [size=1.25em]} [size=1.25em]) [size=1.25em]
[size=1.25em] [size=1.25em]} [size=1.25em]
[size=1.25em]
[size=1.25em] [size=1.25em]setImmediate [size=1.25em]( [size=1.25em]function [size=1.25em]( [size=1.25em]) [size=1.25em]{ [size=1.25em]
[size=1.25em] [size=1.25em]setTimeout [size=1.25em]( [size=1.25em]hook, [size=1.25em]10 [size=1.25em]) [size=1.25em];
[size=1.25em] [size=1.25em]} [size=1.25em])
jscHookR 借助frida赋予python与js强大的交互能力,我写了一份脚本。
GitHub
Mas0nShi/jscHookR
a reverse engineering tool for android cocos2d-js engine application.
★ 15
但是,此脚本也有未曾解决的问题:
替换时,不能够申请新的内存空间存放修改的js代码,只能在原来的内存地址上进行修改,导致修改后的文件大小不能超过原文件。这是此脚本最大的缺陷,目前并没有较好的办法解决,如果你有什么建议和思考,欢迎提出issue或联系我。
结语 失败乃成功之母!(无可奈何)
结语Plus 感觉最近写的文章都好水,开始不知道写什么了…让我想想下个月什么时候再写博客(逃了)
出处:https://blog.shi1011.cn/rev/android/1422