- 前言
- 前置准备
- 目标分析
- 逆向加密逻辑
- 定位sign与key
- 解密luac
- 反编译luajit的bytecode
- 开启上帝模式
- 前言
看了fatezero的《阴阳师:一个非酋的逆向旅程》后,受益匪浅。特别是关于opcode映射关系一节,处理的很精妙。
对手头上的游戏不思议迷宫,技痒的不行。于是上周周六花了整个下午的时间进行了研究。注意本文中针对安卓版本。
- 前置准备
如果读者也想跟着步骤进行操作,需要准备这些工具:ApkIDE少月版、IDA、python、010editor
- 目标分析
APKIDE载入之后,首先看一下libarmeabi目录下,发现了libcocos2dlua.so
从字面意思来看,应该是使用cocos2d引擎,并且使用lua脚本,再看assetssrc
发现大量的luac脚本,进一步确认了我们的想法。010editor打开Main.luac,从头来看,并非luac文件的头
正常的luaca的头应该是1B 4C 75 61开头,如下图
所以不思议迷宫必然是对luac进行了加密。
- 逆向加密逻辑
通过IDA打开libcocos2dlua.so,一般情况下加密会出现cocos2dx_lua_loader->luaL_loadbuffer的某个过程中
源码如下:
而在IDA中,luaL_loadbuffer之前出现了srcDecrypt函数,这可是源码中没有出现了。
数据的流向是:从文件读入->v51->v25->luaL_loadbuffer,再分析srcDecrypt函数
当一个文件的头为11 12 13 的时候,就用charMapList进行替换,而charMapList,通过引用查找
又是从buildEncrypyMap中初始化的,显然这是一组“静态”的置换表,完全可逆而且没有任何难度。
但是回过头来看apk中的luac文件,没有一个的文件头是11 12 13,文件头全部是applicationWillEnterForeground
并没有给我们带来任何帮助,只能继续分析luaLoadBuffer,看到了第二个加密的地方xxtea_decrypt
google,baidu之后,找到非常类似的一段源码
通过sign对文件进行标记,符合条件用key进行解密,梳理一下luac的整体解密过程
由于没有使用srcDecrypt的流程,所以实际上只有xxtea_decrypt,只要找到sign和key,问题就解决了。
- 定位sign与key
通过分析,可以确定加密的最终文件格式。文件头都有固定长度的sign
xxtea_decrypt(buf+decode->m_xxteaSignLen, (xxtea_long)size -(xxtea_long)decode->m_xxteaSignLen, (unsigned char*)decode->m_xxteaKey, (xxtea_long)decode->m_xxteaKeyLen, &len);
再次打开另外一个luac文件,二者相同的文件头如图,所以sign为applicationWillEnterForeground
在IDA中,我们也找到了这个字符串
查找引用之后,这个字符串在initLuaStack中被调用了。
再通过资料搜索,发现一般使用xxtea算法的,都会使用setXXTEAKeyAndSign来设置sign和key,图中v3就是setXXTEAKeyAndSign函数
stack->setXXTEAKeyAndSign("123", strlen("123"), "cloud", strlen("cloud"));
那么自然key就是:applicationDidEnterBackground
- 解密luac
首先pip install xxtea-py,安装python的xxtea的库
编写脚本如下:
import xxtea import os sign = 'applicationWillEnterForeground' key = 'applicationDidEnterBackground' def decode(filename): luacdata = open(filename,'rb').read() decrypt_data = xxtea.decrypt(luacdata[len(sign):],key[:16]) open(filename.replace('.luac','.luacx'),'wb').write(decrypt_data)
解密后如下,出现的文件头为1B 4C 4A 01,也并非luac标准头,难道还有名堂?
经过一番资料查询之后,这是luajit编译的bytecode,并非标准lua。其头为1B 4C 4A
- 反编译luajit的bytecode
在github上找到用于反编译luajit的项目:https://github.com/NightNord/ljd
由于该库只能在python3上跑起来,而我懒得改脚本,于是直接通过命令执行方式,在kali中(同时存在python2、python3)运行了如下脚本
#coding=utf-8 import os sign = 'applicationWillEnterForeground' key = 'applicationDidEnterBackground' path = os.path.join(os.getcwd(),'src') def decode(filename): luacdata = open(filename,'rb').read() decrypt_data = xxtea.decrypt(luacdata[len(sign):],key[:16]) open(filename.replace('.luac','.luacx'),'wb').write(decrypt_data) def decomplie(filename): os.system('python3 ./ljdm/main.py '+filename+' > '+filename.replace('.luacx','.luacxs')) for path,d,filelist in os.walk(path): for filename in filelist: filename = os.path.join(path, filename) if filename.endswith('.luac') and not os.path.isfile(filename.replace('.luac','.luac')): print( filename) decode(filename) if filename.endswith('.luacx') and (not os.path.isfile(filename.replace('.luacx','.luacxs')) or os.path.getsize(filename.replace('.luacx','.luacxs'))==0): print(filename) decomplie(filename)
src目录对应生成的luacxs就是最终反编译好的lua源代码了。
- 开启上帝模式
通过大量的分析之后,我发现程序员在代码中,没有去掉测试用的上帝模式,只是简单的隐藏
我在UIAccountBind.luacxs:380行中加入打开上帝模式的一行代码openGdUI().
这样当我点击复制账号id的时候就会弹出上帝模式窗口
local function onCopyClick(sender, eventType) if eventType == ccui.TouchEventType.ended then copyToClipBoard(rid, getLocStr("text_copied")) end openGdUI() return end
以及GDM.luacxs:24中的check_mode,让其始终返回为true,保证上帝模式功能可正常开启
function check_mode() return true end
然后通过luajit编译,再进行xxtea加密,打包成APK放入手机。
最终效果图为:全自动放置play,程序员已经实现了自动加天赋,自动捡东西,自动打怪,自动进入下一层等等