通过afl-traning学习afl-fuzz

前言

本文是记录通过afl-training熟悉afl-fuzz的一些用法

关于afl-fuzz的安装以及一些基础的使用在前面的文章已经提到过了,这里就不再赘述了

参考文章:

https://tttang.com/archive/1508/#toc_0x01-quickstart

afl-training项目地址:

https://github.com/mykter/afl-training

quickstart

quickstart是通过一个简单的程序来体验afl-fuzz的使用过程

编译里面的vulnerable.c

1
2
cd quickstart
CC=afl-clang-fast AFL_HARDEN=1 make

第二行命令是将编译器换成了afl-clang-fast并加入了环境变量AFL_HARDEN=1,然后进行make

查看一下makefile:

1
2
3
4
5
6
7
8
9
10
11
youlin@ubuntu:~/afl/afl-training/quickstart$ cat Makefile 
# Enable debugging and suppress pesky warnings
CFLAGS ?= -g -w

all: vulnerable

clean:
rm -f vulnerable

vulnerable: vulnerable.c
${CC} ${CFLAGS} vulnerable.c -o vulnerable

make默认会编译all,all编译的是vulnerable,所以最终会形成afl-clang-fast -g -w vulnerable.c -o vulnerable

vulnerable.c的源代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
$ cat vulnerable.c
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

#define INPUTSIZE 100

int process(char *input)
{
char *out;
char *rest;
int len;
if (strncmp(input, "u ", 2) == 0)
{ // upper case command
char *rest;
len = strtol(input + 2, &rest, 10); // how many characters of the string to upper-case
rest += 1; // skip the first char (should be a space)
out = malloc(len + strlen(input)); // could be shorter, but play it safe
if (len > (int)strlen(input))
{
printf("Specified length %d was larger than the input!\n", len);
return 1;
}
else if (out == NULL)
{
printf("Failed to allocate memory\n");
return 1;
}
for (int i = 0; i != len; i++)
{
out[i] = rest[i] - 32; // only handles ASCII
}
out[len] = 0;
strcat(out, rest + len); // append the remaining text
printf("%s", out);
free(out);
}
else if (strncmp(input, "head ", 5) == 0)
{ // head command
if (strlen(input) > 6)
{
len = strtol(input + 4, &rest, 10);
rest += 1; // skip the first char (should be a space)
rest[len] = '\0'; // truncate string at specified offset
printf("%s\n", rest);
}
else
{
fprintf(stderr, "head input was too small\n");
}
}
else if (strcmp(input, "surprise!\n") == 0)
{
// easter egg!
*(char *)1 = 2;
}
else
{
return 1;
}
return 0;
}

int main(int argc, char *argv[])
{
char *usage = "Usage: %s\n"
"Text utility - accepts commands and data on stdin and prints results to stdout.\n"
"\tInput | Output\n"
"\t------------------+-----------------------\n"
"\tu <N> <string> | Uppercased version of the first <N> bytes of <string>.\n"
"\thead <N> <string> | The first <N> bytes of <string>.\n";
char input[INPUTSIZE] = {0};

// Slurp input
if (read(STDIN_FILENO, input, INPUTSIZE) < 0)
{
fprintf(stderr, "Couldn't read stdin.\n");
}

int ret = process(input);
if (ret)
{
fprintf(stderr, usage, argv[0]);
};
return ret;
}

基本功能:

u :对字符串的前n个字节变成大写字符串;
head :截取字符串的前n个字符;
surprise!:隐藏功能,直接触发崩溃。
运行afl-fuzz对程序进行测试:

1
afl-fuzz -i inputs -o output ./vulnerable

inputs目录是输入的种子目录,由用户提供,应该是精心准备的样本以有效提高fuzz效率,可以看到项目提供的inputs目录中包含触发u和head的样例:

1
2
3
4
5
6
7
youlin@ubuntu:~/afl/afl-training/quickstart$ ls inputs/
head u
youlin@ubuntu:~/afl/afl-training/quickstart$ cat inputs/head
head 20 This string is going to be truncated at the 20th position.
youlin@ubuntu:~/afl/afl-training/quickstart$ cat inputs/u
u 4 capsme
youlin@ubuntu:~/afl/afl-training/quickstart$

fuzz结果:

image-20240114212413941

out有相应的产出,其中crashes目录存储的是崩溃样本;queue目录存储的是成果触发新路径的样本即有趣的样本(即新路径)。

查看output当中的crashes并运行查看效果

image-20240114212726170

通过这个小的demo来体验afl fuzz的过程,对afl有了初步的了解。

harness

harness的作用是通过demo来体验如何针对具体的库代码来编写测试框架。

这个项目当中的流程演示图:

image-20240114213011640

研究测试人员创建输入目录并提供变异的语料库(input corpus);针对测试代码编写测试框架(write harness),经过afl-clang-fast/afl-gcc插桩编译后产生支持反馈模糊测试的二进制程序;afl-fuzz从队列(queue)中挑选种子进行变异;变异后的样本扔给测试框架(harness)运行并监控运行结果;如果崩溃,则存储到崩溃目录中(crashes);如果样本成功触发了新路径,则将它添加到队列(queue)当中。

本次实验是通过编写代码对library库进行测试

library.h:

1
2
3
4
5
6
#include <unistd.h>
// an 'nprintf' implementation - print the first len bytes of data
void lib_echo(char *data, ssize_t len);

// optimised multiply - returns x*y
int lib_mul(int x, int y);

library.c:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <assert.h>

#include "library.h"

void lib_echo(char *data, ssize_t len){
if(strlen(data) == 0) {
return;
}
char *buf = calloc(1, len);
strncpy(buf, data, len);
printf("%s",buf);
free(buf);

// A crash so we can tell the harness is working for lib_echo
if(data[0] == 'p') {
if(data[1] == 'o') {
if(data[2] =='p') {
if(data[3] == '!') {
assert(0);
}
}
}
}
}

int lib_mul(int x, int y){
if(x%2 == 0) {
return y << x;
} else if (y%2 == 0) {
return x << y;
} else if (x == 0) {
return 0;
} else if (y == 0) {
return 0;
} else {
return x * y;
}
}

本次实验是通过编写代码对这两个函数进行fuzz

两个函数的功能

  • lib_echo:输出参数data中的前len个字符串;
  • lib_mul:输出参数x乘以y的值。

为了实现目的,编写的程序必须具有一下功能:

  • 编译出来的程序必须是可执行的,即需要一个main函数,从而被编译成可执行的二进制程序;

  • 具备反馈信息的能力以使afl更高效的fuzz,即编写出来的代码需要使用afl-clang-fast或afl-clang或afl-gcc进行插桩编译;

  • 提供数据接口以供afl进行变异;即两个函数使用的参数数据应来自于标准输入或文件,使得afl可以很方便的变异。

最终编写出如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <unistd.h>
#include <string.h>
#include <stdio.h>

#include "library.h"

// fixed size buffer based on assumptions about the maximum size that is likely necessary to exercise all aspects of the target function
#define SIZE 100

int main(int argc, char* argv[]) {
if((argc == 2) && strcmp(argv[1], "echo") == 0) {
// make sure buffer is initialized to eliminate variable behaviour that isn't dependent on the input.
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]);
}
}

可以看到main函数当中由命令行参数决定是对libc_echo函数进行测试还是对libc_mul进行模糊测试;接着由标准输入作为参数对函数进行调用;最后由afl-clang-fast对程序进行插桩编译

编译的命令是:

1
AFL_HARDEN=1 afl-clang-fast harness.c library.c -o harness

接下来先对libc_echo库函数进行模糊测试:

1
2
3
4
youlin@ubuntu:~/afl/afl-training/harness$ rm -rf input_echo/
youlin@ubuntu:~/afl/afl-training/harness$ mkdir input_echo
youlin@ubuntu:~/afl/afl-training/harness$ echo aaaa > input_echo/seed
youlin@ubuntu:~/afl/afl-training/harness$ afl-fuzz -i input_echo/ -o output_echo ./harness echo

没过多久就fuzz出了crashes:

image-20240114215948496

接着对libc_mul进行模糊测试

1
2
3
youlin@ubuntu:~/afl/afl-training/harness$ mkdir input_mul
youlin@ubuntu:~/afl/afl-training/harness$ echo "1 3 " > input_mul/seed
youlin@ubuntu:~/afl/afl-training/harness$ afl-fuzz -i input_mul -o output_mul ./harness mul

image-20240114223558136

通过这个demo可以理解在对特定的目标进行模糊测试时,如何基于afl编写优化框架来对代码进行模糊测试。


通过afl-traning学习afl-fuzz
http://blogyoulin.top/2024/01/14/通过afl-traning学习afl-fuzz/
Author
John Doe
Posted on
January 14, 2024
Licensed under