合成大作战-刷榜

8/31/2023

# 一、起因

相信很多小伙伴都玩过红极一时的"合成大西瓜"。一个有挑战又有趣的小游戏。
最近移动也出了一个类似的游戏:合成大作战。
自己历史达到的最高分为 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 一次)
    • 奖励太低,付出无法和收益成正比。
  • 最难的部分
    • 最难的部分其实是没有章法的在胡乱尝试。
    • 最开始用的并不是随机值,导致刷分接口正常,但是游戏结算失败。
    • 遇到的后台验证:数据的随机性检测、差值区间大小检测。
  • 如何防刷
    • 【易】给响应参数和请求参数加密。让破解难度增加。
    • 【难】每次合成的值都是固定的一批值,能否根据这些值得到一个数学方程公式。结果对应的上就游戏结算正常。