某房卡麻将游戏逆向浅析

房卡麻将游戏起初由闲徕互娱于 2016 年研发,其微商式的传播方式与本地麻将玩法吸引了大量玩家,一经上市便引爆了全国麻将游戏市场。随着第三方开发商的研发与源代码泄漏,房卡麻将游戏在全国遍地开花。
不过绝大多数运营方无相关许可牌照,其游戏属于非法出版物,面临较大的法律风险,同时游戏存在点数计算功能,线上棋牌与线下社交的结合容易变成在线赌博,即游戏结束后,根据点数在微信、支付宝等第三方支付平台结算。
房卡麻将游戏最大的成功在于其传播方式改变了玩家的游戏习惯,颠覆了传统棋牌的游戏方式。包房早已存在于传统棋牌游戏,但之前对于玩家来说包房都是免费,从来没有任何一家棋牌游戏厂商通过开包房收费盈利。

Android 应用安装包的本质是一个经过签名的 zip 压缩包,直接修改后缀进行解压即可。
在 lib 目录下发现了 libcocos2dlua.so 文件,由此可以确定这款游戏使用 Cocos2d-x 框架进行开发,并且使用的是 Lua 脚本语言。

Tips:当存在很多 so 库文件的时候,一般最大的文件就是目标。

既然有 Lua 引擎,那么肯定也有 Lua 脚本,Lua 脚本是明文源代码,稍有安全意识的开发厂商都不会直接将 Lua 脚本源代码打包进应用安装包中,通常会遇到以下三种源代码保护方式:

  • 对称加密
    打包在应用安装包中的 Lua 脚本经过加密,应用加载 Lua 脚本前先进行解密而后运行脚本,通过解密就可以获取 Lua 脚本源代码。
    解密后也可能获得的是 Luac 字节码,文件头为 0x1B 0x4C 0x75 0x61 0x51,这是将 Lua 脚本预编译成二进制文件,从而提升加载速度,需要再进行一次反编译才可以获得 Lua 脚本源代码。

  • 编译 LuaJIT 字节码并加密
    JIT 指的是 Just-In-Time(即时解析运行),文件头为 0x1B 0x4C 0x4A。LuaJIT 使用了一种全新的方式来编译和执行 Lua 脚本。经过处理后的 LuaJIT 程序,字节码的编码实现更加简单,执行效率相比 Lua 和 Luac 更加高效。
    开发者将 Lua 脚本编译成 LuaJIT 字节码,而后再加密 LuaJIT 字节码,应用先进行解密后加载 LuaJIT 字节码。这种方式能够较好的保护 Lua 脚本源代码,反编译存在一定门槛。

  • 打乱 Lua 虚拟机中 OpCode 顺序或修改引擎逻辑
    通过修改 Lua 源代码,将 Lua 脚本预编译成 Luac 字节码,可以理解为重新映射 Lua 虚拟机执行指令,实现门槛较高,无法通过通用工具进行反编译。
    需使用 IDA 定位到虚拟机的 OpMode 和 luaV_execute 函数,通过对比原先 Lua 虚拟机的执行过程,分析出修改后的 OpCode 顺序才可以编译生成反编译工具。

经过查找,Lua 脚本和相关资源文件都在 assets 目录下,开发者使用了 Cocos2d-x 框架自带的 XXTEA 加密算法。

可以通过以下几种方法来获得 Lua 脚本源代码:

  • 静态分析 so 库
    使用 IDA 定位到 luaL_loadbuffer 函数后向上回溯,分析脚本解密过程后制作解密工具。

  • 动态调试 so 库
    使用 IDA 动态调试,定位到 luaL_loadbuffer 地址并下断点,应用会在启动时调用 luaL_loadbuffer 函数加载必要的 Lua 脚本,断下后即可将 Lua 脚本源代码导出。

  • Hook so 库
    Hook 方式与动态调试原理类似,通过 Hook 函数 luaL_loadbuffer 地址实现 Lua 脚本源代码导出。
    动态调试每次只能获取单个 Lua 脚本源代码,如果使用 Hook 只需运行一次即可全部导出。

上文介绍的三种方法均可用于 Cocos2d-x 的 XXTEA 加密算法,XXTEA 加密算法需要 Key 和 Sign 才可以进行解密,但是由于 Cocos2d-x 框架的设计原因,导致这个加密形同虚设,所以有一个取巧的办法可以快速获取 Key 和 Sign。
获取 Sign 只需随意打开一个 .luac 后缀的加密文件,在文件头看到的一串字符串便是 Sign。

Key 也是个字符串,藏在 libcocos2dlua.so 文件中,打开 Terminal 输入 strings -a libcocos2dlua.so 解析出 so 库中所有字符串,搜索刚刚找到的 Sign 值,在 Sign 的上方就是 Key。

Q:为什么可以取巧?
A:通过 Cocos2d-x 的源代码可以了解到开发者需要调用 setXXTEAKeyAndSign 函数进行文件解密,Key 和 Sign 都是在同一函数进行调用,所以生成 so 库时,这两个字符串也在一起。
另外也可以使用 IDA 载入 libcocos2dlua.so 文件,在 String 中搜索 Sign 值,一般 Key 就在附近,如果附近字符串均无法解密的话,亦可尝试暴力枚举。

解密工具有很多,我使用的是 https://github.com/dengxiaochun/luac_decodeTool,填入对应信息后运行脚本即可。

游戏客户端使用 Protobuf 协议与服务端进行通信,Protobuf 全称为 Protocol Buffer,是 Google 出品的一款结构化数据交换协议,与 XML 和 JSON 数据格式类似,与前两者不同的是,Protobuf 采用的是二进制的数据格式,具有性能优异、跨语言、跨平台等特点。

经过分析,游戏过程中大部分操作均为服务端广播指令,没有太多可利用价值或者操作空间,故而中止。

总结一下,由于 Cocos2d-x 框架自身设计原因,自带的加密解决方案只防君子,不防小人,可轻易实现对游戏客户端进行调试、修改等操作,建议通过上文介绍的源代码保护方式自定义加密。

Some rights reserved
Except where otherwise noted, content on this page is licensed under a Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International license