论坛首页 移动安全专区 阅读主题

[原创]一款单机斗地主手机游戏的分析

144 浏览 16 回复
#1 楼主 2026-06-01 21:09:12
一款我很久之前玩过的单机斗地主游戏,但是玩的时间一久,系统发的牌就变得特别差,所以我就想分析分析一下这个App.
注意:这是一个32位的安装包,需要把IDA的32位调试服务器adb push到/data/local/tmp 中。把apk放到GDA4.11中分析,如下图所示。 虽然它有显示疑似有腾讯的什么bugly打包服务,但它Java层代码是可以正常阅读的。以及使用Android killer调用apktool给apk重新打包签名,也没有提示用户安装盗版应用的对话框。大致看了下,有关发牌、金币结算的逻辑并不在Java层里,因此我考虑到它应该是用了一些游戏引擎的,关键的逻辑应该在so层中。使用frida脚本来监视App加载了哪些文件,如下图所示。
frida的JS脚本如下:执行的命令行为:frida -U -f com.june.game.doudizhu.g.baidu -l file.js 可以看到有许许多多的luac文件,特别是在进入房间的时候有一些疑似关键的luac被加载起来了。比如,libsendCard2_2.lua 或者libsendcard2_2.luac ,猜想它是负责发牌的一段脚本。再看看apk中包含的文件,so文件又包含libcocos2dcpp.so 文件,assets 下也有许多luac文件。如下图所示。


那么根据网上的资料来看,这个游戏极可能是基于cocos2dx来开发的。
在使用frida hook open函数的时候,虽然它有感知到有lua文件的打开,但在目标目录下并没有找到解密的lua文件。所以此时的逻辑就是应当解密关键的luac 文件了,luac 的形式如下图所示。

除了最开始的bianfengqipai (边锋棋牌,和游戏的产商名是对的上的),剩余的内容都是加密的。一般来讲,程序是先检查头部的数据再去解密的。不过为了防止开发者做字符串加密,还是hook系统的open函数、过滤参数并跟踪它的调用堆栈就能找它解密的地方了。此时的hook脚本如下:输出如下图所示。都是一样的,以及大致看了一下这些调用堆栈,并没有发现特殊的,负责解密的代码段。于是修改脚本hooklua_pcall ,脚本如下。此时的打印如下,在IDA中回看这些堆栈,依旧没有看到有用的解密代码。 但是仍没有看出调用堆栈能解密的代码。这时我选择了直接hook fread函数,并对比头部的字节,如果是bianfengqipai 则打印出调用堆栈。但在实际操作过程中,它只打印了一行fread 没有打印出更上一级的调用堆栈了,所以我修改了一下frida的脚本,如下。(另外写的一个脚本)打印的调用堆栈如下所示:当然了,这里最好搜搜网上资料了,下图是cocos2dx_lua_loader 的伪代码。函数cocos2d::LuaStack::luaLoadBuffer 就包含了真正解密的函数,如下图所示。
其源码所在的链接为:650K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6U0L8$3y4G2M7K6u0V1i4K6u0r3j5$3!0U0L8%4x3J5k6q4)9J5k6s2S2Q4x3V1k6T1L8r3!0T1i4K6u0r3N6U0c8Q4x3V1k6U0L8$3y4G2M7#2)9J5c8Y4y4U0M7X3W2H3N6r3W2F1k6#2)9J5c8X3I4#2j5g2)9J5k6r3u0A6L8X3c8A6L8X3N6K6i4K6u0r3L8h3q4F1N6h3q4D9i4K6u0r3b7@1y4x3N6h3q4e0N6r3q4U0K9#2)9J5k6h3y4H3M7l9`.`.如下图所示。好在这个so没有去掉符号。
这里再给两个参考链接:https://bbs.kanxue.com/thread-268574-1.htm185K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6%4N6%4N6Q4x3X3f1#2x3Y4m8G2K9X3W2W2i4K6u0W2j5$3&6Q4x3V1k6@1K9s2u0W2j5h3c8Q4x3X3b7I4y4K6R3H3y4U0t1$3i4K6u0V1x3g2)9J5k6o6q4Q4x3X3g2Z5N6r3#2D9那么现在我们就搜索字符串bianfengqipai ,有且仅有一个结果。找到它的交叉引用,找到int __fastcall AppDelegate::applicationDidFinishLaunching(AppDelegate *this) 再看到伪码。
我们寻找这个函数,显然它是一个虚函数,间接调用的形式。所以要找this指针v34 . 以下就是v34+84h 偏移所包含的函数了。 再来看看LuaStack 的源码。是密钥在前,签名在后。 那么我们就能解密luac文件了,密钥是03f0fdcbf5215b45fc790aaf2b965237 ,签名是bianfengqipai 。算法是XXTEA ,我们找到apk解压路径\assets\src\game\libcard ,这个目录下足足有100多个luac文件,而且命名都是差不多的。如下图所示。 随便拿出一个用吾爱破解论坛上的逆向密码学工具来解密即可。一定要先去掉头部的签名,否则会解不出来。 为了不影响原有的文件,我特地将原文件备份了一份,去掉了头部字节。放入解密工具,参数设置如下图所示。
解出来发现是一个超大的数组,如下图所示。 最后返回 例如我拿出最后一个数组。想必应该能猜出来,这是斗地主三个玩家的牌,baseCards 变量应该指的就是底牌了。如此说来,牌型的组合是相对固定的,只是App随机给你分配一个了。我们已经知道\assets\src\game\libcard 中的文件都是定义牌型组合的脚本。那么它的数值是怎么和牌的点数对应上的呢?一副扑克牌有大王、小王,剩下的就是4种花色的A~K了,等于13 * 4 + 2 = 54张牌。游戏的逻辑很显然在libcocos2dcpp.so 里了,那么有很大概率lua会申请一段内存,存放每个玩家的牌,数值就像上文中提到的{53,41,28,15,2,42,29,16,48,47,46,45,31,43,37,9,7} 。由于malloc 的调用次数会非常多,所以建议在开始游戏之前附加frida脚本,点击之后再看输出。脚本如下。当参数等于17时打印调用堆栈,因为在叫地主之前,大家都只有17张牌。先执行命令行frida -U 单机斗地主 -l malloc.js 再开始游戏,如下图所示。 屏幕有一大堆输出,找到有用的调用堆栈如下。在IDA中跳转至lua_RunRule_RunRule_findCards ,这个函数能一下子看见许多疑似的牌点、数值互转的代码段。如下图所示。 我们再看到堆栈中的_ZN8bianfeng7RunRule15findCardsByNumsERKSt6vectorIhSaIhEES5_S5_RS3_ ,其实是函数名int __fastcall bianfeng::Run

...(已截断)

---
来源: 看雪论坛
原文链接: https://bbs.kanxue.com/thread-289099.htm
#17 2026-06-01 21:09:12
强啊,但是这样玩几局就感觉没有意思了。
‹ 上一页 1 2 下一页 ›

请登录后参与讨论

立即登录 注册账号