サービス紹介:FortniteやVALORANTはこうして狙われている!?Unreal Engine製ゲームのチートペネトレーションテスト
高度解析部アプリケーション解析課の伊藤と森木です。普段はAndroid/iOSのアプリ診断、Windows/macOSのデスクトップアプリ診断、ゲームチートペネトレーションテストを担当しています。
今回のブログでは、Unreal Engineが使用されているゲームに対して弊社がどのようにテストを行っているか、またどのような対策が考えられるかをお伝えしたいと思います。
※この記事は、クライアントサーバー型(DedicatedServer+Clients)で動作する、MMORPGやFPSゲームを想定しています。
Unreal Engineの仕様
Unreal EngineとはEpic Games社が開発したゲームエンジンです。C++で作られており、PC/コンソール/モバイル/VR/AR向けの高品質なゲーム開発ができます。Unreal Engineで作成された有名なゲームタイトルとしてFortniteやBLUE PROTOCOL、VALORANT等が存在します。これらのゲームで実際にどのような方法でチートが行われているかを理解するためには、Unreal Engine(以下、UEと表記)の仕様について把握する必要があります。
ブループリント
ブループリントとはUnreal Editor上でスクリプトをゲームに視覚的に追加していくシステムです。
ブループリントの設定(UEドキュメントから引用)
UEのコードは基本的にはC++ですが、ブループリントを使用することでC++のプログラマでなくてもUEのゲームを作成することが可能です。ブループリントはバイトコードに変換され、仮想マシン上で動作します。スクリプトという意味では、C++に組み込み可能なJavascript/Python/Luaと似たようなものです。EpicGamesはUEのソースコードをgithub上で公開しており、Object.hやScriptCore.cppを読むことでどのような命令があるか確認できます。
World
ワールドはゲームのインスタンスのようなものです。Level(マップ)、Actor(キャラクターなどのオブジェクト)やフィールド情報を含みます。
Level
レベルはワールドに存在します。MMORPG/FPSでいうところのマップです。レベルにはプレイヤー、モンスター、床や壁のフィールド等のオブジェクトが存在します。
Actor
アクタについてUEのドキュメントから引用します。
アクタは、レベル内へ配置可能な任意のオブジェクトです。
トランスレーション、回転、スケーリングなどの3D変形をサポートする汎用クラスです。アクタはゲームプレイ コード (C++ またはブループリント) を通して作成 (スポーン)、破壊することができます。C++ において AActor は全てのアクタの基本クラスになります。
アクタ はある意味、コンポーネント と呼ばれる特別なタイプの オブジェクト を保持するコンテナとして考えられます。様々なタイプのコンポーネントを使って、 アクタの移動、レンダリング方法を制御することができます。アクタのその他の主要な機能は、 ゲーム中にネットワーク上でプロパティと関数コールを レプリケーション することです。
キャラクターやモンスター、石や草木、マップ上のアイテムや宝箱はアクタと考えられます。ゲーム内に存在するオブジェクトは基本的に全てアクタであるので、チートテストを行う際には基本的にはアクタに対してアプローチすることになります。
コンポーネント(Component)
コンポーネントとはアクタの一部として含まれます。コンポーネントはワールドに存在しているオブジェクトではなく、アクタの機能の一部と考えられます。例えば、アクタに以下のようなコンポネントを追加することでアクタに機能を追加することができます
- アクタを光らせる
LightComponent
- アクタを回転させる
MovementComponent
- 音声を再生する機能を追加する
AudioComponent
例として、ゲームのキャラクターがスキルを持っており、そのスキルの機能がSkillComponent
として動作している場合、これらのクラスに対してアプローチすることでスキルの改竄が可能だと考えられます。
マルチプレイヤー
マルチプレイヤーを行う際には、ネットワーク間でデータの同期が必要です。UEではレプリケーションと呼ばれるシステムによってネットワーク間でデータの同期が行われます。UEではクライアントサーバーモデルが使用されます。
クライアントサーバーモデル(UEドキュメントから引用)
サーバはゲームのホストとしてゲームの状態を保持しています。マルチプレイヤーゲームが実際に行われているのはサーバー上です。クライアントはサーバー上で起きていることをシミュレーションします。例えばメモリ改竄でゲーム情報を変更しても、サーバーには反映されません。
ネットワークロールとネットワーク権限
アクタのネットワークロールによって、どのクライアントもしくはサーバーがそのアクタを操作する権限を持っているかが決まります。
ネットワークロール | 説明 |
---|---|
Authority | サーバー上のアクタはAuthorityであり、全てのアクタを操作可能です。 |
Simulated Proxy | サーバーが権限を持っておりアクタの操作はできません。 |
Autonomous Proxy | 一部機能をクライアントが実行可能です(ServerRPC)。 |
例えば、専用サーバー上で動作するMMORPGで考えてみます。自キャラクターとパーティーキャラクター、ボスキャラクター、フィールド上オブジェクト(罠)が存在するとします。
もちろん、サーバーは全ての権限を持つので全てのキャラクターの操作、マップの変更、罠オブジェクトの設置や破壊などができます。クライアントは自キャラクターのみ操作可能なはずです。この時、パーティーキャラクターのアイテムを使用したり、ボスキャラクターを操作したり、罠オブジェクトを破壊することができないように設計されるはずです。すなわち、クライアント上ではプレイヤーアクタがAutonomous Proxy、その他全てのアクタはSimulated Proxyに設定されていると考えられます。
UEでは、ネットワークロールによって権限を持たないアクタの変更が行われないように設計されています。つまり、専用サーバー上で動作するクライアントサーバー型のゲームでチート行為を行う際にはAutonomous Proxyのアクタが改竄できないか確認します。
レプリケーション
レプリケーションとは、ネットワーク内の異なるマシン間でゲームの状態を複製するプロセスのことです。変数やアクタの動き、アクタの作成や破壊、コンポーネントのレプリケーションが可能です。例えばサーバーでの変更をクライアントに反映する、クライアントの動作をサーバー上に反映させるといったプロセスは全てレプリケーションです。
リモートプロージャコール(RPC)
RPCとはネットワーク上の異なるマシンで関数を呼び出すための技術です。UEでは3つのRPCタイプをサポートしています。
RPCタイプ | 説明 |
---|---|
Server | ゲームをホストしているサーバー上で実行されます。 |
Client | アクタを所有するクライアント上で呼び出されます。 |
NetMulticast | サーバーに接続されているすべてのクライアント上およびサーバー上で呼び出されます。 |
例えば、自分の操作するキャラクターがスキルを使用する際にはServerRPCをクライアントからコールすることで、スキルの使用をサーバー上に反映します。
逆に、サーバー上でキャラクター情報の変更があり、キャラクターのMPが減少したとします。DecreaseMP
といったClientRPCをサーバーでコールすることでクライアント側にMP減少のイベントを通知できます。
最後の例として、クライアントが保持していないアクタである敵キャラクターがスキルを使用したとします。これらはNetMulticastRPCをサーバーで呼び出すことで全てのクライアント上で敵キャラクターのスキルをレプリケート可能です。
クライアントからRPCを呼び出し、サーバーで実行するにはそのアクタを所有していなければなりません。つまり、通常クライアントではAutonomous ProxyのアクタのServerRPCの呼び出しのみが機能します。
ゲームチートペネトレーションテスト
ここまでの情報を踏まえて、どのようなチート行為が可能かを考えてみます。UEではクライアントからAutonomous Proxyであるアクタを操作し、サーバーに動作を反映させることができます。そのため、Autonomous Proxyであるアクタを特定し、これらのレプリケーション部分に問題がないか確認することでゲームチートのテストが可能です。
また、サーバー上に反映することはできませんが、サーバーからレプリケートされたアクタやコンポネントの改竄を行うことでゲームが有利になるということが考えられます(罠オブジェクト、モンスターのヒット判定、壁や床の判定が自キャラクターのアクタで行われている場合等)。これらは、アクタやコンポネントの改竄、ClientRPC/NetMulticastRPC関数の呼び出しで可能です。
SDK Generator
UEでチートが行われる際にはSDKGeneratorというツールが使用される傾向にあります。UEでは、ブループリントやレプリケーションの仕様上、メタデータが必要になります。例えば、ブループリントから関数の呼び出しを行う際には関数の名前もしくはオフセットが分からないといけませんし、クラス内の変数にアクセスする際には変数の型とオフセットが分からないとアクセスできません。難読化ツールを使用していないJavaコードをデコンパイルしたときにクラス名や変数が残存しているのとよく似ています。
SDKGeneratorを使ってUEのクラス情報をダンプすると以下のようなクラスや変数とそのサイズ、関数を取得可能です。
SDKGeneratorで生成されたクラス
SDKGeneratorでダンプされた情報は、チートプログラムの開発に利用されます。ブループリントはゲーム開発において便利な機能ですが、その性質上、こういった情報がバイナリに含まれてしまうことに注意が必要です。
独自ツール開発
弊社では、Unreal Engineを対象としたゲームチートペネトレーションテストを効率的かつ効果的に行うために以下のような機能を持つツールを作成しました。
- レベル内に存在するアクタをネットワークロールごとに表示させるツール
- RPCやブループリントの呼び出しログを可視化するツール
- RPCやブループリントの呼び出しをGUIから行えるツール
実際にアプリ作ってテストしてみた
開発した独自ツールを実際のゲームに使用している様子を紹介します。今回は対象として、EpicGamesが提供しているサンプルゲームLyra Starter GameをDedicated Server用にセットアップしました。
https://dev.epicgames.com/documentation/ja-jp/unreal-engine/setting-up-dedicated-servers-in-unreal-engine?application_version=5.3
Unreal Engineのビルド
Dedicated Serverを使用するには、Unreal Engineをソースからビルドする必要があります。
https://dev.epicgames.com/documentation/ja-jp/unreal-engine/downloading-unreal-engine-source-code?application_version=5.3
診断の様子
レベルとネットワークロールごとにアクタをツリー表示可能にし、現在ゲーム内にあるアクタ/コンポネントを効率的に確認できるようにしました。
Lyraのレベルとアクタを表示している様子
また、RPCの名前やパラメータをログとして表示させることができるようにしました。
LyraのServerRPCのログ
診断では、これらの情報を参考にアクタやコンポネント、RPCに問題が無いかを検証します。今回ツールを開発したことで、リアルタイムですべてのRPC通信を把握できるため、ソースコードを頂かないブラックボックス診断においても効率的にテストが行えるようになりました。
今回はデモとしてサンプルゲームであるLyra Starter Gameでの動作の様子を紹介しましたが、実際に弊社に依頼されたゲームチートペネトレーションテストにおいても、このツールは非常に役立っており、リスクの高い問題を発見することができています。
診断の課題
アプリケーション解析課では、効率的に、均一な品質で診断業務を行うため、上述のようなツールを日々開発しています。しかし、実際の診断においては、個別の案件ごとに対応しなければならないため以下のような課題が存在しています。
ゲームエンジンのバージョンやコンパイラによる差異
SDKを出力する際に、ゲームエンジンのバージョンやコンパイラの設定によっては動作しないことがあり、それぞれのバイナリに合わせたSDKGeneratorの作成が必要なため診断までに時間がかかってしまう場合があります。
ブループリントのデコンパイル
ブループリントの処理を確認するためには、ブループリント用にコンパイルされたバイトコードを読む必要があります。バイトコードを取得し、定義された90個(UE5.3)の命令リストからディスアセンブルした後可読性の高い疑似コードに変換することで効率よくブループリントの処理を確認可能と考えられます。
UEにはディスアセンブラFKismetBytecodeDisassemblerがあるようなので、こちらを利用もしくは参考にすることで実現可能かもしれません。
こういった課題については、ツールで自動化するのは難しい面があり、診断者個人のスキルが必要です。私の部署には、これらの課題に対応できる優秀なメンバーがそろっています。
対策方法
ここまでUnreal Engineにおけるチートの概要と、弊社のゲームチートペネトレーションテストについて紹介しましたが、チートを防止するためにはどのような対策を施せばいいのでしょうか。ここでは有効と考えられる対策方法を紹介します。
ゲーム設計を見直す
ダメージ計算など、サーバーで行うべきものについてはサーバー上で処理することでゲームが解析されたとしても大きな影響を与えないような設計にすることが重要です。
バリデーションを適切に行う
クライアントから呼び出されるRPCについて、適切にバリデーションを行うことで不正な値をサーバに反映させないことが重要です。UEではUFUNCTION宣言文にWithValidationキーワードを追加することでRPCにバリデーション関数を追加できます。
クライアント解析対策を行う
SDK Generatorの対策や、アンチデバッグ機構、アンチチートツールの導入などが考えられます。こちらについては、一時的な効果に限定される可能性もあります。
信頼性の高いフレームワークを使用する
Gameplay Abilities等、Unreal Engineで用意された信頼性の高いフレームワークを使用することで脆弱性が生まれづらくなると考えられます。
おわりに
前節ではチートの対策方法を紹介しましたが、これらの対策を適切に施すのは難しく、特に設計については気を付けていても見落としが発生しやすいです。弊社のゲームチートペネトレーションテストでは、専門家の観点から、そういった見落としがないか、適切なセキュリティ対策が施せているかを検証します。
また、アプリケーション解析課では、ゲームチートペネトレーションテストの他にもモバイルアプリ診断(Android/iOS)、デスクトップアプリ診断を行っています。是非お気軽にご相談ください。