Flash ゲームの PHP ベースのハイスコア テーブルをハッキングする人々を阻止する最善の方法は何ですか?

StackOverflow https://stackoverflow.com/questions/73947

質問

私が話しているのは、スコアの上限がなく、動きをリプレイするなどしてサーバー上のスコアを確認する方法がないアクション ゲームについてです。

私が本当に必要としているのは、Flash/PHP で可能な最も強力な暗号化と、ユーザーが私の Flash ファイル経由以外で PHP ページを呼び出すことを防ぐ方法です。私は過去に、単一のスコアに対して複数の呼び出しを行ったり、チェックサム/フィボナッチ数列を完了したり、Amayeta SWF Encrypt で SWF を難読化したりするいくつかの簡単な方法を試しましたが、最終的にはすべてハッキングされました。

StackOverflow の応答のおかげで、Adobe からさらに詳しい情報を見つけました - http://www.adobe.com/devnet/flashplayer/articles/secure_swf_apps_12.html そして https://github.com/mikechambers/as3corelib - 暗号化に使用できると思います。ただし、これで CheatEngine を回避できるかどうかはわかりません。

AS2 と AS3 が異なる場合、両方に最適なソリューションを知る必要があります。

主な問題は TamperData や LiveHTTP ヘッダーのようなもののようですが、CheatEngine のような、より高度なハッキング ツールもあると理解しています (Mark Webster に感謝)

役に立ちましたか?

解決

これは、インターネット ゲームやコンテストでよくある問題です。Flash コードはユーザーと協力してゲームのスコアを決定します。しかし、ユーザーは信頼されておらず、Flash コードはユーザーのコンピュータ上で実行されます。ソル君ですね。攻撃者がハイスコアを偽造することを防ぐためにできることは何もありません。

  • Flash は、バイトコードが十分に文書化され、高級言語 (Actionscript) を記述しているため、思っているよりもリバース エンジニアリングが簡単です。Flash ゲームを公開すると、ソース コードを公開することになります。それを知っているかどうか。

  • 攻撃者は Flash インタプリタのランタイム メモリを制御するため、プログラマブル デバッガの使用方法を知っている人がいつでも任意の変数 (現在のスコアを含む) を変更したり、プログラム自体を変更したりできます。

システムに対する最も単純な攻撃は、プロキシ経由でゲームの HTTP トラフィックを実行し、ハイスコアのセーブをキャッチし、より高いスコアでリプレイすることです。

各ハイスコア セーブをゲームの単一インスタンスにバインドすることで、この攻撃をブロックすることができます。たとえば、ゲームの起動時に暗号化されたトークンをクライアントに送信します。これは次のようになります。

hex-encoding( AES(secret-key-stored-only-on-server, timestamp, user-id, random-number))

(同じ効果を得るためにセッション Cookie を使用することもできます)。

ゲーム コードは、ハイスコア セーブとともにこのトークンをサーバーにエコー バックします。ただし、攻撃者はゲームを再度起動してトークンを取得し、そのトークンを再生されたハイスコア セーブにすぐに貼り付けることができます。

次に、トークンまたはセッション Cookie だけでなく、高スコア暗号化セッション キーもフィードします。これは 128 ビット AES キーとなり、Flash ゲームにハードコードされたキーでそれ自体が暗号化されます。

hex-encoding( AES(key-hardcoded-in-flash-game, random-128-bit-key))

ここで、ゲームはハイスコアを投稿する前に、ハイスコア暗号化セッションキーを復号化します。これは、ハイスコア暗号化セッションキー復号化キーを Flash バイナリにハードコードしているため可能です。この復号化されたキーとハイスコアの SHA1 ハッシュを使用してハイスコアを暗号化します。

hex-encoding( AES(random-128-bit-key-from-above, high-score, SHA1(high-score)))

サーバー上の PHP コードは、トークンをチェックしてリクエストが有効なゲーム インスタンスからのものであることを確認し、暗号化されたハイ スコアを復号して、ハイ スコアがハイ スコアの SHA1 と一致することを確認します (この手順をスキップした場合) 、復号化は単にランダムな、おそらく非常に高い、高いスコアを生成します)。

したがって、攻撃者は Flash コードを逆コンパイルし、すぐに AES コードを見つけます。このコードは親指のように突き出ていますが、たとえ見つけられなかったとしても、メモリ検索とトレーサーによって 15 分以内に追跡されるでしょう (「わかっています」このゲームのスコアは 666 なので、メモリ内で 666 を見つけて、その値に触れる操作をキャッチしましょう --- ああ、ハイ スコアの暗号化コードです!」)。セッション キーを使用すると、攻撃者は Flash コードを実行する必要さえありません。彼女はゲーム起動トークンとセッション キーを取得し、任意のハイスコアを送り返すことができます。

現在、ほとんどの開発者はあきらめる段階にいます。次のような方法で攻撃者と干渉するのに数か月かかるかかかるかです。

  • XOR 演算による AES キーのスクランブル

  • キーのバイト配列をキーを計算する関数に置き換える

  • バイナリ全体に偽のキー暗号化と高スコアの投稿を散在させます。

これはほとんど時間の無駄です。言うまでもなく、SSL も役に立ちません。2 つの SSL エンドポイントのうちの 1 つが不正な場合、SSL はユーザーを保護できません。

ハイスコ​​ア不正行為を実際に減らすことができるいくつかのことは次のとおりです。

  • ゲームをプレイするためにログインを要求し、ログインによってセッション Cookie が生成されるようにし、同じセッションでの複数の未処理のゲームの起動や、同じユーザーの複数の同時セッションを許可しません。

  • これまでにプレイされた実際のゲームの最短時間より短いゲーム セッションからの高スコアを拒否します (より洗練されたアプローチの場合は、平均ゲーム継続時間より 2 標準偏差未満続いたゲーム セッションの高スコアを「隔離」してみてください)。サーバー側でゲーム時間を追跡していることを確認してください。

  • ゲームを 1 回か 2 回しかプレイしていないログインからの高スコアを拒否または隔離することで、攻撃者は作成するログインごとに妥当なゲーム プレイの「紙の証跡」を作成する必要があります。

  • 「ハートビート」はゲームプレイ中にスコアを取得するため、サーバーは 1 回のゲームプレイの全期間にわたるスコアの増加を確認します。妥当なスコア曲線に従わない高スコア (たとえば、0 から 999999 までのジャンプ) を拒否します。

  • ゲームプレイ中の「スナップショット」ゲーム状態 (弾薬の量、レベル内の位置など)。後で記録された暫定スコアと照合できます。そもそも、このデータの異常を検出する方法を持っている必要さえありません。それを収集するだけで済み、問題が怪しい場合は戻って分析することができます。

  • セキュリティチェックのいずれかに不合格となったユーザーのアカウントを無効にします (たとえば、検証に失敗した暗号化されたハイスコアを送信したことがあるなど)。

ただし、ここではハイスコア詐欺を阻止しているだけであることに注意してください。あるよ 何もない 場合を防ぐために行うことができます。あなたのゲームに金がかかっているなら、あなたが考え出したどんなシステムでも誰かが打ち破るでしょう。目的はそうではありません 停止 この攻撃。それは、単にゲームが上手になって勝つことよりも、攻撃のコストを高くするためです。

他のヒント

間違った質問をしている可能性があります。あなたは、人々がハイスコアリストを駆け上がるために使用している方法に焦点を当てているようですが、特定の方法をブロックするだけでは限界があります。私は TamperData の経験がないので、それについて話すことはできません。

あなたが尋ねるべき質問は次のとおりです。「提出されたスコアが有効で本物であることを確認するにはどうすればよいですか?」それを行う特定の方法は、ゲーム依存です。非常に単純なパズル ゲームの場合は、特定の開始状態と終了状態に至るまでの一連の動きとともにスコアを送信し、同じ動きを使用してサーバー側でゲームを再実行することがあります。指定されたスコアが計算されたスコアと同じであることを確認し、一致する場合にのみスコアを受け入れます。

これを行う簡単な方法は、スコア自体とともにハイスコア値の暗号化ハッシュを提供することです。たとえば、HTTP GET 経由で結果を投稿する場合:http://example.com/highscores.php?score=500&checksum=0a16df3dc0301a36a34f9065c3ff8095

このチェックサムを計算するときは、共有秘密を使用する必要があります。このシークレットはネットワーク経由で送信してはいけませんが、PHP バックエンドとフラッシュ フロントエンドの両方内でハードコーディングする必要があります。上記のチェックサムは、文字列「」を先頭に付加することによって作成されました。秘密「スコアに合わせて」500" を実行し、md5sum を介して実行します。

このシステムは、ユーザーが任意のスコアを投稿することを防ぎますが、ユーザーが以前に計算したスコアとハッシュの組み合わせを再投稿する「リプレイ攻撃」を防ぐことはできません。上の例では、スコア 500 は常に同じハッシュ文字列を生成します。このリスクの一部は、ハッシュされる文字列にさらに多くの情報 (ユーザー名、タイムスタンプ、IP アドレスなど) を組み込むことで軽減できます。これによってデータの再生は妨げられませんが、データ セットが一度に 1 人のユーザーに対してのみ有効であることが保証されます。

防ぐために どれでも リプレイ攻撃の発生を防ぐには、次のような何らかのタイプのチャレンジ/レスポンス システムを作成する必要があります。

  1. Flash ゲーム (「クライアント」) は、次の HTTP GET を実行します。 http://example.com/highscores.php パラメーターなしで。このページは 2 つの値を返します。ランダムに生成された 値、および共有秘密と組み合わせたそのソルト値の暗号化ハッシュ。このソルト値は、保留中のクエリのローカル データベースに保存する必要があり、おそらく 1 分後に「期限切れになる」ように、タイムスタンプが関連付けられている必要があります。
  2. Flash ゲームは、ソルト値と共有シークレットを組み合わせてハッシュを計算し、これがサーバーによって提供されたものと一致することを確認します。このステップは、ソルト値が実際にサーバーによって生成されたことを確認するため、ユーザーによるソルト値の改ざんを防ぐために必要です。
  3. Flash ゲームは、ソルト値を共有シークレット、ハイスコア値、およびその他の関連情報 (ニックネーム、IP、タイムスタンプ) と組み合わせて、ハッシュを計算します。次に、この情報をソルト値、ハイスコア、その他の情報とともに HTTP GET または POST 経由で PHP バックエンドに送り返します。
  4. サーバーは、クライアントと同じ方法で受信した情報を結合し、ハッシュを計算して、これがクライアントから提供されたものと一致することを確認します。次に、ソルト値が保留中のクエリ リストにリストされているようにまだ有効であることも検証します。これらの条件が両方とも true の場合、ハイ スコアをハイ スコア テーブルに書き込み、署名された「成功」メッセージをクライアントに返します。また、保留中のクエリ リストからソルト値も削除されます。

ユーザーが共有シークレットにアクセスできる場合、上記のいずれかの手法のセキュリティが危険にさらされることに注意してください。

別の方法として、クライアントが HTTPS 経由でサーバーと通信するように強制し、自分だけがアクセスできる特定の認証局によって署名された証明書のみを信頼するようにクライアントが事前構成されていることを確認することで、このやり取りの一部を回避できます。 。

tpqf の意見は気に入っていますが、不正行為が発見されたときにアカウントを無効にするのではなく、ハニーポットを実装して、ログインするたびにハッキングされたスコアが表示され、荒らしとしてマークされているとは決して疑わないようにします。「phpBB MOD Troll」で Google で検索すると、独創的なアプローチが表示されます。

受け入れられた回答の中で、tqbfは、スコア変数のメモリ検索を行うだけでよいと述べています(「私のスコアは666なので、メモリ内の数値666を探します」)。

これを回避する方法があります。ここにクラスがあります: http://divillysausages.com/blog/safenumber_and_safeint

基本的に、スコアを保存するオブジェクトがあります。セッターでは、渡された値に乱数 (+ と -) を乗算し、ゲッターでは、保存された値をランダムな乗算器で除算して元の値に戻します。シンプルですが、メモリ検索を停止するのに役立ちます。

また、PushButton エンジンの背後にいる何人かがハッキングに対抗するためのさまざまな方法について語るビデオもチェックしてください。 http://zaa.tv/2010/12/the-art-of-hacking-flash-games/. 。彼らはクラスの背後にあるインスピレーションでした。

何らかの回避策を講じました...私はスコアが増加する場所を与えました(常に+1スコアを取得します)。まず、ランダムな数値 (たとえば 14 ) からカウントを開始し、スコアを表示するときは、スコア var から 14 を引いたものだけを表示しました。これは、クラッカーがたとえば 20 を探している場合、それが見つからないようにするためです (メモリ内では 34 になります)。次に、次のポイントが何であるべきかはわかっているので...Adobe crypto libraryを使用して、次のポイントのハッシュを作成しました あるべきです. 。スコアをインクリメントする必要がある場合は、インクリメントされたスコアのハッシュが本来あるべきハッシュと等しいかどうかを確認します。クラッカーがメモリ内のポイントを変更した場合、ハッシュは等しくなくなります。サーバー側の検証を実行し、ゲームと PHP から異なるポイントが得られた場合、不正行為が関与していることがわかりました。これは私のコードのスニペットです(私はAdobe Crypto libraty MD5クラスとランダムな暗号化ソルトを使用しています。callPhp() はサーバー側の検証です)

private function addPoint(event:Event = null):void{
            trace("expectedHash: " + expectedHash + "  || new hash: " + MD5.hash( Number(SCORES + POINT).toString() + expectedHashSalt) );
            if(expectedHash == MD5.hash( Number(SCORES + POINT).toString() + expectedHashSalt)){
                SCORES +=POINT;
                callPhp();
                expectedHash = MD5.hash( Number(SCORES + POINT).toString() + expectedHashSalt);
            } else {
                //trace("cheat engine usage");
            }
        }

この技術と SWF 難読化を使用して、クラッカーを阻止することができました。また、スコアをサーバー側に送信するときに、独自の小さな暗号化/復号化関数を使用します。次のようなものです (サーバー側のコードは含まれていませんが、アルゴリズムを確認して PHP で記述することができます)。

package  {

    import bassta.utils.Hash;

    public class ScoresEncoder {

        private static var ranChars:Array;
        private static var charsTable:Hash;

        public function ScoresEncoder() {

        }

        public static function init():void{

            ranChars = String("qwertyuiopasdfghjklzxcvbnm").split("")

            charsTable = new Hash({
                "0": "x",
                "1": "f",
                "2": "q",
                "3": "z",
                "4": "a",
                "5": "o",
                "6": "n",
                "7": "p",
                "8": "w",
                "9": "y"

            });

        }

        public static function encodeScore(_s:Number):String{

            var _fin:String = "";

            var scores:String = addLeadingZeros(_s);
            for(var i:uint = 0; i< scores.length; i++){
                //trace( scores.charAt(i) + " - > " + charsTable[ scores.charAt(i) ] );
                _fin += charsTable[ scores.charAt(i) ];
            }

            return _fin;

        }

        public static function decodeScore(_s:String):String{

            var _fin:String = "";

            var decoded:String = _s;

            for(var i:uint = 0; i< decoded.length; i++){
                //trace( decoded.charAt(i) + " - > "  + charsTable.getKey( decoded.charAt(i) ) );
                _fin += charsTable.getKey( decoded.charAt(i) );
            }

            return _fin;

        }

        public static function encodeScoreRand(_s:Number):String{
            var _fin:String = "";

            _fin += generateRandomChars(10) + encodeScore(_s) + generateRandomChars(3)

            return _fin;
        }

        public static function decodeScoreRand(_s:String):Number{

            var decodedString:String = _s;
            var decoded:Number;

            decodedString = decodedString.substring(10,13);         
            decodedString = decodeScore(decodedString);

            decoded = Number(decodedString);

            return decoded;
        }

        public static function generateRandomChars(_length:Number):String{

            var newRandChars:String = "";

            for(var i:uint = 0; i< _length; i++){
                newRandChars+= ranChars[ Math.ceil( Math.random()*ranChars.length-1 )];
            }

            return newRandChars;
        }

        private static function addLeadingZeros(_s:Number):String{

            var _fin:String;

            if(_s < 10 ){
                 _fin = "00" + _s.toString();
            }

            if(_s >= 10 && _s < 99 ) {
                 _fin = "0" + _s.toString();
            }

            if(_s >= 100 ) {
                _fin = _s.toString();
            }           

            return _fin;
        }


    }//end
}

次に、変数を他の偽の変数と一緒に送信しますが、途中で紛失してしまいます...小さなフラッシュ ゲームだけでも大変な作業ですが、賞品が関係する場合は、貪欲になる人もいます。サポートが必要な場合は、PM を書いてください。

乾杯、イコ

既知の (秘密) 可逆キーを使用して暗号化するのが最も簡単な方法です。私は AS について詳しくないので、どのような種類の暗号化プロバイダーがあるのか​​わかりません。

ただし、ゲームの長さ (これも暗号化されています) やクリック数などの変数を含めることもできます。

こういったものすべて できる リバースエンジニアリングされるため、人々をその匂いから遠ざけるために大量のジャンクデータを投入することを検討してください。

編集:PHP セッションにも参加する価値があるかもしれません。ユーザーが「ゲーム開始」をクリックするとセッションを開始し、(この投稿のコメントにあるように) 時間を記録します。彼らがスコアを提出するとき、彼らが実際にオープンゲームを持っていること、そして彼らがスコアを提出するのが早すぎたり大きすぎたりしていないかを確認できます。

スカラーを計算して、プレイの 1 秒/分あたりの最大スコアがどのくらいになるかを確認する価値はあるかもしれません。

これらはどちらも回避できないわけではありませんが、人々が見ることができる Flash 以外にロジックを用意しておくと役に立ちます。

私の経験では、これはプログラミングの問題ではなく、ソーシャル エンジニアリングの問題としてアプローチするのが最善です。不正行為を不可能にすることに重点を置くのではなく、不正行為をする動機を取り除くことで不正行為を退屈にすることに重点を置きます。たとえば、主なインセンティブが一般に公開されるハイスコアである場合、ハイスコアが表示されるタイミングを遅らせるだけで、不正行為者に対する正のフィードバック ループが排除され、不正行為を大幅に減らすことができます。

クライアントが返すデータは信頼できません。検証はサーバー側で実行する必要があります。私はゲーム開発者ではありませんが、ビジネスソフトウェアを作成しています。どちらの場合も、金銭が関与する可能性があり、人々がクライアント側の難読化技術を破ってしまう可能性があります。

定期的にデータをサーバーに送り返して、検証を行う場合もあります。たとえそれがアプリケーションの場所であっても、クライアント コードに焦点を当てないでください。

ハイスコ​​ア システムが、Flash アプリケーションが暗号化されていない/署名されていないハイスコア データをネットワーク経由で送信するという事実に基づいている場合、そのデータは傍受され、操作/再生される可能性があります。そこから答えが導き出されます。ハイスコ​​アデータを暗号化するか、暗号的に署名します。これにより、SWF ファイルから秘密キーを抽出する必要があるため、少なくともハイスコア システムをクラックすることが難しくなります。おそらくそこで諦めてしまう人も多いでしょう。一方、必要なのは 1 人でキーを抽出してどこかに投稿することだけです。

実際のソリューションでは、Flash アプリケーションとハイスコア データベースの間でより多くの通信が必要となり、後者は特定のスコアがある程度現実的であることを検証できます。これは、ゲームの種類によっては複雑になる可能性があります。

SWF を逆コンパイルするのは簡単なので、完全にハッキングできないようにする方法はありません。熟練した開発者ハッカーがコードを追跡し、使用している暗号化システムをバイパスする方法を見つけ出す可能性があります。

ただし、TamperData などの単純なツールを使用して子供の不正行為を阻止したいだけの場合は、起動時に SWF に渡す暗号化キーを生成できます。次に、次のようなものを使用します http://code.google.com/p/as3crypto/ ハイスコ​​アを PHP コードに戻す前に暗号化します。次に、データベースに保存する前にサーバー側で復号化します。

あなたはいわゆる「クライアントの信頼」問題について話しています。クライアント (このキャッシュでは、ブラウザーで実行される SWF) が、意図されたことを実行しているためです。ハイスコ​​アを保存します。

問題は、「スコアの保存」リクエストが任意の HTTP リクエストではなく、Flash ムービーから送信されていることを確認する必要があることです。これに対する考えられる解決策は、リクエスト時にサーバーによって生成されたトークンを SWF にエンコードすることです ( フラズム) ハイスコアを保存するリクエストを伴う必要があります。サーバーがスコアを保存すると、トークンの有効期限が切れ、リクエストに使用できなくなります。

この欠点は、ユーザーが Flash ムービーのロードごとにハイスコアを 1 つしか送信できないことです。新しいスコアを再度再生するには、ユーザーに SWF の更新/再ロードを強制する必要があります。

通常、ハイスコアエントリにはゲームセッションの「ゴーストデータ」を含めます。なので、レースゲームを作る場合はリプレイデータも含めます。多くの場合、リプレイ機能やゴースト レース機能 (最後のレースとの対戦、またはリーダーボードの 14 位の男のゴーストとの対戦) 用のリプレイ データがすでに存在します。

これらをチェックするのは非常に手作業ですが、コンテストの上位 10 件のエントリーが正当なものであるかどうかを確認することが目的の場合、これは他の人がすでに指摘しているセキュリティ対策の武器庫に追加するのに役立ちます。

ハイスコ​​アリストを誰にも見られずに最後までオンラインに保存することが目標である場合、これはあまり役に立ちません。

新しい人気のあるアーケード MOD のやり方は、データをフラッシュから php に送信し、フラッシュに戻し (または再ロードして)、その後 php に戻すというものです。これにより、データを比較したり、ポストデータ/復号化ハッキングなどをバイパスしたりすることができます。これを行う 1 つの方法は、PHP から 2 つのランダムな値をフラッシュに割り当て (リアルタイム フラッシュ データ グラバーを実行していても取得したり表示したりすることはできません)、数式を使用してランダムな値とスコアを加算し、それを次の方法でチェックすることです。同じ式を逆にして、最終的に最後にphpに送信されるときにスコアが一致するかどうかを確認します。これらのランダムな値は決して目に見えないだけでなく、トランザクションの実行時間を測定し、数秒を超えた場合には不正行為としてフラグを立てます。ある種の暗号を介して数値を取得し、スコア値と比較するために考えられるランダムな値を返します。

私に言わせれば、これはかなり良い解決策のように思えますが、この方法を使用することに問題があると感じている人はいますか?あるいはそれを回避する可能な方法はありますか?

最も簡単な方法は、ゲームが追加するスコアを登録するたびに RegisterScore(score) のような関数を呼び出し、それをエンコードしてパッケージ化し、文字列として PHP スクリプトに送信することだと思います。PHP スクリプトはそれを適切にデコードする方法を知っています。これにより、スコアを強制的に取得しようとすると解凍エラーが発生するため、PHP スクリプトへの直接の呼び出しが停止されます。

を維持することでのみ可能になります。 すべてのゲームロジックはサーバー側で また、ユーザーが知らないうちにスコアが内部的に保存されます。経済的および科学的な理由から、人類はこの理論をターン制を除くすべてのゲーム タイプに適用することはできません。たとえば、サーバー側で物理を維持すると計算コストが高くつき、手の速さで応答するのが困難です。さらに、チェスをプレイしながら、誰でも AI チェスのゲームプレイを相手に合わせることができます。したがって、より良い マルチプレイヤーゲーム オンデマンドの創造性も含める必要があります。

あなたが望んでいることを達成することは実際には不可能です。Flash アプリの内部には、特に次のようなものの使用方法を知っている場合、常に部分的にアクセスできます。 チートエンジン, つまり、Web サイトとブラウザーとサーバーの通信がどれほど安全であっても、それを克服するのは比較的簡単であるということです。

バックエンドと通信するのは良い考えかもしれません AMFPHP. 。少なくとも怠け者がブラウザコンソール経由で結果をプッシュしようとするのを思いとどまらせるはずです。

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top