Skip to content
KitploitKITPLOIT
ツールエクスプロイトブログ
Log in
提出
ツールエクスプロイトブログ
提出

ハッキング、侵入テスト、サイバーセキュリティツールをあなたのセキュリティアーセナルに!

Kitploitはハッキング、サイバーセキュリティ、ペネトレーションテストのツールディレクトリです。最新のプロジェクトアップデートを見つけて、脆弱性の発見、システム分析、テストの自動化、セキュリティの強化を行いましょう。

フィードお問い合わせプライバシー© 2026 Kitploit

ツールディレクトリ

カテゴリ

すべてのカテゴリを見る
Loading categories
fnprint — バイナリ内の関数を、バイト列の見た目ではなく動作内容でマッチングします。マイクロ実行による挙動ベースの関数フィンガープリンティング。 | Kitploit
ツール/GitHubGitHub/1rhino2/fnprint
脆弱性分析動的コード分析 (DAST)リバースエンジニアリングマルウェア分析バイナリ解析ファームウェア解析
GitHub1rhino2/fnprint

fnprint

バイナリ内の関数を、バイト列の見た目ではなく動作内容でマッチングします。マイクロ実行による挙動ベースの関数フィンガープリンティング。

リポジトリを見る
5547012日前Kitploit レビュー済み
ウェブサイト

人気

すべて見る →

コミュニティで最も使われているツールを見つけましょう。

すべてのツールを探索

ツールコレクションを閲覧

すべてのツールを見る →
共有

fnprint

fnprintは、バイト列や制御フローグラフの見た目ではなく、関数が何をするかによってバイナリ内の関数をマッチングします。各関数を小さなエミュレータで偽の入力とともに実行し、生じる副作用を記録し、その振る舞いをフィンガープリントにハッシュ化します。同じ振る舞いをする2つの関数は、異なるコンパイラ、異なる最適化レベル、異なるCPU向けにビルドされていても、類似したフィンガープリントを得ます。

この方法を取る理由: バイトシグネチャ(FLIRT、FunctionID)はコードが再コンパイルされた瞬間に壊れ、CFGマッチャー(BinDiff、Diaphora)は-O0と-O3の間で不安定になり、アーキテクチャをまたぐと諦めてしまいます。振る舞いはそうしたすべてにはるかにうまく耐えます。学習データもモデルも不要、単一の静的バイナリだけです。

他にはない3つの特徴:

  • マルウェアに向けても安全です。 エミュレータは、exec、ソケット、ファイルオープン、実行可能メモリを完全に禁止するseccompジェイルの下で、権限分離されたワーカー内で動作します(unicornはno-JITインタプリタとしてビルドされているため、実行可能メモリを必要としません)。エミュレータを破ろうとする細工されたバイナリが得るのは、計算しかできないプロセスです。詳細はSECURITY.mdを参照してください。BinDiff、Diaphora、BSim、FLIRT、およびMLマッチャーはすべて、敵対的なバイト列をプロセス内でパースします。
  • アーキテクチャをまたいでマッチングします。 シンボルがあるx86-64ビルドをインデックス化し、ストリップされたaarch64ファームウェアに名前を付けられます。エフェクトモデルはISAを一切見ません。zlib x86-64 -O0 対 aarch64 -O0: 関数の97.7%が正しく命名されました。
  • triageは差分ではなく判定を返します。 2つのコーパス(既知の脆弱版、パッチ済み版)、マージン、そして脆弱側に傾く関数のレビューキュー。これがn-dayワークフローを1コマンドにしたものです。

x86-64およびaarch64 ELF。信頼する前に限界を参照してください。

デモ

fnprintがストリップされ、異なるコンパイルがされたバイナリ内の関数を命名する様子

ストリップされたバイナリと、すでに名前が分かっているもののコーパスを指定します:

$ strip --strip-all mystery.so
$ nm mystery.so
nm: mystery.so: no symbols

$ fnprint index libz.so -o corpus.db          # a build you have symbols for
$ fnprint query mystery.so --corpus corpus.db --threshold 0.6
named 27 function(s):
  addr        sim    graph  name
  0x000022f9  100.0%     0%  adler32_z  (libz.so)
  0x00002a8d  100.0%     0%  compress2  (libz.so)
  0x00004500  100.0%     0%  deflateStateCheck  (libz.so)
  0x0000ad5d  100.0%     0%  inflateBackEnd  (libz.so)
  0x0000b69c  100.0%     0%  inflateStateCheck  (libz.so)
  0x00012f8c  100.0%     0%  _tr_tally  (libz.so)
  ...

この実行は、完全にストリップされた-O0ビルドを-O2コーパスから命名したものです。最適化レベルが異なり、シンボルはゼロ、27の名前が返り、27すべてが正解です。自信があるものには名前を付け、それ以外については黙っています。(上のgifは0.5のもので、9個見つけました。0.6では、ストリップされた.soがエクスポートの背後に隠している静的関数も復元します。)

同じことをCPUをまたいで行います。コーパスはx86-64ビルドから、ターゲットは同じソースのaarch64ビルドで、シンボルは消えています:

$ fnprint index libz_x86_64.so -o corpus.db
$ fnprint query libz_arm64_stripped.so --corpus corpus.db
named 42 function(s):
  addr        sim    graph  name
  0x00001af4  100.0%     0%  adler32_z  (libz_x86_64.so)
  0x0000263c  100.0%     0%  compress2  (libz_x86_64.so)
  0x00002958  100.0%   100%  x2nmodp  (libz_x86_64.so)
  0x00002a98  100.0%     0%  crc32_z  (libz_x86_64.so)
  0x000034c4  100.0%   100%  crc32_combine64  (libz_x86_64.so)
  ...

42個が命名され、42個すべてが正解、異なるCPU向けにビルドされたコーパスからです。

もう1つは、2つのビルドを差分し、どの関数の振る舞いが変わったかを教えてくれることです。ベンダーが新しいファームウェアを出荷し、実際に何が変わったのか知りたいときに便利です。シンボル名があってもなくても動作します。ある場合は名前でアラインし、ない場合は振る舞いとコールグラフで2つのビルドをアラインするので、完全にストリップされた2つのファームウェアイメージでも差分が取れます:

$ fnprint match old.so new.so
aligned 38 function pairs by behavior + call graph (no symbol names needed)
  unchanged:  31
  changed:    7
  low-signal: 57 (too small to judge)

changed behavior (lowest similarity first):
   30.5%  compress_block
   43.0%  deflate_rle -> deflate_huff
   68.8%  deflate_slow

実際のn-day作業にはtriageがあります。ある関数の既知の脆弱版から1つのコーパスを、パッチ済み版からもう1つをビルドし、未知のビルドを両方に対してランク付けします。脆弱側に近く、パッチ側から明確に分離されている関数こそ、人間の前に出すべきものであり、解釈が必要な単一のマッチスコアではありません:

$ fnprint index vuln.so    -o vuln.db
$ fnprint index patched.so -o patched.db
$ fnprint triage mystery.so --vuln vuln.db --patched patched.db
43 functions triaged: 1 look vulnerable, 0 patched, 42 inconclusive

review queue (vuln-leaning, strongest first):
   addr         vuln%  patched%  margin  matches
   0x000022f9  100.0     27.3   +72.7  adler32_z vs crc32_z

両バージョンで同一である42の関数は、意図的にinconclusiveとして返されます。どちら側にも固定できず、フラグを立てるべきではないからです。--marginと--min-simは、確定する前に両側がどれだけ強く分離する必要があるかを制御します。

仕組み

各関数について:

  • バイナリをマップし、引数レジスタにジャンクを入れて関数にジャンプします。
  • セットアップしていないメモリからの読み取りは決定論的な値を返し、そのページはオンザフライでマップされます。ワイルドポインタが実行をクラッシュさせることはなく、同じ入力は常に同じトレースを与えます。これはGodefroidのマイクロエグゼキューションのトリックです。
  • 他の関数への呼び出しはスタブ化され(記録してからスキップ)、libcに踏み込むことはなく、実行はこの関数に集中したままです。
  • アーキテクチャ中立なエフェクトのストリームをログします: どの引数バッファと構造体フィールドを読み書きしたか、どの値クラスを書き込んだか(入力のコピー、小さな定数、ポインタ)、行った呼び出し、取った分岐、返り値。絶対アドレスは捨てられ、オフセットと形状だけが保持されます。
  • そのストリームはシングルに変換され、minhashシグネチャになります。類似度は一致するminhashスロットの割合で、2つの関数の振る舞いがどれだけ重なるかを推定します。LSHバンドインデックスにより、クエリがすべてをすべてと比較することを防ぎます。
  • 呼び出しはインポートテーブル(pltスタブ、gotスロット)からのみ命名されます。それがstripを生き残る部分だからです。内部呼び出しは出力では匿名となり、代わりに本体の静的スイープからコールグラフのエッジになります。
  • 命名と差分は、関数ごとのベストヒット検索ではなく、グローバルな1:1アラインメントです: コーパス関数は一度だけ要求でき、最良のペアが先で、その後割り当てはコールグラフ項(ペアの呼び出し元と呼び出し先のうち、互いにペアになっているものがいくつあるか)で2回再スコアされます。これが2つの振る舞いの双子を見分け、同じ状態チェックをすべてインライン化した27の-O2関数がすべてそれを要求するのを防ぎます。

学習データもモデルもありません。同じアイデアは文献ではBlanket Execution (Egele et al, USENIX Security 2014)として登場します。fnprintはそれを実用的かつメンテナンスされた形にし、実際に使えるCLIを備えたものです。エフェクトモデルは構造上アーキテクチャ中立であり、だからこそ同じソースのx86-64とaarch64のプリントが、何も学習せずに一致します。

インストール

rustツールチェーン、cmake、Cツールチェーンが必要です。unicorn(no-JITフォーク)とcapstoneはcargoによってソースからビルドされ、aptでインストールするものはありません。

cargo install --path cli
# or just
cargo build --release   # binary at target/release/fnprint

使い方

fnprint index <binary> [-o out.db]      fingerprint every function, optionally to a db
fnprint match <a> <b>                   diff two binaries (or .db files) by behavior
fnprint query <target> --corpus <db>    name unknown functions from a corpus
fnprint triage <t> --vuln <db> --patched <db>   rank a build against vuln vs patched corpora
fnprint eval <a> <b>                     accuracy metrics using symbol names as truth
fnprint dump <binary> <func>            print the recorded effect trace (debugging)
fnprint completions <shell>             print a shell completion script to stdout

matchとqueryは、ELFまたはindexでビルドした.dbのどちらでも受け取れるので、コーパスを一度フィンガープリントして再利用できます。index -oは追記するので、1つのコーパスに複数のバイナリを保持できます。

match --alignは、シンボルがあるペアに対して名前なしアラインメントを強制し、振る舞いのみのビューが何を言うかを確認できます。--graph-weight(グローバル、デフォルト1.0)は、アライン時に一貫したコールグラフ近傍がペアをどれだけ持ち上げるかを設定します。0は各関数を自身の振る舞いのみでスコアします。

match、query、triageは--limit Nを受け取り、人間向けテーブルが出力する行数を制限します(0 = すべて)。jsonとr2出力は決して制限されないので、スクリプトはすべてを取得できます。

completionsはbash、zsh、fish、powershell、またはelvish用のスクリプトを書き出します。例: fnprint completions bash > ~/.local/share/bash-completion/completions/fnprint。

出力を早期に読み取りを止めるもの(| head)にパイプすると静かに終了するので、テーブルは--limitなしで安全にざっと見られます。

マシン出力

すべてのコマンドはグローバルな--formatを受け取ります:

fnprint query mystery.so --corpus corpus.db --format json > names.json
fnprint query mystery.so --corpus corpus.db --format r2   > fnprint.r2

jsonはスクリプト(およびcontrib/のGhidraインポートスタブ)向けの安定したスキーマで、r2はrizin/radare2内で. fnprint.r2として実行するafnリネームコマンドを出力します。ターゲットからのシンボル名はどちらかに到達する前にサニタイズされるので、細工された名前がr2コマンドを注入することはできません。スキーマとセットアップはdocs/integrations.mdにあります。

インデックス化はデフォルトでシングルプロセスです。FNPRINT_SHARDS=N fnprint index ...は大きなインデックスをN個のジェイルされたワーカーに分散します。コーパスはNが何であれバイト単位で同一です。大きな関数リッチなバイナリでのみ役立つので、要求しない限りオフです。

精度

ペアごとに2つの数値。rank-1はプリント単体です: ビルドAの関数について、Bのすべての関数を類似度でランク付けし、トップヒットが正しいかどうか。alignedはqueryとmatchが実際に行うことです: コールグラフ付きの1:1割り当て、パートナーが正しい頻度、および「same」閾値で確定したペアのprecision/recall。zlib 1.3.1(84関数)とlua 5.4.6(シグナルのある約600関数)、bench/run.shで再現可能:

ツールをダウンロード