devirtualizing-vm-protected-code について
devirtualizing-vm-protected-code は、バイナリ内の独自仮想マシンに隠された処理を復元するリバースエンジニアリングスキルです。VMの特定からハンドラーの解析、バイトコードの抽出、命令のリフトと簡略化まで、段階的な手順を示します。
主な機能
- 01VMの入口、ディスパッチャー、出口を特定
- 02仮想コンテキストとオペコードの復元
- 03ハンドラーと命令の意味を対応付け
- 04仮想バイトコードを抽出
- 05命令をリフトして簡略化
- 06実行トレースとシンボリック実行を併用
活用シーン
- 01ライセンス確認や暗号処理を復元
- 02VMProtectやThemidaで保護された関数を解析
- 03VM全体ではなく対象経路を追跡
- 04バイナリのアンパック後に仮想化を解除
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ trilwu/ secskills --skill devirtualizing-vm-protected-codeインストール後、Claude Code で/devirtualizing-vm-protected-codeを入力して読み込まれたか確認します。
詳細
VM で保護されたコードのデバーチャライズ
仮想化難読化では、ネイティブ命令をバイナリに埋め込まれたカスタム仮想マシン用のバイトコードに置き換え、そのバイトコードをインタープリターで実行します。元のロジックは失われていません。まず復元する必要のある命令セットで表現されているだけです。デバーチャライズは決まった手順で進めます。VM を見つけ、ハンドラーを理解し、バイトコードを抽出し、読みやすい形式に戻します。難読化の方法はビルドごとに変わるため、特定のツールではなく、この手順こそが長く役立つスキルです。
使用する場面
- 関数が、通常の制御フローではなく、ハンドラーテーブルを使う大規模な
fetch-decode-dispatchループに変換されている場合 - VMProtect、Themida/WinLicense、Oreans Code Virtualizer、または独自のオペコード VM で保護されたバイナリを解析する場合
- 仮想化された関数内のアルゴリズム(ライセンスチェック、暗号ルーチン、アンチチートロジックなど)を復元する場合
- アンパック後、実際のコードが単にパックされているのではなく、仮想化されている場合
使用しない場面
- パックまたは保護されたバイナリのアンパック、ダンプ、インポートの修復には、
unpacking-protected-binariesを使います。こちらを先に行い、外側の保護を解除してから、明らかになった仮想化されたコアをデバーチャライズします。 - 通常の(仮想化されていない)難読化(ジャンクコード、不透明述語、文字列暗号化など)は、
analyzing-binariesで扱う一般的な作業です。 - 仮想化/難読化された JavaScriptには、
reversing-obfuscated-javascriptを使います。 - 復元したロジックのバグを悪用する場合は、
exploiting-memory-corruptionを使います。
手順
1. VM を特定します。 ネイティブコードから仮想コードへの切り替わりを見つけます。ネイティブコンテキストを保存して仮想マシンをセットアップする vm_enter スタブ、次の仮想オペコードを取得してハンドラーテーブルを介してジャンプするディスパッチャーループ、ネイティブコンテキストを復元する vm_exit を探します。ディスパッチャーが、それ以降の解析すべての基点になります。
2. VM のアーキテクチャを復元します。 VM のレジスターと仮想命令ポインターを保持する構造体である仮想コンテキストと、ディスパッチャーがオペコードをハンドラーインデックスにデコードする方法を特定します。VM の構成(スタックベースかレジスターベースか、オペコードのエンコード方式、バイトコードポインターに対するキーやローリング方式の難読化の有無など)を把握します。
3. ハンドラーを理解します。 各ハンドラーは、加算、読み込み、書き込み、XOR、プッシュ、ネイティブ関数呼び出しなどのマイクロ操作にあたる仮想命令を実装します。ハンドラーを個別に解析します。単一のハンドラーに対するシンボリック実行(Triton、miasm)を行うと、そのセマンティクスを手作業で読むよりはるかに速く把握できます。周囲の VM がどれほど大規模でも、ハンドラー自体は小さく、副作用に焦点を当てたものだからです。オペコードとセマンティクスを対応付けた表を作成します。
4. バイトコードを抽出します。 ハンドラーを理解したら、ディスパッチャーがたどる仮想命令ストリーム、つまり VM の命令セットで記述された実際のプログラムを読み取ります。
5. リフトして簡略化します。 仮想命令を IR に変換し、最適化パス(定数畳み込み、デッドコード除去、ピープホール最適化など)を実行して、冗長な VM 命令を元のロジックに近い形まで簡略化します。ここでVTILベースのツール(VMProtect x64 なら NoVmp も)が効果を発揮します。最適化可能な IR にリフトして簡略化することで、何千もの仮想命令を少数の実命令に変換できます。対象の VM に適した既製ツールがない場合も、Triton と miasm で同じリフトと簡略化の手法を利用できます。
静的解析と動的解析
ハンドラーは静的に解析できますが、動的実行トレース(特定の入力に対して実際に実行されたハンドラーの順序を記録すること)を使うほうが、特定の仮想化された関数を解析する近道になることがよくあります。どのハンドラーが重要で、どの順序で実行されるかがわかるため、VM 全体ではなく、実際に実行される経路をデバーチャライズできます。両方を組み合わせましょう。トレースで関連するハンドラーを見つけ、シンボリック実行でセマンティクスを把握し、トレースしたバイトコードをリフトします。
期待事項
- 万能なボタンはありません。 VMProtect と Themida では仕組みが異なり、バージョンによっても違いがあります。また、カスタム VM に共通点はありません。既製の逆仮想化ツールは特定のプロテクターのバージョンを対象としており、それ以外では動作しなくなります。パイプラインの代替ではなく、作業を加速するツールとして扱ってください。
- 全体を復元できなくても、核となる部分は復元できます。 バイナリ全体を逆仮想化する必要はほとんどありません。必要なのはアルゴリズムを含む関数だけです。対象を絞り込んでください。
- 多重化された VM や、変化する VM も存在します。 プロテクターによっては、仮想マシンの内部でさらに仮想化したり、ビルドごとにハンドラーを変化させたりします。一度に 1 層ずつ復元してください。
避けるべき思い込み
- 「ツールで逆仮想化できたので、もう終わりです。」 自動逆仮想化ツールは特定のプロテクターのバージョンに対応しており、それ以外では部分的または誤った出力を黙って生成することがあります。復元したロジックが、バイナリで観測された動作と一致するか検証してください。
- 「バイナリ全体を逆仮想化する必要があります。」 必要なのは対象の関数です。そこまでトレースし、実行される部分だけをリフトしてください。バイナリ全体の逆仮想化は、たいてい無駄な作業です。
- 「ハンドラーが大きすぎて読めません。」 ハンドラーは、大量の難読化で囲まれた 1 つのマイクロ命令です。ノイズを読むのではなく、シンボリック実行で意味を明らかにしてください。
- 「まだパックされているので、まず逆仮想化します。」 まずアンパックしてください(
unpacking-protected-binaries)。逆仮想化は展開されたコードを対象に行います。また、外側の層を取り除くまでは、アンチデバッグ/アンチダンプ機能に妨害されます。 - 「仮想命令ストリームが答えです。」 生のバイトコードからロジックを読み取ることはできません。リフトしたうえで簡約する必要があります。復元したオペコードをアルゴリズムに戻すのは、最適化パスです。
参考資料
unpacking-protected-binaries— 逆仮想化の前に、パッキング/アンチデバッグを取り除きますanalyzing-binaries— 復元・逆仮想化したコードを一般的な手法でリバースエンジニアリングしますreviewing-cryptography— 仮想化されたルーチンが暗号処理やライセンスチェックの場合に使用しますexploiting-memory-corruption— 復元したロジックに含まれるバグを悪用します