合成大作战-刷榜
# 一、起因
相信很多小伙伴都玩过红极一时的"合成大西瓜"。一个有挑战又有趣的小游戏。
最近移动也出了一个类似的游戏:合成大作战。
自己历史达到的最高分为 1800+。排行榜里各个都是人才,最高能有 15000+分。
在"虚荣心"和"好奇心"的耸动下,遂想用其他方式冲冲榜。
# 二、观测
一个有序、有章程的观测方法能有效且迅速的找到问题并解决问题。
由于开始之初没有制定每一步需要达到的目的和结果。导致过程中做了较多无用功。
生活工作亦是如此。
# 初步观察
观察到游戏中的所有得分都是由前端发起请求。
那么只要掌握了请求发起的规律,以及提交分数和游戏回合数之间的规律性就能达到目的。
核心接口总共就两个,一个用于轮询提交分数,另一个是游戏结束时结算。
其中[轮询提交分数请求]接口调用时间需要大于 20s
,小于 20s
属于无效请求。
游戏结束结算时提交最高记录即可。
// 轮询提交分数请求
https://xxxxx.cn/hlwyxhdhub/api/watermelon/ckGame
// 游戏结束
https://xxxxx.cn/hlwyxhdhub/api/watermelon/endGame
# 记录正常数据
首先正常玩一局游戏,会得到一批正常的游戏数据记录
对这批正常数据进行处理和编码,以供刷榜时随机提交
// 正常游戏中 提交的数据
[
{
"ck": "XXXXXXXXXXXXXXXXXXXXXX",
"score": 0,
"num": 0
},
{
"ck": "XXXXXXXXXXXXXXXXXXXXXX",
"score": 0,
"num": 8
},
{
"ck": "XXXXXXXXXXXXXXXXXXXXXX",
"score": 40,
"num": 17
},
{
"ck": "XXXXXXXXXXXXXXXXXXXXXX",
"score": 140,
"num": 26
},
{
"ck": "XXXXXXXXXXXXXXXXXXXXXX",
"score": 172,
"num": 35
},
{
"ck": "XXXXXXXXXXXXXXXXXXXXXX",
"score": 240,
"num": 45
},
{
"ck": "XXXXXXXXXXXXXXXXXXXXXX",
"score": 396,
"num": 54
},
{
"ck": "XXXXXXXXXXXXXXXXXXXXXX",
"score": 454,
"num": 63
},
{
"ck": "XXXXXXXXXXXXXXXXXXXXXX",
"score": 558,
"num": 73
},
{
"ck": "XXXXXXXXXXXXXXXXXXXXXX",
"score": 594,
"num": 82
},
{
"ck": "XXXXXXXXXXXXXXXXXXXXXX",
"score": 640,
"num": 91
}
]
// 游戏结算
{"num":91,"score":640,"ck":"XXXXXX"}
由上方正常游戏数据得出 num 和 score 的差值。据观测版本一只能支持 10000 分以下的游戏结算。
为了突破 10000 分,衍生出了变异版
,也就是在原来基础上加了几个大值
// 版本一:正常游戏后的差值列表
const scoreList = [0, 40, 100, 32, 68, 156, 58, 104, 36, 46];
const numList = [8, 9, 9, 9, 10, 9, 9, 10, 9, 9];
//版本二:变异版本
const scoreList = [215, 40, 318, 32, 68, 156, 58, 104, 36, 46, 186, 68, 520];
const numList = [8, 9, 9, 9, 10, 9, 9, 10, 9, 9, 11, 12, 7];
# 接口 ck 字段
轮询接口中有一个 ck 字段,当前请求的 ck 字段 = 上一次响应结果中的 ck 字段。
// 上一次 响应结果
{"code":"SUCCESS","msg":"成功!","status":null,"data":{"ck":"XXXXXX"},"success":true}
// 下一次 请求参数
{"ck":"XXXXXX","score":594,"num":82}
我获取第一次 ck 的方式还是比较原始。
Chrome DevTools
-> Network
设置为 Offline
离线模式,这时所有的请求都会失败。
关闭离线,选中请求 Replay XHR
即可得到最新的 ck。
# 三、编写脚本
由于只是出于研究目的,所以编写的是比较原始的、简易的控制台版本。
将首次 ck 内容粘贴到变量 ck 中。然后回车运行脚本,就会慢慢的开刷了。
let ck = "首次ck";
let score = 0;
let num = 0;
let time = 0;
let endCount = 0;
// const scoreList = [0, 40, 100, 32, 68, 156, 58, 104, 36, 46];
const scoreList = [215, 40, 318, 32, 68, 156, 58, 104, 36, 46, 186, 68, 520];
const numList = [8, 9, 9, 9, 10, 9, 9, 10, 9, 9, 11, 12, 7];
function load() {
num += numList[Math.ceil(Math.random() * 12)] || 10;
score += scoreList[Math.ceil(Math.random() * 12)] || 100;
const obj = {
num: num,
score: score,
ck: ck,
};
fetch("https://xxxx.cn/hlwyxhdhub/api/watermelon/ckGame", {
headers: {
accept: "application/json, text/plain, */*",
"accept-language": "zh-CN,zh;q=0.9",
"content-type": "application/json;charset=UTF-8",
"login-check": "1",
"sec-ch-ua":
'"Not/A)Brand";v="99", "Google Chrome";v="115", "Chromium";v="115"',
"sec-ch-ua-mobile": "?0",
"sec-ch-ua-platform": '"Windows"',
"sec-fetch-dest": "empty",
"sec-fetch-mode": "cors",
"sec-fetch-site": "same-origin",
},
referrer:
"https://xxxx.cn/hlwyxhdhub/act-watermelon/1023081480?channelId=P00000009912&token=08251130161001248582hannelIdP00000009912",
referrerPolicy: "strict-origin-when-cross-origin",
body: JSON.stringify(obj),
method: "POST",
mode: "cors",
credentials: "include",
})
.then((response) => response.json())
.then((data) => {
console.group("刷分阶段");
console.log(data);
console.log(obj);
console.groupEnd("刷分阶段");
ck = data.data.ck;
if (score > 999) {
clearInterval(time);
time = setInterval(() => {
loadEnd();
}, 22000);
}
})
.catch((error) => console.log(error));
}
function loadEnd() {
const obj = {
num: num,
score: score,
ck: ck,
};
endCount++;
fetch("https://xxxx.cn/hlwyxhdhub/api/watermelon/endGame", {
headers: {
accept: "application/json, text/plain, */*",
"accept-language": "zh-CN,zh;q=0.9",
"content-type": "application/json;charset=UTF-8",
"login-check": "1",
"sec-ch-ua":
'"Chromium";v="116", "Not)A;Brand";v="24", "Google Chrome";v="116"',
"sec-ch-ua-mobile": "?0",
"sec-ch-ua-platform": '"Windows"',
"sec-fetch-dest": "empty",
"sec-fetch-mode": "cors",
"sec-fetch-site": "same-origin",
},
referrer:
"https://xxxx.cn/hlwyxhdhub/act-watermelon/1023081480?token=08261530531567781894watermelon1023081480",
referrerPolicy: "strict-origin-when-cross-origin",
body: JSON.stringify(obj),
method: "POST",
mode: "cors",
credentials: "include",
})
.then((response) => response.json())
.then((data) => {
console.group("结算阶段");
console.log(data);
console.log(obj);
console.groupEnd("结算阶段");
if (endCount > 5) {
clearInterval(time);
}
});
}
load();
time = setInterval(() => {
load();
}, 22000);
# 四、总结思考
- 为什么没有人刷榜?
- 我猜比较费精力,假设要刷 10000 分,最少也需要 30 分钟。(限制 20s 一次)
- 奖励太低,付出无法和收益成正比。
- 最难的部分
- 最难的部分其实是没有章法的在胡乱尝试。
- 最开始用的并不是随机值,导致刷分接口正常,但是游戏结算失败。
- 遇到的后台验证:数据的随机性检测、差值区间大小检测。
- 如何防刷
- 【易】给响应参数和请求参数加密。让破解难度增加。
- 【难】每次合成的值都是固定的一批值,能否根据这些值得到一个数学方程公式。结果对应的上就游戏结算正常。