房卡麻将游戏起初由闲徕互娱于 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 框架自身设计原因,自带的加密解决方案只防君子,不防小人,可轻易实现对游戏客户端进行调试、修改等操作,建议通过上文介绍的源代码保护方式自定义加密。