Skip to content
KitploitKITPLOIT
工具博客
提交
工具博客
提交

黑客、渗透测试和网络安全工具,武装您的安全武器库!

Kitploit 是一个黑客、网络安全和渗透测试工具的目录。发现最新的项目更新,查找漏洞、分析系统、自动化测试并加强你的安全。

··订阅源·联系·隐私·© 2026 Kitploit

工具目录

分类

查看所有分类
Loading categories
fnprint — 按函数在二进制中的行为(而非其字节外观)进行匹配。通过微执行实现基于行为的函数指纹识别。 | Kitploit
工具/GitHubGitHub/1rhino2/fnprint
漏洞分析动态代码分析 (DAST)逆向工程恶意软件分析二进制分析固件分析
GitHub1rhino2/fnprint

fnprint

按函数在二进制中的行为(而非其字节外观)进行匹配。通过微执行实现基于行为的函数指纹识别。

查看仓库
444370天前尚未审核

最受欢迎

查看全部 →

发现我们社区最常用的工具。

探索所有工具

浏览我们的工具集合

查看所有工具 →
网站
分享

fnprint

fnprint 根据函数做什么来匹配二进制文件中的函数,而不是根据它们的字节或控制流图长什么样。它在微型模拟器中用虚构的输入运行每个函数,记录它产生的副作用,并将该行为哈希成指纹。行为相同的两个函数会得到相似的指纹,即使它们由不同的编译器或不同的优化级别构建。

这样做的意义在于:字节签名(FLIRT、FunctionID)在代码重新编译的那一刻就会失效,而 CFG 匹配器(BinDiff、Diaphora)在 -O0 与 -O3 之间会变得不稳定。行为对两者的耐受性都要好得多。

目前仅支持 x86-64 ELF。在信任它之前,请先看局限。

给我看看

fnprint 在一个已剥离符号、以不同方式编译的二进制文件中命名函数

将它指向一个已剥离符号的二进制文件,以及一个你已经知道名称的语料库:

root@kitploit:~
$ 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
named 9 function(s):
  0x000022f9  100.0%  adler32_z
  0x00002a8d  100.0%  compress2
  0x0000ad5d  100.0%  inflateBackEnd
  0x0000adc4   86.7%  inflate_fast
  0x00002e67   80.5%  crc32_z
  ...

那次运行是一个完全剥离符号的 -O0 构建,从一个 -O2 语料库中命名。不同的优化级别,零符号残留,名称却正确恢复了。它对有把握的进行命名,对其余的保持沉默。

它做的另一件事是 diff 两个构建,并告诉你哪些函数改变了行为,这在供应商发布新固件而你想知道实际变动了什么时很方便:

root@kitploit:~
$ fnprint match old.so new.so
compared 84 functions present in both
  unchanged:  53
  changed:    1
  low-signal: 31 (too small to judge)

changed behavior (lowest similarity first):
   61.7%  deflate_stored

对于实际的 n-day 工作,有 triage。从函数的已知易受攻击版本构建一个语料库,从已修补版本构建另一个语料库,然后根据两者对一个未知构建进行排名。一个接近易受攻击侧且与已修补侧明显分离的函数,才是你想呈现在人类面前的,而不仅仅是一个你必须解读的单一匹配分数:

root@kitploit:~
$ 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 控制两侧在它做出判断之前必须分离到什么程度。

工作原理

对于每个函数:

  • 映射二进制文件,并在参数寄存器中填入垃圾数据后跳转到该函数。
  • 任何我们未设置的内存读取都会返回一个确定性的值,并且该页会被即时映射。野指针永远不会使运行崩溃,相同的输入总是给出相同的 trace。这就是 Godefroid 的微执行技巧。
  • 对其他函数的调用会被打桩(记录,然后跳过),这样我们永远不会深入 libc,运行始终围绕这个函数。
  • 我们记录一个架构中立的效应流:它读取和写入哪些参数缓冲区和结构体字段,它写入什么值类别(输入的副本、一个小常量、一个指针),它发起的调用,它走的分支,它返回什么。绝对地址被丢弃,只保留偏移和形状。
  • 该流被转换成 shingles 和 minhash 签名。相似度是匹配的 minhash 槽位的比例,它估计两个函数的行为重叠了多少。LSH band 索引使查询不必将每个函数与所有函数进行比较。

没有训练数据,没有模型。同样的想法在文献中以 Blanket Execution(Egele 等人,USENIX Security 2014)出现;fnprint 是它的一种实用、持续维护的实现,带有一个你真正能用的 CLI。

安装

需要 rust 工具链以及 unicorn + capstone 库。

root@kitploit:~
# debian/ubuntu/kali
sudo apt install libunicorn-dev libcapstone-dev

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

用法

root@kitploit:~
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)

match 和 query 接受 ELF 或你用 index 构建的 .db,因此你可以对语料库进行一次指纹提取并重复使用。

机器输出

每个命令都接受一个全局 --format:

root@kitploit:~
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 导入桩)的稳定 schema,r2 输出 afn 重命名命令,你在 rizin/radare2 中用 . fnprint.r2 运行。来自目标的符号名称在进入任一格式之前都会被清理,因此精心构造的名称无法注入 r2 命令。schema 和设置见 docs/integrations.md。

索引默认是单进程的。FNPRINT_SHARDS=N fnprint index ... 将大型索引分散到 N 个受限 worker 上;无论 N 是多少,语料库都是字节完全相同的。它只对大型、函数丰富的二进制文件有帮助,所以除非你要求,否则它是关闭的。

准确度

rank-1 准确度是:对于构建 A 中的一个函数,按相似度对构建 B 中的每个函数进行排名,排名第一的命中是否正确。这正是剥离符号命名任务。在 zlib 1.3.1(84 个函数)上测量,可用 bench/run.sh 复现:

完整表格以及第二个库(lua)见 bench/NUMBERS.md。

eval 还报告 recall@3 / recall@5 和弃权率,因为仅凭 rank-1 会掩盖很多信息。在 gcc O0 -> O2 上,排名第一的命中在 93% 的情况下是正确的,但正确的函数在 96.6% 的情况下位于前 5 名,因此一个小的审查预算就能弥补大部分差距。它还会在它不确定的配对上弃权(拒绝给出自信的“相同”判断),而不是猜测,这就是为什么在相同阈值下精确率保持高位而召回率较低。

诚实的解读:当至少一侧具有一些行为丰富度(任何带有 -O0/-O1 的,或 -O0 下的跨编译器配对)时,它落在 80-98% 的范围内。当两侧都经过重度优化时,我们能观察到的行为变得稀薄,它会下降到接近抛硬币。这是单遍、免训练匹配器的艰难前沿,本文并不假装不是这样。

它不擅长什么

  • 微小的函数。thunk 和单行访问器没有足够的内容来生成指纹,因此它会保留它们(这就是“low-signal”和“with enough signal”计数)。
  • 纯计算。两个都读取缓冲区并返回数字的校验和看起来很相似,因为从外部看它们几乎就是一样的。
  • 真实前置条件背后的深层逻辑。用垃圾输入进行微执行会锻炼函数的入口行为。埋藏在我们用垃圾输入永远无法到达的状态中的更改不会出现在 match 中。它能捕获结构性和早期路径的更改,而不是每一个深层调整。
  • 两侧都重度优化,如上面的数字所示。
  • 重度混淆(尤其是基于 vm 的)会毁掉它。

先前工作,以及本工具的位置

  • FLIRT / FunctionID / Lumina:字节签名。精确、快速,重新编译即失效。
  • BinDiff / Diaphora:图结构。不错,但在优化和架构之间很脆弱。
  • Ghidra BSim:反编译器特征向量。精神上更接近,偏单架构。
  • Asm2Vec / SAFE / jTrans:学习到的嵌入。强大,但需要训练,且无法泛化到没人训练过的架构。
  • 微执行(Godefroid,2014)和 Blanket Execution(Egele 等人,2014):这种方法的学术根源。没有维护的工具将其交付。

fnprint 是免训练、行为优先的选项。效应模型已经是架构中立的,这是跨 CPU 匹配的基础工作。

路线图

  • arm64 和 mips,这样你可以在 x86 上对函数生成指纹,并在已剥离符号的路由器固件中找到它。效应模型已经是架构中立的,这主要是各架构的模拟器管道工作。
  • 更智能的路径覆盖。朴素的 branch flipping 已在代码中(explore_depth,默认关闭),但它会在不可能路径上增加构建特定的噪声,并在测试中损害跨构建准确度,因此它需要一个路径一致性过滤器才能证明自己的价值。
  • pe 和 mach-o 加载器。
  • rizin/radare2 导出现已发布(--format r2);真正的 ghidra 插件是下一步。与此同时,contrib/ 中有一个实验性的 jython 导入桩。

许可证

MIT。见 LICENSE。

下载工具
pairrank-1precision
gcc O0 -> O197.1%93.8%
gcc O0 -> O293.1%83.3%
gcc O0 -> O391.3%100.0%
gcc/clang O097.7%97.1%
gcc O2 -> O356.5%66.7%
gcc/clang O259.1%50.0%