### 对比一下 QEMU 模式的 Fuzzing 和源码模式的 Fuzzing

1. 源码模式 Fuzzing

源码模式 Fuzzing（也称为插桩式 Fuzzing）是在编译时对目标程序进行修改，插入额外的代码（即“插桩”）。这些桩点会在程序运行时收集代码覆盖率等信息，并反馈给 Fuzzer，指导其生成更有效的输入

工作原理

- 编译插桩：Fuzzer 使用专门的编译器前端（如 AFL-Clang 或 LLVM-sanitizer）来编译目标程序的源码
- 插入探针：编译器会在每个基本块（Basic Block）的开头插入一个探针。当程序执行到一个新的基本块时，探针会向 Fuzzer 反馈这个信息
- 反馈循环：Fuzzer 根据这些覆盖率信息，判断哪些输入探索了新的代码路径。它会保留这些有价值的输入，并对其进行变异，以期能找到更深层次的代码逻辑

优点

- 高效：插桩非常轻量级，几乎不会引入额外的性能开销。Fuzzer 能够以极高的速度运行和测试样本，每秒可达数千甚至数万次
- 精确的代码覆盖率：由于插桩在编译时完成，Fuzzer 能够获得非常精确的、基本块级别的代码覆盖率，这使得它能够更有效地探索代码路径
- 直接定位崩溃点：由于 Fuzzer 能够知道输入触发了哪段代码，一旦发生崩溃，它能迅速定位到崩溃发生的基本块

缺点

- 需要源码：这是最大的局限性。如果目标程序是闭源的，你就无法使用这种方法
- 编译复杂：对于复杂的项目，编译过程可能会很复杂，需要处理各种依赖和编译选项

2. QEMU 模式 Fuzzing

QEMU 模式 Fuzzing（也称为黑盒或二进制 Fuzzing）是在二进制级别进行插桩和监控。它使用 QEMU 模拟器来运行目标程序，并通过修改 QEMU 的代码来收集代码覆盖率信息。

工作原理

- 二进制插桩：Fuzzer 启动一个修改过的 QEMU 用户模式模拟器
- 动态翻译：QEMU 在运行目标程序时，会动态地将目标程序的机器码翻译成宿主机的机器码。在翻译过程中，Fuzzer 的插桩逻辑会被嵌入到生成的代码中
- 收集覆盖率：当翻译后的代码执行时，插桩逻辑会收集代码覆盖率信息，并反馈给 Fuzzer。

优点

- 无需源码：这是 QEMU 模式最大的优势。它能够 Fuzz 任何闭源的、可执行的二进制文件，这在分析恶意软件或商业软件时至关重要
- 全系统覆盖：除了用户态程序，QEMU 还可以模拟整个系统。这意味着你可以用它来 Fuzz 驱动程序或内核

缺点

- 性能开销大：由于 QEMU 是一个模拟器，它会引入大量的性能开销。Fuzzing 速度比源码模式慢得多，通常每秒只能运行几十到几百次
- 覆盖率信息粗糙：QEMU 模式通常只能提供基本块级别的覆盖率，但可能无法像源码模式那样精确地追踪到每一条指令的执行
- 不稳定性：由于 QEMU 本身的复杂性，Fuzzing 过程中可能会出现一些不稳定的情况

特性 | 源码模式 Fuzzing | QEMU 模式 Fuzzing
--- | --- | ---
是否需要源码 | 是 | 否
性能 | 极高（每秒数千次） | 较低（每秒数十次）
代码覆盖率 | 非常精确（基本块级别） | 较精确（基本块级别）
适用场景 | 开源项目、内部代码审计 | 闭源软件、恶意软件分析、驱动程序 Fuzzing
代表工具 | AFL++、LibFuzzer | AFL-QEMU