吾爱汇编

 找回密码
 立即注册

QQ登录

绑定QQ避免忘记帐号

查看: 949|回复: 7

[其他图文] 羊了个羊MatchPlayInfo序列化分析

[复制链接]
459121520 发表于 2022-11-17 21:43 | 显示全部楼层 |阅读模式

通过分析源码可以得到以下MatchPlayInfo序列的生成代码const protobuf = require("protobufjs");
var i = protobuf.Reader, a = protobuf.Writer, r = protobuf.util;
MatchStepInfo = function () {
    function t(t) {
        if (t)
            for (var e = Object.keys(t), o = 0; o < e.length; ++o) null != t[e[o]] &&
                (this[e[o]] = t[e[o]])
    }
    return t.prototype.chessIndex = 0, t.prototype.timeTag = 0, t.create = function (
        e) {
        return new t(e)
    }, t.encode = function (t, e) {
        return e || (e = a.create()), null != t.chessIndex && Object.hasOwnProperty
            .call(t, "chessIndex") && e.uint32(8).int32(t.chessIndex), null !=
            t.timeTag && Object.hasOwnProperty.call(t, "timeTag") && e.uint32(
                16).int32(t.timeTag), e
    }, t.decode=1 = function (t, e) {
        t instanceof i || (t = i.create(t));
        for (var o = void 0 === e ? t.len : t.pos + e, n = new MatchStepInfo; t
            .pos < o;) {
            var a = t.uint32();
            switch (a >>> 3) {
                case 1:
                    n.chessIndex = t.int32();
                    break;
                case 2:
                    n.timeTag = t.int32();
                    break;
                default:
                    t.skipType(7 & a)
            }
        }
        return n
    }, t
}(), MatchPlayInfo = function () {
    function t(t) {
        if (this.stepInfoList = [], t)
            for (var e = Object.keys(t), o = 0; o < e.length; ++o) null != t[e[o]] &&
                (this[e[o]] = t[e[o]])
    }
    return t.prototype.gameType = 0, t.prototype.mapId = 0, t.prototype.mapSeed = 0,
        t.prototype.stepInfoList = r.emptyArray, t.create = function (e) {
            return new t(e)
        }, t.encode = function (t, e) {
            if (e || (e = a.create()), null != t.gameType && Object.hasOwnProperty.call(
                t, "gameType") && e.uint32(8).int32(t.gameType), null != t.mapId &&
                Object.hasOwnProperty.call(t, "mapId") && e.uint32(16).int32(t.mapId),
                null != t.mapSeed && Object.hasOwnProperty.call(t, "mapSeed") && e.uint32(
                    24).int32(t.mapSeed), null != t.stepInfoList && t.stepInfoList.length
            )
                for (var o = 0; o < t.stepInfoList.length; ++o) MatchStepInfo
                    .encode(t.stepInfoList[o], e.uint32(34).fork()).ldelim();
            return e
        }, t.decode=1 = function (t, e) {
            t instanceof i || (t = i.create(t));
            for (var o = void 0 === e ? t.len : t.pos + e, n = new MatchPlayInfo; t
                .pos < o;) {
                var a = t.uint32();
                switch (a >>> 3) {
                    case 1:
                        n.gameType = t.int32();
                        break;
                    case 2:
                        n.mapId = t.int32();
                        break;
                    case 3:
                        n.mapSeed = t.int32();
                        break;
                    case 4:
                        n.stepInfoList && n.stepInfoList.length || (n.stepInfoList = []),
                            n.stepInfoList.push(MatchStepInfo.decode=1(t,
                                t.uint32()));
                        break;
                    default:
                        t.skipType(7 & a)
                }
            }
            return n
        }, t
}()
var operationList = []
function addOp(t, e) { //增加操作
    void 0 === e && (e = -100);
    var o = {
        id: t, // 操作卡片的id,从levelData第一层开始按顺序编号
        time: Date.now() // 操作时间
    };
    operationList.push(o)
}
function sleep(delay) {
    for (var t = Date.now(); Date.now() - t <= delay;);
}
let range = n => [...Array(n).keys()]
for (let i of range(50)) { // 生成了50次操作
    addOp(i);
    sleep(Math.random() * 10); // 模拟操作过程中的等待
}
console.log(operationList)
for (var u = operationList, p = [], d = 0, h = 0; h < u.length; h++) // 把时间戳转换为两次操作的间隔
    p.push({ chessIndex: u[h].id, timeTag: 0 == d ? 0 : u[h].time - d }), d = u[h].time;
console.log(p)
GAMEDAILY = 3
GAMETOPIC = 4
for (var f = { gameType: GAMEDAILY, stepInfoList: p }, y = MatchPlayInfo.create(f), v = MatchPlayInfo.encode(y).finish(), b = "", _ = 0; _ < v.length; _++)
    b += String.fromCharCode(v[_]); // 序列化
var data = Buffer.from(b).toString('base64');
console.log(data);
data = Buffer.from(data, 'base64');
console.log(data);
console.log(MatchPlayInfo.decode=1(data));

分析一下MatchPlayInfo的生成操作
首先可以得知MatchPlayInfo是由stepInfoListgameType组成的,stepInfoList里有两个参数分别是chessIndextimeTag,分别记录点击卡片id两次操作间隔
观察MatchPlayInfo.encode可以看到



e = a.create()


会创建一个protobuf.Writer对象


e.uint32(8).int32(t.gameType)


会创建序列"\x08\x03",虽然写着是32但是经过调试发现是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“+("\x22\x04\x08\x??\x10\x??"*卡牌个数)

t{chessIndex: 120, timeTag: 120},
t{chessIndex: 121, timeTag: 121},
t{chessIndex: 122, timeTag: 122},
t{chessIndex: 123, timeTag: 123},
t{chessIndex: 124, timeTag: 124},
t{chessIndex: 125, timeTag: 125},
t{chessIndex: 126, timeTag: 126},
t{chessIndex: 127, timeTag: 127},
t{chessIndex: 16450, timeTag: 16450},t{chessIndex: 16578, timeTag: 16578},
t{chessIndex: 16706, timeTag: 16716},
t{chessIndex: 16834, timeTag: 16834},
t{chessIndex: 16962, timeTag: 16962},
t{chessIndex: 17090, timeTag: 17090},
t{chessIndex: 17218, timeTag: 17218},



然而生成的序列无法增加通关次数,研究了一下发现stepInfoList里的值大于127的数值是错误的,不知道是什么原因,于是过滤掉大于127的数可以成功增加通关次数
python代码如下


import struct
import base64
import requests

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

}
url = 'https://cat-match.easygame2021.com/sheep/v1/game/personal_info?'
r = requests.get(url, headers=headers)
print(r.json())
url = 'https://cat-match.easygame2021.com/sheep/v1/game/map_info_ex?matchType=3'
r = requests.get(url, headers=headers)
map_md5 = r.json()['data']['map_md5'][1]
url = f'https://cat-match-static.easygame2021.com/maps/{map_md5}.txt'  # 由于每天获取的地图不一样,需要计算地图大小
r = requests.get(url)
levelData = r.json()['levelData']
p = []
for h in range(len(sum(levelData.values(), []))):  # 生成操作序列
    p.append({'chessIndex': 127 if h > 127 else h, 'timeTag': 127 if h > 127 else h})
GAME_DAILY = 3
GAME_TOPIC = 4
data = struct.pack('BB', 8, GAME_DAILY)
for i in p:
    c, t = i.values()
    data += struct.pack('BBBBBB', 34, 4, 8, c, 16, t)
MatchPlayInfo = base64.b64encode(data)
print(MatchPlayInfo)
url = 'https://cat-match.easygame2021.com/sheep/v1/game/game_over_ex?'
r = requests.post(url, headers=headers,
                  json={'rank_score': 1, 'rank_state': 1, 'rank_time': 1, 'rank_role': 1, 'skin': 1,
                        'MatchPlayInfo': MatchPlayInfo})
print(r.json())
url = 'https://cat-match.easygame2021.com/sheep/v1/game/personal_info?'
r = requests.get(url, headers=headers)
print(r.json())



补充一下,int采用了多字节编码,所以大于127的数并不是错误的,而是需要进行编码,编码后需要重新计算长度

def multi_byte_int(number: int):
    # <128      : 1
    # <16384    : 2
    # <2097152  : 3
    # <268435456: 4
    # else      : 5
    if number < 128:
        return struct.pack('B', number)
    elif number < 16384:
        return struct.pack('BB', number & 127 | 128, number >> 7)
    elif number < 2097152:
        return struct.pack('BBB', number & 127 | 128, number >> 7 & 127 | 128, number >> 14)
    elif number < 268435456:
        return struct.pack('BBBB', number & 127 | 128, number >> 7 & 127 | 128, number >> 14 & 127 | 128, number >> 21)
    else:
        return struct.pack('BBBBB', number & 127 | 128, number >> 7 & 127 | 128, number >> 14 & 127 | 128, number >> 21 & 127 | 128, number >> 28)

关于游戏更新后通关次数无法增加的问题,原因是接口增加了判断,游戏时间小于1分钟的不算有效记录

评分

参与人数 13HB +24 THX +6 收起 理由
白丁老师 + 1 [吾爱汇编论坛52HB.COM]-学破解防破解,知进攻懂防守!
消逝的过去 + 2
花盗睡鼠 + 2 + 1 [吾爱汇编论坛52HB.COM]-学破解防破解,知进攻懂防守!
Jawon + 2
一路走来不容易 + 1
创客者V2.0 + 1
WolfKing + 2 [吾爱汇编论坛 52HB.COM]-软件反汇编逆向分析,软件安全必不可少!
行行行行行行 + 1
zxjzzh + 1 [吾爱汇编论坛52HB.COM]-学破解防破解,知进攻懂防守!
Cerolluo + 1 [吾爱汇编论坛52HB.COM]-学破解防破解,知进攻懂防守!
三斤回锅肉 + 2 + 1 [吾爱汇编论坛52HB.COM]-吃水不忘打井人,给个评分懂感恩!
VipDongle + 1
Shark恒 + 10 + 1 [吾爱汇编论坛52HB.COM]-感谢楼主热心分享,小小评分不成敬意!

查看全部评分

吾爱汇编论坛-学破解,防破解!知进攻,懂防守!逆向分析,软件安全!52HB.COM
三斤回锅肉 发表于 2022-11-17 23:55 | 显示全部楼层
吾爱汇编论坛-学破解,防破解!知进攻,懂防守!逆向分析,软件安全!52HB.COM
 楼主| 459121520 发表于 2022-11-18 07:37 | 显示全部楼层

三斤回锅肉 发表于 2022-11-17 23:55
支持一下啊 免得冷场了

多谢支持,我总感觉都是要冷场的了
吾爱汇编论坛-学破解,防破解!知进攻,懂防守!逆向分析,软件安全!52HB.COM
Cerolluo 发表于 2022-11-18 08:26 | 显示全部楼层
吾爱汇编论坛-学破解,防破解!知进攻,懂防守!逆向分析,软件安全!52HB.COM
男孩子 发表于 2022-11-18 09:58 | 显示全部楼层

没玩过这游戏听说很弱智,支持一下
吾爱汇编论坛-学破解,防破解!知进攻,懂防守!逆向分析,软件安全!52HB.COM
头像被屏蔽
别管我了行 发表于 2022-11-23 17:33 | 显示全部楼层
吾爱汇编论坛-学破解,防破解!知进攻,懂防守!逆向分析,软件安全!52HB.COM
曾经沧海 发表于 2023-2-23 21:39 | 显示全部楼层

跟着大佬少走弯路
吾爱汇编论坛-学破解,防破解!知进攻,懂防守!逆向分析,软件安全!52HB.COM
一生逍遥 发表于 2023-3-11 08:10 | 显示全部楼层

不错,感谢楼主分享!
吾爱汇编论坛-学破解,防破解!知进攻,懂防守!逆向分析,软件安全!52HB.COM
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

警告:本站严惩灌水回复,尊重自己从尊重他人开始!

1层
2层
3层
4层
5层
6层
7层
8层

免责声明

吾爱汇编(www.52hb.com)所讨论的技术及相关工具仅限用于研究学习,皆在提高软件产品的安全性,严禁用于不良动机。任何个人、团体、组织不得将其用于非法目的,否则,一切后果自行承担。吾爱汇编不承担任何因为技术滥用所产生的连带责任。吾爱汇编内容源于网络,版权争议与本站无关。您必须在下载后的24个小时之内,从您的电脑中彻底删除。如有侵权请邮件或微信与我们联系处理。

站长邮箱:SharkHeng@sina.com
站长QQ:1140549900


QQ|RSS|手机版|小黑屋|帮助|吾爱汇编 ( 京公网安备11011502005403号 , 京ICP备20003498号-6 )|网站地图

Powered by Discuz!

吾爱汇编 www.52hb.com

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