二进制比较:破解 LuaJIT 加密脚本的一种新思路。直接修改,无需反编译!
本帖最后由 SQLite 于 2020-9-3 12:01 编辑有时候,一些游戏的Lua脚本会采用 LuaJIT 的方式来加密,但有些Lua脚本之间相互引用,导致了现有的反编译工具(如 ljd )无法100%地反编译出某些 LuaJIT 的源文件,进而出现一些“err: unwarper.py assert isinstance(else_warp_out, nodes.EndWarp), ”一类的错误。这也导致了我们反编译LuaJIT之后,虽然看到了 Lua 源文件的一些代码,却因为代码不全而无法重新编译,无法修改游戏逻辑。本文便着眼于该问题,提出了一种无需反编译、直接修改LuaJIT 脚本二进制代码的思路。实例如下: 某传奇类手机游戏有一个“挂机”按钮,点选后能够自动打怪。但在某些“挑战地图”中,该功能无法使用,需要氪金40元才可以。由于挂机功能比较复杂,推测其逻辑是在本地完成,遂对其进行一番剖析,实现了不氪金解锁挂机功能。之前从来没破解过手游,在此简要记录流程如下:
1、解压游戏的 apk 安装包,发现 lib 文件夹下 有 LuaCocos2d.so , 推测其为基于 Cocos2d 引擎的游戏,用 lua脚本实现游戏逻辑;
2、翻看游戏对应data目录中的文件,未找到*.lua 脚本,发现了 ZIP 文件,推测所有Lua脚本打包为了ZIP;
3、将 ZIP 复制到本地,双击打开提示“不是ZIP格式文件”,用文本编辑器打开,推测是采用了 cocos2d 的 xxtea 加密;
4、借助 IDA Pro 找到了该 ZIP 的加密 sign 和 key , 使用网上现成的 “XXTEA解密工具” 进行解密;
5、解密后的 ZIP 包解压后得到很多疑似脚本的文件,但用文本编辑器打开后是乱码,观察头部特征,推测为编译后的 LuaJIT文件;
6、使用 ljd 这一工具,写了个批处理脚本,对所有 LuaJIT 文件进行反编译,得到 Lua 源码。
7、通过对源码一番研究,找到了决定挂机能够启动与否的关键位置,将“ if not ”判定修改为“ if ”,应该可以实现既定目标,如图:
然而问题在于,通过 ljd 反编译出来的 lua 源码并不完整, 源码的头几行都是类似于“err: unwarper.py assert isinstance(else_warp_out, nodes.EndWarp), ”一类的错误信息。正因为 lua 源码并不完整,单独修改Lua源码中的“if not ”语句、并重新编译后的LuaJIT 二进制文件无法正常使用。所以通过“改完源码后再进行编译,然后替换原文件”这样的方式无法完成既定目标。
8、联想到在 X86平台 下的机器码修改技术,尝试不反编译该LuaJIT文件,而是对其二进制代码直接进行修改。【注:以 X86 汇编中的跳转指令为例(因为很多时候 if 语句 在汇编语言中都会用跳转指令实现),JZ指令对应的机器码是 74,而 JNZ 指令对应的机器码是75 ,也就意味着只需要改动一个数值,就能实现逻辑上的反转。】 那么我们应该修改哪里呢?没有IDA 、C32ASM 这样的反汇编工具来辅助分析,我们又应该如何确定“if not ”对应的二进制代码的位置?确定了之后又如何将其改为“if”?
通过观察,我发现lua脚本编译后的二进制文件和其源码是有一定对应关系的规律可循的。我们做个小实验找到它:
首先把这个关键函数复制下来,粘贴到 LUA 的 IDE 里面并保存为脚本,然后 使用 LuaJIt.exe 将其编译成二进制文件;再把 if not 改成 if ,使用 LuaJIt.exe 将其编译成另一个二进制文件。
然后用 010 editor 分别打开他俩,对比差异:
由图可知,二者区别就是一个 0E -> 0F ,一个 03 -> 04 。那我们尝试修改真正的脚本二进制文件:通过在目标二进制文件中搜索图中标红的“020E0003005803”关键字(共找到了9个),结合其中的字符串和汉字的辅助定位,以及之前源码的参考,我确定了想要的那一个位置,并修改之:
然后按照 0E -> 0F , 03 -> 04这么一改,然后替换 ZIP 中解压出来的原LuaJIT文件,重新打包(不用加密也可以),传到手机存储中的原位置替换原 ZIP,就可以实现"挑战地图挂机"这一功能了,看:
当然,由于我们只修改了一处判定逻辑,这么改就导致普通地图没办法挂机了。因为本文只做可行性研究,后续代码的完善就不讲了。
【后续:将以上代码改为 0E 00 00 后无论是正常地图还是挑战地图,都可以挂机了。详情见本帖 第15楼 】 逆向功底有待提升,0E 00 03 改成0E 00 00或者0F 00 00 更好 LuaJIT自带字节码解析,找到指令位置改了就行 写的很好 思路很开阔 谢谢楼主分享 辛苦了 优秀的思路,学习了
话说lua的反编译还没有突破吗 谢谢分享 谢谢分享,没法成功重编时不失为一个好方法 学习了好思路 不是直接把那个return删了就能实现挂机了吗{:1_918:} 本帖最后由 SQLite 于 2020-6-14 09:16 编辑
Skyfly 发表于 2020-6-14 08:08
不是直接把那个return删了就能实现挂机了吗
我们想要修改的LuaJIT 二进制文件是看不到 return 这个指令的,无法实现你说的删除。本文中出现的 “return” 只能在反编译出来的Lua 文件中看到,并未存在于游戏中的原始文件。而在 该Lua文件中中删掉“return"固然简单,但是删掉之后没办法重新编译为LuaJIT 啊(因为源码不全,编译出来之后不能正常使用).只能修改原始文件的二进制代码。