01 关于Fuzzing
fuzzing,模糊测试。其与包括静态分析、动态分析、代码审计等传统漏洞检测方式的最大区别,就在于它可以大规模随机或半随机地生成测试数据,并以使程序crash为目标进行测试。这种方法被证明相当高效,可以敏锐地发掘代码中潜在地各种堆栈溢出等常见漏洞,也能用assert去调试非内存越界的问题(比如CVE-2014-3570 in OpenSSL)。
同样,fuzzing也有一定的局限性
- 难以确定何时停止
- 对目标的设置和输入源都由你自己控制
- 可能卡在某一个对数据的检测中不能自拔
- 只有部分类型的问题可以被fuzzer发现
不过fuzzing不失为一种强大的测试方法,就先从声名远扬的AFL(American fuzzy lop)的使用开始吧。
02 quickstart
本次测试源码也都来自afl-training
只需要两行代码就能感受到afl的快乐了
//在对源码调试时,使用afl提供的gcc作为gcc/g++的wrapper编译目标程序才能让afl进行插桩
CC=afl-clang-fast AFL_HARDEN=1 make afl-fuzz -i inputs -o out ./vulnerable

如果出现这个提示只需要照做就行
建议在非root的情况下运行,这样产生的结果不会有权限问题。运行一段时间后,在out的crashes中查看结果,可以用cat打开,如果你觉得乱码看其来不舒服,也可以用vim打开。复现结果时,直接把这些crash输入就可以了
./vulnerable < out/default/crashes/[file]
afl基本功能的实现我们可以从图中的总览来理解

03 harness
上面是对afl基本操作的体验,实际情况下的程序会有不同功能,在有程序源码的情况下,我们需要用harness进行具体的调试
harness基本要求:
- 可运行
- 有效对输入内容进行处理数据输入有两种方法,一种使直接用STDIN_FILENO输入,另一种是使用文件输入
- 运行结束自动退出
- 对非目标的crash,使用error代替crash
- 跳过程序中可能存在的对数据的检测(否则afl会花费大量时间来生成可以通过检测的数据)
STDIN_FILENO输入
buffer型
ssize_t length;
char input[SIZE] = {0};
length = read(STDIN_FILENO, input, SIZE); //STDIN_FILENO为source,input为dest,SIZE为自定义常量
int型
int a = 0;
read(STDIN_FILENO, &a, 4); //直接从输入流中转换一个整数
文件输入
- 从命令行读取文件地址
- 代码打开文件,读取到缓冲区
- 传输内容到测试区块
harness代码
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include "library.h"
// 确定一个能覆盖你要调试部分所需输入的最大size
#define SIZE 100
int main(int argc, char* argv[]) {
if((argc == 2) && strcmp(argv[1], "echo") == 0) {
// 确定要调试的功能,当然也可直接一起调试,输入内容不重要,长度设置好就行了
char input[SIZE] = {0};
ssize_t length;
length = read(STDIN_FILENO, input, SIZE);
lib_echo(input, length);
} else if ((argc == 2) && strcmp(argv[1], "mul") == 0) {
int a,b = 0;
read(STDIN_FILENO, &a, 4);
read(STDIN_FILENO, &b, 4);
printf("%d\n", lib_mul(a,b));
} else {
printf("Usage: %s mul|echo\n", argv[0]);
}
}
编译后开始调试
#using one of afl-clang-fast, afl-clang, or afl-gcc
AFL_HARDEN=1 afl-clang-fast harness.c 原代码.c -o 测试程序名称
afl-fuzz -i 输入文件夹名 -o 输出文件夹 ./测试程序
跑一会就出结果了,很简单的程序,输入pop!就自动abort。
