吾爱破解 - LCG - LSG |安卓破解|病毒分析|www.52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 17473|回复: 211
上一主题 下一主题
收起左侧

[Web逆向] 羊了个羊MatchPlayInfo序列化分析

    [复制链接]
跳转到指定楼层
楼主
wxy1343 发表于 2022-9-21 05:50 回帖奖励
本帖最后由 wxy1343 于 2022-9-23 23:27 编辑

通过分析源码可以得到以下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 = 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 = 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(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(data));


分析一下MatchPlayInfo的生成操作
首先可以得知MatchPlayInfo是由stepInfoListgameType组成的,stepInfoList里有两个参数分别是chessIndextimeTag,分别记录点击卡片id两次操作间隔
观察MatchPlayInfo.encode可以看到
[JavaScript] 纯文本查看 复制代码
e = a.create()

会创建一个protobuf.Writer对象

[JavaScript] 纯文本查看 复制代码
e.uint32(8).int32(t.gameType)
会创建序列"\x08\x03",虽然写着是32但是经过调试发现是1字节的,不知道是为什么
[JavaScript] 纯文本查看 复制代码
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??"*卡牌个数)

然而生成的序列无法增加通关次数,研究了一下发现stepInfoList里的值大于127的数值是错误的,不知道是什么原因,于是过滤掉大于127的数可以成功增加通关次数
python代码如下
[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分钟的不算有效记录

免费评分

参与人数 60威望 +1 吾爱币 +77 热心值 +49 收起 理由
lingweiqiu + 1 我很赞同!
weizimi + 1 + 1 我很赞同!
挚爱红蓝 + 1 我很赞同!
Simonic + 1 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
Reskiiy + 1 + 1 用心讨论,共获提升!
a22488 + 1 + 1 我很赞同!
qiuqiu8083 + 1 + 1 谢谢@Thanks!
cyc1307 + 1 + 1 热心回复!
chen05101 + 1 + 1 用心讨论,共获提升!
天天开心1 + 1 写的挺棒
SSBB007 + 1 我很赞同!
RickSanchez + 1 我很赞同!
root233 + 1 + 1 我很赞同!
dadao815 + 1 + 1 用心讨论,共获提升!
ag129 + 1 谢谢@Thanks!
Arcticlyc + 1 + 1 我很赞同!
chenshu + 1 + 1 我很赞同!
null123 + 1 整复杂了,这其实就是个protobuf
sunlei658 + 1 + 1 谢谢@Thanks!
wwwhhhlll + 1 + 1 我很赞同!
dincia + 1 谢谢@Thanks!
杨辣子 + 1 + 1 感谢,借助楼主的代码,已经开始可以刷次数了
soyiC + 1 + 1 用心讨论,共获提升!
唐小样儿 + 1 + 1 我很赞同!
wanfon + 1 + 1 热心回复!
JanusGreenB + 1 + 1 热心回复!
Dazzleン紫 + 1 22号好像不行了
mini123 + 1 + 1 我玩了几局,最后几个都不一样,根本没有能够合成,,,,0点刚结束,排名.
axktc199 + 1 + 1 谢谢@Thanks!
GGabc + 1 + 1 热心回复!
maicorgj + 1 用心讨论,共获提升!
cjsgtya + 1 + 1 谢谢@Thanks!
woodenwang + 1 + 1 202209211910加循环测试可用!
哇卡s + 1 + 1 我很赞同!
u009009 + 1 我很赞同!
weiguoxin + 1 + 1 21号16点24分测试可用,已经开刷
alderaan + 1 + 1 热心回复!
玄觞 + 1 + 1 鼓励转贴优秀软件安全工具和文档!
病人吗233 + 1 我在80楼参考楼主的写法做了个java版 大家可以看看
linguo2625469 + 2 + 1 感谢,使用js成果还原了,已上线我网站
同舟 + 1 我很赞同!
neeeeeeeeeu + 1 + 1 谢谢@Thanks!
熊猫拍板砖 + 1 + 1 热心回复!
Y.S. + 1 + 1 用心讨论,共获提升!
SouthJoe + 1 谢谢@Thanks!
odmin + 1 这个分析的确实不错
wj55768 + 1 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
arrymarry + 1 + 1 大哥,你这长篇大论,我等小白望而却步。!
loo1221ool + 1 + 1 用心讨论,共获提升!
那我是落叶吧 + 1 + 1 牛逼了操作序列,又可以刷次数了
wangqiang1995 + 1 + 1 能转成e代码吗
ofo + 1 + 1 批量刷人家一检测timeTag累计间隔不就发现了
dutyzqly + 2 + 1 谢谢@Thanks!
lpawuu + 1 + 1 我很赞同!
Old01d + 1 + 1 用心讨论,共获提升!
早七七 + 1 + 1 我很赞同!
涛之雨 + 1 + 20 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
shuaiqi + 1 + 1 谢谢@Thanks!
mossfly + 1 + 1 鼓励转贴优秀软件安全工具和文档!
x2140898408 + 1 + 1 我很赞同!

查看全部评分

本帖被以下淘专辑推荐:

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

推荐
病人吗233 发表于 2022-9-21 14:34
参考楼主的代码写了个java版 供参考
[Java] 纯文本查看 复制代码
String url = "https://cat-match-static.easygame2021.com/maps/fc7c34c18e09f4b29ed75ccb52ad0225.txt";
        HttpRequest httpRequest = HttpRequest.get(url);
        HttpResponse execute = httpRequest.execute();
        String body = execute.body();
        JSONObject jsonObject = JSONUtil.parseObj(body);
        JSONObject levelData = jsonObject.getJSONObject("levelData");
        Iterator<String> iterator = levelData.keySet().iterator();
        Set<String> strings = levelData.keySet();
        int totalSize = 0;
        while(iterator.hasNext()){
            String next = iterator.next();
            JSONArray jsonArray = levelData.getJSONArray(next);
            totalSize += jsonArray.size();
        }
        JSONArray jsonArray = new JSONArray();
        for (int i = 0; i < totalSize; i++) {
            JSONObject data = new JSONObject();
            data.set("chessIndex", Math.min(i, 127));
            data.set("timeTag", Math.min(i, 127));
            jsonArray.add(data);
        }
        List<Byte> bytes = Lists.newArrayList();
        bytes.add((byte) 8);
        bytes.add((byte) 3);
        for (int i = 0; i < jsonArray.size(); i++) {
            JSONObject jsonObject1 = jsonArray.getJSONObject(i);
            bytes.add((byte) 34);
            bytes.add((byte) 4);
            bytes.add((byte) 8);
            bytes.add(jsonObject1.getByte("chessIndex"));
            bytes.add((byte) 16);
            bytes.add(jsonObject1.getByte("chessIndex"));
        }
        byte[] dataByte = new byte[bytes.size()];
        for (int i = 0; i < bytes.size(); i++) {
            dataByte[i] = bytes.get(i);
        }
        System.err.println(Arrays.toString(dataByte));
        // MatchPlayInfo
        System.err.println(Base64.encode(dataByte));
推荐
qitl 发表于 2022-9-21 06:24
推荐
owo163 发表于 2022-9-21 22:38

[Java] 纯文本查看 复制代码
import cn.hutool.core.codec.Base64;
import cn.hutool.http.HttpUtil;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.google.common.collect.Lists;
 
import java.io.*;
import java.net.URL;
import java.net.URLConnection;
import java.util.*;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
 
public class YLYUHttp {
 
    static ExecutorService threadPool= Executors.newFixedThreadPool(10);
 
    static String loginOppoURL = "https://cat-match.easygame2021.com/sheep/v1/user/login_oppo" ;
    static String matchTypeURL = "https://cat-match.easygame2021.com/sheep/v1/game/map_info_ex?matchType=3" ;
 
    static String t = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2OTQ3ODI5NjIsIm5iZiI6MTY2MzY4MDc2MiwiaWF0IjoxNjYzNjc4OTYyLCJqdGkiOiJDTTpjYXRfbWF0Y2g6bHQxMjM0NTYiLCJvcGVuX2lkIjoiIiwidWlkIjo4MTMxOTUyOSwiZGVidWciOiIiLCJsYW5nIjoiIn0.GHT4Oi_3lbLYrsnaXyFSL6Xhv1azQVre6N82_z56-lU";
    public static void main(String[] args) {
 
        //String uid = "230058077";
         String uid = "81315929";
        getToken(t);
 
    }
 
    public static String getToken(String t ) {
        try {
 
        } catch (Exception e) {
            e.printStackTrace();
 
        }
        return null;
    }
 
 
 
 
 
//    public static void  sucees(String uid){
//        if(uid.length() < 10 && uid.matches("[0-9]+")){
//            String tokne = getToken(uid);
//            System.out.println(tokne);
//            int num = 0;
//            for(int i=0 ; i< 10 ; i++){
//                num = send( tokne) + num;
//                try {
//                    Thread.sleep(500);
//                    System.out.print(num);
//                } catch (InterruptedException e) {
//                    e.printStackTrace();
//                }
//            }
//            System.out.println( " [通关 "+num+" 次 ,成功] ");
//        }else {
//            System.out.println("不是数字");
//        }
//    }
 
 
    public static String autoGame(String t) {
        try {
            HashMap<String, Object> paramMap = new HashMap<>();
            paramMap.put("t", t);
            paramMap.put("User-Agent", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/81.0.4044.138 Safari/537.36 MicroMessenger/6.5.2.501 NetType/WIFI MiniGame WindowsWechat");
            paramMap.put("Referer", "https://servicewechat.com/wx141bfb9b73c970a9/23/page-frame.html");
 
            String matchType = HttpUtil.get(matchTypeURL+ "&t=" +t);
 
            String mad5 = JSONObject.parseObject(matchType).getJSONObject("data").getJSONArray("map_md5").getString(1);
            String url = "https://cat-match-static.easygame2021.com/maps/" + mad5 + ".txt";
 
            String s = HttpUtil.get(url);
 
            JSONObject jsonObject = JSONObject.parseObject(s);
            JSONObject levelData = jsonObject.getJSONObject("levelData");
            Iterator<String> iterator = levelData.keySet().iterator();
 
            int totalSize = 0;
            while (iterator.hasNext()) {
                String next = iterator.next();
                JSONArray jsonArray = levelData.getJSONArray(next);
                totalSize += jsonArray.size();
            }
            JSONArray jsonArray = new JSONArray();
            for (int i = 0; i < totalSize; i++) {
                JSONObject data = new JSONObject();
                data.put("chessIndex", Math.min(i, 127));
                data.put("timeTag", Math.min(i, 127));
                jsonArray.add(data);
            }
            List<Byte> bytes = Lists.newArrayList();
            bytes.add((byte) 8);
            bytes.add((byte) 3);
            for (int i = 0; i < jsonArray.size(); i++) {
                JSONObject jsonObject1 = jsonArray.getJSONObject(i);
                bytes.add((byte) 34);
                bytes.add((byte) 4);
                bytes.add((byte) 8);
                bytes.add(jsonObject1.getByte("chessIndex"));
                bytes.add((byte) 16);
                bytes.add(jsonObject1.getByte("chessIndex"));
            }
            byte[] dataByte = new byte[bytes.size()];
            for (int i = 0; i < bytes.size(); i++) {
                dataByte[i] = bytes.get(i);
            }
 
            String gameOverExUrl = "https://cat-match.easygame2021.com/sheep/v1/game/game_over_ex" ;
            HashMap<String, Object> paramMap1 = new HashMap<>();
            paramMap1.put("rank_score",1);
            paramMap1.put("rank_state",1);
            paramMap1.put("rank_time",1);
            paramMap1.put("rank_role",1);
            paramMap1.put("skin",1);
            paramMap1.put("t",t);
            paramMap1.put("MatchPlayInfo", Base64.encode(dataByte));
 
 
            String json = JSONObject.toJSONString(paramMap1);
            System.out.println(json);
            String s1  =   sendPost(gameOverExUrl,json,paramMap);;
 
            System.out.println(s1);
 
            return  "";
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("获取token失败");
            return "01";
        }
    }
 
 
 
 
 
 
 
//    public static void  sucees(String uid){
//        if(uid.length() < 10 && uid.matches("[0-9]+")){
//            String tokne = getToken(uid);
//            System.out.println(tokne);
//            int num = 0;
//            for(int i=0 ; i< 10 ; i++){
//                num = send( tokne) + num;
//                try {
//                    Thread.sleep(500);
//                    System.out.print(num);
//                } catch (InterruptedException e) {
//                    e.printStackTrace();
//                }
//            }
//            System.out.println( " [通关 "+num+" 次 ,成功] ");
//        }else {
//            System.out.println("不是数字");
//        }
//    }
 
 
    public static String sendPost(String url, String param, Map<String, Object> header) throws UnsupportedEncodingException, IOException {
        PrintWriter out = null;
        BufferedReader in = null;
        String result = "";
        URL realUrl = new URL(url);
        // 打开和URL之间的连接
        URLConnection conn = realUrl.openConnection();
        //设置超时时间
        conn.setConnectTimeout(5000);
        conn.setReadTimeout(15000);
        // 设置通用的请求属性
        if (header!=null) {
            for (Map.Entry<String, Object> entry : header.entrySet()) {
                conn.setRequestProperty(entry.getKey(), entry.getValue().toString());
            }
        }
 
        conn.setRequestProperty("accept", "*/*");
        conn.setRequestProperty("connection", "Keep-Alive");
        conn.setRequestProperty("Content-Type", "application/json; charset=utf-8");
        // 发送POST请求必须设置如下两行
        conn.setDoOutput(true);
        conn.setDoInput(true);
        // 获取URLConnection对象对应的输出流
        out = new PrintWriter(conn.getOutputStream());
        // 发送请求参数
        out.print(param);
        // flush输出流的缓冲
        out.flush();
        // 定义BufferedReader输入流来读取URL的响应
        in = new BufferedReader(
                new InputStreamReader(conn.getInputStream(), "utf8"));
        String line;
        while ((line = in.readLine()) != null) {
            result += line;
        }
        if(out!=null){
            out.close();
        }
        if(in!=null){
            in.close();
        }
        return result;
    }
}
推荐
18473487112 发表于 2022-9-21 08:43
MatchPlayInfo = base64.b64encode(data).decode('utf-8')
加了一个.decode('utf-8'),防止读取到bytes类型报错
推荐
jiangxt 发表于 2022-9-21 20:57
代码要怎么使用啊?
3#
fxk2009 发表于 2022-9-21 06:35
大哥,你这长篇大论,我等小白望而却步。!
4#
tl;dr 发表于 2022-9-21 07:11
不要当韭菜
5#
zaq168107 发表于 2022-9-21 07:26
哎呀。小白表示看不懂呢亲亲
6#
新手小白学编程 发表于 2022-9-21 07:42
本帖最后由 新手小白学编程 于 2022-9-21 07:45 编辑

测试完成正常使用大佬牛逼
7#
新手小白学编程 发表于 2022-9-21 07:48
尝试使用这个通关了
8#
aspllh 发表于 2022-9-21 07:58
不明觉厉,赞一个!
9#
she383536296 发表于 2022-9-21 08:03
哎呀。小白表示看不懂呢亲亲
10#
tiange 发表于 2022-9-21 08:11
大佬牛啊
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则 警告:本版块禁止灌水或回复与主题无关内容,违者重罚!

快速回复 收藏帖子 返回列表 搜索

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2022-9-26 06:56

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

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