[python] ylgy攻略 用魔法打败魔法
发布时间:2026/9/14 11:14:36来源:尧图网络
文章目录1. 引言2. 源码分析MatchPlayInfo 的生成逻辑3. 序列化过程详解protobuf 与 JSON 序列化对比4. 关键问题数值大于 127 导致失败5. 实战Python 脚本一键通关6. 总结1. 引言通过逆向分析微信小程序「羊了个羊」的源码可以获取到MatchPlayInfo序列的生成逻辑。本文将逐步拆解其 protobuf 序列化过程并提供一个可直接运行的 Python 脚本用于生成合法的通关数据。下面是本文的整体流程逆向分析小程序源码获取 MatchPlayInfo 生成逻辑分析 protobuf 序列化过程发现数值大于 127 的坑点编写 Python 脚本生成合法数据调用接口完成通关2. 源码分析MatchPlayInfo 的生成逻辑通过源码可以得到以下 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 newt(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 newt(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));3. 序列化过程详解分析一下 MatchPlayInfo 的生成操作。首先可以得知 MatchPlayInfo 是由stepInfoList和gameType组成的stepInfoList里有两个参数分别是chessIndex和timeTag分别记录点击卡片 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里的chessIndex和timeTag循环增加到序列可以得知生成过程是MatchPlayInfo \x08\x03 (\x04\x08\x??\x10\x?? * 卡牌个数)下面是 MatchPlayInfo 的序列化结构图MatchPlayInfogameTypestepInfoListchessIndextimeTag\\x08\\x03\\x08\\x??\\x10\\x??\\x08\\x03 (\\x04\\x08\\x??\\x10\\x?? * 卡牌个数)protobuf 与 JSON 序列化对比维度protobufJSON数据大小二进制紧凑编码体积小如MatchPlayInfo每条操作仅 6 字节文本格式含字段名和引号体积大同样数据可能膨胀数倍编码速度二进制直接写入无需字符串解析速度快需要字符串拼接与解析速度较慢可读性二进制不可直接阅读需借助工具或解码纯文本人类可直接阅读和调试适用场景对性能、流量敏感的高频接口如游戏对战数据上报调试、配置、跨语言简单数据交换为什么小程序选择 protobuf 而非 JSON核心原因在于性能和流量。羊了个羊这类小游戏在每局对局中会频繁上报操作序列数据量大且实时性要求高。protobuf 采用二进制编码去掉了字段名等冗余信息能显著压缩数据体积、降低网络传输耗时同时二进制编码省去了 JSON 的字符串解析开销编解码更快。此外protobuf 通过.proto文件定义强类型结构前后端可自动生成代码保证字段一致减少联调成本。相比之下JSON 虽然可读性好但在高频、大数据量的场景下体积和性能都不占优势因此小程序选择了 protobuf。4. 关键问题数值大于 127 导致失败然而生成的序列无法增加通关次数研究了一下发现stepInfoList里的值大于 127 的数值是错误的不知道是什么原因于是过滤掉大于 127 的数可以成功增加通关次数5. 实战Python 脚本一键通关下面代码替换t值放 Python 直接跑就能增加通关次数importstructimportbase64importrequests# 安装依赖 pip install requests# 苹果手机抓包,商城下载Stream,证书配置参考(https://blog.csdn.net/weixin_44504146/article/details/121946958),Stream点击开始抓包打开微信小程序-羊了个羊开始游戏打开Stream停止抓包。# Stream查看抓包历史打开最新记录找到cat-match.easygame2021.com/sheep/v1/点击进入详情再点击请求选项。# t值就是t:后面的那一段。到conntent-type之前结束。headers{t:,User-Agent:Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 MicroMessenger/8.0.27(0x18001b37) NetType/4G Language/zh_CN,Referer:https://servicewechat.com/wx141bfb9b73c970a9/23/page-frame.html}urlhttps://cat-match.easygame2021.com/sheep/v1/game/personal_info?rrequests.get(url,headersheaders)print(r.json())urlhttps://cat-match.easygame2021.com/sheep/v1/game/map_info_ex?matchType3rrequests.get(url,headersheaders)map_md5r.json()[data][map_md5][1]urlfhttps://cat-match-static.easygame2021.com/maps/{map_md5}.txt# 由于每天获取的地图不一样需要计算地图大小rrequests.get(url)levelDatar.json()[levelData]p[]forhinrange(len(sum(levelData.values(),[]))):# 生成操作序列p.append({chessIndex:127ifh127elseh,timeTag:127ifh127elseh})GAME_DAILY3GAME_TOPIC4datastruct.pack(BB,8,GAME_DAILY)foriinp:c,ti.values()datastruct.pack(BBBBBB,34,4,8,c,16,t)MatchPlayInfobase64.b64encode(data).decode(utf-8)print(MatchPlayInfo)# 每日通关urlhttps://cat-match.easygame2021.com/sheep/v1/game/game_over_ex?rrequests.post(url,headersheaders,json{rank_score:1,rank_state:1,rank_time:1663723,rank_role:1,skin:7,MatchPlayInfo:MatchPlayInfo})print(r.json())urlhttps://cat-match.easygame2021.com/sheep/v1/game/personal_info?rrequests.get(url,headersheaders)print(r.json())下面是 Python 脚本的执行流程抓包获取 t 值请求 personal_info 获取用户信息请求 map_info_ex 获取地图信息获取地图文件并解析 levelData生成操作序列 p序列化生成 MatchPlayInfoPOST game_over_ex 提交通关数据验证通关结果6. 总结本文通过逆向分析「羊了个羊」小程序的 protobuf 序列化逻辑梳理了MatchPlayInfo的完整生成流程并给出了一个可直接运行的 Python 脚本。核心要点如下MatchPlayInfo由gameType和stepInfoList组成其中stepInfoList记录每一步的chessIndex卡片 id和timeTag操作间隔。序列化时每个操作会被编码为\x04\x08\x??\x10\x??的固定格式。关键坑点chessIndex和timeTag的值不能超过 127否则序列化结果无法通过服务端校验导致通关失败。
网站建设高端定制企业官网