AFL提高性能的建议
1. 种子用例最好小于1KB
大型测试用例不仅需要更多的时间和内存被目标二进制文件解析,而且还会在其他方面显着降低 fuzzing 过程的效率。
为了说明,假设您正在随机翻转文件中的bit,一次一位。假设如果你翻转第47 位,你会遇到一个安全漏洞;翻转任何其他位只会导致无效文档。
现在,如果您的起始测试用例是 100B,那么您将有 71% 的机会在前 1,000 次执行中触发错误 - 不错!但如果测试用例的长度为 1 kB,我们在同一时间范围内随机命中正确模式的概率会下降到 11%。如果它有 10 kB ,几率会下降到 1%。
最重要的是,随着更大的输入,二进制文件运行速度可能比以前慢 5-10 倍——因此模糊测试效率的整体下降可能很容易高达 500 倍左右。
在实践中,这意味着你不应该用你的假期照片来fuzz图像解析器。生成一张 16x16 的小图片,然后通过 jpegtran 或 pngcrunch 运行它以获得良好的测量效果。大多数其他类型的文档也是如此。
…/testcases/* 中有很多小的起始测试用例 - 试用它们或提交新的!
如果您想从更大的第三方语料库开始,请先在该数据集上运行 afl-cmin 并设置较短的超时时限。
2. Fuzz更简单的目标
考虑在您的模糊测试工作中使用更简单的目标二进制文件。 例如,对于图像格式,djpeg、readpng 或 gifhisto 等捆绑实用程序比 ImageMagick 的转换工具快得多(10-20 倍)—— 但是它们执行大致相同的库级图像解析代码。
即使您没有针对特定目标的轻量级工具,请记住,您始终可以使用另一个相关的库来生成语料库,然后手动将其提供给更需要资源的程序。
3. 使用LLVM模式
在对慢速目标进行模糊测试时,您可以通过使用 llvm_mode/README.llvm 中描述的基于 LLVM 的检测模式获得 2 倍的性能提升。 请注意,此模式需要使用 clang,并且不适用于 GCC。
LLVM 模式还提供了一种“persistent”的进程内模糊测试模式,可以很好地适用于某些类型的自包含库,对于快速目标,可以提供高达 5-10 倍的性能提升; 以及“deferred fork server”模式,可以为具有高启动开销的程序提供巨大的好处。 这两种模式都要求您编辑模糊程序的源代码,但更改通常只是策略性地放置一两行。
4. 分析和优化二进制文件
检查任何明显提高性能的参数或设置。 例如,IJG jpeg 和 libjpeg-turbo 附带的 djpeg 实用程序可以通过以下方式调用:
-dct fast -nosmooth -onepass -dither none -scale 1/4
……这将加快速度。解码图像的质量会相应下降,但这可能不是你关心的事情。
在某些程序中,可以完全禁用输出,或者至少使用计算成本低的输出格式。例如,使用图像转码工具,转换为 BMP 文件将比转换为 PNG 快得多。
对于一些宽松的解析器,启用“严格”模式(即,在第一个错误后退出)可能会导致文件更小并在不牺牲覆盖范围的情况下提高运行时间;例如,对于 sqlite,您可能需要指定 -bail。
如果程序仍然太慢,您可以使用 strace -tt 或等效的分析工具来查看目标二进制文件是否在做任何愚蠢的事情。有时,您可以简单地通过将 /dev/null 指定为配置文件,或禁用某些工作并不真正需要的编译时功能(尝试 ./configure –help)来加快速度。众所周知的消耗资源的事情之一是通过 exec*()、popen()、system() 或等效调用调用其他实用程序;例如,当 tar 确定输入文件是压缩档案时,它可以调用外部解压缩工具。
有些程序也可能有意调用 sleep()、usleep() 或 nanosleep(); vim 就是一个很好的例子。其他程序可能会尝试 fsync() 等等。有第三方库可以轻松摆脱此类代码,例如:https://launchpad.net/libeatmydata
在由于不可避免的初始化开销而运行缓慢的程序中,您可能需要尝试 LLVM 下的deferred forkserver 模式(请参阅llvm_mode/README.llvm),如上所述,它可以使您的速度提升高达 10 倍。
最后但同样重要的是,如果您正在使用 ASAN 并且性能无法接受,请考虑暂时将其关闭,然后稍后使用启用 ASAN 的二进制文件手动检查生成的语料库。
5. 仅插桩有用的地方
只插桩你现在真正想要进行测试的库,一次一个。 让程序使用系统范围内的非插桩库来实现您实际上不想进行模糊测试的任何功能。 例如,在大多数情况下,它不会仅仅因为您正在测试一个依赖它来进行 bignum 数学运算的加密应用程序而对 libgmp 进行插桩。
谨防带有与其源代码捆绑在一起的古怪第三方库的程序(Spidermonkey 就是一个很好的例子)。 检查 ./configure 选项以改用非插桩 系统范围的副本。
6. 并行fuzz
fuzzer 设计为每个作业需要约 1 个核心。 这意味着在一个 4 核系统上,您可以轻松地运行四个并行的模糊测试作业,而性能影响相对较小。 有关如何执行此操作的提示,请参阅并行模糊测试提示。
afl-gotcpu 实用程序可以帮助您了解系统上是否仍有空闲 CPU 容量。 (它不会告诉您内存带宽、缓存未命中或类似因素,但它们不太可能成为问题。)
7. 设置内存使用和超时限制
如果您增加了 -m 或 -t 限制,超出了真正需要的范围,请考虑将它们调低。
对于名义上非常快但在某些输入上变得迟缓的程序,您还可以尝试设置 -t 值,使其比 afl-fuzz 的默认值更低。 在快速和空闲的机器上,降低到 -t 5 可能是一个可行的计划。
-m 参数也值得一看。 当出现病态输入时,某些程序最终可能会花费大量时间来分配和初始化 M 级别的内存。 低 -m 值可以使它们更快地放弃并且不会浪费 CPU 时间。
8. 检查操作系统配置
有几个操作系统级别的因素可能会影响模糊测试的速度:
系统负载高。 尽可能使用闲置的机器。 杀死任何不必要的 CPU 占用(空闲的浏览器窗口、媒体播放器、复杂的屏幕保护程序等)。
网络文件系统,用于 fuzzer 输入/输出,或由 fuzzed 二进制文件访问以读取配置文件(特别注意主目录 - 许多程序在其上搜索点文件)。
按需 CPU 扩展。 Linux 的“按需”调控器按特定的时间表执行分析,并且众所周知,它低估了由 afl-fuzz(或任何其他模糊器)产生的短期进程的需求。 在 Linux 上,这可以通过以下方式解决:
cd /sys/devices/system/cpu
echo performance | tee cpu*/cpufreq/scaling_governor
在其他系统上,CPU 缩放的影响会有所不同; 进行模糊测试时,使用特定于操作系统的工具来确定是否所有内核都在全速运行。
透明的大页面。 当在内核中启用透明大页 (THP) 时,某些分配器(例如 jemalloc)可能会导致严重的模糊测试损失。 您可以通过以下方式禁用此功能:
echo never > /sys/kernel/mm/transparent_hugepage/enabled
次优调度策略。 其意义因一个目标而异,但在 Linux 上,您可能需要确保设置了以下选项:
echo 1 >/proc/sys/kernel/sched_child_runs_first
echo 1 >/proc/sys/kernel/sched_autogroup_enabled
为 fuzzer 进程设置不同的调度策略——比如 SCHED_RR——通常也可以加快速度,但需要小心操作。
9. -d 跳过确定性模糊测试
对于真的很慢的程序,如果你真的无法使用巨大的输入文件来逃避,或者你只是想尽早获得快速而肮脏的结果,你总是可以求助于 -d 模式。
该模式会导致 afl-fuzz 跳过所有确定性模糊测试步骤,这会使输出变得不那么整洁,最终会使测试变得不那么深入,但它会给您带来与其他模糊测试工具更熟悉的体验。
10. 一些小建议
- 使用AFL的字典
- 使用 libdislocator.so 提高发现内存损坏错误的几率! 这很简单。 有关使用提示,请参阅 libdislocator/README.dislocator。
- -C 检查崩溃是否可用
- AFL 生成的语料库可用于支持其他测试过程
- 想要自动发现非崩溃内存处理错误?尝试通过 ASAN、MSAN 或 Valgrind 运行 AFL 生成的语料库。
- 您可以提高自动发现堆栈损坏问题的几率。在环境中指定 AFL_HARDEN=1 以启用强化标志。
- 遇到不可重现的崩溃问题?它会发生,但通常不难诊断。有关提示,请参阅解释输出的底部。
- Fuzzing 不仅仅是代码库中的内存损坏问题。添加一些完整性检查 assert() / abort() 语句以轻松捕获逻辑错误。