前言
仅供学习研究,不得用于非法行为。
准备工作
-
微信
-
微信开发者工具(可选)
-
WeChatOpenDevTools(可选,推荐采用)
关于微信小程序逆向常用的调试方式有两种,一种是对小程序进行解包后使用微信开发者工具进行补环境调试或者有耐心的话直接硬啃解包后的代码,另一种则是通过Hook技术来对微信打补丁解锁Devtools进行调试,后者的好处是不用解包以及修复环境错误(该方案最先由先志远(x0tools)大佬开源)。
关于微信小程序怎么解包补环境之类的就不细说了,该学习的已经学习完成了!
分析数据包以及解密
先来看一下该麻将小游戏界面
之后组一个好友局
接着看下控制台有没有输出信息、都输出了什么信息、这些信息对我们是否有帮助
可以看到游戏开发者输出了一些调试信息在 game.js 157 行的位置,跟进去
虽然进行混淆了,但不难看出这大抵是一个 console.log 函数 。
可以在此处下一个断点,接着重新进入小程序。
可以看到打印的 h 变量是处理函数的名称,sMt 变量则代表 handlers,this 则代表着当前对象。从这里得知程序内部有一个叫做handlers的属性用于存放一些操作对应的处理函数。
再来看一下网络请求部分,微信小程序本质上还是对chromium的一些封装,因此并不会给小程序提供直接建立tcp、udp链接的能力。
像小游戏这种对于实时性有要求的应用程序一般会采取websocket方式与服务器进行通信。因为只有短链接的http请求和长连接的websocket可选,如果选择采取http请求进行与服务器交互,那么在效率性能延迟上都会大打折扣。所以我选择直接看ws:
看到交互的数据包进行了加密编码处理,交互包第一个元素为 npt。
由于代码进行一定程度上的混淆,直接搜索前缀字符串 npt 大概率行不通。所以我们换种方式进行定位,那就是搜索websocket的onmessage钩子函数。onmessage是服务端来消息时候会触发的一个钩子回调函数,这部分可以自行去搜索Websocket相关的知识进行了解。
注意:进行代码混淆之后直接搜都可能存在搜索不到的情况,这里不是重点就不展开说了。
给这里下一个断点之后触发一下。等断住后单步跟随进入函数
这里是在判断准备处理的数据包类型了,继续跟下去
在这里有一个decodePacket 的函数,看名字是用于解包的。
跟到这里已经到消息解密函数的位置了,我们把一些变量的值打印出来
可以看到用于解密消息的函数为 msgDecode2,第一个参数为解密的消息类型,第二个为解密用的key,第三个参数为解密之后的回调函数。
这段的代码逻辑为拿到消息后,进行解密后根据对应的事件类型使用对应的事件处理函数处理数据。
可以说是数据包解密部分到这里就已经完成了。
判断服务器是否验证数据包
这之前我们需要对游戏设计有一个大概的认知,换一种说法是如果让你来设计一个类似的卡牌游戏,你会怎么去设计?
首先你要了解游戏规则,基本上卡牌游戏的规则都是禁止不同玩家能够直接看到对方的牌。
这一点很重要,也就是说游戏开发者在设计之初没理由需要在初始化游戏时候把对方的牌数据返回给你,不排除开发者留有服务器程序后门接口可以对此进行操作。
所以正常情况下从客户端逆向分析的角度是无法通过返回的数据包来获取到对方的牌(因为服务端压根没有返回对方牌的数据包,只返回属于玩家自己的牌)。
因此市面上绝大多数的棋牌游戏透视挂都是游戏开发商自己设计的后门程序,你可能会觉得这样做是在砸自己招牌的行为,但是只要开发商换个身份谁有知道是谁呢。且大多数的卡牌游戏没有卖挂来的挣钱。
那么真的只有开发者能够进行违规操作吗,答案是否定的。
你忽略了一个特殊的职业:渗透工程师。你可以简称他们为黑客也不为过。利用服务器的一些漏洞获取服务器操作权限,拿到服务端源代码后进行审计,如果游戏程序没有短时间内可以发现的漏洞或者游戏后门咋办?答案是都进入服务器了,当然是可以自己制造一个呀。。
说到这里,我们就可以回到主题了。
我们可以下一个日志断点,程序运行过程中把数据包信息打印出来。
对手出牌时,接收到的数据包:
玩家自己出牌时,发送的数据包:
打个 “八” 发出的牌对应的值 25
接着把数据进行 msgEncode2 函数加密编码一下,得到加密后的字符串 WzcNHz8jLlR6cy84IQZMKiZwQzcjDhNs
把包发送给服务器后,服务器会返回用于通知客户端出牌的数据包,客户端收到指令后就会解码消息并且根据消息类型进行对应操作,这里是玩家出的牌,所以会收到一条消息是玩家自己出的牌
由于他这个心跳包检测间隔有点短,调试长一点时间就会断开重新链接,所以我没法截图收到的包,你们自己调试时候可以验证这一点,这里我随便截一张来牌时候的数据包看看就行了。
上面是出牌时候发出的数据包,以及接收到对手出牌时候的数据包数据结构。
由于出牌时候会经过send函数向服务器发送出牌数据包,所以我们可以在send函数中修改发送的数据。例如:
这里我们出了一张 “束” 对应牌值是 27,可以尝试修改成其他牌。这里随便修改成 12
发送出去时候会发现我们的 “束” 并没有被打出去,取而代之的是 “四条”,这是因为我们手里包含牌值 12 的牌,而牌值12正好对应的是“四条”。如果手牌中没有 12 服务器就会拒绝掉我们的出牌请求。
那么再来看看摸牌时候的请求呢,摸牌摸到的牌应该是:
全部牌 - 打出去的牌 - 所有玩家手中的牌 = 剩余未分配的牌
在剩余未分配的牌中进行随机抽取一张并且返回给当前摸牌的玩家客户端。
这边摸到了一张“17”,我们试试更改为“12”
可以看到当我们尝试把摸来的17改成12,客户端虽然产生了对应的“四条”,但是我们打出“12”时候,服务端并没有给我们返回任意数据,也就是代表着服务端在摸牌时候会把随机产生的牌已经归属到我们牌数组中了。
所以再次把摸到的牌打出去时服务端进行了强校验。校验不通过所以啥也没给我们返回。
结论
在上述白盒测试中想要通过逆向客户端修改网络发包来影响对局数据的可能性几乎为0。
证实了游戏核心数据是存储在服务器内存中,而非在客户端中。
原文始发于微信公众号(随心记事):某麻将小程序逆向 - 透视换牌可行性分析
- 左青龙
- 微信扫一扫
- 右白虎
- 微信扫一扫
评论