最近开始报 WCQ 了,名额也是死难抢,来研究一下怎么开高达。
之前写过一个传动态头像的工具,也顺便总结一下吧。
虽然一开始用的是 Fiddler,不过现在感觉好像被 mitmproxy 完爆了,就不讲 Fiddler 的部分了。
免责声明
本文仅作为个人技术学习与安全研究记录,内容基于本人账号、本人设备及本地测试环境中的调试过程整理,不代表鼓励、支持或指导任何人对第三方平台、服务或系统进行未授权访问、绕过限制、篡改数据、自动化抢占资源、干扰正常运营或破坏比赛公平性的行为。
文中涉及的抓包、代理、请求分析等内容,仅用于理解客户端与服务端交互机制、排查个人使用场景中的技术问题,以及提升安全意识。任何读者在学习相关技术时,均应遵守所在地法律法规、平台服务条款、赛事规则及基本的网络安全伦理。
本人不会提供用于绕过平台规则、批量操作、代抢名额、规避身份校验、影响他人权益或破坏系统正常运行的工具、脚本、接口参数、完整复现步骤或技术支持。若相关平台或权利方认为本文部分内容存在不当之处,可通过站内联系方式联系本人,我将及时核实并进行调整或删除。
本文不构成任何形式的攻击教程、作弊指南或规避平台规则的操作说明。请勿将本文内容用于任何未经授权或违反规则的用途,由此产生的一切法律责任、账号风险及其他后果均由行为人自行承担。
动态头像
抓取通过 mitmproxy 抓取上传头像的请求:

首先这个小程序的大部分非查询类请求都有一个 signature 参数,这个是以秒为单位更新的签名,无法手动计算,所以我们只能通过截取并篡改已经生成的请求实现我们的需求。
观察请求头,请求体格式为 multipart/form-data,----WebKitFormBoundary 是分隔符,用来分隔数据,后面一串随机字符串是为了防止分隔符出现在文件内容里。
观察请求体:
1 | Content-Disposition: form-data; name="path" |
这部分表示文件上传到 /game/ 这个目录下;
1 | Content-Disposition: form-data; name="file"; filename="232132815.png" |
这部分记录了文件名和文件格式。
后面部分为图片数据。
则只需要将图片数据的部分替换为我们准备的其他图片数据,就可以实现替换上传的图片。
注意要同时修改请求头中的 content-length。
尽管上传后文件名仍然是原来的 .png 或者 .jpg,但图片解码器一般还是会按照图片的文件头进行解析,依然能正确识别为 GIF 并播放,这样一来就成功了。
GUI 工具化
用 Pyinstaller 做了一个 GUI 工具方便分发。
当然代码是 AI 写的,不过效果确实不错。

我自己的 mitm 证书也打包进去了,不然用户还得装个 mitm 生成证书。
用户首次启动需要管理员权限,证书会自动安装到本地。
绕过“请使用手机登录端登录”
某一天开始,可能是为了制裁高达代抢巡回赛,小程序不让用电脑登录了。
具体来说,进小程序的时候会弹一个关不掉的弹窗,点确定就会退出小程序。

之前就研究过,手机篡改流量也太麻烦了,着实让我头疼了很久。
不过巧合之中我还是发现了破解的方法:
如果之前在电脑上登录过,再在别的设备上顶号,那么会先弹出“请使用手机登录端登录”,过一会弹出“登录已过期,请重新登录!”。

此时我们点“确认”然后登录,会发现居然能绕过第一个弹窗……
算了,他们程序员大概也就这水平。
那么为了能在任意情况下都能绕过这个弹窗,我们只需要想办法让他以为我被顶号了就行。
我抓住机会分析了一下被顶号的时候的流量,发现被顶号时 response 的内容特征为:
1 | { |
谁懂还有错别字😂
总之我们只需要随便找个包把 response 改成这个格式,就可以让他认为我被顶号了。
而进入首页固定会请求一次 https://yugiohmatchapi.***.com/v1/news 获取公告,于是成功破解。
感觉做代抢的人技术应该比我强,不过不好说能不能发现这个?
巡回赛报名
说实话写到这的时候还没有一套好的方案,让我们一起拭目以待!
之前也不是没有尝试过,不过最终的结果是账号疑似被 ban 了。。
可能是测试的时候做出太多不合法请求了,这次要多加小心!
总之我们来一步一步破解。
我要报名
首先是这个比赛信息页面:

在非报名时间,下方按钮为“待开放”。我们希望能在任何时间都报名,则需要让它以为是可报名时间。
通过抓包可以发现,GET https://yugiohmatchapi.***.com/v1/match/info/87794 的 response 中 bottom 字段表示下方按钮的状态(也太直接了吧)。
对于“开放中”的比赛,内容为:
1 | "bottom": { |
对于可以报名的比赛,则为:
1 | "bottom": { |
通过修改,我们即可在任意时间点击报名按钮。

验证码与赛事协议

验证码其实没什么好说的,显然是调用的外部接口。
完成验证后,出现下一个窗口:

同时产生请求 GET https://yugiohmatchapi.***.com/v1/match/player/check/87916,response:
1 | { |
不好说这一步是否有风险。理论上这一步必须是在开放报名的时间,如果不是,可能会触发风控?
以及浏览器标识最好也要换成手机版,应该。
点击“确认”后进入“赛事协议”页面:

这一步不会产生任何请求。在这里点击“确认”的话,就是真正发送报名请求了。
请求的格式形如 GET https://yugiohmatchapi.***.com/v1/match/signup/87916?code=,后面跟一大长串不知道干啥的 code。
众所周知现在报名之后会弹出来一个排队的界面,不知道到底是在干啥,难道还有后台人工审核?(就积分赛的审核效率,我是不太信)
今天中午其实抓了一次,但我不小心把 mitm 关了,现在没法确认这一步的请求体和返回内容了。但能确定排队过程中没有任何还未返回的请求,过一定时间这个窗口会自动消失。(所以到底在排什么东西)
行动计划
事前准备
脚本启动后,可绕过电脑端检查,可提前点击报名按钮,User-Agent 伪装成手机版微信。
Plan A
提前到达验证码界面,到时间立即完成接下来的步骤。
理论上能比所有手动档玩家快 2 秒,缺点是打不过更凶的高达(如果存在)。
Plan B
直接快进到最后一步,到时间(或过几秒)点击最后的“确认”。
一定比所有人快,也可以刻意晚几秒被识别,但无法避免前一步 check 时间不对。
如果他不检测 check 的话,这个是最好的方案。
5.10 结果
同时执行了两个计划,其中 Plan B 的请求时间为 13:30:04,返回的 code 是 40029。
两边都是排队转圈然后没报上。
而整个排队过程中没有新的请求产生,也没有未返回的请求,所以还是那个问题,到底在排什么。。。
感觉有两种可能:
报名成功和失败都会显示排队,但是实际上对应的
code或data是不一样的。
报名成功会在一定时间后发送新的请求,或是根据返回结果直接进入付款页面,而失败就会一直原地转圈,只是故意拖时间这个防止玩家反复尝试报名增加服务器负载。
如果是这样,就得需要抓一个报名成功的数据。。有其他未被捕获的流量,且报名成功与否不完全依赖于报名时间。但我觉得不太可能。
感觉第一种的可能性更大。
我尝试了一下报名积分赛,报名成功返回的内容为:
1 | { |
我试了一下把 code 和 msg 进行篡改,我草,还真显示正在排队!
并且在过了一小会之后弹出:

并且也确实有一条新的请求:
1 | GET https://yugiohmatchapi.***.com/v1/match/monitor/signup/87916 |
结论
其实中间又试了好几天,过程就不写了,直接写结论吧。
确实是小程序账号被 ban 了。始终返回 40029。
但是 Plan A 对于未被 ban 的账号是可行的。
对我而言,可能可以通过护照小号报名来实现高达补录,如果又被 ban 了就注销重新注册一个号(其实已经发生了,还未验证重新注册的号能不能报名)。
将在上海站进行尝试。