巧了,又有“江湖”。

这个江湖比爆炒江湖要复杂一点,不光是手动能做的事情更多不是纯放置,而且逆向流程也更长,还颇走了点弯路……

不弯的路

Frida Native Hook

关键函数长这样:

nm -DC libcocos2dlua.so | grep -i lual_load
# Signature是手动加的
0072f41c T luaL_loadbufferx(Lua_State *, char *lua_loadable, uint size, char *chunkname)

要用到后面三个参数:

  • lua_loadable: LuaJIT能认的Lua脚本或Bytecode,本文中是后者
  • size: lua_loadable的长度
  • chunkname: 可能是文件名也可能是其他,另存文件时候用得上

Frida Hook:

var func = Module.findBaseAddress("libcocos2dlua.so").add(0x72f41c);
Interceptor.attach(func, {
  onEnter: function (args) {
    # 这里理论上应该用replace(all)而不是split和join..
    this.fileout = "/sdcard/lua/" + Memory.readCString(args[3]).split("/").join(".");
    console.log("read file from: "+this.fileout);
    var tmp = Memory.readByteArray(args[1], args[2].toInt32());
    var file = new File(this.fileout, "w");
    file.write(tmp);
    file.flush();
    file.close();
  }
});

LJD Decompile Bytecode To Pseudo-but-valid Lua

# clone这个fork
git clone https://github.com/Aussiemon/ljd
cd ljd

adb pull /sdcard/lua .
python3 main.py -r ../lua -d output

output/里就是想要的东西了。

弯路

本来10分钟的工作量……

用现成代码解密

找到一个前辈帖子,帖子说Signature是六位,当前版本已经有8位了,果然跑不出结果。然后又找了几个其他版本的xxtea_decrypt,浪费了不少时间。

帖子提到密钥长度是128bit(实际上他代码里用了个fix_key_length取前16位),这和IDA里看到的、后来Frida观察内存里的,相互都不一样。。

写一份代码解密

仗着IDA和写过几行C++,直接忽略了网上的Hook教程,直接准备从libcocos2dlua.so里翻译个decrypt.c来做这事——万一有些文件游戏不load咋办?而且,硬核玩家怎么会用Hook这么粗暴的手段呢?还有到哪能找个Android 4.4真机来用Substrate?

写完没花多久,就是没写对,解不出东西来。不过写的过程中发现了xxtea_decrypt魔改过,.so里还遗留着许多对比文件签名根本不会跳进去的branch。

调bug陆陆续续用了好几天,最后还有个Overflow没什么头绪就先放着了,如果有人对此有研究或者感兴趣可以邮件/开Issue联系……

Frida使用

Frida文档倒是挺长的,就是参考价值不大,只能当函数列表用,所有的类型都要自己猜。其中有个Hook方法叫做findExportByName(“xxx.so”, “symbol”),用它没找到loadbuffer,也没有看到错误日志,可能是因为直接把nm demangle的symbol传进去了……

另外本来应该用Nodejs来跑,这样有语法高亮和自动indent,而不是在Python里拼字符串,写流水账的时候才反应过来。

LuaJIT

找到LuaJIT版本编译之后,一开始想法是按游戏加载顺序把这些加载到内存里去,然后导出成表或者写几个函数直接查。试着按游戏原本顺序依次loadfile,有些许报错,但是接下来的操作就不会了。

跑去看了看文档,比Frida还简陋,又不想读它源代码,放弃。

LJD年久失修

这个帖子LJD要手动加Opcode,加完一跑报异常了,修修补补好一会儿,才想起肯定有前人干过这事,看了看fork,找到上文里那个能用的版本。

接下来

前端太不熟练了,不然(感觉)可以很快搭个查询页面出来……现在指针/ID类型太多,编辑器里查着挺麻烦的。