浅析android手游lua脚本的加密与解密(后续)
趁着周末,把lua的后续文章也写完了。
反编译对抗
众所周知,反汇编/反编译 工具在逆向人员工作中第一步被使用,其地位非常之高,而对于软件保护者来说,如何对抗 反汇编/反编译 就显得尤为重要。例如,动态调试中对OD的的检测、内核调试对windbg的破坏、加壳加花对IDA静态分析的阻碍、apktool的bug导致对修改后的apk反编译失败、修改PE头导致OD无法识别、修改 .Net dll中的区段导致ILspy工具失效等等例子,都说明对抗反编译工具是很常用的一种软件保护手段,当然lua的反编译工具也面临这个问题。处理这样的问题无非就几种思路:
- 用调试器调试反编译工具为何解析错误,排查原因。
- 用调试器调试原引擎是如何解析文件的。
- 用文件格式解析工具解析文件,看哪个点解析出错。
下面将以3个例子来实战lua反编译是如何对抗与修复。
例子1:一个简单的问题
这是在看雪论坛看到的一个问题,问题是由于游戏(可能是征途手游)将lua字符串的长度int32修改为int64,导致反编译失败的一个例子,修复方法请看帖子中本人的回答,地址:https://bbs.pediy.com/thread-217033.htm
例子2:2018腾讯游戏安全竞赛
这一节以2018腾讯游戏安全竞赛决赛第二题进阶版第1关的题目为例子,主要是讲一下如何修复当lua的opcode被修改的情况,以及如何修复该题对抗lua反编译的问题。
opcode问题及其修复
修复opcode的目的是 当输入题目的luac文件,反汇编工具Chunkspy和反编译工具luadec能够输出正确的结果。
首先,我们在ida中分析lua引擎tmgs.dll文件,然后定位到luaV_execute函数(搜索字符串“ ‘for’ limit must be a number ”),发现switch下的case的参数(lua的opcode)是乱序的,到这里我们就能够确认,该题的lua虚拟机opcode被修改了。
接着,我们进行修复操作。一种很耗时的办法就是一个一个opcode还原,分析每一个case下面的代码然后找出对应opcode的顺序。但是这一题我们不用这么麻烦,通过对比分析我们发现普通版的题目并没有修改opcode:
普通版lua引擎的luaV_execute函数 | 进阶版lua引擎的luaV_execute函数 |
---|---|
观察发现,进阶版的题目只是修改了每个case的数值或者多个值映射到同一个opcode,但是没有打乱case里的代码(也就是说,虚拟机解析opcode代码的顺序没有变,只是修改了对应的数值,这跟梦幻手游的打乱opcode的方法不同)。由于lua5.3只使用到0x2D的opcode,而一个opcode长度为6位(0x3F),该题就将剩余的没有使用的字节映射到同一个opcode下,修复时只需要反过来操作就可以了。分析到这里,我们的修复方案就出来了:
- 通过ida分别导出2个版本的 luaV_execute 的文本
- 通过python脚本提取opcode的修复表
- 在工具(Chunkspy和luadec)初始化lua文件后,用修复表将opcode替换
- 测试运行,修复其他bug
第一步直接IDA手动导出: File –> Produce file –> Create LST File ;第二步使用python分析,代码如下:
1 |
|
运行结果:
1 | 基础版: ['0'] 进阶版: ['6', '7', '0x16', '0x1B'] |
注意了,这里有几个opcode是没有对应关系的(默认是-1),跟踪代码发现,其实这些opcode的功能相当于nop操作,而原本lua是不存在nop的,我们只需在修复的过程中跳过这个字节码即可。
最后将获取的修复表替换到工具中,Chunspy修复点在DecodeInst函数中,修改结果如下:
1 | function DecodeInst(code, iValues) |
测试发现出错了,出错结果:
从出错的结果可以看出是luac文件的版本号有错误,这里无法识别lua 11的版本其实是题目故意设计让工具识别错误,我们将文件的第4个字节(lua版本号)11修改成53就可以了。正确结果:
luadec修复点在ldo.c文件的f_parser函数,并且增加一个RepairOpcode函数,修复如下:
1 | // add by littleNA |