reversing-unity-il2cpp について
Unity IL2CPP アプリを調べるモバイルセキュリティ評価者向けのリバースエンジニアリングスキルです。ビルドの判別、メタデータの復元、静的・実行時分析、代表的な脆弱性の確認を扱います。
主な機能
- 01Mono と IL2CPP ビルドの判別
- 02メタデータとネイティブバイナリの照合
- 03Il2CppDumper と Il2CppInspector の利用
- 04Frida による実行時分析
- 05認証情報、通信、ローカル保存の確認
活用シーン
- 01Unity APK や IPA のアプリロジック分析
- 02API、認証、購入処理の特定
- 03保護されたビルドからのメモリ内メタデータ復元
- 04クライアント側ロジックや証明書ピンニングの評価
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ trilwu/ secskills --skill reversing-unity-il2cppインストール後、Claude Code で/reversing-unity-il2cppを入力して読み込まれたか確認します。
詳細
Unity IL2CPP のリバースエンジニアリング
Unity のビルド形式は大きく異なる 2 種類があり、どちらを使用しているかを特定することが最初の重要な判断です。Mono ビルドでは、逆コンパイル可能な .NET アセンブリが得られます。IL2CPP ビルドでは、C# が C++ を経てネイティブコードにコンパイルされ、C# のメタデータは別ファイルに格納されているため、内容を読み取るにはバイナリと再結合する必要があります。
使用する場合
- APK に
assets/bin/Data/Managed/Metadata/global-metadata.datとlib/arm64-v8a/libil2cpp.soが含まれている - APK に
assets/bin/Data/Managed/Assembly-CSharp.dllが含まれている(Mono ビルド) - IPA に
Frameworks/UnityFramework.frameworkが含まれている jadxでUnityPlayerActivityと Unity 関連のコードしか表示されない- ゲームやアプリのロジック、アンチチートの動作、または API 層を復元する必要がある
使用しない場合
- Flutter または React Native —
reversing-flutter-appsまたはreversing-react-native-appsを使用してください - 一般的なネイティブアプリ —
testing-mobile-applicationsを使用してください - Unity の兆候がないデスクトップゲームのバイナリ —
analyzing-binariesを使用してください - ライブマルチプレイヤーサービスでのチート作成やアンチチートの回避 — 対象外です。これは評価ではなく、サービスの不正利用です
ビルドを特定する
unzip -l target.apk | rg 'global-metadata|libil2cpp|Assembly-CSharp|libmono'
| 存在するもの | ビルド | 難易度 |
|---|---|---|
Assembly-CSharp.dll、libmono*.so | Mono | 簡単 — DLL を直接逆コンパイルします |
global-metadata.dat + libil2cpp.so | IL2CPP | 以下で説明する主な手順 |
どちらもないが UnityFramework がある | iOS IL2CPP | IL2CPP と同じです。復号した IPA から抽出します |
Mono ビルドなら、すぐに作業できます。 アセンブリを取り出して開きます。
unzip -j target.apk 'assets/bin/Data/Managed/Assembly-CSharp.dll' -d ./out
# dnSpy / dnSpyEx / ILSpy / dotPeek — full C# source recovery, and dnSpy can edit
ilspycmd ./out/Assembly-CSharp.dll -o ./decompiled
以下はすべて IL2CPP 向けです。
メタデータをバイナリと再結合する
IL2CPP では情報が分かれています。libil2cpp.so にはコンパイル済みコードが格納され、global-metadata.dat には C# の型システム(クラス名、メソッド名、フィールド名、文字列リテラル)が格納されています。どちらか一方だけでは役に立ちません。ツールは両者を照合し、シンボルを生成します。
unzip -j target.apk 'lib/arm64-v8a/libil2cpp.so' \
'assets/bin/Data/Managed/Metadata/global-metadata.dat' -d ./in
# Il2CppDumper: the standard first attempt
Il2CppDumper ./in/libil2cpp.so ./in/global-metadata.dat ./out
一般的な出力とその用途:
| 成果物 | 用途 |
|---|---|
dump.cs | アドレス付きのすべてのクラス、メソッド、フィールド — まずこれを読みます |
DummyDll/ | スタブ .NET アセンブリ。dnSpy/ILSpy で開くと、型システムを簡単に閲覧できます |
script.json | IDA/Ghidra 用のシンボルインポート。libil2cpp.so に名前を適用します |
stringliteral.json | 文字列定数:URL、キー、メッセージ |
Il2CppDumper が失敗する場合は、代替として Il2CppInspector を使用できます。対応する Unity バージョンの範囲が異なり、Ghidra および IDA 用スクリプトに加えて、より詳細な C++ ヘッダーを生成します。ビルドが非対応だと判断する前に、両方を試してください。
バージョンの不一致が、通常、失敗の原因です。 メタデータの形式は Unity のリリースごとに変わるため、ツールはバージョンを尋ねるか、推定します。まず Unity のバージョンを確認し、明示的に指定してください。
strings ./in/libil2cpp.so | rg -m3 '20[0-9]{2}\.[0-9]+\.[0-9]+'
strings ./in/global-metadata.dat | head -5
保護されたビルドでは、global-metadata.dat が暗号化またはスクランブルされているため、ヘッダーのマジック値が正しくなく、ダンプツールは処理を拒否します。兆候としては、ファイルの先頭が想定されるメタデータのマジック値と一致しない、またはエントロピーが全体的に高いことが挙げられます。実用的な方法は動的解析です。アプリ自身にメタデータを復号させ、読み込み後にメモリからダンプします。
# Dump the decrypted metadata from a running process
frida -U -f com.target.app -l dump-metadata.js
# Then feed the dumped file to Il2CppDumper as normal
アプリ自身に展開させてメモリからダンプする方法は、パックされたマルウェアの解析でも使われるもので、静的に保護を破るより、ほとんどの場合、手間がかかりません。analyzing-binaries を参照してください。
復元したコードを調べる
- 型の対応表として
dump.csを読みます。 関連するクラスを検索します。*Manager、*Service、*Api、*Network、*Auth、*Purchase、*Configなどの名前が付いたものを探します。 script.jsonを IDA または Ghidra に読み込み、libil2cpp.soにsub_1A2B3Cの代わりに C# のメソッド名が表示されるようにします。この手順を行わないと逆アセンブリは読めませんが、行えば逆コンパイルした C# のように読めます。stringliteral.jsonでエンドポイントとキーを確認します。 他のフレームワークと同様に、文字列定数はどのような処理を経ても残ります。dump.csのアドレスを使い、Frida で実行時にフックします。 メソッドのアドレスはlibil2cpp.soのベースアドレスからの相対値なので、モジュールのベースアドレスを特定し、オフセットを加算します。
// Pattern: resolve base, add the RVA from dump.cs, intercept
const base = Module.findBaseAddress('libil2cpp.so');
Interceptor.attach(base.add(0x1A2B3C), {
onEnter(args) { console.log('called with', args[1]); },
onLeave(ret) { console.log('->', ret); }
});
セキュリティ上重要な調査結果
単なる興味本位ではなく評価を目的とする場合、繰り返し見られる問題は次のとおりです。
stringliteral.jsonに認証情報やエンドポイントがハードコードされている — バックエンドのキー、分析用トークン、クラウドストレージの認証情報。- クライアントが決定権を持つロジック。 通貨、権限、検証が C# で行われ、サーバーがその結果を信頼しています。API を直接呼び出して確認してください。
- 証明書ピンニングが弱い、または実装されていない。 Unity のネットワーク層は許容範囲が広く設定されていることが多いため、
UnityWebRequestの使用状況を確認してください。 - ローカルのセーブデータや設定の改ざん。 サーバーが信頼する値を
PlayerPrefsやローカルのセーブファイルに保存しています。 - アプリ内購入のレシート検証がクライアント側で行われている。
退けるべき誤った説明
- 「jadx には何も表示されないので、保護されています。」 Unity アプリでは、設計上 dex に含まれるものはほとんどありません。
- 「ダンパーが失敗したので、ビルドは解析できません。」 別のダンパーを試し、Unity のバージョンを明示的に指定してください。メタデータが暗号化されている場合は、メモリからダンプしてください。
- 「IL2CPP はコンパイル済みなので、ロジックは安全です。」 メタデータは IL2CPP と一緒に同梱されています。それこそがこの形式の要点です。
- 「クライアントが購入を検証しています。」 つまり、購入は検証されていません。
- 「生の arm64 を読みます。」 まず
script.jsonを適用してください。同じ関数が、適用前は解読不能でも、適用後は簡単に読めるようになります。
参考資料
testing-mobile-applications— プラットフォームのストレージ、権限、ネイティブ層testing-apis— バックエンド。クライアントが決定権を持つロジックが調査結果につながる場所analyzing-binaries— メモリダンプと保護されたビルドの解析対策- Il2CppDumper、Il2CppInspector、dnSpy/ILSpy、Frida、IDA/Ghidra