查看: 716|回复: 1

羊了个羊MatchPlayInfo序列化分析

[复制链接]
发表于 2022-9-21 10:34 | 显示全部楼层 |阅读模式
非法程序、 2022-9-21 10:34 716 1 显示全部楼层
通过分析源码可以得到以下MatchPlayInfo序列的生成代码
  1. const protobuf = require("protobufjs");
  2. var i = protobuf.Reader, a = protobuf.Writer, r = protobuf.util;
  3. MatchStepInfo = function () {
  4.     function t(t) {
  5.         if (t)
  6.             for (var e = Object.keys(t), o = 0; o < e.length; ++o) null != t[e[o]] &&
  7.                 (this[e[o]] = t[e[o]])
  8.     }
  9.     return t.prototype.chessIndex = 0, t.prototype.timeTag = 0, t.create = function (
  10.         e) {
  11.         return new t(e)
  12.     }, t.encode = function (t, e) {
  13.         return e || (e = a.create()), null != t.chessIndex && Object.hasOwnProperty
  14.             .call(t, "chessIndex") && e.uint32(8).int32(t.chessIndex), null !=
  15.             t.timeTag && Object.hasOwnProperty.call(t, "timeTag") && e.uint32(
  16.                 16).int32(t.timeTag), e
  17.     }, t.decode = function (t, e) {
  18.         t instanceof i || (t = i.create(t));
  19.         for (var o = void 0 === e ? t.len : t.pos + e, n = new MatchStepInfo; t
  20.             .pos < o;) {
  21.             var a = t.uint32();
  22.             switch (a >>> 3) {
  23.                 case 1:
  24.                     n.chessIndex = t.int32();
  25.                     break;
  26.                 case 2:
  27.                     n.timeTag = t.int32();
  28.                     break;
  29.                 default:
  30.                     t.skipType(7 & a)
  31.             }
  32.         }
  33.         return n
  34.     }, t
  35. }(), MatchPlayInfo = function () {
  36.     function t(t) {
  37.         if (this.stepInfoList = [], t)
  38.             for (var e = Object.keys(t), o = 0; o < e.length; ++o) null != t[e[o]] &&
  39.                 (this[e[o]] = t[e[o]])
  40.     }
  41.     return t.prototype.gameType = 0, t.prototype.mapId = 0, t.prototype.mapSeed = 0,
  42.         t.prototype.stepInfoList = r.emptyArray, t.create = function (e) {
  43.             return new t(e)
  44.         }, t.encode = function (t, e) {
  45.             if (e || (e = a.create()), null != t.gameType && Object.hasOwnProperty.call(
  46.                 t, "gameType") && e.uint32(8).int32(t.gameType), null != t.mapId &&
  47.                 Object.hasOwnProperty.call(t, "mapId") && e.uint32(16).int32(t.mapId),
  48.                 null != t.mapSeed && Object.hasOwnProperty.call(t, "mapSeed") && e.uint32(
  49.                     24).int32(t.mapSeed), null != t.stepInfoList && t.stepInfoList.length
  50.             )
  51.                 for (var o = 0; o < t.stepInfoList.length; ++o) MatchStepInfo
  52.                     .encode(t.stepInfoList[o], e.uint32(34).fork()).ldelim();
  53.             return e
  54.         }, t.decode = function (t, e) {
  55.             t instanceof i || (t = i.create(t));
  56.             for (var o = void 0 === e ? t.len : t.pos + e, n = new MatchPlayInfo; t
  57.                 .pos < o;) {
  58.                 var a = t.uint32();
  59.                 switch (a >>> 3) {
  60.                     case 1:
  61.                         n.gameType = t.int32();
  62.                         break;
  63.                     case 2:
  64.                         n.mapId = t.int32();
  65.                         break;
  66.                     case 3:
  67.                         n.mapSeed = t.int32();
  68.                         break;
  69.                     case 4:
  70.                         n.stepInfoList && n.stepInfoList.length || (n.stepInfoList = []),
  71.                             n.stepInfoList.push(MatchStepInfo.decode(t,
  72.                                 t.uint32()));
  73.                         break;
  74.                     default:
  75.                         t.skipType(7 & a)
  76.                 }
  77.             }
  78.             return n
  79.         }, t
  80. }()
  81. var operationList = []
  82. function addOp(t, e) { //增加操作
  83.     void 0 === e && (e = -100);
  84.     var o = {
  85.         id: t, // 操作卡片的id,从levelData第一层开始按顺序编号
  86.         time: Date.now() // 操作时间
  87.     };
  88.     operationList.push(o)
  89. }
  90. function sleep(delay) {
  91.     for (var t = Date.now(); Date.now() - t <= delay;);
  92. }
  93. let range = n => [...Array(n).keys()]
  94. for (let i of range(50)) { // 生成了50次操作
  95.     addOp(i);
  96.     sleep(Math.random() * 10); // 模拟操作过程中的等待
  97. }
  98. console.log(operationList)
  99. for (var u = operationList, p = [], d = 0, h = 0; h < u.length; h++) // 把时间戳转换为两次操作的间隔
  100.     p.push({ chessIndex: u[h].id, timeTag: 0 == d ? 0 : u[h].time - d }), d = u[h].time;
  101. console.log(p)
  102. GAMEDAILY = 3
  103. GAMETOPIC = 4
  104. for (var f = { gameType: GAMEDAILY, stepInfoList: p }, y = MatchPlayInfo.create(f), v = MatchPlayInfo.encode(y).finish(), b = "", _ = 0; _ < v.length; _++)
  105.     b += String.fromCharCode(v[_]); // 序列化
  106. var data = Buffer.from(b).toString('base64');
  107. console.log(data);
  108. data = Buffer.from(data, 'base64');
  109. console.log(data);
  110. console.log(MatchPlayInfo.decode(data));
复制代码
分析一下MatchPlayInfo的生成操作
首先可以得知MatchPlayInfo是由stepInfoListgameType组成的,stepInfoList里有两个参数分别是chessIndextimeTag,分别记录点击卡片id两次操作间隔
观察MatchPlayInfo.encode可以看到
  1. e = a.create()
复制代码
会创建一个protobuf.Writer对象
  1. e.uint32(8).int32(t.gameType)
复制代码
会创建序列"\x08\x03",虽然写着是32但是经过调试发现是1字节的,不知道是为什么
  1. MatchStepInfo.encode(t.stepInfoList[o], e.uint32(34).fork()).ldelim()
复制代码
这里首先插入1字节34变成"\x08\x03\x22",接着fork函数会生成一个分叉,类似于git等待处理完这个分支后调用ldelim就会把当前分支上内容的长度和内容合并回主分支,分析MatchStepInfo.encode后可以得知会插入4个字节,分别是[8,chessIndex,16,timeTag],此时序列就是"\x08\x03\x22"+"\x04"+"\x08\x00\x10\x00"="\x08\x03\x22\x04\x08\x00\x10\x00",重复这个过程把stepInfoList里的chessIndextimeTag循环增加到序列,可以得知生成过程是MatchPlayInfo="\x08\x03“+("\x04\x08\x??\x10\x??"*卡牌个数)
QQ截图20220921103336.jpg
然而生成的序列无法增加通关次数,研究了一下发现stepInfoList里的值大于127的数值是错误的,不知道是什么原因,于是过滤掉大于127的数可以成功增加通关次数
python代码如下
  1. import struct
  2. import base64
  3. import requests

  4. headers = {
  5.     't': '',
  6.     'User-Agent': '',
  7.     'Referer': 'https://servicewechat.com/wx141bfb9b73c970a9/23/page-frame.html'

  8. }
  9. url = 'https://cat-match.easygame2021.com/sheep/v1/game/personal_info?'
  10. r = requests.get(url, headers=headers)
  11. print(r.json())
  12. url = 'https://cat-match.easygame2021.com/sheep/v1/game/map_info_ex?matchType=3'
  13. r = requests.get(url, headers=headers)
  14. map_md5 = r.json()['data']['map_md5'][1]
  15. url = f'https://cat-match-static.easygame2021.com/maps/{map_md5}.txt'  # 由于每天获取的地图不一样,需要计算地图大小
  16. r = requests.get(url)
  17. levelData = r.json()['levelData']
  18. p = []
  19. for h in range(len(sum(levelData.values(), []))):  # 生成操作序列
  20.     p.append({'chessIndex': 127 if h > 127 else h, 'timeTag': 127 if h > 127 else h})
  21. GAME_DAILY = 3
  22. GAME_TOPIC = 4
  23. data = struct.pack('BB', 8, GAME_DAILY)
  24. for i in p:
  25.     c, t = i.values()
  26.     data += struct.pack('BBBBBB', 34, 4, 8, c, 16, t)
  27. MatchPlayInfo = base64.b64encode(data)
  28. print(MatchPlayInfo)
  29. url = 'https://cat-match.easygame2021.com/sheep/v1/game/game_over_ex?'
  30. r = requests.post(url, headers=headers,
  31.                   json={'rank_score': 1, 'rank_state': 1, 'rank_time': 1, 'rank_role': 1, 'skin': 1,
  32.                         'MatchPlayInfo': MatchPlayInfo})
  33. print(r.json())
  34. url = 'https://cat-match.easygame2021.com/sheep/v1/game/personal_info?'
  35. r = requests.get(url, headers=headers)
  36. print(r.json())
复制代码


发表于 2022-9-21 11:59 | 显示全部楼层
qiye0602 2022-9-21 11:59 显示全部楼层
虽然不懂  还是顶一下
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则 返回列表 发新帖

快速回复 返回顶部 返回列表