dwgx@blog:~$dwgx
> cd ../posts

VRChat 向けに IL2CPP ランタイム逆向フレームワークを書いた

// created

vrc-runtime-re は VRChat クライアント向けの IL2CPP ランタイムメモリ逆向フレームワークで、プライベートリポジトリ、完全に授権された研究用途だ。目標は、Unity が IL2CPP にコンパイルされた後、メモリ上で Il2CppClass / MethodInfo / FieldInfo という構造が実際どう並んでいるのかをはっきり見ること。VRChat はさらに Beebyte 難読化をかぶせていて、フィールド順序が全部シャッフルされ、バージョンごとに変わる。手作業でオフセットを合わせるのは消耗が激しいので、いっそツールにした。

核心の考え方を一言でいうと、バイトがどこから来るかと、バイトをどう解釈するかは、結合させるべきではない。だから分離する。MemoryBackend は生バイトを吐くだけで、統一契約は read / modules / regions。型付き読み取り、ポインタチェーン、C 文字列、モジュールベースアドレスの探索、pattern スキャンはすべて上位層に置き、バイトがどこから来たかには関知しない。同じ解析コードがそのまま三種類のバックエンドで動くわけだ。minidump は完全に標準ライブラリのみ、オフラインで EAC からは見えない。dump を一つ取ってきてじっくり噛み砕けて、アンチチートには見えない。これが一番よく使う。memprocfs は dump や VM スナップショットを読め、DMA/FPGA カードや Hyper-V ゲストメモリにもつなげる。frida はリアルタイムプロセスバックエンドで、代償として EAC を切る必要がある。

バイトの上に乗るのが Il2CppWalker で、ASLR 安全にモジュールベースアドレスを解決してから、class/method/field を走査する。レイアウトの知識はすべて Il2CppOffsets の一箇所に集めてあるので、バージョンを変えてもここだけ直せばいい。さらにその上に多数の探針があり、それぞれが一つの問いだけに答える。フィールド名/オフセット/型の復元、生きているインスタンスのスキャン、フィールド値の dump、参照をたどってオブジェクトグラフを N 層クロールする、といった具合だ。スナップショット差分器は、hook を一切かけずに、凍結した二つのスナップショットを IL2CPP オブジェクトレベルで直接 diff する。RAM スナップショット二つあれば EAC の下でも変化を見られる。

一番厄介なのは Beebyte が更新のたびにレイアウトを再配置することで、そのために il2cpp/autodetect.py を書いた。新しい build に対して自己修復的にオフセットを認識し直すもので、今のところ 6 月 5 日のベースラインで検証済み。流れとしては、EAC 安全な経路でメモリを取り、autodetect を走らせてオフセットを認識し、そのバージョン用の新しい Il2CppOffsets を生成する。バックエンドと walker は一行も変えずにそれで合う。

核心部分はサードパーティ依存ゼロ。minidump は標準ライブラリだけで済み、memprocfs / frida が要るときだけ対応パッケージを入れる。ターゲットは x64 Windows、リトルエンディアン。今のところバックエンド抽象、walker、オフセット自己検査、フィールド型の復元はどれも使える。インスタンススキャナはまだ磨いている最中で、復元したフィールド型はついでに自分のもう一つの静的去難読化プロジェクトに食わせている。文字列のクロスリファレンス、singleton 探索、統一 CLI はまだこれからだ。