
2026/06/03 5:27
Themida の静的バチャライズ化解除
RSS: https://news.ycombinator.com/rss
要約▶
Japanese Translation:
この記事は、CodeVirtualizer や Themida といった VM ベースのオビフスキエータで保護されたコードに対する堅牢な記号評価法について詳述し、その適用範囲を VMProtect、OREANS Themida、VXLang など他のツールのものまで拡大しています。脆弱なベンダー固有のトリックやパターンマッチングに依存した以前の手法とは異なり、このアプローチは VM 固有の知識を最小限に抑えることで、バージョンを超えた広範な互換性を確保します。核心的な技術は、ネイティブ命令をカスタム中間表現(IR)へと「リフト」することであり、この IR は AMD64 および ARM64 の両アーキテクチャをサポートしています。ほとんどのレジスターとフラグを記号的に扱いながらスタックポインタ(RSP)を早期に具体化することで、複雑な仮想マシンロジックをネイティブ操作へと最適化します。このプロセスは定数フォールディングを利用して VM の足場(デコード算術やディスパッチロジックなど)を圧縮し、分岐フォールディングを通じて未知の制御フロー経路を解決し、不要な複雑性を防ぐためにデッド依存関係を排除します。最終的にシステムはこの最適化された IR をネイティブコードへと「ローラー」し、レジスタスプレイを引き起こさずにバイナリにクリーンに再挿入します。結果として、この画期的な成果は業界標準の VM 保護を損ない、分析者がオビフスキエーションを効果的に回避することを可能にし、ソフトウェアセキュリティがリバースエンジニアリングへのアプローチ方を根本から変えます。
本文
Themida への脱仮想化(デバーチャル化)技術解説
はじめに
このガイドでは、CodeVirtualizer (Themida) によって保護されたコードの脱仮想化を実証する手法を説明します。紹介される技術はほぼすべての VM ベースのオブフスキエーションに対応しており、わずかな修正で他のプロテクター(VMP, VXLang など)にも応用可能です。
推奨予備知識
関連するバイナリ脱隠蔽(デオブフスキエーション)研究資料を事前に読み込んでください:
- Arxiv: Binary Deobfuscation
- DNa Project
- VMProtect 脱仮想化
- Mergen
- その他、Back Engineering Labs, Lifting Bits Remill, RetDE 等の最新研究も参照推奨。
対象プロテクター
この技術で削減可能な主な保護器:
- Themida (VMPSoft, Oleans など)
- VXLang
- EagleVM
- COVIRT
- BinProtect
- (注:網羅ではありません)
Themida のアーキテクチャ分析
Themida の仮想マシン(VM)アーキテクチャは、VMProtect と異なり、主にネステッドバチュアリゼーション(重層的な仮想化)に対応しています。
- 構造の違い: VMProtect はネイティブスタック上に VM を置くのに対し、Themida はバイナリ内部にVM 文脈や仮想スタックを持ちます。
- 重要コンポーネント: 脱仮想化において特に注意すべき点は以下の 2 点です。
- 仮想分岐
- VMEXIT の振る舞い (詳細なアーキテクチャ解説は他の研究成果を参照してください)
賢者への警告
パターンマッチングアプローチの限界
VM ハンドラを x86 インストラクションへパターンマッチングさせる手法は推奨しません。
- 脆弱性: プロテクターベンダーがハンドラ配置やディスパッチロジックを微細に変えるだけでツールチェーン全体が無効化されます。
- 本ガイドのアプローチ: 意図的にVM 固有の知識を最小限に抑え、広範なバージョンに対応する汎用性を確保しています。
必要な VM 知識の範囲
脱仮想化の大部分は一般的な最適化パスで行えますが、制御フロー(仮想分岐・仮想呼び出し)処理には VM 固有の理解が必要です。
ガイダンス付き記号的評価 (Guided Symbolic Evaluation)
基本コンセプト
- リフティング: ネイティブインストラクションを可塑性のある中間表現 (IR) に昇格します。
- 最適化による解明: 未知のブランチ先が最適化によって具体化(具象化)されるにつれ、制御フローを実体化します。
- リフティングツールの例:
- BLARE2 (Back Engineering Labs): AMD64/ARM64 サポート、カスタム SSA IR、最適化器、リンカ付き。バイナリへの clean な再挿入が可能です。
- Triton / Remill: クリーンで最適化された IR を生成する能力はありますが、バックエンド(ネイティブコード出力・再挿入)での調整が必要です。
リフティングの開始点
- すべてのレジスタとフラグは記号的な状態から始められます。
- IR が進化する停止条件: 次のインストラクションポインタが決まるまで(制御フローによって次の動作が確定するまで)リフティングは続行します。
命令: RSP への最後のストアが次の IP となる。ret- 具象化できない場合:最適化未完了、または仮想 JCC(複数の実行経路)が存在する場合。
具体的な実装パス
1. スタックポインタの具体化 (Concretizing Stack Pointer)
リフティング開始時、スタックポインタ (RSP) は具体的な初期値を与えます。
- 理由: RSP を具体的に保持することで、既存のロード/ストア伝播機械がスタックアクセスを自動処理し、算術に対して定数畳み込みを特殊なケース分けなしに実行できます。
- 代償: 動的スタック割り当て (
など) はサポートされません(スタックディスプロールメントが静的でなくなるため)。ただし、仮想化関数では稀であるため問題にはなりません。alloca
2. 最適化パス一覧
これらのパスは相互依存して固定点に収束します。各パスの出力は次へフィードバックされ、VM の足場を崩壊させます。
[定数昇格とメモリモデリング] (Constant Promotion & Memory Modeling)
- プロセス: メモリからロードされたデータ(例:バイナリティバイトコード)を定数へと昇格します。
- 荷上げ後、周辺の暗号算術への定数畳み込みが可能になります。
- ハンドラアドレスやテーブルインデックスも具象化され、具象的なハンドラアドレスのみが残ります。
- ストア伝播:
へのストアが発生すれば、以降のロードはその SSA 値をフォワードします。0x5000 - ポリシー: ロードが「VM の足場」ではなく「元のプログラム」として解釈されるリスク(過剰な昇格)を防ぐ範囲制限が必要です。
[定数の畳み込み] (Constant Folding)
- 式のすべてのオペランドが既知の定数であれば、結果で置き換えます(例:
→10 + 10
)。20 - 重要性: このパスは収束まで実行される必要があります。
- バイトコードデコード算術が消滅
- ハンドラテーブルインデックスが具象化
- ディスパッチロジックが消失
- これらが互いに連鎖し、VM 構造が崩壊します。
[デッドストアの除去] (Dead Store Elimination)
- 安全性: VM プライベートメモリ(文脈・仮想スタック)に限定されたストアのみ対象にします。これらは元のプログラムからは観測されないため、証明可能に「デッド」です。
- 効果:
- スキップすると、VM インタープリタの状態シャッフルが IR 内のダングリング表現として残り、ノイズになります。
- 除去により、実際に関数のように見える IR が生成されます。
[命令の結合] (Instruction Combination)
- 目的: 代数的同値性を認識し、IR を最小限に減らします。
- 効果: VM ハンドラに見られるノイズ(自己相殺算術、冗長なマスキング)を剥ぎ取ります。複雑な式は単なる定数へ減少します。
[ブランチの畳み込み] (Branch Folding)
- 最適化によりフラグ計算が定数化された場合、不透明なブランチターゲットは完全に除去できます(静的に知られた目的地へ分岐)。
3. VMEXIT の振る舞い (VMEXIT Behavior)
リフティング開始時の RSP が具体化されているため、以下のパターンを識別します。
- VMEXIT-CALL の検出:
- リフターが
の RSP でinitRSP - 0x10
を遭遇 → VMEXIT-CALL と判定。ret - 呼び出し先目標は RSP に、戻りアドレスは
に配置されます。RSP + 0x8
- リフターが
- 呼出先の扱い:
- コール型出口の場合:呼び出し先は記号的である可能性あり → 明示的に扱う必要があります。
- CET 準拠ではない点に留意(スタック上の戻りアドレスが
スタブで置かれていないため)。vmenter
- 出口分類:
- 元の関数へのリターン、サポート外指令退出、コール型出口は異なる RSP ディスプロールメント値を持つため、制御フローマッチロジックで分類します。
4. 仮想化された制御フロー (Virtualized Control Flow)
すべての仮想インストラクションポインタ (VIP) を記録・追跡する必要があります。
- 理由: バックエッジ(ループ検出)発見時、無限にアンロールさせるのではなくループとして認識するため。
- VMP vs Themida の違い:
- VMP: ビイトコードアドレスから直ちに次のハンドラアドレスをエンコード。
- Themida: 単純なヒューリスティクスは使えません。
- 条件分岐ハンドラ (
) では、結果がVJCC
に書き込まれます。branch_taken_flag - VIP はハンドラ終了後の条件付 VPC 更新ロジックを通じて追跡する必要があります。
- 条件分岐ハンドラ (
5. デッド依存関係解析パス (Dead Dependency Analysis Pass)
- 目的: 仮想化領域から「実際に出る」レジスタとフラグを特定します。
- 手法: VM EXIT 直後のネイティブコードで変更(クラッブ)されるレジスタ集合を収集し、それ以外をデッドとして除外します。
- 効果: 記号的表現が IR 内でダングリングすることなくアンカーを持ちます(例:ZF フラグの式木が不要になる)。
6. スタックポインタ書き換えパス (Stack Pointer Rewrite Pass)
- 目的: シンプル化された IR の定数スタックアドレスアクセスを、RSP 相対形式へ書き直します。
- 注意点: スタック参照は負でなければならない(Red Zone への到達を示す場合を除く)。
IR の低次元化 (Lowering IR)
クリーンな IR を得られた段階で、元の ISA(機械語)へ低次元化します。
- 処理内容: 指令選択、レジスタ割り当て、アセンブリ、バイナリへの連結。
- 制約: レジスタプレッシャー。
- スパイルが発生すると、復元されたコードに独自のスタックフレームができ、IDA や Binary Ninja で誤読されるリスクがあります。
- 目標: 元のコードに近い実行可能出力で、スパイルなし・余計なアティファクトなし。
- LLVM 製への懸念: LLVM IR は読みやすいですが、バイナリへの clean な再挿入(振る舞いの整合性)までは容易ではなく、各段階で戦わなければなりません。
結果
デモ比較
(画像の代わりに解説)
- 左: 仮想化前の元の関数 (IDA)
- 右: 脱仮想化後の同じ関数
特性確認
- 機能的同等性: 元のものともう 1:1 です。
- コード品質: バックエンドによる指令選択の違いはあるものの、スパイルは発生せず、元のコードに近い緊密でクリーンな出力です。
- 実行可能性: 回復された関数はネイティブコードとして実行でき、逆アセンブラで cleanly な読み込みが可能です。
リソース
検証や比較のために以下のリポジトリがあります:
記号的評価の防止 (Preventing Symbolic Evaluation)
オブフスキエーターが脱仮想化を阻む主な手法:
- 間接ジャンプ先の本質具象化阻止:
- ブランチ先が定数畳み込みで還元されないように、不透明な値を持つ MBA(マルチバイトアレー?文脈に依存)表現としてエンコードします。
- 強力な防御:
- これらの手法は単なる「隠蔽」を超えており、記号的評価を一般的に不可能にするレベルです。
などの製品が重い保護ティアで実装しています(詳細はこのガイドの範囲外)。CodeDefender