Patsakas/Nemesis

GitHub: Patsakas/Nemesis

该项目是一个面向 C/C++ 库的自动化漏洞挖掘引擎,结合大语言模型、符号执行与模糊测试来实现端到端的安全缺陷发现。

Stars: 0 | Forks: 0

# NEMESIS [![CI](https://static.pigsec.cn/wp-content/uploads/repos/cas/ad/ad5834178f7599af9fdda11629d49cae07f2997beec49821b2920eff5bfd50e7.svg)](https://github.com/Patsakas/Nemesis/actions/workflows/ci.yml) [![License: MIT](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE) [![Python 3.11+](https://img.shields.io/badge/python-3.11%2B-blue.svg)](https://www.python.org/downloads/) [![Fuzzer: AFL++](https://img.shields.io/badge/fuzzer-AFL%2B%2B-orange.svg)](https://github.com/AFLplusplus/AFLplusplus) [![Solver: Z3](https://img.shields.io/badge/solver-Z3-8A2BE2.svg)](https://github.com/Z3Prover/z3) **面向软件安全缺陷的神经符号漏洞挖掘引擎** NEMESIS 是一个用于 C/C++ 库的自动化漏洞发现引擎。它将基于 LLM 的代码推理、符号验证 (Z3) 和覆盖率引导的模糊测试 (AFL++) 链接成一个单一的 pipeline,无需人工干预即可实现从*目标选择*到*崩溃分诊*的全流程。 你只需将其指向一个 C/C++ 项目。它会自动分析出哪些函数值得攻击,为每个函数编写 fuzzing harness,构建注入插桩的二进制文件,对其进行模糊测试,并仅反馈那些在未修改库上能够复现的崩溃。 ![NEMESIS 仪表板 — 已配置的目标](https://static.pigsec.cn/wp-content/uploads/repos/cas/8c/8c83e366d101b8cd782e8b9b4caa72a614f948e3c19602d8c6bc7c7a82d06406.png) 该项目附带一个 Web 仪表板:你可以并排浏览库中的每个函数及其 **OSS-Fuzz 覆盖率和 NEMESIS 达到的覆盖率**,固定值得攻击的目标,启动运行任务,并实时监视 AFL++。 ![单函数覆盖率与固定](https://static.pigsec.cn/wp-content/uploads/repos/cas/fc/fc1e0eec4fab8a803dbad00e4bd51c32804c605f5e7d55e1b6a876af502d94f7.png) ## 工作原理 NEMESIS 运行四个阶段,并通过反馈循环来改进失败的尝试。 ``` +------------------+ | Target Library | any C/C++ project +--------+---------+ | Stage 1: RECON - Scan the source tree (and optional OSS-Fuzz coverage data) - Find low-coverage functions that touch untrusted input - Rank by memory ops, pointer arithmetic, call depth, complexity | Stage 2: NEURAL (LLM) - Analyze each target: likely bug class, CWE, reachability blockers - Generate a format-specific AFL++ harness (3 temperature variants) - Extract an AFL dictionary (magic bytes) from the source | Stage 3: SYMBOLIC (Z3 + Build) - Verify reachability constraints with Z3 - Auto-fix harness compile errors (LLM repair, multi-pass retry) - Build an instrumented binary (AFL + ASan/UBSan, optional MSan/TSan) - Pre-fuzz profiling: confirm the harness actually reaches the target | Stage 4: FUZZING (AFL++) - Parallel AFL++ instances with format-specific seeds + dictionary - Crash triage: sanitizer classification, GDB backtrace, CWE mapping - Reproduce every crash against the clean, unmodified library | +--------+---------+ | Reproduces on | | clean library? |----- yes ----> Reported as a candidate +------------------+ | no Feedback loop (max N iterations) Diagnose why (0% coverage -> broken harness, coverage but no crash -> different approach) and send back to Stage 2 for refinement. ``` ### 为什么它能避免误报 每次崩溃都会在目标的**干净、从未修改过的检出**版本上重放。如果崩溃仅出现在已修补或注入插桩的构建版本中,但在干净的库上没有出现,则将其作为伪影丢弃。仅报告在真实库上能够复现的崩溃。 ### 超越“段错误”的检查器 当目标配置开启时,NEMESIS 能够检测到普通的 ASan 崩溃永远无法发现的 bug 类型: - **往返 / 差异检查器** — `decode(encode(x)) == x`,或将目标与参考实现进行比较,以捕获静默损坏和逻辑分歧。 - **输出不变量** — 每次操作后断言的逐格式安全契约。 - **MemorySanitizer** — 使用未初始化的值 (CWE-908)。 - **ThreadSanitizer** — 多线程 harness 中的数据竞争 (CWE-362)。 - **LeakSanitizer** — 内存泄漏,从运行后的 AFL 队列中抽样。 ## 验证 NEMESIS 通过**对已发布的 CVE 进行回测**来评估:将其指向库的易受攻击版本,并衡量它是否能独立重新发现该 bug 以及花费的时间。这些都是可复现的——因为基本事实是公开的。 | 库 | CVE | CWE | 首次崩溃时间 | 人工输入 | |---------|-----|-----|--------------------|-------------| | **libpng** | [CVE-2018-13785](https://nvd.nist.gov/vuln/detail/CVE-2018-13785) | CWE-369 除以零 (经由 CWE-190 溢出) | **60 秒** (整个 pipeline 22.7 分钟) | 函数名 + 文件路径 | | **cJSON** | [CVE-2023-53154](https://nvd.nist.gov/vuln/detail/CVE-2023-53154) | CWE-125 越界读取 | **81 秒** | 无 — 完全自动 | | **libtiff** | [CVE-2022-3970](https://nvd.nist.gov/vuln/detail/CVE-2022-3970) | CWE-190 溢出 → 堆 OOB 写入 | 34 分钟 | 函数名 + 文件路径 | **libpng** — 在 `pngrutil.c:3154` 处崩溃,这正是 NVD 条目中指明的确切位置。唯一的输入是固定的函数;NEMESIS 生成了 harness,发现并注入了绕过 libpng 自身防护所需的 `png_set_user_limits()` 调用,并自行合成了一个感知数据块的 PNG 变异器。 **cJSON** — 完全无需人工干预端到端完成。后来被 `--auto-sanitizer` 独立重新发现,该功能使用 LLM 对 sanitizer 配置进行排名,并将前两个作为单独的步骤运行(约 51 分钟,去重后合并为一项发现)。 **libtiff** — 这是反馈循环的一个很好的例证:第 0-2 次迭代生成的 harness 虽然调用了目标,但从未进入其主体(0% 行覆盖率,因为 `TIFFReadRGBATileExt` 在非分块输入上提前返回)。循环不断进行优化,第 3 次迭代中的第二次 LLM 修复生成了一个跳过非分块图像并预分配光栅缓冲区大小的 harness — 在模糊测试开始 60 秒内就出现了崩溃。 *坦诚的警告:* 这些崩溃是在 ASan 下手动复现并验证的;由于 libtiff 回调签名周围无关的 LLM 噪声干扰,自动发现条目失败,因此这不是像其他两个那样完全干净的端到端自动结果。 ### 目前尚不适用的情况 只有同时报告失败案例,回测才是诚实的。 - **狭窄的结构触发条件无法通过字节级变异触及。** libwebp 的 [CVE-2023-4863](https://nvd.nist.gov/vuln/detail/CVE-2023-4863) (Huffman 码长损坏) 和 lz4 的 [CVE-2021-3520](https://nvd.nist.gov/vuln/detail/CVE-2021-3520) (字面长度 token 链) 在 15 分钟的预算内**未能**被重新发现,尽管 harness 到达了正确的函数。原生的 AFL 字节翻转无法合成这些位模式。这正是前文所述促使开发格式感知变异器合成的原因 —— 它现在能生成符合规范的适配器,但执行成本仍然是一个瓶颈。 - **CVE 归因可能具有误导性。** libtiff CVE-2022-22844 被描述为通过 `TIFFFetchNormalTag` 进行的越界读取,但上游修复补丁修补的是 `tools/tiffset.c` 中的 argv 处理 —— bug 出在 CLI 工具中,而不是库中,且无法从任何内存文件 harness 中触达。报告中指出的是崩溃位置,而不是 bug 位置。 ## 快速开始 ### 前置条件 - Python 3.11+ - [AFL++](https://github.com/AFLplusplus/AFLplusplus) (模糊测试阶段) - [Z3](https://github.com/Z3Prover/z3) (符号验证) - `clang` / `afl-clang-fast`,以及目标自身的构建依赖 - 至少一个 LLM 提供商的 API 密钥(见下文) ### 安装 ``` git clone https://github.com/Patsakas/Nemesis.git cd Nemesis pip install -e ".[dev]" ``` `pip install -e` 会将 `nemesis` 命令放置在你的 PATH 中。如果找不到该命令(或者你安装到了与 PATH 上的解释器不同的解释器中 —— 这在 conda 环境中很常见),下文的所有命令也可以通过在仓库根目录下运行 `python -m nemesis.cli ` 来代替。 ### 配置 LLM 提供商 NEMESIS 使用多提供商链,并在达到速率限制时自动回退。复制模板并填入你拥有的任何密钥 —— 单个提供商即可运行。 ``` cp .env.example .env # 然后编辑 .env: # NVIDIA_API_KEY=... # NVIDIA NIM # GROQ_API_KEY=... # Groq(在 console.groq.com 有免费层级) # CEREBRAS_API_KEY=... # Cerebras # GOOGLE_AI_KEY=... # Gemini ``` ### 运行 ``` # 接入新库:从其 source tree 自动生成 target config nemesis onboard --source-root ~/libfoo --project-name libfoo # Scan mode:每个 target 的预算较短,target 数量多 —— 适合首次扫描 nemesis run --target libfoo --scan # 对单个 target 运行 Full pipeline nemesis run --target libfoo # Deep mode:scan -> score -> 对 top-N targets 进行更长时间的 deep-fuzz nemesis run --target libfoo --deep # 仅执行 Recon(只对 targets 排名,不进行 fuzzing) nemesis recon --target libfoo # 显示某个 target 完全解析后的 config nemesis config --target libfoo --show ``` 有用的参数: | 参数 | 功能 | |------|--------------| | `--timeout-hours N` | 每个目标的模糊测试预算 (`0.5` = 30 分钟)。覆盖 `--scan` 使用的 15 分钟。 | | `--max-targets N` | 在 N 个函数后停止。 | | `--auto-sanitizer` | 让 LLM 对 sanitizer 配置进行排名,并将排名靠前的配置作为单独的步骤运行。 | | `--stages 1,2` | 仅运行部分阶段。 | | `--resume` | 跳过上次运行已处理的函数。 | ## 其他命令 除了核心的 `onboard` / `run` 循环外,CLI 还提供了一些辅助工具。使用 `--help` 运行它们以获取完整的参数列表。 ### `nemesis scout` — 寻找值得模糊测试的目标 将未经过模糊测试的 C/C++ 解析器库作为新 bug 的候选目标进行排名。它会排除所有 OSS-Fuzz 已经持续模糊测试的项目,并根据可 fuzz 性和往返检查器潜力对其余项目进行评分,这也是最有可能发现新漏洞的地方。 ``` nemesis scout # top 25 candidates nemesis scout --round-trip-only # only where a differential oracle applies nemesis scout -n 50 --out scout.md # write a markdown report ``` ### `nemesis setup` — 准备目标的工作区 读取现有的目标配置,通过 rsync 同步工作副本,创建构建目录,并验证注入插桩和调试构建能够编译。请参阅[添加新目标](#adding-a-new-target)。 ``` nemesis setup -t libfoo nemesis setup -t libfoo --url https://github.com/some/libfoo # clone if source is missing nemesis setup -t libfoo --skip-build # workspace only ``` ### `nemesis verify-crashes` — 将真正的 bug 与补丁伪影区分开来 仅与 `patch` 策略相关。重新构建*未打补丁*的库并针对其重放每个崩溃:复现意味着真正的 bug,未能复现则意味着该崩溃是 LLM 的可达性补丁人为制造的。 ``` nemesis verify-crashes -t libfoo ``` ### `nemesis report` — 导出发现 ``` nemesis report # all findings, to stdout nemesis report --run-id --format markdown # one run, as markdown nemesis report --format json -o findings.json ``` ### `nemesis daemon` — 计划扫描 按计划在无人值守的情况下运行 pipeline,并可选择发送至 webhook。 ``` nemesis daemon --once # one cycle, then exit nemesis daemon -t all --interval 12 # every 12 hours nemesis daemon --schedule '0 2 * * *' # 2am daily, cron syntax ``` ## 添加新目标 NEMESIS 是**完全配置驱动**的 —— 添加库不需要修改代码,只需在 `config/targets/` 下放置一个 YAML 文件。 通常的流程: ``` git clone https://github.com/some/libfoo ~/libfoo_clean # onboard scans a real source tree nemesis onboard --source-root ~/libfoo_clean --project-name libfoo nemesis setup -t libfoo # prepare the work copy + verify the builds compile nemesis run -t libfoo --scan # first pass ``` `nemesis onboard` 会扫描源码并生成一个初始配置(构建命令、头文件路径、C 与 C++ 检测、依赖提示);随后你可以对其进行完善。`nemesis setup` 读取该配置以通过 rsync 同步工作副本,创建构建目录,并在你花费时间进行模糊测试之前检查注入插桩 + 调试构建是否能够真正编译 —— 传入 `--url `,如果缺少 `source_root`,它也会将其克隆(在全新的机器上很有用),或者传入 `--skip-build` 仅准备工作区。 最小的配置如下所示: ``` target: name: "mylib" source_root: "$HOME/mylib_clean" # pristine checkout — NEVER modified work_root: "$HOME/mylib_work" # working copy — patched per target (Strategy B only) build_dir: "$HOME/mylib_work/build_fuzz" debug_build_dir: "$HOME/mylib_clean/build_debug" build: configure: > rm -f CMakeCache.txt && CC=afl-clang-fast cmake .. -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_FLAGS="-g -fsanitize=address -Wno-error" make: "make -j$(nproc)" debug_configure: > rm -f CMakeCache.txt && CC=clang cmake .. -DCMAKE_BUILD_TYPE=Debug -DCMAKE_C_FLAGS="-g -O1 -fsanitize=address,undefined -Wno-error" debug_make: "make -j$(nproc)" library_name: "libmylib.a" # static library in the build tree link_libs: "-lz -lm -lpthread" # linker flags for the harness harness_includes: ["mylib.h"] # public headers the harness needs api_func_fixes: # correct known LLM API hallucinations wrong_api_name: "correct_api_name" magic_bytes: # format magic bytes -> AFL dictionary format_foo: ["FOO\x00"] recon_scoring: bonus_patterns: "mylib_parse_": 5.0 # boost high-value filename prefixes penalty_files: ["utils.c", "log.c"] penalty_dirs: ["test", "examples"] penalty_funcs: ["_free", "_init"] fuzzing: timeout_hours: 0.25 seeds: formats: foo: "$HOME/nemesis/seeds/mylib/foo" ``` 有关完整、真实的示例,请参阅 `config/targets/libarchive.yaml`。 ## 架构 [ARCHITECTURE.md](ARCHITECTURE.md) 进行了更深入的探讨:保持 pipeline 与具体库无关的不变量、harness 生成和崩溃分诊的工作原理,以及塑造了它们的故障模式。 ``` nemesis/ ├── config/ │ ├── default.yaml # default engine parameters │ └── targets/ # per-target YAML overrides ├── nemesis/ # Python package │ ├── cli.py # Click CLI entry point │ ├── config.py # Pydantic settings + YAML layering │ ├── models.py # all shared data models (Pydantic v2) │ ├── pipeline.py # orchestrator + feedback loop │ ├── onboard.py # `nemesis onboard` — auto-generate a target config │ ├── reporter.py # findings database management │ ├── recon/ # Stage 1: source scan + OSS-Fuzz introspector + scoring │ ├── neural/ # Stage 2: LLM analysis, harness generation, RAG oracle │ ├── symbolic/ # Stage 3: Z3 verification, build, instrumentation │ ├── fuzzing/ # Stage 4: AFL++ orchestration, crash triage, coverage │ ├── api/ # FastAPI backend for the dashboard (routes + models) │ └── templates/ # harness scaffolding (FuzzedDataProvider, etc.) ├── frontend/ # React dashboard (Vite), served by the API in production ├── seeds/ # seed corpora per format ├── tests/ # pytest suite └── pyproject.toml ``` ### 配置分层 `config/default.yaml` → `config/targets/.yaml` → `NEMESIS_*` 环境变量。 目标配置覆盖默认配置;环境变量覆盖一切。 ### 双仓库模型 每个目标都有两个根目录。**干净的**检出 (`source_root`) 从未被修改,用于崩溃复现。**工作**副本 (`work_root`) 仅由可选的补丁策略触碰。默认的 harness 策略直接从干净的源码构建,从不进行任何修补。 ## Web 仪表板 可选的 React 仪表板 (`frontend/`) 与 FastAPI 后端通信,该后端读取与 CLI 生成的相同的发现数据库、运行结果和实时 AFL 统计信息: - **目标** — 库中的每个函数,其 OSS-Fuzz 覆盖率与 NEMESIS 达到的覆盖率并排显示;勾选一个以固定它(写入 `config/targets/.yaml`,保留注释),并打开固定行以调整检查器和 harness 参数。从这里启动运行任务,并选择每个目标的模糊测试预算。 - **实时** — 通过 websocket 传输的 AFL++ 统计信息:execs/sec、语料库、崩溃、挂起。 - **操作** — 运行 `onboard`、`setup`、`recon`、`scout` 和 `verify-crashes` 并跟踪其输出。这些命令通过固定的服务端白名单底层调用相同的 CLI,并且每个字段都作为单独的 argv 条目传递,而不是作为 shell 字符串传递。 - **运行 / 报告** — 运行历史记录(包含逐函数的结果)和披露草稿。 一个运行任务会在最初的几分钟里进行探测、harness 生成和注入插桩的构建 —— 远在 AFL 产生任何统计信息之前。pipeline 会持续推进并写入心跳信息,因此横幅会显示当前处于哪个阶段、正在处理的函数以及目标列表的进度。它同样适用于从 CLI 启动的运行任务,并会将进程已死亡的运行报告为“陈旧”状态,而不是让其永远处于“运行中”。 ![运行进度](https://static.pigsec.cn/wp-content/uploads/repos/cas/a9/a9ad48f790d02b4d6f8eef05eb23bbf5fcd97e8bd9dc30635eb6842672f6e180.png) ![选择模糊测试预算](https://static.pigsec.cn/wp-content/uploads/repos/cas/94/949c307b3eaba495d79f12f87a1563421d258baffb8949430922ced6ca0cfbea.png) ![操作](https://static.pigsec.cn/wp-content/uploads/repos/cas/26/26313044d329dd0927b2d662a2e3bfd91e4e18c6c465029264501ab1e59557f1.png) 仪表板仅以源码形式提供 —— `frontend/dist/` 是构建产物,不在仓库中,因此**在提供服务前构建一次**,否则 `/` 将返回 404(`/api` 路由仍然有效)。 **单端口(推荐):** ``` cd frontend && npm install && npm run build && cd .. # build the UI once pip install -e ".[web]" nemesis serve # UI + API together on http://localhost:8000 ``` **双端口(开发模式,热重载):** ``` nemesis serve # terminal 1 — API on :8000 cd frontend && npm run dev # terminal 2 — UI on :5173, proxies /api → :8000 ``` 在仓库根目录下运行 `nemesis serve`它会解析相对于工作目录的 `config/targets/`、`workspace/` 和 `findings.yaml`。如果 `nemesis` 脚本不在你的 PATH 中,`python -m nemesis.cli serve` 是等效的。 仪表板从端到端都是数据驱动的 —— 如果还没有运行任务或发现,它将渲染空状态而不是示例数据。视图是可寻址的 (`#/targets/libpng`, `#/live`),因此你可以直接为它们添加书签或链接。 | | | |---|---| | ![实时 AFL++ 统计](https://static.pigsec.cn/wp-content/uploads/repos/cas/05/0567ee78994c1fd6dad353fe555bd2cd4b18bd53b8b03e73791b0e24caf7406b.png) | ![运行历史](https://static.pigsec.cn/wp-content/uploads/repos/cas/4e/4eebe5426cb05b19c2a633804030dbe55abe7030e61228ccbd9091414cc15899.png) | | **实时** — 通过 websocket 传输的 execs/sec、语料库增长、崩溃和挂起 | **运行** — 每次 pipeline 执行,包含逐函数结果 | ## 开发 ``` pytest tests/ -v # run the test suite ruff check nemesis/ tests/ # lint ruff format nemesis/ tests/ # format ``` ## 路线图 - [x] 配置驱动的 pipeline — 零代码修改添加目标 - [x] `nemesis onboard` — 从源码树自动生成目标配置 - [x] 集成 OSS-Fuzz 语料库作为种子 - [x] 深度模糊测试模式 (扫描 → 评分 → 对前 N 名进行深度模糊测试) - [x] 多检查器支持(往返、差异、MSan、TSan、LSan、不变量) - [x] 自动选择 sanitizer(由 LLM 排序,多步执行) - [ ] 自动检测除 cmake 之外的构建系统(autotools、meson) - [ ] 结构感知变异(protobuf、ASN.1、归档文件头) - [ ] 污点引导的模糊测试:追踪哪些输入字节触达了目标 - [ ] Git 历史分析:最近更改的函数、过去的 bug 位置 - [ ] 自动起草漏洞报告 + PoC ## 负责任地使用 NEMESIS 是一款安全研究工具,用于在**你获得授权**进行测试的软件中寻找内存安全 bug。如果你使用它在第三方项目中发现漏洞,请遵循协同披露原则:私下向维护者报告,并给予他们在任何公开披露之前修复漏洞的时间。 ## 作者 **Georgios Patsakas** — https://github.com/Patsakas ## 许可证 [MIT](LICENSE)
标签:C2, DLL 劫持, 大语言模型, 神经符号AI, 逆向工具, 防御框架