Martyx00/VulnFanatic-NG
GitHub: Martyx00/VulnFanatic-NG
一款集成于 Binary Ninja 的 LLM 辅助漏洞研究插件,通过程序化扫描与多模型验证精准识别反编译二进制中的安全漏洞。
Stars: 10 | Forks: 1
# VulnFanatic-NG
用于 [Binary Ninja](https://binary.ninja/) 的 LLM 辅助漏洞研究工具。
VulnFanatic-NG 添加了一个侧边栏面板,它会扫描当前的二进制文件,并请求
LLM —— 默认为**本地托管的** OpenAI 兼容模型,或者 Anthropic
Claude、Google Gemini 或 Azure OpenAI(参见 **LLM 后端**)—— 来判断
可疑代码是否真的存在漏洞。它主要基于 Binary Ninja 的
**反编译器输出 (HLIL)** 进行工作,必要时会回退到汇编代码,并且仅报告
带有可点击代码引用的已确认问题。
## 工作原理
扫描最多分为**三个阶段**运行(Phase 3 为可选阶段,且仅限在线模式):
### Phase 1 — 危险函数调用
查找 [`rules/phase1_rules.json`](rules/phase1_rules.json) 中定义的危险函数的调用点 —
`strcpy`,
`memcpy`,`sprintf`/格式化字符串,`system`,`alloca`,`scanf`,命令/exec
API,弱 RNG,`free`/`delete` 家族(**use-after-free / double-free**),
**将不受信任的输入读取到固定缓冲区中**(`recv`/`read`/`fread`/`ReadFile`),
**SQL 注入**(`sqlite3_exec`/`mysql_query`/`PQexec`),**禁用
TLS 证书验证**(`SSL_CTX_set_verify`/curl),**SSRF**,以及**不当的
权限管理**(`setuid`/`setresgid`),**`memset`/`bzero` 家族**,以及
**与攻击者可控长度的比较**(`memcmp`/`strncmp` →
身份验证绕过),涵盖 C/C++、Win32 以及(尽最大努力的)Rust FFI。覆盖范围
包括增强型 `_chk` (FORTIFY) 和 Annex-K `_s` 变体。有边界的
格式化输出函数(`snprintf` 及其变体)有其自己的默认安全规则,因此正确的 size 参数不会被报告为溢出。调用点
通过三种方式找到:直接调用命名符号;通过**转发 thunks / PLT stubs** 路由的调用(真实的调用者会被恢复,因此仅通过 stub 到达的导入不会被遗漏);并且 — 除非
`vulnfanatic.scanIndirectCalls` 关闭 — 否则包括通过 Binary Ninja 解析为危险函数的**通过函数指针或 vtable 分发的间接调用**。
对于每个调用点,它会构建一个以反编译器为中心的**过程间**上下文,
并设有 token 限制(默认为 100k):
- **调用表达式及其参数**,
- **被调用函数的声明原型**(来自 Binary Ninja 的类型信息,
否则使用内置表),以便模型将参数正确映射到形参 —
增强型 `__*_chk` 和边界检查的 `*_s` 变体接受额外的开头
参数,从而改变格式/大小/目标位置,
- **每个调用参数的类型和字节大小**(缓冲区容量),衍生自
参数的 HLIL 表达式类型,因此像 `s->buf` 这样的结构体字段
会解析为该字段实际的数组大小,而不是 `s` 的指针大小;
types 部分中的结构体定义也带有每个字段的字节大小,
- Binary Ninja 的常量传播和值集分析解析出的**每个参数的具体值/范围**(例如,被证明为常量 `0x40` 或限定在 `[0, 0xff]` 范围内的长度),
模型在比较
大小与缓冲区容量时将其作为基准事实,而不是进行猜测,
- **调用函数的栈帧布局**(变量偏移量和字节大小),当它包含固定大小的缓冲区时,以便可以针对相邻变量和保存的返回地址判断栈溢出
(`vulnfanatic.includeStackLayout`),
- **路径约束**(保护调用的 `if`/循环/`switch` 条件),
- **参数数据流摘要** — 每个调用参数在函数内的定义和使用位置,
- **跨调用者的参数解析** — 当一个危险的参数是调用函数的一个*参数*时,上下文会报告每个调用者实际上为其传递了什么
(例如,“所有调用者都传递了字符串字面量”),因此总是常量的 format/
size 参数不会被误认为是攻击者可控的,
- **调用函数的完整反编译主体**,
- **数据类型定义**(struct/union/enum),针对调用链和参数变量中引用的类型,以便模型知道真实的缓冲区/字段大小和整数宽度,
- **生成或消耗调用参数变量的函数的反编译主体**(通过 HLIL def/use 追踪),这正是使
use-after-free / double-free 和污点大小推理成为可能的原因,
- 从入口点/导出函数到该调用的**调用路径**,
- **这些调用路径上每个函数的反编译主体**(最接近危险调用的排在前面),每个都带有调用点和保护下一跳的条件的注释,
- **这些路径函数调用的其他函数的主体**(例如,对于
`MAIN→ABCD→strcpy`,还包括 `MAIN` 和 `ABCD` 在其他地方调用的函数),因为
它们可能包含限制危险值的边界/验证检查
(`vulnfanatic.includeCallPathSiblings`,在预算允许的情况下填充),以及
- **污点源提示**(在同一个函数中调用的输入函数,如 `recv`/`read`/`getenv`)。
该上下文连同特定于规则的 prompt 一起发送给模型,模型返回一个
结构化的判定。非问题会被丢弃。这些 prompt 针对强大的本地代码模型(例如 **Qwen2.5-Coder**)进行了调整,并指示其分析整个
流程并仅输出 JSON。
### 推理、草稿板和置信度
模型被指示倾向于**召回率** — 报告合理的、与安全相关的问题
并通过**置信度**来表达不确定性,而不是丢弃任何它
无法完全证明的东西。它会在一个**草稿板**中展示其工作过程,其中引用了它所依赖的逐字代码片段(输入源、每个保护条件、大小/长度、相关类型和 sink),这些内容存储在发现结果中,以便您审计推理过程。
每个发现结果都带有一个**置信度**(high/medium/low):high = 整个链条都显示在上下文中;medium = 很可能,推断出了一两个链接;low = 值得人工审查的线索。这是主要指标(模型的严重性估计是次要字段)。设置 `vulnfanatic.minConfidence` 可以丢弃低于某个阈值的结果。
### 调整精确率与召回率
默认情况下,VulnFanatic-NG 倾向于召回率(捕获真实问题)。如果您发现误报太多,可以使用以下任一方法进行调整:
- **`vulnfanatic.validationPass`**(默认**关闭**)— 运行第二轮 LLM 检查,该检查会针对相同的上下文仔细检查每个被标记的问题(验证草稿板片段并重新追踪流程),并且可以更正判定或置信度。这会使被标记候选项的 LLM 调用次数加倍。
- **独立的验证器模型(使用验证轮次时推荐)。** 设置
**`vulnfanatic.validatorModel`**(加上 `validatorProvider` / `validatorBaseUrl` /
`validatorApiKey`),以便在**不同**的模型上运行第二轮。来自*独立*模型的第二意见要更有用得多 — 它共享的盲点更少,并且极不可能仅仅为第一个判定盖章通过(模型往往
更喜欢自己的答案)。一个好的模式是**级联**:使用一个快速的模型作为分析师(广泛的召回率),使用您最强大的模型作为验证器,验证器仅在
被标记的候选项上运行。将验证器模型留为空白,则使用分析师模型进行验证。验证器应该至少与分析模型一样强大 — 较弱的验证器主要只会增加错误的拒绝。除了 provider/base URL/key/
model 之外的所有内容都继承自分析师连接设置;留空的验证器 key 会重用分析师的 key;如果验证器端点不可达,则保留第一个判定(发现结果绝不会因为验证器故障而丢失)。
- **`vulnfanatic.minConfidence`**(默认 `low`)— 提高到 `medium`/`high` 以仅报告更强的发现结果。
- **`vulnfanatic.skipConstantArgCalls`**(默认**关闭**)— 跳过那些参数全为编译时常量的溢出类调用点。
**速度。** 每次调用的大部分延迟来自于书面的推理,因此
**`vulnfanatic.verdictReasoning`** 控制模型写多少内容:
- **`concise`**(默认)— 简短的 1–3 句基本原理,无逐字代码。比 `full` 快得多,且准确性损失很小;您还可以降低
`vulnfanatic.maxResponseTokens`。
- **`full`** — 带有引用片段的详细草稿板(最易于审计,最慢)。
- **`none`** — 仅输出判定。最快;将其与具有推理能力的后端
(`vulnfanatic.reasoningEffort`)配对,以便模型的*内部*思考来完成工作。
在普通的本地模型上,`none` 会丧失准确性(完全没有思维链)。
支持精确率的特性始终开启(它们在不抑制发现的情况下为模型提供信息):
- **参数来源** — 上下文按参数告诉模型,它是*编译时常量*、*函数参数*,还是*衍生自污点源*,模型利用这些信息来设定置信度。
- **边界检查变体意识** — prompt 将 `_s` (Annex K) 和
`_chk` (FORTIFY) 变体以及长度受限的 API 视为安全的,除非 size 参数本身是错误的。
- **显式缓冲区大小** — 提供了参数和结构体字段的字节容量,以便模型将容量与写入的字节进行比较,而不是进行猜测。
### 离线扫描(无 LLM)
**Scan Offline** 按钮在**无模型**的情况下运行 Phase 1 — 纯程序化的
启发式规则声明在 `phase1_rules.json` 中的每个规则的 `offline` 块里。它
标记危险调用点并*消除那些明显安全的调用点*,分配一个启发式的
**置信度**:
- **被消除**(不报告):具有常量长度的 `memcpy`/`memmove`,来自常量字符串的
`strcpy`,具有常量格式的 `printf`,具有常量命令的
`system` 等 — 这些调用的控制参数是编译时常量,因此不能被攻击者控制。“常量”包括 Binary Ninja 的值集分析在上游固定为具体数字的值,而不仅仅是字面参数。
- **High** 置信度:被标记且在执行流程中的任何地方都未找到缓解性检查。
- **降级**(→ medium):size/length 参数要么被 Binary Ninja 的值集分析解析为常量/有界范围,要么在流程的某处找到了真正的边界/验证检查,用于*比较*相关参数(例如 `strlen`/size 比较,`if (len < …)`) — 包括在路径上调用的函数中 — 因此它可能已经被处理了。(仅仅提到变量而没有对其进行比较的分支不再计入,消除了虚假降级的来源。)
启发式规则在规则文件中使用了一个小的**声明式词汇表**
(`constant_safe_args`,`eliminate_if_all_args_constant`,`format_arg_lookup`,
`length_guard_vars`,`base_confidence`,`skip`),由 Python 谓词求值 —
没有用于 `exec` 的嵌入式代码。大多数规则都有离线定义(溢出、
格式化字符串、命令执行、scanf、路径处理、弱 RNG、弱数值解析、
权限更改、分配大小等)。只有两类真正需要语义分析的类别在离线模式下会被跳过,留给 LLM:`free`/`delete` 家族(**use-after-free / double-free**,需要指针生命周期追踪)和 **TLS 验证**(该 bug 是一个特定的常量值,如 `SSL_VERIFY_NONE`)。离线摘要会报告有多少站点被标记 / 消除 / 跳过(需要 LLM) / 失败,因此总数是对得上的。这是一种快速的分流;对于真正的判断 — 以及对于被跳过的类别 — 请运行完整的 LLM 扫描。
离线发现仍然会构建与在线扫描发送的**相同完整的过程间上下文**(仅针对被标记的站点)并将其存储起来,因此一旦您对它们进行了分流,它们就可以像在线发现一样被**导出为微调数据**。如果您想要最大的离线速度,请使用 `vulnfanatic.offlineBuildContext` 禁用此功能。
### Phase 2 — 安全敏感代码(受符号限制)
仅在二进制文件似乎具有真实符号/变量名时运行。定位
[`rules/phase2_rules.json`]( ) 中定义的安全敏感函数 —
身份验证、密码学(包括弱算法)、签名/证书验证、会话/token 处理、
访问控制、密钥/key 处理、输入验证、非常量时间秘密比较和不安全的
反序列化 — 通过函数名和引用字符串进行匹配,然后由模型进行审计。
### Phase 3 — 硬件攻击加固审计(在线,可选)
一项针对故障注入(电压/时钟/EM 故障注入)和侧信道(时序/功率)攻击的**固件加固**审计,基于硬件攻击缓解指南。与 Phase 1–2(查找 *bug*)不同,Phase 3 报告的是在安全关键功能上**缺失或被破坏的加固控制** — 例如:
默认失败分支、双重检查的安全决策、循环后计数器验证、高汉明距离的状态常量(对比普通的 0/1)、常数时间的全长秘密比较、随机偏移的秘密访问/清除、
先加密后验证(反 DFA)、控制流完整性计数器、避免用户态加密以及不直接处理原始 key 材料
([`rules/phase3_rules.json`](rules/phase3_rules.json))。
因为编译器优化可能会剥离源代码级别的防护,所以这些控制最好在**已编译的二进制文件**上进行验证 — 这正是此阶段检查的内容。Phase 3 是 **仅限 LLM(在线)**、受符号限制且**默认禁用**的;在 New Scan 标签页上选中 **Phase 3** 复选框即可按扫描启用(它从不在离线模式下运行)。
发现结果显示在一个表格中(状态、置信度、阶段、CWE、函数、地址、标题),带有显示解释、分析草稿板和验证注释的详细面板。**双击一行可导航**二进制视图至该代码处。
### 分流工作流
每个发现初始状态均为 **Untriaged**。**右键单击**某一行可设置其状态 —
**Mark as Real Issue**、**Mark as False Positive** 或 **Mark as Untriaged**。每次
状态更改都会弹出一个 **"Provide reason:"** 文本框(该原因会与发现一起存储)。该表格使状态一目了然:Real Issues 为**绿色/加粗并排在顶部**,False Positives 为**灰色/带删除线并排在底部**,Untriaged 介于两者之间,并带有其置信度颜色。摘要行显示了计数。
每个结果标签页都有一个 **Export triaged (fine-tuning)…** 按钮,它会将*仅*经过分流的发现导出为 **OpenAI 聊天格式的 JSONL**
以供微调:每个示例将原始的 system+user prompt 与作为助手目标(assistant target)的**人工纠正判定**配对(False Positive 教导
`is_vulnerable=false` 并附带您的原因;Real Issue 强化 `is_vulnerable=true`),
因此您可以在您的二进制文件上迭代提高模型的准确性。
详细面板中显示的针对单个发现的上下文(并用于重建微调 prompt),默认保持**完整** — 由
`vulnfanatic.storedContextChars` 控制(`0` = 无限制;设置一个正数上限,例如 `4000`,以限制 BNDB 的增长,但代价是牺牲上下文保真度)。
### 多次扫描(标签页)
该面板是带标签页的。第一个标签页始终是 **New Scan**,您可以在其中设置:
- 一个可选的 **Scan name**(留空 → ` `,例如
`2026-06-15 14:03:50 offline`),
- 一个可选的 **Phase 1 / Phase 2 / Phase 3 自定义规则路径**(留空 → 使用捆绑的默认值),以便您可以运行另一套规则集,
- 一个 **Phase 3** 复选框(默认关闭)以额外运行仅限在线的硬件攻击加固审计,
然后按 **Start Scan** 或 **Scan Offline**。每次运行都会打开其自己的结果标签页,并且发现结果会实时流入其中。**所有扫描都存储在 BNDB 中**,因此您可以例如保留一次离线扫描,稍后再添加一次在线扫描,或者并排比较使用不同规则集的运行结果 — 当您重新打开数据库时,它们会作为标签页重新出现。**关闭一个标签页会永久从 BNDB 中删除该扫描** — 为了防止意外,它会弹出一个确认对话框,要求勾选*"I confirm that I will lose the results from forever."*,然后 **Delete results forever** 按钮才会激活。**Export current scan…** 会将所选标签页的内容写入 Markdown/JSON。
每个**打开的二进制文件都有其独立的面板状态** — 它有自己的扫描标签页和正在运行的扫描。在一个二进制文件中启动扫描并切换到另一个文件时会显示第二个二进制文件的结果(并允许您单独扫描它);第一个二进制文件的扫描会在后台继续运行,并且在您切换回来时完好无损。
## 安装
该插件的包文件夹名为 `vulnfanatic_ng`(一个有效的 Python 标识符 —
Binary Ninja 将插件文件夹名称作为模块导入,因此像 `VulnFanatic-NG` 这样带有连字符的名称将无法加载)。
1. (可选) 将精确的 token 计数安装到 Binary Ninja 的 Python 中:
pip install tiktoken
2. 将 **`vulnfanatic_ng`** 文件夹符号链接或复制到您的 Binary Ninja 用户插件目录中:
- macOS: `~/Library/Application Support/Binary Ninja/plugins/`
- Linux: `~/.binaryninja/plugins/`
- Windows: `%APPDATA%\Binary Ninja\plugins\`
例如,在 macOS 上:
ln -s "$(pwd)/vulnfanatic_ng" "$HOME/Library/Application Support/Binary Ninja/plugins/vulnfanatic_ng"
3. 重启 Binary Ninja(或运行 *Reload Plugins*)。右侧边栏会出现一个 **VF** 图标。
## 配置
打开 **Settings**(齿轮图标 / `Edit ▸ Preferences ▸ Settings`)并搜索
**`vulnfanatic`**。至少进行以下设置:
| Setting | Meaning |
| --- | --- |
| `vulnfanatic.apiProvider` | 调用哪个 LLM 后端:`openai`(默认)、`anthropic`、`google` 或 `azure`。请参见下文的 **LLM backends**。所有 provider 都通过 Python 标准库进行访问 — 无需 `pip install`。 |
| `vulnfanatic.apiBaseUrl` | 所选 provider 的端点 base(参见下表)。默认为 `http://localhost:8080/v1`。设置为字面量 `TEST` 可启用**测试模式**(参见下文)。 |
| `vulnfanatic.apiKey` | API key / bearer token。对于本地服务器可以为空。可以被 `VULNFANATIC_API_KEY` 或 `OPENAI_API_KEY` 环境变量覆盖。 |
| `vulnfanatic.model` | **必填**(测试模式除外)。模型标识符(对于 `azure`,为**部署名称**)。 |
| `vulnfanatic.apiMode` | 仅限 `openai`:`chat`(默认,`/chat/completions`)与 `completions`(单一扁平化 prompt — 用于**不带 chat template** 服务的基础/指令模型)。 |
| `vulnfanatic.azureApiVersion` | 仅限 `azure`:`api-version` 查询参数(默认为 `2024-10-21`)。 |
### LLM backends
`vulnfanatic.apiProvider` 选择请求的构建和身份验证方式。判定契约(以及所有规则 prompt)在各个 provider 之间是相同的。
| Provider | `apiBaseUrl` | Auth | Notes |
| --- | --- | --- | --- |
| `openai` | 您的服务器,例如 `http://localhost:8080/v1` | `Authorization: Bearer` | OpenAI 兼容的 Chat/Completions:本地 llama.cpp / ollama / vLLM、OpenAI 以及 **AWS Bedrock 的 OpenAI 兼容端点**。 |
| `anthropic` | 留空 → `https://api.anthropic.com` | `x-api-key` + `anthropic-version` | Claude **Messages API**(`POST /v1/messages`)。不发送 `temperature`(当前的 Claude 模型会拒绝它)。 |
| `google` | 留空 → `https://generativelanguage.googleapis.com` | URL 中的 API key | Gemini `generateContent`(` /v1beta/models/:generateContent`)。 |
| `azure` | `https://.openai.azure.com` | `api-key` header | Azure OpenAI;将 `model` 设置为**部署名称**,并将 `azureApiVersion` 设置为您的 API 版本。 |
其他有用的设置:`vulnfanatic.maxContextTokens`(默认 100000)、
`vulnfanatic.maxResponseTokens`、`vulnfanatic.temperature`、
`vulnfanatic.reasoningEffort`(`off`/`low`/`medium`/`high`;默认**高** — 在支持的条件下,要求
模型在回答前进行思考,按 provider 进行映射:
openai/azure `reasoning_effort`,anthropic 自适应思考 + `output_config.effort`,
google 动态 `thinkingConfig`;如果模型拒绝则自动剥离并重试)、
`vulnfanatic.requestTimeoutSec`、
`vulnfanatic.callPathMaxDepth` / `vulnfanatic.callPathMaxPaths`、
`vulnfanatic.callPathIncludeBodies`(包含调用路径上函数的反编译主体;默认开启) / `vulnfanatic.callPathMaxBodies`(上限,默认 12)、
`vulnfanatic.includeCallPathSiblings`(还包括沿路径调用的其他函数,它们可能包含边界/验证检查;默认开启) /
`vulnfanatic.callPathSiblingMaxBodies`(上限,默认 12)、
`vulnfanatic.includeDataTypes`(包含 struct/union/enum 定义;默认开启) /
`vulnfanatic.maxTypeDefs`(上限,默认 24)、
`vulnfanatic.includeVariableDataflow`(通过其生产者/消费者回溯调用参数并包含这些函数主体;默认开启) / `vulnfanatic.dataflowMaxFunctions`
(上限,默认 8)、
`vulnfanatic.includeStackLayout`(当调用函数包含固定大小缓冲区时,包含其堆栈变量布局;默认开启)、
`vulnfanatic.scanIndirectCalls`(同时匹配通过解析的
函数指针/vtable 分发的危险调用;默认开启 — 在非常大的二进制文件上可关闭以加快扫描速度)、
`vulnfanatic.validationPass`(运行第二轮复查;默认关闭) /
`vulnfanatic.validatorModel` / `vulnfanatic.validatorProvider` /
`vulnfanatic.validatorBaseUrl` / `vulnfanatic.validatorApiKey`(在
一个单独的、独立的模型上运行验证轮次 — 留空 = 与分析模型相同) /
`vulnfanatic.minConfidence`(`low`/`medium`/`high`;丢弃低于此值的结果;默认
`low`)、`vulnfanatic.flagUnparseableResponses`(将模型无法评分的站点报告为
`UNKNOWN`-置信度的 "Unscored" 线索,而不是丢弃它们;默认开启)、
`vulnfanatic.skipConstantArgCalls`(跳过全为常量的溢出调用站点;
默认关闭)、`vulnfanatic.verdictReasoning`(`concise`/`full`/`none`;模型每次判定写下多少
推理内容 — 主要的速度控制杆;默认 `concise`)、
`vulnfanatic.runPhase1` /
`vulnfanatic.runPhase2` / `vulnfanatic.runPhase3`(启用每个阶段;Phase 3 是
仅限在线的,通常通过 New Scan 复选框按扫描切换,而不是在这里)、
`vulnfanatic.phase2RequireSymbols` / `vulnfanatic.phase2ForceEnable`、
`vulnfanatic.tokenizerEncoding`(用于 token 估计的 tiktoken 编码;如果
未安装 tiktoken,则回退到字符启发式方法)、
`vulnfanatic.offlineBuildContext`(为离线发现构建完整上下文,以便它们可以
被导出用于微调;默认开启)、
`vulnfanatic.debugLogging`(向控制台输出详细的流水线追踪;默认关闭) /
`vulnfanatic.debugAnonymous`(隐去所有可识别二进制文件的详细信息,以便可以
分享日志 — 参见下文的 **Debug logging**)、
`vulnfanatic.sendJsonResponseFormat`、`vulnfanatic.tlsVerify`(验证 HTTPS
证书;默认开启) / `vulnfanatic.caBundlePath`(用于 HTTPS 的 CA 捆包 — 如果您遇到 `CERTIFICATE_VERIFY_FAILED`,请参见 Troubleshooting),以及
`vulnfanatic.rulesPhase1Path` / `vulnfanatic.rulesPhase2Path` /
`vulnfanatic.rulesPhase3Path`(将这些指向您自己的规则文件,以自定义
检测和 prompt)。
### 测试模式(空运行,无 LLM)
将 `vulnfanatic.apiBaseUrl` 设置为字面量 **`TEST`** 以在没有任何 LLM 的情况下运行:
- 模型**从不被调用**(无需网络,无需 API key/model)。
- **每个**候选者(Phase 1 中的每个危险调用点,Phase 2 中的每个
安全敏感函数)都会被标记。
- 每个候选者的完整 prompt — system prompt **以及**完整的生成上下文 — 都会被写入其在
`/tmp/vulnfanatic_ng/-/` 下各自的文件中。
- 发现结果表格显示 **Prompt File** 列(悬停查看完整路径),
并且详细面板和导出内容中包含该路径。
使用此功能可以检查并验证 VulnFanatic-NG 将确切发送给模型的内容,并在不花费模型时间的情况下迭代规则 prompt/上下文。
### 调试日志记录
开启 **`vulnfanatic.debugLogging`** 可将扫描流水线(在线和离线)的详细分步追踪打印到 Binary Ninja 的日志/控制台中:每个调用
点、每次跳过/消除决定、上下文构建(仅大小)、每次 LLM 请求(provider/model/endpoint、重试、回退)、每个判定以及每个报告的发现结果。**API key 绝不会被记录。**
当开启调试日志记录时,**在线**扫描会将**每个候选者保留在结果表中**,而不是丢弃那些未成为确认问题的候选者,每个候选者都标有仅用于调试的状态(变暗,排在底部):
- **REJECTED** — LLM 返回了*不是问题*的判定。
- **SKIPPED** — 在 LLM 之前被可证明安全的规则(常量格式
字符串,或全为常量的参数)消除;该行会说明原因。
- **ERROR** — 该候选者无法被分析(上下文构建失败,或者 LLM
响应无法解析 / 连接失败);该行带有错误信息。
因此,调试扫描会在 `/N` 总数中为每个候选者显示一行,并且摘要会分别报告
问题数与被拒绝/跳过/错误的计数。您可以右键单击这些行中的任意一行,将其重新分流为 Real Issue 或 False Positive(这使其符合
微调导出的条件)。(离线扫描不受影响 — 它们从不调用 LLM。)
独立于调试模式,当模型返回无法解析的响应时 — 例如 Gemma 的杂散 token 如 ``、散文而不是 JSON,或者**空消息**(仅有 `role`,没有 `content`)— 客户端会进行**一次纠正性重试**,仅在不使用结构化输出格式的情况下重新要求返回 JSON;如果成功,它将在剩余的扫描过程中保持关闭该格式。当 `content` 为空时,客户端还会读取**推理通道**(`reasoning_content` / `reasoning`),因此将答案放在那里的推理模型仍然可以工作。
在使用 OpenAI 兼容 API(例如 `mlx-community/gpt-oss-20b`)服务的**推理模型(如 GPT-OSS / o1)**中,空消息情况很常见:在设置了 `response_format=json_object` 的情况下,harmony "final" 答案通道通常会被抑制,服务器返回的 `{"role": "assistant"}` 没有 content。这些模型还可能将其整个输出预算消耗在推理通道上,并在**思考中途被截断**,返回完全不包含 JSON 的散文。自动重试可以恢复与格式相关的案例;如果
这种情况持续存在,请**关闭 `vulnfanatic.sendJsonResponseFormat`**,**降低 `vulnfanatic.reasoningEffort`**(这样分配给思考的预算就会减少),和/或**提高 `vulnfanatic.maxResponseTokens`**。如果持续出现 ``/垃圾回复,通常意味着 prompt 超过了模型的上下文窗口(设置 `vulnfanatic.modelContextWindow` 和/或提高服务器的上下文长度),或者该模型不适合严格的 JSON 输出(在这种情况下,Qwen2.5-Coder 等代码模型的表现要比 Gemma 好得多)。
**保留召回率的回退。** 当一个候选者在重试后仍然无法评分时,`vulnfanatic.flagUnparseableResponses`(默认**开启**)会将其作为
**"Unscored"** 发现报告,置信度为 **`UNKNOWN`** — 这是一个不同于 `low` 的值(模型从未产生判定,因此它不是低置信度的*判断*),它排在底部 — 同时保留模型的输出作为解释,因此您不会丢失该站点,您只需手动审查它即可。将其关闭以丢弃此类站点(它们随后仅作为分析错误或调试 ERROR 行出现)。
还可以启用 **`vulnfanatic.debugAnonymous`** 以使日志安全共享:它会隐去所有可能识别被分析文件的信息 — 符号/变量名和
地址变成每次运行加 salt 的哈希值(在单次运行中仍然保持一致,因此流程是可以跟踪的),文件名被隐藏,发现文本被替换为 ``,LLM 端点主机被哈希处理,反编译的代码 / prompt / 上下文仅记录大小(绝不记录内容)。因此,您可以发送调试日志来报告问题,而不会披露有关您二进制文件的任何信息。
## 用法
1. 打开一个二进制文件并等待分析完成。
2. 单击 **VF** 侧边栏图标以打开 VulnFanatic-NG。
3. 按 **Start Scan**(完整 LLM 扫描)或 **Scan Offline**(快速、无 LLM 的程序化 Phase 1 — 见上文)。进度显示在面板和 Binary Ninja 状态栏中;发现结果会实时出现,并且可以取消。
4. 单击发现结果以阅读解释;双击以跳转到代码。
5. **右键单击**发现结果以 *Mark as false positive* — 它会移动到表格底部,变灰并带有删除线,并且菜单项会变为 *Mark as real issue* 以撤销操作。(右键菜单中还有 *Go to code*。)
6. **导出**为 Markdown 或 JSON,或者 **清除** 以丢弃已保存的发现结果。
发现结果 — 包括它们的误报状态 — 存储在 Binary Ninja
数据库中。它们在您保存数据库时被写入 `.bndb`(如果 `.bndb` 已经存在,则会立即刷新),因此它们在重新打开后依然存在。
扫描会分析**每个**匹配的调用点(无上限),这对于
本地模型是合适的。对于托管/付费端点,在大型
二进制文件上要注意数据量。
## 自定义规则
两个规则文件共享一个包含通用 `system_prompt` 和
`output_schema` 的信封,外加一个 `rules` 列表。复制捆绑的文件,编辑
函数/关键字/prompt,并将 `vulnfanatic.rulesPhase1Path` /
`vulnfanatic.rulesPhase2Path` 指向您的副本。Phase 1 规则通过 `functions`(精确匹配)和 `name_regex` 匹配;Phase 2 规则通过 `name_keywords`、`name_regex` 和 `string_keywords` 匹配。每个规则的 `prompt` 可以使用 `{function}` 占位符。
## 微调模型 (MLX, Apple Silicon)
分流后的导出内容旨在直接反馈给模型。在**多个二进制文件**中对发现结果进行分流并单击每个文件上的 **Export triaged (fine-tuning)…**(将 `.jsonl` 文件收集到一个文件夹中)后,
`scripts/finetune_mlx.py` 会在它们上运行 [MLX](https://github.com/ml-explore/mlx) LoRA
微调。
```
pip install mlx-lm # Apple Silicon / macOS
# 对 ./exports 下的每个 *.jsonl 进行 Fine-tune 本地 4-bit model
python scripts/finetune_mlx.py ./exports \
--model mlx-community/Qwen2.5-Coder-7B-Instruct-4bit \
--adapter-path ./vf-adapters --iters 800
# ...然后将 adapters 融合到一个 standalone model 中
python scripts/finetune_mlx.py ./exports --model \
--fuse --fused-path ./vf-qwen-coder-vuln
```
该脚本将**训练数据文件夹**作为其位置参数,将基础 **`--model`**(本地路径或 MLX/HF repo id)作为参数;其他参数是可选的:
`--adapter-path`、`--valid-split` (0.1)、`--iters`、`--batch-size`(自动限制以
适应极小的拆分)、`--num-layers`、`--learning-rate`、`--max-seq-length`
(`0` = **自动适应**最长示例,上限为 16384;设置正值以
强制执行)、`--fine-tune-type`(`lora`/`dora`/`full`)、`--seed`、`--fuse`/`--fused-path`,
以及 `--dry-run`(准备数据 + 打印命令而不进行训练)。字面量 `--` 之后的任何内容都将原封不动地转发给 `mlx_lm lora`。它会递归合并文件夹中的每个 `*.jsonl`,验证并**去重**聊天示例,进行 MLX 所期望的 `train.jsonl`/`valid.jsonl` 拆分,然后启动 `python -m mlx_lm lora`(如果带有 `--fuse` 则还会启动 `mlx_lm fuse`)。
使用 OpenAI 兼容的服务器(`mlx_lm.server --model `)提供结果,并将 `vulnfanatic.apiBaseUrl` 指回它,以便使用您调整过的模型进行扫描。
## 开发与测试
该插件没有必需的第三方依赖。纯模块(`rules`、`tokens`、`llm`、`findings`、`settings`、`prototypes`)由一个既不需要 Binary Ninja 也不需要网络的离线测试套件覆盖。`tests/` 套件位于项目的源代码存储库中(它不包含在发布的插件中);从那里运行它。从包目录中,您仍然可以对每个模块进行语法检查:
```
python3 -m py_compile *.py ui/*.py
python3 -m unittest discover -s tests # from the source repository
```
面向 Binary Ninja 的模块(`context_builder`、`phase1`、`phase2`)在没有 Binary Ninja 的情况下也能干净地导入(它们的 API 访问受到保护),但需要运行中的 Binary Ninja 才能执行。
### 在 Binary Ninja 中的手动测试
1. 构建一个带有漏洞的小型 C 程序(例如,使用 `strcpy` 将 `argv[1]` 复制到
固定大小的栈缓冲区中,并对输入调用 `system()`)。**带符号**编译,以同时测试 Phase 2。
2. 启动您的本地 OpenAI 兼容服务器,并设置 `vulnfanatic.apiBaseUrl`、
`vulnfanatic.apiKey` 和 `vulnfanatic.model`。
3. 打开二进制文件,运行 **Start Scan**,并确认报告了危险调用,且双击可导航到调用点。
## 故障排除
**`SSL: CERTIFICATE_VERIFY_FAILED ... unable to get local issuer certificate`** —
HTTPS 端点的证书没有问题,但 Binary Ninja 捆绑的 Python 没有
可用于验证它的 CA 捆绑包(在 macOS 和嵌入式 Python 中很常见;您会在 AWS Bedrock、Anthropic、Google、Azure 等托管端点上遇到此问题)。按优先顺序使用以下方法之一进行修复:
- 将 **certifi** 安装到 Binary Ninja 使用的 Python 中:`pip install certifi`。
VulnFanatic-NG 会自动识别它。
- **指向一个 CA 捆绑包:** 将 `vulnfanatic.caBundlePath` 设置为一个捆绑包文件(或目录) — 例如 `python3 -m certifi` 打印的路径,或 `/etc/ssl/cert.pem`。
- **最后手段:** 关闭 `vulnfanatic.tlsVerify`(仅适用于受信任的/内部端点或带有自签名证书的本地服务器 — 这将禁用证书检查)。
**`HTTP 400 ... tokenizer.chat_template is not set`** — 您正在服务的模型没有 chat template,因此 `/chat/completions` 端点无法格式化消息。VulnFanatic-NG 在看到此错误时会针对剩余的扫描**自动回退**到 `/completions` 端点,因此扫描会继续进行。为了完全避免第一次失败的请求,请将 `vulnfanatic.apiMode` 设置为 `completions`。或者,通过服务带有 chat template 的模型在服务器端修复它,或者将一个 template 传递给您的服务器 — 例如对于 vLLM:`--chat-template `(或者使用 `-Instruct`/`-Chat` 模型变体)。专用的 chat template 通常比扁平化的 completions prompt 提供更好的结果。
**`No JSON object found ... response looks truncated`** — 模型的回复在 JSON 完成之前被截断了。原因有两个:
- 草稿板超过了响应预算 → 提高 `vulnfanatic.maxResponseTokens`。
- **更常见的情况:** prompt 填满了模型的**上下文窗口**,没有留下任何生成空间,因此无论 `maxResponseTokens` 有多高,回复都会在 token 后停止。本地服务器通常有一个较小的窗口(ollama 默认为 `num_ctx=2048`!)。通过将 **`vulnfanatic.modelContextWindow`** 设置为您的服务器窗口(例如 ollama `num_ctx`、llama.cpp `-c`、vLLM `--max-model-len`)来修复它 — 然后 VulnFanatic-NG 会自动限制它发送的上下文,以便 prompt + 响应能够容纳。
同时保持 `vulnfanatic.maxResponseTokens` 合理(≈8192,而不是 65535)和/或提高服务器的窗口。极小的窗口(≤8k)无法容纳完整的过程间上下文;使用配置为 32k+ 的模型/服务器。
**`IncompleteRead` / `Could not complete request ... after N attempt(s)`** — 服务器接受了请求,但在发送完整响应之前关闭了连接。这几乎总是意味着模型服务器在生成过程中死亡或停滞:内存不足(大上下文 + 长输出)、内部/工作者超时,或者代理重置了连接。VulnFanatic-NG 会自动重试一次,然后跳过该站点。**检查模型服务器自身的日志**以找出真正原因;减少 `vulnfanatic.maxContextTokens` 和/或 `vulnfanatic.maxResponseTokens`,或者给服务器更多的内存 / 更大的上下文窗口,通常可以解决问题。
## 限制
- **Phase 2 具有符号感知能力。** 在被 strip 的二进制文件上,基于名称的匹配是不可靠的,因此默认情况下仅运行 **字符串证据** 规则(引用标志性字符串常量的函数仍然可以被审计)。设置 `vulnfanatic.phase2ForceEnable` 也可以审计基于名称的匹配,或者设置 `vulnfanatic.phase2RequireSymbols`=off。符号限制是一种启发式方法。
- HLIL ↔ 调用点映射可能会失败;VulnFanatic-NG 会回退到 MLIL/汇编,并在每个发现中注明所使用的表示形式。
- 判定的好坏取决于模型。应将发现结果视为供人工审查的线索,而不是基准事实。
- 一些本地服务器会忽略或拒绝 `response_format=json_object`;客户端可以容忍这一点,并且仍然会提取 JSON。如果您的服务器彻底拒绝了该参数,请禁用 `vulnfanatic.sendJsonResponseFormat`。
## License
Apache-2.0 (© Martin Petran) — 参见 [`plugin.json`](plugin.json)。
标签:C2, DLL 劫持, Petitpotam, 二进制分析, 云安全运维, 云资产清单, 大语言模型, 插件, 逆向工具, 逆向工程