blog

DeNAのエンジニアが考えていることや、担当しているサービスについて情報発信しています

2023.01.10 技術記事

DeNAでのセキュリティチェックから分かるゲーム開発で作りがちなチートの穴

by hyu.ogawa

#セキュリティ #PacketProxy #脆弱性診断 #チート

こんにちは、技術統括部セキュリティ部セキュリティ技術グループの小川です。

今回は Shibuya.gamesec #2 にて発表した内容について、ブログ上で紹介していきたいと思います。ゲーム開発者の方がどのようなことに気を付けて開発をすると良いか、参考になれば幸いです。

DeNA におけるリリース前のチート・脆弱性診断

DeNA ではサービスの新規リリースやアップデートを行う前に、セキュリティ部がコードレビュー等を含むチート・脆弱性診断を行っています。公開する前に「その内容を世の中に出して安全か」を確認し、危険な箇所があれば指摘、安全な状態にしてからリリースをしています。

ここでは、このセキュリティチェックで過去発見・修正した脆弱性について集計し、どのような脆弱性がありがちかに触れていきます。集計対象はゲームアプリ・サーバーに関する脆弱性に限ります。しかし、一般的な統計情報として利用するための十分な対象選定とはなっていないため「一般的にこの脆弱性が多い」と断言するには至らないのでご注意ください。

ありがちなチート・脆弱性

過去数年、DeNAのゲームに関するチート・脆弱性診断で見つかった脆弱性を以下の 2 分類で集計しました。結果と共に示します。

攻撃種別による脆弱性の分類

脆弱性の原因別による脆弱性の分類

この内、攻撃種別に大別して「どのような脆弱性が多いか」について触れていきます。それぞれの事象の中で脆弱性の原因についても触れていきます。紹介される脆弱性は全て現時点で再現が修正されるなどで現在時点で再現は不可能なものです。

リクエスト・レスポンス改ざん

今回の分類の中では、「リクエスト・レスポンス改ざん」によるものが最多となっていました。正確には以下の 5 つの小分類からなります。

  • リクエスト改ざん
  • レスポンス改ざん
  • リクエストの再送・同時送信
  • 脆弱な暗号方式の利用
    • これによりMan-in-the-Middleの可能性がありますが、難易度を含めたポリシーを満たしているかの判定なため、詳細は触れません。
  • DoS

リクエスト改ざん

ゲームクライアントから送られるパケットのデータを都合のいいよう変更してサーバーに送信、改ざん後の値が正しいものとして受理されれば成立する攻撃です。 PacketProxy 等の Proxy ツールを用いて行うことが多いと考えられます。

リクエスト改ざんに関する事例を 2 つ紹介します。

不正なアイテムでのガチャの実行

ガチャを行う際のリクエストに gacha_idcoin_id の両方が含まれているような実装をされているケースを考えます。この時、サーバー側ではそれぞれの値の検証処理が別々に行われており、組み合わせが正しくないものでも正当なものとして扱われるようになっている場合、脆弱性につながります。

例えば gacha_id を本来有償通貨でしか購入できないものに、coin_id を無償で大量に手に入るようなゲーム内の通貨や通貨に準ずるアイテム・チケットの識別子に改ざんすることで、有償でのみ利用できるガチャを無償で利用することが出来てしまいます。

このような脆弱性の原因として、開発時におけるサーバー側のバリデーションの不足が挙げられます。gacha_idcoin_id には想定される組み合わせがあり、それ以外の組み合わせ(有償通貨限定ガチャと無償通貨等)は処理の実行前に弾かなくてはなりません。また、gacha_id が決まった時点で coin_id も決まるような仕様にすることで回避することもできます。

リクエスト改ざんによるDoS

プロフィール変更機能でプロフィールアイコンとして指定された一覧の中から自由に選べる形式を採用していた際、存在しないIDをアイコンにするようなリクエストを送信し、それがサーバーに受理されたとします。

その結果、存在しないプロフィールアイコンを取得しようとしたクライアントアプリがクラッシュするとします。ここまででは自分自身のクライアントにしか影響が無いのですが、もしこれがフレンド機能やランキング機能などで不特定多数の人に自然に参照されるような状態だった場合、大規模なDoSになる可能性があります。

これもバリデーションの不足によるもので、サーバー側で「存在しないプロフィールアイコンを登録させない」もしくはクライアント側で「プロフィールアイコンが存在しない場合はデフォルトアイコンを表示する」といった処理が求められます。

レスポンス改ざん

リクエストに対してサーバーから返送されるパケットのデータを変更し、クライアントでそのデータを扱うことでゲームを優位に進める手法です。分かりやすい例で、PvE におけるボスの HP を 1 にすることで、クライアント側で優位にその対戦に勝利することが出来ます。

以下事例を紹介します。

利用不可ユニットの利用

編成機能にて、「レイドにおける再利用不可」や「PvPにおける禁止ユニット」といった一部機能にて特定のルールに沿って編成することを要求する場合があります。この際、その編成で利用できない禁止ユニット一覧をサーバーから送信、編成時にクライアントでは該当ユニットを表示しないようにしているとします。

しかし、サーバーからのレスポンスを改ざんして禁止ユニット一覧を空にできてしまう場合、クライアントで本来利用できないユニットを利用して編成をすることが出来、ゲームを優位に進めるのみならず他ユーザーにも影響が出る可能性があります。

このケースではレスポンスにレスポンスのハッシュ値を加えるなどして通信の改ざんに耐性のある状態にすることが望ましいです。

リクエスト再送・同時送信

リクエスト改ざんで述べたProxyツールなどを用いて、保存しておいたリクエストを再度送信します。例えば「一日一回限定ガチャを実行するリクエストを再送する」ことでアプリの表示上の回数制限を突破するのがリクエスト再送です。

例えば「最後にそのガチャを引いた時刻」を記録することでサーバー側で検証することが可能です。

リクエスト同時送信はリクエスト再送の対策が不十分な場合に有効で、DBのトランザクションによるロックがなされていない場合を狙い、全く同じリクエストを短時間に大量に送信します。次に具体例をあげます。

リクエスト同時送信による一回分のアイテム消費で複数回ガチャを引く

トランザクション不備により、同時に複数のリクエストが捌かれる際に、以下のように処理が行われてしまうケースがあります。

  1. 複数スレッドでDBからread、現在所有するコイン数が足りていることを確認
  2. ガチャ1回分のコイン消費を行った数を複数スレッドで書き込み。この時点で複数スレッド分のコイン消費がされていません。
  3. 各スレッドでコイン数が足りていると判断されているので、各々のスレッドでガチャの実行

結果として、消費コイン数よりも多い回数のガチャが実行されることになります。

対策として、処理にロックを行うことが求められます。データベースのトランザクションにより、コイン数のread-modify-writeをatomicに行うことにより、同時に大量にリクエストが来ても順番に捌かれ、整合性を保つことが出来ます。

DoS

リクエストを送信し、他ユーザの正常利用を阻害する攻撃です。主に処理に長い時間を要するリクエストを大量に送る DDoS が有名です。

個人的には正常系はパフォーマンスを計測されていると思われ、リクエスト改ざんを併用して想定外のリクエストを送信された際に発生することが多いと考えています。珍しい例があったので 1 つ紹介します。

リクエスト毎にリクエストサイズが増大することによる DDoS の可能性

パーティーの編成機能で、「このユニットをこの枠に入れた」という変更情報をサーバーに送信しており、一括編成を考慮してこの通信では変更情報を複数要素からなる配列としていました。しかしその配列の長さの上限は定められておらず、非常に巨大なリクエストを送信しても受理される状態でした。また、クライアント側でも本来不要と思われる過去に行った編成の変更情報も含めてリクエスト送信しており、編成を何度も繰り返すことでリクエストの長さが増大していくことにより脆弱性に気付きやすく・容易に攻撃することが出来る環境になっていました。

対策として、配列の長さに上限を設けるのが妥当です。それに合わせて、クライアント側でも過去の変更履歴を一緒に送らないようにすることが求められると思われます。

メモリ改ざん

ゲーム実行中におけるメモリ空間上の値を変更し、ゲームを有効に進めるものです。 apk-medit 等のテスト用ツールがあり、 Frida でも行うことが出来ます。

例えば値をそのまま保持せず、ランダムな値と排他的論理和を取った値を保持するようにすることで、直接的な検索が出来なくなります。

しかし、例えば固定値の場合は変化値から読み取れてしまったり、乱数を用いている場合はメモリ上の offset を用いて検索出来る場合があり、最終的にバイナリ解析してメモリ保護ロジックを解析されてしまった場合は意味が無くなってしまいます。また、必ずしも改ざんを防ぐ必要はなく、改ざんされた際に確実に検知出来るようなシステムがあっても良いですが、同様にバイナリ解析でロジックを回避されると無力です(解析耐性のあるバイナリを生成する必要があります)。

メモリ改ざんによって見つかった脆弱性を 2 つ紹介します。

ランキング機能におけるスコアチート

一人でプレイするゲームにおいてスコアをメモリ改ざんすることが出来たとします。あくまでそれだけなら外部への影響が少ないので危険度が低くなるのですが、それが例えば理論上取れないカンストしたスコアでランキング表示などされると、多くの人に不正なスコアが見えることになり、サービス的に打撃となるために危険度が高くなります。

ただ、ならば理論値を超える値は不正な値として弾けば良い、という問題でもなく、その場合は理論値ギリギリを狙って不正をされる可能性が高いです。

  • 途中経過の状態をサーバーに送信することで突然おかしな変化をしていないかどうかを確認する。あるいは後追いで検証出来るようにしておく。
  • 本来スコアを送信している箇所にスコアのみならずその計算に用いる値も送信し、再計算した結果が一致しているか確かめる。これにより改ざんすべき箇所が増える。

などの対策が考えられます。

放置系コンテンツにおける時間の改ざん

ゲーム中で探索・バイトなどの表現で実装されることのある、一定時間の経過によって報酬を受け取る機能があります。これの時間管理がローカルで行われていて、報酬受け取りが可能になる時間をUNIX時間としてメモリ空間を探索、現在時刻に改ざんすることで一瞬で報酬受け取りが出来る、という脆弱性が見つかることがあります。

元々メモリ改ざんが意図されていない構造だった、ということも出来ますが、サーバー側で報酬受け取りが可能になる時間を保持しておき、「報酬受け取りリクエストが正しいタイミングで送られているか」の確認が足りていなかった、という考えによっても解決します。

このように、メモリ改ざんによる攻撃であっても、リクエスト改ざんの対策によって防ぐことが出来る可能性があります。

その他の脆弱性

アプリ解析によるチート・不正

アプリ解析によってアプリ改ざん・リークといった被害に繋がる恐れがあります。以下、脆弱性の具体例です。

Unity(Il2Cpp)の難読化対策の不足

Unityで作成したAndroidアプリはデフォルトではglobal-metadata.datの中身を読み取ることで関数名やシンボル名を復元することが出来てしまいます。具体的に Il2CppDumper といったツールで復元・IDAやGhidraなどの解析ツールに適用することが出来ます。

これによってアプリ改ざんが容易になる可能性があります。

関数名やシンボル名の難読化をすることで読めたところで分からない、ということになる可能性はあります。

アセットデータの暗号化不備、セーブデータの改ざん

アセットデータが暗号化されていない、あるいは暗号化されているが復号鍵がローカルに保存されている場合もあります。抜かれても構わないようなデータであればいいですが、著作権的な観点で取られたくないデータ、未公開情報などの知られたくない情報には気を付ける必要があります。

また、対戦状況がローカルに保存されており、対戦状況の改ざんやロールバックが出来る場合もありました。この情報は簡単に読める状態でローカルに置いて問題ないかを確認する必要があります。

重要な情報のログ出力

例えばパケットを送信する前に中身をログ出力しておく、といったことをしている場合、パケットの暗号化し、解析しにくくするなどの対策の意味が薄れてしまいます。また、ログの内容をバイナリ上で検索することで、コード解読のヒントになってしまう場合があるので、不要なログは出力しないようにすることが大切です。

その他多様な脆弱性

自動化

iOSならスイッチコントロール、Androidならadbなどで自動操作をするものです。実際には自動化されても差し支えない程度の内容であるケースも多いのですが、PC版ではAIを用いたチート手法があり、非常に対策が難しいケースもあると考えられます。

ゲームの強制終了

特定のタイミングで強制的にゲームを落としたりSIGSTOPをすることでゲームを優位に進めることが出来ます。セーブバグと呼ばれるものを悪用した手法はここに分類されます。

このタイミングでゲームを強制終了したらどうなるだろうか?ということを少し考えてみると脆弱性の発見に繋がることがあります。

端末の時間操作による脆弱性

サーバー時間と端末時間のズレを検出せず、オフライン状態で端末時間を変更することでゲームを優位に出来る、というものです。

基本的に端末時間を使用しないというのが大切です。あるいはゲーム起動時に時間の逆行を検出することが出来ればペナルティを課すことが出来るかもしれません。

まとめ

ここまでDeNAのチート・脆弱性診断で見つかった脆弱性の例について触れていきました。この中で見つかった脆弱性の多くがバリデーション不足や改ざん対策の不足によるものが多いイメージです。ゲーム開発者が「どのような攻撃があり得るか」という観点をどんどん増やし、「今の実装方法はこのような攻撃があり得るのでは?」と思えるようになると、より堅牢なゲームを作ることが出来るのでは無いかと思います。

最後まで読んでいただき、ありがとうございます!
この記事をシェアしていただける方はこちらからお願いします。

recruit

DeNAでは、失敗を恐れず常に挑戦し続けるエンジニアを募集しています。