data-protection

ゲームセーブファイルはどのように改ざんされるか — 保存データ保護の実践

「Gold: 9,999,999」— テキストエディタ一つでゲーム経済が崩壊する

何百時間ものプレイテスト、精緻に設計したアプリ内課金モデル——しかし攻撃者がローカルデバイスのセーブファイルを開き、通貨の値を数分で書き換えれば、それらは全て無意味になります。クライアントがディスクに保存されたデータを無検証で信頼する設計では、ゲーム全体の経済がたった一つのファイルにかかっています。

セーブファイル改ざんが特に危険なのは、高度なハッキングスキルや特殊なツールが不要なことです。一般的なテキストエディタや、rootedデバイスのファイルブラウザがあれば十分です。生み出された「超アカウント」はマルチプレイヤーの公平性を著しく損ない、正当なプレイヤーが課金する動機を失わせます。

この記事では、セーブファイルが実際にどのように改ざんされるか、クライアントサイドの暗号化だけでは不十分な理由、そして多層防御がどのように機能するかを解説します。

セーブファイルの保存場所と形式

Unityゲームでは、保存された遊戯データの物理パスはOSプラットフォームによって異なりますが、常にデバイスのファイルシステム上のどこかに存在します。

  • Android: 通常/data/data/<package-name>/files/または外部ストレージ内。標準デバイスではアクセス制限があるが、rootedデバイスとエミュレーターでは自由にアクセス可能。
  • iOS: アプリサンドボックス内に保存されるが、脱獄済みデバイスやサードパーティのバックアップ抽出ツールでアクセス可能。
  • PC(Windows): %AppData%\LocalLow\<CompanyName>\<GameName>\配下にファイルとして保存されるか、レジストリ(PlayerPrefs)に記録される。権限昇格なしでユーザーが自由にアクセス・編集可能。

形式はゲーム設計によってJSON、XML、バイナリ、Unityの組み込みPlayerPrefsなど様々です。暗号化や難読化なしでデータが平文で保存されていれば、攻撃者は構造を一目で把握し、任意の値を変更できます。

実際の改ざん手順

攻撃者の視点からセーブデータを操作する手順はシンプルで直感的です。

セーブファイルを特定 ──> 抽出・解析 ──> 値を変更 ──> 上書き ──> ゲーム再起動

Step 1: ファイル抽出と構造解析 rootedまたは脱獄済みモバイルデバイスやPCからターゲットファイルを抽出します。JSONやXMLファイルは基本的なテキストエディタで即座に読めます。バイナリ形式でも、特定の値を変えたときにどのバイトが変わるかを追うことで、ターゲットフィールドのオフセットを素早く特定できます。

Step 2: 値とフラグの変更 構造を理解した後、攻撃者は"gold": 1200"gold": 9999999に変え、キャラクターのレベルとステータスを水増しし、持っていない有料アイテムの所有フラグをtrueに変更します。

Step 3: 上書きと再起動 変更したファイルを元のシステムパスに書き戻し、ゲームを起動します。クライアントがファイルの完全性チェックを行わなければ、改ざんされた値がそのままメモリに読み込まれ、即座に有効になります。

単純なクライアントサイド暗号化が不十分な理由

「テキストエディタで読めないようにセーブファイルを暗号化すればいいのでは?」——これは自然な最初の疑問です。クライアントサイドの暗号化は多少の障壁を上げますが、単独の対策としては重大な構造的弱点があります。

ファイルの暗号化・復号に必要なキーとロジックは、ゲームクライアントの実行ファイルのどこかに存在しなければなりません。IL2CPPリバースエンジニアリングの記事で解説したように、攻撃者はglobal-metadata.datのダンプやランタイム解析を通じて、復号関数やハードコードされた暗号化キーを比較的簡単に抽出できます。キーが露出すれば、暗号化は不便なエンコードに過ぎなくなります。

また暗号化が完璧に機能していても、クライアントがランタイムにデータを復号してメモリに読み込んだ瞬間、メモリ改ざんツールが直接値を変更できます。単純な暗号化は「開けにくい鍵」であり、データ操作を防ぐ完全な検証メカニズムではありません。

有効なセーブデータ保護戦略

プレイヤーデータを保護するには、クライアントサイドの障壁を高めながら、単一の検証失敗でシステム全体が崩壊しない設計が必要です。多層(Defense in Depth)アプローチが求められます。

(1) サーバーサイドの権威(Source of Truth) コア通貨、プレミアム通貨、有料アイテム購入履歴、マルチプレイヤーランキングなどの重要データは、クライアントの申告だけで信頼してはなりません。サーバーのデータベースを権威ある情報源として扱い、差異が生じた場合はサーバーの値を優先します。ローカルセーブファイルは理想的には、ネットワーク遅延を減らすためのUIキャッシュとして機能するに過ぎません。

(2) ファイル完全性検証のためのHMAC署名 オフラインプレイをサポートするゲームでローカルセーブが必要な場合、サーバーに保存または何らかの方法で保護された秘密キーを使用して、セーブデータにHMAC(Hash-based Message Authentication Code)署名を付与します。クライアントはデータを読み込む際に署名を検証し、1バイトでも変更されたファイルは破損として拒否されます。

(3) クラウドセーブ(Steam Cloud等)互換のセキュリティ設計 よくある失敗は、ローカルファイルの不正コピーや改ざんを防ぐために、デバイス固有のハードウェア情報(Hardware ID)だけから暗号化キーを生成することです。これはセキュリティを向上させますが、デスクトップとSteam Deckを切り替えるプレイヤーのクラウドセーブ同期を完全に壊します。正当なクロスデバイス同期をサポートしながらセーブデータを保護するには、デバイスではなくユーザーアカウント(Steam ID等)に紐付けた完全性検証アーキテクチャが必要です。

(4) ファイルレベル保護とランタイムメモリ改ざん検出の組み合わせ ファイルレベルの保護(暗号化+HMAC)を通じて安全にデータが読み込まれても、ランタイムでメモリ値を操作しようとする試みは続きます。重要な数値フィールドは、ファイルが読み込まれた後に発生するメモリ上書き攻撃を追加で抑止するため、Nativeレイヤーでリアルタイムに監視する必要があります。

(5) サーバーサイドのサニティチェック サーバーは、達成可能な最大通貨量、時間あたりの成長上限、クエスト報酬範囲などを論理的に検証する必要があります。通常の範囲をはるかに超えた通貨を生み出すセッションは、異常シグナルとして扱われ対処されるべきです。

OZero Securityは、改ざんされたデータがゲームプレイに影響することを防ぐため、Native C++レイヤーで強力なランタイムメモリ値改ざん検出と環境完全性検証を提供します。また、Steam Cloudのようなクロスデバイス環境でファイルの完全性を保ちながらセーブデータを安全に同期するための柔軟なセキュリティアーキテクチャもサポートします。

まとめ

  • セーブファイルはroot化デバイスやPCなど直接ファイルシステムにアクセスできる環境で、高度なハッキングなしに簡単に変更できる。
  • データの暗号化は必須だが、キー露出とランタイムメモリ改ざんのリスクにより単独の防御としては不十分。
  • 主にオフラインのゲームでは、HMACシグネチャがファイル改ざんを厳格にブロックしなければならず——かつクラウド同期との互換性のために識別スキームはアカウントベースである必要がある。
  • ファイル改ざん後のメモリ上での二次操作経路をカバーするため、ディスクレベルの保護とランタイムメモリ検出を組み合わせることが、真に有効な防御を実現する。

ゲームのセーブファイルは、プレイヤーが投資した時間と努力の記録です。信頼が脆弱性にならないよう、慎重に設計・検証することが安全なゲームサービスの基盤です。

OZero SecurityのNativeレイヤー検出でセーブファイル改ざんと通貨操作を抑止。

あわせて読みたい