ハットダッシュホワイトハットの殿堂とリーダーボードの更新

Dec 22 2020

数日前、私は人々がHat Dashで不正行為をするのを防ぐ(または少なくとも難しくする)方法を見つけるための助けを求めました。これまでに支援してくれたすべてのユーザーに一言申し上げます。HatDashリーダーボードの下部にある新しいWhiteHat Hall ofFameでそれらを見ることができます。これらの各ユーザーは、(クライアント、サーバー、またはその両方で)後で対処されるゲーム保護の弱点を明らかにしました。これらのすべてのユーザーは、賞金の報酬と、特別な新しい帽子(Defender of the Unicorn)も受け取ります。

投稿が行われたので、クライアントとサーバーの両方での変更を特徴とする、HatDashの不正防止コントロールとヒューリスティックの新しいバージョンをリリースしました。物事は間違いなく100%安全ではありません。それがどのように構築されているかを知っているので、私は間違いなく偽のスコアを取得する方法を考案することができます(そしてあなたの何人かはそうしようとすると確信しています)。ただし、現在はかなり積極的なユーザーの自動禁止メカニズムが導入されているため、物事をいじり始めた場合、禁止されるのは非常に簡単です。したがって、emptorに注意してください。禁止ステータスを確認することもできるようになりました。

とはいえ、新しいシステムでチートできる場合(チート=リーダーボードに不正なスコアを取得し、これをどのように行ったかを報告できる場合;不正=ゲームをプレイするか、その他の方法でAPIにアクセスするスペース/上矢印/タップを使用してゲームをプレイするよりも)、ハット/バウンティ/ホフを獲得したい場合は、下または元の投稿に自由に投稿してください(12月30日までに投稿された回答のみが考慮されます) 、賞は私の裁量です)。

また、すでに禁止されていて、システムを打ち負かすことができるかどうかを確認したい場合は、各ゲームの後にHat Dashがコンソールに印刷さ[Date] | Is Game Suspect | (true/false)れ、ゲームに何かが巻き込まれたかどうかが通知されます(注:すべての疑わしいゲームは自動禁止につながります)。

アンチチートシステムのアップグレードに伴い、全体的な統計リストは非推奨になりました。現在、リーダーボードの下部にあります。リーダーボードの上部にある新しい全体的な統計(2020-12-22から)セクション(より良い名前を思いつくことができたら、私に知らせてください)は、今日以降のスコアのみを含みます。

オッズがあなたに有利になり、幸せなジャンプができるように。

(ああ、そしてあなたが新しい秘密の帽子を手に入れたいが、白い帽子の詐欺師になりたくないのなら、お楽しみに…)

更新:ホワイトハットの殿堂の最終ラインナップが設定されました。ご参加いただきありがとうございます。

回答

9 Tuqay Dec 22 2020 at 23:28

チャットで、もうカンニングはしないと言ったのですが、この方法を試すのを止められず、うまくいきました。

setInterval(function() {
    Runner.instance_.horizon.obstacles[0].collisionBoxes = [];
},200);

私は200msごとに衝突ボックスを空にしていて、ユニコーンがそれらを通過します。でもジャンプカウントをチェックしてみようと思ったので、必要がなくても障害物を見るたびにジャンプしました。

PS私の行動の後、あなたは私を一度だけ禁止することができます

5 nobody Dec 23 2020 at 19:40

ルールに従わずにシステムを打ち負かしてハイスコアを獲得する方法を見つけました。リーダーボードへの参加が禁止されているため、スコアを取得できませんでしたが、ブラウザコンソールのメッセージで、ゲームが疑われていないことが確認されました。

私はjavascriptにあまり詳しくないので、おそらく私のものが私がしていることを行うための最良の方法ではありませんが、それが機能するので、これを行う正しい方法を考え出すのに時間を無駄にしません。

基本的には、ゲームを開始して障害物にぶつかる必要があります。ゲームが終了したら、

Runner.instance_.horizon.obstacles[0].typeConfig.yPos = 1337

ブラウザコンソールに。次に、もう一度開始し、別の障害物にぶつかって、同じコードをコンソールに入力します。障害物がなくなるまで繰り返します(問題がない場合は、これを3回行う必要があり、3回目はしばらくして障害物が来るので、実際の障害物を避けているかのようにジャンプして、つまずかないようにする必要があります。アンチチートシステム)。これで障害物なしでゲームをプレイできますが、アンチチートシステムをだますには、実際に障害物を避けているかのようにジャンプしてダッキングし続ける必要があります。疲れるまで続けてから、別のタブに切り替えてゲームを終了します。

ボンネットの下:、障害物の3種類がありCACTUS_SMALLCACTUS_LARGEそしてPTERODACTYL(彼らは、配列で定義されているがs.types)。障害物にぶつかると、その特定の障害物タイプの参照がに保存されるRunner.instance_.horizon.obstacles[0]ため、その特定の障害物タイプyPosを巨大なものに変更します。このタイプの障害物はキャンバスの外側に配置されるため、見えなくなり、さらに重要なことに、障害物にぶつかることができなくなります。私が言ったように、私はjavascriptにあまり精通していないのでs.types、コンソールから直接編集する方法を理解できませんでした(この場合、yPos3つのタイプすべてを一度に変更できます)が、可能であれば、お気軽にこの投稿を編集するか、以下にコメントしてください。

ここで私は2557を獲得し、コンソールは Is Game Suspect = false

4 Panda Dec 22 2020 at 22:02

ゲームはJavaScriptを介して自動再生できるようです。

まず、明確にするために、私はこのスクリプト全体を作成しませんでした。(元のソース)ユニコーンがかわすように修正し、ジャンプの高さを微調整しました。

それは一貫して私に約1500から2000+のスコアをもたらしました(ユニコーンがうっかりつまずく前に)。

他のチート方法を試しているときに、誤って(以下のスクリプトを使用したためではなく)禁止されました。なぜ私が禁止されたのか、おそらくに関連する理論がありtotalJumpsます。

これが、禁止される前に以下のスクリプトを使用して得たハイスコアの1つです。

const autoPlayLoop = function() {
    const JUMP_SPEED = 750;
    const DISTANCE_BEFORE_JUMP = 112;

    const instance = window.Runner.instance_;
    const tRex = instance.tRex;

    if (tRex.jumping) {
        requestAnimationFrame(autoPlayLoop);
        return;
    }

    const tRexPos = tRex.xPos;
    const obstacles = instance.horizon.obstacles;

    const nextObstacle = obstacles.find(o => o.xPos > tRexPos);

        if (nextObstacle && (nextObstacle.xPos - tRexPos) <= DISTANCE_BEFORE_JUMP) {
        if (nextObstacle.yPos < 80) {
            tRex.setDuck(true);
        } else {
            tRex.startJump(JUMP_SPEED)
        }
    }
    requestAnimationFrame(autoPlayLoop);
}
requestAnimationFrame(autoPlayLoop);
4 Vickel Dec 22 2020 at 21:24

リーダーボードの上部にあるのは、新しい全体的な統計(2020-12-22から)セクションです(より良い名前を思いつくことができたら、私に知らせてください)

ゲームホールでどう呼んでいるのか考えていたのですが

史上最高のスコア

そして

毎日のハイスコア

いいだろう

4 SPArcheon Dec 23 2020 at 00:41

パンダがすでに言及しているので、私はここから取られた別のアプローチを使用してゲームを自動化しようとしていました。元々はT-Rexランゲームをプレイすることを目的としていました。

コードはパンダによって投稿されたものよりもさらに単純です。これが私を禁止した理由かどうかはわかりませんが、参照用に使用した修正バージョンを次に示します。

(function loop() {
    var rand = Math.round(Math.random() * (3)) + 3;
    setTimeout(function() {
      try{
        DoAction()
      }
      catch(e){
              
      }          
      loop();  
    }, rand);
}());

function DoAction(){      
  if (Runner.instance_.horizon.obstacles.length > 0){ // if obsticles exist
    if (Runner.instance_.horizon.obstacles[0].xPos < Runner.instance_.currentSpeed * 20 - Runner.instance_.horizon.obstacles[0].width/3 && Runner.instance_.horizon.obstacles[0].yPos > 75){
      keyUp(40);
      keyDown(38);
    }
    else if (Runner.instance_.horizon.obstacles[0].xPos < Runner.instance_.currentSpeed * 20 - Runner.instance_.horizon.obstacles[0].width && Runner.instance_.horizon.obstacles[0].yPos > 75){
      keyDown(40);
    }    
   }
}

私は現在ゲームを禁止されていることに注意してください。おそらく、最初のハックテストフェーズの後に実装されたゲーム変数への直接アクセスのチェックがあるか、ジャンプの規則性がヒューリスティックをトリガーしました。これは、ジャンプがより「人間らしい」ように見えるようにループにランダムな遅延が含まれるようにコードを変更した理由でもありますが、それがインプレースのチート防止を妨げるのに十分かどうかをテストすることはできません。悲しいことに、私は禁止を受ける前にそれについて考えていませんでした。

ネットワークタブのレポートに基づくと、ゲームは3種類のリクエストを実行しているようです。

  1. https://winterbash2020.stackexchange.com/hat-dash/start:ゲームが開始されたことを示す信号です。タイムスタンプが含まれています
  2. https://winterbash2020.stackexchange.com/hat-dash/cp:奇妙なもの。期間と「ジャンプ合計」を含むメッセージ。現在、これがいつ呼び出されるかはわかりませんでしたが、クライアント側のチート防止システムの一部である可能性があります。それでも、チートなしでプレイしているときでもこれが呼ばれていることに気づいたので、よくわかりません。
  3. https://winterbash2020.stackexchange.com/hat-dash/end:ゲーム終了時に呼び出され、期間などの予想される情報が含まれます。また、「合計ジャンプ」変数を再度参照します。奇妙なことに、ゲームが以前のスコアも参照しているように、「履歴キー」のリストも含まれています。

もともと私は、その「totaljump」がチート防止に関連している可能性が最も高いと思われましたが、次に履歴配列が続きました。

更新:そもそもボットが原因で禁止されたのではないかと疑い始めています。これは、少なくとも技術的には不正行為とは関係のない他のサーバー側のヒューリスティックに関連しているに違いないと思います(テストするだけでトリガーされる可能性があります)。私が持っていたいくつかのランダムなアイデア:

  • 実際にスコアを送信せずにテストを行うためにログオフするように、プライベートブラウザウィンドウで再生しようとしました。ログが記録されていない場合とログに記録されている場合の両方で同じIP結果を持つクライアントがトリガーになる可能性があります。
  • 私はFirefoxの2つのバージョン、標準と開発者を使用しています。好奇心から、開発者を使用して「モバイルモード」でゲームをプレイし、誰かが報告した機能(低い三角形が消える)が正しいかどうかを確認し、T-RexChromeゲームとの動作を比較しました。一部のユーザーは、「ユーザーエージェント文字列の切り替えが多すぎる」ために禁止されていると報告しています
  • APIはプレーヤーのスコア履歴を追跡しているように見えるため、おそらく「スキル」に基づいて何らかの推論を実行しようとします。誰かが突然、自分のスキルと互換性がないと思われるスコアを取得した場合、それがトリガーになる可能性があります。かなり奇妙に思えます-もしそうなら、世界外のスコアの多くが即座に禁止されたと思います(ところで、速度の変化のためにゲームがおそらく最大で約20kで人間がプレイできなくなることを考えると、なぜこれは問題ではないのですか?人間が100kに達する可能性があるとは思えません)。

とは言うものの、これはロジックが元のT-Rexの実行と十分に類似しているかどうかを確認するためのテストであり、元のゲーム用のチートはHat-Dashバージョンでも実行されます(誰かが古いものですでに試し、実証したもの)役職)。今は暇がないので、気になる情報があれば後で報告します。


PS:誰かがこの行が何をすることになっているのか疑問に思っている場合に備えて...

if (Runner.instance_.horizon.obstacles[0].xPos < Runner.instance_.currentSpeed * 20 - Runner.instance_.horizon.obstacles[0].width && Runner.instance_.horizon.obstacles[0].yPos > 75){
  keyDown(40);
}

これは、このゲームが時々持つ「不可能な」ジャンプの問題を解決するための非常に原始的な試みです。マギッシュが気づいたように、ユニコーンが前の障害物に近すぎる障害物をジャンプするのに十分な速さで落下しないことがあります。この場合、押し下げるとユニコーンの落下が速くなります。

4 ZachLipton Dec 29 2020 at 14:43

私は最近、劇作家のブラウザ自動化ライブラリを使ってたくさんの仕事をしています。あなたが持っているのはハンマーだけです...これが不正行為の良い方法だとは言いませんが、そうではありませんが、興味があったのはゲーム自体をまったくいじることなくそれを行うことができました。

私の当初のアイデアは、Playwrightを使用してページを読み込み、ゲームを開始してから、分析のためにスクリーンショットを繰り返しキャプチャすることでした。これは、最大のステルスを得るために、スペースを押す以外にブラウザの環境内で何にも触れる必要がないことを意味します。これは機能しましたが、Chromiumにスクリーンショットを撮ってもらい(ページ全体のサイズ変更を引き起こしました)、それをバッファにロードしてから処理するのに150ミリ秒のオーダーがかかり、遅すぎて私が念頭に置いていた非常に単純なロジックを使用した立派なスコア。ひどく浮気していた。

そこで、クライアントブラウザにtoDataURL()ゲームの<canvas>要素を呼び出すように依頼し、その方法で画像データを取得するように切り替えました。これにより、10〜15ミリ秒程度の遅延が発生する可能性があります。私はゲームの画像を繰り返しキャプチャし、障害物の小さな検出長方形を調べます(非白色ピクセル。コンピュータービジョン、これはそうではありません)。見つかった場合は、スペースバーを押します。これは特に賢いわけではありませんが(私は下矢印を使用しようとはしません。高速では、空中にいる間にジャンプしようとするため失敗する可能性があります)、ゲームは自動化されているため、そのまま維持できます。幸運な走りができるまでプレーします。

このプロセスは、2つの定数によって制御されます。XCROP検出領域を決定するがあります。障害物を検出するときに、どれだけ前に先を見越すかです。設定を間違えると、ジャンプが早すぎたり遅すぎたりします。そしてSPEED_FACTOR、ゲームが進むにつれてxcropが直線的に増加して、より速い速度に調整するものがあります(速度は直線的に増加しますか?私はチェックしませんでした)。これらの定数は、スクリプトが実行されている環境に非常に敏感です。自動化をゲームと同期させる試みは行われずSCALE_FACTOR、画像のキャプチャ/処理ループの実行にかかる時間に完全に依存します。console.log()テストでステートメントを追加/削除するだけでも、それらを破棄するのに十分でした。したがって、これらの値はおそらくシステムでは機能しません。しかし、きちんと調整すれば(さまざまな値で多くのゲームをプレイすることで自動化できます)、毎日のリーダーボードに十分なスコアを簡単に達成できます。

これは特に検出を回避しようとはしません—オブジェクトが検出されるとスペースバーを超人的な速度で繰り返しスパムし、ゲームの後にゲームをプレイする超人的な耐久性を持ちます—そして白以外の33px幅の長方形を見る検出ロジックピクセルはほとんど賢いものではありませんが、常に「Is Game Suspect = false」と表示されるようです。

const Jimp = require('jimp');
const {chromium} = require('playwright');

(async () => {
  const XCROP = 122
  const SPEED_FACTOR = 0.008

  const browser = await chromium.launch({
    headless: false
  })

  const context = await browser.newContext()
  const page = await context.newPage()

  // pipe the browser console to our console so we can see the "Is Game Suspect" message
  page.on('console', msg => console.log(msg.text()))

  const playGame = async (xcrop, speedFactor) => {
    await page.goto('https://winterbash2020.stackexchange.com/run-with-the-hats')
    const container = await page.waitForSelector('.runner-container')

    // start the game
    await container.click()
    await container.press(' ')

    await page.waitForTimeout(500)

    const canvas = await page.$('canvas') let weLost = false for (let count=0; !weLost; count++) { // check if we lost and return our score if we did if (await page.$('.js-personal-stats > div')) {
        weLost = true
        const scoreElems = await page.$$('.js-personal-stats strong')
        const score = parseInt(await scoreElems[2].innerText())
        return score
      }

      // ask the game's <canvas> for its image data as a data url and stuff it in a buffer
      const dataURL = await page.evaluate((elem) => {
        return elem.toDataURL()
      }, canvas)
      const buffer = Buffer.from(dataURL.substr(22), 'base64')

      // parse the image data and crop out a small rectangle from it with 1-bit color depth for analysis
      const img = await Jimp.read(buffer)
      let foundPixel = false
      img
        .crop(xcrop + (speedFactor * count), 100, 33, 20)
        .posterize(2)

      // look though the image data for any non-white pixels, which indicates we found an object
      // the image is in RGBA format, so we can skip bits
      for (let i = img.bitmap.data.length - 1; i >= 0; i -= 4) {
        if (img.bitmap.data[i] != 0) {
          foundPixel = true
          break
        }
      }

      // if we detected an object, press space to jump
      if (foundPixel) {
        await container.press(' ')
        console.log(xcrop + (speedFactor * count), speedFactor, speedFactor * count)
      }
    }
  }

  const playMultipleGames = async (tries, ...args) => {
    const results = []
    for (let i=0; i<tries; i++) {
      const score = await playGame.apply(null, args)
      results.push(score)
    }
    console.log(results, args)
  }

  console.log(await playMultipleGames(20, XCROP, SPEED_FACTOR))

  await page.close()
  await context.close()
  await browser.close()
})().catch((ex) => {
  console.error(ex);
  process.exit(1)
});

ほとんどのテストをログアウトしましたが、いくつかの最終テストのためにスクリプトにログインステップを追加しました(しばらくして、ログインフォームでキャプチャのトリガーを開始したので、少なくとも機能します)。あなたは今私を禁止することができます。

3 iBugsaysReinstateMonica Dec 23 2020 at 02:12

パンダの回答から自動再生スクリプトを修正し、多くの実装の詳細を置き換え、SPArcheonの回答からいくつかの式を使用し(ジャンプ距離の計算)、Tuqayの回答から取得した不正なコードを挿入しました(障害物の衝突ボックスをリセットしました)。また、クライアント側のチート検出を回避するためにゲーム速度を切り捨てようとしていますが、実際にはうまく機能します。

今回はリソースオーバーライドをオフにしたので、SEファイルは操作されません。拡張機能のないVanillaGoogleChromeで十分です。

自作自演から2つのスコアを送信しましたが(メインアカウントは禁止されています)、まだ禁止されていません。下のスクリーンショットを参照してください。ただし、トップスコアはまだリーダーボードに表示されていません。

PS GoogleChromeまたはMicrosoftEdge(新しいChromiumベース)で実行されているJavaScriptの編集とデバッグには、Microsoft Visual StudioCodeをお勧めします。

const keySpace = {
    key: " ",
    keyCode: 32,
    code: "Space",
    which: 32,
    shiftKey: false,
    ctrlKey: false,
    metaKey: false,
    isDown: false,
  },
  keyDown = {
    key: "down",
    keyCode: 40,
    code: "Down",
    which: 40,
    shiftKey: false,
    ctrlKey: false,
    metaKey: false,
    isDown: false,
  };

const createKeyEvent = function (keyObj, duration) {
  if (!keyObj.isDown) {
    document.dispatchEvent(new KeyboardEvent("keydown", keyObj));
    keyObj.isDown = true;
  }
  setTimeout(() => {
    if (keyObj.isDown) {
      document.dispatchEvent(new KeyboardEvent("keyup", keyObj));
      keyObj.isDown = false;
    }
  }, duration)
  ;
};

const autoPlayLoop = function () {
  const instance = Runner.instance_;
  const speed = instance.currentSpeed;
  if (speed > instance.config.MAX_SPEED) {
    instance.currentSpeed = instance.config.MAX_SPEED - 0.1;
  }

  const tRex = instance.tRex;
  // if (tRex.jumping) {
  //   requestAnimationFrame(autoPlayLoop);
  //   return;
  // }

  const tRexPos = tRex.xPos;
  const obstacles = instance.horizon.obstacles;
  const prevObstacle = obstacles.find((o) => o.xPos <= tRexPos);
  const nextObstacle = obstacles.find((o) => o.xPos > tRexPos);

  if (tRex.jumping) {
    if (prevObstacle) {
      createKeyEvent(keyDown, 200);
    }
  } else if (nextObstacle) {
    nextObstacle.collisionBoxes = [];

    const DISTANCE_BEFORE_JUMP = 20 * speed - nextObstacle.width / 3;
    if (nextObstacle.xPos - tRexPos <= DISTANCE_BEFORE_JUMP) {
      if (nextObstacle.yPos < 80) {
        // dodge instead of jump
        createKeyEvent(keyDown, 300);
      } else {
        if (speed >= 18) {
          createKeyEvent(keySpace, 30);
          //setTimeout(() => createKeyEvent(keyDown, 300), 150);
        } else {
          createKeyEvent(keySpace, 30);
        }
        //tRex.startJump(JUMP_SPEED);
      }
    }
  }
  requestAnimationFrame(autoPlayLoop);
};
requestAnimationFrame(autoPlayLoop);
2 double-beep Dec 23 2020 at 22:45

怠惰すぎてSpaceを押してゲームを開始し、他のハックを試すことができない人のための解決策は次のとおりです(私):

(async () => {
    var score = 4000;
    var seconds = 200;
    var jumps = 400;
    Runner.gameStarted();
    Runner.setCurrentScore(score);
    await new Promise(resolve => setTimeout(resolve, seconds * 1000));
    Runner.gameEnded(Runner.instance_.startedAt, jumps);
})();

を置き換えscoresecondsそれにjumps応じて。リーダーボードで他の人の正当なスコアを確認すると便利です。

私の以前の答えから適応。jumpCount新しい不正行為防止機能とともにサーバーにも送信されるため、が追加されました。

1 k170 Dec 28 2020 at 23:48

更新された自動禁止システムは、重力、落下速度、初期ジャンプ速度、現在の速度、障害物の数の不正な値をチェックします。これらの値を変更していなかったので、ゲームが進むにつれて現在の速度が上がるので、現在の速度だけを気にする必要がありました。自動禁止システムの作動を回避するために、現在の速度が25未満の場合は、衝突ボックスをy軸上でのみシフトアップしました。これは私が使用したコードです。

setInterval(function() {
    var yPosition = 1000;
    function shiftCollisionBoxes(obstacles, yPosition) {
        if (obstacles && yPosition >= 0) {
            for (var i = 0; i < obstacles.length; i++){
                for (var j = 0; j < obstacles[i].collisionBoxes.length; j++){
                    obstacles[i].collisionBoxes[j].y = yPosition;
                }
            }
        }
    }
    if (Runner.instance_.currentSpeed < 25) {
        shiftCollisionBoxes(Runner.instance_.horizon.obstacles, yPosition);
        shiftCollisionBoxes(Runner.instance_.obstacles, yPosition);
    }
},200);

私は良いことを誓いました。いつ言及するのを忘れたと思います。他のソリューションがすでにコリジョンボックスとy軸の配置をいじっているので、このソリューションは100%ユニークではないことを認めます。ただし、このソリューションは、衝突ボックスの存在を確認するだけでは不十分であることを示しています。コードは、衝突ボックスの位置が改ざんされていないことも確認する必要があります。このソリューションは、更新された自動禁止システムの呼び出しを明示的に回避します。なんとか殿堂入りできれば、この帽子は私の白いバンとよく合います。みなさん、おめでとうございます!

Anonymous Dec 28 2020 at 06:28

Number関数を編集してスコアを変更できます。置き換えられると、コードはスコア文字列をFirefoxのきれいに印刷されたコードの1168行目の数値に変換しようとし、代わりにカスタム関数を実行します。このコードをJavaScriptコンソールに貼り付けます。

function Number(n) {
  return 1000000;
}