首页
社区
课程
招聘
[原创] Cocos2d-x与LuaJIT汇编的初步解密
发表于: 2017-11-19 01:00 19418

[原创] Cocos2d-x与LuaJIT汇编的初步解密

2017-11-19 01:00
19418

最近在玩一个游戏,于是想要拿下它其中的所有立绘。在逆向的过程中,开拓了一些前人没有发掘的领域,无论是中文还是英文都没有相应的资料。本文证明了,即使Lua源码被编译过,也不是绝对的安全。厂商们打的小算盘可能是——没有成熟的工具,不好破解。但是对于了解原理的安全研究者,这不是问题。反而对于不了解原理,着重于快速开发复用代码的游戏开发者来说,没有成熟的防护体系,无论是自己造轮子还是用别人的轮子,造成的威胁都会更大。

先上一张解密后的样图。透明背景带Alpha层的png,很容易拿来做坏事,比如做套昆特牌啥的。
sample

玩过的童鞋可能一眼就能认出来,嘿嘿嘿,游戏本身我不多作评价,本文所分析的版本为官网下载20170922版本。

解包后发现所有的png资源全部加密了打不开(嘿,但是音频还没有加密……不过并不会有人看嘛)。结合lib发现游戏是cocos2x与Lua编写。虽然Lua出了名的好逆,但是查看文件头后发现是编译后的LuaJIT……还是最新的2.1版。一番操作后找到一个项目 luajit-lang-toolkit(以下简称LLT),不过用起来有点小问题。make以后在src文件夹生成了luajit-x程序,与luajit的命令行工具参数一样。

我们可以看到……啥也看不到……但是官方命令行工具的反汇编结果却大有不同。

你看人家至少有个RET啊!

直接撸汇编可以,但是LLT可以解析更多的东西,怎么办?我仔细参考了一下opcode表,发现很多指令与正确指令之间在指令表之间(比如第一行的TGETVGGET)只有两个指令的距离。再翻了翻LLT的issues里面有提到了2.1版本,某人还为他写了个patch。我才明白——因为2.1的指令表与2.0相比,中间插入了两个指令,因此后面的指令在表中的位置都相应后挪。

我完善了一下这个patch,将更新的指令表加入了读写过程中,修改后的版本位于我的Github中的v2.1分支。运行修改后的版本输出就正常了。

这个工具可以列出文件的hexdump与子程序的栈帧、局部变量等信息,是我特别心水的地方。下面进入逆向部分。

首先先把所有字节码反汇编并保存至src目录。在命令行中运行

查看PNG头发现字样img.libla,在png以外的文件里查找此字符串

如此看来解密过程应该就在libcocos2dlua.so中。先拖IDA里让它分析起来。好吧勉强承认一下南辕北辙了(没事Lua部分后面用到)。

在这个so文件里搜索img.libla 定位到函数cocos2d::Image::isMpi(uchar const*,int) 。去cocos2d-x官方文档里查询了一下竟然连Image这个类都没有。感觉不太可能,翻2.x版本的文档找到了对应的类CCImage,一直往后翻发现3.4是最后一个拥有这个类的版本。在字符串里搜一下3. 找一下有没有具体版本,有了意外发现。

噫~原来是用了框架。在GitHub上找到这个框架,作者表示选择了“公认最稳定的版本”3.3。 随便搜一搜还能找到很多有意思的加密方式(见参考资料)。我们开心的开始带源码作业。但是简单搜索后却发现源码里根本没有这个函数。我们回到IDA中,查看调用这个函数判断文件类型的地方。

而调用这个函数的地方附近又是这样。

于是我们的目的就明确了——开发者魔改了cocos2d::Image::initWithImageData加入了自己的文件格式,并在 detectFormat 识别格式时调用 isMpi 判断并指定输入文件是否为自己的私有文件格式,然后自己写了一个initWithMPIData并在其中进行了解码或解密操作。我们要做的就是解析或利用这个函数的反向过程来解密图片资源。

开始我尝试了用unicorn-engine模拟运行。但是后来发现函数里调用了malloc等标准库函数,处理起来很麻烦。IDA调试的选项也被我pass掉了,结合cocos2dx在CCImage.cpp中对图片的处理过程可知,内存里面存的是原始的RGBA数据,即使做出来内存dump也卵用无。折腾两天后决定利用NDK自己写一个wrapper,利用这个函数已导出的特性,从so里面获取这个函数并调用它来为我们解密。

我们先从已有代码入手判断一下Image类的结构。结合一下 CCImage.hCCImage.cppcocos2d::Image::initWithPngData 与其在IDA中反汇编的对应关系整理如下。

其中要注意,这里一定要打开IDA的Show casts显示强制类型转换,例如*((_DWORD *)this + 6) = 4 * v5;这一句中实际被赋值的成员变量的偏移量不是6个字节而是6*DWORD=24字节。

我们据此编写这个对象的C++代码并测试一下是否需要改动。

经NDK编译在armv7设备上运行结果如下(如果编译遇到缺标准库问题,在Application.mk里指定一下GNU C++运行时即可)

bingo~由于里面的成员变量都是int或指针一类的东西,所以不怎么需要考虑对齐的问题。如果有问题的话参考一下下面参考资料里的ARM文档。

然后就是编写程序了。C++类成员函数在编译时被mangle成一个乱乱的字符串,在IDA里或objdump都可以获得,原样拿来并用dlsym寻找函数指针,用我们伪造的Image类作为第一个参数的this指针调用即可。至于如何将Image对象的内容保存为png,这个就留给大家作为思考题,方法是一样的。

注意,这里没有写但是我已经提前分析过我们要调用的函数对Image类的读写状况,对成员变量的所有操作都是先写入再读取,不存在未初始化的情况。如果大家要用类似方法调用其他函数,也要注意传入的数据成员的值。

push到手机上运行一下,结果还不错。顺便一提,1.out.png和1.png的大小是完全一样的。说明这个算法完全可逆。就是我懒而已……

OK,然后用shell脚本批量处理一下就可以把所有文件都解密掉了。接下来我们要做的就是,将每个图片与他的角色匹配起来,顺便看一看能不能挖掘出其他的信息。

这是一个三国题材的游戏,我们搜索关键字“赵云”,定位到类 app.data.db.cardDef 。这里再赞一下LLT,kgc和knum信息都是官方工具无法dump出来的。而官方文档又晦涩难懂(还一堆TODO干脆就是没写完),稍微好一点的教材就是LLT对字节码解析过程的源代码了。

在这里就结合一下例子来解释一下 LuaJIT 生成的二进制文件格式。看最左边的hex与右侧的数字注释

我解释的比较概括,更准确的定义需要参考官方文档上的范式。

我们可以看见,字节码格式非常紧凑,几乎没有浪费什么空间。而且不得不说,写程序解析原始字节码比读取LLT输出的文本要容易得许多……所以我写了一个。

下面的脚本用于解析LuaJIT二进制文件并将其中的常量信息提取到一个csv文件中。

得到类似下面格式的csv。表头被我删去了。太长,丑。但是下面这一串信息里面已经包含了绝大多数我们可能需要的信息,包括角色攻击力、生命值、售价、技能ID、语音ID等等,甚至还有放大招的时候喊的口号。也有很大的潜力哦~

搜索一下文首配图的文件编号2308,找到大概在关羽那行的第……呃……58列。当然这列有名字就叫icon,而名字顾名思义列名就叫name。继续祭出python把解密输出目录中所有名字中含编号的文件批量重命名。

大功告成!

result

这部分是弄完了,但是学无止境嘛~好了我学PIL去了,再把图切切剪剪做个卡牌合集去(逃

原文首发于 本人博客

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

收藏
免费 2
支持
分享
最新回复 (12)
雪    币: 144
活跃值: (38)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
写的不错,达到加精的标准了
2017-11-19 06:30
0
雪    币: 84
活跃值: (17)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
吾爱看到过,问下楼主为啥我密出来,修改了lua函数,显示奖励999999,但我实际签到得到的还是1金币,是不是需要去libmomo.so文件修改
2017-11-19 22:46
0
雪    币: 1631
活跃值: (1364)
能力值: ( LV7,RANK:117 )
在线值:
发帖
回帖
粉丝
4
linfengtai 吾爱看到过,问下楼主为啥我密出来,修改了lua函数,显示奖励999999,但我实际签到得到的还是1金币,是不是需要去libmomo.so文件修改
hh没错都是我写的。

你这情况就不一定了,万一奖励是在服务器上发放的呢?
2017-11-20 15:47
0
雪    币: 8
活跃值: (505)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5
写的很详细,学习了
2017-11-20 18:27
0
雪    币: 84
活跃值: (17)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
6
luajit-decomp-master  有这个工具的,2015年就有了
2017-11-29 11:53
0
雪    币: 1631
活跃值: (1364)
能力值: ( LV7,RANK:117 )
在线值:
发帖
回帖
粉丝
7
linfengtai luajit-decomp-master 有这个工具的,2015年就有了
是的,只做了一些微小的工作  ;-)
2017-12-24 20:50
1
雪    币: 1
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
9
Hi,楼主好,我最近也在研究LuaJIT的反编译,刚好看到你写的博文,很不错啊。
想问一下经过LuaJIT编译的Luac文件,楼主已经可以反编译出来了吗?
2018-4-23 21:55
0
雪    币: 13
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
10
楼主我试了不行,希望指导
2018-12-10 22:53
0
雪    币: 1541
活跃值: (519)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
11
写的很详细,学习了
2019-1-1 22:56
0
雪    币: 120
活跃值: (1597)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
12
写的很棒!!
2019-1-17 10:59
0
雪    币: 5
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
13
大佬 “折腾两天后决定利用NDK自己写一个wrapper,利用这个函数已导出的特性,从so里面获取这个函数并调用它来为我们解密” 这部分您是怎么做的呢 可以指导一下吗
2019-9-21 17:09
0
游客
登录 | 注册 方可回帖
返回
//