模糊测试(Fuzzing)是一种自动化软件测试技术。它通过将无效的、非预期的或随机数据作为程序参数的输入数据,然后监控程序是否有异常比如崩溃或潜在的内存泄漏等问题,以此来发现程序潜在的安全漏洞。Fuzzing技术的理论和应用已经很成熟,各种模糊测试工具(例如AFL、AFL++、honggfuzz、LibFuzzer、syzkaller等)的出现极大地降低了模糊测试的门槛,模糊测试也成为当前发现软件安全问题最强大的测试技术之一。
本文先编写存在安全问题的程序,然后使用两个常用的模糊测试工具来发现程序里存在的安全问题,通过实例来带领大家入门实操Fuzz。
AFL(American Fuzzy Lop)是由Google安全研究员Michał Zalewski开发的一款基于代码覆盖引导的模糊测试工具。
它的官方网址是http://lcamtuf.coredump.cx/afl/
它采用源码编译插桩和QEMU模式,通过记录输入样本的代码覆盖率,不断对输入数据进行变异来提高代码覆盖率,这是Fuzzing技术的一项重要改进,它使得模糊测试变得更具导向性,大大增强了发现漏洞的能力。在它之后很多模糊测试工具也相继添加基于代码覆盖引导的Fuzzing方式。
$ wget http://lcamtuf.coredump.cx/afl/releases/afl-latest.tgz
$ tar zxvf afl-latest.tgz
解压完后会出现文件夹afl-2.52b,我们再cd进去依次执行以下命令来编译和安装AFL
在新的命令行里面运行afl-fuzz,会出现以下图示内容说明AFL安装成功了。
接下来我们编写一个存在安全漏洞的小程序,再用AFL工具来fuzz测试一下,看它能不能发现里面存在的问题。
#include <stdio.h>
#include <string.h>
int main(int argc, char* argv[]) {
FILE *fp;
char line[1024];
char buf[20]={0};
int nums[10];
printf("startsn");
if(argc < 2) {
printf("argc < 2n");
return -1;
}
fp = fopen(argv[1], "r");
if(NULL == fp) {
printf("open file failedn");
return -1;
}
fgets(line, 1024, fp);
fclose(fp);
switch(line[8]) {
case 'A':
printf("case An");
strcpy(buf, line);
break;
case 'B':
printf("case Bn");
*(int*)0xffffffffabcdef10=80;
break;
case 'C':
printf("case Cn");
nums[10] = 100;
break;
default:
printf("defaultn");
break;
}
printf("endsn");
return 0;
}
代码里面的case A, B, C分别对应3个不同的安全问题。23行因为输入的line的长度可能比buf长导致溢出问题,27行给一个没访问权限的地址赋值,31行是数组越界操作。接下来用AFL来测试一下,看它能不能发现这3个问题。
要用AFL来测试,先要用AFL的工具把test1.c插桩编译成可执行程序test1_aflfuzz1:
$ afl-gcc test1.c -o test1_aflfuzz1
插桩编译生成的可执行程序运行时能知道代码的覆盖情况。
接下来要给被测试程序构造一些初始的测试文件,AFL会在这些文件的基础上对文件的内容进行变异,再将变异出来的新文件作为参数给被测试程序运行,看会不会引发安全问题。这些文件也叫测试用例或者种子文件。
新建一个样本文件夹samples_test,再在里面新建几个样本文件,这里我们随意输入文件内容。构造完的样本文件和内容的大概样子如下图所示。
有时我们也可能会在外面不同的网站上收集别人已经建立好的测试用例集,为了提高后续Fuzz效率,可以使用AFL自带的工具afl-cmin或afl-tmin来精简用例集。afl-cmin会把具有相同执行路径或者说代码覆盖的测试用例删除掉只保留一个,afl-tmin能用来减小单个测试文件的大小。因为前面我们构造的测试文件内容很简单,这里我们只执行以下命令用afl-cmin来精简下测试用例的数量,并把精简后的测试用例集保存到samples_cmin目录里,@@的意思是把测试用例作为参数传给被测试程序。
$ afl-cmin -i samples_test -o samples_cmin ./test1_aflfuzz1 @@
执行后可以看到精简后的测试用例只有一个文件,说明前面建立的4个测试文件都会执行相同的代码路径。
在执AFL fuzz之前需要执行以下命令修改一下系统设置,否则系统将core dump通知发送给外部程序,导致把崩溃信息发送给AFL工具的延迟变大,可能会让AFL工具把崩溃误认为是超时。
# echo core > /proc/sys/kernel/core_pattern
$ afl-fuzz -i samples_cmin/ -o aflfuzz_output ./test1_aflfuzz1 @@
可以看到AFL很快就发现了里面的2个不同的崩溃点(uniq crashes: 2)。
AFL的运行界面会展示Fuzzer运行时的一些信息。
右上角的overall results是Fuzzer当前状态的概述,通常主要关注uniq crashes,它表示模糊测试工具发现的不同崩溃的个数,这个数字和findings in depth里total crashes后面括号里的数字一个意思。total crashes括号前面的数字74代表产生的崩溃总次数,但只有2个不同的崩溃位置。Uniq hangs里面表示的是发现程序hang住的次数。
process timing包含Fuzzer的运行时长等时间信息。
stage progress包含现阶段文件变异策略、执行次数和执行速度信息。执行速度(exec speed)也是大家比较关注的信息,Fuzz的速度越快,就能越早发现漏洞。
fuzzing strategy yields是数据变异的相关信息。
path geometry是找到的执行路径的相关信息。
afl-fuzz的输出目录的crashes目录里包含了导致崩溃的测试文件
查看两个测试文件的内容如下所示,可以看出它们分别触发了case A和case B里面代码的崩溃。
对于测试程序的case C里的数组越界访问,即使人为构造测试用例触发case C里的代码程序也不崩溃,一下也看不出有什么异常的反应。
但这种漏洞也很常见,接下来就介绍另一个工具AddressSanitizer来检测此类问题。
AddressSanitizer又名ASan,官方github网址是:https://github.com/google/sanitizers/wiki/AddressSanitizer。
-
-
-
-
-
-
-
Initialization order bugs
-
AFL使用ASan插桩编译生成测试程序test1_aflfuzz1_asan的命令如下$ afl-gcc test1.c -o test1_aflfuzz1_asan -fsanitize=address再用下面命令执行afl-fuzz来fuzz测试程序,需要加上-m none选项否则AFL也会提示添加
$ afl-fuzz -i samples_cmin/ -o aflfuzz_output/ -m none ./test1_aflfuzz1_asan @@
运行后可以发现AFL也很快找到3个不同的崩溃点。有时在测试的时候也可能不会那么快发现,这和给fuzz工具提供的测试用例集、数据的变异方式还有执行速度都有关系。
可以看到现在的Fuzz工具对开发已经很友好了,除了提示是哪个变量的内存访问越界了,还会提示是在源代码的哪一行出的错。这里对应前面的代码查看31行的内容可以看到把前面检测不了问题给检测了。
现在LLVM和GCC都已经集成了AddressSanitizer的功能。可以直接使用以下命令用GCC编译添加AddressSanitizer的功能。
$ gcc test1.c -o test1_asan -fsanitize=address
然后同样用前面导致crash的文件作为参数给程序运行查看报错结果
$ ./test1_asan ./aflfuzz_output/crashes/id:000000,sig:06,src:000000,op:havoc,rep:8
可以看到这里的问题代码定位信息变成了可执行程序的偏移地址。用IDA打开test1_asan查看0x1676处的代码,64h就是十进制的100,也就是对应源代码31行的赋值代码。
honggfuzz也是google开发的一款支持基于代码覆盖率的进化和反馈驱动的Fuzz工具。可以运行在Linux,FreeBSD,NetBSD,Mac OS X,Windows/CygWin和Android系统上。官方github网址是https://github.com/google/honggfuzz。
$ sudo apt install libbfd-dev
$ sudo apt install libunwind-dev
$ git clone https://github.com/google/honggfuzz
下载完后cd进honggfuzz源码目录,依次执行以下命令编译和安装honggfuzz:
在新的命令行里运行hongffuzz会出现以下说明honggfuzz安装成功
接下来还是用honggfuzz来测试前面的小程序test1.c,通过实例测试过程来入手honggfuzz的使用。
Honggfuzz的使用和AFL有点类似。先用honggfuzz的工具把test1.c插桩编译成可执行程序test1_hfuzz1,这里直接把fsanitize选项加上:
$ hfuzz-clang test1.c -o test1_hfuzz1 -fsanitize=address
这里用static模式运行honggfuzz,___FILE___类似AFL里的@@,代表把测试文件作为参数传给被测试程序。
$ honggfuzz -i samples_cmin/ -x -- ./test1_hfuzz1 ___FILE___
可以看到honggfuzz也发现了这3个问题,在它运行目录下会保存导致崩溃的3个文件,文件名以SIG开头。
把其中一个文件作为测试程序test1_hfuzz1的参数运行结果如下
可以看到也输出了是访问哪个变量的内存访问越界了和问题代码的偏移位置,照样用IDA打开test1_hfuzz1看看偏移位置0x4c48c7处的代码
和真正出问题的代码位置0x4c48cc其实相差一行,问题定位也还算比较准确了。
接下来介绍honggfuzz的persistent fuzz模式,也叫持久性Fuzz模式,这个更多地用来测试一些函数接口,github上带有模糊测试用例的项目经常能看到这类例子。
测试程序就在前面test1.c的基础上修改一点内容再命名为test2.c,内容如下:
#include <stdio.h>
#include <string.h>
#include <libhfuzz/libhfuzz.h>
int test_function1(const uint8_t* buffer, size_t len) {
FILE *fp;
char buf[20]={0};
int nums[10];
printf("startsn");
if(len <= 8) {
printf("len <= 8, returnn");
return -1;
}
switch(buffer[8]) {
case 'A':
printf("case An");
strcpy(buf, buffer);
break;
case 'B':
printf("case Bn");
*(int*)0xffffffffabcdef10=80;
break;
case 'C':
printf("case Cn");
nums[len] = 100;
break;
default:
printf("defaultn");
break;
}
printf("endsn");
return 0;
}
int LLVMFuzzerTestOneInput(const uint8_t* buffer, size_t len) {
test_function1(buffer, len);
return 0;
}
上面代码中函数test_function1是需要被测试的函数,函数LLVMFuzzerTestOneInput是honggfuzz中约定的一个接口,
它的参数buffer由honggfuzz不断变异生成,长度为len。把这个接口生成的参数传给要测试的函数就可以对函数进行测试了。
接下来先用honggfuzz的工具把test2.c编译成可执行程序test2_hfuzz1
$ hfuzz-clang test2.c -o test2_hfuzz1
再运行honggfuzz,这里用持久性模式fuzz
$ honggfuzz -i samples_cmin/ -P -- ./test2_hfuzz1
可以看到很快发现了46个crash,这里显示的不同crash个数明显偏多,通过查看有部分测试文件导致的crash发现pc指向的地址是相同的,说明它统计不同崩溃个数的方法不只是根据pc指向的出问题的代码地址。
把其中一个文件作为测试程序test2_hfuzz1的参数运行结果如下
可以看到也把出错的位置报出来了。用IDA打开test2_hfuzz1文件可以看出导致这个crash的代码是25行的赋值nums[len] = 100;
到这里使用模糊测试工具AFL和honggfuzz 检测测试程序里的安全问题的过程就已经结束了。
AFL和honggfuzz还有很多其它的用法,包括插桩编译的方式、语料库的精简、黑盒测试方式、不同的数据变异方式,也可以自定义数据变异函数等等。
限于篇幅这里只介绍常用的基本用法,主要目的在于用实例带领大家入门实操Fuzzing。更多的使用介绍可以查看它的帮助信息、github上的文档信息和阅读源代码。
这些模糊测试Fuzzing框架的出现让模糊测试变得简单很多了,有的对开发也很友好。只是最先出来关于模糊测试框架的各种文档很多是英文写的,作为程序员最好还是要有一定的英文阅读能力,这样就能在第一时间接触到新的技术和利用别人造好的轮子。否则等别人有时间并且愿意来写这些工具的用法可能已经是一两年之后甚至更晚。
[1] american fuzzy lop:https://lcamtuf.coredump.cx/afl/
[2] The AFL++ fuzzing framework | AFLplusplushttps://aflplus.plus/
[3] AFLplusplus github | https://github.com/AFLplusplus/AFLplusplus
[4] google/honggfuzz: Security oriented software fuzzer.
https://github.com/google/honggfuzz
[5] 经典 Fuzzer 工具 AFL 模糊测试指南https://blog.csdn.net/song_lee/article/details/104777149
[6] honggfuzz漏洞挖掘技术深究系列-二进制漏洞-看雪论坛
https://bbs.pediy.com/thread-247954.htm
图灵大会 | OPPO安全高峰论坛落幕,共话安全挑战
![手把手实操入门模糊测试Fuzzing 手把手实操入门模糊测试Fuzzing]()
扫描关注我们
原文始发于微信公众号(OPPO安珀实验室):手把手实操入门模糊测试Fuzzing
免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
点赞
https://cn-sec.com/archives/2495052.html
复制链接
复制链接
-
左青龙
- 微信扫一扫
-
-
右白虎
- 微信扫一扫
-
评论