综述
Radamsa是一个可以用于健壮性测试的测试用例生成器(aka fuzzer,模糊器)。它通常用来测试程序对格式错误或者潜在恶意输入的承受能力。它的工作原理是读取有效数据的示例数据并基于这些数据生成各种“有趣“的输出。通常被用于模糊测试(Fuzz)中。
Radamsa 的主要卖点是它已经在实践中发现了大量重要的错误。它易于编写脚本,且易于启动和运行。
模糊测试(Fuzz)是什么
模糊测试(Fuzz)是从程序中发现意外行为的技术之一。Fuzz的想法是让程序接受各种不同类型的输入,看看会发生什么。整个测试过程分为两部分:
1、构造各种不同类型的输入
2、查看系统返回结果
Radamsa是第一部分的一种解决方案,而第二部分通常只需要一个简短的shell脚本。
测试人员通常会有一个模糊的感觉哪些行为是程序不应该发生的,他们会尝试去验证这些点。这类的测试被认为是负面测试(negative testing),是所谓的正面测试(positive testing)如单元测试,集成测试的反面。开发人员知道服务不应该崩溃,不应该消耗过多的内存,不应该陷入死循环等等,攻击者知道他们可能会将某些类型的内存安全问题转化为可利用的漏洞。理论上来说,如果一个程序声明对于所有的输入,某些状况是不会发生的,那么模糊测试的思路就是找到一个反例来证明这个程序的声明是不正确的。
模糊器(fuzzer)的类型有很多。有些通过跟踪目标程序并根据其行为生成测试用例,有些需要知道数据的格式并根据该信息生成测试用例。有人总结过Fuzzing引擎的算法中,测试用例的生成方式主要有2种:
1)基于变异:根据已知数据样本通过变异的方法生成新的测试用例;
2)基于生成:根据已知的协议或接口规范进行建模,生成测试用例;
一般Fuzzing工具中,都会综合使用这两种生成方式。Radamsa 是一个极其“黑箱”的模糊器,因为它不需要有关程序的信息,也不需要数据的格式,从表现看是基于变异的一种模糊器。
Radamsa 旨在成为适用于各种数据的通用模糊器。无论处理的是何种数据,xml或者mp3,都能通过Radamsa 发现问题。这是通过Radamsa 的启发式和更改模式来实现的。在测试过程中,这些模式会发生变化:有时候只有一处更改,有时候会有非常多的更改;有时只是一些翻转,有时会有更多高级和新颖的变化。
使用Radamsa
Radamsa 可以被认为是类似cat 的UNIX 工具,它可以在数据处理时以“有趣“的方式重构数据。它还支持一次生成多个输出,并在必要时充当 TCP 服务器或客户端。我们通过一些例子来展示Radamsa的功能。
首先我们尝试改写"aaa":
如果我们再执行一次:
默认情况下 Radamsa 会从 /dev/urandom 获取一个随机种子,如果它没有被设定一个特定的随机状态作为开始,你通常会在每次执行后看到不同的结果,当然,对于一些简单输入,你可能会看到相同的结果。
Radamsa 可以使用 -s 参数指定要使用的随机种子,该参数后跟一个数字,使用相同的随机种子将生成相同的数据。
你也可以使用 -n 参数生成多个输出,如下所示:
Radamsa不能保证所有输出都是唯一的。但一般来说,当样本不是极简单时,相同输出往往非常罕见。
到目前为止,我们所了解到的内容可以帮助我们用来测试Linux的bc命令:
或者来测试解压缩功能:
$ gzip -c /bin/bash | radamsa -n 1000 | gzip -d > /dev/null
如果我们希望测试脚本持续运行,可以使用一个loop循环:
$ gzip -c /bin/bash > sample.gz $ while true; do radamsa sample.gz | gzip -d > /dev/null; done
请注意,我们在这里将示例作为文件提供,而不是在管道中运行 Radamsa。该测试针对 gzip 使用模糊数据,但并不关心会发生什么。找出(简单的单线程)程序是否出现异常的一种简单方法是检查退出值是否大于 127,这表示程序被异常终止:
$ gzip -c /bin/bash > sample.gz $ while true do radamsa sample.gz > fuzzed.gz gzip -dc fuzzed.gz > /dev/null test $? -gt 127 && break done
只要 gzip 不崩溃,这个脚本将一直运行;如果脚本停止,fuzzed.gz 可用于检查问题。我们发现过几个这样的案例,最近的一个案例是花了大约3个月的时间才找到,它们会被提交为bug,并被上游及时修复。
需要注意的一件事是,因为输出大多是基于给定样本(标准输入或命令行给出的文件)中的数据,因此找到好的样本非常重要,最好是多个样本。
在更真实的测试脚本中,Radamsa 通常用数以万计的样本一次生成多个输出,并且输出的结果以并行的方式用于测试,通过将每个输出提供给目标程序的命令行。我们将为 bc 制作一个这样的简单脚本,它接受来自命令行的文件。-o 参数可以用来给出一个文件名,Radamsa 会把输出写入到这个文件中。如果生成多个输出,路径中应该有一个 %n 作为输出的数字。
$ echo "1 + 2" > sample-1 $ echo "(124 % 7) ^ 1*2" > sample-2 $ echo "sqrt((1 + length(10^4)) * 5)" > sample-3 $ bc sample-* < /dev/null 3 10 5 $ while true do radamsa -o fuzz-%n -n 100 sample-* bc fuzz-* < /dev/null test $? -gt 127 && break done
这个脚本将会再次运行,直到发生异常退出,或者目标程序卡住。
在实践中,许多程序会以各种方式失败。常见的捕获错误的方法有检查退出值,在内核中启用致命信号打印并检查 dmesg 中是否出现新的东西,在 strace、gdb 或 valgrind 下运行程序并查看是否捕获了一些“有趣“的东西,检查启动程序后是否出现错误报告程序等等。
上面的例子要么是写入标准输出,要么写入文件。还可以通过使用 -o 的参数使 Radamsa 成为 TCP 客户端或服务器。你可以使用例如tcpflow 将 TCP 流量记录到文件中,然后可以将其用作 Radamsa 的样本来产生新的数据。
相关工具
GDB (http://www.gnu.org/software/gdb/)
Valgrind (http://valgrind.org/)
AddressSanitizer (http://code.google.com/p/address-sanitizer/wiki/AddressSanitizer)
strace (http://sourceforge.net/projects/strace/)
tcpflow (http://www.circlemud.org/~jelson/software/tcpflow/)
American fuzzy lop (http://lcamtuf.coredump.cx/afl/)
Zzuf (http://caca.zoy.org/wiki/zzuf)
Bunny the Fuzzer (http://code.google.com/p/bunny-the-fuzzer/)
Peach (http://peachfuzzer.com/)
Sulley (http://code.google.com/p/sulley/)
还没有评论,来说两句吧...