Compare commits

..

No commits in common. 'main' and 'dev-zhumingji' have entirely different histories.

@ -1,18 +0,0 @@
{
"configurations": [
{
"name": "windows-gcc-x64",
"includePath": [
"${workspaceFolder}/**"
],
"compilerPath": "gcc",
"cStandard": "${default}",
"cppStandard": "${default}",
"intelliSenseMode": "windows-gcc-x64",
"compilerArgs": [
""
]
}
],
"version": 4
}

@ -1,24 +0,0 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "C/C++ Runner: Debug Session",
"type": "cppdbg",
"request": "launch",
"args": [],
"stopAtEntry": false,
"externalConsole": true,
"cwd": "c:/Users/18249/Desktop/google_afl/src",
"program": "c:/Users/18249/Desktop/google_afl/src/build/Debug/outDebug",
"MIMode": "gdb",
"miDebuggerPath": "gdb",
"setupCommands": [
{
"description": "Enable pretty-printing for gdb",
"text": "-enable-pretty-printing",
"ignoreFailures": true
}
]
}
]
}

@ -1,11 +1,6 @@
{
"files.associations": {
"stdio.h": "c",
"alloc-inl.h": "c",
"config.h": "c",
"afl-as.h": "c",
"types.h": "c",
"fcntl.h": "c",
"android-ashmem.h": "c"
"alloc-inl.h": "c"
}
}

@ -4,16 +4,6 @@
"*.wpy": "vue",
"*.wxml": "html",
"*.wxss": "css",
"string.h": "c",
"cstdlib": "c",
"typeinfo": "c",
"stdlib.h": "c",
"alloc-inl.h": "c",
"debug.h": "c",
"android-ashmem.h": "c",
"limits": "c",
"stdio.h": "c",
"file.h": "c",
"config.h": "c"
"string.h": "c"
}
}

File diff suppressed because it is too large Load Diff

@ -36,378 +36,379 @@
*/
#define AFL_MAIN // 定义主程序宏
#define AFL_MAIN
#include "config.h" // 包含配置文件
#include "types.h" // 包含类型定义
#include "debug.h" // 包含调试工具
#include "alloc-inl.h" // 包含内存分配工具
#include "config.h"
#include "types.h"
#include "debug.h"
#include "alloc-inl.h"
#include "afl-as.h" // 包含AFL汇编插桩头文件
#include "afl-as.h"
#include <stdio.h> // 标准输入输出
#include <unistd.h> // POSIX标准库
#include <stdlib.h> // 标准库
#include <string.h> // 字符串处理
#include <time.h> // 时间处理
#include <ctype.h> // 字符处理
#include <fcntl.h> // 文件控制
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <ctype.h>
#include <fcntl.h>
#include <sys/wait.h> // 进程等待
#include <sys/time.h> // 时间处理
#include <sys/wait.h>
#include <sys/time.h>
static u8** as_params; // 传递给真实 'as' 的参数
static u8** as_params; /* Parameters passed to the real 'as' */
static u8* input_file; // 原始输入文件
static u8* modified_file; // 插桩后的文件
static u8* input_file; /* Originally specified input file */
static u8* modified_file; /* Instrumented file for the real 'as' */
static u8 be_quiet, // 静默模式(不输出错误信息)
clang_mode, // 是否在clang模式下运行
pass_thru, // 是否直接传递数据
just_version, // 是否只显示版本
sanitizer; // 是否使用ASAN / MSAN
static u8 be_quiet, /* Quiet mode (no stderr output) */
clang_mode, /* Running in clang mode? */
pass_thru, /* Just pass data through? */
just_version, /* Just show version? */
sanitizer; /* Using ASAN / MSAN */
static u32 inst_ratio = 100, // 插桩概率(%
as_par_cnt = 1; // 传递给 'as' 的参数数量
static u32 inst_ratio = 100, /* Instrumentation probability (%) */
as_par_cnt = 1; /* Number of params to 'as' */
// 如果命令行中没有找到 --32 或 --64 参数,则默认对编译该工具时使用的模式进行插桩。
// 这不是完美的,但对于大多数使用场景来说已经足够了。
/* 如果命令行中没有找到 --32 或 --64 参数,则默认对编译该工具时使用的模式进行插桩。
使 */
#ifdef WORD_SIZE_64 // 如果是64位系统
#ifdef WORD_SIZE_64
static u8 use_64bit = 1; // 使用64位模式
static u8 use_64bit = 1;
#else // 否则
#else
static u8 use_64bit = 0; // 使用32位模式
static u8 use_64bit = 0;
#ifdef __APPLE__ // 如果是苹果系统
# error "Sorry, 32-bit Apple platforms are not supported." // 不支持32位苹果平台
#ifdef __APPLE__
# error "Sorry, 32-bit Apple platforms are not supported."
#endif /* __APPLE__ */
#endif /* ^WORD_SIZE_64 */
// 检查并修改传递给 'as' 的参数。注意 GCC 总是将文件名作为最后一个参数传递给 'as'
// 因此我们利用这一特性来简化代码。
/* 检查并修改传递给 'as' 的参数。注意 GCC 总是将文件名作为最后一个参数传递给 'as'
*/
static void edit_params(int argc, char** argv) {
u8 *tmp_dir = getenv("TMPDIR"), *afl_as = getenv("AFL_AS"); // 获取临时目录和AFL_AS环境变量
u32 i; // 循环变量
u8 *tmp_dir = getenv("TMPDIR"), *afl_as = getenv("AFL_AS");
u32 i;
#ifdef __APPLE__ // 如果是苹果系统
#ifdef __APPLE__
u8 use_clang_as = 0; // 是否使用clang作为汇编器
u8 use_clang_as = 0;
// 在 MacOS X 上Xcode cctool 'as' 驱动程序有点过时,无法处理由用户自己编译的较新版本的 clang
// 生成的代码。详见此线程http://goo.gl/HBWDtn.
/* 在 MacOS X 上Xcode cctool 'as' 驱动程序有点过时,无法处理由用户自己编译的较新版本的 clang
线http://goo.gl/HBWDtn.
// 为了绕过这一问题,当使用 clang 且未指定 AFL_AS 时,我们将实际调用 'clang -c' 而不是 'as -q'
// 来编译汇编文件.
使 clang AFL_AS 'clang -c' 'as -q'
.
// 虽然这两个工具不是命令行兼容的,但我们可以通过进行一些小的修改来让它们在某些情况下似乎可以很好地协同工作。
// 感谢 Nico Weber 提出这一思路。
Nico Weber */
if (clang_mode && !afl_as) { // 如果是clang模式且没有指定AFL_AS
if (clang_mode && !afl_as) {
use_clang_as = 1; // 使用clang作为汇编器
use_clang_as = 1;
afl_as = getenv("AFL_CC"); // 获取AFL_CC环境变量
if (!afl_as) afl_as = getenv("AFL_CXX"); // 如果没有AFL_CC则获取AFL_CXX
if (!afl_as) afl_as = "clang"; // 如果都没有则默认使用clang
afl_as = getenv("AFL_CC");
if (!afl_as) afl_as = getenv("AFL_CXX");
if (!afl_as) afl_as = "clang";
}
#endif /* __APPLE__ */
// 虽然这在文档中没有提及,但 GCC 实际上也使用 TEMP 和 TMP当 TMPDIR 未设置时)。
// 我们需要检查这些非常规变量以正确处理 pass_thru 逻辑。
/* 虽然这在文档中没有提及,但 GCC 实际上也使用 TEMP 和 TMP当 TMPDIR 未设置时)。
pass_thru */
if (!tmp_dir) tmp_dir = getenv("TEMP"); // 如果没有TMPDIR则获取TEMP
if (!tmp_dir) tmp_dir = getenv("TMP"); // 如果没有TEMP则获取TMP
if (!tmp_dir) tmp_dir = "/tmp"; // 如果都没有,则默认使用/tmp
if (!tmp_dir) tmp_dir = getenv("TEMP");
if (!tmp_dir) tmp_dir = getenv("TMP");
if (!tmp_dir) tmp_dir = "/tmp";
as_params = ck_alloc((argc + 32) * sizeof(u8*)); // 分配参数数组内存
as_params = ck_alloc((argc + 32) * sizeof(u8*));
as_params[0] = afl_as ? afl_as : (u8*)"as"; // 设置第一个参数为AFL_AS或默认的as
as_params[0] = afl_as ? afl_as : (u8*)"as";
as_params[argc] = 0; // 设置最后一个参数为NULL
as_params[argc] = 0;
for (i = 1; i < argc - 1; i++) { // 遍历参数
for (i = 1; i < argc - 1; i++) {
if (!strcmp(argv[i], "--64")) use_64bit = 1; // 如果参数是--64则使用64位模式
else if (!strcmp(argv[i], "--32")) use_64bit = 0; // 如果参数是--32则使用32位模式
if (!strcmp(argv[i], "--64")) use_64bit = 1;
else if (!strcmp(argv[i], "--32")) use_64bit = 0;
#ifdef __APPLE__ // 如果是苹果系统
#ifdef __APPLE__
// MacOS X 的情况有点不同...
/* MacOS X 的情况有点不同... */
if (!strcmp(argv[i], "-arch") && i + 1 < argc) { // 如果参数是-arch
if (!strcmp(argv[i], "-arch") && i + 1 < argc) {
if (!strcmp(argv[i + 1], "x86_64")) use_64bit = 1; // 如果架构是x86_64则使用64位模式
else if (!strcmp(argv[i + 1], "i386")) // 如果架构是i386
FATAL("Sorry, 32-bit Apple platforms are not supported."); // 不支持32位苹果平台
if (!strcmp(argv[i + 1], "x86_64")) use_64bit = 1;
else if (!strcmp(argv[i + 1], "i386"))
FATAL("Sorry, 32-bit Apple platforms are not supported.");
}
// 移除 Xcode 中设置特定上游汇编器的选项
/* 移除 Xcode 中设置特定上游汇编器的选项 */
if (clang_mode && (!strcmp(argv[i], "-q") || !strcmp(argv[i], "-Q"))) // 如果是clang模式且参数是-q或-Q
continue; // 跳过这些参数
if (clang_mode && (!strcmp(argv[i], "-q") || !strcmp(argv[i], "-Q")))
continue;
#endif /* __APPLE__ */
as_params[as_par_cnt++] = argv[i]; // 将参数添加到as_params数组中
as_params[as_par_cnt++] = argv[i];
}
#ifdef __APPLE__ // 如果是苹果系统
#ifdef __APPLE__
// 当调用 clang 作为上游汇编器时,追加 -c -x assembler 选项并希望一切顺利。
/* 当调用 clang 作为上游汇编器时,追加 -c -x assembler 选项并希望一切顺利。 */
if (use_clang_as) { // 如果使用clang作为汇编器
if (use_clang_as) {
as_params[as_par_cnt++] = "-c"; // 添加-c参数
as_params[as_par_cnt++] = "-x"; // 添加-x参数
as_params[as_par_cnt++] = "assembler"; // 添加assembler参数
as_params[as_par_cnt++] = "-c";
as_params[as_par_cnt++] = "-x";
as_params[as_par_cnt++] = "assembler";
}
#endif /* __APPLE__ */
input_file = argv[argc - 1]; // 获取输入文件
input_file = argv[argc - 1];
if (input_file[0] == '-') { // 如果输入文件是标准输入
if (input_file[0] == '-') {
if (!strcmp(input_file + 1, "-version")) { // 如果参数是-version
just_version = 1; // 设置只显示版本
modified_file = input_file; // 设置修改后的文件为输入文件
goto wrap_things_up; // 跳转到结束处理
if (!strcmp(input_file + 1, "-version")) {
just_version = 1;
modified_file = input_file;
goto wrap_things_up;
}
if (input_file[1]) FATAL("Incorrect use (not called through afl-gcc?)"); // 如果输入文件不是标准输入
else input_file = NULL; // 否则设置为NULL
if (input_file[1]) FATAL("Incorrect use (not called through afl-gcc?)");
else input_file = NULL;
} else { // 如果输入文件不是标准输入
} else {
// 检查是否为标准调用,作为编译程序的一部分,而不是使用 gcc 对一个独立的 .s 文件进行编译。
// 这解决了在编译 NSS 时遇到的问题。
/* 检查是否为标准调用,作为编译程序的一部分,而不是使用 gcc 对一个独立的 .s 文件进行编译。
NSS */
if (strncmp(input_file, tmp_dir, strlen(tmp_dir)) && // 如果输入文件不在临时目录
strncmp(input_file, "/var/tmp/", 9) && // 且不在/var/tmp/
strncmp(input_file, "/tmp/", 5)) pass_thru = 1; // 且不在/tmp/则设置为pass_thru模式
if (strncmp(input_file, tmp_dir, strlen(tmp_dir)) &&
strncmp(input_file, "/var/tmp/", 9) &&
strncmp(input_file, "/tmp/", 5)) pass_thru = 1;
}
modified_file = alloc_printf("%s/.afl-%u-%u.s", tmp_dir, getpid(), // 生成修改后的文件名
modified_file = alloc_printf("%s/.afl-%u-%u.s", tmp_dir, getpid(),
(u32)time(NULL));
wrap_things_up: // 结束处理
wrap_things_up:
as_params[as_par_cnt++] = modified_file; // 将修改后的文件添加到参数数组
as_params[as_par_cnt] = NULL; // 设置最后一个参数为NULL
as_params[as_par_cnt++] = modified_file;
as_params[as_par_cnt] = NULL;
}
// 处理输入文件并生成 modified_file。在所有适当的位置插入插桩代码。
/* 处理输入文件并生成 modified_file。在所有适当的位置插入插桩代码。 */
static void add_instrumentation(void) {
static u8 line[MAX_LINE]; // 读取文件的缓冲区
static u8 line[MAX_LINE];
FILE* inf; // 输入文件指针
FILE* outf; // 输出文件指针
s32 outfd; // 输出文件描述符
u32 ins_lines = 0; // 插桩的行数
FILE* inf;
FILE* outf;
s32 outfd;
u32 ins_lines = 0;
u8 instr_ok = 0, skip_csect = 0, skip_next_label = 0, // 插桩状态标志
u8 instr_ok = 0, skip_csect = 0, skip_next_label = 0,
skip_intel = 0, skip_app = 0, instrument_next = 0;
#ifdef __APPLE__ // 如果是苹果系统
#ifdef __APPLE__
u8* colon_pos; // 冒号位置
u8* colon_pos;
#endif /* __APPLE__ */
if (input_file) { // 如果有输入文件
if (input_file) {
inf = fopen(input_file, "r"); // 打开输入文件
if (!inf) PFATAL("Unable to read '%s'", input_file); // 如果打开失败则报错
inf = fopen(input_file, "r");
if (!inf) PFATAL("Unable to read '%s'", input_file);
} else inf = stdin; // 否则使用标准输入
} else inf = stdin;
outfd = open(modified_file, O_WRONLY | O_EXCL | O_CREAT, 0600); // 打开输出文件
outfd = open(modified_file, O_WRONLY | O_EXCL | O_CREAT, 0600);
if (outfd < 0) PFATAL("Unable to write to '%s'", modified_file); // 如果打开失败则报错
if (outfd < 0) PFATAL("Unable to write to '%s'", modified_file);
outf = fdopen(outfd, "w"); // 将文件描述符转换为文件指针
outf = fdopen(outfd, "w");
if (!outf) PFATAL("fdopen() failed"); // 如果转换失败则报错
if (!outf) PFATAL("fdopen() failed");
while (fgets(line, MAX_LINE, inf)) { // 逐行读取输入文件
while (fgets(line, MAX_LINE, inf)) {
// 在某些情况下,我们希望在所有标签、宏、注释等之后再插入插桩跳板代码。
// 如果处于这一模式,且行以制表符开头,后跟一个字母,则现在插入跳板代码。
/* 在某些情况下,我们希望在所有标签、宏、注释等之后再插入插桩跳板代码。
*/
if (!pass_thru && !skip_intel && !skip_app && !skip_csect && instr_ok &&
instrument_next && line[0] == '\t' && isalpha(line[1])) { // 如果满足插桩条件
instrument_next && line[0] == '\t' && isalpha(line[1])) {
fprintf(outf, use_64bit ? trampoline_fmt_64 : trampoline_fmt_32, // 插入跳板代码
fprintf(outf, use_64bit ? trampoline_fmt_64 : trampoline_fmt_32,
R(MAP_SIZE));
instrument_next = 0; // 重置插桩标志
ins_lines++; // 增加插桩行数
instrument_next = 0;
ins_lines++;
}
// 输出实际的行,在 pass-thru 模式下结束操作。
/* 输出实际的行,在 pass-thru 模式下结束操作。 */
fputs(line, outf); // 输出当前行
fputs(line, outf);
if (pass_thru) continue; // 如果是pass-thru模式则跳过
if (pass_thru) continue;
// 现在开始真正的插桩操作。首先,我们只希望插桩 .text 部分。
// 因此,我们需要跟踪处理的汇编文件中各部分的状态,并据此设置 instr_ok。
/* 现在开始真正的插桩操作。首先,我们只希望插桩 .text 部分。
instr_ok */
if (line[0] == '\t' && line[1] == '.') { // 如果行以制表符和点开头
if (line[0] == '\t' && line[1] == '.') {
// OpenBSD 在代码中直接放置跳转表,这稍微有点麻烦。
// 它们使用特定格式的 p2align 指令围绕它们,因此我们可以使用该格式作为信号。
/* OpenBSD 在代码中直接放置跳转表,这稍微有点麻烦。
使 p2align 使 */
if (!clang_mode && instr_ok && !strncmp(line + 2, "p2align ", 8) && // 如果是OpenBSD的p2align指令
isdigit(line[10]) && line[11] == '\n') skip_next_label = 1; // 设置跳过下一个标签
if (!clang_mode && instr_ok && !strncmp(line + 2, "p2align ", 8) &&
isdigit(line[10]) && line[11] == '\n') skip_next_label = 1;
if (!strncmp(line + 2, "text\n", 5) || // 如果是.text部分
!strncmp(line + 2, "section\t.text", 13) || // 或者section .text
!strncmp(line + 2, "section\t__TEXT,__text", 21) || // 或者section __TEXT,__text
!strncmp(line + 2, "section __TEXT,__text", 21)) { // 或者section __TEXT,__text
instr_ok = 1; // 设置插桩标志
continue; // 继续
if (!strncmp(line + 2, "text\n", 5) ||
!strncmp(line + 2, "section\t.text", 13) ||
!strncmp(line + 2, "section\t__TEXT,__text", 21) ||
!strncmp(line + 2, "section __TEXT,__text", 21)) {
instr_ok = 1;
continue;
}
if (!strncmp(line + 2, "section\t", 8) || // 如果是其他section
!strncmp(line + 2, "section ", 8) || // 或者其他section
!strncmp(line + 2, "bss\n", 4) || // 或者.bss部分
!strncmp(line + 2, "data\n", 5)) { // 或者.data部分
instr_ok = 0; // 重置插桩标志
continue; // 继续
if (!strncmp(line + 2, "section\t", 8) ||
!strncmp(line + 2, "section ", 8) ||
!strncmp(line + 2, "bss\n", 4) ||
!strncmp(line + 2, "data\n", 5)) {
instr_ok = 0;
continue;
}
}
// 检测非常规汇编(罕见,例如在 gdb 中)。当遇到这种汇编时,我们设置 skip_csect
// 直到遇到相反的指令,此时我们不进行插桩。
/* 检测非常规汇编(罕见,例如在 gdb 中)。当遇到这种汇编时,我们设置 skip_csect
*/
if (strstr(line, ".code")) { // 如果行包含.code
if (strstr(line, ".code")) {
if (strstr(line, ".code32")) skip_csect = use_64bit; // 如果是.code32则根据64位模式设置skip_csect
if (strstr(line, ".code64")) skip_csect = !use_64bit; // 如果是.code64则根据64位模式设置skip_csect
if (strstr(line, ".code32")) skip_csect = use_64bit;
if (strstr(line, ".code64")) skip_csect = !use_64bit;
}
// 检测并跳过手写汇编块__asm__同样不进行插桩。
/* 检测并跳过手写汇编块__asm__同样不进行插桩。 */
if (line[0] == '#' || line[1] == '#') { // 如果行以#开头
if (line[0] == '#' || line[1] == '#') {
if (strstr(line, "#APP")) skip_app = 1; // 如果包含#APP则设置skip_app
if (strstr(line, "#NO_APP")) skip_app = 0; // 如果包含#NO_APP则重置skip_app
if (strstr(line, "#APP")) skip_app = 1;
if (strstr(line, "#NO_APP")) skip_app = 0;
}
// 如果我们处于插桩模式,检查函数名或条件标签。这里逻辑有些复杂,但基本目标是捕获:
/* 如果我们处于插桩模式,检查函数名或条件标签。这里逻辑有些复杂,但基本目标是捕获:
// ^main: - 函数入口点(总是插桩)
// ^.L0: - GCC 分支标签
// ^.LBB0_0: - clang 分支标签(但仅在 clang 模式下)
// ^\tjnz foo - 条件分支
^main: -
^.L0: - GCC
^.LBB0_0: - clang clang
^\tjnz foo -
// ...而不捕获:
...
// ^# BB#0: - clang 注释
// ^ # BB#0: - 同上
// ^.Ltmp0: - clang 非分支标签
// ^.LC0 - GCC 非分支标签
// ^.LBB0_0: - 同上(当处于 GCC 模式下)
// ^\tjmp foo - 非条件跳转
^# BB#0: - clang
^ # BB#0: -
^.Ltmp0: - clang
^.LC0 - GCC
^.LBB0_0: - GCC
^\tjmp foo -
// 此外MacOS X 上的 clang 和 GCC 使用不同的标签格式,没有前导点,因此我们根据这一情况处理。
MacOS X clang GCC 使
*/
if (skip_intel || skip_app || skip_csect || !instr_ok || // 如果跳过插桩
line[0] == '#' || line[0] == ' ') continue; // 或者行以#或空格开头,则跳过
if (skip_intel || skip_app || skip_csect || !instr_ok ||
line[0] == '#' || line[0] == ' ') continue;
// 条件分支指令jnz 等)。我们会在分支之后插入插桩(以插桩不执行路径),
// 并在分支目标标签处插入(稍后处理)。
/* 条件分支指令jnz 等)。我们会在分支之后插入插桩(以插桩不执行路径),
*/
if (line[0] == '\t') { // 如果行以制表符开头
if (line[0] == '\t') {
if (line[1] == 'j' && line[2] != 'm' && R(100) < inst_ratio) { // 如果是条件分支指令
if (line[1] == 'j' && line[2] != 'm' && R(100) < inst_ratio) {
fprintf(outf, use_64bit ? trampoline_fmt_64 : trampoline_fmt_32, // 插入跳板代码
fprintf(outf, use_64bit ? trampoline_fmt_64 : trampoline_fmt_32,
R(MAP_SIZE));
ins_lines++; // 增加插桩行数
ins_lines++;
}
continue; // 继续
continue;
}
// 某类标签。这可能是分支目标,但我们需要小心处理不同的格式约定。
/* 某类标签。这可能是分支目标,但我们需要小心处理不同的格式约定。 */
#ifdef __APPLE__ // 如果是苹果系统
#ifdef __APPLE__
// MacOS X: L<whatever><digit>:
/* MacOS X: L<whatever><digit>: */
if ((colon_pos = strstr(line, ":"))) { // 如果行包含冒号
if ((colon_pos = strstr(line, ":"))) {
if (line[0] == 'L' && isdigit(*(colon_pos - 1))) { // 如果标签以L开头且冒号前是数字
if (line[0] == 'L' && isdigit(*(colon_pos - 1))) {
#else // 否则
#else
// 其他人:.L<whatever>:
/* 其他人:.L<whatever>: */
if (strstr(line, ":")) { // 如果行包含冒号
if (strstr(line, ":")) {
if (line[0] == '.') { // 如果标签以点开头
if (line[0] == '.') {
#endif /* __APPLE__ */
// .L0: 或 LBB0_0: 风格的分支目标
/* .L0: 或 LBB0_0: 风格的分支目标 */
#ifdef __APPLE__ // 如果是苹果系统
#ifdef __APPLE__
// MacOS X: L<num> / LBB<num>
/* MacOS X: L<num> / LBB<num> */
if ((isdigit(line[1]) || (clang_mode && !strncmp(line, "LBB", 3))) // 如果标签是L<num>或LBB<num>
&& R(100) < inst_ratio) { // 并且随机数小于插桩概率
if ((isdigit(line[1]) || (clang_mode && !strncmp(line, "LBB", 3)))
&& R(100) < inst_ratio) {
#else // 否则
#else
// MacOS X: .L<num> / .LBB<num>
/* MacOS X: .L<num> / .LBB<num> */
if ((isdigit(line[2]) || (clang_mode && !strncmp(line + 1, "LBB", 3))) // 如果标签是.L<num>或.LBB<num>
&& R(100) < inst_ratio) { // 并且随机数小于插桩概率
if ((isdigit(line[2]) || (clang_mode && !strncmp(line + 1, "LBB", 3)))
&& R(100) < inst_ratio) {
#endif /* __APPLE__ */
// 在仅需要在标签被引用时(非调用/跳转上下文)才添加代码的情况下可以进行优化。
// 这会引入两遍处理过程的复杂性(当使用 stdin 时尤其麻烦),并且通常只能带来不到 10% 的速度提升。
// 因为编译器通常不会生成不相关的函数内跳转。
/* 在仅需要在标签被引用时(非调用/跳转上下文)才添加代码的情况下可以进行优化。
使 stdin 10%
// 我们使用延迟输出主要是为了避免干扰 MacOS X 上 .Lfunc_begin0 风格异常处理计算的问题。
使 MacOS X .Lfunc_begin0 */
if (!skip_next_label) instrument_next = 1; else skip_next_label = 0; // 设置插桩标志
if (!skip_next_label) instrument_next = 1; else skip_next_label = 0;
}
} else { // 否则
} else {
// 函数标签(总是插桩,延迟模式)。
/* 函数标签(总是插桩,延迟模式)。 */
instrument_next = 1; // 设置插桩标志
instrument_next = 1;
}
@ -415,17 +416,17 @@ static void add_instrumentation(void) {
}
if (ins_lines) // 如果有插桩行
fputs(use_64bit ? main_payload_64 : main_payload_32, outf); // 插入主插桩代码
if (ins_lines)
fputs(use_64bit ? main_payload_64 : main_payload_32, outf);
if (input_file) fclose(inf); // 关闭输入文件
fclose(outf); // 关闭输出文件
if (input_file) fclose(inf);
fclose(outf);
if (!be_quiet) { // 如果不是静默模式
if (!be_quiet) {
if (!ins_lines) WARNF("No instrumentation targets found%s.", // 如果没有插桩目标
if (!ins_lines) WARNF("No instrumentation targets found%s.",
pass_thru ? " (pass-thru mode)" : "");
else OKF("Instrumented %u locations (%s-bit, %s mode, ratio %u%%).", // 输出插桩信息
else OKF("Instrumented %u locations (%s-bit, %s mode, ratio %u%%).",
ins_lines, use_64bit ? "64" : "32",
getenv("AFL_HARDEN") ? "hardened" :
(sanitizer ? "ASAN/MSAN" : "non-hardened"),
@ -436,27 +437,27 @@ static void add_instrumentation(void) {
}
// 程序主入口点
/* 程序主入口点 */
int main(int argc, char** argv) {
s32 pid; // 进程ID
u32 rand_seed; // 随机种子
int status; // 进程状态
u8* inst_ratio_str = getenv("AFL_INST_RATIO"); // 获取插桩概率环境变量
s32 pid;
u32 rand_seed;
int status;
u8* inst_ratio_str = getenv("AFL_INST_RATIO");
struct timeval tv; // 时间结构
struct timezone tz; // 时区结构
struct timeval tv;
struct timezone tz;
clang_mode = !!getenv(CLANG_ENV_VAR); // 设置clang模式
clang_mode = !!getenv(CLANG_ENV_VAR);
if (isatty(2) && !getenv("AFL_QUIET")) { // 如果是终端且没有设置AFL_QUIET
if (isatty(2) && !getenv("AFL_QUIET")) {
SAYF(cCYA "afl-as " cBRI VERSION cRST " by <lcamtuf@google.com>\n"); // 输出版本信息
SAYF(cCYA "afl-as " cBRI VERSION cRST " by <lcamtuf@google.com>\n");
} else be_quiet = 1; // 否则设置为静默模式
} else be_quiet = 1;
if (argc < 2) { // 如果参数少于2个
if (argc < 2) {
SAYF("\n"
"This is a helper application for afl-fuzz. It is a wrapper around GNU 'as',\n"
@ -465,55 +466,55 @@ int main(int argc, char** argv) {
"Rarely, when dealing with extremely complex projects, it may be advisable to\n"
"set AFL_INST_RATIO to a value less than 100 in order to reduce the odds of\n"
"instrumenting every discovered branch.\n\n"); // 输出帮助信息
"instrumenting every discovered branch.\n\n");
exit(1); // 退出
exit(1);
}
gettimeofday(&tv, &tz); // 获取当前时间
gettimeofday(&tv, &tz);
rand_seed = tv.tv_sec ^ tv.tv_usec ^ getpid(); // 生成随机种子
rand_seed = tv.tv_sec ^ tv.tv_usec ^ getpid();
srandom(rand_seed); // 设置随机种子
srandom(rand_seed);
edit_params(argc, argv); // 编辑参数
edit_params(argc, argv);
if (inst_ratio_str) { // 如果有插桩概率环境变量
if (inst_ratio_str) {
if (sscanf(inst_ratio_str, "%u", &inst_ratio) != 1 || inst_ratio > 100) // 如果解析失败或大于100
FATAL("Bad value of AFL_INST_RATIO (must be between 0 and 100)"); // 报错
if (sscanf(inst_ratio_str, "%u", &inst_ratio) != 1 || inst_ratio > 100)
FATAL("Bad value of AFL_INST_RATIO (must be between 0 and 100)");
}
if (getenv(AS_LOOP_ENV_VAR)) // 如果设置了AS_LOOP_ENV_VAR
FATAL("Endless loop when calling 'as' (remove '.' from your PATH)"); // 报错
if (getenv(AS_LOOP_ENV_VAR))
FATAL("Endless loop when calling 'as' (remove '.' from your PATH)");
setenv(AS_LOOP_ENV_VAR, "1", 1); // 设置AS_LOOP_ENV_VAR
setenv(AS_LOOP_ENV_VAR, "1", 1);
// 使用 ASAN 时,我们没有特别优雅的方法来跳过 ASAN 特定的分支。
// 但可以通过按概率补偿来解决这个问题...
/* 使用 ASAN 时,我们没有特别优雅的方法来跳过 ASAN 特定的分支。
... */
if (getenv("AFL_USE_ASAN") || getenv("AFL_USE_MSAN")) { // 如果使用ASAN或MSAN
sanitizer = 1; // 设置sanitizer标志
inst_ratio /= 3; // 降低插桩概率
if (getenv("AFL_USE_ASAN") || getenv("AFL_USE_MSAN")) {
sanitizer = 1;
inst_ratio /= 3;
}
if (!just_version) add_instrumentation(); // 如果不是只显示版本,则进行插桩
if (!just_version) add_instrumentation();
if (!(pid = fork())) { // 创建子进程
if (!(pid = fork())) {
execvp(as_params[0], (char**)as_params); // 执行as命令
FATAL("Oops, failed to execute '%s' - check your PATH", as_params[0]); // 如果执行失败则报错
execvp(as_params[0], (char**)as_params);
FATAL("Oops, failed to execute '%s' - check your PATH", as_params[0]);
}
if (pid < 0) PFATAL("fork() failed"); // 如果fork失败则报错
if (pid < 0) PFATAL("fork() failed");
if (waitpid(pid, &status, 0) <= 0) PFATAL("waitpid() failed"); // 等待子进程结束
if (waitpid(pid, &status, 0) <= 0) PFATAL("waitpid() failed");
if (!getenv("AFL_KEEP_ASSEMBLY")) unlink(modified_file); // 如果没有设置AFL_KEEP_ASSEMBLY则删除修改后的文件
if (!getenv("AFL_KEEP_ASSEMBLY")) unlink(modified_file);
exit(WEXITSTATUS(status)); // 退出
exit(WEXITSTATUS(status));
}

File diff suppressed because it is too large Load Diff

@ -1,91 +1,89 @@
#!/usr/bin/env bash
#
# American Fuzzy Lop - 语料库最小化工具
# american fuzzy lop - corpus minimization tool
# ---------------------------------------------
#
# 作者和维护者:Michal Zalewski <lcamtuf@google.com>
# Written and maintained by Michal Zalewski <lcamtuf@google.com>
#
# 版权所有 2014, 2015 Google LLC 保留所有权利。
# Copyright 2014, 2015 Google LLC All rights reserved.
#
# 根据 Apache 许可证 2.0 版("许可证")授权;
# 除非符合许可证的规定,否则您不得使用此文件。
# 您可以从以下网址获取许可证的副本:
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at:
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# 此工具尝试查找输入目录中最小的文件子集,
# 该子集仍然触发启动语料库中看到的所有仪器数据点。
# 这有两个用途:
# This tool tries to find the smallest subset of files in the input directory
# that still trigger the full range of instrumentation data points seen in
# the starting corpus. This has two uses:
#
# - 在将大的输入文件用作 afl-fuzz 的种子之前筛选。
# 该工具将删除功能上冗余的文件,并可能
# 留下一个更小的集合。
# - Screening large corpora of input files before using them as a seed for
# afl-fuzz. The tool will remove functionally redundant files and likely
# leave you with a much smaller set.
#
# (在这种情况下,您可能还想考虑稍后对
# 各个文件运行 afl-tmin 以减少其大小。)
# (In this case, you probably also want to consider running afl-tmin on
# the individual files later on to reduce their size.)
#
# - 最小化由 afl-fuzz 自然生成的语料库,
# 可能在计划将其供给更多资源密集型工具时。
# 该工具通过删除所有曾经触发独特行为的条目实现此目的,
# 但这些条目已被后来的结果取代。
# - Minimizing the corpus generated organically by afl-fuzz, perhaps when
# planning to feed it to more resource-intensive tools. The tool achieves
# this by removing all entries that used to trigger unique behaviors in the
# past, but have been made obsolete by later finds.
#
# 请注意,该工具不会修改文件本身。
# 对于此,您希望使用 afl-tmin。
# Note that the tool doesn't modify the files themselves. For that, you want
# afl-tmin.
#
# 此脚本必须使用 bash因为其他 shell 可能对
# 数组大小有硬编码限制。
# This script must use bash because other shells may have hardcoded limits on
# array sizes.
#
echo "为 afl-fuzz 提供的语料库最小化工具 <lcamtuf@google.com>" # 输出程序名称
echo "corpus minimization tool for afl-fuzz by <lcamtuf@google.com>"
echo
#########
# 设置 #
# SETUP #
#########
# 处理命令行选项...
# Process command-line options...
MEM_LIMIT=100 # 内存限制初始值为 100 MB
TIMEOUT=none # 超时初始值为无
MEM_LIMIT=100
TIMEOUT=none
# 取消设置以下变量
unset IN_DIR OUT_DIR STDIN_FILE EXTRA_PAR MEM_LIMIT_GIVEN \
AFL_CMIN_CRASHES_ONLY AFL_CMIN_ALLOW_ANY QEMU_MODE
# 解析命令行选项
while getopts "+i:o:f:m:t:eQC" opt; do
case "$opt" in
"i") # 输入目录选项
"i")
IN_DIR="$OPTARG"
;;
"o") # 输出目录选项
"o")
OUT_DIR="$OPTARG"
;;
"f") # 从中读取的模糊程序位置(标准输入)
"f")
STDIN_FILE="$OPTARG"
;;
"m") # 内存限制
"m")
MEM_LIMIT="$OPTARG"
MEM_LIMIT_GIVEN=1
;;
"t") # 超时时间
"t")
TIMEOUT="$OPTARG"
;;
"e") # 额外参数
"e")
EXTRA_PAR="$EXTRA_PAR -e"
;;
"C") # 仅保留崩溃输入
"C")
export AFL_CMIN_CRASHES_ONLY=1
;;
"Q") # 使用仅二进制的仪器QEMU 模式)
"Q")
EXTRA_PAR="$EXTRA_PAR -Q"
test "$MEM_LIMIT_GIVEN" = "" && MEM_LIMIT=250
QEMU_MODE=1
;;
"?") # 无效选项
"?")
exit 1
;;
@ -93,84 +91,84 @@ while getopts "+i:o:f:m:t:eQC" opt; do
done
shift $((OPTIND-1)) # 移动位置参数
shift $((OPTIND-1))
TARGET_BIN="$1" # 目标二进制文件
TARGET_BIN="$1"
# 检查必需参数是否缺失
if [ "$TARGET_BIN" = "" -o "$IN_DIR" = "" -o "$OUT_DIR" = "" ]; then
# 输出用法信息到标准错误
cat 1>&2 <<_EOF_
使用: $0 [选项] -- /path/to/target_app [ ... ]
Usage: $0 [ options ] -- /path/to/target_app [ ... ]
所需参数:
Required parameters:
-i dir - 包含起始语料库的输入目录
-o dir - 最小化文件的输出目录
-i dir - input directory with the starting corpus
-o dir - output directory for minimized files
执行控制设置:
Execution control settings:
-f file - 由模糊程序读取的位置(标准输入)
-m megs - 子进程的内存限制($MEM_LIMIT MB
-t msec - 子进程的运行时间限制(无)
-Q - 使用仅二进制的仪器QEMU 模式)
-f file - location read by the fuzzed program (stdin)
-m megs - memory limit for child process ($MEM_LIMIT MB)
-t msec - run time limit for child process (none)
-Q - use binary-only instrumentation (QEMU mode)
最小化设置:
Minimization settings:
-C - 保留崩溃输入,拒绝其他所有内容
-e - 仅解决边缘覆盖,忽略命中计数
-C - keep crashing inputs, reject everything else
-e - solve for edge coverage only, ignore hit counts
有关其他提示,请参阅 docs/README。
For additional tips, please consult docs/README.
_EOF_
exit 1
fi
# 进行完整性检查,避免使用 /tmp因为我们无法安全处理它。
# Do a sanity check to discourage the use of /tmp, since we can't really
# handle this safely from a shell script.
if [ "$AFL_ALLOW_TMP" = "" ]; then
echo "$IN_DIR" | grep -qE '^(/var)?/tmp/' # 检查输入目录是否在/tmp
echo "$IN_DIR" | grep -qE '^(/var)?/tmp/'
T1="$?"
echo "$TARGET_BIN" | grep -qE '^(/var)?/tmp/' # 检查目标二进制文件是否在/tmp
echo "$TARGET_BIN" | grep -qE '^(/var)?/tmp/'
T2="$?"
echo "$OUT_DIR" | grep -qE '^(/var)?/tmp/' # 检查输出目录是否在/tmp
echo "$OUT_DIR" | grep -qE '^(/var)?/tmp/'
T3="$?"
echo "$STDIN_FILE" | grep -qE '^(/var)?/tmp/' # 检查标准输入文件是否在/tmp
echo "$STDIN_FILE" | grep -qE '^(/var)?/tmp/'
T4="$?"
echo "$PWD" | grep -qE '^(/var)?/tmp/' # 检查当前工作目录是否在/tmp
echo "$PWD" | grep -qE '^(/var)?/tmp/'
T5="$?"
if [ "$T1" = "0" -o "$T2" = "0" -o "$T3" = "0" -o "$T4" = "0" -o "$T5" = "0" ]; then
echo "[-] 错误: 请勿在 /tmp 或 /var/tmp 中使用此脚本。" 1>&2
echo "[-] Error: do not use this script in /tmp or /var/tmp." 1>&2
exit 1
fi
fi
# 如果指定了 @@,但没有 -f创建一个临时输入文件名。
# If @@ is specified, but there's no -f, let's come up with a temporary input
# file name.
TRACE_DIR="$OUT_DIR/.traces"
if [ "$STDIN_FILE" = "" ]; then
if echo "$*" | grep -qF '@@'; then
STDIN_FILE="$TRACE_DIR/.cur_input" # 使用当前输入文件名
STDIN_FILE="$TRACE_DIR/.cur_input"
fi
fi
# 检查明显的错误。
# Check for obvious errors.
if [ ! "$MEM_LIMIT" = "none" ]; then
if [ "$MEM_LIMIT" -lt "5" ]; then
echo "[-] 错误: 内存限制过低。" 1>&2
echo "[-] Error: dangerously low memory limit." 1>&2
exit 1
fi
@ -179,7 +177,7 @@ fi
if [ ! "$TIMEOUT" = "none" ]; then
if [ "$TIMEOUT" -lt "10" ]; then
echo "[-] 错误: 超时过低。" 1>&2
echo "[-] Error: dangerously low timeout." 1>&2
exit 1
fi
@ -187,91 +185,92 @@ fi
if [ ! -f "$TARGET_BIN" -o ! -x "$TARGET_BIN" ]; then
TNEW="`which "$TARGET_BIN" 2>/dev/null`" # 查找目标二进制文件的路径
TNEW="`which "$TARGET_BIN" 2>/dev/null`"
if [ ! -f "$TNEW" -o ! -x "$TNEW" ]; then
echo "[-] 错误: 未找到或不可执行的二进制文件 '$TARGET_BIN'。" 1>&2
echo "[-] Error: binary '$TARGET_BIN' not found or not executable." 1>&2
exit 1
fi
TARGET_BIN="$TNEW" # 更新目标二进制文件路径
TARGET_BIN="$TNEW"
fi
if [ "$AFL_SKIP_BIN_CHECK" = "" -a "$QEMU_MODE" = "" ]; then
if ! grep -qF "__AFL_SHM_ID" "$TARGET_BIN"; then
echo "[-] 错误: 二进制文件 '$TARGET_BIN' 似乎没有被仪器化。" 1>&2
echo "[-] Error: binary '$TARGET_BIN' doesn't appear to be instrumented." 1>&2
exit 1
fi
fi
if [ ! -d "$IN_DIR" ]; then
echo "[-] 错误: 目录 '$IN_DIR' 未找到。" 1>&2
echo "[-] Error: directory '$IN_DIR' not found." 1>&2
exit 1
fi
test -d "$IN_DIR/queue" && IN_DIR="$IN_DIR/queue" # 如果存在队列目录,则更新输入目录
test -d "$IN_DIR/queue" && IN_DIR="$IN_DIR/queue"
find "$OUT_DIR" -name 'id[:_]*' -maxdepth 1 -exec rm -- {} \; 2>/dev/null # 删除输出目录中的旧轨迹
rm -rf "$TRACE_DIR" 2>/dev/null # 删除临时轨迹目录
find "$OUT_DIR" -name 'id[:_]*' -maxdepth 1 -exec rm -- {} \; 2>/dev/null
rm -rf "$TRACE_DIR" 2>/dev/null
rmdir "$OUT_DIR" 2>/dev/null # 删除输出目录
rmdir "$OUT_DIR" 2>/dev/null
if [ -d "$OUT_DIR" ]; then
echo "[-] 错误: 目录 '$OUT_DIR' 已存在且非空 - 请先删除它。" 1>&2
echo "[-] Error: directory '$OUT_DIR' exists and is not empty - delete it first." 1>&2
exit 1
fi
mkdir -m 700 -p "$TRACE_DIR" || exit 1 # 创建临时轨迹目录并设置权限
mkdir -m 700 -p "$TRACE_DIR" || exit 1
if [ ! "$STDIN_FILE" = "" ]; then
rm -f "$STDIN_FILE" || exit 1 # 删除旧的标准输入文件
touch "$STDIN_FILE" || exit 1 # 创建新的标准输入文件
rm -f "$STDIN_FILE" || exit 1
touch "$STDIN_FILE" || exit 1
fi
if [ "$AFL_PATH" = "" ]; then
SHOWMAP="${0%/afl-cmin}/afl-showmap" # 设置 afl-showmap 的路径
SHOWMAP="${0%/afl-cmin}/afl-showmap"
else
SHOWMAP="$AFL_PATH/afl-showmap" # 使用 AFL_PATH 中指定的路径
SHOWMAP="$AFL_PATH/afl-showmap"
fi
if [ ! -x "$SHOWMAP" ]; then
echo "[-] 错误: 找不到 'afl-showmap' - 请设置 AFL_PATH。" 1>&2
rm -rf "$TRACE_DIR" # 删除临时轨迹目录
echo "[-] Error: can't find 'afl-showmap' - please set AFL_PATH." 1>&2
rm -rf "$TRACE_DIR"
exit 1
fi
IN_COUNT=$((`ls -- "$IN_DIR" 2>/dev/null | wc -l`)) # 计算输入目录中的文件数量
IN_COUNT=$((`ls -- "$IN_DIR" 2>/dev/null | wc -l`))
if [ "$IN_COUNT" = "0" ]; then
echo "[+] 嗯,目标目录中没有输入。无需处理。"
rm -rf "$TRACE_DIR" # 删除临时轨迹目录
echo "[+] Hmm, no inputs in the target directory. Nothing to be done."
rm -rf "$TRACE_DIR"
exit 1
fi
FIRST_FILE=`ls "$IN_DIR" | head -1` # 获取第一个文件名
FIRST_FILE=`ls "$IN_DIR" | head -1`
# 确保不是处理目录。
# Make sure that we're not dealing with a directory.
if [ -d "$IN_DIR/$FIRST_FILE" ]; then
echo "[-] 错误: 目标目录包含子目录 - 请修复。" 1>&2
rm -rf "$TRACE_DIR" # 删除临时轨迹目录
echo "[-] Error: The target directory contains subdirectories - please fix." 1>&2
rm -rf "$TRACE_DIR"
exit 1
fi
# 检查复制文件的更有效方法...
# Check for the more efficient way to copy files...
if ln "$IN_DIR/$FIRST_FILE" "$TRACE_DIR/.link_test" 2>/dev/null; then
CP_TOOL=ln # 如果可以链接,则设置复制工具为 ln
CP_TOOL=ln
else
CP_TOOL=cp # 否则使用 cp
CP_TOOL=cp
fi
# 确保我们能从 afl-showmap 中获取任何信息,以免浪费时间。
# Make sure that we can actually get anything out of afl-showmap before we
# waste too much time.
echo "[*] 测试目标二进制文件..."
echo "[*] Testing the target binary..."
if [ "$STDIN_FILE" = "" ]; then
@ -279,42 +278,43 @@ if [ "$STDIN_FILE" = "" ]; then
else
cp "$IN_DIR/$FIRST_FILE" "$STDIN_FILE" # 复制第一个文件到标准输入文件
cp "$IN_DIR/$FIRST_FILE" "$STDIN_FILE"
AFL_CMIN_ALLOW_ANY=1 "$SHOWMAP" -m "$MEM_LIMIT" -t "$TIMEOUT" -o "$TRACE_DIR/.run_test" -Z $EXTRA_PAR -A "$STDIN_FILE" -- "$@" </dev/null
fi
FIRST_COUNT=$((`grep -c . "$TRACE_DIR/.run_test"`)) # 计算运行测试的输出
FIRST_COUNT=$((`grep -c . "$TRACE_DIR/.run_test"`))
if [ "$FIRST_COUNT" -gt "0" ]; then
echo "[+] 好的,记录了 $FIRST_COUNT 个元组。"
echo "[+] OK, $FIRST_COUNT tuples recorded."
else
echo "[-] 错误: 未检测到仪器输出(可能崩溃或超时)。" 1>&2
test "$AFL_KEEP_TRACES" = "" && rm -rf "$TRACE_DIR" # 删除临时轨迹
echo "[-] Error: no instrumentation output detected (perhaps crash or timeout)." 1>&2
test "$AFL_KEEP_TRACES" = "" && rm -rf "$TRACE_DIR"
exit 1
fi
# 开始工作!
# Let's roll!
#############################
# 步骤 1收集轨迹 #
# STEP 1: COLLECTING TRACES #
#############################
echo "[*] 获取 '$IN_DIR' 中输入文件的轨迹..."
echo "[*] Obtaining traces for input files in '$IN_DIR'..."
(
CUR=0 # 当前文件计数器
CUR=0
if [ "$STDIN_FILE" = "" ]; then
while read -r fn; do # 逐行读取输入文件名
while read -r fn; do
CUR=$((CUR+1))
printf "\\r 正在处理文件 $CUR/$IN_COUNT... " # 输出当前进度
printf "\\r Processing file $CUR/$IN_COUNT... "
"$SHOWMAP" -m "$MEM_LIMIT" -t "$TIMEOUT" -o "$TRACE_DIR/$fn" -Z $EXTRA_PAR -- "$@" <"$IN_DIR/$fn"
@ -322,16 +322,18 @@ echo "[*] 获取 '$IN_DIR' 中输入文件的轨迹..."
else
while read -r fn; do # 逐行读取输入文件名
while read -r fn; do
CUR=$((CUR+1))
printf "\\r 正在处理文件 $CUR/$IN_COUNT... " # 输出当前进度
printf "\\r Processing file $CUR/$IN_COUNT... "
cp "$IN_DIR/$fn" "$STDIN_FILE" # 复制文件到标准输入文件
cp "$IN_DIR/$fn" "$STDIN_FILE"
"$SHOWMAP" -m "$MEM_LIMIT" -t "$TIMEOUT" -o "$TRACE_DIR/$fn" -Z $EXTRA_PAR -A "$STDIN_FILE" -- "$@" </dev/null
done < <(ls "$IN_DIR")
fi
)
@ -339,117 +341,121 @@ echo "[*] 获取 '$IN_DIR' 中输入文件的轨迹..."
echo
##########################
# 步骤 2排序元组 #
# STEP 2: SORTING TUPLES #
##########################
# 完成这一步后,我们将按流行度对所有元组进行排序。
# 理由是我们无法避免触发唯一元组的文件,
# 所以我们将从这些文件开始,看看剩下的是什么。
# With this out of the way, we sort all tuples by popularity across all
# datasets. The reasoning here is that we won't be able to avoid the files
# that trigger unique tuples anyway, so we will want to start with them and
# see what's left.
echo "[*] 排序轨迹集(这可能需要一段时间)..."
echo "[*] Sorting trace sets (this may take a while)..."
ls "$IN_DIR" | sed "s#^#$TRACE_DIR/#" | tr '\n' '\0' | xargs -0 -n 1 cat | \
sort | uniq -c | sort -n >"$TRACE_DIR/.all_uniq" # 获取所有唯一元组
sort | uniq -c | sort -n >"$TRACE_DIR/.all_uniq"
TUPLE_COUNT=$((`grep -c . "$TRACE_DIR/.all_uniq"`)) # 计算唯一元组数量
TUPLE_COUNT=$((`grep -c . "$TRACE_DIR/.all_uniq"`))
echo "[+] 找到 $TUPLE_COUNT 个唯一元组,遍历了 $IN_COUNT 个文件。"
echo "[+] Found $TUPLE_COUNT unique tuples across $IN_COUNT files."
#####################################
# 步骤 3选择候选文件 #
# STEP 3: SELECTING CANDIDATE FILES #
#####################################
# 下一步是找到每个元组的最佳候选者。这里的“最佳”
# 指的是包含特定元组的最小输入文件。
# 经验表明这比更复杂的算法要好,
# 而这些算法在 shell 脚本中仍然可以执行。
# The next step is to find the best candidate for each tuple. The "best"
# part is understood simply as the smallest input that includes a particular
# tuple in its trace. Empirical evidence suggests that this produces smaller
# datasets than more involved algorithms that could be still pulled off in
# a shell script.
echo "[*] 寻找每个元组的最佳候选者..."
echo "[*] Finding best candidates for each tuple..."
CUR=0
while read -r fn; do # 逐行读取输入文件名
while read -r fn; do
CUR=$((CUR+1))
printf "\\r 正在处理文件 $CUR/$IN_COUNT... " # 输出当前进度
printf "\\r Processing file $CUR/$IN_COUNT... "
sed "s#\$# $fn#" "$TRACE_DIR/$fn" >>"$TRACE_DIR/.candidate_list" # 将元组与文件名关联
sed "s#\$# $fn#" "$TRACE_DIR/$fn" >>"$TRACE_DIR/.candidate_list"
done < <(ls -rS "$IN_DIR") # 按文件大小倒序列出文件名
done < <(ls -rS "$IN_DIR")
echo
##############################
# 步骤 4加载候选 #
# STEP 4: LOADING CANDIDATES #
##############################
# 此时,我们有一个元组-文件对的文件,按文件大小升序排序
# (由于 ls -rS 的结果)。通过仅按元组排序 (-k 1,1)
# 并配置为对每个键的第一个输出行 (-s -u)
# 我们最终得到了每个元组的最小文件。
# At this point, we have a file of tuple-file pairs, sorted by file size
# in ascending order (as a consequence of ls -rS). By doing sort keyed
# only by tuple (-k 1,1) and configured to output only the first line for
# every key (-s -u), we end up with the smallest file for each tuple.
echo "[*] 排序候选列表(耐心等候)..."
echo "[*] Sorting candidate list (be patient)..."
sort -k1,1 -s -u "$TRACE_DIR/.candidate_list" | \
sed 's/^/BEST_FILE[/;s/ /]="/;s/$/"/' >"$TRACE_DIR/.candidate_script" # 创建候选脚本
sed 's/^/BEST_FILE[/;s/ /]="/;s/$/"/' >"$TRACE_DIR/.candidate_script"
if [ ! -s "$TRACE_DIR/.candidate_script" ]; then
echo "[-] 错误: 从测试用例中未获得轨迹,请检查语法!" 1>&2
echo "[-] Error: no traces obtained from test cases, check syntax!" 1>&2
test "$AFL_KEEP_TRACES" = "" && rm -rf "$TRACE_DIR"
exit 1
fi
# sed 命令将排序后的列表转换为一个填充
# BEST_FILE[tuple]="fname" 的 shell 脚本。让我们加载它!
# The sed command converted the sorted list to a shell script that populates
# BEST_FILE[tuple]="fname". Let's load that!
. "$TRACE_DIR/.candidate_script" # 执行候选脚本
. "$TRACE_DIR/.candidate_script"
##########################
# 步骤 5写出输出 #
# STEP 5: WRITING OUTPUT #
##########################
# 最后一步是获取每个元组的最佳选择,除非由于包含
# 早期候选者而已经设置了该元组;然后将所有
# 与新添加文件关联的元组放入“已拥有”列表。
# 循环从最不常见的元组开始,到最常见的元组结束。
# The final trick is to grab the top pick for each tuple, unless said tuple is
# already set due to the inclusion of an earlier candidate; and then put all
# tuples associated with the newly-added file to the "already have" list. The
# loop works from least popular tuples and toward the most common ones.
echo "[*] 处理候选并写入输出文件..."
echo "[*] Processing candidates and writing output files..."
CUR=0
touch "$TRACE_DIR/.already_have" # 创建已拥有文件
touch "$TRACE_DIR/.already_have"
while read -r cnt tuple; do
while read -r cnt tuple; do # 逐行读取元组和计数
CUR=$((CUR+1))
printf "\\r 正在处理元组 $CUR/$TUPLE_COUNT... " # 输出当前进度
printf "\\r Processing tuple $CUR/$TUPLE_COUNT... "
# 如果我们已经拥有此元组,跳过它。
# If we already have this tuple, skip it.
grep -q "^$tuple\$" "$TRACE_DIR/.already_have" && continue # 检查已拥有列表
grep -q "^$tuple\$" "$TRACE_DIR/.already_have" && continue
FN=${BEST_FILE[tuple]} # 获取最佳候选文件名
FN=${BEST_FILE[tuple]}
$CP_TOOL "$IN_DIR/$FN" "$OUT_DIR/$FN" # 复制文件到输出目录
$CP_TOOL "$IN_DIR/$FN" "$OUT_DIR/$FN"
if [ "$((CUR % 5))" = "0" ]; then # 每处理五个元组时进行一次排序
sort -u "$TRACE_DIR/$FN" "$TRACE_DIR/.already_have" >"$TRACE_DIR/.tmp" # 合并并去重
mv -f "$TRACE_DIR/.tmp" "$TRACE_DIR/.already_have" # 替换原有的已拥有文件
if [ "$((CUR % 5))" = "0" ]; then
sort -u "$TRACE_DIR/$FN" "$TRACE_DIR/.already_have" >"$TRACE_DIR/.tmp"
mv -f "$TRACE_DIR/.tmp" "$TRACE_DIR/.already_have"
else
cat "$TRACE_DIR/$FN" >>"$TRACE_DIR/.already_have" # 否则直接追加到已拥有文件
cat "$TRACE_DIR/$FN" >>"$TRACE_DIR/.already_have"
fi
done <"$TRACE_DIR/.all_uniq" # 从所有唯一元组读取数据
done <"$TRACE_DIR/.all_uniq"
echo
OUT_COUNT=`ls -- "$OUT_DIR" | wc -l` # 统计输出目录中的文件数量
OUT_COUNT=`ls -- "$OUT_DIR" | wc -l`
if [ "$OUT_COUNT" = "1" ]; then
echo "[!] 警告: 所有测试用例具有相同的轨迹,请检查语法!"
echo "[!] WARNING: All test cases had the same traces, check syntax!"
fi
echo "[+] 已缩小到 $OUT_COUNT 个文件,保存在 '$OUT_DIR' 中。" # 输出结果数量
echo "[+] Narrowed down to $OUT_COUNT files, saved in '$OUT_DIR'."
echo
test "$AFL_KEEP_TRACES" = "" && rm -rf "$TRACE_DIR" # 删除临时轨迹目录(如果需要)
test "$AFL_KEEP_TRACES" = "" && rm -rf "$TRACE_DIR"
exit 0 # 正常退出脚本
exit 0

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

@ -47,73 +47,79 @@
#include <stdlib.h>
#include <string.h>
static u8 *as_path; /* AFL“as”包装器的路径 */
static u8 **cc_params; /* 传递给真实C编译器的参数 */
static u32 cc_par_cnt = 1; /* 参数计数包括argv0 */
static u8 be_quiet, /* 静音模式 */
clang_mode; /* 被称为afl clang模式 */
static u8 *as_path; /*AFL“as”包装器的路径*/
static u8 **cc_params; /*传递给真实CC的参数*/
static u32 cc_par_cnt = 1; /*参数计数包括argv0*/
static u8 be_quiet, /*静音模式*/
clang_mode; /*被称为afl clang模式**/
/* 尝试在 AFL_PATH 或从 argv[0] 派生的位置找到我们的“假”GNU 汇编器。
*/
static void find_as(u8 *argv0)
{
u8 *afl_path = getenv("AFL_PATH"); // 获取环境变量AFL_PATH的值
u8 *afl_path = getenv("AFL_PATH");
u8 *slash, *tmp;
if (afl_path) // 如果AFL_PATH环境变量存在
if (afl_path)
{
tmp = alloc_printf("%s/as", afl_path); // 构造路径字符串
if (!access(tmp, X_OK)) // 检查路径是否存在且可执行
tmp = alloc_printf("%s/as", afl_path);
if (!access(tmp, X_OK))
{
as_path = afl_path; // 设置as_path为找到的路径
ck_free(tmp); // 释放临时字符串
as_path = afl_path;
ck_free(tmp);
return;
}
ck_free(tmp); // 如果不可执行,释放临时字符串
ck_free(tmp);
}
slash = strrchr(argv0, '/'); // 在argv0中查找最后一个'/',以获取目录部分
slash = strrchr(argv0, '/');
if (slash) // 如果找到了'/'说明argv0是一个路径
if (slash)
{
u8 *dir;
*slash = 0; // 暂时将'/'替换为'\0',以便截取目录部分
dir = ck_strdup(argv0); // 复制目录部分
*slash = '/'; // 恢复'/'恢复argv0的完整路径
*slash = 0;
dir = ck_strdup(argv0);
*slash = '/';
tmp = alloc_printf("%s/afl-as", dir); // 构造路径字符串
tmp = alloc_printf("%s/afl-as", dir);
if (!access(tmp, X_OK)) // 检查路径是否存在且可执行
if (!access(tmp, X_OK))
{
as_path = dir; // 设置as_path为找到的路径
ck_free(tmp); // 释放临时字符串
as_path = dir;
ck_free(tmp);
return;
}
ck_free(tmp); // 如果不可执行,释放临时字符串
ck_free(dir); // 释放目录字符串
ck_free(tmp);
ck_free(dir);
}
if (!access(AFL_PATH "/as", X_OK)) // 检查默认路径AFL_PATH/as是否存在且可执行
if (!access(AFL_PATH "/as", X_OK))
{
as_path = AFL_PATH; // 设置as_path为默认路径
as_path = AFL_PATH;
return;
}
FATAL("Unable to find AFL wrapper binary for 'as'. Please set AFL_PATH"); // 如果找不到,输出错误信息并中止
FATAL("Unable to find AFL wrapper binary for 'as'. Please set AFL_PATH");
}
/* 将argv复制到cc_params进行必要的编辑 */
/*将argv复制到cc_params进行必要的编辑*/
static void edit_params(u32 argc, char **argv)
{
u8 fortify_set = 0, asan_set = 0; // 标志变量用于检测是否设置了fortify和asan
u8 fortify_set = 0, asan_set = 0;
u8 *name;
#if defined(__FreeBSD__) && defined(__x86_64__)
u8 m32_set = 0; // FreeBSD x86_64环境下检测是否设置了m32
u8 m32_set = 0;
#endif
/********************************************************************************
@ -121,74 +127,77 @@ static void edit_params(u32 argc, char **argv)
* 使 afl-clang使 C C++
********************************************************************************/
cc_params = ck_alloc((argc + 128) * sizeof(u8 *)); // 分配足够大的空间来存储编译器参数
name = strrchr(argv[0], '/'); // 获取可执行文件名
if (!name)
name = argv[0]; // 如果没有找到'/'则argv0就是可执行文件名
cc_params = ck_alloc((argc + 128) * sizeof(u8 *));
name = strrchr(argv[0], '/');
if (!name)
name = argv[0];
else
name++;
if (!strncmp(name, "afl-clang", 9))
{
clang_mode = 1;
setenv(CLANG_ENV_VAR, "1", 1);
if (!strcmp(name, "afl-clang++"))
{
u8 *alt_cxx = getenv("AFL_CXX");
cc_params[0] = alt_cxx ? alt_cxx : (u8 *)"clang++";
}
else
{
u8 *alt_cc = getenv("AFL_CC");
cc_params[0] = alt_cc ? alt_cc : (u8 *)"clang";
}
}
else
name++; // 否则name指向可执行文件名的第一个字符
if (!strncmp(name, "afl-clang", 9)) // 如果可执行文件名以afl-clang开头
{
clang_mode = 1; // 设置clang_mode标志为1表示使用clang模式
setenv(CLANG_ENV_VAR, "1", 1); // 设置环境变量表示正在使用afl-clang
/*安装了GCJ和Eclipse后您实际上可以编译Java这个
abortafl-fuzz使
使Java退
*/
if (!strcmp(name, "afl-clang++")) // 如果是afl-clang++
{
u8 *alt_cxx = getenv("AFL_CXX"); // 获取环境变量AFL_CXX的值
cc_params[0] = alt_cxx ? alt_cxx : (u8 *)"clang++"; // 如果设置了AFL_CXX则使用该值作为编译器否则使用clang++
}
else // 如果是afl-clang
{
u8 *alt_cc = getenv("AFL_CC"); // 获取环境变量AFL_CC的值
cc_params[0] = alt_cc ? alt_cc : (u8 *)"clang"; // 如果设置了AFL_CC则使用该值作为编译器否则使用clang
}
}
else // 如果不是afl-clang
{
// 安装了GCJ和Eclipse后您实际上可以编译Java这个
// 仪器将工作(令人惊讶)。唉,未处理的异常确实如此
// 不调用abort因此需要修改afl-fuzz以使其相等
// 使用Java时具有崩溃条件的非零退出代码
// 二进制文件。嗯
#ifdef __APPLE__ // 如果是在MacOS X系统上
if (!strcmp(name, "afl-g++")) // 如果是afl-g++
cc_params[0] = getenv("AFL_CXX"); // 获取环境变量AFL_CXX的值作为编译器
else if (!strcmp(name, "afl-gcj")) // 如果是afl-gcj
cc_params[0] = getenv("AFL_GCJ"); // 获取环境变量AFL_GCJ的值作为编译器
#ifdef __APPLE__
if (!strcmp(name, "afl-g++"))
cc_params[0] = getenv("AFL_CXX");
else if (!strcmp(name, "afl-gcj"))
cc_params[0] = getenv("AFL_GCJ");
else
cc_params[0] = getenv("AFL_CC"); // 其他情况获取环境变量AFL_CC的值作为编译器
cc_params[0] = getenv("AFL_CC");
if (!cc_params[0]) // 如果没有设置相应的环境变量
if (!cc_params[0])
{
SAYF("\n" cLRD "[-] " cRST // 输出错误信息
SAYF("\n" cLRD "[-] " cRST
"On Apple systems, 'gcc' is usually just a wrapper for clang. Please use the\n"
" 'afl-clang' utility instead of 'afl-gcc'. If you really have GCC installed,\n"
" set AFL_CC or AFL_CXX to specify the correct path to that compiler.\n");
FATAL("AFL_CC or AFL_CXX required on MacOS X"); // 输出错误信息并中止
FATAL("AFL_CC or AFL_CXX required on MacOS X");
}
#else // 如果不是在MacOS X系统上
#else
if (!strcmp(name, "afl-g++")) // 如果是afl-g++
if (!strcmp(name, "afl-g++"))
{
u8 *alt_cxx = getenv("AFL_CXX"); // 获取环境变量AFL_CXX的值
cc_params[0] = alt_cxx ? alt_cxx : (u8 *)"g++"; // 如果设置了AFL_CXX则使用该值作为编译器否则使用g++
u8 *alt_cxx = getenv("AFL_CXX");
cc_params[0] = alt_cxx ? alt_cxx : (u8 *)"g++";
}
else if (!strcmp(name, "afl-gcj")) // 如果是afl-gcj
else if (!strcmp(name, "afl-gcj"))
{
u8 *alt_cc = getenv("AFL_GCJ"); // 获取环境变量AFL_GCJ的值
cc_params[0] = alt_cc ? alt_cc : (u8 *)"gcj"; // 如果设置了AFL_GCJ则使用该值作为编译器否则使用gcj
u8 *alt_cc = getenv("AFL_GCJ");
cc_params[0] = alt_cc ? alt_cc : (u8 *)"gcj";
}
else // 如果是afl-gcc
else
{
u8 *alt_cc = getenv("AFL_CC"); // 获取环境变量AFL_CC的值
cc_params[0] = alt_cc ? alt_cc : (u8 *)"gcc"; // 如果设置了AFL_CC则使用该值作为编译器否则使用gcc
u8 *alt_cc = getenv("AFL_CC");
cc_params[0] = alt_cc ? alt_cc : (u8 *)"gcc";
}
#endif /* __APPLE__ */
@ -224,35 +233,32 @@ static void edit_params(u32 argc, char **argv)
if (!strcmp(cur, "-pipe"))
continue;
/ *
*
*
* FORTIFY_SOURCEASAN/MSAN
* /
#if defined(__FreeBSD__) && defined(__x86_64__)
// 检测是否在FreeBSD的64位系统上使用了-m32选项如果是则设置m32_set为1
if (!strcmp(cur, "-m32"))
m32_set = 1;
#endif
// 检测是否使用了address sanitizer或memory sanitizer选项如果是则设置asan_set为1
if (!strcmp(cur, "-fsanitize=address") ||
!strcmp(cur, "-fsanitize=memory"))
asan_set = 1;
// 检测是否设置了_FORTIFY_SOURCE宏如果是则设置fortify_set为1
if (strstr(cur, "FORTIFY_SOURCE"))
fortify_set = 1;
// 将当前命令行参数添加到编译器参数列表中
cc_params[cc_par_cnt++] = cur;
}
// 添加编译器的-B参数指定路径给as_path
cc_params[cc_par_cnt++] = "-B";
cc_params[cc_par_cnt++] = as_path;
// 如果clang_mode为真则添加-no-integrated-as参数
if (clang_mode)
cc_params[cc_par_cnt++] = "-no-integrated-as";
// 如果设置了AFL_HARDEN环境变量则添加-fstack-protector-all参数并在未设置_FORTIFY_SOURCE时定义它
if (getenv("AFL_HARDEN"))
{
@ -262,7 +268,6 @@ static void edit_params(u32 argc, char **argv)
cc_params[cc_par_cnt++] = "-D_FORTIFY_SOURCE=2";
}
// 如果asan_set为真则设置AFL_USE_ASAN环境变量为1用于通知afl使用asan
if (asan_set)
{
@ -272,27 +277,25 @@ static void edit_params(u32 argc, char **argv)
}
else if (getenv("AFL_USE_ASAN"))
{
// 如果设置了AFL_USE_ASAN但未设置asan_set检查是否同时设置了MSAN和AFL_HARDEN如果是则报错
if (getenv("AFL_USE_MSAN"))
FATAL("ASAN和MSAN是互斥的");
FATAL("ASAN and MSAN are mutually exclusive");
if (getenv("AFL_HARDEN"))
FATAL("ASAN和AFL_HARDEN是互斥的");
FATAL("ASAN and AFL_HARDEN are mutually exclusive");
// 取消定义_FORTIFY_SOURCE宏添加address sanitizer参数
cc_params[cc_par_cnt++] = "-U_FORTIFY_SOURCE";
cc_params[cc_par_cnt++] = "-fsanitize=address";
}
else if (getenv("AFL_USE_MSAN"))
{
// 如果设置了AFL_USE_MSAN检查是否同时设置了ASAN和AFL_HARDEN如果是则报错
if (getenv("AFL_USE_ASAN"))
FATAL("ASAN和MSAN是互斥的");
FATAL("ASAN and MSAN are mutually exclusive");
if (getenv("AFL_HARDEN"))
FATAL("MSAN和AFL_HARDEN是互斥的");
FATAL("MSAN and AFL_HARDEN are mutually exclusive");
// 取消定义_FORTIFY_SOURCE宏添加memory sanitizer参数
cc_params[cc_par_cnt++] = "-U_FORTIFY_SOURCE";
cc_params[cc_par_cnt++] = "-fsanitize=memory";
}
@ -303,7 +306,6 @@ static void edit_params(u32 argc, char **argv)
* : d:\code\google_AFL\src\afl-gcc.c
********************************************************************************/
// 如果未设置AFL_DONT_OPTIMIZE环境变量则进行优化设置
if (!getenv("AFL_DONT_OPTIMIZE"))
{
@ -313,45 +315,41 @@ static void edit_params(u32 argc, char **argv)
bug*/
// 如果不是clang模式或者未设置m32选项则添加-g参数
if (!clang_mode || !m32_set)
cc_params[cc_par_cnt++] = "-g";
#else
// 添加调试信息参数-g
cc_params[cc_par_cnt++] = "-g";
#endif
// 添加-O3优化级别参数和-unroll-loops参数
cc_params[cc_par_cnt++] = "-O3";
cc_params[cc_par_cnt++] = "-funroll-loops";
/* Two indicators that you're building for fuzzing; one of them is
AFL-specific, the other is shared with libfuzzer. */
// 添加编译器标志,指示正在为模糊测试构建代码
cc_params[cc_par_cnt++] = "-D__AFL_COMPILER=1";
cc_params[cc_par_cnt++] = "-DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION=1";
}
// 如果设置了AFL_NO_BUILTIN环境变量则添加参数禁用内置的字符串和内存比较函数
if (getenv("AFL_NO_BUILTIN"))
{
cc_params[cc_par_cnt++] = "-fno-builtin-strcmp";
cc_params[cc_par_cnt++] = "-fno-builtin-strncmp";
cc_params[cc_par_cnt++] = "-fno-builtin-strcasecmp";
cc_params[cc_par_cnt++] = "-fno-builtin-strncasecmp";
cc_params[cc_par_cnt++] = "-fno-builtin-memcmp";
cc_params[cc_par_cnt++] = "-fno-builtin-strstr";
cc_params[cc_par_cnt++] = "-fno-builtin-strcasestr";
// 注意:这里重复了两遍-fno-builtin-strncasecmp 和 -fno-builtin-memcmp应去掉重复的部分
// cc_params[cc_par_cnt++] = "-fno-builtin-strncasecmp";
// cc_params[cc_par_cnt++] = "-fno-builtin-memcmp";
}
// 设置编译器参数列表的结束标志NULL
/***********************************************
*
* "AFL_NO_BUILTIN"
*
***********************************************/
if (getenv("AFL_NO_BUILTIN"))
{
cc_params[cc_par_cnt++] = "-fno-builtin-strcmp";
cc_params[cc_par_cnt++] = "-fno-builtin-strncmp";
cc_params[cc_par_cnt++] = "-fno-builtin-strcasecmp";
cc_params[cc_par_cnt++] = "-fno-builtin-strncasecmp";
cc_params[cc_par_cnt++] = "-fno-builtin-memcmp";
cc_params[cc_par_cnt++] = "-fno-builtin-strstr";
cc_params[cc_par_cnt++] = "-fno-builtin-strcasestr";
} cc_params[cc_par_cnt++] = "-fno-builtin-strncasecmp";
cc_params[cc_par_cnt++] = "-fno-builtin-memcmp";
cc_params[cc_par_cnt] = NULL;
}
@ -360,42 +358,45 @@ static void edit_params(u32 argc, char **argv)
// 主函数,程序的入口点
int main(int argc, char **argv)
{
// 检查标准错误输出是否为终端,以及环境变量是否开启安静模式
if (isatty(2) && !getenv("AFL_QUIET"))
{
SAYF(cCYA "afl-cc " cBRI VERSION cRST " by <lcamtuf@google.com>\n");
}
else
be_quiet = 1;
// 检查传入的参数数量是否少于2,如果是则输出使用说明并退出
// 检查传入的参数数量是否少于2
if (argc < 2)
{
SAYF("\n"
"这是一个辅助afl-fuzz的工具程序。它可以用作gcc或clang的替代品\n"
"让你能够使用必要的运行时检测重新编译第三方代码。\n"
"常见的使用模式如下之一:\n\n"
"This is a helper application for afl-fuzz. It serves as a drop-in replacement\n"
"for gcc or clang, letting you recompile third-party code with the required\n"
"runtime instrumentation. A common use pattern would be one of the following:\n\n"
" CC=%s/afl-gcc ./configure\n"
" CXX=%s/afl-g++ ./configure\n\n"
"你可以通过AFL_CC, AFL_CXX, 和 AFL_AS指定自定义的后续编译工具链。\n"
"设置AFL_HARDEN会在编译代码时启用hardening优化。\n\n",
"You can specify custom next-stage toolchain via AFL_CC, AFL_CXX, and AFL_AS.\n"
"Setting AFL_HARDEN enables hardening optimizations in the compiled code.\n\n",
BIN_PATH, BIN_PATH);
exit(1);
}
// 查找汇编器根据argv[0]来确定使用的编译器是gcc还是clang
// 查找汇编器
find_as(argv[0]);
// 编辑命令行参数,根据需要添加和调整编译器参数
// 编辑参数
edit_params(argc, argv);
// 执行编译器,并传递编辑后的参数列表
// 执行编译器,并传递参数
execvp(cc_params[0], (char **)cc_params);
// 如果execvp调用失败输出错误信息并退出程序
// 如果执行失败,输出错误信息
FATAL("Oops, failed to execute '%s' - check your PATH", cc_params[0]);
return 0;

@ -57,108 +57,104 @@
/* Get unix time in microseconds. */
// 定义一个函数来获取当前时间(以微秒为单位)
static u64 get_cur_time_us(void) {
struct timeval tv; // 用于存储时间值
struct timezone tz; // 用于存储时区信息
struct timeval tv;
struct timezone tz;
gettimeofday(&tv, &tz); // 获取当前时间
gettimeofday(&tv, &tz);
// 返回以微秒为单位的时间
return (tv.tv_sec * 1000000ULL) + tv.tv_usec;
}
/* 获取CPU使用时间以微秒为单位。 */
/* Get CPU usage in microseconds. */
static u64 get_cpu_usage_us(void) {
struct rusage u; // 用于存储进程资源使用情况
struct rusage u;
getrusage(RUSAGE_SELF, &u); // 获取当前进程的资源使用情况
getrusage(RUSAGE_SELF, &u);
// 返回用户态和内核态CPU使用时间之和以微秒为单位
return (u.ru_utime.tv_sec * 1000000ULL) + u.ru_utime.tv_usec +
(u.ru_stime.tv_sec * 1000000ULL) + u.ru_stime.tv_usec;
}
/* 测量抢占率。 */
/* Measure preemption rate. */
static u32 measure_preemption(u32 target_ms) {
static volatile u32 v1, v2; // 定义两个静态易失变量用于循环计数
static volatile u32 v1, v2;
u64 st_t, en_t, st_c, en_c, real_delta, slice_delta; // 定义时间变量
s32 loop_repeats = 0; // 定义循环重复次数
u64 st_t, en_t, st_c, en_c, real_delta, slice_delta;
s32 loop_repeats = 0;
st_t = get_cur_time_us(); // 获取开始时间
st_c = get_cpu_usage_us(); // 获取开始时的CPU使用时间
st_t = get_cur_time_us();
st_c = get_cpu_usage_us();
repeat_loop: // 定义循环标签
repeat_loop:
v1 = CTEST_BUSY_CYCLES; // 设置v1为循环次数
v1 = CTEST_BUSY_CYCLES;
// 循环v1次每次v2自增1模拟CPU繁忙
while (v1--) v2++;
sched_yield(); // 让出CPU允许其他进程运行
sched_yield();
en_t = get_cur_time_us(); // 获取结束时间
en_t = get_cur_time_us();
// 如果实际运行时间小于目标时间,则增加循环次数并继续循环
if (en_t - st_t < target_ms * 1000) {
loop_repeats++;
goto repeat_loop;
}
// 获取结束时的CPU使用时间
/* Let's see what percentage of this time we actually had a chance to
run, and how much time was spent in the penalty box. */
en_c = get_cpu_usage_us();
// 计算实际运行时间(以毫秒为单位)
real_delta = (en_t - st_t) / 1000;
// 计算CPU使用时间以毫秒为单位
slice_delta = (en_c - st_c) / 1000;
// 返回实际运行时间占CPU使用时间的百分比作为抢占率
return real_delta * 100 / slice_delta;
}
/* 进行基准测试。 */
/* Do the benchmark thing. */
int main(int argc, char** argv) {
#ifdef HAVE_AFFINITY // 如果支持AFFINITY设置进程CPU亲和性
#ifdef HAVE_AFFINITY
u32 cpu_cnt = sysconf(_SC_NPROCESSORS_ONLN), // 获取CPU核心数
idle_cpus = 0, maybe_cpus = 0, i; // 初始化空闲和可能可用的CPU核心数
u32 cpu_cnt = sysconf(_SC_NPROCESSORS_ONLN),
idle_cpus = 0, maybe_cpus = 0, i;
SAYF(cCYA "afl-gotcpu " cBRI VERSION cRST " by <lcamtuf@google.com>\n"); // 输出程序信息
SAYF(cCYA "afl-gotcpu " cBRI VERSION cRST " by <lcamtuf@google.com>\n");
// 输出测量核心抢占率的信息,包括预计所需时间
ACTF("Measuring per-core preemption rate (this will take %0.02f sec)...",
((double)CTEST_CORE_TRG_MS) / 1000);
// 对每个CPU核心进行抢占率测量
for (i = 0; i < cpu_cnt; i++) {
s32 fr = fork(); // 创建子进程
s32 fr = fork();
if (fr < 0) PFATAL("fork failed"); // 如果fork失败则输出错误信息
if (fr < 0) PFATAL("fork failed");
if (!fr) { // 子进程中
if (!fr) {
cpu_set_t c; // 定义CPU亲和性集合
u32 util_perc; // 定义CPU利用率
cpu_set_t c;
u32 util_perc;
CPU_ZERO(&c); // 清空CPU亲和性集合
CPU_SET(i, &c); // 设置亲和性为当前核心
CPU_ZERO(&c);
CPU_SET(i, &c);
// 设置进程亲和性到指定的核心
if (sched_setaffinity(0, sizeof(c), &c))
PFATAL("sched_setaffinity failed for cpu %d", i);
util_perc = measure_preemption(CTEST_CORE_TRG_MS); // 测量抢占率
util_perc = measure_preemption(CTEST_CORE_TRG_MS);
// 根据抢占率输出核心状态
if (util_perc < 110) {
SAYF(" Core #%u: " cLGN "AVAILABLE " cRST "(%u%%)\n", i, util_perc);
@ -179,21 +175,18 @@ int main(int argc, char** argv) {
}
// 等待所有子进程结束,并统计结果
for (i = 0; i < cpu_cnt; i++) {
int ret;
if (waitpid(-1, &ret, 0) < 0) PFATAL("waitpid failed"); // 等待子进程结束
if (waitpid(-1, &ret, 0) < 0) PFATAL("waitpid failed");
// 根据子进程退出状态判断核心是否可用
if (WEXITSTATUS(ret) == 0) idle_cpus++;
if (WEXITSTATUS(ret) <= 1) maybe_cpus++;
}
SAYF(cGRA "\n>>> "); // 输出结果标题
SAYF(cGRA "\n>>> ");
// 根据统计结果输出最终判断
if (idle_cpus) {
if (maybe_cpus == idle_cpus) {
@ -208,30 +201,24 @@ int main(int argc, char** argv) {
}
SAYF(cGRA " <<<" cRST "\n\n"); // 输出结果结束标志
SAYF(cGRA " <<<" cRST "\n\n");
return 0;
}
// 如果有可能可用的核心,但没有完全空闲的核心
if (maybe_cpus) {
SAYF(cYEL "CAUTION: " cRST "You may still have %u core%s available.",
maybe_cpus, maybe_cpus > 1 ? "s" : "");
SAYF(cGRA " <<<" cRST "\n\n"); // 输出结果结束标志
SAYF(cGRA " <<<" cRST "\n\n");
return 1;
}
// 如果所有核心都过载
SAYF(cLRD "FAIL: " cRST "All cores are overbooked.");
SAYF(cGRA " <<<" cRST "\n\n"); // 输出结果结束标志
SAYF(cGRA " <<<" cRST "\n\n");
return 2;
#endif
}
#else
u32 util_perc;

@ -1,175 +1,170 @@
#!/bin/sh
#
# American Fuzzy Lop - 高级持久图形化工具
# american fuzzy lop - Advanced Persistent Graphing
# -------------------------------------------------
#
# 作者和维护者:Michal Zalewski <lcamtuf@google.com>
# 基于 Michael Rash 的设计和原型。
# Written and maintained by Michal Zalewski <lcamtuf@google.com>
# Based on a design & prototype by Michael Rash.
#
# 版权所有 2014, 2015 Google LLC 保留所有权利。
# Copyright 2014, 2015 Google LLC All rights reserved.
#
# 根据 Apache 许可证,版本 2.0"许可证")授权;
# 除非遵循许可证,否则您不能使用此文件。
# 您可以从以下网址获取许可证的副本:
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at:
#
# http://www.apache.org/licenses/LICENSE-2.0
#
echo "为 afl-fuzz 提供的进度绘图工具 <lcamtuf@google.com>" # 输出程序名称
echo "progress plotting utility for afl-fuzz by <lcamtuf@google.com>"
echo
# 检查参数数量是否为 2
if [ ! "$#" = "2" ]; then
# 输出使用说明到标准错误
cat 1>&2 <<_EOF_
此程序从 afl-fuzz 输出数据生成 gnuplot 图像。用法:
This program generates gnuplot images from afl-fuzz output data. Usage:
$0 afl_state_dir graph_output_dir
参数 afl_state_dir 应指向现有的状态目录,该目录属于任何
正在运行或已停止的 afl-fuzz 实例;而 graph_output_dir 应指向
一个空目录,在该目录中此工具可以写入结果图表。
The afl_state_dir parameter should point to an existing state directory for any
active or stopped instance of afl-fuzz; while graph_output_dir should point to
an empty directory where this tool can write the resulting plots to.
该程序将在输出目录中放置 index.html 和三张 PNG 图像;
您应该能够用任何您喜欢的 web 浏览器查看它。
The program will put index.html and three PNG images in the output directory;
you should be able to view it with any web browser of your choice.
_EOF_
exit 1 # 退出程序,返回错误状态
exit 1
fi
# 如果 AFL_ALLOW_TMP 变量为空,则进行临时目录检查
if [ "$AFL_ALLOW_TMP" = "" ]; then
echo "$1" | grep -qE '^(/var)?/tmp/' # 检查第一个参数是否在/tmp
echo "$1" | grep -qE '^(/var)?/tmp/'
T1="$?"
echo "$2" | grep -qE '^(/var)?/tmp/' # 检查第二个参数是否在/tmp
echo "$2" | grep -qE '^(/var)?/tmp/'
T2="$?"
if [ "$T1" = "0" -o "$T2" = "0" ]; then
echo "[-] 错误: 不应在共享 /tmp 目录中使用此脚本。" 1>&2 # 输出错误信息
exit 1 # 退出程序,返回错误状态
echo "[-] Error: this script shouldn't be used with shared /tmp directories." 1>&2
exit 1
fi
fi
# 检查输入目录是否有效(必须存在 'plot_data' 文件)
if [ ! -f "$1/plot_data" ]; then
echo "[-] 错误: 输入目录无效(缺少 'plot_data' 文件)。" 1>&2 # 输出错误信息
exit 1 # 退出程序,返回错误状态
echo "[-] Error: input directory is not valid (missing 'plot_data')." 1>&2
exit 1
fi
# 从 fuzzer_stats 文件中提取 banner 信息
BANNER="`cat "$1/fuzzer_stats" | grep '^afl_banner ' | cut -d: -f2- | cut -b2-`"
test "$BANNER" = "" && BANNER="(none)" # 如果未找到 banner设置为 (none)
# 查找 gnuplot 命令
test "$BANNER" = "" && BANNER="(none)"
GNUPLOT=`which gnuplot 2>/dev/null`
# 检查是否能找到 gnuplot
if [ "$GNUPLOT" = "" ]; then
echo "[-] 错误: 在您的 \$PATH 中找不到 'gnuplot'。" 1>&2 # 输出错误信息
exit 1 # 退出程序,返回错误状态
echo "[-] Error: can't find 'gnuplot' in your \$PATH." 1>&2
exit 1
fi
# 创建输出目录,如果目录已经存在则忽略错误
mkdir "$2" 2>/dev/null
# 检查输出目录是否成功创建
if [ ! -d "$2" ]; then
echo "[-] 错误: 无法创建输出目录 - 请选择另一个位置。" 1>&2 # 输出错误信息
exit 1 # 退出程序,返回错误状态
echo "[-] Error: unable to create the output directory - pick another location." 1>&2
exit 1
fi
# 删除旧的图像文件
rm -f "$2/high_freq.png" "$2/low_freq.png" "$2/exec_speed.png"
mv -f "$2/index.html" "$2/index.html.orig" 2>/dev/null # 备份旧的 index.html 文件
mv -f "$2/index.html" "$2/index.html.orig" 2>/dev/null
echo "[*] 生成图表..." # 输出生成图表的提示
echo "[*] Generating plots..."
(
# gnuplot 脚本开始
cat <<_EOF_
set terminal png truecolor enhanced size 1000,300 butt # 设置输出为 PNG 格式,启用颜色和大小
set terminal png truecolor enhanced size 1000,300 butt
set output '$2/high_freq.png' # 设置输出文件为高频图像文件
set output '$2/high_freq.png'
set xdata time # 设置 x 轴数据为时间
set timefmt '%s' # 设置时间格式
set format x "%b %d\n%H:%M" # 设置 x 轴刻度格式
set tics font 'small' # 设置刻度字体
unset mxtics # 禁用 x 轴的次刻度
unset mytics # 禁用 y 轴的次刻度
set xdata time
set timefmt '%s'
set format x "%b %d\n%H:%M"
set tics font 'small'
unset mxtics
unset mytics
set grid xtics linetype 0 linecolor rgb '#e0e0e0' # 设置 x 轴网格线样式和颜色
set grid ytics linetype 0 linecolor rgb '#e0e0e0' # 设置 y 轴网格线样式和颜色
set border linecolor rgb '#50c0f0' # 设置边框颜色
set tics textcolor rgb '#000000' # 设置刻度文本颜色
set key outside # 设置图例位置在外部
set grid xtics linetype 0 linecolor rgb '#e0e0e0'
set grid ytics linetype 0 linecolor rgb '#e0e0e0'
set border linecolor rgb '#50c0f0'
set tics textcolor rgb '#000000'
set key outside
set autoscale xfixmin # 自动缩放 x 轴最小值
set autoscale xfixmax # 自动缩放 x 轴最大值
set autoscale xfixmin
set autoscale xfixmax
# 绘制高频图像
plot '$1/plot_data' using 1:4 with filledcurve x1 title 'total paths' linecolor rgb '#000000' fillstyle transparent solid 0.2 noborder, \\
'' using 1:3 with filledcurve x1 title 'current path' linecolor rgb '#f0f0f0' fillstyle transparent solid 0.5 noborder, \\
'' using 1:5 with lines title 'pending paths' linecolor rgb '#0090ff' linewidth 3, \\
'' using 1:6 with lines title 'pending favs' linecolor rgb '#c00080' linewidth 3, \\
'' using 1:2 with lines title 'cycles done' linecolor rgb '#c000f0' linewidth 3
set terminal png truecolor enhanced size 1000,200 butt # 设置输出为低频图像文件
set output '$2/low_freq.png' # 设置输出文件为低频图像文件
set terminal png truecolor enhanced size 1000,200 butt
set output '$2/low_freq.png'
# 绘制低频图像
plot '$1/plot_data' using 1:8 with filledcurve x1 title '' linecolor rgb '#c00080' fillstyle transparent solid 0.2 noborder, \\
'' using 1:8 with lines title ' uniq crashes' linecolor rgb '#c00080' linewidth 3, \\
'' using 1:9 with lines title 'uniq hangs' linecolor rgb '#c000f0' linewidth 3, \\
'' using 1:10 with lines title 'levels' linecolor rgb '#0090ff' linewidth 3
set terminal png truecolor enhanced size 1000,200 butt # 设置输出为执行速度图像文件
set terminal png truecolor enhanced size 1000,200 butt
set output '$2/exec_speed.png'
set output '$2/exec_speed.png' # 设置输出文件为执行速度图像文件
# 绘制执行速度图像
plot '$1/plot_data' using 1:11 with filledcurve x1 title '' linecolor rgb '#0090ff' fillstyle transparent solid 0.2 noborder, \\
'$1/plot_data' using 1:11 with lines title ' execs/sec' linecolor rgb '#0090ff' linewidth 3 smooth bezier; # 使用平滑贝塞尔曲线
'$1/plot_data' using 1:11 with lines title ' execs/sec' linecolor rgb '#0090ff' linewidth 3 smooth bezier;
_EOF_
) | gnuplot # 将生成的 gnuplot 脚本管道到 gnuplot
) | gnuplot
# 检查执行速度图像是否生成成功
if [ ! -s "$2/exec_speed.png" ]; then
echo "[-] 错误: 出现问题!您可能使用了古老版本的 gnuplot。" 1>&2 # 输出错误信息
exit 1 # 退出程序,返回错误状态
echo "[-] Error: something went wrong! Perhaps you have an ancient version of gnuplot?" 1>&2
exit 1
fi
echo "[*] 生成 index.html..." # 输出生成 index.html 的提示
echo "[*] Generating index.html..."
# 创建 index.html 文件
cat >"$2/index.html" <<_EOF_
<table style="font-family: 'Trebuchet MS', 'Tahoma', 'Arial', 'Helvetica'">
<tr><td style="width: 18ex"><b>Banner:</b></td><td>$BANNER</td></tr> # 显示 banner 信息
<tr><td><b>Directory:</b></td><td>$1</td></tr> # 显示输入目录
<tr><td><b>Generated on:</b></td><td>`date`</td></tr> # 显示生成日期
<tr><td style="width: 18ex"><b>Banner:</b></td><td>$BANNER</td></tr>
<tr><td><b>Directory:</b></td><td>$1</td></tr>
<tr><td><b>Generated on:</b></td><td>`date`</td></tr>
</table>
<p>
<img src="high_freq.png" width=1000 height=300><p> # 显示高频图像
<img src="low_freq.png" width=1000 height=200><p> # 显示低频图像
<img src="exec_speed.png" width=1000 height=200> # 显示执行速度图像
<img src="high_freq.png" width=1000 height=300><p>
<img src="low_freq.png" width=1000 height=200><p>
<img src="exec_speed.png" width=1000 height=200>
_EOF_
# 使在直接输出到由 Apache 或其他 HTTP 守护进程服务的目录时,
# 容易查看结果。由于图表不太敏感,这似乎是合理的权衡。
# Make it easy to remotely view results when outputting directly to a directory
# served by Apache or other HTTP daemon. Since the plots aren't horribly
# sensitive, this seems like a reasonable trade-off.
chmod 755 "$2" # 设置输出目录权限
chmod 644 "$2/high_freq.png" "$2/low_freq.png" "$2/exec_speed.png" "$2/index.html" # 设置文件权限
chmod 755 "$2"
chmod 644 "$2/high_freq.png" "$2/low_freq.png" "$2/exec_speed.png" "$2/index.html"
echo "[+] 完成 - 享受您的图表!" # 输出完成提示
echo "[+] All done - enjoy your charts!"
exit 0 # 正常退出程序
exit 0

@ -111,132 +111,132 @@ static const u8 count_class_binary[256] = {
};
/* 对计数进行分类,根据 edges_only 标志决定是否忽略命中计数 */
static void classify_counts(u8* mem, const u8* map) {
u32 i = MAP_SIZE;
if (edges_only) {
// 如果 edges_only 为真只保留是否命中的信息1 或 0
while (i--) {
if (*mem) *mem = 1; // 如果 mem 中的值不为 0则将其设置为 1
mem++; // 移动到下一个字节
if (*mem) *mem = 1;
mem++;
}
} else {
// 如果 edges_only 为假,使用 map 中的值来更新 mem 中的值
while (i--) {
*mem = map[*mem]; // 使用 map 中的值替换 mem 中的值
mem++; // 移动到下一个字节
*mem = map[*mem];
mem++;
}
}
}
/* 清理共享内存atexit 处理程序) */
/* Get rid of shared memory (atexit handler). */
static void remove_shm(void) {
shmctl(shm_id, IPC_RMID, NULL); // 删除共享内存段
shmctl(shm_id, IPC_RMID, NULL);
}
/* 配置共享内存 */
/* Configure shared memory. */
static void setup_shm(void) {
u8* shm_str;
// 创建一个新的共享内存段
shm_id = shmget(IPC_PRIVATE, MAP_SIZE, IPC_CREAT | IPC_EXCL | 0600);
if (shm_id < 0) PFATAL("shmget() failed"); // 如果创建失败,输出错误信息并退出
if (shm_id < 0) PFATAL("shmget() failed");
atexit(remove_shm); // 注册 atexit 处理程序,确保程序退出时删除共享内存
atexit(remove_shm);
shm_str = alloc_printf("%d", shm_id); // 将共享内存 ID 转换为字符串
shm_str = alloc_printf("%d", shm_id);
setenv(SHM_ENV_VAR, shm_str, 1); // 将共享内存 ID 设置到环境变量中
setenv(SHM_ENV_VAR, shm_str, 1);
ck_free(shm_str); // 释放分配的内存
ck_free(shm_str);
trace_bits = shmat(shm_id, NULL, 0); // 将共享内存附加到当前进程的地址空间
trace_bits = shmat(shm_id, NULL, 0);
if (trace_bits == (void *)-1) PFATAL("shmat() failed"); // 如果附加失败,输出错误信息并退出
if (trace_bits == (void *)-1) PFATAL("shmat() failed");
}
/* 写入结果 */
/* Write results. */
static u32 write_results(void) {
s32 fd;
u32 i, ret = 0;
// 获取环境变量 AFL_CMIN_CRASHES_ONLY 和 AFL_CMIN_ALLOW_ANY 的值
u8 cco = !!getenv("AFL_CMIN_CRASHES_ONLY"),
caa = !!getenv("AFL_CMIN_ALLOW_ANY");
// 根据 out_file 的值决定如何打开输出文件
if (!strncmp(out_file, "/dev/", 5)) {
fd = open(out_file, O_WRONLY, 0600); // 打开设备文件
if (fd < 0) PFATAL("Unable to open '%s'", out_file); // 如果打开失败,输出错误信息并退出
fd = open(out_file, O_WRONLY, 0600);
if (fd < 0) PFATAL("Unable to open '%s'", out_file);
} else if (!strcmp(out_file, "-")) {
fd = dup(1); // 如果输出文件是 "-",则复制标准输出文件描述符
if (fd < 0) PFATAL("Unable to open stdout"); // 如果复制失败,输出错误信息并退出
fd = dup(1);
if (fd < 0) PFATAL("Unable to open stdout");
} else {
unlink(out_file); /* Ignore errors */ // 删除已存在的文件(忽略错误)
fd = open(out_file, O_WRONLY | O_CREAT | O_EXCL, 0600); // 创建新文件
if (fd < 0) PFATAL("Unable to create '%s'", out_file); // 如果创建失败,输出错误信息并退出
unlink(out_file); /* Ignore errors */
fd = open(out_file, O_WRONLY | O_CREAT | O_EXCL, 0600);
if (fd < 0) PFATAL("Unable to create '%s'", out_file);
}
if (binary_mode) {
// 如果处于二进制模式,直接写入 trace_bits 的内容
for (i = 0; i < MAP_SIZE; i++)
if (trace_bits[i]) ret++; // 统计非零的字节数
if (trace_bits[i]) ret++;
ck_write(fd, trace_bits, MAP_SIZE, out_file); // 将 trace_bits 写入文件
close(fd); // 关闭文件描述符
ck_write(fd, trace_bits, MAP_SIZE, out_file);
close(fd);
} else {
FILE* f = fdopen(fd, "w"); // 将文件描述符转换为 FILE 指针
FILE* f = fdopen(fd, "w");
if (!f) PFATAL("fdopen() failed"); // 如果转换失败,输出错误信息并退出
if (!f) PFATAL("fdopen() failed");
for (i = 0; i < MAP_SIZE; i++) {
if (!trace_bits[i]) continue; // 如果 trace_bits[i] 为 0跳过
ret++; // 统计非零的字节数
if (!trace_bits[i]) continue;
ret++;
if (cmin_mode) {
// 如果处于 cmin_mode根据条件决定是否写入
if (child_timed_out) break; // 如果子进程超时,停止写入
if (!caa && child_crashed != cco) break; // 如果不允许任意崩溃且崩溃状态不匹配,停止写入
if (child_timed_out) break;
if (!caa && child_crashed != cco) break;
fprintf(f, "%u%u\n", trace_bits[i], i); // 写入 trace_bits[i] 和索引 i
fprintf(f, "%u%u\n", trace_bits[i], i);
} else fprintf(f, "%06u:%u\n", i, trace_bits[i]); // 否则,写入索引 i 和 trace_bits[i]
} else fprintf(f, "%06u:%u\n", i, trace_bits[i]);
}
fclose(f); // 关闭文件
fclose(f);
}
return ret; // 返回写入的非零字节数
return ret;
}
/* 处理超时信号 */
/* Handle timeout signal. */
static void handle_timeout(int sig) {
child_timed_out = 1;
@ -244,118 +244,124 @@ static void handle_timeout(int sig) {
}
/* 执行目标程序 */
/* Execute target application. */
static void run_target(char** argv) {
static struct itimerval it; // 定义一个定时器结构体
int status = 0; // 用于存储子进程的状态
static struct itimerval it;
int status = 0;
if (!quiet_mode)
SAYF("-- Program output begins --\n" cRST); // 如果不是静默模式,输出程序开始信息
SAYF("-- Program output begins --\n" cRST);
MEM_BARRIER(); // 内存屏障,确保内存操作顺序
MEM_BARRIER();
child_pid = fork(); // 创建子进程
child_pid = fork();
if (child_pid < 0) PFATAL("fork() failed"); // 如果fork失败输出错误信息并退出
if (child_pid < 0) PFATAL("fork() failed");
if (!child_pid) { // 如果是子进程
if (!child_pid) {
struct rlimit r; // 定义一个资源限制结构体
struct rlimit r;
if (quiet_mode) { // 如果是静默模式
if (quiet_mode) {
s32 fd = open("/dev/null", O_RDWR); // 打开/dev/null以忽略输出
s32 fd = open("/dev/null", O_RDWR);
if (fd < 0 || dup2(fd, 1) < 0 || dup2(fd, 2) < 0) { // 将标准输出和标准错误重定向到/dev/null
*(u32*)trace_bits = EXEC_FAIL_SIG; // 如果失败设置trace_bits为EXEC_FAIL_SIG
PFATAL("Descriptor initialization failed"); // 输出错误信息并退出
if (fd < 0 || dup2(fd, 1) < 0 || dup2(fd, 2) < 0) {
*(u32*)trace_bits = EXEC_FAIL_SIG;
PFATAL("Descriptor initialization failed");
}
close(fd); // 关闭/dev/null文件描述符
close(fd);
}
if (mem_limit) { // 如果设置了内存限制
if (mem_limit) {
r.rlim_max = r.rlim_cur = ((rlim_t)mem_limit) << 20; // 设置内存限制为指定的MB数
r.rlim_max = r.rlim_cur = ((rlim_t)mem_limit) << 20;
#ifdef RLIMIT_AS
setrlimit(RLIMIT_AS, &r); /* Ignore errors */ // 设置地址空间大小限制,忽略错误
setrlimit(RLIMIT_AS, &r); /* Ignore errors */
#else
setrlimit(RLIMIT_DATA, &r); /* Ignore errors */ // 设置数据段大小限制,忽略错误
setrlimit(RLIMIT_DATA, &r); /* Ignore errors */
#endif /* ^RLIMIT_AS */
}
if (!keep_cores) r.rlim_max = r.rlim_cur = 0; // 如果不需要核心转储文件设置核心转储大小为0
else r.rlim_max = r.rlim_cur = RLIM_INFINITY; // 否则设置为无限大小
if (!keep_cores) r.rlim_max = r.rlim_cur = 0;
else r.rlim_max = r.rlim_cur = RLIM_INFINITY;
setrlimit(RLIMIT_CORE, &r); /* Ignore errors */ // 设置核心转储大小限制,忽略错误
setrlimit(RLIMIT_CORE, &r); /* Ignore errors */
if (!getenv("LD_BIND_LAZY")) setenv("LD_BIND_NOW", "1", 0); // 设置环境变量以立即绑定动态链接库
if (!getenv("LD_BIND_LAZY")) setenv("LD_BIND_NOW", "1", 0);
setsid(); // 创建一个新的会话,使子进程成为会话首进程,并与控制台分离
setsid();
execv(target_path, argv); // 执行目标程序
execv(target_path, argv);
*(u32*)trace_bits = EXEC_FAIL_SIG; // 如果execv失败设置trace_bits为EXEC_FAIL_SIG
exit(0); // 退出子进程
*(u32*)trace_bits = EXEC_FAIL_SIG;
exit(0);
}
/* 配置超时,等待子进程,取消超时 */
if (exec_tmout) { // 如果设置了执行超时时间
/* Configure timeout, wait for child, cancel timeout. */
if (exec_tmout) {
child_timed_out = 0; // 初始化子进程超时标志为未超时
it.it_value.tv_sec = (exec_tmout / 1000); // 设置定时器的秒数部分
it.it_value.tv_usec = (exec_tmout % 1000) * 1000; // 设置定时器的微秒数部分
child_timed_out = 0;
it.it_value.tv_sec = (exec_tmout / 1000);
it.it_value.tv_usec = (exec_tmout % 1000) * 1000;
}
setitimer(ITIMER_REAL, &it, NULL); // 设置定时器
setitimer(ITIMER_REAL, &it, NULL);
if (waitpid(child_pid, &status, 0) <= 0) FATAL("waitpid() failed"); // 等待子进程结束,如果失败则输出错误信息并退出
if (waitpid(child_pid, &status, 0) <= 0) FATAL("waitpid() failed");
child_pid = 0; // 重置子进程ID
it.it_value.tv_sec = 0; // 重置定时器的秒数部分
it.it_value.tv_usec = 0; // 重置定时器的微秒数部分
setitimer(ITIMER_REAL, &it, NULL); // 取消定时器
child_pid = 0;
it.it_value.tv_sec = 0;
it.it_value.tv_usec = 0;
setitimer(ITIMER_REAL, &it, NULL);
MEM_BARRIER(); // 内存屏障,确保内存操作顺序
MEM_BARRIER();
/* 清理位图,分析退出条件等 */
if (*(u32*)trace_bits == EXEC_FAIL_SIG) // 如果trace_bits等于EXEC_FAIL_SIG表示程序执行失败
FATAL("Unable to execute '%s'", argv[0]); // 输出错误信息并退出
/* Clean up bitmap, analyze exit condition, etc. */
classify_counts(trace_bits, binary_mode ? // 根据二进制模式选择分类方法
count_class_binary : count_class_human); // 分类trace_bits中的覆盖率信息
if (*(u32*)trace_bits == EXEC_FAIL_SIG)
FATAL("Unable to execute '%s'", argv[0]);
if (!quiet_mode) // 如果不是静默模式
SAYF(cRST "-- Program output ends --\n"); // 输出程序结束信息
classify_counts(trace_bits, binary_mode ?
count_class_binary : count_class_human);
if (!child_timed_out && !stop_soon && WIFSIGNALED(status)) // 如果子进程没有超时、没有被用户中断且以信号方式结束
child_crashed = 1; // 设置子进程崩溃标志为真
if (!quiet_mode)
SAYF(cRST "-- Program output ends --\n");
if (!child_timed_out && !stop_soon && WIFSIGNALED(status))
child_crashed = 1;
if (!quiet_mode) { // 如果不是静默模式
if (!quiet_mode) {
if (child_timed_out)
SAYF(cLRD "\n+++ Program timed off +++\n" cRST); // 如果子进程超时,输出超时信息
SAYF(cLRD "\n+++ Program timed off +++\n" cRST);
else if (stop_soon)
SAYF(cLRD "\n+++ Program aborted by user +++\n" cRST); // 如果用户中断,输出中断信息
SAYF(cLRD "\n+++ Program aborted by user +++\n" cRST);
else if (child_crashed)
SAYF(cLRD "\n+++ Program killed by signal %u +++\n" cRST, WTERMSIG(status)); // 如果子进程崩溃,输出崩溃信号信息
SAYF(cLRD "\n+++ Program killed by signal %u +++\n" cRST, WTERMSIG(status));
}
}
/* 处理 Ctrl-C 等信号 */
/* Handle Ctrl-C and the like. */
static void handle_stop_sig(int sig) {
stop_soon = 1;
@ -364,7 +370,9 @@ static void handle_stop_sig(int sig) {
}
/* 进行基本准备 - 持久化文件描述符、文件名等 */
/* Do basic preparations - persistent fds, filenames, etc. */
static void set_up_environment(void) {
setenv("ASAN_OPTIONS", "abort_on_error=1:"
@ -385,7 +393,9 @@ static void set_up_environment(void) {
}
/* 设置信号处理程序 */
/* Setup signal handlers, duh. */
static void setup_signal_handlers(void) {
struct sigaction sa;
@ -396,212 +406,382 @@ static void setup_signal_handlers(void) {
sigemptyset(&sa.sa_mask);
/* 各种停止信号的处理 */
/* Various ways of saying "stop". */
sa.sa_handler = handle_stop_sig;
sigaction(SIGHUP, &sa, NULL);
sigaction(SIGINT, &sa, NULL);
sigaction(SIGTERM, &sa, NULL);
/* 执行超时通知的处理 */
/* Exec timeout notifications. */
sa.sa_handler = handle_timeout;
sigaction(SIGALRM, &sa, NULL);
}
/* 检测参数中的 @@ */
/* Detect @@ in args. */
static void detect_file_args(char** argv) {
u32 i = 0; // 初始化索引变量 i 用于遍历 argv 数组
u8* cwd = getcwd(NULL, 0); // 获取当前工作目录的路径
u32 i = 0;
u8* cwd = getcwd(NULL, 0);
if (!cwd) PFATAL("getcwd() failed"); // 如果获取当前工作目录失败,则输出错误信息并终止程序
if (!cwd) PFATAL("getcwd() failed");
while (argv[i]) { // 遍历命令行参数数组,直到遇到 NULL 结束符
while (argv[i]) {
u8* aa_loc = strstr(argv[i], "@@"); // 查找当前参数中是否包含 "@@" 字符串
u8* aa_loc = strstr(argv[i], "@@");
if (aa_loc) { // 如果找到了 "@@" 字符串
if (aa_loc) {
u8 *aa_subst, *n_arg;
if (!at_file) FATAL("@@ syntax is not supported by this tool."); // 如果 at_file 为空,则 "@@" 语法不被支持,输出错误信息并终止程序
if (!at_file) FATAL("@@ syntax is not supported by this tool.");
/* Be sure that we're always using fully-qualified paths. */
/* 确保始终使用完全限定的路径 */
if (at_file[0] == '/') aa_subst = at_file; // 如果 at_file 是绝对路径,则直接使用
else aa_subst = alloc_printf("%s/%s", cwd, at_file); // 如果 at_file 是相对路径,则将其转换为绝对路径
if (at_file[0] == '/') aa_subst = at_file;
else aa_subst = alloc_printf("%s/%s", cwd, at_file);
/* 构造替换的 argv 值 */
*aa_loc = 0; // 在 "@@" 出现的位置临时截断字符串
n_arg = alloc_printf("%s%s%s", argv[i], aa_subst, aa_loc + 2); // 将 aa_subst 替换到 "@@" 的位置,形成新的命令行参数
argv[i] = n_arg; // 更新 argv 数组中的当前参数为替换后的值
*aa_loc = '@'; // 恢复原字符串的 "@@" 部分,以便在后续处理中保持一致
/* Construct a replacement argv value. */
if (at_file[0] != '/') ck_free(aa_subst); // 如果 at_file 是相对路径,则释放通过 alloc_printf 分配的内存空间
*aa_loc = 0;
n_arg = alloc_printf("%s%s%s", argv[i], aa_subst, aa_loc + 2);
argv[i] = n_arg;
*aa_loc = '@';
// 如果 at_file 不是绝对路径,则释放 aa_subst 内存
if (at_file[0] != '/') ck_free(aa_subst);
}
i++; // 移动到下一个命令行参数
i++; // 进入下一个参数
}
free(cwd); // 释放通过 getcwd 分配的内存空间
free(cwd); // 释放当前工作目录内存,但不进行追踪
}
/* 显示工具的横幅信息 */
// 显示工具的横幅信息
static void show_banner(void) {
SAYF(cCYA "afl-showmap " cBRI VERSION cRST " by <lcamtuf@google.com>\n"); // 输出工具的名称、版本号及作者信息,使用彩色控制码美化输出
// 输出工具名称以及作者信息
SAYF(cCYA "afl-showmap " cBRI VERSION cRST " by <lcamtuf@google.com>\n");
}
/* 显示用法提示信息 */
// 显示用法提示信息
static void usage(u8* argv0) {
show_banner(); // 首先显示横幅信息
show_banner(); // 显示横幅信息
// 输出工具的用法信息和参数说明
SAYF("\n%s [ options ] -- /path/to/target_app [ ... ]\n\n"
SAYF("\n%s [ options ] -- /path/to/target_app [ ... ]\n\n" // 输出基本用法示例
"Required parameters:\n\n"
"必需的参数:\n\n"
" -o file - file to write the trace data to\n\n" // 输出文件参数
" -o file - 将跟踪数据写入的文件\n\n" // 解释 -o 选项的作用,即指定输出跟踪数据的文件
// 输出工具的用法信息和参数说明
"Execution control settings:\n\n"
"执行控制设置:\n\n"
" -t msec - timeout for each run (none)\n" // 设置每次执行的超时时间,单位为毫秒
" -m megs - memory limit for child process (%u MB)\n" // 设置子进程的内存限制单位为MB
" -t msec - 每次运行的超时时间 (无限制)\n" // 解释 -t 选项的作用,即设置每个测试用例的超时时间
" -m megs - 子进程的内存限制 (%u MB)\n" // 解释 -m 选项的作用,即设置子进程的内存限制,默认值为 MEM_LIMIT
// 其他参数和设置选项的说明
"Other settings:\n\n"
"其他设置:\n\n"
" -q - sink program's output and don't show messages\n" // 隐藏程序输出,不显示信息
" -e - show edge coverage only, ignore hit counts\n" // 仅显示边缘覆盖,忽略命中计数
" -c - allow core dumps\n" // 允许生成核心转储
" -V - show version number and exit\n\n" // 显示版本号并退出
" -q - 静默程序的输出,不显示消息\n" // 解释 -q 选项的作用,即抑制程序的输出
" -e - 仅显示边缘覆盖率,忽略命中次数\n" // 解释 -e 选项的作用,即只显示边缘覆盖率而不关心具体命中次数
" -c - 允许生成核心转储文件\n" // 解释 -c 选项的作用,即允许程序在崩溃时生成核心转储文件
" -V - 显示版本号并退出\n\n" // 解释 -V 选项的作用,即显示工具的版本号后退出程序
// 说明该工具的功能,并指向额外的帮助信息
"This tool displays raw tuple data captured by AFL instrumentation.\n"
"For additional help, consult %s/README.\n\n" cRST,
"此工具显示由 AFL 仪器化捕获的原始元组数据。\n" // 说明此工具的功能
"更多信息,请参考 %s/README。\n\n" cRST, // 提供更多帮助信息的文件路径,并使用彩色控制码重置输出格式
argv0, MEM_LIMIT, doc_path); // 输出用法信息,包括程序名、内存限制和文档路径
argv0, MEM_LIMIT, doc_path);
exit(1); // 退出程序,返回错误状态
exit(1); // 输出完帮助信息后,退出程序,返回状态码 1
}
// 查找可执行二进制文件的函数
static void find_binary(u8* fname) {
// 定义环境变量路径和文件状态结构体
u8* env_path = 0;
struct stat st;
u8* env_path = 0; // 环境变量路径
struct stat st; // 文件状态信息
// 如果文件名包含 '/', 或者环境变量 "PATH" 不存在
// 如果文件名包含 '/' 或者环境变量 PATH 不存在,直接使用提供的路径
if (strchr(fname, '/') || !(env_path = getenv("PATH"))) {
// 直接将文件名复制为目标路径
target_path = ck_strdup(fname);
target_path = ck_strdup(fname); // 复制目标路径
// 检查文件是否存在、是否是普通文件、是否可执行、且大小是否至少为4字节
// 检查文件的状态,确认其存在且可执行
if (stat(target_path, &st) || !S_ISREG(st.st_mode) ||
!(st.st_mode & 0111) || st.st_size < 4)
// 如果上述任一条件不满足,输出错误信息并终止程序
FATAL("Program '%s' not found or not executable", fname);
FATAL("Program '%s' not found or not executable", fname); // 如果不可执行,则报错
} else {
// 环境变量 "PATH" 存在,逐个检查其中的路径
// 逐个遍历 PATH 中的目录,并查找目标程序
while (env_path) {
u8 *cur_elem, *delim = strchr(env_path, ':');
u8 *cur_elem, *delim = strchr(env_path, ':'); // 分割当前路径元素
// 检查当前路径元素是否以 ':' 结尾
// 如果找到分隔符,则处理当前路径元素
if (delim) {
// 分配内存并复制当前路径元素
cur_elem = ck_alloc(delim - env_path + 1);
memcpy(cur_elem, env_path, delim - env_path);
delim++;
cur_elem = ck_alloc(delim - env_path + 1); // 分配内存
memcpy(cur_elem, env_path, delim - env_path); // 复制当前路径
delim++; // 指向下一个路径元素
} else
// 如果没有 ':',则直接复制整个环境变量路径
cur_elem = ck_strdup(env_path);
cur_elem = ck_strdup(env_path); // 没有分隔符,直接复制
// 更新环境变量路径指针到下一个元素
env_path = delim;
env_path = delim; // 更新环境路径
// 如果当前路径元素不为空,将文件名添加到该路径后面形成完整路径
// 构造目标路径
if (cur_elem[0])
target_path = alloc_printf("%s/%s", cur_elem, fname);
target_path = alloc_printf("%s/%s", cur_elem, fname); // 拼接路径
else
// 如果当前路径元素为空,则直接使用文件名作为目标路径
target_path = ck_strdup(fname);
target_path = ck_strdup(fname); // 若当前路径为空则直接使用文件名
// 释放当前路径元素的空间
ck_free(cur_elem);
ck_free(cur_elem); // 释放当前路径元素的内存
// 检查形成的路径是否指向一个存在且可执行的文件
// 检查拼接后的目标路径是否有效
if (!stat(target_path, &st) && S_ISREG(st.st_mode) &&
(st.st_mode & 0111) && st.st_size >= 4) break;
(st.st_mode & 0111) && st.st_size >= 4) break; // 找到可执行文件,退出查找
// 如果当前路径无效,释放目标路径的空间并重置目标路径指针
ck_free(target_path);
target_path = 0;
ck_free(target_path); // 释放无效目标路径内存
target_path = 0; // 重新初始化目标路径
}
// 如果遍历完所有路径后仍未找到目标程序,输出错误信息并终止程序
// 如果没有找到目标程序,则报错
if (!target_path) FATAL("Program '%s' not found or not executable", fname);
}
}
/* 修复针对 QEMU 的 argv 参数 */
// 修复针对 QEMU 的 argv 参数
static char** get_qemu_argv(u8* own_loc, char** argv, int argc) {
// 分配足够的空间存储新的 argv 数组,包括额外的四个参数
char** new_argv = ck_alloc(sizeof(char*) * (argc + 4));
char** new_argv = ck_alloc(sizeof(char*) * (argc + 4)); // 分配新的参数数组内存
u8 *tmp, *cp, *rsl, *own_copy;
// 设置 QEMU 日志环境变量为 "nochain"
// 为了处理 QEMU 的稳定性问题,禁止链式调用
setenv("QEMU_LOG", "nochain", 1);
// 将原始 argv 数组中的参数从第二个开始复制到新的 argv 数组中
// 将原始参数复制到新的参数数组中
memcpy(new_argv + 3, argv + 1, sizeof(char*) * argc);
// 将目标程序路径设置为新的 argv 数组的第三个参数
new_argv[2] = target_path;
// 设置 "--" 作为新的 argv 数组的第二个参数
new_argv[1] = "--";
new_argv[2] = target_path; // 新参数的第三个元素为目标路径
new_argv[1] = "--"; // 添加分隔符 " -- " 以告诉 QEMU 参数的结束
// 检查环境变量 "AFL_PATH" 是否存在
// 查找 afl-qemu-trace 的路径,使用 AFL_PATH 环境变量
tmp = getenv("AFL_PATH");
if (tmp) {
// 如果存在,构建 afl-qemu-trace 工具的完整路径
cp = alloc_printf("%s/afl-qemu-trace", tmp);
cp = alloc_printf("%s/afl-qemu-trace", tmp); // 拼接 AFL_PATH
// 检查 afl-qemu-trace 工具是否存在且可执行
// 如果没有找到,则报错
if (access(cp, X_OK))
// 如果工具不存在或不可执行,输出错误信息并终止程序
FATAL("Unable to find '%s'", tmp);
// 更新目标路径和新的 argv 数组的第一个参数为 afl-qemu-trace 工具的路径
target_path = new_argv[0] = cp;
return new_argv;
target_path = new_argv[0] = cp; // 将找到的路径赋值给目标路径
return new_argv; // 返回新的参数数组
}
// 如果环境变量 "AFL_PATH" 不存在,复制当前程序的位置
// 复制当前程序的路径以查找 afl-qemu-trace
own_copy = ck_strdup(own_loc);
// 查找最后一个 '/' 的位置
rsl = strrchr(own_copy, '/');
rsl = strrchr(own_copy, '/'); // 找到最后一个 '/' 的位置
if (rsl) {
// 将最后一个 '/' 替换为0截断字符串以获得目录路径
*rsl = 0;
*rsl = 0; // 将最后一个 '/' 替换为终止符
// 构建 afl-qemu-trace 工具的完整路径
// 拼接 afl-qemu-trace 路径
cp = alloc_printf("%s/afl-qemu-trace", own_copy);
// 释放复制的程序位置的空间
ck_free(own_copy);
ck_free(own_copy); // 释放复制的路径内存
// 检查 afl-qemu-trace 工具是否存在且可执行
// 检查拼接后的路径是否有效
if (!access(cp, X_OK)) {
// 如果工具存在且可执行,更新目标路径和新的 argv 数组的第一个参数
target_path = new_argv[0] = cp;
return new_argv;
target_path = new_argv[0] = cp; // 更新目标路径
return new_argv; // 返回新的参数数组
}
} else
// 如果没有找到 '/',直接释放复制的程序位置的空间
ck_free(own_copy);
ck_free(own_copy); // 如果没有找到 '/',则释放内存
// 如果上述方法都未能找到 afl-qemu-trace 工具,检查预定义的二进制路径
// 在默认的 BIN_PATH 查找 afl-qemu-trace
if (!access(BIN_PATH "/afl-qemu-trace", X_OK)) {
// 如果工具存在且可执行,更新目标路径和新的 argv 数组的第一个参数
target_path = new_argv[0] = alloc_printf("%s/afl-qemu-trace", BIN_PATH);
return new_argv;
target_path = new_argv[0] = BIN_PATH "/afl-qemu-trace"; // 设置目标路径
return new_argv; // 返回新的参数数组
}
// 如果以上步骤都未能找到 afl-qemu-trace则报错
FATAL("Unable to find 'afl-qemu-trace'.");
}
/*
Main entry point
*/
int main(int argc, char** argv) {
s32 opt; // 选项变量
u8 mem_limit_given = 0, timeout_given = 0, qemu_mode = 0; // 标志变量
u32 tcnt; // 计数变量,用于记录捕获的元组数量
char** use_argv; // 用于存储最终的参数数组
// 检查文档路径是否存在,用于后续帮助信息
doc_path = access(DOC_PATH, F_OK) ? "docs" : DOC_PATH;
// 处理命令行参数
while ((opt = getopt(argc,argv,"+o:m:t:A:eqZQbcV")) > 0)
switch (opt) {
case 'o':
// 处理输出文件参数
if (out_file) FATAL("Multiple -o options not supported"); // 防止重复设置
out_file = optarg; // 保存输出文件路径
break;
case 'm': {
u8 suffix = 'M'; // 默认单位为MB
// 检查是否已经设置了内存限制
if (mem_limit_given) FATAL("Multiple -m options not supported");
mem_limit_given = 1;
// 如果输入为 "none",则不设置内存限制
if (!strcmp(optarg, "none")) {
mem_limit = 0; // 设置内存限制为0
break;
}
// 解析输入的内存限制
if (sscanf(optarg, "%llu%c", &mem_limit, &suffix) < 1 ||
optarg[0] == '-') FATAL("Bad syntax used for -m");
// 根据单位调整内存限制
switch (suffix) {
case 'T': mem_limit *= 1024 * 1024; break; // TB
case 'G': mem_limit *= 1024; break; // GB
case 'k': mem_limit /= 1024; break; // kB
case 'M': break; // MB
default: FATAL("Unsupported suffix or bad syntax for -m"); // 报错
}
// 设置的内存值不能低于阈值5
if (mem_limit < 5) FATAL("Dangerously low value of -m");
// 在32位系统中内存限制不能超过2000MB
if (sizeof(rlim_t) == 4 && mem_limit > 2000)
FATAL("Value of -m out of range on 32-bit systems");
}
break;
case 't':
// 处理超时时间参数
if (timeout_given) FATAL("Multiple -t options not supported");
timeout_given = 1;
// 如果不为 "none",则将输入解析为超时时间
if (strcmp(optarg, "none")) {
exec_tmout = atoi(optarg); // 将时间转换为整数
// 超时时间设置不能低于20毫秒
if (exec_tmout < 20 || optarg[0] == '-')
FATAL("Dangerously low value of -t");
}
break;
case 'e':
// 处理边缘覆盖参数
if (edges_only) FATAL("Multiple -e options not supported"); // 防止重复设置
edges_only = 1; // 设置为只显示边缘覆盖
break;
case 'q':
// 处理安静模式参数
if (quiet_mode) FATAL("Multiple -q options not supported"); // 防止重复设置
quiet_mode = 1; // 启用安静模式
break;
case 'Z':
// 处理 afl-cmin 特定功能参数
cmin_mode = 1;
quiet_mode = 1; // 同时启用安静模式
break;
case 'A':
// 处理替代文件参数
at_file = optarg; // 保存替代文件名
break;
case 'Q':
// 处理 QEMU 特定功能参数
if (qemu_mode) FATAL("Multiple -Q options not supported"); // 防止重复设置
if (!mem_limit_given) mem_limit = MEM_LIMIT_QEMU; // 若未设置内存限制,则使用 QEMU 的默认值
qemu_mode = 1; // 启用 QEMU 模式
break;
case 'b':
// 处理原始二进制模式参数
binary_mode = 1; // 设置为原始二进制模式
break;
case 'c':
// 处理核心转储允许参数
if (keep_cores) FATAL("Multiple -c options not supported"); // 防止重复设置
keep_cores = 1; // 允许核心转储
break;
case 'V':
// 处理版本显示参数
show_banner(); // 显示工具的横幅信息
exit(0); // 退出程序
default:
// 如果遇到未知参数则显示用法提示
usage(argv[0]);
}
// 检查命令行参数的完整性,如果缺少必要参数则显示用法信息
if (optind == argc || !out_file) usage(argv[0]);
// 设置共享内存和信号处理
setup_shm();
setup_signal_handlers();
// 设置环境变量
set_up_environment();
// 查找用户提供的二进制文件路径
find_binary(argv[optind]);
// 如果不是安静模式,则显示正在执行的程序信息
if (!quiet_mode) {
show_banner(); // 显示横幅信息
ACTF("Executing '%s'...\n", target_path); // 显示正在执行的程序路径
}
// 检测需要替换的文件参数
detect_file_args(argv + optind);
// 根据是否使用 QEMU 来准备参数数组
if (qemu_mode)
use_argv = get_qemu_argv(argv[0], argv + optind, argc - optind);
else
use_argv = argv + optind; // 如果不是 QEMU使用原始参数数组
// 执行目标程序
run_target(use_argv);
// 写入执行结果
tcnt = write_results();
// 如果不是安静模式,则输出结果提示信息
if (!quiet_mode) {
if (!tcnt) FATAL("No instrumentation detected" cRST); // 如果没有捕获到元组数据,则报错
OKF("Captured %u tuples in '%s'." cRST, tcnt, out_file); // 输出捕获到的元组数量
}
// 退出程序,返回状态值,状态值根据是否崩溃和超时进行设置
exit(child_crashed * 2 + child_timed_out);
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

@ -1,24 +1,24 @@
/*
2013 Google LLC
Copyright 2013 Google LLC All rights reserved.
Apache 2.0 "许可证"
使
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at:
http://www.apache.org/licenses/LICENSE-2.0
"按现状"
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
/*
American Fuzzy Lop - /
american fuzzy lop - debug / error handling macros
--------------------------------------------------
Michal Zalewski <lcamtuf@google.com>
Written and maintained by Michal Zalewski <lcamtuf@google.com>
*/
#ifndef _HAVE_DEBUG_H
@ -30,49 +30,49 @@
#include "config.h"
/*******************
* *
* Terminal colors *
*******************/
#ifdef USE_COLOR
# define cBLK "\x1b[0;30m" // 黑色文本
# define cRED "\x1b[0;31m" // 红色文本
# define cGRN "\x1b[0;32m" // 绿色文本
# define cBRN "\x1b[0;33m" // 棕色文本
# define cBLU "\x1b[0;34m" // 蓝色文本
# define cMGN "\x1b[0;35m" // 紫色文本
# define cCYA "\x1b[0;36m" // 青色文本
# define cLGR "\x1b[0;37m" // 浅灰色文本
# define cGRA "\x1b[1;90m" // 深灰色文本
# define cLRD "\x1b[1;91m" // 浅红色文本
# define cLGN "\x1b[1;92m" // 浅绿色文本
# define cYEL "\x1b[1;93m" // 浅黄色文本
# define cLBL "\x1b[1;94m" // 浅蓝色文本
# define cPIN "\x1b[1;95m" // 浅紫色文本
# define cLCY "\x1b[1;96m" // 浅青色文本
# define cBRI "\x1b[1;97m" // 白色文本
# define cRST "\x1b[0m" // 重置颜色
# define bgBLK "\x1b[40m" // 黑色背景
# define bgRED "\x1b[41m" // 红色背景
# define bgGRN "\x1b[42m" // 绿色背景
# define bgBRN "\x1b[43m" // 棕色背景
# define bgBLU "\x1b[44m" // 蓝色背景
# define bgMGN "\x1b[45m" // 紫色背景
# define bgCYA "\x1b[46m" // 青色背景
# define bgLGR "\x1b[47m" // 浅灰色背景
# define bgGRA "\x1b[100m" // 深灰色背景
# define bgLRD "\x1b[101m" // 浅红色背景
# define bgLGN "\x1b[102m" // 浅绿色背景
# define bgYEL "\x1b[103m" // 浅黄色背景
# define bgLBL "\x1b[104m" // 浅蓝色背景
# define bgPIN "\x1b[105m" // 浅紫色背景
# define bgLCY "\x1b[106m" // 浅青色背景
# define bgBRI "\x1b[107m" // 白色背景
# define cBLK "\x1b[0;30m"
# define cRED "\x1b[0;31m"
# define cGRN "\x1b[0;32m"
# define cBRN "\x1b[0;33m"
# define cBLU "\x1b[0;34m"
# define cMGN "\x1b[0;35m"
# define cCYA "\x1b[0;36m"
# define cLGR "\x1b[0;37m"
# define cGRA "\x1b[1;90m"
# define cLRD "\x1b[1;91m"
# define cLGN "\x1b[1;92m"
# define cYEL "\x1b[1;93m"
# define cLBL "\x1b[1;94m"
# define cPIN "\x1b[1;95m"
# define cLCY "\x1b[1;96m"
# define cBRI "\x1b[1;97m"
# define cRST "\x1b[0m"
# define bgBLK "\x1b[40m"
# define bgRED "\x1b[41m"
# define bgGRN "\x1b[42m"
# define bgBRN "\x1b[43m"
# define bgBLU "\x1b[44m"
# define bgMGN "\x1b[45m"
# define bgCYA "\x1b[46m"
# define bgLGR "\x1b[47m"
# define bgGRA "\x1b[100m"
# define bgLRD "\x1b[101m"
# define bgLGN "\x1b[102m"
# define bgYEL "\x1b[103m"
# define bgLBL "\x1b[104m"
# define bgPIN "\x1b[105m"
# define bgLCY "\x1b[106m"
# define bgBRI "\x1b[107m"
#else
# define cBLK "" // 不使用颜色
# define cBLK ""
# define cRED ""
# define cGRN ""
# define cBRN ""
@ -90,7 +90,7 @@
# define cBRI ""
# define cRST ""
# define bgBLK "" // 不使用背景颜色
# define bgBLK ""
# define bgRED ""
# define bgGRN ""
# define bgBRN ""
@ -115,25 +115,25 @@
#ifdef FANCY_BOXES
# define SET_G1 "\x1b)0" /* 设置 G1 用于绘制框 */
# define RESET_G1 "\x1b)B" /* 重置 G1 为 ASCII 字符 */
# define bSTART "\x0e" /* 进入 G1 绘制模式 */
# define bSTOP "\x0f" /* 离开 G1 绘制模式 */
# define bH "q" /* 水平线 */
# define bV "x" /* 垂直线 */
# define bLT "l" /* 左上角 */
# define bRT "k" /* 右上角 */
# define bLB "m" /* 左下角 */
# define bRB "j" /* 右下角 */
# define bX "n" /* 交叉点 */
# define bVR "t" /* 垂直,右分支 */
# define bVL "u" /* 垂直,左分支 */
# define bHT "v" /* 水平,顶部分支 */
# define bHB "w" /* 水平,底部分支 */
# define SET_G1 "\x1b)0" /* Set G1 for box drawing */
# define RESET_G1 "\x1b)B" /* Reset G1 to ASCII */
# define bSTART "\x0e" /* Enter G1 drawing mode */
# define bSTOP "\x0f" /* Leave G1 drawing mode */
# define bH "q" /* Horizontal line */
# define bV "x" /* Vertical line */
# define bLT "l" /* Left top corner */
# define bRT "k" /* Right top corner */
# define bLB "m" /* Left bottom corner */
# define bRB "j" /* Right bottom corner */
# define bX "n" /* Cross */
# define bVR "t" /* Vertical, branch right */
# define bVL "u" /* Vertical, branch left */
# define bHT "v" /* Horizontal, branch top */
# define bHB "w" /* Horizontal, branch bottom */
#else
# define SET_G1 "" // 不使用 G1 绘制框
# define SET_G1 ""
# define RESET_G1 ""
# define bSTART ""
# define bSTOP ""
@ -152,105 +152,107 @@
#endif /* ^FANCY_BOXES */
/***********************
* *
* Misc terminal codes *
***********************/
#define TERM_HOME "\x1b[H" // 移动光标到屏幕左上角
#define TERM_CLEAR TERM_HOME "\x1b[2J" // 清除屏幕
#define cEOL "\x1b[0K" // 清除光标到行尾
#define CURSOR_HIDE "\x1b[?25l" // 隐藏光标
#define CURSOR_SHOW "\x1b[?25h" // 显示光标
#define TERM_HOME "\x1b[H"
#define TERM_CLEAR TERM_HOME "\x1b[2J"
#define cEOL "\x1b[0K"
#define CURSOR_HIDE "\x1b[?25l"
#define CURSOR_SHOW "\x1b[?25h"
/************************
* *
* Debug & error macros *
************************/
/* 只是将内容打印到适当的输出流。 */
/* Just print stuff to the appropriate stream. */
#ifdef MESSAGES_TO_STDOUT
# define SAYF(x...) printf(x) // 输出到标准输出
# define SAYF(x...) printf(x)
#else
# define SAYF(x...) fprintf(stderr, x) // 输出到标准错误
# define SAYF(x...) fprintf(stderr, x)
#endif /* ^MESSAGES_TO_STDOUT */
/* 显示带前缀的警告信息。 */
/* Show a prefixed warning. */
#define WARNF(x...) do { \
SAYF(cYEL "[!] " cBRI "警告: " cRST x); \
SAYF(cYEL "[!] " cBRI "WARNING: " cRST x); \
SAYF(cRST "\n"); \
} while (0)
/* 显示带前缀的"正在做某事"消息。 */
/* Show a prefixed "doing something" message. */
#define ACTF(x...) do { \
SAYF(cLBL "[*] " cRST x); \
SAYF(cRST "\n"); \
} while (0)
/* 显示带前缀的"成功"消息。 */
/* Show a prefixed "success" message. */
#define OKF(x...) do { \
SAYF(cLGN "[+] " cRST x); \
SAYF(cRST "\n"); \
} while (0)
/* 显示带前缀的致命错误消息(未在 afl 中使用)。 */
/* Show a prefixed fatal error message (not used in afl). */
#define BADF(x...) do { \
SAYF(cLRD "\n[-] " cRST x); \
SAYF(cRST "\n"); \
} while (0)
/* 带有详细非操作系统致命错误消息退出程序。 */
/* Die with a verbose non-OS fatal error message. */
#define FATAL(x...) do { \
SAYF(bSTOP RESET_G1 CURSOR_SHOW cRST cLRD "\n[-] 程序中止 : " \
SAYF(bSTOP RESET_G1 CURSOR_SHOW cRST cLRD "\n[-] PROGRAM ABORT : " \
cBRI x); \
SAYF(cLRD "\n 位置 : " cRST "%s(), %s:%u\n\n", \
SAYF(cLRD "\n Location : " cRST "%s(), %s:%u\n\n", \
__FUNCTION__, __FILE__, __LINE__); \
exit(1); \
} while (0)
/* 通过调用 abort() 以提供核心转储而退出。 */
/* Die by calling abort() to provide a core dump. */
#define ABORT(x...) do { \
SAYF(bSTOP RESET_G1 CURSOR_SHOW cRST cLRD "\n[-] 程序中止 : " \
SAYF(bSTOP RESET_G1 CURSOR_SHOW cRST cLRD "\n[-] PROGRAM ABORT : " \
cBRI x); \
SAYF(cLRD "\n 停止位置 : " cRST "%s(), %s:%u\n\n", \
SAYF(cLRD "\n Stop location : " cRST "%s(), %s:%u\n\n", \
__FUNCTION__, __FILE__, __LINE__); \
abort(); \
} while (0)
/* 在包含 perror() 输出的同时终止程序。 */
/* Die while also including the output of perror(). */
#define PFATAL(x...) do { \
fflush(stdout); \
SAYF(bSTOP RESET_G1 CURSOR_SHOW cRST cLRD "\n[-] 系统错误 : " \
SAYF(bSTOP RESET_G1 CURSOR_SHOW cRST cLRD "\n[-] SYSTEM ERROR : " \
cBRI x); \
SAYF(cLRD "\n 停止位置 : " cRST "%s(), %s:%u\n", \
SAYF(cLRD "\n Stop location : " cRST "%s(), %s:%u\n", \
__FUNCTION__, __FILE__, __LINE__); \
SAYF(cLRD " 操作系统消息 : " cRST "%s\n", strerror(errno)); \
SAYF(cLRD " OS message : " cRST "%s\n", strerror(errno)); \
exit(1); \
} while (0)
/* 根据 res 的值(用于解释 read()、write() 等的不同失败模式)调用 FATAL() 或 PFATAL()。 */
/* Die with FAULT() or PFAULT() depending on the value of res (used to
interpret different failure modes for read(), write(), etc). */
#define RPFATAL(res, x...) do { \
if (res < 0) PFATAL(x); else FATAL(x); \
} while (0)
/* 检查错误的 read() 和 write() 的版本,在适当的情况下调用 RPFATAL()。 */
/* Error-checking versions of read() and write() that call RPFATAL() as
appropriate. */
#define ck_write(fd, buf, len, fn) do { \
u32 _len = (len); \
s32 _res = write(fd, buf, _len); \
if (_res != _len) RPFATAL(_res, "对 %s 的短写入", fn); \
if (_res != _len) RPFATAL(_res, "Short write to %s", fn); \
} while (0)
#define ck_read(fd, buf, len, fn) do { \
u32 _len = (len); \
s32 _res = read(fd, buf, _len); \
if (_res != _len) RPFATAL(_res, "对 %s 的短读取", fn); \
if (_res != _len) RPFATAL(_res, "Short read from %s", fn); \
} while (0)
#endif /* ! _HAVE_DEBUG_H */

@ -33,31 +33,24 @@
# task.
#
# 输出脚本的作者信息
echo "cgroup tool for afl-fuzz by <samir.hakim@nyu.edu> and <dwheeler@ida.org>"
echo
# 清除NEW_USER变量的值
unset NEW_USER
# 设置默认内存限制为50MB
MEM_LIMIT="50"
# 解析命令行参数
while getopts "+u:m:" opt; do
case "$opt" in
# -u 参数用于指定运行fuzzer的用户
"u")
NEW_USER="$OPTARG"
;;
# -m 参数用于设置内存限制单位为MB
"m")
MEM_LIMIT="$OPTARG"
MEM_LIMIT="$[OPTARG]"
;;
# 如果遇到未知参数,退出脚本
"?")
exit 1
;;
@ -66,22 +59,17 @@ while getopts "+u:m:" opt; do
done
# 检查内存限制是否低于安全阈值
if [ "$MEM_LIMIT" -lt "5" ]; then
echo "[-] Error: malformed or dangerously low value of -m." 1>&2
exit 1
fi
# 移除已解析的选项保留fuzz命令
shift $((OPTIND-1))
# 获取目标二进制文件路径
TARGET_BIN="$1"
# 检查是否提供了必要的参数
if [ "$TARGET_BIN" = "" -o "$NEW_USER" = "" ]; then
# 输出使用说明
cat 1>&2 <<_EOF_
Usage: $0 [ options ] -- /path/to/afl-fuzz [ ...afl options... ]
@ -101,81 +89,75 @@ conjunction with '-m none' passed to the afl-fuzz binary itself, say:
_EOF_
# 因为缺少必要的参数,退出脚本
exit 1
fi
# 基本的系统检查
# 检查是否为Linux系统
# Basic sanity checks
if [ ! "`uname -s`" = "Linux" ]; then
echo "[-] Error: this tool does not support non-Linux systems." 1>&2
exit 1
fi
# 检查是否以root用户运行脚本
if [ ! "`id -u`" = "0" ]; then
echo "[-] Error: you need to run this script as root (sorry!)." 1>&2
exit 1
fi
# 检查是否安装了cgroup工具
if ! type cgcreate 2>/dev/null 1>&2; then
echo "[-] Error: you need to install cgroup tools first." 1>&2
# 根据包管理器提供安装命令建议
if type apt-get 2>/dev/null 1>&2; then
echo " (Perhaps 'apt-get install cgroup-bin' will work.)" 1>&2
elif type yum 2>/dev/null 1>&2; then
echo " (Perhaps 'yum install libcgroup-tools' will work.)" 1>&2
fi
# 因为缺少必要的工具,退出脚本
exit 1
fi
# 检查指定的用户是否存在
if ! id -u "$NEW_USER" 2>/dev/null 1>&2; then
echo "[-] Error: user '$NEW_USER' does not seem to exist." 1>&2
exit 1
fi
# 创建一个新的cgroup路径如果必要使用PID键值组来确保并行的afl-fuzz任务相互独立
CID="afl-$NEW_USER-$"
# Create a new cgroup path if necessary... We used PID-keyed groups to keep
# parallel afl-fuzz tasks separate from each other.
CID="afl-$NEW_USER-$$"
CPATH="/sys/fs/cgroup/memory/$CID"
# 如果路径不存在则创建cgroup
if [ ! -d "$CPATH" ]; then
cgcreate -a "$NEW_USER" -g memory:"$CID" || exit 1
fi
# 设置内存限制
# 如果系统支持交换空间限制,则同时设置内存和交换空间限制
# Set the appropriate limit...
if [ -f "$CPATH/memory.memsw.limit_in_bytes" ]; then
echo "${MEM_LIMIT}M" > "$CPATH/memory.limit_in_bytes" 2>/dev/null
echo "${MEM_LIMIT}M" > "$CPATH/memory.memsw.limit_in_bytes" || exit 1
echo "${MEM_LIMIT}M" > "$CPATH/memory.limit_in_bytes" || exit 1
# 如果系统有启用交换空间,则要求先禁用交换空间
elif grep -qE 'partition|file' /proc/swaps; then
echo "[-] Error: your system requires swap to be disabled first (swapoff -a)." 1>&2
exit 1
# 如果系统不支持交换空间限制,则仅设置内存限制
else
echo "${MEM_LIMIT}M" > "$CPATH/memory.limit_in_bytes" || exit 1
fi
# 运行fuzz命令并确保其在设置的cgroup内存限制下执行
# All right. At this point, we can just run the command.
cgexec -g "memory:$CID" su -c "$*" "$NEW_USER"
# 删除cgroup以清理资源
cgdelete -g "memory:$CID"

@ -36,19 +36,19 @@
#include "../types.h"
#ifndef PAGE_SIZE
# define PAGE_SIZE 4096 // 定义页面大小为4096字节
# define PAGE_SIZE 4096
#endif /* !PAGE_SIZE */
#ifndef MAP_ANONYMOUS
# define MAP_ANONYMOUS MAP_ANON // 定义MAP_ANONYMOUS为MAP_ANON用于匿名映射
# define MAP_ANONYMOUS MAP_ANON
#endif /* !MAP_ANONYMOUS */
/* 错误/消息处理: */
/* Error / message handling: */
#define DEBUGF(_x...) do { \
if (alloc_verbose) { \
if (++call_depth == 1) { \
fprintf(stderr, "[AFL] " _x); // 输出调试信息
fprintf(stderr, "[AFL] " _x); \
fprintf(stderr, "\n"); \
} \
call_depth--; \
@ -57,97 +57,101 @@
#define FATAL(_x...) do { \
if (++call_depth == 1) { \
fprintf(stderr, "*** [AFL] " _x); // 输出致命错误信息
fprintf(stderr, "*** [AFL] " _x); \
fprintf(stderr, " ***\n"); \
abort(); // 终止程序
abort(); \
} \
call_depth--; \
} while (0)
/* 宏来计算存储缓冲区所需的页面数量: */
/* Macro to count the number of pages needed to store a buffer: */
#define PG_COUNT(_l) (((_l) + (PAGE_SIZE - 1)) / PAGE_SIZE) // 计算所需页面数,向上取整
#define PG_COUNT(_l) (((_l) + (PAGE_SIZE - 1)) / PAGE_SIZE)
/* Canary & clobber bytes: */
#define ALLOC_CANARY 0xAACCAACC // 定义canary值
#define ALLOC_CLOBBER 0xCC // 定义clobber值
#define ALLOC_CANARY 0xAACCAACC
#define ALLOC_CLOBBER 0xCC
#define PTR_C(_p) (((u32*)(_p))[-1]) // 获取canary值的指针
#define PTR_L(_p) (((u32*)(_p))[-2]) // 获取分配长度值的指针
#define PTR_C(_p) (((u32*)(_p))[-1])
#define PTR_L(_p) (((u32*)(_p))[-2])
/* 可配置项使用AFL_LD_*来设置): */
/* Configurable stuff (use AFL_LD_* to set): */
static u32 max_mem = MAX_ALLOC; /* 允许的最大堆使用量 */
static u8 alloc_verbose, /* 是否显示额外的调试消息 */
hard_fail, /* 当超过max_mem时是否使用abort() */
no_calloc_over; /* 对calloc()溢出是否使用abort() */
static u32 max_mem = MAX_ALLOC; /* Max heap usage to permit */
static u8 alloc_verbose, /* Additional debug messages */
hard_fail, /* abort() when max_mem exceeded? */
no_calloc_over; /* abort() on calloc() overflows? */
static __thread size_t total_mem; /* 当前已分配的内存 */
static __thread size_t total_mem; /* Currently allocated mem */
static __thread u32 call_depth; /* 避免通过fprintf()引起的递归 */
static __thread u32 call_depth; /* To avoid recursion via fprintf() */
/* 这是主要的分配函数。它分配比必要多一个页面的内存,
PROT_NONE
使使mmap()
*/
/* This is the main alloc function. It allocates one page more than necessary,
sets that tailing page to PROT_NONE, and then increments the return address
so that it is right-aligned to that boundary. Since it always uses mmap(),
the returned memory will be zeroed. */
static void* __dislocator_alloc(size_t len) {
void* ret;
if (total_mem + len > max_mem || total_mem + len < total_mem) {
if (hard_fail)
FATAL("total allocs exceed %u MB", max_mem / 1024 / 1024); // 如果超过最大内存且hard_fail为真输出错误并终止程序
FATAL("total allocs exceed %u MB", max_mem / 1024 / 1024);
DEBUGF("total allocs exceed %u MB, returning NULL",
max_mem / 1024 / 1024); // 如果超过最大内存且hard_fail为假输出调试信息并返回NULL
max_mem / 1024 / 1024);
return NULL;
}
/* 我们还会在实际缓冲区下面存储缓冲区长度和canary
8 */
/* We will also store buffer length and a canary below the actual buffer, so
let's add 8 bytes for that. */
ret = mmap(NULL, (1 + PG_COUNT(len + 8)) * PAGE_SIZE, PROT_READ | PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); // 使用mmap分配内存
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
if (ret == (void*)-1) {
if (hard_fail) FATAL("mmap() failed on alloc (OOM?)"); // 如果mmap失败且hard_fail为真输出错误并终止程序
if (hard_fail) FATAL("mmap() failed on alloc (OOM?)");
DEBUGF("mmap() failed on alloc (OOM?)"); // 如果mmap失败且hard_fail为假输出调试信息并返回NULL
DEBUGF("mmap() failed on alloc (OOM?)");
return NULL;
}
/* 在最后一个页面设置PROT_NONE。 */
/* Set PROT_NONE on the last page. */
if (mprotect(ret + PG_COUNT(len + 8) * PAGE_SIZE, PAGE_SIZE, PROT_NONE))
FATAL("mprotect() failed when allocating memory"); // 如果mprotect失败输出错误并终止程序
FATAL("mprotect() failed when allocating memory");
/* 增加返回指针,使其对齐到页面边界。 */
/* Offset the return pointer so that it's right-aligned to the page
boundary. */
ret += PAGE_SIZE * PG_COUNT(len + 8) - len - 8;
/* 存储分配元数据。 */
/* Store allocation metadata. */
ret += 8;
PTR_L(ret) = len; // 存储分配长度
PTR_C(ret) = ALLOC_CANARY; // 存储canary值
PTR_L(ret) = len;
PTR_C(ret) = ALLOC_CANARY;
total_mem += len; // 增加已分配内存计数
total_mem += len;
return ret;
}
/* 面向用户的calloc()包装器。这只是一个溢出检查和
*/
/* The "user-facing" wrapper for calloc(). This just checks for overflows and
displays debug messages if requested. */
void* calloc(size_t elem_len, size_t elem_cnt) {
@ -155,40 +159,42 @@ void* calloc(size_t elem_len, size_t elem_cnt) {
size_t len = elem_len * elem_cnt;
/* 进行一些简单的检查,以检测明显的错误... */
/* Perform some sanity checks to detect obvious issues... */
if (elem_cnt && len / elem_cnt != elem_len) {
if (no_calloc_over) {
DEBUGF("calloc(%zu, %zu) would overflow, returning NULL", elem_len, elem_cnt); // 如果no_calloc_over为真输出调试信息并返回NULL
DEBUGF("calloc(%zu, %zu) would overflow, returning NULL", elem_len, elem_cnt);
return NULL;
}
FATAL("calloc(%zu, %zu) would overflow", elem_len, elem_cnt); // 如果no_calloc_over为假输出错误并终止程序
FATAL("calloc(%zu, %zu) would overflow", elem_len, elem_cnt);
}
ret = __dislocator_alloc(len); // 调用内部分配函数
ret = __dislocator_alloc(len);
DEBUGF("calloc(%zu, %zu) = %p [%zu total]", elem_len, elem_cnt, ret,
total_mem); // 输出调试信息
total_mem);
return ret;
}
/* malloc()的包装器。大致相同,
calloc()malloc() */
/* The wrapper for malloc(). Roughly the same, also clobbers the returned
memory (unlike calloc(), malloc() is not guaranteed to return zeroed
memory). */
void* malloc(size_t len) {
void* ret;
ret = __dislocator_alloc(len); // 调用内部分配函数
ret = __dislocator_alloc(len);
DEBUGF("malloc(%zu) = %p [%zu total]", len, ret, total_mem); // 输出调试信息
DEBUGF("malloc(%zu) = %p [%zu total]", len, ret, total_mem);
if (ret && len) memset(ret, ALLOC_CLOBBER, len); // 使用clobber值填充内存
if (ret && len) memset(ret, ALLOC_CLOBBER, len);
return ret;
@ -200,84 +206,70 @@ void* malloc(size_t len) {
read the canary. Not very graceful, but works, right? */
void free(void* ptr) {
// 定义一个变量len用于存储要释放的内存块的长度
u32 len;
// 调试信息,打印正在释放的内存指针地址
DEBUGF("free(%p)", ptr);
u32 len;
DEBUGF("free(%p)", ptr);
if (!ptr) return;
if (PTR_C(ptr) != ALLOC_CANARY) FATAL("bad allocator canary on free()");
// 如果指针为NULL直接返回不进行任何操作
if (!ptr) return;
len = PTR_L(ptr);
// 检查指针的canary值是否正确如果不正确程序将致命错误并退出
if (PTR_C(ptr) != ALLOC_CANARY) FATAL("bad allocator canary on free()");
total_mem -= len;
// 获取指针所指向的内存块的实际长度
len = PTR_L(ptr);
/* Protect everything. Note that the extra page at the end is already
set as PROT_NONE, so we don't need to touch that. */
// 减少全局变量total_mem的值表示当前分配的内存总大小减少
total_mem -= len;
ptr -= PAGE_SIZE * PG_COUNT(len + 8) - len - 8;
// 计算出内存块的实际起始地址,以便后续对整个内存块进行操作
// 减去len+8是因为在分配内存时内存块的前面8个字节用于存储canary和长度信息
ptr -= PAGE_SIZE * PG_COUNT(len + 8) - len - 8;
if (mprotect(ptr - 8, PG_COUNT(len + 8) * PAGE_SIZE, PROT_NONE))
FATAL("mprotect() failed when freeing memory");
// 使用mprotect系统调用来将内存块的权限设置为PROT_NONE即无法读写执行
// 这样可以防止内存块被再次使用,增加了程序的安全性
if (mprotect(ptr - 8, PG_COUNT(len + 8) * PAGE_SIZE, PROT_NONE))
FATAL("mprotect() failed when freeing memory");
/* Keep the mapping; this is wasteful, but prevents ptr reuse. */
// 保持内存映射的存在,虽然这样做会浪费一些内存,但是防止内存地址被重复使用
// 这是一种保护机制,防止使用已经释放的内存
}
/* realloc函数用于重新分配内存其逻辑是
1.
2.
3. freefreemprotect
*/
/* Realloc is pretty straightforward, too. We forcibly reallocate the buffer,
move data, and then free (aka mprotect()) the original one. */
void* realloc(void* ptr, size_t len) {
// 定义一个指针ret用于存储新分配的内存地址
void* ret;
// 为新的长度分配内存分配失败时ret为NULL
ret = malloc(len);
void* ret;
// 如果新内存分配成功且原始指针不为NULL则进行数据复制和原始内存释放
if (ret && ptr) {
// 检查原始指针的canary值是否正确如果不正确程序将致命错误并退出
if (PTR_C(ptr) != ALLOC_CANARY) FATAL("bad allocator canary on realloc()");
ret = malloc(len);
// 将原始内存中的数据复制到新分配的内存中,复制的数据长度为原始内存和新内存长度的最小值
memcpy(ret, ptr, MIN(len, PTR_L(ptr)));
// 释放原始内存free函数中同样会调用mprotect来保护原始内存
free(ptr);
}
if (ret && ptr) {
if (PTR_C(ptr) != ALLOC_CANARY) FATAL("bad allocator canary on realloc()");
// 调试信息,打印原始指针地址、新长度、新内存地址以及当前分配的总内存大小
DEBUGF("realloc(%p, %zu) = %p [%zu total]", ptr, len, ret, total_mem);
memcpy(ret, ptr, MIN(len, PTR_L(ptr)));
free(ptr);
}
DEBUGF("realloc(%p, %zu) = %p [%zu total]", ptr, len, ret, total_mem);
return ret;
// 返回新分配的内存地址
return ret;
}
// __dislocator_init函数在程序加载时通过构造函数属性自动执行
__attribute__((constructor)) void __dislocator_init(void) {
// 定义一个临时变量tmp用于存储环境变量AFL_LD_LIMIT_MB的值
u8* tmp = getenv("AFL_LD_LIMIT_MB");
// 如果环境变量AFL_LD_LIMIT_MB存在则将其转换为max_mem的值以字节为单位
if (tmp) {
// atoi将字符串转换为整数乘以1024*1024表示将MB转换为字节
max_mem = atoi(tmp) * 1024 * 1024;
// 如果转换后的max_mem为0表示环境变量设置不正确程序将致命错误并退出
if (!max_mem) FATAL("Bad value for AFL_LD_LIMIT_MB");
}
// 检查环境变量AFL_LD_VERBOSE是否存在存在则将alloc_verbose设置为1否则为0
alloc_verbose = !!getenv("AFL_LD_VERBOSE");
// 检查环境变量AFL_LD_HARD_FAIL是否存在存在则将hard_fail设置为1否则为0
hard_fail = !!getenv("AFL_LD_HARD_FAIL");
// 检查环境变量AFL_LD_NO_CALLOC_OVER是否存在存在则将no_calloc_over设置为1否则为0
no_calloc_over = !!getenv("AFL_LD_NO_CALLOC_OVER");
u8* tmp = getenv("AFL_LD_LIMIT_MB");
if (tmp) {
max_mem = atoi(tmp) * 1024 * 1024;
if (!max_mem) FATAL("Bad value for AFL_LD_LIMIT_MB");
}
alloc_verbose = !!getenv("AFL_LD_VERBOSE");
hard_fail = !!getenv("AFL_LD_HARD_FAIL");
no_calloc_over = !!getenv("AFL_LD_NO_CALLOC_OVER");
}

@ -33,95 +33,89 @@
#include "../types.h"
#include "../config.h"
// 检查是否为Linux系统如果不是则报错
#ifndef __linux__
# error "Sorry, this library is Linux-specific for now!"
#endif /* !__linux__ */
// 定义最大映射数量
/* Mapping data and such */
#define MAX_MAPPINGS 1024
// 定义映射结构体,存储只读内存区域的起始和结束地址
static struct mapping {
void *st, *en;
} __tokencap_ro[MAX_MAPPINGS];
// 定义当前加载的只读映射数量
static u32 __tokencap_ro_cnt;
// 定义只读映射是否已加载标志
static u8 __tokencap_ro_loaded;
// 定义输出文件指针
static FILE* __tokencap_out_file;
// 功能:加载只读内存区域的映射信息
// 通过读取/proc/self/maps文件识别出只读且不可写的内存区域并存储在__tokencap_ro数组中
/* Identify read-only regions in memory. Only parameters that fall into these
ranges are worth dumping when passed to strcmp() and so on. Read-write
regions are far more likely to contain user input instead. */
static void __tokencap_load_mappings(void) {
u8 buf[MAX_LINE]; // 用于存储每行读取的内存映射信息
FILE* f = fopen("/proc/self/maps", "r"); // 打开/proc/self/maps文件该文件包含了当前进程的内存映射信息
u8 buf[MAX_LINE];
FILE* f = fopen("/proc/self/maps", "r");
__tokencap_ro_loaded = 1; // 标记只读映射已加载
__tokencap_ro_loaded = 1;
if (!f) return; // 如果文件打开失败,则直接返回
if (!f) return;
// 逐行读取文件内容
while (fgets(buf, MAX_LINE, f)) {
u8 rf, wf; // rf表示是否可读wf表示是否可写
void* st, *en; // st表示内存区域的起始地址en表示内存区域的结束地址
u8 rf, wf;
void* st, *en;
// 解析每行内存映射信息,提取起始地址、结束地址、是否可读和是否可写
if (sscanf(buf, "%p-%p %c%c", &st, &en, &rf, &wf) != 4) continue;
if (wf == 'w' || rf != 'r') continue; // 跳过可写或不可读的内存区域
if (wf == 'w' || rf != 'r') continue;
// 将只读内存区域的起始和结束地址存储到数组中
__tokencap_ro[__tokencap_ro_cnt].st = (void*)st;
__tokencap_ro[__tokencap_ro_cnt].en = (void*)en;
// 如果已达到最大映射数量,则停止加载
if (++__tokencap_ro_cnt == MAX_MAPPINGS) break;
}
fclose(f); // 关闭文件
fclose(f);
}
// 功能:检查给定地址是否位于只读内存区域
// 如果未加载映射信息则先调用__tokencap_load_mappings加载映射信息
/* Check an address against the list of read-only mappings. */
static u8 __tokencap_is_ro(const void* ptr) {
u32 i;
if (!__tokencap_ro_loaded) __tokencap_load_mappings(); // 如果只读映射未加载,则加载
if (!__tokencap_ro_loaded) __tokencap_load_mappings();
// 遍历只读映射数组,检查给定地址是否在任一只读内存区域内
for (i = 0; i < __tokencap_ro_cnt; i++)
if (ptr >= __tokencap_ro[i].st && ptr <= __tokencap_ro[i].en) return 1;
return 0; // 如果不在任何只读内存区域内则返回0
return 0;
}
// 功能:将感兴趣的数据转储到输出文件中
// 数据会被正确引用和转义,例如,非打印字符会被转义为\xXX的形式
/* Dump an interesting token to output file, quoting and escaping it
properly. */
static void __tokencap_dump(const u8* ptr, size_t len, u8 is_text) {
u8 buf[MAX_AUTO_EXTRA * 4 + 1]; // 存储转义后的数据
u8 buf[MAX_AUTO_EXTRA * 4 + 1];
u32 i;
u32 pos = 0; // 当前写入buf的位置
u32 pos = 0;
// 如果数据长度不符合要求或输出文件未打开,则直接返回
if (len < MIN_AUTO_EXTRA || len > MAX_AUTO_EXTRA || !__tokencap_out_file)
return;
// 遍历数据,进行转义处理
for (i = 0; i < len; i++) {
// 如果是文本数据且遇到空字符,则停止处理
if (is_text && !ptr[i]) break;
// 根据字符类型进行处理
switch (ptr[i]) {
case 0 ... 31:
@ -129,237 +123,189 @@ static void __tokencap_dump(const u8* ptr, size_t len, u8 is_text) {
case '\"':
case '\\':
// 对于非打印字符、双引号和反斜杠进行转义
sprintf(buf + pos, "\\x%02x", ptr[i]);
pos += 4; // 转义后的字符串长度为4
pos += 4;
break;
default:
// 对于可打印字符直接复制到buf中
buf[pos++] = ptr[i];
}
}
buf[pos] = 0; // 添加字符串结束符
buf[pos] = 0;
// 将转义后的字符串写入输出文件
fprintf(__tokencap_out_file, "\"%s\"\n", buf);
}
// 功能替换strcmp函数用于识别并转储只读内存区域中的字符串
// 如果目标程序编译时使用了-fno-builtins选项并动态链接则会使用此函数
/* Replacements for strcmp(), memcmp(), and so on. Note that these will be used
only if the target is compiled with -fno-builtins and linked dynamically. */
#undef strcmp
int strcmp(const char* str1, const char* str2) {
// 检查str1是否位于只读内存区域如果是则转储其内容
if (__tokencap_is_ro(str1)) __tokencap_dump(str1, strlen(str1), 1);
// 检查str2是否位于只读内存区域如果是则转储其内容
if (__tokencap_is_ro(str2)) __tokencap_dump(str2, strlen(str2), 1);
// 实现strcmp函数的核心功能比较两个字符串
while (1) {
unsigned char c1 = *str1, c2 = *str2;
// 如果两个字符不同,则返回比较结果
if (c1 != c2) return (c1 > c2) ? 1 : -1;
// 如果遇到字符串结束符则返回0
if (!c1) return 0;
// 指向下一个字符
str1++; str2++;
}
}
// 取消定义原本的strncmp函数以便重新定义
#undef strncmp
// 自定义的strncmp函数用于比较两个字符串的前len个字符
int strncmp(const char* str1, const char* str2, size_t len) {
// 检查str1是否为只读内存如果是则输出内存内容
if (__tokencap_is_ro(str1)) __tokencap_dump(str1, len, 1);
// 检查str2是否为只读内存如果是则输出内存内容
if (__tokencap_is_ro(str2)) __tokencap_dump(str2, len, 1);
// 循环比较两个字符串的前len个字符
while (len--) {
unsigned char c1 = *str1, c2 = *str2; // 获取当前字符
unsigned char c1 = *str1, c2 = *str2;
// 如果str1的第一个字符为0即字符串结束则返回0
if (!c1) return 0;
// 如果当前字符不相等根据字符的ASCII值大小返回1或-1
if (c1 != c2) return (c1 > c2) ? 1 : -1;
str1++; str2++; // 移动到下一个字符
str1++; str2++;
}
// 如果循环结束说明前len个字符都相等返回0
return 0;
}
// 取消定义原本的strcasecmp函数以便重新定义
#undef strcasecmp
// 自定义的strcasecmp函数用于忽略大小写的字符串比较
int strcasecmp(const char* str1, const char* str2) {
// 检查str1是否为只读内存如果是则输出内存内容
if (__tokencap_is_ro(str1)) __tokencap_dump(str1, strlen(str1), 1);
// 检查str2是否为只读内存如果是则输出内存内容
if (__tokencap_is_ro(str2)) __tokencap_dump(str2, strlen(str2), 1);
// 无限循环,逐字符比较两个字符串(忽略大小写)
while (1) {
unsigned char c1 = tolower(*str1), c2 = tolower(*str2); // 转换为小写后比较
unsigned char c1 = tolower(*str1), c2 = tolower(*str2);
// 如果当前字符不相等根据字符的ASCII值大小返回1或-1
if (c1 != c2) return (c1 > c2) ? 1 : -1;
// 如果str1的第一个字符为0即字符串结束则返回0
if (!c1) return 0;
str1++; str2++; // 移动到下一个字符
str1++; str2++;
}
}
// 取消定义原本的strncasecmp函数以便重新定义
#undef strncasecmp
// 自定义的strncasecmp函数用于忽略大小写的字符串前len个字符比较
int strncasecmp(const char* str1, const char* str2, size_t len) {
// 检查str1是否为只读内存如果是则输出内存内容
if (__tokencap_is_ro(str1)) __tokencap_dump(str1, len, 1);
// 检查str2是否为只读内存如果是则输出内存内容
if (__tokencap_is_ro(str2)) __tokencap_dump(str2, len, 1);
// 循环比较两个字符串的前len个字符忽略大小写
while (len--) {
unsigned char c1 = tolower(*str1), c2 = tolower(*str2); // 转换为小写后比较
unsigned char c1 = tolower(*str1), c2 = tolower(*str2);
// 如果str1的第一个字符为0即字符串结束则返回0
if (!c1) return 0;
// 如果当前字符不相等根据字符的ASCII值大小返回1或-1
if (c1 != c2) return (c1 > c2) ? 1 : -1;
str1++; str2++; // 移动到下一个字符
str1++; str2++;
}
// 如果循环结束说明前len个字符都相等返回0
return 0;
}
// 取消定义原本的memcmp函数以便重新定义
#undef memcmp
// 自定义的memcmp函数用于比较两个内存区域的前len个字节
int memcmp(const void* mem1, const void* mem2, size_t len) {
// 检查mem1是否为只读内存如果是则输出内存内容
if (__tokencap_is_ro(mem1)) __tokencap_dump(mem1, len, 0);
// 检查mem2是否为只读内存如果是则输出内存内容
if (__tokencap_is_ro(mem2)) __tokencap_dump(mem2, len, 0);
// 循环比较两个内存区域的前len个字节
while (len--) {
unsigned char c1 = *(const char*)mem1, c2 = *(const char*)mem2; // 获取当前字节
// 如果当前字节不相等根据字节的ASCII值大小返回1或-1
unsigned char c1 = *(const char*)mem1, c2 = *(const char*)mem2;
if (c1 != c2) return (c1 > c2) ? 1 : -1;
mem1++; mem2++; // 移动到下一个字节
mem1++; mem2++;
}
// 如果循环结束说明前len个字节都相等返回0
return 0;
}
// 取消定义原本的strstr函数以便重新定义
#undef strstr
// 自定义的strstr函数用于在haystack字符串中查找needle字符串
char* strstr(const char* haystack, const char* needle) {
// 检查haystack是否为只读内存如果是则输出内存内容
if (__tokencap_is_ro(haystack))
__tokencap_dump(haystack, strlen(haystack), 1);
// 检查needle是否为只读内存如果是则输出内存内容
if (__tokencap_is_ro(needle))
__tokencap_dump(needle, strlen(needle), 1);
// 在haystack中查找needle
do {
const char* n = needle;
const char* h = haystack;
// 逐字符比较haystack和needle
while(*n && *h && *n == *h) n++, h++;
// 如果needle的所有字符都被匹配返回匹配开始的位置
if(!*n) return (char*)haystack;
} while (*(haystack++)); // 移动到haystack的下一个字符并继续查找
} while (*(haystack++));
// 如果没有找到needle返回NULL
return 0;
}
// 取消定义原本的strcasestr函数以便重新定义
#undef strcasestr
// 自定义的strcasestr函数用于忽略大小写的字符串查找
char* strcasestr(const char* haystack, const char* needle) {
// 检查haystack是否为只读内存如果是则输出内存内容
if (__tokencap_is_ro(haystack))
__tokencap_dump(haystack, strlen(haystack), 1);
// 检查needle是否为只读内存如果是则输出内存内容
if (__tokencap_is_ro(needle))
__tokencap_dump(needle, strlen(needle), 1);
// 在haystack中查找needle忽略大小写
do {
const char* n = needle;
const char* h = haystack;
// 逐字符比较haystack和needle忽略大小写
while(*n && *h && tolower(*n) == tolower(*h)) n++, h++;
// 如果needle的所有字符都被匹配返回匹配开始的位置
if(!*n) return (char*)haystack;
} while(*(haystack++)); // 移动到haystack的下一个字符并继续查找
} while(*(haystack++));
// 如果没有找到needle返回NULL
return 0;
}
// 程序启动时执行的初始化代码
/* Init code to open the output file (or default to stderr). */
__attribute__((constructor)) void __tokencap_init(void) {
u8* fn = getenv("AFL_TOKEN_FILE"); // 获取环境变量AFL_TOKEN_FILE的值
// 如果环境变量存在,则以追加模式打开文件
u8* fn = getenv("AFL_TOKEN_FILE");
if (fn) __tokencap_out_file = fopen(fn, "a");
// 如果文件打开失败,则将输出重定向到标准错误输出
if (!__tokencap_out_file) __tokencap_out_file = stderr;
}

@ -45,453 +45,210 @@ static u8** cc_params; /* Parameters passed to the real CC */
static u32 cc_par_cnt = 1; /* Param count, including argv0 */
/*
*/
static void find_obj(u8* argv0) {
u8 *afl_path = getenv("AFL_PATH"); // 获取环境变量 AFL_PATH 的值
u8 *slash, *tmp; // 定义用于存储路径分隔符和临时路径的变量
if (afl_path) { // 如果 AFL_PATH 环境变量存在
tmp = alloc_printf("%s/afl-llvm-rt.o", afl_path); // 构造运行时库的路径
if (!access(tmp, R_OK)) { // 检查路径是否可读
obj_path = afl_path; // 如果可读,设置 obj_path 为 AFL_PATH
ck_free(tmp); // 释放临时路径的内存
return; // 返回,结束函数
}
ck_free(tmp); // 如果路径不可读,释放临时路径的内存
}
slash = strrchr(argv0, '/'); // 查找 argv0 中最后一个斜杠的位置
if (slash) { // 如果找到斜杠
u8 *dir; // 定义用于存储目录路径的变量
*slash = 0; // 将斜杠位置置为字符串结束符,以便提取目录路径
dir = ck_strdup(argv0); // 复制 argv0 的目录路径
*slash = '/'; // 恢复斜杠
tmp = alloc_printf("%s/afl-llvm-rt.o", dir); // 构造运行时库的路径
if (!access(tmp, R_OK)) { // 检查路径是否可读
obj_path = dir; // 如果可读,设置 obj_path 为当前目录
ck_free(tmp); // 释放临时路径的内存
return; // 返回,结束函数
}
ck_free(tmp); // 如果路径不可读,释放临时路径的内存
ck_free(dir); // 释放目录路径的内存
}
// 检查默认的 AFL_PATH 目录下是否存在 'afl-llvm-rt.o' 文件
if (!access(AFL_PATH "/afl-llvm-rt.o", R_OK)) {
obj_path = AFL_PATH; // 如果存在,设置 obj_path 为 AFL_PATH
return; // 返回,结束函数
}
// 如果以上方法都未找到运行时库,抛出致命错误
FATAL("Unable to find 'afl-llvm-rt.o' or 'afl-llvm-pass.so'. Please set AFL_PATH");
}
/*
argv cc_params
*/
static void edit_params(u32 argc, char** argv) {
u8 fortify_set = 0, asan_set = 0, x_set = 0, bit_mode = 0; // 初始化标志变量
u8 *name; // 定义用于存储程序名的变量
// 为 cc_params 分配足够的内存,以容纳传入的参数加上额外的空间
cc_params = ck_alloc((argc + 128) * sizeof(u8*));
// 提取程序名
name = strrchr(argv[0], '/'); // 查找 argv0 中最后一个斜杠的位置
if (!name) name = argv[0]; else name++; // 如果没有斜杠,使用完整的 argv[0]
// 根据程序名确定使用的编译器
if (!strcmp(name, "afl-clang-fast++")) { // 如果程序名是 afl-clang-fast++
u8* alt_cxx = getenv("AFL_CXX"); // 获取环境变量 AFL_CXX 的值
cc_params[0] = alt_cxx ? alt_cxx : (u8*)"clang++"; // 如果存在则使用其值,否则默认使用 clang++
} else { // 如果程序名不是 afl-clang-fast++
u8* alt_cc = getenv("AFL_CC"); // 获取环境变量 AFL_CC 的值
cc_params[0] = alt_cc ? alt_cc : (u8*)"clang"; // 如果存在则使用其值,否则默认使用 clang
}
#ifdef USE_TRACE_PC // 如果定义了 USE_TRACE_PC
// 添加用于 sanitization 的覆盖率参数
cc_params[cc_par_cnt++] = "-fsanitize-coverage=trace-pc-guard"; // 启用 trace-pc-guard 插桩
#ifndef __ANDROID__ // 如果不是 Android 平台
cc_params[cc_par_cnt++] = "-mllvm"; // 添加 LLVM 相关参数
cc_params[cc_par_cnt++] = "-sanitizer-coverage-block-threshold=0"; // 设置目标覆盖率的阈值为 0
#endif
#else // 如果没有定义 USE_TRACE_PC
// 添加 LLVM 插件的加载参数
cc_params[cc_par_cnt++] = "-Xclang"; // 添加 Clang 参数
cc_params[cc_par_cnt++] = "-load"; // 指定加载插件
cc_params[cc_par_cnt++] = "-Xclang"; // 添加 Clang 参数
cc_params[cc_par_cnt++] = alloc_printf("%s/afl-llvm-pass.so", obj_path);
// 追加 AFL 关键依赖项 afl-llvm-pass.so 到 cc_params用于插桩分析
#endif /* ^USE_TRACE_PC */
cc_params[cc_par_cnt++] = "-Qunused-arguments";
// 添加一个参数,告诉编译器忽略未使用的命令行参数
while (--argc) { // 循环处理剩余的命令行参数
u8* cur = *(++argv); // 获取当前参数
if (!strcmp(cur, "-m32")) bit_mode = 32; // 如果参数是 -m32设置 bit_mode 为 32
if (!strcmp(cur, "armv7a-linux-androideabi")) bit_mode = 32; // 针对特定架构的设置
if (!strcmp(cur, "-m64")) bit_mode = 64; // 如果参数是 -m64设置 bit_mode 为 64
if (!strcmp(cur, "-x")) x_set = 1; // 如果参数为 -x设置 x_set 为 1表示启用此选项
// 检查是否使用地址或内存的安全检测
if (!strcmp(cur, "-fsanitize=address") ||
!strcmp(cur, "-fsanitize=memory")) asan_set = 1;
// 检查是否启用了 FORTIFY_SOURCE
if (strstr(cur, "FORTIFY_SOURCE")) fortify_set = 1;
// 如果链接器选项是 -Wl,-z,defs 或 -Wl,--no-undefined跳过该参数
if (!strcmp(cur, "-Wl,-z,defs") ||
!strcmp(cur, "-Wl,--no-undefined")) continue;
cc_params[cc_par_cnt++] = cur; // 将当前参数添加到 cc_params
}
// 如果环境变量 AFL_HARDEN 存在
if (getenv("AFL_HARDEN")) {
cc_params[cc_par_cnt++] = "-fstack-protector-all"; // 启用所有堆栈保护
// 如果未启用 FORTIFY_SOURCE添加定义以启用其功能
if (!fortify_set)
cc_params[cc_par_cnt++] = "-D_FORTIFY_SOURCE=2";
}
// 如果尚未启用 AddressSanitizer
if (!asan_set) {
// 检查是否设置了使用 AddressSanitizer 的环境变量
if (getenv("AFL_USE_ASAN")) {
// 检查 MSAN 和 ASAN 互斥
if (getenv("AFL_USE_MSAN"))
FATAL("ASAN and MSAN are mutually exclusive");
// 检查 AFL_HARDEN 是否与 ASAN 互斥
if (getenv("AFL_HARDEN"))
FATAL("ASAN and AFL_HARDEN are mutually exclusive");
cc_params[cc_par_cnt++] = "-U_FORTIFY_SOURCE"; // 取消定义 FORTIFY_SOURCE
cc_params[cc_par_cnt++] = "-fsanitize=address"; // 启用 AddressSanitizer
}
// 检查是否设置了使用 MemorySanitizer 的环境变量
else if (getenv("AFL_USE_MSAN")) {
// 检查 ASAN 和 MSAN 互斥
if (getenv("AFL_USE_ASAN"))
FATAL("ASAN and MSAN are mutually exclusive");
// 检查 AFL_HARDEN 是否与 MSAN 互斥
if (getenv("AFL_HARDEN"))
FATAL("MSAN and AFL_HARDEN are mutually exclusive");
cc_params[cc_par_cnt++] = "-U_FORTIFY_SOURCE"; // 取消定义 FORTIFY_SOURCE
cc_params[cc_par_cnt++] = "-fsanitize=memory"; // 启用 MemorySanitizer
}
}
// 如果定义了 USE_TRACE_PC
#ifdef USE_TRACE_PC
// 检查 AFL_INST_RATIO 环境变量是否可用
if (getenv("AFL_INST_RATIO"))
FATAL("AFL_INST_RATIO not available at compile time with 'trace-pc'.");
#endif /* USE_TRACE_PC */
// 如果未设置 AFL_DONT_OPTIMIZE则启用优化选项
if (!getenv("AFL_DONT_OPTIMIZE")) {
cc_params[cc_par_cnt++] = "-g"; // 添加调试信息
cc_params[cc_par_cnt++] = "-O3"; // 启用高优化级别
cc_params[cc_par_cnt++] = "-funroll-loops"; // 启用循环展开
}
// 如果设置了 AFL_NO_BUILTIN则禁用特定的内置函数
if (getenv("AFL_NO_BUILTIN")) {
cc_params[cc_par_cnt++] = "-fno-builtin-strcmp"; // 禁用 strcmp 的内置实现
cc_params[cc_par_cnt++] = "-fno-builtin-strncmp"; // 禁用 strncmp 的内置实现
cc_params[cc_par_cnt++] = "-fno-builtin-strcasecmp"; // 禁用 strcasecmp 的内置实现
cc_params[cc_par_cnt++] = "-fno-builtin-strncasecmp"; // 禁用 strncasecmp 的内置实现
cc_params[cc_par_cnt++] = "-fno-builtin-memcmp"; // 禁用 memcmp 的内置实现
}
// 添加 AFL 控制宏,确保手动控制和编译器的定义
cc_params[cc_par_cnt++] = "-D__AFL_HAVE_MANUAL_CONTROL=1"; // 指示支持手动控制
cc_params[cc_par_cnt++] = "-D__AFL_COMPILER=1"; // 编译器标识
cc_params[cc_par_cnt++] = "-DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION=1"; // 表示为不安全的生产模式
/*
使 forkserver
(便 afl-fuzz )
.o
1)
__attribute__((used))
2) -Wl,--gc-sections
'volatile'
3) __afl_persistent_loop()
- :: extern "C"
__attribute__((alias(...))) 使 __asm__
*/
// 定义一个宏,用于处理 AFL 循环的持久化
cc_params[cc_par_cnt++] = "-D__AFL_LOOP(_A)="
// 开始一个代码块,其中静态的、易失的字符指针用于存放持久化的信号
"({ static volatile char *_B __attribute__((used)); "
" _B = (char*)\"" PERSIST_SIG "\"; "; // 将持久化信号字符串赋值给指针 _B
#ifdef __APPLE__ // 根据系统平台选择合适的函数名称
"__attribute__((visibility(\"default\"))) "
"int _L(unsigned int) __asm__(\"___afl_persistent_loop\"); " // Apple 平台下的持久化函数
#else
"__attribute__((visibility(\"default\"))) "
"int _L(unsigned int) __asm__(\"__afl_persistent_loop\"); " // 其他平台下的持久化函数
#endif /* ^__APPLE__ */
"_L(_A); })"; // 调用持久化函数,并结束代码块
/* Try to find the runtime libraries. If that fails, abort. */
// 定义一个宏,用于初始化 AFL
cc_params[cc_par_cnt++] = "-D__AFL_INIT()="
"do { static volatile char *_A __attribute__((used)); "
" _A = (char*)\"" DEFER_SIG "\"; "; // 将延迟信号字符串赋值给指针 _A
#ifdef __APPLE__ // 根据系统平台选择合适的初始化函数名称
"__attribute__((visibility(\"default\"))) "
"void _I(void) __asm__(\"___afl_manual_init\"); " // Apple 平台下的初始化函数
#else
"__attribute__((visibility(\"default\"))) "
"void _I(void) __asm__(\"__afl_manual_init\"); " // 其他平台下的初始化函数
#endif /* ^__APPLE__ */
"_I(); } while (0)"; // 调用初始化函数,并结束循环结构
// 如果 x_set 被设置,添加参数来指示关闭类型检查
if (x_set) {
cc_params[cc_par_cnt++] = "-x"; // 添加-x参数
cc_params[cc_par_cnt++] = "none"; // 指示后续没有特定类型
}
// 如果不是在 Android 平台下
#ifndef __ANDROID__
switch (bit_mode) { // 根据位数选择合适的运行时库
case 0: // 如果未设置位模式
cc_params[cc_par_cnt++] = alloc_printf("%s/afl-llvm-rt.o", obj_path); // 使用默认的 afl-llvm-rt.o 路径
break;
case 32: // 如果设置了 32 位模式
cc_params[cc_par_cnt++] = alloc_printf("%s/afl-llvm-rt-32.o", obj_path); // 使用 32 位运行时库路径
// 检查路径是否可读,如果不可读则抛出错误
if (access(cc_params[cc_par_cnt - 1], R_OK))
FATAL("-m32 is not supported by your compiler"); // 抛出错误信息
break;
case 64: // 如果设置了 64 位模式
cc_params[cc_par_cnt++] = alloc_printf("%s/afl-llvm-rt-64.o", obj_path); // 使用 64 位运行时库路径
/* 查找运行时库的路径。如果找不到,则中止程序。 */
static void find_obj(u8* argv0) {
// 获取环境变量 AFL_PATH
u8 *afl_path = getenv("AFL_PATH");
u8 *slash, *tmp;
// 如果找到 AFL_PATH
if (afl_path) {
// 生成 afl-llvm-rt.o 的完整路径
tmp = alloc_printf("%s/afl-llvm-rt.o", afl_path);
// 检查文件是否可读
if (!access(tmp, R_OK)) {
obj_path = afl_path; // 设置对象路径为 AFL_PATH
ck_free(tmp); // 释放临时路径内存
return; // 找到文件,结束函数
obj_path = afl_path;
ck_free(tmp);
return;
}
ck_free(tmp); // 释放临时路径内存
ck_free(tmp);
}
// 查找 argv0 中最后一个 '/' 的位置
slash = strrchr(argv0, '/');
// 如果找到 '/'
if (slash) {
u8 *dir;
*slash = 0; // 将 '/' 替换为结束符,以获取目录
dir = ck_strdup(argv0); // 复制目录名
*slash = '/'; // 恢复原来的 '/' 字符
*slash = 0;
dir = ck_strdup(argv0);
*slash = '/';
// 生成 afl-llvm-rt.o 的完整路径
tmp = alloc_printf("%s/afl-llvm-rt.o", dir);
// 检查文件是否可读
if (!access(tmp, R_OK)) {
obj_path = dir; // 设置对象路径为找到的目录
ck_free(tmp); // 释放临时路径内存
return; // 找到文件,结束函数
obj_path = dir;
ck_free(tmp);
return;
}
ck_free(tmp); // 释放临时路径内存
ck_free(dir); // 释放目录名内存
ck_free(tmp);
ck_free(dir);
}
// 检查默认路径下的 afl-llvm-rt.o 是否可读
if (!access(AFL_PATH "/afl-llvm-rt.o", R_OK)) {
obj_path = AFL_PATH; // 设置对象路径为默认的 AFL_PATH
return; // 找到文件,结束函数
obj_path = AFL_PATH;
return;
}
// 如果都找不到,则抛出致命错误
FATAL("Unable to find 'afl-llvm-rt.o' or 'afl-llvm-pass.so'. Please set AFL_PATH");
}
/* 复制 argv 到 cc_params并进行必要的编辑。 */
/* Copy argv to cc_params, making the necessary edits. */
static void edit_params(u32 argc, char** argv) {
// 初始化标志变量
u8 fortify_set = 0, asan_set = 0, x_set = 0, bit_mode = 0;
u8 *name;
// 分配空间以存储参数
cc_params = ck_alloc((argc + 128) * sizeof(u8*));
// 获取执行的程序名称
name = strrchr(argv[0], '/');
if (!name) name = argv[0]; else name++;
// 根据程序名称选择合适的编译器
if (!strcmp(name, "afl-clang-fast++")) {
u8* alt_cxx = getenv("AFL_CXX"); // 获取环境变量 AFL_CXX
cc_params[0] = alt_cxx ? alt_cxx : (u8*)"clang++"; // 设置 C++ 编译器
u8* alt_cxx = getenv("AFL_CXX");
cc_params[0] = alt_cxx ? alt_cxx : (u8*)"clang++";
} else {
u8* alt_cc = getenv("AFL_CC"); // 获取环境变量 AFL_CC
cc_params[0] = alt_cc ? alt_cc : (u8*)"clang"; // 设置 C 编译器
u8* alt_cc = getenv("AFL_CC");
cc_params[0] = alt_cc ? alt_cc : (u8*)"clang";
}
/* There are two ways to compile afl-clang-fast. In the traditional mode, we
use afl-llvm-pass.so to inject instrumentation. In the experimental
'trace-pc-guard' mode, we use native LLVM instrumentation callbacks
instead. The latter is a very recent addition - see:
http://clang.llvm.org/docs/SanitizerCoverage.html#tracing-pcs-with-guards */
#ifdef USE_TRACE_PC
// 启用跟踪 PC 的覆盖率
cc_params[cc_par_cnt++] = "-fsanitize-coverage=trace-pc-guard";
#ifndef __ANDROID__
cc_params[cc_par_cnt++] = "-mllvm"; // LLVM 选项
cc_params[cc_par_cnt++] = "-sanitizer-coverage-block-threshold=0"; // 设定阈值
cc_params[cc_par_cnt++] = "-mllvm";
cc_params[cc_par_cnt++] = "-sanitizer-coverage-block-threshold=0";
#endif
#else
// 加载 afl-llvm-pass.so 插件
cc_params[cc_par_cnt++] = "-Xclang";
cc_params[cc_par_cnt++] = "-load";
cc_params[cc_par_cnt++] = "-Xclang";
cc_params[cc_par_cnt++] = alloc_printf("%s/afl-llvm-pass.so", obj_path);
#endif /* ^USE_TRACE_PC */
cc_params[cc_par_cnt++] = "-Qunused-arguments"; // 忽略未使用的参数
cc_params[cc_par_cnt++] = "-Qunused-arguments";
// 循环处理输入参数
while (--argc) {
u8* cur = *(++argv); // 获取当前参数
u8* cur = *(++argv);
// 检查位模式
if (!strcmp(cur, "-m32")) bit_mode = 32;
if (!strcmp(cur, "armv7a-linux-androideabi")) bit_mode = 32;
if (!strcmp(cur, "-m64")) bit_mode = 64;
// 检查其他编译选项
if (!strcmp(cur, "-x")) x_set = 1;
// 检查是否启用地址/内存的 sanitization
if (!strcmp(cur, "-fsanitize=address") ||
!strcmp(cur, "-fsanitize=memory")) asan_set = 1;
// 检查 FORTIFY_SOURCE 是否启用
if (strstr(cur, "FORTIFY_SOURCE")) fortify_set = 1;
// 跳过某些链接器选项
if (!strcmp(cur, "-Wl,-z,defs") ||
!strcmp(cur, "-Wl,--no-undefined")) continue;
// 将当前参数添加到 cc_params
cc_params[cc_par_cnt++] = cur;
}
// 如果启用了硬化选项
if (getenv("AFL_HARDEN")) {
cc_params[cc_par_cnt++] = "-fstack-protector-all"; // 启用栈保护
// 如果没有启用 FORTIFY_SOURCE 则添加对应宏定义
cc_params[cc_par_cnt++] = "-fstack-protector-all";
if (!fortify_set)
cc_params[cc_par_cnt++] = "-D_FORTIFY_SOURCE=2";
}
// 检查地址或内存 sanitization 的设置
if (!asan_set) {
if (getenv("AFL_USE_ASAN")) {
if (getenv("AFL_USE_MSAN"))
FATAL("ASAN and MSAN are mutually exclusive"); // 互斥检查
FATAL("ASAN and MSAN are mutually exclusive");
if (getenv("AFL_HARDEN"))
FATAL("ASAN and AFL_HARDEN are mutually exclusive"); // 互斥检查
cc_params[cc_par_cnt++] = "-U_FORTIFY_SOURCE"; // 禁用 FORTIFY_SOURCE
cc_params[cc_par_cnt++] = "-fsanitize=address"; // 启用地址条件检测
FATAL("ASAN and AFL_HARDEN are mutually exclusive");
cc_params[cc_par_cnt++] = "-U_FORTIFY_SOURCE";
cc_params[cc_par_cnt++] = "-fsanitize=address";
} else if (getenv("AFL_USE_MSAN")) {
if (getenv("AFL_USE_ASAN"))
FATAL("ASAN and MSAN are mutually exclusive"); // 互斥检查
FATAL("ASAN and MSAN are mutually exclusive");
if (getenv("AFL_HARDEN"))
FATAL("MSAN and AFL_HARDEN are mutually exclusive"); // 互斥检查
cc_params[cc_par_cnt++] = "-U_FORTIFY_SOURCE"; // 禁用 FORTIFY_SOURCE
cc_params[cc_par_cnt++] = "-fsanitize=memory"; // 启用内存条件检测
FATAL("MSAN and AFL_HARDEN are mutually exclusive");
cc_params[cc_par_cnt++] = "-U_FORTIFY_SOURCE";
cc_params[cc_par_cnt++] = "-fsanitize=memory";
}
}
#ifdef USE_TRACE_PC
// 检查 AFL_INST_RATIO 环境变量的设置
if (getenv("AFL_INST_RATIO"))
FATAL("AFL_INST_RATIO not available at compile time with 'trace-pc'.");
#endif /* USE_TRACE_PC */
// 检查是否不优化
if (!getenv("AFL_DONT_OPTIMIZE")) {
cc_params[cc_par_cnt++] = "-g"; // 启用调试信息
cc_params[cc_par_cnt++] = "-O3"; // 启用最高级别的优化
cc_params[cc_par_cnt++] = "-funroll-loops"; // 循环展开以提高性能
}
if (!getenv("AFL_DONT_OPTIMIZE")) {
cc_params[cc_par_cnt++] = "-g";
cc_params[cc_par_cnt++] = "-O3";
cc_params[cc_par_cnt++] = "-funroll-loops";
}
if (getenv("AFL_NO_BUILTIN")) {
// 检查是否禁用内置函数的使用
if (getenv("AFL_NO_BUILTIN")) {
// 禁用特定的内置字符串比较和内存比较函数
cc_params[cc_par_cnt++] = "-fno-builtin-strcmp";
cc_params[cc_par_cnt++] = "-fno-builtin-strncmp";
cc_params[cc_par_cnt++] = "-fno-builtin-strcasecmp";
cc_params[cc_par_cnt++] = "-fno-builtin-strncasecmp";
cc_params[cc_par_cnt++] = "-fno-builtin-memcmp";
}
// 添加 AFL 相关的宏定义
cc_params[cc_par_cnt++] = "-D__AFL_HAVE_MANUAL_CONTROL=1"; // 手动控制
cc_params[cc_par_cnt++] = "-D__AFL_COMPILER=1"; // 标记为 AFL 编译器
cc_params[cc_par_cnt++] = "-DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION=1"; // 不适合生产的模糊构建模式
}
cc_params[cc_par_cnt++] = "-D__AFL_HAVE_MANUAL_CONTROL=1";
cc_params[cc_par_cnt++] = "-D__AFL_COMPILER=1";
cc_params[cc_par_cnt++] = "-DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION=1";
/* When the user tries to use persistent or deferred forkserver modes by
appending a single line to the program, we want to reliably inject a
signature into the binary (to be picked up by afl-fuzz) and we want
to call a function from the runtime .o file. This is unnecessarily
painful for three reasons:
1) We need to convince the compiler not to optimize out the signature.
This is done with __attribute__((used)).
2) We need to convince the linker, when called with -Wl,--gc-sections,
not to do the same. This is done by forcing an assignment to a
'volatile' pointer.
// 添加 AFL 循环的实现
cc_params[cc_par_cnt++] = "-D__AFL_LOOP(_A)="
3) We need to declare __afl_persistent_loop() in the global namespace,
but doing this within a method in a class is hard - :: and extern "C"
are forbidden and __attribute__((alias(...))) doesn't work. Hence the
__asm__ aliasing trick.
*/
cc_params[cc_par_cnt++] = "-D__AFL_LOOP(_A)="
"({ static volatile char *_B __attribute__((used)); "
" _B = (char*)\"" PERSIST_SIG "\"; "
#ifdef __APPLE__
@ -501,10 +258,9 @@ cc_params[cc_par_cnt++] = "-D__AFL_LOOP(_A)="
"__attribute__((visibility(\"default\"))) "
"int _L(unsigned int) __asm__(\"__afl_persistent_loop\"); "
#endif /* ^__APPLE__ */
"_L(_A); })"; // 定义 AFL 循环的宏
"_L(_A); })";
// 添加 AFL 初始化函数的实现
cc_params[cc_par_cnt++] = "-D__AFL_INIT()="
cc_params[cc_par_cnt++] = "-D__AFL_INIT()="
"do { static volatile char *_A __attribute__((used)); "
" _A = (char*)\"" DEFER_SIG "\"; "
#ifdef __APPLE__
@ -514,57 +270,58 @@ cc_params[cc_par_cnt++] = "-D__AFL_INIT()="
"__attribute__((visibility(\"default\"))) "
"void _I(void) __asm__(\"__afl_manual_init\"); "
#endif /* ^__APPLE__ */
"_I(); } while (0)"; // 定义 AFL 初始化的宏
"_I(); } while (0)";
// 如果启用 -x 选项
if (x_set) {
cc_params[cc_par_cnt++] = "-x"; // 添加 -x 选项
cc_params[cc_par_cnt++] = "none"; // 设置为 none表示不对输入进行特定语言解析
}
if (x_set) {
cc_params[cc_par_cnt++] = "-x";
cc_params[cc_par_cnt++] = "none";
}
#ifndef __ANDROID__
// 根据位模式选择不同的运行时库文件
switch (bit_mode) {
switch (bit_mode) {
case 0:
cc_params[cc_par_cnt++] = alloc_printf("%s/afl-llvm-rt.o", obj_path); // 默认情况下使用通用的运行时文件
break;
cc_params[cc_par_cnt++] = alloc_printf("%s/afl-llvm-rt.o", obj_path);
break;
case 32:
cc_params[cc_par_cnt++] = alloc_printf("%s/afl-llvm-rt-32.o", obj_path); // 使用 32 位版本的运行时文件
// 检查此文件是否可读
if (access(cc_params[cc_par_cnt - 1], R_OK))
FATAL("-m32 is not supported by your compiler"); // 如果不可读则报错
break;
cc_params[cc_par_cnt++] = alloc_printf("%s/afl-llvm-rt-32.o", obj_path);
if (access(cc_params[cc_par_cnt - 1], R_OK))
FATAL("-m32 is not supported by your compiler");
break;
case 64:
cc_params[cc_par_cnt++] = alloc_printf("%s/afl-llvm-rt-64.o", obj_path); // 使用 64 位版本的运行时文件
// 检查此文件是否可读
if (access(cc_params[cc_par_cnt - 1], R_OK))
FATAL("-m64 is not supported by your compiler"); // 如果不可读则报错
break;
cc_params[cc_par_cnt++] = alloc_printf("%s/afl-llvm-rt-64.o", obj_path);
if (access(cc_params[cc_par_cnt - 1], R_OK))
FATAL("-m64 is not supported by your compiler");
break;
}
#endif
cc_params[cc_par_cnt] = NULL;
}
// 结束参数数组,以空指针结尾
cc_params[cc_par_cnt] = NULL;
/* Main entry point */
/* 主入口点 */
int main(int argc, char** argv) {
// 检查标准错误是否连接到终端且 AFL_QUIET 环境变量未设置
if (isatty(2) && !getenv("AFL_QUIET")) {
#ifdef USE_TRACE_PC
// 如果启用了 USE_TRACE_PC打印版本信息
SAYF(cCYA "afl-clang-fast [tpcg] " cBRI VERSION cRST " by <lszekeres@google.com>\n");
#else
// 否则,仅打印版本信息
SAYF(cCYA "afl-clang-fast " cBRI VERSION cRST " by <lszekeres@google.com>\n");
#endif /* ^USE_TRACE_PC */
}
// 如果参数数量小于 2打印帮助信息并退出
if (argc < 2) {
SAYF("\n"
@ -582,23 +339,21 @@ int main(int argc, char** argv) {
"AFL_HARDEN enables hardening optimizations in the compiled code.\n\n",
BIN_PATH, BIN_PATH);
exit(1); // 退出程序,返回错误码 1
exit(1);
}
#ifndef __ANDROID__
// 在非 Android 平台下调用 find_obj 函数查找运行时库
find_obj(argv[0]);
#endif
// 调用 edit_params 函数处理命令行参数并设置编译参数
edit_params(argc, argv);
// 使用 execvp 执行指定的编译器命令
execvp(cc_params[0], (char**)cc_params);
// 如果 execvp 失败,抛出致命错误并打印相关信息
FATAL("Oops, failed to execute '%s' - check your PATH", cc_params[0]);
return 0; // 正常结束程序
return 0;
}

@ -1,94 +1,92 @@
/*
Copyright 2015 Google LLC All rights reserved. // 版权声明2015年谷歌公司所有权利保留
Copyright 2015 Google LLC All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License"); // 根据Apache许可证第2.0版授权
you may not use this file except in compliance with the License. // 除非遵守许可证,否则不得使用此文件
You may obtain a copy of the License at: // 可以通过以下网址获得许可证副本
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at:
http://www.apache.org/licenses/LICENSE-2.0 // 许可证的链接
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software // 除非相关法律要求或书面协议,软件
distributed under the License is distributed on an "AS IS" BASIS, // 在“按原样”基础上分发
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // 不提供任何形式的明示或暗示的保证或条件
See the License for the specific language governing permissions and // 请参阅许可证以获取约束和限制的具体语言
limitations under the License. // 在许可证下的限制条款
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
/*
american fuzzy lop - high-performance binary-only instrumentation // American Fuzzy Lop - 高性能二进制插桩
american fuzzy lop - high-performance binary-only instrumentation
-----------------------------------------------------------------
Written by Andrew Griffiths <agriffiths@google.com> and // 由Andrew Griffiths和
Michal Zalewski <lcamtuf@google.com> // Michal Zalewski编写
Written by Andrew Griffiths <agriffiths@google.com> and
Michal Zalewski <lcamtuf@google.com>
Idea & design very much by Andrew Griffiths. // 概念和设计主要由Andrew Griffiths提出。
Idea & design very much by Andrew Griffiths.
This code is a shim patched into the separately-distributed source // 此代码是补丁,插入到单独分发的源代码中
code of QEMU 2.10.0. It leverages the built-in QEMU tracing functionality // QEMU 2.10.0的源代码中。 该代码利用了内置的QEMU跟踪功能
to implement AFL-style instrumentation and to take care of the remaining // 实现AFL风格的插桩并处理剩余
parts of the AFL fork server logic. // AFL fork服务器逻辑的部分内容。
This code is a shim patched into the separately-distributed source
code of QEMU 2.10.0. It leverages the built-in QEMU tracing functionality
to implement AFL-style instrumentation and to take care of the remaining
parts of the AFL fork server logic.
The resulting QEMU binary is essentially a standalone instrumentation // 生成的QEMU二进制文件基本上是一个独立的插桩工具
tool; for an example of how to leverage it for other purposes, you can // 用于其他目的的示例可以参考
have a look at afl-showmap.c. // afl-showmap.c
The resulting QEMU binary is essentially a standalone instrumentation
tool; for an example of how to leverage it for other purposes, you can
have a look at afl-showmap.c.
*/
#include <sys/shm.h> // 包含共享内存的头文件
#include "../../config.h" // 包含项目配置的头文件
#include <sys/shm.h>
#include "../../config.h"
/***************************
* VARIOUS AUXILIARY STUFF *
***************************/
/* A snippet patched into tb_find_slow to inform the parent process that // 一段插入到tb_find_slow中的代码用于通知父进程
we have hit a new block that hasn't been translated yet, and to tell // 我们已经遇到了一个尚未翻译的新块,并告诉父进程
it to translate within its own context, too (this avoids translation // 也在其自己的上下文中翻译(这避免了在下一个
overhead in the next forked-off copy). // 被fork的副本中出现翻译开销
*/
/* A snippet patched into tb_find_slow to inform the parent process that
we have hit a new block that hasn't been translated yet, and to tell
it to translate within its own context, too (this avoids translation
overhead in the next forked-off copy). */
#define AFL_QEMU_CPU_SNIPPET1 do { \
afl_request_tsl(pc, cs_base, flags); \ // 调用afl_request_tsl函数传入参数
} while (0) // 循环体
afl_request_tsl(pc, cs_base, flags); \
} while (0)
/* This snippet kicks in when the instruction pointer is positioned at // 当指令指针位于_start位置时此代码段生效
_start and does the usual forkserver stuff, not very different from // 并执行常规的forkserver逻辑与通过afl-as.h注入的逻辑没有太大区别
/* This snippet kicks in when the instruction pointer is positioned at
_start and does the usual forkserver stuff, not very different from
regular instrumentation injected via afl-as.h. */
#define AFL_QEMU_CPU_SNIPPET2 do { \
if(itb->pc == afl_entry_point) { \ // 如果当前程序计数器等于入口点
afl_setup(); \ // 设置插桩环境
afl_forkserver(cpu); \ // 启动fork服务器
if(itb->pc == afl_entry_point) { \
afl_setup(); \
afl_forkserver(cpu); \
} \
afl_maybe_log(itb->pc); \ // 可能记录当前地址
} while (0) // 循环体
afl_maybe_log(itb->pc); \
} while (0)
/* We use one additional file descriptor to relay "needs translation" // 我们使用一个附加文件描述符来传递“需要翻译”的信息
/* We use one additional file descriptor to relay "needs translation"
messages between the child and the fork server. */
#define TSL_FD (FORKSRV_FD - 1) // 定义一个文件描述符,用于传递翻译请求
#define TSL_FD (FORKSRV_FD - 1)
/* This is equivalent to afl-as.h: */
static unsigned char *afl_area_ptr; // 定义指向AFL区域的指针
static unsigned char *afl_area_ptr;
/* Exported variables populated by the code patched into elfload.c: */
abi_ulong afl_entry_point, /* ELF entry point (_start) */ // ELF入口点
afl_start_code, /* .text start pointer */ // .text段起始指针
afl_end_code; /* .text end pointer */ // .text段结束指针
abi_ulong afl_entry_point, /* ELF entry point (_start) */
afl_start_code, /* .text start pointer */
afl_end_code; /* .text end pointer */
/* Set in the child process in forkserver mode: */
static unsigned char afl_fork_child; // 用于标记是否在fork子进程中
unsigned int afl_forksrv_pid; // fork服务器进程的PID
static unsigned char afl_fork_child;
unsigned int afl_forksrv_pid;
/* Instrumentation ratio: */
static unsigned int afl_inst_rms = MAP_SIZE; // 插桩比例
static unsigned int afl_inst_rms = MAP_SIZE;
/* Function declarations. */
// 函数声明
static void afl_setup(void);
static void afl_forkserver(CPUState*);
static inline void afl_maybe_log(abi_ulong);
@ -98,16 +96,16 @@ static void afl_request_tsl(target_ulong, target_ulong, uint64_t);
/* Data structure passed around by the translate handlers: */
struct afl_tsl { // 传递给翻译处理程序的数据结构
target_ulong pc; // 当前程序计数器
target_ulong cs_base; // 段基址
uint64_t flags; // 标志
struct afl_tsl {
target_ulong pc;
target_ulong cs_base;
uint64_t flags;
};
/* Some forward decls: */
TranslationBlock *tb_htable_lookup(CPUState*, target_ulong, target_ulong, uint32_t); // 查找翻译块的函数
static inline TranslationBlock *tb_find(CPUState*, TranslationBlock*, int); // 查找翻译块的内联函数
TranslationBlock *tb_htable_lookup(CPUState*, target_ulong, target_ulong, uint32_t);
static inline TranslationBlock *tb_find(CPUState*, TranslationBlock*, int);
/*************************
* ACTUAL IMPLEMENTATION *
@ -115,194 +113,201 @@ static inline TranslationBlock *tb_find(CPUState*, TranslationBlock*, int); //
/* Set up SHM region and initialize other stuff. */
static void afl_setup(void) { // 设置共享内存区域并初始化其他内容
static void afl_setup(void) {
char *id_str = getenv(SHM_ENV_VAR), // 获取共享内存环境变量
*inst_r = getenv("AFL_INST_RATIO"); // 获取插桩比例环境变量
char *id_str = getenv(SHM_ENV_VAR),
*inst_r = getenv("AFL_INST_RATIO");
int shm_id; // 共享内存标识符
int shm_id;
if (inst_r) { // 如果设置了插桩比例
if (inst_r) {
unsigned int r; // 声明插桩比例变量
unsigned int r;
r = atoi(inst_r); // 将环境变量转为整数
r = atoi(inst_r);
if (r > 100) r = 100; // 最大为100
if (!r) r = 1; // 如果为0则设置为1
if (r > 100) r = 100;
if (!r) r = 1;
afl_inst_rms = MAP_SIZE * r / 100; // 计算实际插桩比例
afl_inst_rms = MAP_SIZE * r / 100;
}
if (id_str) { // 如果设定了共享内存ID
if (id_str) {
shm_id = atoi(id_str);
afl_area_ptr = shmat(shm_id, NULL, 0);
shm_id = atoi(id_str); // 将ID转为整数
afl_area_ptr = shmat(shm_id, NULL, 0); // 附加共享内存
if (afl_area_ptr == (void*)-1) exit(1);
if (afl_area_ptr == (void*)-1) exit(1); // 失败则退出
/* With AFL_INST_RATIO set to a low value, we want to touch the bitmap
so that the parent doesn't give up on us. */
/* With AFL_INST_RATIO set to a low value, we want to touch the bitmap //
so that the parent doesn't give up on us. */ // 当插桩比例设置为较低值时,访问位图防止父进程放弃我们
if (inst_r) afl_area_ptr[0] = 1;
if (inst_r) afl_area_ptr[0] = 1; // 访问位图的第一个字节
}
if (getenv("AFL_INST_LIBS")) { // 如果设置了AFL_INST_LIBS环境变量
afl_start_code = 0; // 设置代码段起始位置
afl_end_code = (abi_ulong)-1; // 设置代码段结束位置
if (getenv("AFL_INST_LIBS")) {
afl_start_code = 0;
afl_end_code = (abi_ulong)-1;
}
/* pthread_atfork() seems somewhat broken in util/rcu.c, and I'm //
not entirely sure what is the cause. This disables that //
behaviour, and seems to work alright? */ // pthread_atfork()在util/rcu.c中似乎存在问题这禁用此行为并且有效
/* pthread_atfork() seems somewhat broken in util/rcu.c, and I'm
not entirely sure what is the cause. This disables that
behaviour, and seems to work alright? */
rcu_disable_atfork(); // 禁用atfork功能
rcu_disable_atfork();
}
/* Fork server logic, invoked once we hit _start. */
static void afl_forkserver(CPUState *cpu) { // fork服务器逻辑在_start时调用
static void afl_forkserver(CPUState *cpu) {
static unsigned char tmp[4];
static unsigned char tmp[4]; // 临时缓冲区
if (!afl_area_ptr) return;
if (!afl_area_ptr) return; // 如果指针为空则返回
/* Tell the parent that we're alive. If the parent doesn't want
to talk, assume that we're not running in forkserver mode. */
/* Tell the parent that we're alive. If the parent doesn't want //
to talk, assume that we're not running in forkserver mode. */ // 告诉父进程我们已经活着如果父进程不响应则认为不在fork服务器模式
if (write(FORKSRV_FD + 1, tmp, 4) != 4) return;
if (write(FORKSRV_FD + 1, tmp, 4) != 4) return; // 写入父进程
afl_forksrv_pid = getpid();
afl_forksrv_pid = getpid(); // 获取当前进程ID
/* All right, let's await orders... */
/* All right, let's await orders... */ // 好的,让我们等待命令…
while (1) {
while (1) { // 进入无限循环
pid_t child_pid;
int status, t_fd[2];
pid_t child_pid; // 子进程ID
int status, t_fd[2]; // 状态和文件描述符数组
/* Whoops, parent dead? */
/* Whoops, parent dead? */ // 哎呀,父进程死了?
if (read(FORKSRV_FD, tmp, 4) != 4) exit(2);
if (read(FORKSRV_FD, tmp, 4) != 4) exit(2); // 读取父进程信息失败则退出
/* Establish a channel with child to grab translation commands. We'll
read from t_fd[0], child will write to TSL_FD. */
/* Establish a channel with child to grab translation commands. We'll //
read from t_fd[0], child will write to TSL_FD. */ // 建立与子进程的通道获取翻译命令。我们从t_fd[0]读取子进程写入TSL_FD。
if (pipe(t_fd) || dup2(t_fd[1], TSL_FD) < 0) exit(3);
close(t_fd[1]);
if (pipe(t_fd) || dup2(t_fd[1], TSL_FD) < 0) exit(3); // 创建管道,复制描述符
close(t_fd[1]); // 关闭写入端
child_pid = fork();
if (child_pid < 0) exit(4);
child_pid = fork(); // 创建子进程
if (child_pid < 0) exit(4); // 创建失败则退出
if (!child_pid) {
if (!child_pid) { // 如果是子进程
/* Child process. Close descriptors and run free. */
/* Child process. Close descriptors and run free. */ // 子进程。关闭描述符,进入自由运行。
afl_fork_child = 1;
close(FORKSRV_FD);
close(FORKSRV_FD + 1);
close(t_fd[0]);
return;
afl_fork_child = 1; // 标记为子进程
close(FORKSRV_FD); // 关闭fork服务器文件描述符
close(FORKSRV_FD + 1); // 关闭fork服务器文件描述符的另一个副本
close(t_fd[0]); // 关闭读取端
return; // 返回
}
/* Parent. */ // 父进程。
/* Parent. */
close(TSL_FD); // 关闭TSL文件描述符
close(TSL_FD);
if (write(FORKSRV_FD + 1, &child_pid, 4) != 4) exit(5); // 向父进程写入子进程ID失败则退出。
if (write(FORKSRV_FD + 1, &child_pid, 4) != 4) exit(5);
/* Collect translation requests until child dies and closes the pipe. */ // 收集翻译请求,直到子进程结束并关闭管道。
/* Collect translation requests until child dies and closes the pipe. */
afl_wait_tsl(cpu, t_fd[0]); // 等待翻译请求
afl_wait_tsl(cpu, t_fd[0]);
/* Get and relay exit status to parent. */ // 获取子进程退出状态并传递给父进程。
/* Get and relay exit status to parent. */
if (waitpid(child_pid, &status, 0) < 0) exit(6); // 等待子进程结束,失败则退出
if (write(FORKSRV_FD + 1, &status, 4) != 4) exit(7); // 向父进程写入状态,失败则退出
if (waitpid(child_pid, &status, 0) < 0) exit(6);
if (write(FORKSRV_FD + 1, &status, 4) != 4) exit(7);
}
}
/* The equivalent of the tuple logging routine from afl-as.h. */
static inline void afl_maybe_log(abi_ulong cur_loc) { // 记录当前地址的内联函数
static inline void afl_maybe_log(abi_ulong cur_loc) {
static __thread abi_ulong prev_loc; // 上一个地址
static __thread abi_ulong prev_loc;
/* Optimize for cur_loc > afl_end_code, which is the most likely case on //
Linux systems. */ // 优化条件常见于Linux系统
/* Optimize for cur_loc > afl_end_code, which is the most likely case on
Linux systems. */
if (cur_loc > afl_end_code || cur_loc < afl_start_code || !afl_area_ptr) // 如果当前地址不在有效范围
return; // 返回
if (cur_loc > afl_end_code || cur_loc < afl_start_code || !afl_area_ptr)
return;
/* Looks like QEMU always maps to fixed locations, so ASAN is not a //
concern. Phew. But instruction addresses may be aligned. Let's mangle //
the value to get something quasi-uniform. */ // QEMU似乎总是映射到固定位置因此不需要担心ASAN。但指令地址可能会对齐。通过一些操作来获取统一值。
/* Looks like QEMU always maps to fixed locations, so ASAN is not a
concern. Phew. But instruction addresses may be aligned. Let's mangle
the value to get something quasi-uniform. */
cur_loc = (cur_loc >> 4) ^ (cur_loc << 8); // 按位操作获取新值
cur_loc &= MAP_SIZE - 1; // 避免越界
cur_loc = (cur_loc >> 4) ^ (cur_loc << 8);
cur_loc &= MAP_SIZE - 1;
/* Implement probabilistic instrumentation by looking at scrambled block //
address. This keeps the instrumented locations stable across runs. */ // 通过查看混乱的块地址实现概率性插桩,这样可以在多次运行中保持插桩位置的稳定性。
/* Implement probabilistic instrumentation by looking at scrambled block
address. This keeps the instrumented locations stable across runs. */
if (cur_loc >= afl_inst_rms) return; // 如果当前地址超过插桩比率,返回
if (cur_loc >= afl_inst_rms) return;
afl_area_ptr[cur_loc ^ prev_loc]++; // 增加对应计数
prev_loc = cur_loc >> 1; // 更新上一个位置
afl_area_ptr[cur_loc ^ prev_loc]++;
prev_loc = cur_loc >> 1;
}
/* This code is invoked whenever QEMU decides that it doesn't have a //
translation of a particular block and needs to compute it. When this happens, //
we tell the parent to mirror the operation, so that the next fork() has a //
cached copy. */ // 每当QEMU决定没有特定块的翻译并需要计算时会调用此代码。当发生这种情况时我们告诉父进程镜像操作以便下一个fork()具有缓存副本。
static void afl_request_tsl(target_ulong pc, target_ulong cb, uint64_t flags) { // 请求翻译
/* This code is invoked whenever QEMU decides that it doesn't have a
translation of a particular block and needs to compute it. When this happens,
we tell the parent to mirror the operation, so that the next fork() has a
cached copy. */
static void afl_request_tsl(target_ulong pc, target_ulong cb, uint64_t flags) {
struct afl_tsl t; // 创建AFI_TSL结构体
struct afl_tsl t;
if (!afl_fork_child) return; // 如果不是子进程,返回
if (!afl_fork_child) return;
t.pc = pc; // 设置程序计数器
t.cs_base = cb; // 设置基址
t.flags = flags; // 设置标志
t.pc = pc;
t.cs_base = cb;
t.flags = flags;
if (write(TSL_FD, &t, sizeof(struct afl_tsl)) != sizeof(struct afl_tsl)) // 写入数据请求
return; // 返回
if (write(TSL_FD, &t, sizeof(struct afl_tsl)) != sizeof(struct afl_tsl))
return;
}
/* This is the other side of the same channel. Since timeouts are handled by //
afl-fuzz simply killing the child, we can just wait until the pipe breaks. */ // 这是同一通道的另一侧。由于超时通过afl-fuzz简单地终止子进程来处理我们只需等待管道断开即可。
/* This is the other side of the same channel. Since timeouts are handled by
afl-fuzz simply killing the child, we can just wait until the pipe breaks. */
static void afl_wait_tsl(CPUState *cpu, int fd) { // 等待翻译请求
static void afl_wait_tsl(CPUState *cpu, int fd) {
struct afl_tsl t; // 请求结构体
TranslationBlock *tb; // 翻译块
struct afl_tsl t;
TranslationBlock *tb;
while (1) { // 无限循环
while (1) {
/* Broken pipe means it's time to return to the fork server routine. */ // 异常管道表示可以返回fork服务器例程
/* Broken pipe means it's time to return to the fork server routine. */
if (read(fd, &t, sizeof(struct afl_tsl)) != sizeof(struct afl_tsl)) // 读取请求数据
break; // 读取失败,退出循环
if (read(fd, &t, sizeof(struct afl_tsl)) != sizeof(struct afl_tsl))
break;
tb = tb_htable_lookup(cpu, t.pc, t.cs_base, t.flags); // 查找翻译块
tb = tb_htable_lookup(cpu, t.pc, t.cs_base, t.flags);
if(!tb) { // 如果没有找到翻译块
mmap_lock(); // 锁定内存映射
tb_lock(); // 锁定翻译块
tb_gen_code(cpu, t.pc, t.cs_base, t.flags, 0); // 生成新的翻译块
mmap_unlock(); // 解锁内存映射
tb_unlock(); // 解锁翻译块
if(!tb) {
mmap_lock();
tb_lock();
tb_gen_code(cpu, t.pc, t.cs_base, t.flags, 0);
mmap_unlock();
tb_unlock();
}
}
close(fd); // 关闭文件描述符
close(fd);
}

Loading…
Cancel
Save