input corpus
收集语料库
对于模糊测试工具而言 ,浅谈我们需要为其准备一个或多个起始的何进输入案例
,这些案例通常能够很好的行有效测试目标程序的预期功能 ,这样我们就可以尽可能多的规整覆盖目标程序。
收集语料的浅谈来源多种多样。通常目标程序会包含一些测试用例
,何进我们可以将其做位我们初始语料的行有效一部分,此外互联网上也有些公开的规整语料库你可以收集他们做为你的需要。
关于语料库的建站模板浅谈主动性选择 ,这个更多需要你对fuzzing 目标内部结构的何进了解 。例如你当你要fuzzing的行有效目标对随着输入的规模内存变化非常敏感,那么制作一批很大的规整文件与较小的文件可能是一个策略,具体是浅谈否是否有效取决于你经验、以及对目标的何进理解。
此外,服务器租用行有效需要注意控制语料库的规模,太过庞大的语料库并不是好的选择 ,太过潘达的语料库会拖慢fuzzing的效率,尽可能用相对较小的语料覆盖更多目标代码的预期功能即可 。 语料库唯一化
我们在上一小节最后提到一点,太过庞大的语料库会因为有太多的测试用例重复相同的源码库路径覆盖
,这会减慢fuzzing的效率
。因此人们制作了一个工具,能够使语料库覆盖的路径唯一化,简单的说就算去除重复的种子输入
,缩减语料库的规模 ,同时保持相当的测试路径效果。
在AFL++中可以使用工具afl-cmin从语料库中去除不会产生新路径和覆盖氛围的重复输入 ,并且AFL++官方提示强烈建议我们对语料库唯一化
,云计算这是一个几乎不会产生坏处的友谊操作。
具体的使用如下:将收集到的所有种子文件放入一个目录中
,例如 INPUTS运行 afl-cmin:字典
其实将字典放到这一个大节下面不是很合适
,因为字典可以归类为一种辅助技巧 ,不过因为字典影响输入,所以我就将其划到这里了
。
关于是否使用字典,取决于fuzzing的目的与目标。免费模板例如fuzzing的目标是ftp服务器,我们fuzzing的目的是站在用户的视角仅能输入命令,因此我们的输入其中很大一部分可以规范到ftp提供的命令,我们更多的是通过重复测试各种命令的组合来测试目标ftp服务器在各种场景都能正确运行。
又比如
,高防服务器当你fuzzing一个很复杂的目标时,它通常提供一个非常非常丰富的命令行参数,每一次运行时组合不同的参数可能会有更好的覆盖效果
,因此可以将你需要启用的参数标记为字典添加进命令行参数列表中。
最后
,目标程序可能经常有常量的比较和验证,而这些环节通常会使得fuzzing停滞在此
,因为模糊器的变异策略通常对应常量的猜测是非常低效的
。我们可以收集目标程序中使用到的常量,定义为一个字典提供给模糊器
。但目前对于AFL++来说有更好的方法解决这种需求 ,而无需定义字典,后面我们会介绍这些方法
。 复制# 模糊器默认的变异策略通常难以命中if分支为true的情况,因为input做为64位,其值的空间太大了 ,根本难以猜测
。 if (input = 0x1122336644587) { crash(); } else { OK(); }1.2.3.4.5.6.7. 编译前的准备
选择最佳的编译器
如我们上一节中谈到收集程序常量定义字典时
,事实上收集常量并生成字典这个事情
,在编译时完全可以顺便将其解决
。没错 ,功能强大的编译器可以使我们在编译期间获得非常多有用的功能
。对于AFL++的编译器选择
,官方提供了一个简单的选择流程 ,如下
复制+--------------------------------+ | clang/clang++ 11+ is available | --> use LTO mode (afl-clang-lto/afl-clang-lto++) +--------------------------------+ see [https://github.com/AFLplusplus/AFLplusplus/blob/stable/instrumentation/README.lto.md](https://github.com/AFLplusplus/AFLplusplus/blob/stable/instrumentation/README.lto.md) | | if not, or if the target fails with LTO afl-clang-lto/++ | v +---------------------------------+ | clang/clang++ 3.8+ is available | --> use LLVM mode (afl-clang-fast/afl-clang-fast++) +---------------------------------+ see [https://github.com/AFLplusplus/AFLplusplus/blob/stable/instrumentation/README.llvm.md](https://github.com/AFLplusplus/AFLplusplus/blob/stable/instrumentation/README.llvm.md) | | if not, or if the target fails with LLVM afl-clang-fast/++ | v +--------------------------------+ | gcc 5+ is available | -> use GCC_PLUGIN mode (afl-gcc-fast/afl-g++-fast) +--------------------------------+ see [https://github.com/AFLplusplus/AFLplusplus/blob/stable/instrumentation/README.gcc_plugin.md](https://github.com/AFLplusplus/AFLplusplus/blob/stable/instrumentation/README.gcc_plugin.md) and [https://github.com/AFLplusplus/AFLplusplus/blob/stable/instrumentation/README.instrument_list.md](https://github.com/AFLplusplus/AFLplusplus/blob/stable/instrumentation/README.instrument_list.md) | | if not, or if you do not have a gcc with plugin support | v use GCC mode (afl-gcc/afl-g++) (or afl-clang/afl-clang++ for clang)1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.22.23. 若你的LLVM和clang版本大于等于11,那么你可以启用LLVM LTO模式,使用afl-clang-lto/afl-clang-lto++,该模式通常是最佳的 。随后依次是afl-clang-fast/afl-clang-fast++和afl-gcc-fast/afl-g++-fast。
关于为什么LTO模式通常是最佳的
,其中一个原因是它解决了原版AFL中边碰撞的情况 ,提供了无碰撞的边(edge)检测 。在原本AFL中 ,因为其对边(edge)的标识是随机的,对于AFL默认2^16容量来说,一旦程序足够大,边的标识会重复,这种现象就算边碰撞 ,它会降低模糊测试的效率。此外LTO模式会自动收集目标代码中的常量制作成为一个字典并自动启用
,并且社区提供的一些有用的插件和功能很多时候是要求LLVM模式(clang-fast)甚至是LTO模式(clang-lto)。 NOTE:此处涉及一点AFL度量覆盖率的工作原理,可以参考我注意的另一篇文章《基于覆盖率的Fuzzer和AFL》,写的很一般(逃
关于编译器的选择,如果可能直接选LTO模式即可。但你需要注意,LTO模式编译代码非常的吃内存,编译时间也会很久,尤其是启用某些Sanitizer的时候 。
NOTE:你的计算机配置最好至少由8核心 ,内存最好不低于16G。请注意8核心
,16G仍然不是很够用,最好32G,16核或以上,核心越多越好 。因为到时候你会编译很多不同版本的程序,不同的插件、不同的sanitizer、不同策略等等 ,这些不同的选项往往不能兼并到一个程序上,往往需要编译多分不同配置的程序,并你会经常patch程序再编译测试patch的效果。简言之,你会编译很多次程序,你需要足够大的内存和核心来编译目标
,使得你不必经常阻塞等待编译队列和结果。
编译的选项
AFL++是一个非常活跃的社区,AFL++会集成社区中
、互联网上一些强大的第三方插件
,这些集成的插件有一些我们可以通过设置对应的编译选项启用
。
对于LTO模式(afl-clang-fast/afl-clang-lto)进行编译插桩时,可以启用下面两项比较通用的特性 ,主要用于优化一些固定值的比较和校验
。Laf-Intel:能够拆分程序中整数
、字符串、浮点数等固定常量的比较和检测
。考虑下面一个情况assert x == 0x11223344,Laf-Intel会拆分为assert (x & 0xff) == 0x44 && ((x >> 8) & 0xff) == 0x33 ....这样形式 ,每一次只会进行单字节的比较,这样AFL就可以逐个字节的猜测,每当确定一个字节时
,就会发现一个新的路径
,进而继续在第一个字节的基础上猜测第二个字节 ,如此使得模糊器可以快速猜出0x11223344 。如果你没有自己制作好的字典、丰富的语料库,这个功能会非常有用,通常建议至少有一个AFL++实例运行Laf-Intel插件
。在编译前设置如下环境使用 :export AFL_LLVM_LAF_ALL=1CmpLog:这个插件会提取程序中的比较的固定值,这些值会被用于变异算法中。功能与Laf-Intel类似,但效果通常比Laf-Intel。使用该插件需要单独编译一份cmplog版本的程序,在fuzzing时指定该cmplog版本加入到fuzzing中 。具体的用法如下: 复制# 编译一份常规常规版本 cd /target/path CC=afl-clang-lto make -j4 cp ./program/path/target ./target/target.afl # 编译cmplog版本 make clean export AFL_LLVM_CMPLOG=1 CC=afl-clang-lto make -j4 cp ./program/path/target ./target/target.cmplog unset AFL_LLVM_CMPLOG # 使用cmplog, 用-c参数指定cmplog版本目标
,因为cmplog回申请很多内存做映射因此我们设置 # -m none,表示不限制afl-fuzz的内存使用 。你也可以指定一个值例如 -m 1024 ,即1GB 。 afl-fuzz -i input -o output -c ./target.cmplog -m none -- ./target.afl @@1.2.3.4.5.6.7.8.9.10.11.12.13.14.15. NOTE :需要注意,两个插件并不是说谁替代谁,往往在实际fuzzing中两者都会用至少一个afl实例启用。
考虑下面两种场景 。
有时候你想要fuzzing的目标中,他自动的集成了很多第三方的库代码,他们会在编译中一并编译,而你并不想fuzzing这些第三方库来,你只想高效、快速的fuzzing目标的代码,额外的fuzzing第三方代码只会拖慢你fuzzing的效率
。
有时候你的目标会非常庞大和复杂
,他们的构建往往是模块化的 ,有时候你只想fuzzing某几个模块 。
这上面两种情况都是我们fuzzing中很常遇见的
,所幸AFL++提供了部分插桩编译的功能 ,即"partial instrumentation",它允许我们指定应该检测那些内容以及不应该检测那些内容,这个检测的颗粒是代码源文件
、函数级两级
。具体用法如下 :检测指定部分。创建一个文件(allowlist.txt,文件名没有要求),需要在其中指定应包含检测的源代码文件或者函数。1.在文件中每行输入一个文件名或函数 复制foo.cpp # 将会匹配所有命名为foo.cpp的文件 ,注意是所有命名为foo.cpp的文件 path/foo.cpp # 将会只确定的包含该路径的foo.cpp文件,不会造成意外的包含 fun:foo_fun # 将会包含所有foo_fun函数1.2.3. 设置export AFL_LLVM_ALLOWLIST=allowlist.txt 启用选择性检测排除某些部分。与指定某些部分类似,编写一个文件然后设置环境变量export AFL_LLVM_DENYLIST=denylist.txt以启用,这会跳过我们文件中指定的内容。Note
:有些小函数可能在编译期间被优化,内联到上级调用者
,即类似于宏函数展开 。这时将会导致指定失效!如果不想受此影响 ,禁用内联函数优化即可
。
此外,对于C++由于函数命名粉碎机制
,你需要特别的提取粉碎后的函数名。例如函数名为test的函数可能会被粉碎重命名为_Z4testv
。可以用nm提取函数名,创建一个脚本筛选出来 。 添加Sanitizer检测更多BUG
Sanitizer最初是Google的一个开源项目 ,它们是一组检测工具。例如AddressSanitizer是一个内存错误检测器
,可以检测诸如OOB、UAF
、Double-free等到内存错误的场景。现在该项目以及成为LLVM的一部分 ,相对较高的gcc和clang都默认包含Sanitizer功能 。
由于AFL++基本只会检测到导致Crash的BUG
,因此启用一些Sanitizer可以使得我们检测一些并不会导致Crash的错误,例如内存泄露
。
AFL++内置支持下面几种Sanitizer :ASAN:AddressSanitizer ,用于发现内存错误的bug,如use-after-free、空指针解引用(NULL pointer dereference)
、缓冲区溢出(buffer overruns)、Stack And Heap Overflow、Double Free/ Wild Free、Stack use outside scope等 。若要使用请在编译前设置环境变量export AFL_USE_MSAN=1。更多关于ASAN的信息参与LLVM官网对ASAN
:AddressSanitizer的描述(https://clang.llvm.org/docs/AddressSanitizer.html)
。MSAN :MemorySanitizer,用于检测对未初始化内存的访问。若要启用 ,在编译前设置export AFL_USE_MSAN=1以启用。UBSAN
:UndefinedBehavior Sanitizer,如其名字一般用于检测和查找C和C++语言标的未定义行为 。未定义行为是语言标准没有定义的行为,编译器在编译时可能不会报错 ,然而这些行为导致的结果是不可预测的
,对于程序而言是一个极大的隐患。请在编译前,设置export AFL_USE_UBSAN=1环境变量以启用
。CFISAN:Control Flow Integrity Sanitizer ,CFI的实现有多种,它们是为了在程序出现未知的危险行为时终止程序,这些危险行为可能导致控制流劫持或破坏 ,用于预防ROP 。在Fuzzing中 ,CFISAN主要用于检测类型混淆
。请在编译前