去年发了一个帖子是分析魔女防御战的存档校验. 当时貌似提到了这个程序是Corona SDK, 逻辑核心是lua完成的.
废话不说, 今天我们就来实现针对游戏中lua逻辑的修改.
下载了1.8.0版的Defense Witches, 照例用decar解压(decar -x resource.car .\lu), 得到一堆lu文件.
这些.lu文件+0C的地方开始就是lua字节码, +8是大小.
今天下手的目标决定是初始化的300点, 这里还有个小插曲, 因为一开始以为那个叫做水晶点, 所以费了一番工夫, 各种修改都无效果.
一开始将main.lu反汇编成为main_luadec.asm, 在里面搜索300, 搜索到一行这个:
1 2 | 117 [-]: LOADK R0 K64 ; R0 := 300
118 [-]: SETGLOBAL R0 K63 ; GetCrystal := R0
|
K63/K64都是常量, 存储在这段代码块后面的常量表里面. 在main.lu搜索GetCrystal得到如下数据:
1 2 | 0A37h: [COLOR=red]04[ /COLOR ] 0B 00 00 00 47 65 74 43 72 79 73 74 61 6C 00 .....GetCrystal.
0A47h: [COLOR=red]03[ /COLOR ] [COLOR=darkorange]00 00 00 00 00 C0 72 40[ /COLOR ] ......Àr@
|
注意橙色标注的部分. 常量表的结构是开头是常量的总数的DWORD, 然后后面跟每条记录. 记录有一个字节的类型和后面的数据构成, 类型这里出现了2种, 03 = Number, 04 = String.
lua的字节码里面, Number是用标准的IEE 754编码的double, 占8个字节. 这个长度从lua头部也可以看出.
顺便八一下字节码文件的头部 前五个字节分别是, 0x1B,Lua, 0x51这里的51指定了是5.1, 后面还有是否是正式版本和Endian/各数据类型长度, 简单的说这个arm游戏的字节码跟x86是一样的.
想要折腾这个SDK的推荐一本pdf叫做ANoFrillsIntroToLua51VMInstructions.pdf, 网上也有各种不同的翻译版本, 里面描述了字节码文件和函数块常量数据, 指令等二进制编码方式.
在010Editor中把光标定位在橙色部分的首字节, 在Data Inspector里面, 在Double一栏将数据从300修改为2300, 保存.
尝试将resource.car做同样修改, 覆盖回程序目录, 试验运行, 果断闪退. 原因吗, 是因为_CodeSignature\CodeResources里面有每个资源文件的签名.
不过这个难不倒我们, 我们可以hook呀. 说干就干, 先用dumpdecrypted.dylib来dump出未加密的主程序, 打开IDA, 在字串列表里查找main.lu
1 2 3 4 5 6 7 8 | __text:0008677A 61 6B LDR R1, [R4,
__text:0008677C 01 23 MOVS R3,
__text:0008677E 20 6C LDR R0, [R4,
__text:00086780 09 68 LDR R1, [R1] ; a2
__text:00086782 4A F6 F2 42 C0 F2 09 02 MOV R2, (aMain_lu - 0x8678E) ; "main.lu"
__text:0008678A 7A 44 ADD R2, PC ; "main.lu"
__text:0008678C E3 F7 46 FC BL func_executebytecodefile
__text:00086790 80 46 MOV R8, R0
|

这个被调用的函数定义是这样的
int func_executebytecodefile(ResourceObjRef resource, lua_State *state, const char *filename, int flag)
其中ResourceObjRef是我的定义的类型指针, +0C地方是resource.car完整内容的内存映射指针.
写出替换函数如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | [FONT=Consolas][COLOR=blue][COLOR=blue]int[ /COLOR ][COLOR=
[COLOR=
[COLOR=
[COLOR=
[COLOR=
[COLOR=
[COLOR=
[COLOR=
[COLOR=
[COLOR=
[COLOR=
[COLOR=
[COLOR=
[COLOR=
[COLOR=
[COLOR=
[COLOR=
[COLOR=
[COLOR=
[COLOR=
[COLOR=
[ /COLOR ][ /FONT ]
|
因为对resource.car映射的内容写将引发bus error, 虽然有办法强写, 不过还是用了更简单的办法, 先复制一份再修改调用. 同时找到更内层的函数进行截取, 但是结果很遗憾, 和一开始说的一样, 这个GetCrystal是水晶也就是内购的货币, 我改错对象了.
然后再次把每个lu文件里面都搜索300.0过程中, 发现game.lu里面的_G["MP"]可能是我们想要的东西.
1 2 3 4 | 36 [-]: GETGLOBAL R1 K3 ; R1 := _G
37 [-]: SETTABLE R1 K18 K15 ; R1[ "MP" ] := 0
38 [-]: GETGLOBAL R1 K3 ; R1 := _G
39 [-]: SETTABLE R1 K19 K20 ; R1[ "HP" ] := 20
|
这段就是_G.MP = 0; _G.HP = 20; 对应的是画面可以用来购买和升级己方单位的点数和剩余塔的攻击剩余次数.
当时想法是, 初始化是0, 在什么地方被添加成了300吧, 那么我们把初始化的地方改为20, 不就变为320了么?
为什么非要用20, 那是因为SETTABLE opcode里面没有立即数寻址, 只有寄存器和常量寻址.
这个时候要了解一下lua的opcode编码方式了.
SETTABLE R1 K18 K15 是iABC格式的编码, 我们今天所用到的所有修改都是这个格式.
其中SETTABLE(助记符编号)是i, R1(目标) K18(源1) K15(源2)是A B C. 其中B/C可以使用常数和寄存器编号, 使用常数表编号时候, 最高bit置1, 也就是+256.
i的值查表可以得到, 每个opcode对应一个索引, SETTABLE是9, ADD是12.
手动换算的话, 就是9, 1, 256+18, 256+15
将这四个数字化为2进制, 然后按照
B:9 C:9 A:8 i:6的方式组合起来
1 2 | 100010010 100001111 00000001 001001
8943C049
|
[注意]看雪招聘,专注安全领域的专业人才平台!