AFL命令行中"@@"的作用
参考:https://barro.github.io/2018/06/afl-fuzz-on-different-file-systems/
AFL有两种方式将输入传递给目标程序,一个是通过标准输入,一个是通过文件
$ afl-fuzz -i in -o out ./test # 标准输入
$ afl-fuzz -i in -o out ./test @@ # 文件
具体地,在afl-fuzz.c
文件的 main
函数中,会调用 detect_file_args
函数区检测命令行参数中是否含有 “@@”,
-
如果有,就会将"@@"替换为
out_dir
下的.cur_input
文件(实际上会替换为绝对路径,即 @@ => /…/out_dir/.cur_input),之后调用execv
替换进程空间时,就相当于如下命令:./test /…/out_dir/.cur_input,自然而然会去文件里读数据。此外,
out_file
这个变量也会被赋上值(其实就是通过这个变量是否为空来判断是标准输入还是文件输入) -
如果没有,就会调用
setup_stdio_file
函数,令out_fd
文件描述符指向out_dir
下的.cur_input
文件
虽然都是在操作 out_dir
下的 .cur_input
文件,但是还是有区别
static void write_to_testcase(void* mem, u32 len) {
s32 fd = out_fd; // 输出文件
if (out_file) { // 如果通过文件方式传递
unlink(out_file); /* Ignore errors. */
fd = open(out_file, O_WRONLY | O_CREAT | O_EXCL, 0600); // 创建out_file,具有读写权限
if (fd < 0) PFATAL("Unable to create '%s'", out_file);
}
else { // 如果通过标准输入方式传递
lseek(fd, 0, SEEK_SET); // 设置文件out_file 从头开始读写
}
ck_write(fd, mem, len, out_file); // 将缓冲区mem中存放的内容写入到fd所指的文件中去,
if (!out_file) { // 如果通过标准输入方式传递
// 为什么有这个截断操作,因为这个函数是直接覆盖之前文件内的内容,防止之前的内容比现在写进去的内容要长。
if (ftruncate(fd, len)) PFATAL("ftruncate() failed"); // 清空fd, 并将偏移量定位到文件开头
lseek(fd, 0, SEEK_SET);
} else close(fd);
}
EXP_ST void init_forkserver(char** argv) {
...
if (out_file) { // 如果通过文件方式传递
dup2(dev_null_fd, 0); // 如果指定了要模糊测试的文件,就将标准输入重定向到dev_null_fd
} else { // 如果通过标准输入方式传递
dup2(out_fd, 0); // 如果没有指定标准要模糊测试的文件,就将out_fd重定向到标准输入
close(out_fd);
}
...
}
标准输入传递
可以看到下面从write_to_testcase
摘出的程序中,是通过标准输入的方式传递所执行的语句,会直接用新数据覆盖掉旧的数据,并将文件更改为新数据的长度。值得注意的是,init_forkserver
函数中会将out_fd
重定向到0,也就是标准输入,这样一切就通了,然后目标程序会从这个out_fd
文件描述符中读取数据并运行。
上面提到out_fd
文件描述符指向 out_dir
下的 .cur_input
文件,所以子进程的标准输入由 out_dir
下的 .cur_input
这么一个文件支持, AFL 进程通过新数据覆盖旧数据的方式产生新输入。
// AFL的操作
lseek(out_fd, 0, SEEK_SET); // 从头开始读写
ck_write(out_fd, mem, len, out_file); // 写入新数据,会覆盖旧数据
ftruncate(out_fd, len); // 截断,防止新数据比旧数据短
lseek(out_fd, 0, SEEK_SET); // ftruncate后要重新设置从头开始读写
// 目标程序的操作
len = read(out_fd, buffer, sizeof(buffer));
fuzzing(buffer, len);
文件传递
下面是通过文件方式传递所执行的语句,删除旧文件,创建新文件,写入数据,关闭。目标程序打开刚刚创建的文件,从中读取数据并运行。
// AFL的操作 out_file一直指向.cur_input文件
unlink(out_file); // 删除旧的文件
fd = open(out_file, O_WRONLY | O_CREAT | O_EXCL, 0600); // 创建新的文件
ck_write(fd, mem, len, out_file); // 写入数据
close(fd);
// 目标程序的操作
fd = open(out_file, O_RDONLY);
len = read(fd, buffer, sizeof(buffer));
close(in_fd);
fuzzing(buffer, len);
整体上来讲,文件传递方式会用到更多与文件系统相关的函数,也更耗时,如调用 unlink() 和 open() 时,Linux 内核需要进行路径名查找以确定访问的确切文件对象,打开一个新文件时,必须为文件系统创建相应的 inode 及数据结构,而标准输入传递方式不需要这些操作。但是,很多程序需要从文件获取输入,此时我们就要用“@@”。
自定义文件传递
AFL的命令行参数中有一个 -f
选项,通过这个选项可以任意指定目标程序读取输入的位置,使用方式如下
$ afl-fuzz -i in -o out -f ./input ./test
此时,AFL对 ./input 文件的处理操作以及目标程序的操作就和上面文件方式传递一样。