fuzzming/fuzzming
GitHub: fuzzming/fuzzming
一款 LLM 驱动的语言无关型模糊测试助手,通过自动生成、执行和迭代测试用例来闭环发现代码缺陷。
Stars: 4 | Forks: 3
FuzzMing
LLM-powered fuzzing assistant for any language, any fuzzer
Point it at a project. It thinks, it fuzzes, it finds bugs.
FuzzMing 是一个开源工具,它打通了 LLM 与 fuzzer 之间的闭环。它会生成测试合约、运行测试、读取输出,并一轮一轮地迭代,直到找出所有 bug、实现完全覆盖,或者耗尽其轮次预算。
**当前技术栈:Solidity + Foundry。** 首个支持的目标是使用 Foundry 进行 fuzz 的 Solidity 智能合约。但 FuzzMing 不是一个 Foundry 工具,它采用六边形架构构建,专门为了让新语言和 fuzzer 作为 adapter 接入,而无需改动核心代码。Rust + cargo-fuzz、Vyper + Echidna、Move + 任何 fuzzer:每种组合只需开发相应的 adapter 即可实现。其协调器(orchestrator)、会话循环(session loop)、LLM 集成和报告格式均与具体语言和 fuzzer 无关。
## 目录
- [FuzzMing 的功能](#what-fuzzming-offers)
- [前置条件](#prerequisites)
- [安装](#install)
- [快速开始](#quick-start)
- [支持的 LLM 提供商](#supported-llm-providers)
- [fuzzming.config](#fuzzmingconfig)
- [子命令](#subcommands)
- [工作原理](#how-it-works)
- [局限性](#limitations)
- [案例研究](#case-study)
- [日志](#logging)
- [贡献](#contributing)
- [贡献者](#contributors)
- [关于本项目](#about-this-project)
- [许可证](#license)
## FuzzMing 的功能
- **零样板代码:** 给它一个 `.sol` 文件,它就能从零开始生成完整的 handler + invariant 测试套件
- **持续审计:** bug 不会中断会话,FuzzMing 会剥离损坏的 invariant,继续搜寻,并累积所有轮次中的每一项发现
- **多合约会话:** 一次运行中可针对多个合约,每个合约都有自己独立的并发 fuzzing 通道
- **任意强大的 LLM:** OpenRouter、Groq、OpenAI、Anthropic,只需一个标志即可切换提供商
- **编译错误恢复:** 预演阶段的 `forge build` 会在完整测试运行前立即捕获编译错误;该错误会被反馈给 LLM 并在下一轮重试
- **隔离的测试执行:** `foundry.toml` 中的 `[profile.fuzzming]` 部分设置了 `test = "test/fuzzming"`,因此 forge 只会运行 FuzzMing 生成的测试:你现有的测试套件永远不会被触碰
- **Bug 去重:** 每个唯一的破坏性 invariant 无论在多少轮中被触发,都只会被记录一次;最终报告绝不会因重复而虚报
- **报告中的 invariant 代码:** 每一项确认的发现都包含完整的 Solidity invariant 函数以及缩减后的调用序列:可以直接将其放入 Foundry 回归测试中
- **覆盖率反馈:** 在每个通过的轮次之后,LCOV 覆盖率缺口会被反馈给 LLM,以便它在下次写出更好的 invariant
- **迭代安全分析:** 补丁轮次包含一个专门的 LLM 审计步骤,该步骤会审查 fuzz 输出 + 确认的 bug,并在会话结束时打印清晰的发现摘要
- **交互式或无头模式:** 为首次使用的用户提供引导提示,`--defaults` / `--from-config` 可用于 CI pipeline
- **非破坏性配置修补:** 仅更新 `foundry.toml` 中的 fuzzming profile,保留你其余的配置
- **演示模式:** `fuzzming run --demo` 使用 mock adapter 运行完整的 UI,无需进行 LLM 调用,也不消耗 token
## 前置条件
| 需求 | 安装 |
|---|---|
| Rust stable (2021 edition) | [rustup.rs](https://rustup.rs) |
| Foundry (`forge`):Solidity 技术栈必需 | `curl -L https://foundry.paradigm.xyz \| bash` |
| LLM API key | OpenRouter, Groq, OpenAI, 或 Anthropic |
## 安装
```
cargo install fuzzming
```
或者从源码构建:
```
git clone https://github.com/AchrefHemissi/fuzzming
cd fuzzming
cargo install --path .
```
## 快速开始
导航到你的 Foundry 项目,然后运行:
```
fuzzming run
```
FuzzMing 会提示你输入目标合约、模型和 API key,然后将你的回答保存到 `fuzzming.config` 中,这样你就不必重复输入了。
### 非交互式 (CI / 脚本)
```
fuzzming run \
--targets src/Vault.sol \
--max-rounds 5 \
--model openrouter/anthropic/claude-3.5-sonnet \
--llm-key $OPENROUTER_KEY \
--defaults
```
### 从配置中读取所有内容
```
# 首次交互式运行将设置保存到 fuzzming.config
fuzzming run
# 所有后续运行都会跳过所有提示
fuzzming run --from-config
```
### 多个合约
```
fuzzming run --targets src/Vault.sol src/Token.sol src/Pool.sol --defaults
```
## 支持的 LLM 提供商
`--model` 前缀用于选择提供商。通过 `--llm-key` 或 `LLM_KEY` 传入匹配的 API key:
| 前缀 | 提供商 | 示例模型 |
|---|---|---|
| `openrouter/` | OpenRouter | `openrouter/anthropic/claude-3.5-sonnet` |
| `groq/` | Groq | `groq/llama-3.3-70b-versatile` |
| `openai/` | OpenAI | `openai/gpt-4o` |
| `anthropic/` | Anthropic | `anthropic/claude-3-5-sonnet-20241022` |
敏感信息可以通过环境变量提供,以避免它们留在 shell 历史记录中:
```
export LLM_MODEL=groq/llama-3.3-70b-versatile
export LLM_KEY=$GROQ_KEY
fuzzming run --targets src/Vault.sol --defaults
```
## fuzzming.config
首次运行时,FuzzMing 会在当前目录下创建一个 `fuzzming.config` 文件:
```
targets=src/Vault.sol
max_rounds=5
model=openrouter/anthropic/claude-3.5-sonnet
llm_key=sk-...
workspace_root=.
max_tokens=0
llm_timeout_secs=120
full_coverage_rounds=2
prompt_mode=guided
```
查看它(API key 已脱敏):
```
fuzzming config
```
删除它并重新提示:
```
fuzzming config --reset
```
## 子命令
| 命令 | 描述 |
|---|---|
| `fuzzming run` | 启动 fuzzing 会话 |
| `fuzzming guide` | 在终端打印完整的 CLI 参考 |
| `fuzzming report` | 打印上一次运行产物的摘要 |
| `fuzzming config` | 查看或重置已保存的 `fuzzming.config` |
### 全局标志
| 标志 | 描述 |
|---|---|
| `--help`, `-h` | 打印完整的 CLI 参考 |
| `--version` | 打印已安装的版本 |
### `fuzzming run`
针对一个或多个合约启动 fuzzing 会话。如果存在 `fuzzming.config` 则加载它,然后提示输入任何缺失的值。使用 `--defaults` 或 `--from-config` 可跳过所有提示。
| 标志 | 默认值 | 描述 |
|---|---|---|
| `--targets
` | - | 目标 `.sol` 文件的路径 |
| `--max-rounds ` | 10 | 每个合约的最大 fuzzing 轮次 |
| `--model ` | - | LLM 模型标识符(`LLM_MODEL` 环境变量) |
| `--llm-key ` | - | 模型提供商的 API key(`LLM_KEY` 环境变量) |
| `--workspace-root ` | `.` | Foundry 项目根目录 |
| `--max-tokens ` | 无限制 | LLM 每次调用可生成的最大 token 数 |
| `--llm-timeout-secs ` | 120 | 每次调用的 LLM 超时时间(秒) |
| `--full-coverage-rounds ` | 2 | 在停止前连续达到 100% 覆盖率的轮次 |
| `--defaults` | false | 跳过所有提示;使用标志和环境变量 |
| `--from-config` | false | 跳过所有提示;从 `fuzzming.config` 读取所有内容 |
| `--interactive` | false | 即使存在配置也强制进行交互式提示 |
| `--demo` | false | Mock 运行:完整 UI,无 LLM 调用,不消耗 token |
| `--verbose` | false | 启用详细跟踪日志 |
```
fuzzming run # interactive: prompts for missing values
fuzzming run --targets src/Vault.sol --max-rounds 5 # explicit flags, no prompts
fuzzming run --defaults --targets src/Vault.sol # skip prompts, use flags/env vars
fuzzming run --from-config # skip prompts, read from fuzzming.config
fuzzming run --interactive # force prompts even if config exists
fuzzming run --demo # mock run, no LLM calls
```
### `fuzzming guide`
打印完整的 CLI 参考和示例到标准输出。无标志。
```
fuzzming guide
```
### `fuzzming report`
打印上一次运行的摘要。读取上次会话期间写入的 `.fuzzming//` 产物,显示每个合约的覆盖百分比和已确认的 bug。
| 标志 | 默认值 | 描述 |
|---|---|---|
| `--workspace-root ` | `.` | 读取产物的 Foundry 项目根目录 |
```
fuzzming report
fuzzming report --workspace-root ./my-project
```
### `fuzzming config`
查看或重置已保存的 `fuzzming.config`。不带标志:打印所有已保存的键,并对 API key 进行脱敏。使用 `--reset`:删除该文件,以便下次运行时重新提示所有设置。
| 标志 | 默认值 | 描述 |
|---|---|---|
| `--reset` | false | 删除 `fuzzming.config`;下次运行将重新提示 |
```
fuzzming config # view saved settings (API key masked)
fuzzming config --reset # delete config and re-prompt on next run
```
## 工作原理
每轮 fuzzing 遵循以下顺序:
```
1. Reader : reads the target contract + previous-round artifacts
2. Security analysis (round 2+ only): separate LLM pass that reviews fuzz output + confirmed bugs
3. Generator : assembles a prompt, calls the LLM, parses the response
4. Executor : writes Handler.sol + InvariantTest.sol; patches foundry.toml with
`test = "test/fuzzming"` so forge only sees generated tests
5. Fuzzer : runs `forge build` (fast compile check), then `forge test`
both scoped to `test/fuzzming/` via the profile's `test` key
6. Orchestrator: accumulates bugs (one entry per unique invariant name), strips confirmed invariants, checks termination
7. Reporter : emits a formatted findings summary when a contract's session ends
```
会话在**完全覆盖或耗尽轮次**时结束,而不是在发现第一个 bug 时。当 invariant 被破坏时,FuzzMing 会将其记录下来,从下一轮的测试中移除它,并继续搜寻更多 bug。
### 轮次结果
| 结果 | 操作 |
|---|---|
| Bug 确认 | 记录 bug,剥离损坏的 invariant,继续 |
| 编译错误 | 将编译器输出反馈给 LLM,在下一轮重试 |
| 开发者测试失败 | 将错误反馈给 LLM,在下一轮重试 |
| 达到完全覆盖 | 停止:没有更多缺口需要覆盖 |
| 耗尽轮次预算 | 报告所有轮次中发现的所有 bug |
## 局限性
FuzzMing 通过生成数以千计的随机调用序列并检查每一步之后属性是否成立来发现 bug。这种方法存在已知的盲区:无论运行多少轮,invariant fuzzing 在结构上都无法检测到的某些类别的 bug。
### 1. 没有可观察行为差异的 bug
如果一个 bug 改变了执行的内部代码路径,但总是产生相同的输出,那么没有任何 invariant 会失败。不存在错误版本和正确版本在返回值或存储更改上不一致的状态。
**示例:** 一个使用了错误变量的冗余前置检查:但其下方的 try/catch 无论如何都会处理所有的失败情况。两条路径都返回 `0`。FuzzMing 无法编写在此处失败的规则,因为合约的可观察行为在有或没有该 bug 的情况下是完全相同的。要检测到这一点,需要进行静态分析:即一种阅读代码结构并标记“这两个连续的块总是产生相同结果”的工具。
### 2. 测试期间永远无法执行的代码中的 bug
某些代码路径受 `tx.origin`(启动交易的钱包地址)控制。在 Foundry invariant 测试中,`tx.origin` 始终是测试合约自身的地址,而不是真实的用户钱包。如果错误代码仅在特定注册地址为 `tx.origin` 时运行,fuzzer 将永远不会触发它:测试合约永远不会在相关的 mapping 中,因此条件始终为 false,并且在每一次调用中都会跳过该代码块。
FuzzMing 通过 Rule 21 和专门的 `tx_origin_paths` 分析字段来处理此问题:当在源码中检测到 `tx.origin` 时,会指示 LLM 在 handler 内部使用 `vm.prank(addr, addr)` 调用目标:这种双参数形式会同时设置 `msg.sender` 和 `tx.origin`:然后将结果存储在 ghost variable 中供 invariant 检查。这种模式成功确认了 DynamicSwapFeeModule 案例研究中与折扣相关的 bug。
**剩余风险:** 无论调用者身份如何,依赖于 `tx.origin` 的路径永远无法到达的合约,或者所需的先决状态过于苛刻,导致 fuzzer 无法在轮次预算内偶然发现的合约。
### 3. 需要特定链知识的 bug
FuzzMing 读取合约并按原样使用其常量。如果一个硬编码的常量对于合约实际将要部署的链来说是错误的值,FuzzMing 则无从知晓。这些知识完全存在于合约之外。
**示例:** 一个设置为 `2` 的常量,注释写着“必须等于出块时间”。合约内部是一致的:`2` 在各处的使用方式相同。但目标链每 0.45 秒产生一个区块,而不是每 2 秒,这使得该常量大了 4 倍。对合约进行再多的 fuzzing 也无法揭示这一点。解决方法是使用一个 `--chain` 标志,为目标链加载已知参数(出块时间、gas limit、预言机模式),以便分析阶段可以将硬编码常量与实际世界的值进行比较。
### 4. 需要两个对抗性参与者的 bug
FuzzMing 的 invariant 测试使用单个参与者随机调用函数。它没有模拟一个地址蓄意试图损害另一个地址的情况。需要协调多笔交易序列的攻击(即攻击者在受害者的交易之前移动状态,导致受害者支付更多或收到更少)对单参与者模型来说是不可见的,无论运行多少轮次皆是如此。
**示例:** 使用实时现货价格而不是时间平均价格的费用公式。攻击者可以执行一次大规模 swap,将现货价格推离平均水平,从而夸大对同一区块中随后发生的任何 swap 收取的费用。攻击者会赔钱:这是一种纯粹的恶意破坏攻击。发现它需要两个参与者:一个以对抗性的方式移动状态,另一个检查受害者支付的费用是否超过了公平阈值。这更接近于博弈论模拟,而不是属性测试,需要专门的多参与者对抗模式。
### 总结
| 局限性 | 状态 | 什么能捕获它 |
|---|---|---|
| Bug 未产生可观察到的差异 | 待处理 | 静态分析:代码 linter 或形式化验证工具 |
| `tx.origin` 门控的代码路径 | 已处理:Rule 21 + `vm.prank(addr, addr)` | 在 DynamicSwapFeeModule 中确认了折扣相关的 bug |
| 针对特定链的错误常量 | 待处理 | 包含已知链参数的 `--chain` 标志 |
| 攻击需要两个对抗性参与者 | 待处理 | 多参与者对抗性模拟模式 |
这些局限性在 [DynamicSwapFeeModule 案例研究](docs/case-study-dynamicswapfeemodule.md) 中有详细记录,该研究在同一个 161 行的合约上对 FuzzMing 与专业审计及 Claude Web 进行了基准对比。
## 案例研究
五种独立的方法针对同一个 161 行的 Solidity 合约(`DynamicSwapFeeModule.sol`)进行了基准测试。完整分析:各方法的发现、正面交锋对比、优势与局限性,以及一张五方汇总表:都在案例研究中:
**[docs/case-study-dynamicswapfeemodule.md](docs/case-study-dynamicswapfeemodule.md)**
## 日志
```
# 逐轮进度
fuzzming run --verbose --targets src/Vault.sol ...
# 细粒度追踪(通过 RUST_LOG)
RUST_LOG=debug fuzzming run --targets src/Vault.sol ...
```
## 贡献
FuzzMing 基于六边形架构构建,因此每种语言和 fuzzer 都是一等公民。添加新的技术栈(Rust、Vyper、Move、Echidna、Medusa、cargo-fuzz)意味着编写新的 adapter:协调器、会话循环、LLM 集成和报告格式永远不会改变。这就是核心设计理念所在。面向合作者的技术文档位于 [docs/](docs/):
| 文档 | 涵盖内容 |
|---|---|
| [docs/orchestrator.md](docs/orchestrator.md) | 会话循环、终止逻辑、轮次协调 |
| [docs/generator.md](docs/generator.md) | 3 阶段 LLM 调用链、prompt 组装、重试/修复 |
| [docs/executor.md](docs/executor.md) | 写入网关:Solidity 文件,foundry.toml |
| [docs/fuzzer.md](docs/fuzzer.md) | Forge 子进程、输出解析、覆盖率 |
| [docs/reader.md](docs/reader.md) | 读取网关:源文件、覆盖率上下文 |
| [docs/reporter.md](docs/reporter.md) | 报告格式化程序和输出 adapter |
| [docs/shared.md](docs/shared.md) | 共享数据层:模型、端口、请求、响应 |
| [docs/entry.md](docs/entry.md) | CLI 入口点:子命令、标志、退出代码 |
| [docs/composition.md](docs/composition.md) | 组合根:完整的装配图 |
| [docs/case-study-dynamicswapfeemodule.md](docs/case-study-dynamicswapfeemodule.md) | FuzzMing vs. Shieldify vs. Claude Web vs. Claude Code:12 个独立 bug,所有四种方式合计 0 个误报 |
要添加新的语言或 fuzzer,请查看 [docs/composition.md](docs/composition.md) 中的清单。
**如何贡献:**
1. Fork 仓库并从 `main` 创建一个分支。
2. 先阅读 [docs/shared.md](docs/shared.md):了解共享数据层是让你快速上手的最快方法。
3. 如果可能,将更改保留在一个组件内;跨组件的更改必须通过 `src/shared/` 进行。
4. 在提交 PR 之前运行 `cargo test`:fuzzer 集成测试要求已安装 Foundry。
5. 向 `main` 提交带有清晰描述(更改了什么以及为什么)的 PR。
## 贡献者
每一次贡献都很重要:代码、文档、bug 报告、想法。感谢每一位帮助 FuzzMing 成长的人。
## 关于本项目
FuzzMing **最初是** **[AchrefHemissi](https://github.com/AchrefHemissi)**、**[Dhia9030](https://github.com/Dhia9030)** 和 **[HanineKhemir](https://github.com/HanineKhemir)** 的**毕业工程项目**,他们是 **[INSAT: Institut National des Sciences Appliquées et de Technologie](https://insat.rnu.tn)** 的计算机工程系学生,并在 **[Dar Blockchain](https://darblockchain.io)** 的支持和指导下完成了该项目。
我们感谢在这个旅程中指导我们的每一个人:
**学术导师**
- **Ms. Lilia Sfaxi**
**行业导师**
- **Mr. Nadhir Abdelatif**
- **Mr. Ayoub Amer**
- **Mr. Anas Hammou**
他们的专业知识、反馈和鼓励使这个项目成为可能。谢谢你们 ❤️
## 许可证
获得 [Apache License, Version 2.0](LICENSE) 许可。
```
Copyright 2026 FuzzMing Contributors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
```
标签:C2, DLL 劫持, pocsuite3, 可视化界面, 大语言模型, 智能合约审计, 软件测试, 通知系统