模糊测试(Fuzzing),是一种通过向目标系统提供非预期的输入并监视异常结果来发现软件漏洞的方法。它是一种挖掘软件安全漏洞、检测软件健壮性的黑盒测试,它通过向软件输入非法的字段,观测被测试软件是否异常而实现。
进行软件漏洞挖掘时,通常有静态分析(Static Analysis)、动态分析(Dynamic Analysis)、符号执行(Symbolic Execution)、模糊测试(Fuzzing)这几种技术手段。
静态分析(Static Analysis)是指在不运行代码的方式下,通过词法分析、语法分析数据流分析等技术对程序代码进行扫描,验证代码是否满足规范性、安全性等指标的一种代码分析技术。静态分析的速度快,但是误报率高。
动态分析(Dynamic Analysis)是指逐步跟踪程序运行进行的分析。它的准确率很高,但是需要调试人员丰富的知识储备,而且这种调试方法很难进行大规模的程序漏洞挖掘。
符号执行(Symbolic Execution)简单来说,就是试图找到什么输入对应什么样的运行状态,它要去覆盖所有的执行路径。因此,当被分析的程序比较复杂,有很多执行路径时,就会遇到路径爆炸的问题。
模糊测试(Fuzzing)不需要过多的人为参与,也不像动态分析那样要求分析人员有丰富的知识。在模糊测试中,用随机数据攻击一个程序,然后等着观察哪里遭到了破坏。模糊测试的技巧在于,它是不符合常规逻辑的:自动模糊测试不去猜测哪个数据会导致破坏,将尽可能多的杂乱数据投入程序中。从而发现哪些输入能够使程序发生异常,进而分析可能存在的漏洞。当前比较成功的Fuzzer(执行模糊测试的程序)有AFL、libFuzzer、OSS-Fuzz等。
AFL(American Fuzzy Lop)是由安全研究员Michal Zalewski(@lcamtuf)开发的一款基于覆盖引导(Coverage-guided)的模糊测试工具,它通过记录输入样本的代码覆盖率,从而调整输入样本以提高覆盖率,增加发现漏洞的概率。
AFL工作流程大致如下:
系统:Ubuntu 20.04
AFL:afl-2.52b
下载AFL源码(https://github.com/google/AFL/),解压后,编译安装。
sudo make
sudo make install
用c语言编写一个测试用例,设置一些BUG和漏洞。
sudo vim easy_test.c
#include
#include
#include
#include
#include
int AFLTest(char *str)
{
int len = strlen(str);
if(str[0] == 'A' && len == 6)
{
raise(SIGSEGV);
//如果输入的字符串的首字符为A并且长度为6,则异常退出
}
else if(str[0] == 'F' && len == 16)
{
raise(SIGSEGV);
//如果输入的字符串的首字符为F并且长度为16,则异常退出
}
else if(str[0] == 'L' && len == 66)
{
raise(SIGSEGV);
//如果输入的字符串的首字符为F并且长度为66,则异常退出
}
else
{
printf("it is good!\n");
}
return 0;
}
int main(int argc, char *argv[])
{
char buf[100]={0};
gets(buf);
//存在栈溢出漏洞
printf(buf);
//存在格式化字符串漏洞
AFLTest(buf);
return 0;
}
对该源文件(easy_test.c)进行插桩编译(编译插桩是指在代码编译期间修改或新增代码)。
afl-gcc easy_test.c -o easy_test
可以看到在编译过程中,编译器已经提示存在漏洞,不理会,用AFL去测试。
从stdin读取输入的目标程序,语法如下:
./afl-fuzz -i testcase_dir -o findings_dir /path/to/program […params…]
从文件读取输入的目标程序来说,语法如下:
./afl-fuzz -i testcase_dir -o findings_dir /path/to/program @@
因此需要给程序设置testcase_dir和findings_dir两个文件夹,以及在testcase_dir中存放一些测试用例testcase。
mkdir fuzz_in
mkdir fuzz_out
cd fuzz_in
vim testcase
testcase中的输入可以随意写下(例如:aaa),接着就可以进行fuzz了。
afl-fuzz -i fuzz_in -o fuzz_out ./easy_test
经过5分钟的fuzz,共找到了7个crash,可以在fuzz_out中查看详情。
第1个crash:发现符合首字符为A且字符串长度为6的异常退出情况
第2个crash:发现符合栈溢出漏洞的crash情况
第3个crash:发现符合栈溢出漏洞的crash情况
第4个crash:发现符合栈溢出漏洞的crash情况
第5个crash:发现符合栈溢出漏洞的crash情况
第6个crash:发现符合首字符为L且字符串长度为66的异常退出情况
第7个crash:发现符合格式化字符串的%任意地址写入的漏洞情况
w3m是一个基于文本的网页浏览器,支持多种操作系统,在命令行终端可以很好的支持中文。即使在没有鼠标支持的情况下也可以检查网页的输出。w3m可以处理表、Cookie、身份验证以及除 JavaScript 以外的几乎所有内容。下图为用w3m访问www.qq.com。
该项目在GitHub上开源,https://github.com/tats/w3m,目前有300多个star,最近的一次代码更新在几天前,表明该项目一直在维护中。
下载源码
git clone https://github.com/tats/w3m.git
cd w3m
./configure CC="afl-gcc" CXX="afl-g++"
配置生成Makefile,出现报错configure: error: gc.h not found,提示缺少gc.h
查阅资料后发现,需要安装gc,解决方案如下:
cd gc-7.2
./configure
make
sudo make install
重新对w3m进行配置后,能成功生成Makefile。
./configure CC="afl-gcc" CXX="afl-g++"
查看Makefile,可以看到编译器为afl-gcc和afl-g++后,确认无误后,执行编译。
gedit Makefile
make
设置in和out文件夹,由于w3m是一款命令行浏览器,因此我们可以收集一些html文件放入in文件夹中作为测试用例。
由于是输入为文件,因此fuzz的命令如下:
afl-fuzz -i in -o out ./w3m @@
进行了一个多小时的fuzz,没有触发crash。原因可能是软件维护的比较好,bug较少,不容易找到。
本文主要对模糊测试进行了熟悉运用,依靠AFL工具完成了对简单例子的fuzz和对开源程序w3m的fuzz,并对简单例子中发现的crash进行了分析,了解报错的原因。经过实验,了解了利用AFL进行模糊测试的大概过程:①插桩编译;②设置种子测试用例;③开始fuzz。模糊测试对于软件测试的帮助很大,其不受限于被测系统的内部实现细节和复杂程度,例如,使用模糊测试可以不用关心被测对象的实现语言等细节,因此即使是并不怎么熟悉代码内部结构的程序员也能完成测试。但是若模糊器是以穷举方式产生各种可能的数据组合,则被测目标程序响应的检测时间将急剧增长,这使得检测费力且耗时。总之,模糊测试是一项用于验证程序中真实错误的重要工具,也是所有意识到安全性问题且着力于程序健壮性的程序员们的工具箱中所必备的工具。