### 介绍一下 fuzz 的流程，从选取目标开始

**1. 选取目标**

Fuzzing 不是盲目的，你需要选择一个合适的、有价值的目标。好的目标通常具有以下特点：

- **处理复杂或不受信任的输入**：比如文件解析器、网络协议栈、命令行参数处理程序。这些程序是攻击者的首要目标，因为它们直接暴露在外部输入之下
- **高权限运行**：如果一个程序以 `root` 或 `SYSTEM` 权限运行，它将是更具吸引力的攻击目标
- **处理多种数据格式**：例如，一个视频解码器需要处理各种容器格式（MP4, AVI）、编码格式（H.264, VP9）等
- **有公开源码或已知的开源版本**：如果你有源码，可以使用更高效的**源码插桩**（Source-based Instrumentation）模式，如 AFL++ 或 LibFuzzer。如果没有，则需要使用**二进制插桩**（Binary Instrumentation）模式，如 QEMU

**2. 准备工作**

在开始模糊测试之前，需要做好充分的准备，这直接影响到 Fuzzing 的效率和成功率

- **获取种子文件（Seed Corpus）**：种子文件是模糊测试的起点。你需要收集一批高质量的、能代表正常输入的样本文件。这些样本应该尽可能地覆盖程序的不同功能。高质量的种子文件能显著提高 Fuzzing 效率
- **编译目标程序**：如果是源码模式，你需要使用 Fuzzer 专用的编译器（例如 `afl-clang-fast`）来编译目标程序。这会在程序中植入探针，用于收集代码覆盖率信息
- **创建 Fuzzing 脚本**：你需要编写一个脚本，作为 Fuzzer 和目标程序之间的**适配器（Harness）**。这个脚本负责将 Fuzzer 生成的输入数据传递给目标程序。对于文件输入，适配器通常很简单，可能就是 `read()` 或 `fopen()` 函数。对于更复杂的网络协议，适配器需要解析并发送数据包
- **配置 Fuzzer**：根据你的目标和环境，你需要选择并配置一个合适的 Fuzzer（例如 AFL++）。配置参数包括：
  - Fuzzing 模式（源码、QEMU、网络等）
  - Fuzzing 进程数量
  - 字典文件（如果目标程序有特定的关键字）

**3. Fuzzing 运行**

这个阶段是 Fuzzing 的核心，Fuzzer 将持续不断地生成、变异和测试输入

- **启动 Fuzzer**：运行 Fuzzer 进程，将种子文件作为输入，并指定输出目录
- **监控 Fuzzing 状态**：在 Fuzzing 过程中，需要持续监控其状态。好的 Fuzzer 都会提供一个状态界面，显示：
  - 测试速度（每秒执行次数）
  - 代码覆盖率
  - 发现的崩溃数量和异常（`timeout`）数量
  - 队列中已有的有价值的输入数量
- **分析结果**：当 Fuzzer 发现一个崩溃或异常时，它会将导致该问题的输入文件保存在一个特定的目录中。你需要定期检查这个目录，并对新发现的崩溃进行分类和分析

**4. 漏洞分析与验证**

这个阶段是**人工分析**的过程，目的是将 Fuzzer 发现的“崩溃”转化为可利用的“漏洞”

- **漏洞重现**：首先，你需要用调试器（如 GDB, WinDbg）或逆向工具（如 IDA Pro）来加载导致崩溃的输入文件，并**重现崩溃**。这能帮助你确定崩溃的类型和位置
- **漏洞分类**：将崩溃分为不同的类型，例如**栈溢出、堆溢出、空指针解引用**等。这有助于你理解漏洞的性质
- **可利用性分析**：并非所有的崩溃都是可利用的漏洞。你需要分析崩溃的原因和上下文，判断攻击者是否能通过它来劫持程序流或执行任意代码
- **漏洞报告**：一旦确认漏洞是可利用的，你需要编写一份详细的漏洞报告，包括：
  - 漏洞的描述和类型
  - 导致漏洞的输入文件
  - 崩溃发生时的堆栈信息
  - 漏洞的严重性评估

**5. 修复与回归测试**

这是整个流程的最后一步，也是最重要的

- **漏洞修复**：将漏洞报告交给开发者，由他们来修复代码中的缺陷
- **回归测试**：在修复完成后，你需要将导致漏洞的输入文件添加到你的测试套件中，确保未来的代码修改不会再次引入这个漏洞。这个过程也被称为**回归测试**