0. 前言
本文是我在学习Android手游安全时总结的一篇关于lua的文章。 不足之处敬请谅解,欢迎大牛前来交流。 本文目录如下:
0. 前言
2.lua、luac、luaJIT文件之间的关系
3.Lua脚本保护
3.1 普通对称加密,加载脚本前揭晓
3.2 将lua脚本编译成luaJIT字节码但加密打包
3.3 修改lua虚拟机中opcode的顺序
4.获取lua代码的常用技巧
4.1 静态分析揭示方法
4.2 动态调试:ida + idc + dump
4.3 钩子
4.4 分析lua虚拟机的操作码顺序
5、揭秘三款游戏的lua脚本过程
5.1 54 钓鱼
5.2 钓鱼大师4
5.3 梦幻西游手游
六、总结
参考文章
主要使用的工具和环境:
1.Win7系统一
2、Quick-cocos2d-x开发环境(方便学习的一个开发环境,大部分lua手游都使用cocos2d-x框架,还有一个用处,可以在源码的关键函数中查看特征字符串,然后在IDA中定位关键函数就非常方便了)
3.IDA6.8(分析so文件+动态调试so)
4.vs2015(写秘码)这里推荐使用vs2013来编译运行cocos2d-x,vs2015有太多的坑需要填......
5、AndroidKiller 1.3.1(反编译apk,其中apktool.exe为最新版本)
6. luadec51(反编译luac)
7. luajit-decomp(反编译luaJIT)
ETC...
1、手游中lua脚本的现状
轻微地。
2.lua、luac和luaJIT文件之间的关系
在学习lua手游的过程中,我遇到的lua文件大部分就是这三种类型。 其中lua是纯文本代码,可以直接用记事本打开,luac是lua编译后的字节码,文件头为0x1B 0x4C 0x75 0x61 0x51,lua虚拟机可以直接解析lua和luac脚本文件,并且luaJIT 是 lua 的另一个实现版本(不是原作者写的),JIT 指的是 Just-In-Time(即时解析并运行),luaJIT 比 lua 和 luac 效率更高,文件头为 0x1B 0x4C 0x4A。
卢阿克:
卢吉特:
3.Lua脚本保护
一般有安全意识的游戏厂商不会直接将lua源码脚本打包成APK来发布,所以对lua脚本的保护通常有以下三种:
3.1 普通对称加密,加载脚本前揭晓
这种情况意味着APK中打包的Lua代码是加密的,程序在加载Lua脚本时就会泄露秘密(关键函数luaL_loadbuffer),只有解密后才能获取Lua源代码。 如果泄密后得到了luac字节码,那么通过反编译也可以得到lua源码。 反编译主要使用的工具是unluac和luadec51,后面会详细分析。
3.2 将lua脚本编译成luaJIT字节码但加密打包
因为反编译的结果不容易检查,这些情况可以更好地保护lua源代码。 这种情况,解密后反编译即可。 反编译主要是通过luajit-decomp项目,该项目也可以将luajit字节码反编译为伪lua代码。
3.3 修改lua虚拟机中opcode的顺序
这种情况主要是改变lua虚拟机的源代码,然后通过改变后的虚拟机将lua脚本编译成luac字节码,从而达到保护的目的。 这种情况下apktool源码如何编译,如果直接使用之前的反编译工具反编译luac,则需要分析程序中对应的opcode,然后改变lua工程的opcode的顺序,重新编译生成反编译工具,然后就可以反编译了是的,我们稍后会详细分析。
一般都会跨越之前的情况。
4.获取lua源码的常用技巧
这里我们主要介绍四种方法,将在第五节中举例说明。
4.1 静态分析so解密方法
这种方法需要分析揭秘的整个过程,费时费力。 主要方法是通过ida定位到luaL_loadbuffer函数,然后回溯分析泄密的过程。
4.2 动态调试:ida + idc + dump
这里我们主要使用ida来动态调试so文件,然后定位到luaL_loadbuffer地址。 游戏启动时会通过调用luaL_loadbuffer函数加载必要的lua脚本。 通过在luaL_loadbuffer中设置断点,可以运行idc脚本转换lua代码导入(程序调用一次luaL_loadbuffer加载一个lua脚本,如果idc脚本不写,需要自动导入N次...) 。
4.3 钩子
原理和4.2一样,都是通过钩子函数luaL_loadbuffer地址来保存代码。 与4.2相比,游戏过程中需要加载一些lua脚本。 如果使用4.2,则需要在游戏过程中自动中断一次。 运行一次 idc 脚本,通常一次只加载一个 lua 文件。 如果是钩子的话,就不用那么麻烦了。 只要玩一次游戏,所有的lua脚本就已经被保存了。
4.4 分析lua虚拟机opcode的顺序
这里的主要原因是操作码的顺序已经改变。 需要使用ida定位虚拟机执行luac字节码的地方,然后对比原来lua虚拟机的执行流程,获取修改后的opcode顺序,最后恢复lua脚本。
5、三款游戏lua脚本揭秘示例
好,我们用3个例子来说明一下里面的情况。
5.1 54 钓鱼
首先用AndroidKiller加载,然后查看lib目录下的so文件,发现libcocos2dlua.so文件基本都是lua脚本编译出来的。 这里有一个小方法。 当so文件很多时,一般最大的文件就是我们的目标(文件大是因为集成了lua引擎)。 既然有lua引擎,就一定有lua脚本,然后寻找lua脚本。 资源文件和lua脚本文件都在assets目录下。 发现游戏的资源文件和配置文件都是明文的,这里可以通过直接改变游戏的配置文件来作弊(比如改变升级要塞所需的金币和砖块就可以达到目的)快速升级炮台的方法),然后没有找到类似Lua脚本的文件。
我解压res目录下的liveupdate_precompiled.zip,发现解压失败。 好像是加密的(从名字就知道是更新游戏的代码)。 这是一个解释。 一般这些满足xxxx_precompiled.zip的文件都是quick-cocos2d-x框架(quick简单来说就是lua的扩展),在quick-cocos2d-x框架下,可以使用compile_scripts命令将lua文件加密打包成xxxx_precompiled.zipapktool源码如何编译,然后在游戏运行时加载。注意,这样打包的lua脚本通常会被编译成luaJIT。 加载的关键函数是loadChunksFromZIP。 你可以直接在IDA中搜索这个函数。 如果找不到,可以搜索字符串 luaLoadChunksFromZIP 来定位该函数。
OK,明白原理后,开始手工分析,将libcocos2dlua.so拖到IDA中加载,在函数中直接搜索loadChunksFromZIP,定位后F5。
一路往下走(交叉引用),来到右图,找到泄露出来的秘钥和签名,其中xiaoxian是秘钥,XXFISH是签名。
进入函数看一下,其实你会发现调用的是XXTea算法。 这里我们也可以直接分析loadChunksFromZIP函数的源码(所以配置一个cocos2d开发环境是非常有必要的)。 查看源码中lua_loadChunksFromZIP函数的原型:
intCCLuaStack::lua_loadChunksFromZIP(lua_State*L)
if(lua_gettop(L)m_xxteaSignLen,
(xxtea_long)大小-(xxtea_long)堆栈->m_xxteaSignLen,
(unsignedchar*)stack->m_xxteaKey,
(xxtea_long)stack->m_xxteaKeyLen,
&len);
删除[]zip文件数据;
zip文件数据=NULL;
zip=CCZipFile::createWithBuffer(buffer,len);
...
接下来直接写secret函数(cocos2d-x项目上写的secret函数,很多工具都可以直接调用)
voiddecryptZipFile_54BY(stringstrZipFilePath)
CCFileUtils*utils=CCFileUtils::sharedFileUtils();
无符号longlZipFileSize = 0;
无符号字符 * szBuffer = NULL;
unsignedchar*zipFileData=utils->getFileData(strZipFilePath.c_str(),"rb",&lZipFileSize);
xxtea_longxxBufferLen=0;
szBuffer=xxtea_decrypt(zipFileData+6, //6为签名XXFISH的宽度
(xxtea_long)lZipFileSize-(xxtea_long)6,//减去签名的宽度
(unsignedchar*) "xiaoxian", //xiaoxian 是密钥
(xxtea_long)8,//键的宽度
&xxBufferLen);
// 获取zip中的所有文件
CCZipFile*zipFile=CCZipFile::createWithBuffer(szBuffer,xxBufferLen);
整数计数=0;
stringstrFileName=zipFile->getFirstFilename();
while(strFileName.length())
库特