美文网首页电竞·游戏
使用Python获取王者头脑2的问题与答案

使用Python获取王者头脑2的问题与答案

作者: 木兮家先生 | 来源:发表于2019-01-20 00:47 被阅读42次

最近和老婆迷上了头脑王者2。赢一局25金币,输一局掉100金币,这设置妥妥的亏本啊,玩下来才发现运营者的考虑是想让你多多分享,病毒营销啊。
天天金币不够用,又不想分享怎么办,技术手段看看有没有思路,开干!

0x00.获取网络数据流

小程序的所有通信都是必须要求https的。所以要想获取数据必须采用中间人攻击获取明文数据流

1.Charles分析

先用了Charles。发现这个小游戏大部分的数据交互都用的是websocket。在下面的地址
https://tnwz2-server.hortorgames.com

2.更强工具mitmproxy安装启动

对付websocket Charles局限了。我找到了这个工具 :mitmproxy
mitmproxy 如官方描述:

mitmproxy is a free and open source interactive HTTPS proxy.

看了一下果真强大,还有Python接口
嗯,先安装(MacOS):

brew install mitmproxy 

再安装python支持包:

pip3 install mitmproxy --user

启动:

mitmproxy -m socks5

启动后会在8080端口监听。指定socks5代理方式,http代理也可以使用

3.mitmproxy配置

手机WIFI设置中配置好代理端口和电脑ip
用手机浏览器访问http://mitm.it,看到下面的界面说明代理设置没问题了

image.png
点击对应的平台下载证书安装。然后在系统中设置信任,我就不多说怎么设置了
试一下访问https://www.baidu.com没问题。证书生效了

4.获取websocket数据流

如下代码(file:websocket.py):

mport mitmproxy.log
import mitmproxy.websocket
import mitmproxy.proxy.protocol
from mitmproxy import ctx



class InfoPrint:
    # HTTP lifecycle
    # 去除用不到的生命周期方法
   def websocket_message(self, flow: mitmproxy.websocket.WebSocketFlow):
        """
            Called when a WebSocket message is received from the client or
            server. The most recent message will be flow.messages[-1]. The
            message is user-modifiable. Currently there are two types of
            messages, corresponding to the BINARY and TEXT frame types.
        """
        content = flow.messages[-1].content # 获取二进制数据
        ctx.log.info("## msg:%s" % (str(content)))
    

addons = [
    InfoPrint()
]

启动:mitmdump -s websocket.py -m socks5 |grep '##'

0x01.数据解包

1.找规律

数据流是2进制。但看起来没有加密。拿了一条数据分析:
1f8b08000000000002ff6a5e9c9c9bb2d62fbf2433ad32de39233fbf387571716aa1f292a4fc94ca232e6dab8b32d3334a1cf38acb538b2632ae484a2c29c949f57439c77420517e716966ca39867791624b8b93f38b52ddd66416bb651615970481b41c5a9608d504080000ffff4852639f63000000
所有数据都是1f8b08开头。。。似乎是一种格式,google一下,简单了,gzip!

In [155]: import gzip

In [156]: gzip.decompress(b)
Out[156]: b'\x83\xa3cmd\xadNotify_Choose\xa3seq#\xa4body\xc4D\x86\xabrightAnswer\x91\x01\xa8battleID\xce\x02\xc0a\x1f\xa3uid\xce\x00\xeeY\x16\xa5scoreF\xacisFirstRight\xc2\xa6answer\x91\x01'

多处理几条找规律:

b'\x83\xa3cmd\xa9Resp_Sync\xa3seq\x08\xa4body\xc41\x81\xaamatchTeams\x81\xa5teams\x81\xce\x00\xd2\xd4\xd0\x81\xa7members\x81\xce\x00\xeeY\x16\x81\xa6online\xc2'
b'\x83\xa3cmd\xa9Resp_Sync\xa3seq\t\xa4body\xc41\x81\xaamatchTeams\x81\xa5teams\x81\xce\x00\xd2\xd4\xd0\x81\xa7members\x81\xce\x00\xeeY\x16\x81\xa6online\xc3'
b'\x83\xa3cmd\xafTeam_MatchStart\xa3seq\x04\xa4body\xc4\x01\x80'
b'\x84\xa3cmd\xb0Push_MatchStared\xa3seq\n\xa4rSeq\x04\xa4body\xc4;\x82\xa6player\x80\xaamatchTeams\x81\xa5teams\x81\xce\x00\xd2\xd4\xd0\x82\xa5state\x02\xacstateTimeout\xce\\C\x13\xca'
b'\x83\xa3cmd\xa9Resp_Sync\xa3seq\x0b\xa4body\xc43\x81\xaamatchTeams\x81\xa5teams\x81\xce\x00\xd2\xd4\xd0\x82\xa5state\x03\xacstateTimeout\xce\\C\x145'

看样子像是某种协议,或者某种序列化方式

2.确定反序列化方法

试了bson、pickle都不对
Google一下\0x83\0xa3cmd 找到一个线索有下面一段代码:

序列化前的包:
message = {'load': {'cmd': '_auth', 'id': 'zhu1', 'pub': '-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqicJ8L2vmjeEpSfi1TEE\nZzdPo5Ibgo5a+EvQtMqzm/elKhWjNSp82PE9Fl5BuGgexk9P0+kwpAEws6vWNgyG\nJuTow+PYhVMWU6B0P+pMcdm7YxcqKczvHXmH6ugzLt1uQmwcW0RjL2POGxgLyprL\nM1isdX/bSLnMabtAfsORv7q7BmsJaXIxtdFwH8yDuJRvluia448OjHU4ugQr+yWj\nfRuqOzR5UFk0K4CivPt6E/E7DdxJV4j1OSsnGXi5XPLoJ7dACQne6/2xOlSkb6tZ\neQBC/HdfWTflD4LYSlrsVlQTl1+DDmYHQL7eCYg0zC34xi+DC+Qd0MJ1DOPiRwWq\nswIDAQAB\n-----END PUBLIC KEY-----'}, 'enc': 'clear'}

序列化后:
'\x82\xa4load\x83\xa3cmd\xa5_auth\xa2id\xa4zhu1\xa3pub\xda\x01\xc2-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqicJ8L2vmjeEpSfi1TEE\nZzdPo5Ibgo5a+EvQtMqzm/elKhWjNSp82PE9Fl5BuGgexk9P0+kwpAEws6vWNgyG\nJuTow+PYhVMWU6B0P+pMcdm7YxcqKczvHXmH6ugzLt1uQmwcW0RjL2POGxgLyprL\nM1isdX/bSLnMabtAfsORv7q7BmsJaXIxtdFwH8yDuJRvluia448OjHU4ugQr+yWj\nfRuqOzR5UFk0K4CivPt6E/E7DdxJV4j1OSsnGXi5XPLoJ7dACQne6/2xOlSkb6tZ\neQBC/HdfWTflD4LYSlrsVlQTl1+DDmYHQL7eCYg0zC34xi+DC+Qd0MJ1DOPiRwWq\nswIDAQAB\n-----END PUBLIC KEY-----\xa3enc\xa5clear'

#master收到该包后
在zeromq.py文件ZeroMQReqServerChannel类的handle_message方法
if payload['enc'] == 'clear' and payload.get('load', {}).get('cmd') == '_auth':
    stream.send(self.serial.dumps(self._auth(payload['load'])))

和saltstack有关,我没接触过这个,看来这个数据流应该是一种salt支持的序列化方式
那就google搜源码看看:
找到这个 https://github.com/MadeiraCloud/salt/blob/master/sources/salt/payload.py
发现用的是msgpack这个包

In [158]: import msgpack

In [159]: msgpack.loads(b'\x83\xa3cmd\xadNotify_Choose\xa3seq#\xa4body\xc4D\x86\xabrightAnswer\x91\x01\xa8battleID\xce\x02\xc0a\x1f\xa3uid\xce\x00\xeeY\x16\xa5scoreF\xacisFirstRight\xc2\xa6answer\x91\x01')
Out[159]:
{b'cmd': b'Notify_Choose',
 b'seq': 35,
 b'body': b'\x86\xabrightAnswer\x91\x01\xa8battleID\xce\x02\xc0a\x1f\xa3uid\xce\x00\xeeY\x16\xa5scoreF\xacisFirstRight\xc2\xa6answer\x91\x01'}

找到路子了,那就看看这里面都有什么数据

0x02.分析协议

def websocket_message(self, flow: mitmproxy.websocket.WebSocketFlow):
       content = flow.messages[-1].content
        content = gzip.decompress(content)
        if len(content) == 0:
            return
        try:
            msg = msgpack.loads(content)
            cmd = msg[b'cmd']
            seq = msg[b'seq']
            body = msgpack.loads(msg[b'body'])
            # 打印出来分析,消息前加两个#方便过滤
            ctx.log.info("## msg:%s  seq:%d" % (cmd, seq))
            ctx.log.info("## body:%s " % str(body))
        except Exception as e:
            pass

打印数据来分析
启动:mitmdump -s websocket.py -m socks5 |grep '##'

部分数据:

## cmd:b'Resp_Sync' seq:8
## body:{b'battleInfo': {b'quizMap': {1: {b'className': b'\xe5\x8e\x86\xe5\x8f\xb2', b'category': 1, b'categoryName': b'\xe6\x96\x87\xe5\x8c\x96', b'answers': [2], b'answerNum': 1, b'id': 26408, b'quizType': 2, b'class': 3, b'quiz': b'\xe4\xb8\x89\xe5\x9b\xbd\xe6\x97\xb6\xe6\x9c\x9f\xef\xbc\x8c\xe6\x9d\x83\xe8\x87\xa3\xe8\x91\xa3\xe5\x8d\x93\xe7\x9a\x84\xe4\xb9\x89\xe5\xad\x90\xe6\x98\xaf\xef\xbc\x9f', b'options': [b'\xe5\x85\xb3\xe7\xbe\xbd', b'\xe5\xbc\xa0\xe9\xa3\x9e', b'\xe5\x90\x95\xe5\xb8\x83', b'\xe5\xad\x99\xe6\x9d\x83']}}, b'quizNum': 1, b'nextStateBeginTime': 1547907141, b'battleState': 3}}
## cmd:b'Resp_Sync' seq:9
## body:{b'battleInfo': {b'nextStateBeginTime': 1547907152, b'battleState': 4}}
## cmd:b'Resp_Sync' seq:10
## body:{b'battleInfo': {b'teams': {3: {b'score': 155}}, b'playerBattleInfo': {14423928: {b'rightQuizIDs': [1], b'answers': {1: {b'chooseData': [2], b'costTime': 1}}, b'scores': {1: 155}}}}}
## cmd:b'Notify_Choose' seq:11
## body:{b'answer': [2], b'rightAnswer': [2], b'battleID': 54182041, b'uid': 14423928, b'score': 155, b'isFirstRight': True}
## cmd:b'Resp_Sync' seq:12
## body:{b'battleInfo': {b'teams': {2: {b'score': 155}}, b'playerBattleInfo': {16296535: {b'scores': {1: 155}, b'rightQuizIDs': [1], b'answers': {1: {b'chooseData': [2], b'costTime': 1}}}}}}
## cmd:b'Notify_Choose' seq:13
## body:{b'battleID': 54182041, b'uid': 16296535, b'score': 155, b'isFirstRight': True, b'answer': [2], b'rightAnswer': [2]}
## cmd:b'Resp_Sync' seq:14
## body:{b'battleInfo': {b'teams': {2: {b'score': 239}}, b'playerBattleInfo': {14317409: {b'rightQuizIDs': [1], b'answers': {1: {b'chooseData': [2], b'costTime': 3}}, b'scores': {1: 84}}}}}
## cmd:b'Notify_Choose' seq:15
## body:{b'answer': [2], b'rightAnswer': [2], b'battleID': 54182041, b'uid': 14317409, b'score': 84, b'isFirstRight': False}
## cmd:b'Resp_Sync' seq:16
## body:{b'battleInfo': {b'playerBattleInfo': {16276981: {b'rightQuizIDs': [1], b'answers': {1: {b'chooseData': [2], b'costTime': 4}}, b'scores': {1: 74}}}, b'teams': {2: {b'score': 313}}}}
## cmd:b'Notify_Choose' seq:17
## body:{b'score': 74, b'isFirstRight': False, b'answer': [2], b'rightAnswer': [2], b'battleID': 54182041, b'uid': 16276981}
## cmd:b'Resp_Sync' seq:18
## body:{b'battleInfo': {b'playerBattleInfo': {16126335: {b'wrongQuizIDs': [1], b'answers': {1: {b'chooseData': [3], b'costTime': 4}}, b'scores': {1: 0}}}}}
## cmd:b'Notify_Choose' seq:19
## body:{b'uid': 16126335, b'score': 0, b'isFirstRight': False, b'answer': [3], b'rightAnswer': [2], b'battleID': 54182041}
## cmd:b'Fight_BattleChoose' seq:4
## body:{b'answer': [2], b'battleID': 54182041}
## cmd:b'Resp_Sync' seq:20
## body:{b'battleInfo': {b'teams': {3: {b'score': 229}}, b'playerBattleInfo': {15620374: {b'rightQuizIDs': [1], b'answers': {1: {b'chooseData': [2], b'costTime': 4}}, b'scores': {1: 74}}}}}
## cmd:b'Notify_Choose' seq:21
## body:{b'battleID': 54182041, b'uid': 15620374, b'score': 74, b'isFirstRight': False, b'answer': [2], b'rightAnswer': [2]}
## cmd:b'Resp_Sync' seq:22
## body:{b'baseSync': {b'sysTime': 1547907145}}

1.分析msg

数据包太多,专看看包含answer的。发现个有意思的,看样子像是找到目标了

cmd:b'Resp_Sync'
seq:8
body:{
b'battleInfo':
  {b'quizMap': {
    1: {
      b'className': b'\xe5\x8e\x86\xe5\x8f\xb2', b'category': 1, 
      b'categoryName': b'\xe6\x96\x87\xe5\x8c\x96', 
      b'answers': [2], b'answerNum': 1, b'id': 26408, b'quizType': 2, b'class': 3, 
      b'quiz': b'\xe4\xb8\x89\xe5\x9b\xbd\xe6\x97\xb6\xe6\x9c\x9f\xef\xbc\x8c\xe6\x9d\x83\xe8\x87\xa3\xe8\x91\xa3\xe5\x8d\x93\xe7\x9a\x84\xe4\xb9\x89\xe5\xad\x90\xe6\x98\xaf\xef\xbc\x9f', 
      b'options': [
          b'\xe5\x85\xb3\xe7\xbe\xbd', 
          b'\xe5\xbc\xa0\xe9\xa3\x9e', 
          b'\xe5\x90\x95\xe5\xb8\x83', 
          b'\xe5\xad\x99\xe6\x9d\x83'
      ]}},
       b'quizNum': 1, 
       b'nextStateBeginTime': 1547907141, b'battleState': 3
}}

似乎答案和问题都在这条数据里面,并且还告诉了客户端下个问题开始的时间。
修改代码解析看看

2.解析数据

    def websocket_message(self, flow: mitmproxy.websocket.WebSocketFlow):
        """
            Called when a WebSocket message is received from the client or
            server. The most recent message will be flow.messages[-1]. The
            message is user-modifiable. Currently there are two types of
            messages, corresponding to the BINARY and TEXT frame types.
        """
        content = flow.messages[-1].content
        content = gzip.decompress(content)
        if len(content) == 0:
            return
        try:
            msg = msgpack.loads(content)
            cmd = msg[b'cmd']
            if cmd != b'Resp_Sync':
                ctx.log.info("## cmd:%s" % (cmd))
                return
            seq = msg[b'seq']
            body = msgpack.loads(msg[b'body'])
            if b'battleInfo' not in body:
                ctx.log.info("battleInfo is None")
                return
            quizMap = body.get(b'battleInfo', {}).get(b'quizMap')
            if not quizMap:
                return
            for k in quizMap:
                num = k
                quiz = quizMap[k]
                if b'className' not in quiz:
                    continue
                className = quiz[b'className'].decode()
                categoryName = quiz[b'categoryName'].decode()
                question = quiz[b'quiz'].decode()
                answers = {}
                for index in quiz[b'answers']:
                    answers[index] = quiz[b'options'][index].decode()
            ctx.log.info("## class:%s  category:%s" % (className, categoryName))
            ctx.log.info("## question:%s " % question)
            for index, a in answers.items():
                ctx.log.info("## %d:%s" % (index+1, a))
            # ctx.log.info("## bdoy:%s" % (str(body)))
        except Exception as e:
            pass
            # ctx.log.info("## msg:%s" % (str(msg)))
    

不出所料:

## class:世界  category:生活
## question:《汉谟拉比法典》是谁颁布的?
## 2:古巴比伦国王
## cmd:b'Fight_BattleChoose'
## cmd:b'Notify_Choose'
## cmd:b'Notify_Choose'
## cmd:b'Notify_Choose'
## cmd:b'Notify_Choose'
## cmd:b'Notify_Choose'
## class:文学  category:文化
## question:「东风不与周郎便」的下一句是?
## 0:铜雀春深锁二乔
## cmd:b'Fight_BattleChoose'
## cmd:b'Notify_Choose'
## cmd:b'Notify_Choose'
## cmd:b'Notify_Choose'
## cmd:b'Notify_Choose'
## cmd:b'Notify_Choose'
## cmd:b'Notify_Choose'
## class:人物  category:流行
## question:马云在创业之前从事过什么正式工作?
## 1:教师
## cmd:b'Fight_BattleChoose'
## cmd:b'Notify_Choose'
## cmd:b'Notify_Choose'
## cmd:b'Notify_Choose'
## cmd:b'Notify_Choose'
## cmd:b'Notify_Choose'
## cmd:b'Notify_Choose'

哈哈,实测,在上道题还没有结束的时候就已经把下道题的问题和答案都拿到了。

0x03.结束

试了几局,妥妥的MVP啊,对手一定是以为遇到了机器人。不过这样玩意义就不大了。
把过程分享出来大家一起讨论学习一下。

好了,不能玩游戏了,看代码学习去。。。。

相关文章

网友评论

    本文标题:使用Python获取王者头脑2的问题与答案

    本文链接:https://www.haomeiwen.com/subject/ucetjqtx.html