mcpField
A
analyzing-rust-binaries

analyzing-rust-binaries

github.com/trilwuセキュリティ17751 回インストール12 週間前に更新
リポジトリ

analyzing-rust-binaries について

マルウェア、CLI、コンパイル済みサービスなどの Rust バイナリを調べるリバースエンジニアリング向けスキルです。panic 文字列、シンボル、依存関係のパスを使って独自コードを特定し、逆アセンブル時の Rust 特有の構造を解説します。

主な機能

  1. 01panic 文字列からソースパスを特定
  2. 02Cargo 依存関係とビルド情報の確認
  3. 03旧形式と v0 シンボルのデマングル
  4. 04プロジェクトコードと標準ライブラリの識別
  5. 05Result、Option、単相化のパターン認識
  6. 06unsafe、FFI、デシリアライズのリスク確認

活用シーン

  1. 01シンボルを削除した Rust 実行ファイルの分析
  2. 02Rust マルウェアやインプラントの調査
  3. 03コンパイル済みバイナリのセキュリティレビュー
  4. 04サービスのネットワーク処理やエラーパスの追跡

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/trilwu/secskills --skill analyzing-rust-binaries

インストール後、Claude Code で/analyzing-rust-binariesを入力して読み込まれたか確認します。

詳細

Rust バイナリの分析

Rust バイナリはサイズが大きく、静的リンクされ、インライン化されたジェネリックコードで満ちていますが、多くの人が想像する以上に多くの情報が残されています。パニック箇所にはソースファイルのパスと行番号が埋め込まれ、シンボルにはクレートやモジュールの構造が含まれ、標準ライブラリのフォーマット処理機構はすぐに見分けられます。重要なのは、どの部分が独自のもので、どの部分が core、alloc、ベンダー提供のクレートからなる90%なのかを把握することです。

使用する場面

  • strings に rustc version、core::panicking、/rustc/<hash>/library/ が表示される
  • シンボルが _ZN...17h<hex>E(旧方式)または _R(v0マングリング)で始まる
  • ストリップ済みバイナリのサイズが3~30 MBで、動的インポートが最小限である
  • Rust マルウェア、Rust CLI、またはコンパイル済み Rust サービスを分析する

使用しない場面

  • Go バイナリ — analyzing-go-binaries を使用してください。シンボルの復元方法がまったく異なります
  • .NET または Unity — 該当するスキルを使用してください
  • マルウェアの疑いがあり、隔離前の場合 — 先に analyzing-malware を使用してください
  • ソースが入手可能な場合 — Rust の unsafe チェックリストがある auditing-code-for-vulnerabilities を使用してください

確認とフィンガープリントの取得

strings -n 8 target | rg -m8 'rustc version|/rustc/[0-9a-f]{40}|core::panicking|cargo/registry'

逆アセンブルする前に、次の2つの手がかりから大部分を把握できます。

パニック文字列からソースツリーがわかります。 Rust は、すべての panic!、unwrap()、境界チェックについて、ファイルパスと行番号を埋め込みます。これにより、クレートの配置、開発者のディレクトリ構造、そして多くの場合はプロジェクト名もわかります。

strings -n 10 target | rg 'src/[a-z_/]+\.rs' | sort -u | head -40
# → src/main.rs, src/crypto/aes.rs, /home/dev/projects/implant/src/c2.rs

レジストリパスから依存関係がわかります。 コンパイルに含まれたベンダー提供のクレートは、パニック箇所に ~/.cargo/registry/src/.../<crate>-<version>/ のパスを残します。これは実質的に依存関係の一覧です。

strings target | rg -o 'cargo/registry/src/[^/]+/([a-z0-9_-]+-[0-9.]+)' -r '$1' | sort -u

モジュールの配置と依存関係の一覧を組み合わせれば、通常は命令をひとつも読む前にバイナリの動作を把握できます。

マングリングの解除

Rust には2種類のマングリング方式があり、ツールはその両方に対応する必要があります。

# Legacy: _ZN4core3fmt5Write9write_fmt17h<16 hex>E
nm -C target | head -30
rustfilt < symbols.txt

# v0 (RFC 2603): starts with _R, encodes generics and paths properly
rustfilt      # handles both
# Ghidra 11+ and IDA 8+ have v0 demanglers; enable Rust demangling in the
# analyzer options rather than reading raw symbols
# Filter to your target's code. Everything under core::, alloc::, std::, and
# a registry crate name is stock.
nm -C target 2>/dev/null | rg -v '^.* (core|alloc|std|hashbrown|serde|tokio)::' | head -40

シンボルが完全にストリップされている場合は、パニック文字列を手がかりにします。各文字列は、それを含む関数の近くにあるため、パニックメッセージと既知のソースパスを照合すれば、周辺の関数名がわかります。

##逆アセンブラーで Rust を読む

混乱の大半は、次の5つのパターンで説明できます。

単相化。 ジェネリック関数は具体的な型ごとにコンパイルされるため、Vec<u8>::push と Vec<String>::push は、似た本体を持つ別々の関数になります。重複する関数があることを想定し、ほとんど同じ関数が2つあっても、コピー&ペーストされたものだと思い込まないでください。

Result と Option の戻り値。 これらは値渡しで返される列挙型で、多くの場合、判別値とペイロードの2つのレジスターに格納されます。Result<T, E> を返す関数は構造体を返しているように見えます。呼び出し直後に最初のレジスターを検査する分岐が、エラーチェックです。これを認識できるとエラーパスが読みやすくなります。そして、エラーパスにはバグが潜んでいることがよくあります。

あらゆる場所にある境界チェック。 スライスのインデックス参照ごとに、比較と、パニック箇所への条件分岐が生成されます。これらは逆アセンブル結果を見づらくするため、読み飛ばせるようになってください。境界チェックがない場合は重要な手がかりであり、unsafe または get_unchecked が使われていることを意味します。

トレイトオブジェクトのディスパッチ。 dyn Trait の呼び出しは vtable を経由します。つまり、データと vtable のポインターのペアを使い、固定された vtable オフセットへの間接呼び出しを行います。C++ の場合と同じように、vtable を復元すれば具体的な型を特定できます。

文字列の処理。 Rust の String と &str は、Go とまったく同じく、終端文字のないポインターと長さの組み合わせです。strings の出力では隣接するリテラルがつながって表示されます。ポインターの読み込みの隣にある長さ定数を探してください。

重要なコードを見つける

# Panic paths point at your code; use them as anchors
strings -t x target | rg 'src/' | head -30      # offsets included
# Cross-reference an offset in the disassembler to land in the owning function

# Crypto and network crates are recognizable by their panic paths
strings target | rg -i 'ring-|rustls|openssl|aes-gcm|chacha20|reqwest|hyper|tokio'

サービスやインプラントを分析する場合は、次の手順が最短です。まず非同期ランタイムを特定します(ほとんどの場合 tokio です)。次に、パニックパスからリクエストハンドラーまたは C2 ループを見つけ、その周辺を読み進めます。

Rust固有のセキュリティレビュー

挙動ではなくバグを探している場合:

  • unsafe ブロックにはメモリ安全性のバグが潜んでいます。バイナリでは、インデックス参照時に境界チェックがない箇所や、シンボルが残っている場合は from_raw_parts / transmute の呼び出し箇所を探してください。
  • 整数オーバーフローはデバッグビルドではチェックされますが、リリースでは黙ってラップアラウンドします。リリースバイナリの算術演算にはオーバーフロー時のトラップがありません。
  • **攻撃者が制御できる入力に対する unwrap() / expect()**はリモートパニックにつながります。サービス拒否であり、ネットワークサービスでは実際の脆弱性として報告すべきものです。
  • FFI 境界。 ポインターを受け取る extern "C" 関数では、Rust の保証が及ばなくなります。
  • デシリアライズ。 serde を使って、コンストラクターでは不変条件が適用されるものの、Deserialize では適用されない型にデシリアライズする場合です。

ソースコードがある場合は、auditing-code-for-vulnerabilities と cargo geiger を使用してください。

Rustマルウェアに関する注意点

Rustは、主にクロスコンパイルと解析者の負担を増やせることから、ランサムウェアやローダーでの使用が増えています。それでも、次の点は役立ちます。

  • パニック経路から開発者のビルド環境が漏れることがあります。ユーザー名、プロジェクト名、ディレクトリー構造など、帰属分析に役立つ情報が含まれます。
  • レジストリーパスから依存関係の一覧を特定できます。ネットワーク関連や暗号関連のクレートが分かれば、コードを読む前にC2プロトコルや暗号化方式を絞り込めます。
  • バイナリ内の rustc のバージョンからビルド時期が分かります。
  • 徹底的にシンボルを削除したサンプルでも、panic=abort と強力な strip を使ってコンパイルしない限り、境界チェックによるパニック箇所が残ります。それらもなくなっている場合は、意図的な解析妨害策として記録してください。

封じ込めとIOCの作業は analyzing-malware に任せてください。

退けるべき合理化

  • 「シンボルが削除されているから、何も復元できない。」 パニック文字列はシンボル削除後も残り、ソースファイル名を示します。
  • 「関数が何千個もある。」 そのほとんどは core / alloc /ベンダー提供クレートです。逆マングル化したパスで絞り込んでください。
  • 「この2つの関数は同じだから、コピー&ペーストだ。」 1つのジェネリック関数がモノモーフィゼーションされたものです。
  • 「逆コンパイラーに表示された構造体の戻り値の意味が分からない。」 Result / Option です。呼び出し後の判別値の検査がエラー分岐です。
  • 「Rustはメモリー安全だから、メモリーバグはない。」 unsafe と FFI が存在し、ロジックのバグは借用チェッカーの影響を受けません。
  • 「文字列が壊れている。」 Rustの文字列はポインターと長さで構成され、ヌル終端ではありません。

参考資料

  • analyzing-binaries — 一般的なトリアージ、動的解析、解析妨害
  • analyzing-go-binaries — 静的リンクされていてサイズが大きい、もう1つのケース
  • analyzing-malware — Rustサンプルの封じ込めとIOC
  • auditing-code-for-vulnerabilities — ソースコードがある場合のRust unsafe チェックリスト
  • rustfilt、Ghidra/IDA用Rust逆マングラー、cargo-geiger、Binary Ninja

コメント