
Rustバイナリ内で難読化が隠しているもの(文字列、クレート、ライブラリ関数、平坦化された制御フロー)を、実行せずに静的解析とUnicornエミュレーションで復元します。マルウェアのトリアージや保護されたアプリの監査に。ELF/PE/Mach-O、x86-64/ARM64/32ビット対応。
strings には何も見えない。FLOSS にも何も見えない。rust-deobf には C2 が見える。
rust-deobf は難読化された Rust バイナリが隠しているもの——C2 アドレス、ファイルパス、ミューテックス、レジストリキー、設定——を、サンプルを一切実行することなく復元します。
マルウェアが出荷されるのと同じ方法(opt-level="z"、fat LTO、panic=abort)でビルドされた、シンボルを削除した Rust プログラムで、8 つのインジケータが obfstr によって暗号化されています——文字列は構造体、match のアーム、比較、そして決して実行されない分岐の中に存在します。どれも出力されることはありません。
実際の実行(Linux x86-64、rustc 1.97)でキャプチャした出力からレンダリングしたものです。テキストは逐語的で、色とタイピングアニメーションは追加され、約 8 秒のエミュレーション待機は早送りされ、出力はデコードされた文字列に絞り込まれています。以下のプレーンテキスト出力は同じ実行のものです。
ファイルから分かること:
$ B=build/scatter_obfstr__elf__size-lto__strip
$ strings $B | grep -ciE 'northwind|10\.66|NW-5c1e9a|skip-update'
0
rust-deobf が復元するもの(静的解析 + Unicorn エミュレーション。バイナリは実行されません):
$ python3 scripts/recover.py $B
binary: scatter_obfstr__elf__size-lto__strip format=ELF arch=x86_64
static x86-64 (recover_callsite): 0 strings decoders=9 seconds=0.2
emulation (recover_emulate): 8 strings functions=416/416 timed_out=False workers=1 seconds=6.8
== decoded strings ==
10.66.12.7:8443 [ip]
function 0xf20c (emulation)
C:\ProgramData\NorthwindSvc\state.db [path]
function 0xf20c (emulation)
Mozilla/5.0 (Windows NT 10.0; Win64; x64) NorthwindAgent/2.1
function 0xf20c (emulation)
--skip-update-check
function 0xf443 (emulation)
Software\Northwind\Agent\Settings [registry]
function 0xf51b (emulation)
Global\NW-5c1e9a [mutex]
function 0xf5a8 (emulation)
https://telemetry.northwind.example/api/v3/beacon [url]
function 0xfd58 (emulation)
nw-agent::stage=collect
function 0xfd58 (emulation)
10秒以内に8/8、各文字列はタグ付けされ、それを構築する関数まで追跡されます。
ONLY=scatter_obfstr PROFILES=size-lto FORMATS=elf bash scripts/build_all.sh で再現できます
(出力はデコードされた文字列に絞り込まれています)。(入力は無害なベンチマーク
プログラムであり、埋め込まれたすべての文字列は bench_src/manifest.json に記載されています。)
Rustマルウェアは増加しており、その難読化解除ツールは事実上存在しません。FLOSS — 標準的なオープンソースの文字列難読化解除ツール — は、一般的なRust難読化クレートからは何も復元できません:
| 難読化ツール | strings | FLOSS | rust-deobf |
|---|---|---|---|
| litcrypt / litcrypt2 | 0/5 | 0/5 | 5/5 |
| obfstr(インライン化されたXOR+ADD) | 0/5 | 0/5 | 5/5 |
| cryptify(デフォルトキー) | 0/5 | 0/5 | 5/5 |
| muddy(ChaCha20-Poly1305 AEAD) | 0/5 | 0/5 | 5/5 |
| タイトなスタック文字列 | 0/5 | 0/5 | 5/5 |
| 手書きのXOR / ADD / SUB | 0/5 | 0/5 | 5/5 |
カナリアベンチマーク、リリースビルド、ELFおよびPE x86-64およびAArch64、ストリップ済みおよび未ストリップ。完全な表 (動的復元を必要とする1つのケース、カスタムキーを使用したcryptifyを含む)は 結果にあります。
実際のサンプルでは:EDDIESTEALER(PE) — 隠されたC2ドメインと URLパスを含む32個の難読化された文字列で、静的ツールとFLOSSは0個しか復元できません。AmnesiaStealer(macOS Mach-O) — C2外部送信URL、 暗号クリッパー設定、ステージングパス。詳細は以下。
文字列難読化解除 — 静的な呼び出しサイト復元(数秒)+ 実際のローダー、Cランタイム、パス探索、グローバルコンストラクタ再生を備えたUnicornエミュレーションエンジン。1つのmatchアーム内でのみ、または決して実行されない分岐上で構築された文字列を復元します。
ワンスクリーンのSOCトリアージ — soc_triage.py:フォーマット/アーキテクチャ、Rustツールチェーン、クレート機能フィンガープリント(ネットワーキング / 暗号 / 偵察 / 永続化 / ステージング)、タイプ別にグループ化されたインジケーター、およびその証拠を伴うキャリブレーションされたLOW / MEDIUM / HIGHシグナル。SIEM向けの--json、スクリプトによるゲーティング向けの--fail-on HIGH。--fast(メタデータ + リテラルインジケーター、デコードなし)はバイナリサイズに応じてスケールします:小さなサンプルでは1秒未満、20 MBのバイナリでは約20秒。
マルチアーキテクチャ、マルチフォーマット — ELF、PE、Mach-Oにわたるx86-64、ARM64、32ビットx86。
ワークフローに適合 — FLOSS互換のエクスポート(--floss-json)、ヘッドレスGhidraアノテーション(プレートコメント、ブックマーク、リネーム)、バイトシグネチャによるライブラリ関数の識別(ストリップされたバイナリでのlib_<name>)、ディレクトリに対するバッチモード(--jsonl)。
文字列を超えて — 設定/C2抽出、ハッシュ難読化されたAPIインポートの解決、アンパックされたステージ2の切り出し、制御フロー平坦化の検出および再構築(x86-64およびAArch64、goldberg、OLLVM -fla、実際のLLVM-18平坦化パスで検証済み)、Z3によるMBA / 不透明述語の簡略化、Rustトレイトオブジェクトのvtable復元(recover_types.py)、および最適化の逆変換(ジャンプテーブルのスイッチ解除、アンロールされたループの検出 — premium/opt_reverse.py)。
コード仮想化 — detect_vm.pyはVMProtect/Themida/TigressスタイルのバイトコードVMを検出します。プレミアムのdevirtualizerはそれらを元に戻します。ランダム化されたオペコードと暗号化された
オペランドを持つVMから、以下を再構築します:
return (((a0 > 1000) & (((((a0 * 31) + a1) ^ (a0 >> 3)) % 97) == 0)) ? (a1 + a0) : 0xffffffff);
— 関数の正確なソース。
設計上安全 — デフォルトで静的解析+エミュレーション:エミュレーションはUnicorn上でバイナリの関数を実行し、バイナリ自体は決して実行しない。動的(Frida)リカバリはオプトインであり、分離されたVM専用。
strip後も残るRustメタデータ — rustcのバージョン、正確なバージョンを持つサードパーティクレート、パニック発生箇所を、コンパイラがすべてのRustバイナリに残すソースパスから再構築する(cargo-auditableは不要)。
デコンパイラの文字列ビュー、strings、FLOSS — ファイルから文字列を読み取るものは何であれ、ファイル内に実際に存在するバイトしか表示できない。難読化クレートは平文が決してファイル内に存在しないようにするため、デコンパイラを読むAIエージェントはあなたと同じ「何もない」ものを見ることになる。rust-deobfは代わりに事実を提供する。
これはJSON出力を備えたCLIであるため、シェルコマンドを実行できるあらゆるエージェント(Claude Code、Codex、Cursor、…)は、すでに駆動しているデコンパイラツール群の隣でこれを呼び出せる:
python3 scripts/soc_triage.py sample.bin --json # one-screen facts: toolchain, crates, IOCs, signal
python3 scripts/recover.py sample.bin --json # every decoded string + the function that builds it
scripts/ghidra_run.sh sample.bin # write them into the Ghidra decompiler as plate comments
python3 scripts/mcp_server.py # or serve recover/triage/metadata/detect as MCP tools
CLI 解析よりも型付きツールを好みますか? scripts/mcp_server.py は detect / metadata /
triage / recover を stdio 経由の MCP ツールとして公開しており(サードパーティの
パッケージは不要)、エージェントは JSON スキーマでこれらを呼び出し、構造化された JSON を受け取ります。さらに
scripts/export_rules.py は復元されたファクトを capa ルールと YARA ルール(Mandiant の検出フォーマット)に変換します。
これは難読化クレート向け、および復元されたデコーダのイディオム/ハッシュ難読化されたインポートから生成されます — --static-only は任意のコーパス向けにクレートファミリーのルールセットを出力します。
うまく機能するプロンプト:
「rust-deobf から
python3 scripts/soc_triage.py sample.bin --jsonとpython3 scripts/recover.py sample.bin --jsonを実行し、それらの文字列が由来する関数を逆コンパイルして、それらが何をするのか説明してください。rust-deobf がデコードした文字列を ground truth として扱い、推論したものには仮説というラベルを付けてください。」
LLM ステップを組み込みたいですか? scripts/llm_triage.py は復元されたファクトのみ(生のバイナリは決して渡しません)をローカルモデル(Ollama / vLLM、RUST_DEOBF_LLM_BASE 経由 — マルウェアデータはホスト上に留まります)、Anthropic または OpenAI API に渡すか、オフライン利用向けにグラウンディングされたプロンプトを出力します。
git clone https://github.com/VAHPEM/rust-deobf.git
cd rust-deobf
python3 -m pip install -r requirements.txt # lief, capstone, unicorn, z3-solver
python3 scripts/recover.py path/to/binary # static + emulation (default), never runs it
python3 scripts/soc_triage.py path/to/binary # one-screen triage card
python3 scripts/recover.py path/to/binary --json # machine-readable, for agents and pipelines