逆向流水账(3): 放置江湖Lua提取
巧了,又有“江湖”。
这个江湖比爆炒江湖要复杂一点,不光是手动能做的事情更多不是纯放置,而且逆向流程也更长,还颇走了点弯路……
不弯的路
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类型太多,编辑器里查着挺麻烦的。