Satori5ama 8 months ago
commit a1e241250d

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

@ -0,0 +1,24 @@
{
"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
}
]
}
]
}

@ -0,0 +1,11 @@
{
"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"
}
}

@ -4,6 +4,16 @@
"*.wpy": "vue", "*.wpy": "vue",
"*.wxml": "html", "*.wxml": "html",
"*.wxss": "css", "*.wxss": "css",
"string.h": "c" "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"
} }
} }

File diff suppressed because it is too large Load Diff

@ -36,414 +36,378 @@
*/ */
#define AFL_MAIN #define AFL_MAIN // 定义主程序宏
#include "config.h" #include "config.h" // 包含配置文件
#include "types.h" #include "types.h" // 包含类型定义
#include "debug.h" #include "debug.h" // 包含调试工具
#include "alloc-inl.h" #include "alloc-inl.h" // 包含内存分配工具
#include "afl-as.h" #include "afl-as.h" // 包含AFL汇编插桩头文件
#include <stdio.h> #include <stdio.h> // 标准输入输出
#include <unistd.h> #include <unistd.h> // POSIX标准库
#include <stdlib.h> #include <stdlib.h> // 标准库
#include <string.h> #include <string.h> // 字符串处理
#include <time.h> #include <time.h> // 时间处理
#include <ctype.h> #include <ctype.h> // 字符处理
#include <fcntl.h> #include <fcntl.h> // 文件控制
#include <sys/wait.h> #include <sys/wait.h> // 进程等待
#include <sys/time.h> #include <sys/time.h> // 时间处理
static u8** as_params; /* Parameters passed to the real 'as' */ static u8** as_params; // 传递给真实 'as' 的参数
static u8* input_file; /* Originally specified input file */ static u8* input_file; // 原始输入文件
static u8* modified_file; /* Instrumented file for the real 'as' */ static u8* modified_file; // 插桩后的文件
static u8 be_quiet, /* Quiet mode (no stderr output) */ static u8 be_quiet, // 静默模式(不输出错误信息)
clang_mode, /* Running in clang mode? */ clang_mode, // 是否在clang模式下运行
pass_thru, /* Just pass data through? */ pass_thru, // 是否直接传递数据
just_version, /* Just show version? */ just_version, // 是否只显示版本
sanitizer; /* Using ASAN / MSAN */ sanitizer; // 是否使用ASAN / MSAN
static u32 inst_ratio = 100, /* Instrumentation probability (%) */ static u32 inst_ratio = 100, // 插桩概率(%
as_par_cnt = 1; /* Number of params to 'as' */ as_par_cnt = 1; // 传递给 'as' 的参数数量
/* If we don't find --32 or --64 in the command line, default to // 如果命令行中没有找到 --32 或 --64 参数,则默认对编译该工具时使用的模式进行插桩。
instrumentation for whichever mode we were compiled with. This is not // 这不是完美的,但对于大多数使用场景来说已经足够了。
perfect, but should do the trick for almost all use cases. */
#ifdef WORD_SIZE_64 #ifdef WORD_SIZE_64 // 如果是64位系统
static u8 use_64bit = 1; static u8 use_64bit = 1; // 使用64位模式
#else #else // 否则
static u8 use_64bit = 0; static u8 use_64bit = 0; // 使用32位模式
#ifdef __APPLE__ #ifdef __APPLE__ // 如果是苹果系统
# error "Sorry, 32-bit Apple platforms are not supported." # error "Sorry, 32-bit Apple platforms are not supported." // 不支持32位苹果平台
#endif /* __APPLE__ */ #endif /* __APPLE__ */
#endif /* ^WORD_SIZE_64 */ #endif /* ^WORD_SIZE_64 */
/* Examine and modify parameters to pass to 'as'. Note that the file name // 检查并修改传递给 'as' 的参数。注意 GCC 总是将文件名作为最后一个参数传递给 'as'
is always the last parameter passed by GCC, so we exploit this property // 因此我们利用这一特性来简化代码。
to keep the code simple. */
static void edit_params(int argc, char** argv) { static void edit_params(int argc, char** argv) {
u8 *tmp_dir = getenv("TMPDIR"), *afl_as = getenv("AFL_AS"); u8 *tmp_dir = getenv("TMPDIR"), *afl_as = getenv("AFL_AS"); // 获取临时目录和AFL_AS环境变量
u32 i; u32 i; // 循环变量
#ifdef __APPLE__ #ifdef __APPLE__ // 如果是苹果系统
u8 use_clang_as = 0; u8 use_clang_as = 0; // 是否使用clang作为汇编器
/* On MacOS X, the Xcode cctool 'as' driver is a bit stale and does not work // 在 MacOS X 上Xcode cctool 'as' 驱动程序有点过时,无法处理由用户自己编译的较新版本的 clang
with the code generated by newer versions of clang that are hand-built // 生成的代码。详见此线程http://goo.gl/HBWDtn.
by the user. See the thread here: http://goo.gl/HBWDtn.
To work around this, when using clang and running without AFL_AS // 为了绕过这一问题,当使用 clang 且未指定 AFL_AS 时,我们将实际调用 'clang -c' 而不是 'as -q'
specified, we will actually call 'clang -c' instead of 'as -q' to // 来编译汇编文件.
compile the assembly file.
The tools aren't cmdline-compatible, but at least for now, we can // 虽然这两个工具不是命令行兼容的,但我们可以通过进行一些小的修改来让它们在某些情况下似乎可以很好地协同工作。
seemingly get away with this by making only very minor tweaks. Thanks // 感谢 Nico Weber 提出这一思路。
to Nico Weber for the idea. */
if (clang_mode && !afl_as) { if (clang_mode && !afl_as) { // 如果是clang模式且没有指定AFL_AS
use_clang_as = 1; use_clang_as = 1; // 使用clang作为汇编器
afl_as = getenv("AFL_CC"); afl_as = getenv("AFL_CC"); // 获取AFL_CC环境变量
if (!afl_as) afl_as = getenv("AFL_CXX"); if (!afl_as) afl_as = getenv("AFL_CXX"); // 如果没有AFL_CC则获取AFL_CXX
if (!afl_as) afl_as = "clang"; if (!afl_as) afl_as = "clang"; // 如果都没有则默认使用clang
} }
#endif /* __APPLE__ */ #endif /* __APPLE__ */
/* Although this is not documented, GCC also uses TEMP and TMP when TMPDIR // 虽然这在文档中没有提及,但 GCC 实际上也使用 TEMP 和 TMP当 TMPDIR 未设置时)。
is not set. We need to check these non-standard variables to properly // 我们需要检查这些非常规变量以正确处理 pass_thru 逻辑。
handle the pass_thru logic later on. */
if (!tmp_dir) tmp_dir = getenv("TEMP"); if (!tmp_dir) tmp_dir = getenv("TEMP"); // 如果没有TMPDIR则获取TEMP
if (!tmp_dir) tmp_dir = getenv("TMP"); if (!tmp_dir) tmp_dir = getenv("TMP"); // 如果没有TEMP则获取TMP
if (!tmp_dir) tmp_dir = "/tmp"; if (!tmp_dir) tmp_dir = "/tmp"; // 如果都没有,则默认使用/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"; as_params[0] = afl_as ? afl_as : (u8*)"as"; // 设置第一个参数为AFL_AS或默认的as
as_params[argc] = 0; as_params[argc] = 0; // 设置最后一个参数为NULL
for (i = 1; i < argc - 1; i++) { for (i = 1; i < argc - 1; i++) { // 遍历参数
if (!strcmp(argv[i], "--64")) use_64bit = 1; if (!strcmp(argv[i], "--64")) use_64bit = 1; // 如果参数是--64则使用64位模式
else if (!strcmp(argv[i], "--32")) use_64bit = 0; else if (!strcmp(argv[i], "--32")) use_64bit = 0; // 如果参数是--32则使用32位模式
#ifdef __APPLE__ #ifdef __APPLE__ // 如果是苹果系统
/* The Apple case is a bit different... */ // MacOS X 的情况有点不同...
if (!strcmp(argv[i], "-arch") && i + 1 < argc) { if (!strcmp(argv[i], "-arch") && i + 1 < argc) { // 如果参数是-arch
if (!strcmp(argv[i + 1], "x86_64")) use_64bit = 1; if (!strcmp(argv[i + 1], "x86_64")) use_64bit = 1; // 如果架构是x86_64则使用64位模式
else if (!strcmp(argv[i + 1], "i386")) else if (!strcmp(argv[i + 1], "i386")) // 如果架构是i386
FATAL("Sorry, 32-bit Apple platforms are not supported."); FATAL("Sorry, 32-bit Apple platforms are not supported."); // 不支持32位苹果平台
} }
/* Strip options that set the preference for a particular upstream // 移除 Xcode 中设置特定上游汇编器的选项
assembler in Xcode. */
if (clang_mode && (!strcmp(argv[i], "-q") || !strcmp(argv[i], "-Q"))) if (clang_mode && (!strcmp(argv[i], "-q") || !strcmp(argv[i], "-Q"))) // 如果是clang模式且参数是-q或-Q
continue; continue; // 跳过这些参数
#endif /* __APPLE__ */ #endif /* __APPLE__ */
as_params[as_par_cnt++] = argv[i]; as_params[as_par_cnt++] = argv[i]; // 将参数添加到as_params数组中
} }
#ifdef __APPLE__ #ifdef __APPLE__ // 如果是苹果系统
/* When calling clang as the upstream assembler, append -c -x assembler // 当调用 clang 作为上游汇编器时,追加 -c -x assembler 选项并希望一切顺利。
and hope for the best. */
if (use_clang_as) { if (use_clang_as) { // 如果使用clang作为汇编器
as_params[as_par_cnt++] = "-c"; as_params[as_par_cnt++] = "-c"; // 添加-c参数
as_params[as_par_cnt++] = "-x"; as_params[as_par_cnt++] = "-x"; // 添加-x参数
as_params[as_par_cnt++] = "assembler"; as_params[as_par_cnt++] = "assembler"; // 添加assembler参数
} }
#endif /* __APPLE__ */ #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")) { if (!strcmp(input_file + 1, "-version")) { // 如果参数是-version
just_version = 1; just_version = 1; // 设置只显示版本
modified_file = input_file; modified_file = input_file; // 设置修改后的文件为输入文件
goto wrap_things_up; goto wrap_things_up; // 跳转到结束处理
} }
if (input_file[1]) FATAL("Incorrect use (not called through afl-gcc?)"); if (input_file[1]) FATAL("Incorrect use (not called through afl-gcc?)"); // 如果输入文件不是标准输入
else input_file = NULL; else input_file = NULL; // 否则设置为NULL
} else { } else { // 如果输入文件不是标准输入
/* Check if this looks like a standard invocation as a part of an attempt // 检查是否为标准调用,作为编译程序的一部分,而不是使用 gcc 对一个独立的 .s 文件进行编译。
to compile a program, rather than using gcc on an ad-hoc .s file in // 这解决了在编译 NSS 时遇到的问题。
a format we may not understand. This works around an issue compiling
NSS. */
if (strncmp(input_file, tmp_dir, strlen(tmp_dir)) && if (strncmp(input_file, tmp_dir, strlen(tmp_dir)) && // 如果输入文件不在临时目录
strncmp(input_file, "/var/tmp/", 9) && strncmp(input_file, "/var/tmp/", 9) && // 且不在/var/tmp/
strncmp(input_file, "/tmp/", 5)) pass_thru = 1; strncmp(input_file, "/tmp/", 5)) pass_thru = 1; // 且不在/tmp/则设置为pass_thru模式
} }
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)); (u32)time(NULL));
wrap_things_up: wrap_things_up: // 结束处理
as_params[as_par_cnt++] = modified_file; as_params[as_par_cnt++] = modified_file; // 将修改后的文件添加到参数数组
as_params[as_par_cnt] = NULL; as_params[as_par_cnt] = NULL; // 设置最后一个参数为NULL
} }
/* Process input file, generate modified_file. Insert instrumentation in all // 处理输入文件并生成 modified_file。在所有适当的位置插入插桩代码。
the appropriate places. */
static void add_instrumentation(void) { static void add_instrumentation(void) {
static u8 line[MAX_LINE]; static u8 line[MAX_LINE]; // 读取文件的缓冲区
FILE* inf; FILE* inf; // 输入文件指针
FILE* outf; FILE* outf; // 输出文件指针
s32 outfd; s32 outfd; // 输出文件描述符
u32 ins_lines = 0; 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; skip_intel = 0, skip_app = 0, instrument_next = 0;
#ifdef __APPLE__ #ifdef __APPLE__ // 如果是苹果系统
u8* colon_pos; u8* colon_pos; // 冒号位置
#endif /* __APPLE__ */ #endif /* __APPLE__ */
if (input_file) { if (input_file) { // 如果有输入文件
inf = fopen(input_file, "r"); inf = fopen(input_file, "r"); // 打开输入文件
if (!inf) PFATAL("Unable to read '%s'", input_file); 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)) { // 逐行读取输入文件
/* In some cases, we want to defer writing the instrumentation trampoline // 在某些情况下,我们希望在所有标签、宏、注释等之后再插入插桩跳板代码。
until after all the labels, macros, comments, etc. If we're in this // 如果处于这一模式,且行以制表符开头,后跟一个字母,则现在插入跳板代码。
mode, and if the line starts with a tab followed by a character, dump
the trampoline now. */
if (!pass_thru && !skip_intel && !skip_app && !skip_csect && instr_ok && 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)); R(MAP_SIZE));
instrument_next = 0; instrument_next = 0; // 重置插桩标志
ins_lines++; ins_lines++; // 增加插桩行数
} }
/* Output the actual line, call it a day in pass-thru mode. */ // 输出实际的行,在 pass-thru 模式下结束操作。
fputs(line, outf); fputs(line, outf); // 输出当前行
if (pass_thru) continue; if (pass_thru) continue; // 如果是pass-thru模式则跳过
/* All right, this is where the actual fun begins. For one, we only want to // 现在开始真正的插桩操作。首先,我们只希望插桩 .text 部分。
instrument the .text section. So, let's keep track of that in processed // 因此,我们需要跟踪处理的汇编文件中各部分的状态,并据此设置 instr_ok。
files - and let's set instr_ok accordingly. */
if (line[0] == '\t' && line[1] == '.') { if (line[0] == '\t' && line[1] == '.') { // 如果行以制表符和点开头
/* OpenBSD puts jump tables directly inline with the code, which is // OpenBSD 在代码中直接放置跳转表,这稍微有点麻烦。
a bit annoying. They use a specific format of p2align directives // 它们使用特定格式的 p2align 指令围绕它们,因此我们可以使用该格式作为信号。
around them, so we use that as a signal. */
if (!clang_mode && instr_ok && !strncmp(line + 2, "p2align ", 8) && if (!clang_mode && instr_ok && !strncmp(line + 2, "p2align ", 8) && // 如果是OpenBSD的p2align指令
isdigit(line[10]) && line[11] == '\n') skip_next_label = 1; isdigit(line[10]) && line[11] == '\n') skip_next_label = 1; // 设置跳过下一个标签
if (!strncmp(line + 2, "text\n", 5) || if (!strncmp(line + 2, "text\n", 5) || // 如果是.text部分
!strncmp(line + 2, "section\t.text", 13) || !strncmp(line + 2, "section\t.text", 13) || // 或者section .text
!strncmp(line + 2, "section\t__TEXT,__text", 21) || !strncmp(line + 2, "section\t__TEXT,__text", 21) || // 或者section __TEXT,__text
!strncmp(line + 2, "section __TEXT,__text", 21)) { !strncmp(line + 2, "section __TEXT,__text", 21)) { // 或者section __TEXT,__text
instr_ok = 1; instr_ok = 1; // 设置插桩标志
continue; continue; // 继续
} }
if (!strncmp(line + 2, "section\t", 8) || if (!strncmp(line + 2, "section\t", 8) || // 如果是其他section
!strncmp(line + 2, "section ", 8) || !strncmp(line + 2, "section ", 8) || // 或者其他section
!strncmp(line + 2, "bss\n", 4) || !strncmp(line + 2, "bss\n", 4) || // 或者.bss部分
!strncmp(line + 2, "data\n", 5)) { !strncmp(line + 2, "data\n", 5)) { // 或者.data部分
instr_ok = 0; instr_ok = 0; // 重置插桩标志
continue; continue; // 继续
} }
} }
/* Detect off-flavor assembly (rare, happens in gdb). When this is // 检测非常规汇编(罕见,例如在 gdb 中)。当遇到这种汇编时,我们设置 skip_csect
encountered, we set skip_csect until the opposite directive is // 直到遇到相反的指令,此时我们不进行插桩。
seen, and we do not instrument. */
if (strstr(line, ".code")) { if (strstr(line, ".code")) { // 如果行包含.code
if (strstr(line, ".code32")) skip_csect = use_64bit; if (strstr(line, ".code32")) skip_csect = use_64bit; // 如果是.code32则根据64位模式设置skip_csect
if (strstr(line, ".code64")) skip_csect = !use_64bit; if (strstr(line, ".code64")) skip_csect = !use_64bit; // 如果是.code64则根据64位模式设置skip_csect
} }
/* Detect syntax changes, as could happen with hand-written assembly. // 检测并跳过手写汇编块__asm__同样不进行插桩。
Skip Intel blocks, resume instrumentation when back to AT&T. */
if (strstr(line, ".intel_syntax")) skip_intel = 1; if (line[0] == '#' || line[1] == '#') { // 如果行以#开头
if (strstr(line, ".att_syntax")) skip_intel = 0;
/* Detect and skip ad-hoc __asm__ blocks, likewise skipping them. */ if (strstr(line, "#APP")) skip_app = 1; // 如果包含#APP则设置skip_app
if (strstr(line, "#NO_APP")) skip_app = 0; // 如果包含#NO_APP则重置skip_app
if (line[0] == '#' || line[1] == '#') {
if (strstr(line, "#APP")) skip_app = 1;
if (strstr(line, "#NO_APP")) skip_app = 0;
} }
/* If we're in the right mood for instrumenting, check for function // 如果我们处于插桩模式,检查函数名或条件标签。这里逻辑有些复杂,但基本目标是捕获:
names or conditional labels. This is a bit messy, but in essence,
we want to catch:
^main: - function entry point (always instrumented) // ^main: - 函数入口点(总是插桩)
^.L0: - GCC branch label // ^.L0: - GCC 分支标签
^.LBB0_0: - clang branch label (but only in clang mode) // ^.LBB0_0: - clang 分支标签(但仅在 clang 模式下)
^\tjnz foo - conditional branches // ^\tjnz foo - 条件分支
...but not: // ...而不捕获:
^# BB#0: - clang comments // ^# BB#0: - clang 注释
^ # BB#0: - ditto // ^ # BB#0: - 同上
^.Ltmp0: - clang non-branch labels // ^.Ltmp0: - clang 非分支标签
^.LC0 - GCC non-branch labels // ^.LC0 - GCC 非分支标签
^.LBB0_0: - ditto (when in GCC mode) // ^.LBB0_0: - 同上(当处于 GCC 模式下)
^\tjmp foo - non-conditional jumps // ^\tjmp foo - 非条件跳转
Additionally, clang and GCC on MacOS X follow a different convention // 此外MacOS X 上的 clang 和 GCC 使用不同的标签格式,没有前导点,因此我们根据这一情况处理。
with no leading dots on labels, hence the weird maze of #ifdefs
later on.
*/ if (skip_intel || skip_app || skip_csect || !instr_ok || // 如果跳过插桩
line[0] == '#' || line[0] == ' ') continue; // 或者行以#或空格开头,则跳过
if (skip_intel || skip_app || skip_csect || !instr_ok || // 条件分支指令jnz 等)。我们会在分支之后插入插桩(以插桩不执行路径),
line[0] == '#' || line[0] == ' ') continue; // 并在分支目标标签处插入(稍后处理)。
/* Conditional branch instruction (jnz, etc). We append the instrumentation if (line[0] == '\t') { // 如果行以制表符开头
right after the branch (to instrument the not-taken path) and at the
branch destination label (handled later on). */
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)); R(MAP_SIZE));
ins_lines++; ins_lines++; // 增加插桩行数
} }
continue; continue; // 继续
} }
/* Label of some sort. This may be a branch destination, but we need to // 某类标签。这可能是分支目标,但我们需要小心处理不同的格式约定。
tread carefully and account for several different formatting
conventions. */
#ifdef __APPLE__ #ifdef __APPLE__ // 如果是苹果系统
/* Apple: 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))) { if (line[0] == 'L' && isdigit(*(colon_pos - 1))) { // 如果标签以L开头且冒号前是数字
#else #else // 否则
/* Everybody else: .L<whatever>: */ // 其他人:.L<whatever>:
if (strstr(line, ":")) { if (strstr(line, ":")) { // 如果行包含冒号
if (line[0] == '.') { if (line[0] == '.') { // 如果标签以点开头
#endif /* __APPLE__ */ #endif /* __APPLE__ */
/* .L0: or LBB0_0: style jump destination */ // .L0: 或 LBB0_0: 风格的分支目标
#ifdef __APPLE__ #ifdef __APPLE__ // 如果是苹果系统
/* Apple: L<num> / LBB<num> */ // MacOS X: L<num> / LBB<num>
if ((isdigit(line[1]) || (clang_mode && !strncmp(line, "LBB", 3))) if ((isdigit(line[1]) || (clang_mode && !strncmp(line, "LBB", 3))) // 如果标签是L<num>或LBB<num>
&& R(100) < inst_ratio) { && R(100) < inst_ratio) { // 并且随机数小于插桩概率
#else #else // 否则
/* Apple: .L<num> / .LBB<num> */ // MacOS X: .L<num> / .LBB<num>
if ((isdigit(line[2]) || (clang_mode && !strncmp(line + 1, "LBB", 3))) if ((isdigit(line[2]) || (clang_mode && !strncmp(line + 1, "LBB", 3))) // 如果标签是.L<num>或.LBB<num>
&& R(100) < inst_ratio) { && R(100) < inst_ratio) { // 并且随机数小于插桩概率
#endif /* __APPLE__ */ #endif /* __APPLE__ */
/* An optimization is possible here by adding the code only if the // 在仅需要在标签被引用时(非调用/跳转上下文)才添加代码的情况下可以进行优化。
label is mentioned in the code in contexts other than call / jmp. // 这会引入两遍处理过程的复杂性(当使用 stdin 时尤其麻烦),并且通常只能带来不到 10% 的速度提升。
That said, this complicates the code by requiring two-pass // 因为编译器通常不会生成不相关的函数内跳转。
processing (messy with stdin), and results in a speed gain
typically under 10%, because compilers are generally pretty good
about not generating spurious intra-function jumps.
We use deferred output chiefly to avoid disrupting // 我们使用延迟输出主要是为了避免干扰 MacOS X 上 .Lfunc_begin0 风格异常处理计算的问题。
.Lfunc_begin0-style exception handling calculations (a problem on
MacOS X). */
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 { // 否则
/* Function label (always instrumented, deferred mode). */ // 函数标签(总是插桩,延迟模式)。
instrument_next = 1; instrument_next = 1; // 设置插桩标志
} }
@ -451,17 +415,17 @@ static void add_instrumentation(void) {
} }
if (ins_lines) if (ins_lines) // 如果有插桩行
fputs(use_64bit ? main_payload_64 : main_payload_32, outf); fputs(use_64bit ? main_payload_64 : main_payload_32, outf); // 插入主插桩代码
if (input_file) fclose(inf); if (input_file) fclose(inf); // 关闭输入文件
fclose(outf); 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)" : ""); 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", ins_lines, use_64bit ? "64" : "32",
getenv("AFL_HARDEN") ? "hardened" : getenv("AFL_HARDEN") ? "hardened" :
(sanitizer ? "ASAN/MSAN" : "non-hardened"), (sanitizer ? "ASAN/MSAN" : "non-hardened"),
@ -472,27 +436,27 @@ static void add_instrumentation(void) {
} }
/* Main entry point */ // 程序主入口点
int main(int argc, char** argv) { int main(int argc, char** argv) {
s32 pid; s32 pid; // 进程ID
u32 rand_seed; u32 rand_seed; // 随机种子
int status; int status; // 进程状态
u8* inst_ratio_str = getenv("AFL_INST_RATIO"); u8* inst_ratio_str = getenv("AFL_INST_RATIO"); // 获取插桩概率环境变量
struct timeval tv; struct timeval tv; // 时间结构
struct timezone tz; struct timezone tz; // 时区结构
clang_mode = !!getenv(CLANG_ENV_VAR); clang_mode = !!getenv(CLANG_ENV_VAR); // 设置clang模式
if (isatty(2) && !getenv("AFL_QUIET")) { if (isatty(2) && !getenv("AFL_QUIET")) { // 如果是终端且没有设置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) { if (argc < 2) { // 如果参数少于2个
SAYF("\n" SAYF("\n"
"This is a helper application for afl-fuzz. It is a wrapper around GNU 'as',\n" "This is a helper application for afl-fuzz. It is a wrapper around GNU 'as',\n"
@ -501,57 +465,55 @@ int main(int argc, char** argv) {
"Rarely, when dealing with extremely complex projects, it may be advisable to\n" "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" "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) 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)"); FATAL("Bad value of AFL_INST_RATIO (must be between 0 and 100)"); // 报错
} }
if (getenv(AS_LOOP_ENV_VAR)) if (getenv(AS_LOOP_ENV_VAR)) // 如果设置了AS_LOOP_ENV_VAR
FATAL("Endless loop when calling 'as' (remove '.' from your PATH)"); FATAL("Endless loop when calling 'as' (remove '.' from your PATH)"); // 报错
setenv(AS_LOOP_ENV_VAR, "1", 1); setenv(AS_LOOP_ENV_VAR, "1", 1); // 设置AS_LOOP_ENV_VAR
/* When compiling with ASAN, we don't have a particularly elegant way to skip // 使用 ASAN 时,我们没有特别优雅的方法来跳过 ASAN 特定的分支。
ASAN-specific branches. But we can probabilistically compensate for // 但可以通过按概率补偿来解决这个问题...
that... */
if (getenv("AFL_USE_ASAN") || getenv("AFL_USE_MSAN")) { if (getenv("AFL_USE_ASAN") || getenv("AFL_USE_MSAN")) { // 如果使用ASAN或MSAN
sanitizer = 1; sanitizer = 1; // 设置sanitizer标志
inst_ratio /= 3; 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); execvp(as_params[0], (char**)as_params); // 执行as命令
FATAL("Oops, failed to execute '%s' - check your PATH", as_params[0]); FATAL("Oops, failed to execute '%s' - check your PATH", as_params[0]); // 如果执行失败则报错
} }
if (pid < 0) PFATAL("fork() failed"); if (pid < 0) PFATAL("fork() failed"); // 如果fork失败则报错
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); if (!getenv("AFL_KEEP_ASSEMBLY")) unlink(modified_file); // 如果没有设置AFL_KEEP_ASSEMBLY则删除修改后的文件
exit(WEXITSTATUS(status)); exit(WEXITSTATUS(status)); // 退出
} }

File diff suppressed because it is too large Load Diff

@ -1,89 +1,91 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# #
# american fuzzy lop - corpus minimization tool # American Fuzzy Lop - 语料库最小化工具
# --------------------------------------------- # ---------------------------------------------
# #
# Written and maintained by Michal Zalewski <lcamtuf@google.com> # 作者和维护者:Michal Zalewski <lcamtuf@google.com>
# #
# Copyright 2014, 2015 Google LLC All rights reserved. # 版权所有 2014, 2015 Google LLC 保留所有权利。
# #
# 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: # 您可以从以下网址获取许可证的副本:
# #
# http://www.apache.org/licenses/LICENSE-2.0 # 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: # 这有两个用途:
# #
# - Screening large corpora of input files before using them as a seed for # - 在将大的输入文件用作 afl-fuzz 的种子之前筛选。
# afl-fuzz. The tool will remove functionally redundant files and likely # 该工具将删除功能上冗余的文件,并可能
# leave you with a much smaller set. # 留下一个更小的集合。
# #
# (In this case, you probably also want to consider running afl-tmin on # (在这种情况下,您可能还想考虑稍后对
# the individual files later on to reduce their size.) # 各个文件运行 afl-tmin 以减少其大小。)
# #
# - Minimizing the corpus generated organically by afl-fuzz, perhaps when # - 最小化由 afl-fuzz 自然生成的语料库,
# 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. # 但这些条目已被后来的结果取代。
# #
# Note that the tool doesn't modify the files themselves. For that, you want # 请注意,该工具不会修改文件本身。
# afl-tmin. # 对于此,您希望使用 afl-tmin。
# #
# This script must use bash because other shells may have hardcoded limits on # 此脚本必须使用 bash因为其他 shell 可能对
# array sizes. # 数组大小有硬编码限制。
# #
echo "corpus minimization tool for afl-fuzz by <lcamtuf@google.com>" echo "为 afl-fuzz 提供的语料库最小化工具 <lcamtuf@google.com>" # 输出程序名称
echo echo
######### #########
# SETUP # # 设置 #
######### #########
# Process command-line options... # 处理命令行选项...
MEM_LIMIT=100 MEM_LIMIT=100 # 内存限制初始值为 100 MB
TIMEOUT=none TIMEOUT=none # 超时初始值为无
# 取消设置以下变量
unset IN_DIR OUT_DIR STDIN_FILE EXTRA_PAR MEM_LIMIT_GIVEN \ unset IN_DIR OUT_DIR STDIN_FILE EXTRA_PAR MEM_LIMIT_GIVEN \
AFL_CMIN_CRASHES_ONLY AFL_CMIN_ALLOW_ANY QEMU_MODE AFL_CMIN_CRASHES_ONLY AFL_CMIN_ALLOW_ANY QEMU_MODE
# 解析命令行选项
while getopts "+i:o:f:m:t:eQC" opt; do while getopts "+i:o:f:m:t:eQC" opt; do
case "$opt" in case "$opt" in
"i") "i") # 输入目录选项
IN_DIR="$OPTARG" IN_DIR="$OPTARG"
;; ;;
"o") "o") # 输出目录选项
OUT_DIR="$OPTARG" OUT_DIR="$OPTARG"
;; ;;
"f") "f") # 从中读取的模糊程序位置(标准输入)
STDIN_FILE="$OPTARG" STDIN_FILE="$OPTARG"
;; ;;
"m") "m") # 内存限制
MEM_LIMIT="$OPTARG" MEM_LIMIT="$OPTARG"
MEM_LIMIT_GIVEN=1 MEM_LIMIT_GIVEN=1
;; ;;
"t") "t") # 超时时间
TIMEOUT="$OPTARG" TIMEOUT="$OPTARG"
;; ;;
"e") "e") # 额外参数
EXTRA_PAR="$EXTRA_PAR -e" EXTRA_PAR="$EXTRA_PAR -e"
;; ;;
"C") "C") # 仅保留崩溃输入
export AFL_CMIN_CRASHES_ONLY=1 export AFL_CMIN_CRASHES_ONLY=1
;; ;;
"Q") "Q") # 使用仅二进制的仪器QEMU 模式)
EXTRA_PAR="$EXTRA_PAR -Q" EXTRA_PAR="$EXTRA_PAR -Q"
test "$MEM_LIMIT_GIVEN" = "" && MEM_LIMIT=250 test "$MEM_LIMIT_GIVEN" = "" && MEM_LIMIT=250
QEMU_MODE=1 QEMU_MODE=1
;; ;;
"?") "?") # 无效选项
exit 1 exit 1
;; ;;
@ -91,84 +93,84 @@ while getopts "+i:o:f:m:t:eQC" opt; do
done done
shift $((OPTIND-1)) shift $((OPTIND-1)) # 移动位置参数
TARGET_BIN="$1" TARGET_BIN="$1" # 目标二进制文件
# 检查必需参数是否缺失
if [ "$TARGET_BIN" = "" -o "$IN_DIR" = "" -o "$OUT_DIR" = "" ]; then if [ "$TARGET_BIN" = "" -o "$IN_DIR" = "" -o "$OUT_DIR" = "" ]; then
# 输出用法信息到标准错误
cat 1>&2 <<_EOF_ cat 1>&2 <<_EOF_
Usage: $0 [ options ] -- /path/to/target_app [ ... ] 使用: $0 [选项] -- /path/to/target_app [ ... ]
Required parameters: 所需参数:
-i dir - input directory with the starting corpus -i dir - 包含起始语料库的输入目录
-o dir - output directory for minimized files -o dir - 最小化文件的输出目录
Execution control settings: 执行控制设置:
-f file - location read by the fuzzed program (stdin) -f file - 由模糊程序读取的位置(标准输入)
-m megs - memory limit for child process ($MEM_LIMIT MB) -m megs - 子进程的内存限制($MEM_LIMIT MB
-t msec - run time limit for child process (none) -t msec - 子进程的运行时间限制(无)
-Q - use binary-only instrumentation (QEMU mode) -Q - 使用仅二进制的仪器QEMU 模式)
Minimization settings: 最小化设置:
-C - keep crashing inputs, reject everything else -C - 保留崩溃输入,拒绝其他所有内容
-e - solve for edge coverage only, ignore hit counts -e - 仅解决边缘覆盖,忽略命中计数
For additional tips, please consult docs/README. 有关其他提示,请参阅 docs/README。
_EOF_ _EOF_
exit 1 exit 1
fi fi
# Do a sanity check to discourage the use of /tmp, since we can't really # 进行完整性检查,避免使用 /tmp因为我们无法安全处理它。
# handle this safely from a shell script.
if [ "$AFL_ALLOW_TMP" = "" ]; then if [ "$AFL_ALLOW_TMP" = "" ]; then
echo "$IN_DIR" | grep -qE '^(/var)?/tmp/' echo "$IN_DIR" | grep -qE '^(/var)?/tmp/' # 检查输入目录是否在/tmp
T1="$?" T1="$?"
echo "$TARGET_BIN" | grep -qE '^(/var)?/tmp/' echo "$TARGET_BIN" | grep -qE '^(/var)?/tmp/' # 检查目标二进制文件是否在/tmp
T2="$?" T2="$?"
echo "$OUT_DIR" | grep -qE '^(/var)?/tmp/' echo "$OUT_DIR" | grep -qE '^(/var)?/tmp/' # 检查输出目录是否在/tmp
T3="$?" T3="$?"
echo "$STDIN_FILE" | grep -qE '^(/var)?/tmp/' echo "$STDIN_FILE" | grep -qE '^(/var)?/tmp/' # 检查标准输入文件是否在/tmp
T4="$?" T4="$?"
echo "$PWD" | grep -qE '^(/var)?/tmp/' echo "$PWD" | grep -qE '^(/var)?/tmp/' # 检查当前工作目录是否在/tmp
T5="$?" T5="$?"
if [ "$T1" = "0" -o "$T2" = "0" -o "$T3" = "0" -o "$T4" = "0" -o "$T5" = "0" ]; then if [ "$T1" = "0" -o "$T2" = "0" -o "$T3" = "0" -o "$T4" = "0" -o "$T5" = "0" ]; then
echo "[-] Error: do not use this script in /tmp or /var/tmp." 1>&2 echo "[-] 错误: 请勿在 /tmp 或 /var/tmp 中使用此脚本。" 1>&2
exit 1 exit 1
fi fi
fi fi
# If @@ is specified, but there's no -f, let's come up with a temporary input # 如果指定了 @@,但没有 -f创建一个临时输入文件名。
# file name.
TRACE_DIR="$OUT_DIR/.traces" TRACE_DIR="$OUT_DIR/.traces"
if [ "$STDIN_FILE" = "" ]; then if [ "$STDIN_FILE" = "" ]; then
if echo "$*" | grep -qF '@@'; then if echo "$*" | grep -qF '@@'; then
STDIN_FILE="$TRACE_DIR/.cur_input" STDIN_FILE="$TRACE_DIR/.cur_input" # 使用当前输入文件名
fi fi
fi fi
# Check for obvious errors. # 检查明显的错误。
if [ ! "$MEM_LIMIT" = "none" ]; then if [ ! "$MEM_LIMIT" = "none" ]; then
if [ "$MEM_LIMIT" -lt "5" ]; then if [ "$MEM_LIMIT" -lt "5" ]; then
echo "[-] Error: dangerously low memory limit." 1>&2 echo "[-] 错误: 内存限制过低。" 1>&2
exit 1 exit 1
fi fi
@ -177,7 +179,7 @@ fi
if [ ! "$TIMEOUT" = "none" ]; then if [ ! "$TIMEOUT" = "none" ]; then
if [ "$TIMEOUT" -lt "10" ]; then if [ "$TIMEOUT" -lt "10" ]; then
echo "[-] Error: dangerously low timeout." 1>&2 echo "[-] 错误: 超时过低。" 1>&2
exit 1 exit 1
fi fi
@ -185,92 +187,91 @@ fi
if [ ! -f "$TARGET_BIN" -o ! -x "$TARGET_BIN" ]; then 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 if [ ! -f "$TNEW" -o ! -x "$TNEW" ]; then
echo "[-] Error: binary '$TARGET_BIN' not found or not executable." 1>&2 echo "[-] 错误: 未找到或不可执行的二进制文件 '$TARGET_BIN'。" 1>&2
exit 1 exit 1
fi fi
TARGET_BIN="$TNEW" TARGET_BIN="$TNEW" # 更新目标二进制文件路径
fi fi
if [ "$AFL_SKIP_BIN_CHECK" = "" -a "$QEMU_MODE" = "" ]; then if [ "$AFL_SKIP_BIN_CHECK" = "" -a "$QEMU_MODE" = "" ]; then
if ! grep -qF "__AFL_SHM_ID" "$TARGET_BIN"; then if ! grep -qF "__AFL_SHM_ID" "$TARGET_BIN"; then
echo "[-] Error: binary '$TARGET_BIN' doesn't appear to be instrumented." 1>&2 echo "[-] 错误: 二进制文件 '$TARGET_BIN' 似乎没有被仪器化。" 1>&2
exit 1 exit 1
fi fi
fi fi
if [ ! -d "$IN_DIR" ]; then if [ ! -d "$IN_DIR" ]; then
echo "[-] Error: directory '$IN_DIR' not found." 1>&2 echo "[-] 错误: 目录 '$IN_DIR' 未找到。" 1>&2
exit 1 exit 1
fi 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 find "$OUT_DIR" -name 'id[:_]*' -maxdepth 1 -exec rm -- {} \; 2>/dev/null # 删除输出目录中的旧轨迹
rm -rf "$TRACE_DIR" 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 if [ -d "$OUT_DIR" ]; then
echo "[-] Error: directory '$OUT_DIR' exists and is not empty - delete it first." 1>&2 echo "[-] 错误: 目录 '$OUT_DIR' 已存在且非空 - 请先删除它。" 1>&2
exit 1 exit 1
fi fi
mkdir -m 700 -p "$TRACE_DIR" || exit 1 mkdir -m 700 -p "$TRACE_DIR" || exit 1 # 创建临时轨迹目录并设置权限
if [ ! "$STDIN_FILE" = "" ]; then if [ ! "$STDIN_FILE" = "" ]; then
rm -f "$STDIN_FILE" || exit 1 rm -f "$STDIN_FILE" || exit 1 # 删除旧的标准输入文件
touch "$STDIN_FILE" || exit 1 touch "$STDIN_FILE" || exit 1 # 创建新的标准输入文件
fi fi
if [ "$AFL_PATH" = "" ]; then if [ "$AFL_PATH" = "" ]; then
SHOWMAP="${0%/afl-cmin}/afl-showmap" SHOWMAP="${0%/afl-cmin}/afl-showmap" # 设置 afl-showmap 的路径
else else
SHOWMAP="$AFL_PATH/afl-showmap" SHOWMAP="$AFL_PATH/afl-showmap" # 使用 AFL_PATH 中指定的路径
fi fi
if [ ! -x "$SHOWMAP" ]; then if [ ! -x "$SHOWMAP" ]; then
echo "[-] Error: can't find 'afl-showmap' - please set AFL_PATH." 1>&2 echo "[-] 错误: 找不到 'afl-showmap' - 请设置 AFL_PATH。" 1>&2
rm -rf "$TRACE_DIR" rm -rf "$TRACE_DIR" # 删除临时轨迹目录
exit 1 exit 1
fi 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 if [ "$IN_COUNT" = "0" ]; then
echo "[+] Hmm, no inputs in the target directory. Nothing to be done." echo "[+] 嗯,目标目录中没有输入。无需处理。"
rm -rf "$TRACE_DIR" rm -rf "$TRACE_DIR" # 删除临时轨迹目录
exit 1 exit 1
fi 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 if [ -d "$IN_DIR/$FIRST_FILE" ]; then
echo "[-] Error: The target directory contains subdirectories - please fix." 1>&2 echo "[-] 错误: 目标目录包含子目录 - 请修复。" 1>&2
rm -rf "$TRACE_DIR" rm -rf "$TRACE_DIR" # 删除临时轨迹目录
exit 1 exit 1
fi fi
# Check for the more efficient way to copy files... # 检查复制文件的更有效方法...
if ln "$IN_DIR/$FIRST_FILE" "$TRACE_DIR/.link_test" 2>/dev/null; then if ln "$IN_DIR/$FIRST_FILE" "$TRACE_DIR/.link_test" 2>/dev/null; then
CP_TOOL=ln CP_TOOL=ln # 如果可以链接,则设置复制工具为 ln
else else
CP_TOOL=cp CP_TOOL=cp # 否则使用 cp
fi fi
# Make sure that we can actually get anything out of afl-showmap before we # 确保我们能从 afl-showmap 中获取任何信息,以免浪费时间。
# waste too much time.
echo "[*] Testing the target binary..." echo "[*] 测试目标二进制文件..."
if [ "$STDIN_FILE" = "" ]; then if [ "$STDIN_FILE" = "" ]; then
@ -278,43 +279,42 @@ if [ "$STDIN_FILE" = "" ]; then
else 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 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 fi
FIRST_COUNT=$((`grep -c . "$TRACE_DIR/.run_test"`)) FIRST_COUNT=$((`grep -c . "$TRACE_DIR/.run_test"`)) # 计算运行测试的输出
if [ "$FIRST_COUNT" -gt "0" ]; then if [ "$FIRST_COUNT" -gt "0" ]; then
echo "[+] OK, $FIRST_COUNT tuples recorded." echo "[+] 好的,记录了 $FIRST_COUNT 个元组。"
else else
echo "[-] Error: no instrumentation output detected (perhaps crash or timeout)." 1>&2 echo "[-] 错误: 未检测到仪器输出(可能崩溃或超时)。" 1>&2
test "$AFL_KEEP_TRACES" = "" && rm -rf "$TRACE_DIR" test "$AFL_KEEP_TRACES" = "" && rm -rf "$TRACE_DIR" # 删除临时轨迹
exit 1 exit 1
fi fi
# Let's roll! # 开始工作!
############################# #############################
# STEP 1: COLLECTING TRACES # # 步骤 1收集轨迹 #
############################# #############################
echo "[*] Obtaining traces for input files in '$IN_DIR'..." echo "[*] 获取 '$IN_DIR' 中输入文件的轨迹..."
( (
CUR=0 CUR=0 # 当前文件计数器
if [ "$STDIN_FILE" = "" ]; then if [ "$STDIN_FILE" = "" ]; then
while read -r fn; do while read -r fn; do # 逐行读取输入文件名
CUR=$((CUR+1)) CUR=$((CUR+1))
printf "\\r Processing file $CUR/$IN_COUNT... " printf "\\r 正在处理文件 $CUR/$IN_COUNT... " # 输出当前进度
"$SHOWMAP" -m "$MEM_LIMIT" -t "$TIMEOUT" -o "$TRACE_DIR/$fn" -Z $EXTRA_PAR -- "$@" <"$IN_DIR/$fn" "$SHOWMAP" -m "$MEM_LIMIT" -t "$TIMEOUT" -o "$TRACE_DIR/$fn" -Z $EXTRA_PAR -- "$@" <"$IN_DIR/$fn"
@ -322,18 +322,16 @@ echo "[*] Obtaining traces for input files in '$IN_DIR'..."
else else
while read -r fn; do while read -r fn; do # 逐行读取输入文件名
CUR=$((CUR+1)) CUR=$((CUR+1))
printf "\\r Processing file $CUR/$IN_COUNT... " printf "\\r 正在处理文件 $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 "$SHOWMAP" -m "$MEM_LIMIT" -t "$TIMEOUT" -o "$TRACE_DIR/$fn" -Z $EXTRA_PAR -A "$STDIN_FILE" -- "$@" </dev/null
done < <(ls "$IN_DIR") done < <(ls "$IN_DIR")
fi fi
) )
@ -341,121 +339,117 @@ echo "[*] Obtaining traces for input files in '$IN_DIR'..."
echo echo
########################## ##########################
# STEP 2: SORTING TUPLES # # 步骤 2排序元组 #
########################## ##########################
# 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 "[*] Sorting trace sets (this may take a while)..." echo "[*] 排序轨迹集(这可能需要一段时间)..."
ls "$IN_DIR" | sed "s#^#$TRACE_DIR/#" | tr '\n' '\0' | xargs -0 -n 1 cat | \ 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 "[+] Found $TUPLE_COUNT unique tuples across $IN_COUNT files." echo "[+] 找到 $TUPLE_COUNT 个唯一元组,遍历了 $IN_COUNT 个文件。"
##################################### #####################################
# STEP 3: SELECTING CANDIDATE FILES # # 步骤 3选择候选文件 #
##################################### #####################################
# 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 # 而这些算法在 shell 脚本中仍然可以执行。
# a shell script.
echo "[*] Finding best candidates for each tuple..." echo "[*] 寻找每个元组的最佳候选者..."
CUR=0 CUR=0
while read -r fn; do while read -r fn; do # 逐行读取输入文件名
CUR=$((CUR+1)) CUR=$((CUR+1))
printf "\\r Processing file $CUR/$IN_COUNT... " printf "\\r 正在处理文件 $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 echo
############################## ##############################
# STEP 4: LOADING CANDIDATES # # 步骤 4加载候选 #
############################## ##############################
# 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 # (由于 ls -rS 的结果)。通过仅按元组排序 (-k 1,1)
# only by tuple (-k 1,1) and configured to output only the first line for # 并配置为对每个键的第一个输出行 (-s -u)
# every key (-s -u), we end up with the smallest file for each tuple. # 我们最终得到了每个元组的最小文件。
echo "[*] Sorting candidate list (be patient)..." echo "[*] 排序候选列表(耐心等候)..."
sort -k1,1 -s -u "$TRACE_DIR/.candidate_list" | \ 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 if [ ! -s "$TRACE_DIR/.candidate_script" ]; then
echo "[-] Error: no traces obtained from test cases, check syntax!" 1>&2 echo "[-] 错误: 从测试用例中未获得轨迹,请检查语法!" 1>&2
test "$AFL_KEEP_TRACES" = "" && rm -rf "$TRACE_DIR" test "$AFL_KEEP_TRACES" = "" && rm -rf "$TRACE_DIR"
exit 1 exit 1
fi fi
# The sed command converted the sorted list to a shell script that populates # sed 命令将排序后的列表转换为一个填充
# BEST_FILE[tuple]="fname". Let's load that! # BEST_FILE[tuple]="fname" 的 shell 脚本。让我们加载它!
. "$TRACE_DIR/.candidate_script" . "$TRACE_DIR/.candidate_script" # 执行候选脚本
########################## ##########################
# STEP 5: WRITING OUTPUT # # 步骤 5写出输出 #
########################## ##########################
# 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 "[*] Processing candidates and writing output files..." echo "[*] 处理候选并写入输出文件..."
CUR=0 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)) CUR=$((CUR+1))
printf "\\r Processing tuple $CUR/$TUPLE_COUNT... " printf "\\r 正在处理元组 $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 if [ "$((CUR % 5))" = "0" ]; then # 每处理五个元组时进行一次排序
sort -u "$TRACE_DIR/$FN" "$TRACE_DIR/.already_have" >"$TRACE_DIR/.tmp" sort -u "$TRACE_DIR/$FN" "$TRACE_DIR/.already_have" >"$TRACE_DIR/.tmp" # 合并并去重
mv -f "$TRACE_DIR/.tmp" "$TRACE_DIR/.already_have" mv -f "$TRACE_DIR/.tmp" "$TRACE_DIR/.already_have" # 替换原有的已拥有文件
else else
cat "$TRACE_DIR/$FN" >>"$TRACE_DIR/.already_have" cat "$TRACE_DIR/$FN" >>"$TRACE_DIR/.already_have" # 否则直接追加到已拥有文件
fi fi
done <"$TRACE_DIR/.all_uniq" done <"$TRACE_DIR/.all_uniq" # 从所有唯一元组读取数据
echo echo
OUT_COUNT=`ls -- "$OUT_DIR" | wc -l` OUT_COUNT=`ls -- "$OUT_DIR" | wc -l` # 统计输出目录中的文件数量
if [ "$OUT_COUNT" = "1" ]; then if [ "$OUT_COUNT" = "1" ]; then
echo "[!] WARNING: All test cases had the same traces, check syntax!" echo "[!] 警告: 所有测试用例具有相同的轨迹,请检查语法!"
fi fi
echo "[+] Narrowed down to $OUT_COUNT files, saved in '$OUT_DIR'." echo "[+] 已缩小到 $OUT_COUNT 个文件,保存在 '$OUT_DIR' 中。" # 输出结果数量
echo 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,79 +47,73 @@
#include <stdlib.h> #include <stdlib.h>
#include <string.h> #include <string.h>
static u8 *as_path; /*AFL“as”包装器的路径*/ static u8 *as_path; /* AFL“as”包装器的路径 */
static u8 **cc_params; /*传递给真实CC的参数*/ static u8 **cc_params; /* 传递给真实C编译器的参数 */
static u32 cc_par_cnt = 1; /*参数计数包括argv0*/ static u32 cc_par_cnt = 1; /* 参数计数包括argv0 */
static u8 be_quiet, /*静音模式*/ static u8 be_quiet, /* 静音模式 */
clang_mode; /*被称为afl clang模式**/ clang_mode; /* 被称为afl clang模式 */
/* 尝试在 AFL_PATH 或从 argv[0] 派生的位置找到我们的“假”GNU 汇编器。 /* 尝试在 AFL_PATH 或从 argv[0] 派生的位置找到我们的“假”GNU 汇编器。
*/ */
static void find_as(u8 *argv0) static void find_as(u8 *argv0)
{ {
u8 *afl_path = getenv("AFL_PATH"); // 获取环境变量AFL_PATH的值
u8 *afl_path = getenv("AFL_PATH");
u8 *slash, *tmp; u8 *slash, *tmp;
if (afl_path) if (afl_path) // 如果AFL_PATH环境变量存在
{ {
tmp = alloc_printf("%s/as", afl_path); // 构造路径字符串
tmp = alloc_printf("%s/as", afl_path); if (!access(tmp, X_OK)) // 检查路径是否存在且可执行
if (!access(tmp, X_OK))
{ {
as_path = afl_path; as_path = afl_path; // 设置as_path为找到的路径
ck_free(tmp); ck_free(tmp); // 释放临时字符串
return; return;
} }
ck_free(tmp); ck_free(tmp); // 如果不可执行,释放临时字符串
} }
slash = strrchr(argv0, '/'); slash = strrchr(argv0, '/'); // 在argv0中查找最后一个'/',以获取目录部分
if (slash) if (slash) // 如果找到了'/'说明argv0是一个路径
{ {
u8 *dir; u8 *dir;
*slash = 0; *slash = 0; // 暂时将'/'替换为'\0',以便截取目录部分
dir = ck_strdup(argv0); dir = ck_strdup(argv0); // 复制目录部分
*slash = '/'; *slash = '/'; // 恢复'/'恢复argv0的完整路径
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 = dir; // 设置as_path为找到的路径
ck_free(tmp); ck_free(tmp); // 释放临时字符串
return; return;
} }
ck_free(tmp); ck_free(tmp); // 如果不可执行,释放临时字符串
ck_free(dir); ck_free(dir); // 释放目录字符串
} }
if (!access(AFL_PATH "/as", X_OK)) if (!access(AFL_PATH "/as", X_OK)) // 检查默认路径AFL_PATH/as是否存在且可执行
{ {
as_path = AFL_PATH; as_path = AFL_PATH; // 设置as_path为默认路径
return; 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) 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; u8 *name;
#if defined(__FreeBSD__) && defined(__x86_64__) #if defined(__FreeBSD__) && defined(__x86_64__)
u8 m32_set = 0; u8 m32_set = 0; // FreeBSD x86_64环境下检测是否设置了m32
#endif #endif
/******************************************************************************** /********************************************************************************
@ -127,77 +121,74 @@ static void edit_params(u32 argc, char **argv)
* 使 afl-clang使 C C++ * 使 afl-clang使 C C++
********************************************************************************/ ********************************************************************************/
cc_params = ck_alloc((argc + 128) * sizeof(u8 *)); 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)) name = strrchr(argv[0], '/'); // 获取可执行文件名
{ if (!name)
name = argv[0]; // 如果没有找到'/'则argv0就是可执行文件名
else
name++; // 否则name指向可执行文件名的第一个字符
clang_mode = 1; if (!strncmp(name, "afl-clang", 9)) // 如果可执行文件名以afl-clang开头
{
clang_mode = 1; // 设置clang_mode标志为1表示使用clang模式
setenv(CLANG_ENV_VAR, "1", 1); setenv(CLANG_ENV_VAR, "1", 1); // 设置环境变量表示正在使用afl-clang
if (!strcmp(name, "afl-clang++")) if (!strcmp(name, "afl-clang++")) // 如果是afl-clang++
{ {
u8 *alt_cxx = getenv("AFL_CXX"); u8 *alt_cxx = getenv("AFL_CXX"); // 获取环境变量AFL_CXX的值
cc_params[0] = alt_cxx ? alt_cxx : (u8 *)"clang++"; cc_params[0] = alt_cxx ? alt_cxx : (u8 *)"clang++"; // 如果设置了AFL_CXX则使用该值作为编译器否则使用clang++
}
else
{
u8 *alt_cc = getenv("AFL_CC");
cc_params[0] = alt_cc ? alt_cc : (u8 *)"clang";
}
} }
else 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这个
/*安装了GCJ和Eclipse后您实际上可以编译Java这个 // 仪器将工作(令人惊讶)。唉,未处理的异常确实如此
// 不调用abort因此需要修改afl-fuzz以使其相等
abortafl-fuzz使 // 使用Java时具有崩溃条件的非零退出代码
使Java退 // 二进制文件。嗯
*/
#ifdef __APPLE__ // 如果是在MacOS X系统上
#ifdef __APPLE__
if (!strcmp(name, "afl-g++")) // 如果是afl-g++
if (!strcmp(name, "afl-g++")) cc_params[0] = getenv("AFL_CXX"); // 获取环境变量AFL_CXX的值作为编译器
cc_params[0] = getenv("AFL_CXX"); else if (!strcmp(name, "afl-gcj")) // 如果是afl-gcj
else if (!strcmp(name, "afl-gcj")) cc_params[0] = getenv("AFL_GCJ"); // 获取环境变量AFL_GCJ的值作为编译器
cc_params[0] = getenv("AFL_GCJ");
else else
cc_params[0] = getenv("AFL_CC"); cc_params[0] = getenv("AFL_CC"); // 其他情况获取环境变量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" "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" " '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"); " 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 #else // 如果不是在MacOS X系统上
if (!strcmp(name, "afl-g++")) if (!strcmp(name, "afl-g++")) // 如果是afl-g++
{ {
u8 *alt_cxx = getenv("AFL_CXX"); u8 *alt_cxx = getenv("AFL_CXX"); // 获取环境变量AFL_CXX的值
cc_params[0] = alt_cxx ? alt_cxx : (u8 *)"g++"; cc_params[0] = alt_cxx ? alt_cxx : (u8 *)"g++"; // 如果设置了AFL_CXX则使用该值作为编译器否则使用g++
} }
else if (!strcmp(name, "afl-gcj")) else if (!strcmp(name, "afl-gcj")) // 如果是afl-gcj
{ {
u8 *alt_cc = getenv("AFL_GCJ"); u8 *alt_cc = getenv("AFL_GCJ"); // 获取环境变量AFL_GCJ的值
cc_params[0] = alt_cc ? alt_cc : (u8 *)"gcj"; cc_params[0] = alt_cc ? alt_cc : (u8 *)"gcj"; // 如果设置了AFL_GCJ则使用该值作为编译器否则使用gcj
} }
else else // 如果是afl-gcc
{ {
u8 *alt_cc = getenv("AFL_CC"); u8 *alt_cc = getenv("AFL_CC"); // 获取环境变量AFL_CC的值
cc_params[0] = alt_cc ? alt_cc : (u8 *)"gcc"; cc_params[0] = alt_cc ? alt_cc : (u8 *)"gcc"; // 如果设置了AFL_CC则使用该值作为编译器否则使用gcc
} }
#endif /* __APPLE__ */ #endif /* __APPLE__ */
@ -233,32 +224,35 @@ static void edit_params(u32 argc, char **argv)
if (!strcmp(cur, "-pipe")) if (!strcmp(cur, "-pipe"))
continue; continue;
/ *
*
*
* FORTIFY_SOURCEASAN/MSAN
* /
#if defined(__FreeBSD__) && defined(__x86_64__) #if defined(__FreeBSD__) && defined(__x86_64__)
// 检测是否在FreeBSD的64位系统上使用了-m32选项如果是则设置m32_set为1
if (!strcmp(cur, "-m32")) if (!strcmp(cur, "-m32"))
m32_set = 1; m32_set = 1;
#endif #endif
// 检测是否使用了address sanitizer或memory sanitizer选项如果是则设置asan_set为1
if (!strcmp(cur, "-fsanitize=address") || if (!strcmp(cur, "-fsanitize=address") ||
!strcmp(cur, "-fsanitize=memory")) !strcmp(cur, "-fsanitize=memory"))
asan_set = 1; asan_set = 1;
// 检测是否设置了_FORTIFY_SOURCE宏如果是则设置fortify_set为1
if (strstr(cur, "FORTIFY_SOURCE")) if (strstr(cur, "FORTIFY_SOURCE"))
fortify_set = 1; fortify_set = 1;
// 将当前命令行参数添加到编译器参数列表中
cc_params[cc_par_cnt++] = cur; cc_params[cc_par_cnt++] = cur;
} }
// 添加编译器的-B参数指定路径给as_path
cc_params[cc_par_cnt++] = "-B"; cc_params[cc_par_cnt++] = "-B";
cc_params[cc_par_cnt++] = as_path; cc_params[cc_par_cnt++] = as_path;
// 如果clang_mode为真则添加-no-integrated-as参数
if (clang_mode) if (clang_mode)
cc_params[cc_par_cnt++] = "-no-integrated-as"; cc_params[cc_par_cnt++] = "-no-integrated-as";
// 如果设置了AFL_HARDEN环境变量则添加-fstack-protector-all参数并在未设置_FORTIFY_SOURCE时定义它
if (getenv("AFL_HARDEN")) if (getenv("AFL_HARDEN"))
{ {
@ -268,6 +262,7 @@ static void edit_params(u32 argc, char **argv)
cc_params[cc_par_cnt++] = "-D_FORTIFY_SOURCE=2"; cc_params[cc_par_cnt++] = "-D_FORTIFY_SOURCE=2";
} }
// 如果asan_set为真则设置AFL_USE_ASAN环境变量为1用于通知afl使用asan
if (asan_set) if (asan_set)
{ {
@ -277,25 +272,27 @@ static void edit_params(u32 argc, char **argv)
} }
else if (getenv("AFL_USE_ASAN")) else if (getenv("AFL_USE_ASAN"))
{ {
// 如果设置了AFL_USE_ASAN但未设置asan_set检查是否同时设置了MSAN和AFL_HARDEN如果是则报错
if (getenv("AFL_USE_MSAN")) if (getenv("AFL_USE_MSAN"))
FATAL("ASAN and MSAN are mutually exclusive"); FATAL("ASAN和MSAN是互斥的");
if (getenv("AFL_HARDEN")) if (getenv("AFL_HARDEN"))
FATAL("ASAN and AFL_HARDEN are mutually exclusive"); FATAL("ASAN和AFL_HARDEN是互斥的");
// 取消定义_FORTIFY_SOURCE宏添加address sanitizer参数
cc_params[cc_par_cnt++] = "-U_FORTIFY_SOURCE"; cc_params[cc_par_cnt++] = "-U_FORTIFY_SOURCE";
cc_params[cc_par_cnt++] = "-fsanitize=address"; cc_params[cc_par_cnt++] = "-fsanitize=address";
} }
else if (getenv("AFL_USE_MSAN")) else if (getenv("AFL_USE_MSAN"))
{ {
// 如果设置了AFL_USE_MSAN检查是否同时设置了ASAN和AFL_HARDEN如果是则报错
if (getenv("AFL_USE_ASAN")) if (getenv("AFL_USE_ASAN"))
FATAL("ASAN and MSAN are mutually exclusive"); FATAL("ASAN和MSAN是互斥的");
if (getenv("AFL_HARDEN")) if (getenv("AFL_HARDEN"))
FATAL("MSAN and AFL_HARDEN are mutually exclusive"); FATAL("MSAN和AFL_HARDEN是互斥的");
// 取消定义_FORTIFY_SOURCE宏添加memory sanitizer参数
cc_params[cc_par_cnt++] = "-U_FORTIFY_SOURCE"; cc_params[cc_par_cnt++] = "-U_FORTIFY_SOURCE";
cc_params[cc_par_cnt++] = "-fsanitize=memory"; cc_params[cc_par_cnt++] = "-fsanitize=memory";
} }
@ -306,6 +303,7 @@ static void edit_params(u32 argc, char **argv)
* : d:\code\google_AFL\src\afl-gcc.c * : d:\code\google_AFL\src\afl-gcc.c
********************************************************************************/ ********************************************************************************/
// 如果未设置AFL_DONT_OPTIMIZE环境变量则进行优化设置
if (!getenv("AFL_DONT_OPTIMIZE")) if (!getenv("AFL_DONT_OPTIMIZE"))
{ {
@ -315,41 +313,45 @@ static void edit_params(u32 argc, char **argv)
bug*/ bug*/
// 如果不是clang模式或者未设置m32选项则添加-g参数
if (!clang_mode || !m32_set) if (!clang_mode || !m32_set)
cc_params[cc_par_cnt++] = "-g"; cc_params[cc_par_cnt++] = "-g";
#else #else
// 添加调试信息参数-g
cc_params[cc_par_cnt++] = "-g"; cc_params[cc_par_cnt++] = "-g";
#endif #endif
// 添加-O3优化级别参数和-unroll-loops参数
cc_params[cc_par_cnt++] = "-O3"; cc_params[cc_par_cnt++] = "-O3";
cc_params[cc_par_cnt++] = "-funroll-loops"; cc_params[cc_par_cnt++] = "-funroll-loops";
/* Two indicators that you're building for fuzzing; one of them is /* Two indicators that you're building for fuzzing; one of them is
AFL-specific, the other is shared with libfuzzer. */ AFL-specific, the other is shared with libfuzzer. */
// 添加编译器标志,指示正在为模糊测试构建代码
cc_params[cc_par_cnt++] = "-D__AFL_COMPILER=1"; cc_params[cc_par_cnt++] = "-D__AFL_COMPILER=1";
cc_params[cc_par_cnt++] = "-DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION=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"; // 如果设置了AFL_NO_BUILTIN环境变量则添加参数禁用内置的字符串和内存比较函数
cc_params[cc_par_cnt++] = "-fno-builtin-strncmp"; if (getenv("AFL_NO_BUILTIN"))
cc_params[cc_par_cnt++] = "-fno-builtin-strcasecmp"; {
cc_params[cc_par_cnt++] = "-fno-builtin-strncasecmp"; cc_params[cc_par_cnt++] = "-fno-builtin-strcmp";
cc_params[cc_par_cnt++] = "-fno-builtin-memcmp"; cc_params[cc_par_cnt++] = "-fno-builtin-strncmp";
cc_params[cc_par_cnt++] = "-fno-builtin-strstr"; cc_params[cc_par_cnt++] = "-fno-builtin-strcasecmp";
cc_params[cc_par_cnt++] = "-fno-builtin-strcasestr"; cc_params[cc_par_cnt++] = "-fno-builtin-strncasecmp";
} cc_params[cc_par_cnt++] = "-fno-builtin-strncasecmp"; cc_params[cc_par_cnt++] = "-fno-builtin-memcmp";
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
cc_params[cc_par_cnt] = NULL; cc_params[cc_par_cnt] = NULL;
} }
@ -358,45 +360,42 @@ static void edit_params(u32 argc, char **argv)
// 主函数,程序的入口点 // 主函数,程序的入口点
int main(int argc, char **argv) int main(int argc, char **argv)
{ {
// 检查标准错误输出是否为终端,以及环境变量是否开启安静模式 // 检查标准错误输出是否为终端,以及环境变量是否开启安静模式
if (isatty(2) && !getenv("AFL_QUIET")) if (isatty(2) && !getenv("AFL_QUIET"))
{ {
SAYF(cCYA "afl-cc " cBRI VERSION cRST " by <lcamtuf@google.com>\n"); SAYF(cCYA "afl-cc " cBRI VERSION cRST " by <lcamtuf@google.com>\n");
} }
else else
be_quiet = 1; be_quiet = 1;
// 检查传入的参数数量是否少于2 // 检查传入的参数数量是否少于2,如果是则输出使用说明并退出
if (argc < 2) if (argc < 2)
{ {
SAYF("\n" SAYF("\n"
"This is a helper application for afl-fuzz. It serves as a drop-in replacement\n" "这是一个辅助afl-fuzz的工具程序。它可以用作gcc或clang的替代品\n"
"for gcc or clang, letting you recompile third-party code with the required\n" "让你能够使用必要的运行时检测重新编译第三方代码。\n"
"runtime instrumentation. A common use pattern would be one of the following:\n\n" "常见的使用模式如下之一:\n\n"
" CC=%s/afl-gcc ./configure\n" " CC=%s/afl-gcc ./configure\n"
" CXX=%s/afl-g++ ./configure\n\n" " CXX=%s/afl-g++ ./configure\n\n"
"You can specify custom next-stage toolchain via AFL_CC, AFL_CXX, and AFL_AS.\n" "你可以通过AFL_CC, AFL_CXX, 和 AFL_AS指定自定义的后续编译工具链。\n"
"Setting AFL_HARDEN enables hardening optimizations in the compiled code.\n\n", "设置AFL_HARDEN会在编译代码时启用hardening优化。\n\n",
BIN_PATH, BIN_PATH); BIN_PATH, BIN_PATH);
exit(1); exit(1);
} }
// 查找汇编器 // 查找汇编器根据argv[0]来确定使用的编译器是gcc还是clang
find_as(argv[0]); find_as(argv[0]);
// 编辑参数 // 编辑命令行参数,根据需要添加和调整编译器参数
edit_params(argc, argv); edit_params(argc, argv);
// 执行编译器,并传递参数 // 执行编译器,并传递编辑后的参数列表
execvp(cc_params[0], (char **)cc_params); execvp(cc_params[0], (char **)cc_params);
// 如果执行失败,输出错误信息 // 如果execvp调用失败输出错误信息并退出程序
FATAL("Oops, failed to execute '%s' - check your PATH", cc_params[0]); FATAL("Oops, failed to execute '%s' - check your PATH", cc_params[0]);
return 0; return 0;

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

@ -1,170 +1,175 @@
#!/bin/sh #!/bin/sh
# #
# american fuzzy lop - Advanced Persistent Graphing # American Fuzzy Lop - 高级持久图形化工具
# ------------------------------------------------- # -------------------------------------------------
# #
# Written and maintained by Michal Zalewski <lcamtuf@google.com> # 作者和维护者:Michal Zalewski <lcamtuf@google.com>
# Based on a design & prototype by Michael Rash. # 基于 Michael Rash 的设计和原型。
# #
# Copyright 2014, 2015 Google LLC All rights reserved. # 版权所有 2014, 2015 Google LLC 保留所有权利。
# #
# 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: # 您可以从以下网址获取许可证的副本:
# #
# http://www.apache.org/licenses/LICENSE-2.0 # http://www.apache.org/licenses/LICENSE-2.0
# #
echo "progress plotting utility for afl-fuzz by <lcamtuf@google.com>" echo "为 afl-fuzz 提供的进度绘图工具 <lcamtuf@google.com>" # 输出程序名称
echo echo
# 检查参数数量是否为 2
if [ ! "$#" = "2" ]; then if [ ! "$#" = "2" ]; then
# 输出使用说明到标准错误
cat 1>&2 <<_EOF_ cat 1>&2 <<_EOF_
This program generates gnuplot images from afl-fuzz output data. Usage: 此程序从 afl-fuzz 输出数据生成 gnuplot 图像。用法:
$0 afl_state_dir graph_output_dir $0 afl_state_dir graph_output_dir
The afl_state_dir parameter should point to an existing state directory for any 参数 afl_state_dir 应指向现有的状态目录,该目录属于任何
active or stopped instance of afl-fuzz; while graph_output_dir should point to 正在运行或已停止的 afl-fuzz 实例;而 graph_output_dir 应指向
an empty directory where this tool can write the resulting plots to. 一个空目录,在该目录中此工具可以写入结果图表。
The program will put index.html and three PNG images in the output directory; 该程序将在输出目录中放置 index.html 和三张 PNG 图像;
you should be able to view it with any web browser of your choice. 您应该能够用任何您喜欢的 web 浏览器查看它。
_EOF_ _EOF_
exit 1 exit 1 # 退出程序,返回错误状态
fi fi
# 如果 AFL_ALLOW_TMP 变量为空,则进行临时目录检查
if [ "$AFL_ALLOW_TMP" = "" ]; then if [ "$AFL_ALLOW_TMP" = "" ]; then
echo "$1" | grep -qE '^(/var)?/tmp/' echo "$1" | grep -qE '^(/var)?/tmp/' # 检查第一个参数是否在/tmp
T1="$?" T1="$?"
echo "$2" | grep -qE '^(/var)?/tmp/' echo "$2" | grep -qE '^(/var)?/tmp/' # 检查第二个参数是否在/tmp
T2="$?" T2="$?"
if [ "$T1" = "0" -o "$T2" = "0" ]; then if [ "$T1" = "0" -o "$T2" = "0" ]; then
echo "[-] 错误: 不应在共享 /tmp 目录中使用此脚本。" 1>&2 # 输出错误信息
echo "[-] Error: this script shouldn't be used with shared /tmp directories." 1>&2 exit 1 # 退出程序,返回错误状态
exit 1
fi fi
fi fi
# 检查输入目录是否有效(必须存在 'plot_data' 文件)
if [ ! -f "$1/plot_data" ]; then if [ ! -f "$1/plot_data" ]; then
echo "[-] 错误: 输入目录无效(缺少 'plot_data' 文件)。" 1>&2 # 输出错误信息
echo "[-] Error: input directory is not valid (missing 'plot_data')." 1>&2 exit 1 # 退出程序,返回错误状态
exit 1
fi fi
# 从 fuzzer_stats 文件中提取 banner 信息
BANNER="`cat "$1/fuzzer_stats" | grep '^afl_banner ' | cut -d: -f2- | cut -b2-`" BANNER="`cat "$1/fuzzer_stats" | grep '^afl_banner ' | cut -d: -f2- | cut -b2-`"
test "$BANNER" = "" && BANNER="(none)" # 如果未找到 banner设置为 (none)
test "$BANNER" = "" && BANNER="(none)" # 查找 gnuplot 命令
GNUPLOT=`which gnuplot 2>/dev/null` GNUPLOT=`which gnuplot 2>/dev/null`
# 检查是否能找到 gnuplot
if [ "$GNUPLOT" = "" ]; then if [ "$GNUPLOT" = "" ]; then
echo "[-] 错误: 在您的 \$PATH 中找不到 'gnuplot'。" 1>&2 # 输出错误信息
echo "[-] Error: can't find 'gnuplot' in your \$PATH." 1>&2 exit 1 # 退出程序,返回错误状态
exit 1
fi fi
# 创建输出目录,如果目录已经存在则忽略错误
mkdir "$2" 2>/dev/null mkdir "$2" 2>/dev/null
# 检查输出目录是否成功创建
if [ ! -d "$2" ]; then if [ ! -d "$2" ]; then
echo "[-] 错误: 无法创建输出目录 - 请选择另一个位置。" 1>&2 # 输出错误信息
echo "[-] Error: unable to create the output directory - pick another location." 1>&2 exit 1 # 退出程序,返回错误状态
exit 1
fi fi
# 删除旧的图像文件
rm -f "$2/high_freq.png" "$2/low_freq.png" "$2/exec_speed.png" 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 mv -f "$2/index.html" "$2/index.html.orig" 2>/dev/null # 备份旧的 index.html 文件
echo "[*] Generating plots..." echo "[*] 生成图表..." # 输出生成图表的提示
( (
# gnuplot 脚本开始
cat <<_EOF_ cat <<_EOF_
set terminal png truecolor enhanced size 1000,300 butt set terminal png truecolor enhanced size 1000,300 butt # 设置输出为 PNG 格式,启用颜色和大小
set output '$2/high_freq.png' set output '$2/high_freq.png' # 设置输出文件为高频图像文件
set xdata time set xdata time # 设置 x 轴数据为时间
set timefmt '%s' set timefmt '%s' # 设置时间格式
set format x "%b %d\n%H:%M" set format x "%b %d\n%H:%M" # 设置 x 轴刻度格式
set tics font 'small' set tics font 'small' # 设置刻度字体
unset mxtics unset mxtics # 禁用 x 轴的次刻度
unset mytics unset mytics # 禁用 y 轴的次刻度
set grid xtics linetype 0 linecolor rgb '#e0e0e0' set grid xtics linetype 0 linecolor rgb '#e0e0e0' # 设置 x 轴网格线样式和颜色
set grid ytics linetype 0 linecolor rgb '#e0e0e0' set grid ytics linetype 0 linecolor rgb '#e0e0e0' # 设置 y 轴网格线样式和颜色
set border linecolor rgb '#50c0f0' set border linecolor rgb '#50c0f0' # 设置边框颜色
set tics textcolor rgb '#000000' set tics textcolor rgb '#000000' # 设置刻度文本颜色
set key outside set key outside # 设置图例位置在外部
set autoscale xfixmin set autoscale xfixmin # 自动缩放 x 轴最小值
set autoscale xfixmax set autoscale xfixmax # 自动缩放 x 轴最大值
# 绘制高频图像
plot '$1/plot_data' using 1:4 with filledcurve x1 title 'total paths' linecolor rgb '#000000' fillstyle transparent solid 0.2 noborder, \\ 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: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: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:6 with lines title 'pending favs' linecolor rgb '#c00080' linewidth 3, \\
'' using 1:2 with lines title 'cycles done' linecolor rgb '#c000f0' 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 terminal png truecolor enhanced size 1000,200 butt # 设置输出为低频图像文件
set output '$2/low_freq.png'
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, \\ 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: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:9 with lines title 'uniq hangs' linecolor rgb '#c000f0' linewidth 3, \\
'' using 1:10 with lines title 'levels' linecolor rgb '#0090ff' 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, \\ 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_ _EOF_
) | gnuplot ) | gnuplot # 将生成的 gnuplot 脚本管道到 gnuplot
# 检查执行速度图像是否生成成功
if [ ! -s "$2/exec_speed.png" ]; then if [ ! -s "$2/exec_speed.png" ]; then
echo "[-] 错误: 出现问题!您可能使用了古老版本的 gnuplot。" 1>&2 # 输出错误信息
echo "[-] Error: something went wrong! Perhaps you have an ancient version of gnuplot?" 1>&2 exit 1 # 退出程序,返回错误状态
exit 1
fi fi
echo "[*] Generating index.html..." echo "[*] 生成 index.html..." # 输出生成 index.html 的提示
# 创建 index.html 文件
cat >"$2/index.html" <<_EOF_ cat >"$2/index.html" <<_EOF_
<table style="font-family: 'Trebuchet MS', 'Tahoma', 'Arial', 'Helvetica'"> <table style="font-family: 'Trebuchet MS', 'Tahoma', 'Arial', 'Helvetica'">
<tr><td style="width: 18ex"><b>Banner:</b></td><td>$BANNER</td></tr> <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>Directory:</b></td><td>$1</td></tr> # 显示输入目录
<tr><td><b>Generated on:</b></td><td>`date`</td></tr> <tr><td><b>Generated on:</b></td><td>`date`</td></tr> # 显示生成日期
</table> </table>
<p> <p>
<img src="high_freq.png" width=1000 height=300><p> <img src="high_freq.png" width=1000 height=300><p> # 显示高频图像
<img src="low_freq.png" width=1000 height=200><p> <img src="low_freq.png" width=1000 height=200><p> # 显示低频图像
<img src="exec_speed.png" width=1000 height=200> <img src="exec_speed.png" width=1000 height=200> # 显示执行速度图像
_EOF_ _EOF_
# Make it easy to remotely view results when outputting directly to a directory # 使在直接输出到由 Apache 或其他 HTTP 守护进程服务的目录时,
# 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 755 "$2" # 设置输出目录权限
chmod 644 "$2/high_freq.png" "$2/low_freq.png" "$2/exec_speed.png" "$2/index.html" chmod 644 "$2/high_freq.png" "$2/low_freq.png" "$2/exec_speed.png" "$2/index.html" # 设置文件权限
echo "[+] All done - enjoy your charts!" echo "[+] 完成 - 享受您的图表!" # 输出完成提示
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) { static void classify_counts(u8* mem, const u8* map) {
u32 i = MAP_SIZE; u32 i = MAP_SIZE;
if (edges_only) { if (edges_only) {
// 如果 edges_only 为真只保留是否命中的信息1 或 0
while (i--) { while (i--) {
if (*mem) *mem = 1; if (*mem) *mem = 1; // 如果 mem 中的值不为 0则将其设置为 1
mem++; mem++; // 移动到下一个字节
} }
} else { } else {
// 如果 edges_only 为假,使用 map 中的值来更新 mem 中的值
while (i--) { while (i--) {
*mem = map[*mem]; *mem = map[*mem]; // 使用 map 中的值替换 mem 中的值
mem++; mem++; // 移动到下一个字节
} }
} }
} }
/* 清理共享内存atexit 处理程序) */
/* Get rid of shared memory (atexit handler). */
static void remove_shm(void) { 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) { static void setup_shm(void) {
u8* shm_str; u8* shm_str;
// 创建一个新的共享内存段
shm_id = shmget(IPC_PRIVATE, MAP_SIZE, IPC_CREAT | IPC_EXCL | 0600); 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(remove_shm); // 注册 atexit 处理程序,确保程序退出时删除共享内存
shm_str = alloc_printf("%d", shm_id); shm_str = alloc_printf("%d", shm_id); // 将共享内存 ID 转换为字符串
setenv(SHM_ENV_VAR, shm_str, 1); setenv(SHM_ENV_VAR, shm_str, 1); // 将共享内存 ID 设置到环境变量中
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) { static u32 write_results(void) {
s32 fd; s32 fd;
u32 i, ret = 0; u32 i, ret = 0;
// 获取环境变量 AFL_CMIN_CRASHES_ONLY 和 AFL_CMIN_ALLOW_ANY 的值
u8 cco = !!getenv("AFL_CMIN_CRASHES_ONLY"), u8 cco = !!getenv("AFL_CMIN_CRASHES_ONLY"),
caa = !!getenv("AFL_CMIN_ALLOW_ANY"); caa = !!getenv("AFL_CMIN_ALLOW_ANY");
// 根据 out_file 的值决定如何打开输出文件
if (!strncmp(out_file, "/dev/", 5)) { if (!strncmp(out_file, "/dev/", 5)) {
fd = open(out_file, O_WRONLY, 0600); fd = open(out_file, O_WRONLY, 0600); // 打开设备文件
if (fd < 0) PFATAL("Unable to open '%s'", out_file); if (fd < 0) PFATAL("Unable to open '%s'", out_file); // 如果打开失败,输出错误信息并退出
} else if (!strcmp(out_file, "-")) { } else if (!strcmp(out_file, "-")) {
fd = dup(1); fd = dup(1); // 如果输出文件是 "-",则复制标准输出文件描述符
if (fd < 0) PFATAL("Unable to open stdout"); if (fd < 0) PFATAL("Unable to open stdout"); // 如果复制失败,输出错误信息并退出
} else { } else {
unlink(out_file); /* Ignore errors */ unlink(out_file); /* Ignore errors */ // 删除已存在的文件(忽略错误)
fd = open(out_file, O_WRONLY | O_CREAT | O_EXCL, 0600); fd = open(out_file, O_WRONLY | O_CREAT | O_EXCL, 0600); // 创建新文件
if (fd < 0) PFATAL("Unable to create '%s'", out_file); if (fd < 0) PFATAL("Unable to create '%s'", out_file); // 如果创建失败,输出错误信息并退出
} }
if (binary_mode) { if (binary_mode) {
// 如果处于二进制模式,直接写入 trace_bits 的内容
for (i = 0; i < MAP_SIZE; i++) 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); ck_write(fd, trace_bits, MAP_SIZE, out_file); // 将 trace_bits 写入文件
close(fd); close(fd); // 关闭文件描述符
} else { } else {
FILE* f = fdopen(fd, "w"); FILE* f = fdopen(fd, "w"); // 将文件描述符转换为 FILE 指针
if (!f) PFATAL("fdopen() failed"); if (!f) PFATAL("fdopen() failed"); // 如果转换失败,输出错误信息并退出
for (i = 0; i < MAP_SIZE; i++) { for (i = 0; i < MAP_SIZE; i++) {
if (!trace_bits[i]) continue; if (!trace_bits[i]) continue; // 如果 trace_bits[i] 为 0跳过
ret++; ret++; // 统计非零的字节数
if (cmin_mode) { if (cmin_mode) {
if (child_timed_out) break; // 如果处于 cmin_mode根据条件决定是否写入
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); fprintf(f, "%u%u\n", trace_bits[i], i); // 写入 trace_bits[i] 和索引 i
} else fprintf(f, "%06u:%u\n", i, trace_bits[i]); } else fprintf(f, "%06u:%u\n", i, trace_bits[i]); // 否则,写入索引 i 和 trace_bits[i]
} }
fclose(f); fclose(f); // 关闭文件
} }
return ret; return ret; // 返回写入的非零字节数
} }
/* 处理超时信号 */
/* Handle timeout signal. */
static void handle_timeout(int sig) { static void handle_timeout(int sig) {
child_timed_out = 1; child_timed_out = 1;
@ -244,124 +244,118 @@ static void handle_timeout(int sig) {
} }
/* 执行目标程序 */
/* Execute target application. */
static void run_target(char** argv) { static void run_target(char** argv) {
static struct itimerval it; static struct itimerval it; // 定义一个定时器结构体
int status = 0; int status = 0; // 用于存储子进程的状态
if (!quiet_mode) 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"); if (child_pid < 0) PFATAL("fork() failed"); // 如果fork失败输出错误信息并退出
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); s32 fd = open("/dev/null", O_RDWR); // 打开/dev/null以忽略输出
if (fd < 0 || dup2(fd, 1) < 0 || dup2(fd, 2) < 0) { if (fd < 0 || dup2(fd, 1) < 0 || dup2(fd, 2) < 0) { // 将标准输出和标准错误重定向到/dev/null
*(u32*)trace_bits = EXEC_FAIL_SIG; *(u32*)trace_bits = EXEC_FAIL_SIG; // 如果失败设置trace_bits为EXEC_FAIL_SIG
PFATAL("Descriptor initialization failed"); PFATAL("Descriptor initialization failed"); // 输出错误信息并退出
} }
close(fd); close(fd); // 关闭/dev/null文件描述符
} }
if (mem_limit) { if (mem_limit) { // 如果设置了内存限制
r.rlim_max = r.rlim_cur = ((rlim_t)mem_limit) << 20; r.rlim_max = r.rlim_cur = ((rlim_t)mem_limit) << 20; // 设置内存限制为指定的MB数
#ifdef RLIMIT_AS #ifdef RLIMIT_AS
setrlimit(RLIMIT_AS, &r); /* Ignore errors */ setrlimit(RLIMIT_AS, &r); /* Ignore errors */ // 设置地址空间大小限制,忽略错误
#else #else
setrlimit(RLIMIT_DATA, &r); /* Ignore errors */ setrlimit(RLIMIT_DATA, &r); /* Ignore errors */ // 设置数据段大小限制,忽略错误
#endif /* ^RLIMIT_AS */ #endif /* ^RLIMIT_AS */
} }
if (!keep_cores) r.rlim_max = r.rlim_cur = 0; if (!keep_cores) r.rlim_max = r.rlim_cur = 0; // 如果不需要核心转储文件设置核心转储大小为0
else r.rlim_max = r.rlim_cur = RLIM_INFINITY; 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; *(u32*)trace_bits = EXEC_FAIL_SIG; // 如果execv失败设置trace_bits为EXEC_FAIL_SIG
exit(0); exit(0); // 退出子进程
} }
/* Configure timeout, wait for child, cancel timeout. */ /* 配置超时,等待子进程,取消超时 */
if (exec_tmout) { // 如果设置了执行超时时间
if (exec_tmout) {
child_timed_out = 0; child_timed_out = 0; // 初始化子进程超时标志为未超时
it.it_value.tv_sec = (exec_tmout / 1000); it.it_value.tv_sec = (exec_tmout / 1000); // 设置定时器的秒数部分
it.it_value.tv_usec = (exec_tmout % 1000) * 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; child_pid = 0; // 重置子进程ID
it.it_value.tv_sec = 0; it.it_value.tv_sec = 0; // 重置定时器的秒数部分
it.it_value.tv_usec = 0; it.it_value.tv_usec = 0; // 重置定时器的微秒数部分
setitimer(ITIMER_REAL, &it, NULL); setitimer(ITIMER_REAL, &it, NULL); // 取消定时器
MEM_BARRIER(); MEM_BARRIER(); // 内存屏障,确保内存操作顺序
/* Clean up bitmap, analyze exit condition, etc. */ /* 清理位图,分析退出条件等 */
if (*(u32*)trace_bits == EXEC_FAIL_SIG) // 如果trace_bits等于EXEC_FAIL_SIG表示程序执行失败
FATAL("Unable to execute '%s'", argv[0]); // 输出错误信息并退出
if (*(u32*)trace_bits == EXEC_FAIL_SIG) classify_counts(trace_bits, binary_mode ? // 根据二进制模式选择分类方法
FATAL("Unable to execute '%s'", argv[0]); count_class_binary : count_class_human); // 分类trace_bits中的覆盖率信息
classify_counts(trace_bits, binary_mode ? if (!quiet_mode) // 如果不是静默模式
count_class_binary : count_class_human); SAYF(cRST "-- Program output ends --\n"); // 输出程序结束信息
if (!quiet_mode)
SAYF(cRST "-- Program output ends --\n");
if (!child_timed_out && !stop_soon && WIFSIGNALED(status)) if (!child_timed_out && !stop_soon && WIFSIGNALED(status)) // 如果子进程没有超时、没有被用户中断且以信号方式结束
child_crashed = 1; child_crashed = 1; // 设置子进程崩溃标志为真
if (!quiet_mode) { if (!quiet_mode) { // 如果不是静默模式
if (child_timed_out) if (child_timed_out)
SAYF(cLRD "\n+++ Program timed off +++\n" cRST); SAYF(cLRD "\n+++ Program timed off +++\n" cRST); // 如果子进程超时,输出超时信息
else if (stop_soon) 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) 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)); // 如果子进程崩溃,输出崩溃信号信息
} }
} }
/* Handle Ctrl-C and the like. */ /* 处理 Ctrl-C 等信号 */
static void handle_stop_sig(int sig) { static void handle_stop_sig(int sig) {
stop_soon = 1; stop_soon = 1;
@ -370,9 +364,7 @@ static void handle_stop_sig(int sig) {
} }
/* 进行基本准备 - 持久化文件描述符、文件名等 */
/* Do basic preparations - persistent fds, filenames, etc. */
static void set_up_environment(void) { static void set_up_environment(void) {
setenv("ASAN_OPTIONS", "abort_on_error=1:" setenv("ASAN_OPTIONS", "abort_on_error=1:"
@ -393,9 +385,7 @@ static void set_up_environment(void) {
} }
/* 设置信号处理程序 */
/* Setup signal handlers, duh. */
static void setup_signal_handlers(void) { static void setup_signal_handlers(void) {
struct sigaction sa; struct sigaction sa;
@ -406,390 +396,212 @@ static void setup_signal_handlers(void) {
sigemptyset(&sa.sa_mask); sigemptyset(&sa.sa_mask);
/* Various ways of saying "stop". */ /* 各种停止信号的处理 */
sa.sa_handler = handle_stop_sig; sa.sa_handler = handle_stop_sig;
sigaction(SIGHUP, &sa, NULL); sigaction(SIGHUP, &sa, NULL);
sigaction(SIGINT, &sa, NULL); sigaction(SIGINT, &sa, NULL);
sigaction(SIGTERM, &sa, NULL); sigaction(SIGTERM, &sa, NULL);
/* Exec timeout notifications. */ /* 执行超时通知的处理 */
sa.sa_handler = handle_timeout; sa.sa_handler = handle_timeout;
sigaction(SIGALRM, &sa, NULL); sigaction(SIGALRM, &sa, NULL);
} }
/* 检测参数中的 @@ */
/* Detect @@ in args. */
static void detect_file_args(char** argv) { static void detect_file_args(char** argv) {
u32 i = 0; u32 i = 0; // 初始化索引变量 i 用于遍历 argv 数组
u8* cwd = getcwd(NULL, 0); u8* cwd = getcwd(NULL, 0); // 获取当前工作目录的路径
if (!cwd) PFATAL("getcwd() failed"); if (!cwd) PFATAL("getcwd() failed"); // 如果获取当前工作目录失败,则输出错误信息并终止程序
while (argv[i]) { while (argv[i]) { // 遍历命令行参数数组,直到遇到 NULL 结束符
u8* aa_loc = strstr(argv[i], "@@"); u8* aa_loc = strstr(argv[i], "@@"); // 查找当前参数中是否包含 "@@" 字符串
if (aa_loc) { if (aa_loc) { // 如果找到了 "@@" 字符串
u8 *aa_subst, *n_arg; u8 *aa_subst, *n_arg;
if (!at_file) FATAL("@@ syntax is not supported by this tool."); if (!at_file) FATAL("@@ syntax is not supported by this tool."); // 如果 at_file 为空,则 "@@" 语法不被支持,输出错误信息并终止程序
/* Be sure that we're always using fully-qualified paths. */
if (at_file[0] == '/') aa_subst = at_file;
else aa_subst = alloc_printf("%s/%s", cwd, at_file);
/* Construct a replacement argv value. */ /* 确保始终使用完全限定的路径 */
if (at_file[0] == '/') aa_subst = at_file; // 如果 at_file 是绝对路径,则直接使用
else aa_subst = alloc_printf("%s/%s", cwd, at_file); // 如果 at_file 是相对路径,则将其转换为绝对路径
*aa_loc = 0; /* 构造替换的 argv 值 */
n_arg = alloc_printf("%s%s%s", argv[i], aa_subst, aa_loc + 2); *aa_loc = 0; // 在 "@@" 出现的位置临时截断字符串
argv[i] = n_arg; n_arg = alloc_printf("%s%s%s", argv[i], aa_subst, aa_loc + 2); // 将 aa_subst 替换到 "@@" 的位置,形成新的命令行参数
*aa_loc = '@'; argv[i] = n_arg; // 更新 argv 数组中的当前参数为替换后的值
*aa_loc = '@'; // 恢复原字符串的 "@@" 部分,以便在后续处理中保持一致
if (at_file[0] != '/') ck_free(aa_subst); if (at_file[0] != '/') ck_free(aa_subst); // 如果 at_file 是相对路径,则释放通过 alloc_printf 分配的内存空间
} }
i++; i++; // 移动到下一个命令行参数
} }
free(cwd); /* not tracked */ free(cwd); // 释放通过 getcwd 分配的内存空间
} }
/* 显示工具的横幅信息 */
/* Show banner. */
static void show_banner(void) { 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");
} }
/* Display usage hints. */ /* 显示用法提示信息 */
static void usage(u8* argv0) { 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" " -t msec - 每次运行的超时时间 (无限制)\n" // 解释 -t 选项的作用,即设置每个测试用例的超时时间
" -m megs - memory limit for child process (%u MB)\n" " -m megs - 子进程的内存限制 (%u MB)\n" // 解释 -m 选项的作用,即设置子进程的内存限制,默认值为 MEM_LIMIT
" -Q - use binary-only instrumentation (QEMU mode)\n\n"
"Other settings:\n\n" "其他设置:\n\n"
" -q - sink program's output and don't show messages\n" " -q - 静默程序的输出,不显示消息\n" // 解释 -q 选项的作用,即抑制程序的输出
" -e - show edge coverage only, ignore hit counts\n" " -e - 仅显示边缘覆盖率,忽略命中次数\n" // 解释 -e 选项的作用,即只显示边缘覆盖率而不关心具体命中次数
" -c - allow core dumps\n" " -c - 允许生成核心转储文件\n" // 解释 -c 选项的作用,即允许程序在崩溃时生成核心转储文件
" -V - show version number and exit\n\n" " -V - 显示版本号并退出\n\n" // 解释 -V 选项的作用,即显示工具的版本号后退出程序
"This tool displays raw tuple data captured by AFL instrumentation.\n" "此工具显示由 AFL 仪器化捕获的原始元组数据。\n" // 说明此工具的功能
"For additional help, consult %s/README.\n\n" cRST, "更多信息,请参考 %s/README。\n\n" cRST, // 提供更多帮助信息的文件路径,并使用彩色控制码重置输出格式
argv0, MEM_LIMIT, doc_path); argv0, MEM_LIMIT, doc_path);
exit(1); exit(1); // 输出完帮助信息后,退出程序,返回状态码 1
} }
/* Find binary. */
static void find_binary(u8* fname) { static void find_binary(u8* fname) {
// 定义环境变量路径和文件状态结构体
u8* env_path = 0; u8* env_path = 0;
struct stat st; struct stat st;
// 如果文件名中包含 '/', 或者环境变量 "PATH" 不存在
if (strchr(fname, '/') || !(env_path = getenv("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) || if (stat(target_path, &st) || !S_ISREG(st.st_mode) ||
!(st.st_mode & 0111) || st.st_size < 4) !(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 { } else {
// 环境变量 "PATH" 存在,逐个检查其中的路径
while (env_path) { while (env_path) {
u8 *cur_elem, *delim = strchr(env_path, ':'); u8 *cur_elem, *delim = strchr(env_path, ':');
// 检查当前路径元素是否以 ':' 结尾
if (delim) { if (delim) {
// 分配内存并复制当前路径元素
cur_elem = ck_alloc(delim - env_path + 1); cur_elem = ck_alloc(delim - env_path + 1);
memcpy(cur_elem, env_path, delim - env_path); memcpy(cur_elem, env_path, delim - env_path);
delim++; delim++;
} else
// 如果没有 ':',则直接复制整个环境变量路径
cur_elem = ck_strdup(env_path);
} else cur_elem = ck_strdup(env_path); // 更新环境变量路径指针到下一个元素
env_path = delim; env_path = delim;
// 如果当前路径元素不为空,将文件名添加到该路径后面形成完整路径
if (cur_elem[0]) if (cur_elem[0])
target_path = alloc_printf("%s/%s", cur_elem, fname); target_path = alloc_printf("%s/%s", cur_elem, fname);
else 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) && 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); ck_free(target_path);
target_path = 0; target_path = 0;
} }
// 如果遍历完所有路径后仍未找到目标程序,输出错误信息并终止程序
if (!target_path) FATAL("Program '%s' not found or not executable", fname); if (!target_path) FATAL("Program '%s' not found or not executable", fname);
} }
} }
/* 修复针对 QEMU 的 argv 参数 */
/* Fix up argv for QEMU. */
static char** get_qemu_argv(u8* own_loc, char** argv, int argc) { 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; u8 *tmp, *cp, *rsl, *own_copy;
/* Workaround for a QEMU stability glitch. */ // 设置 QEMU 日志环境变量为 "nochain"
setenv("QEMU_LOG", "nochain", 1); setenv("QEMU_LOG", "nochain", 1);
// 将原始 argv 数组中的参数从第二个开始复制到新的 argv 数组中
memcpy(new_argv + 3, argv + 1, sizeof(char*) * argc); memcpy(new_argv + 3, argv + 1, sizeof(char*) * argc);
// 将目标程序路径设置为新的 argv 数组的第三个参数
new_argv[2] = target_path; new_argv[2] = target_path;
// 设置 "--" 作为新的 argv 数组的第二个参数
new_argv[1] = "--"; new_argv[1] = "--";
/* Now we need to actually find qemu for argv[0]. */ // 检查环境变量 "AFL_PATH" 是否存在
tmp = getenv("AFL_PATH"); tmp = getenv("AFL_PATH");
if (tmp) { if (tmp) {
// 如果存在,构建 afl-qemu-trace 工具的完整路径
cp = alloc_printf("%s/afl-qemu-trace", tmp); cp = alloc_printf("%s/afl-qemu-trace", tmp);
// 检查 afl-qemu-trace 工具是否存在且可执行
if (access(cp, X_OK)) if (access(cp, X_OK))
// 如果工具不存在或不可执行,输出错误信息并终止程序
FATAL("Unable to find '%s'", tmp); FATAL("Unable to find '%s'", tmp);
// 更新目标路径和新的 argv 数组的第一个参数为 afl-qemu-trace 工具的路径
target_path = new_argv[0] = cp; target_path = new_argv[0] = cp;
return new_argv; return new_argv;
} }
// 如果环境变量 "AFL_PATH" 不存在,复制当前程序的位置
own_copy = ck_strdup(own_loc); own_copy = ck_strdup(own_loc);
// 查找最后一个 '/' 的位置
rsl = strrchr(own_copy, '/'); rsl = strrchr(own_copy, '/');
if (rsl) { if (rsl) {
// 将最后一个 '/' 替换为0截断字符串以获得目录路径
*rsl = 0; *rsl = 0;
// 构建 afl-qemu-trace 工具的完整路径
cp = alloc_printf("%s/afl-qemu-trace", own_copy); 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)) { if (!access(cp, X_OK)) {
// 如果工具存在且可执行,更新目标路径和新的 argv 数组的第一个参数
target_path = new_argv[0] = cp; target_path = new_argv[0] = cp;
return new_argv; return new_argv;
} }
} else
// 如果没有找到 '/',直接释放复制的程序位置的空间
ck_free(own_copy);
} else ck_free(own_copy); // 如果上述方法都未能找到 afl-qemu-trace 工具,检查预定义的二进制路径
if (!access(BIN_PATH "/afl-qemu-trace", X_OK)) { if (!access(BIN_PATH "/afl-qemu-trace", X_OK)) {
// 如果工具存在且可执行,更新目标路径和新的 argv 数组的第一个参数
target_path = new_argv[0] = BIN_PATH "/afl-qemu-trace"; target_path = new_argv[0] = alloc_printf("%s/afl-qemu-trace", BIN_PATH);
return new_argv; return new_argv;
} }
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';
if (mem_limit_given) FATAL("Multiple -m options not supported");
mem_limit_given = 1;
if (!strcmp(optarg, "none")) {
mem_limit = 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;
case 'G': mem_limit *= 1024; break;
case 'k': mem_limit /= 1024; break;
case 'M': break;
default: FATAL("Unsupported suffix or bad syntax for -m");
}
if (mem_limit < 5) FATAL("Dangerously low value of -m");
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;
if (strcmp(optarg, "none")) {
exec_tmout = atoi(optarg);
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':
/* This is an undocumented option to write data in the syntax expected
by afl-cmin. Nobody else should have any use for this. */
cmin_mode = 1;
quiet_mode = 1;
break;
case 'A':
/* Another afl-cmin specific feature. */
at_file = optarg;
break;
case 'Q':
if (qemu_mode) FATAL("Multiple -Q options not supported");
if (!mem_limit_given) mem_limit = MEM_LIMIT_QEMU;
qemu_mode = 1;
break;
case 'b':
/* Secret undocumented mode. Writes output in raw binary format
similar to that dumped by afl-fuzz in <out_dir/queue/fuzz_bitmap. */
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);
if (qemu_mode)
use_argv = get_qemu_argv(argv[0], argv + optind, argc - optind);
else
use_argv = argv + optind;
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 @@
/* /*
Copyright 2013 Google LLC All rights reserved. 2013 Google LLC
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:
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.
*/ */
/* /*
american fuzzy lop - debug / error handling macros American Fuzzy Lop - /
-------------------------------------------------- --------------------------------------------------
Written and maintained by Michal Zalewski <lcamtuf@google.com> Michal Zalewski <lcamtuf@google.com>
*/ */
#ifndef _HAVE_DEBUG_H #ifndef _HAVE_DEBUG_H
@ -30,49 +30,49 @@
#include "config.h" #include "config.h"
/******************* /*******************
* Terminal colors * * *
*******************/ *******************/
#ifdef USE_COLOR #ifdef USE_COLOR
# define cBLK "\x1b[0;30m" # define cBLK "\x1b[0;30m" // 黑色文本
# define cRED "\x1b[0;31m" # define cRED "\x1b[0;31m" // 红色文本
# define cGRN "\x1b[0;32m" # define cGRN "\x1b[0;32m" // 绿色文本
# define cBRN "\x1b[0;33m" # define cBRN "\x1b[0;33m" // 棕色文本
# define cBLU "\x1b[0;34m" # define cBLU "\x1b[0;34m" // 蓝色文本
# define cMGN "\x1b[0;35m" # define cMGN "\x1b[0;35m" // 紫色文本
# define cCYA "\x1b[0;36m" # define cCYA "\x1b[0;36m" // 青色文本
# define cLGR "\x1b[0;37m" # define cLGR "\x1b[0;37m" // 浅灰色文本
# define cGRA "\x1b[1;90m" # define cGRA "\x1b[1;90m" // 深灰色文本
# define cLRD "\x1b[1;91m" # define cLRD "\x1b[1;91m" // 浅红色文本
# define cLGN "\x1b[1;92m" # define cLGN "\x1b[1;92m" // 浅绿色文本
# define cYEL "\x1b[1;93m" # define cYEL "\x1b[1;93m" // 浅黄色文本
# define cLBL "\x1b[1;94m" # define cLBL "\x1b[1;94m" // 浅蓝色文本
# define cPIN "\x1b[1;95m" # define cPIN "\x1b[1;95m" // 浅紫色文本
# define cLCY "\x1b[1;96m" # define cLCY "\x1b[1;96m" // 浅青色文本
# define cBRI "\x1b[1;97m" # define cBRI "\x1b[1;97m" // 白色文本
# define cRST "\x1b[0m" # define cRST "\x1b[0m" // 重置颜色
# define bgBLK "\x1b[40m" # define bgBLK "\x1b[40m" // 黑色背景
# define bgRED "\x1b[41m" # define bgRED "\x1b[41m" // 红色背景
# define bgGRN "\x1b[42m" # define bgGRN "\x1b[42m" // 绿色背景
# define bgBRN "\x1b[43m" # define bgBRN "\x1b[43m" // 棕色背景
# define bgBLU "\x1b[44m" # define bgBLU "\x1b[44m" // 蓝色背景
# define bgMGN "\x1b[45m" # define bgMGN "\x1b[45m" // 紫色背景
# define bgCYA "\x1b[46m" # define bgCYA "\x1b[46m" // 青色背景
# define bgLGR "\x1b[47m" # define bgLGR "\x1b[47m" // 浅灰色背景
# define bgGRA "\x1b[100m" # define bgGRA "\x1b[100m" // 深灰色背景
# define bgLRD "\x1b[101m" # define bgLRD "\x1b[101m" // 浅红色背景
# define bgLGN "\x1b[102m" # define bgLGN "\x1b[102m" // 浅绿色背景
# define bgYEL "\x1b[103m" # define bgYEL "\x1b[103m" // 浅黄色背景
# define bgLBL "\x1b[104m" # define bgLBL "\x1b[104m" // 浅蓝色背景
# define bgPIN "\x1b[105m" # define bgPIN "\x1b[105m" // 浅紫色背景
# define bgLCY "\x1b[106m" # define bgLCY "\x1b[106m" // 浅青色背景
# define bgBRI "\x1b[107m" # define bgBRI "\x1b[107m" // 白色背景
#else #else
# define cBLK "" # define cBLK "" // 不使用颜色
# define cRED "" # define cRED ""
# define cGRN "" # define cGRN ""
# define cBRN "" # define cBRN ""
@ -90,7 +90,7 @@
# define cBRI "" # define cBRI ""
# define cRST "" # define cRST ""
# define bgBLK "" # define bgBLK "" // 不使用背景颜色
# define bgRED "" # define bgRED ""
# define bgGRN "" # define bgGRN ""
# define bgBRN "" # define bgBRN ""
@ -115,25 +115,25 @@
#ifdef FANCY_BOXES #ifdef FANCY_BOXES
# define SET_G1 "\x1b)0" /* Set G1 for box drawing */ # define SET_G1 "\x1b)0" /* 设置 G1 用于绘制框 */
# define RESET_G1 "\x1b)B" /* Reset G1 to ASCII */ # define RESET_G1 "\x1b)B" /* 重置 G1 为 ASCII 字符 */
# define bSTART "\x0e" /* Enter G1 drawing mode */ # define bSTART "\x0e" /* 进入 G1 绘制模式 */
# define bSTOP "\x0f" /* Leave G1 drawing mode */ # define bSTOP "\x0f" /* 离开 G1 绘制模式 */
# define bH "q" /* Horizontal line */ # define bH "q" /* 水平线 */
# define bV "x" /* Vertical line */ # define bV "x" /* 垂直线 */
# define bLT "l" /* Left top corner */ # define bLT "l" /* 左上角 */
# define bRT "k" /* Right top corner */ # define bRT "k" /* 右上角 */
# define bLB "m" /* Left bottom corner */ # define bLB "m" /* 左下角 */
# define bRB "j" /* Right bottom corner */ # define bRB "j" /* 右下角 */
# define bX "n" /* Cross */ # define bX "n" /* 交叉点 */
# define bVR "t" /* Vertical, branch right */ # define bVR "t" /* 垂直,右分支 */
# define bVL "u" /* Vertical, branch left */ # define bVL "u" /* 垂直,左分支 */
# define bHT "v" /* Horizontal, branch top */ # define bHT "v" /* 水平,顶部分支 */
# define bHB "w" /* Horizontal, branch bottom */ # define bHB "w" /* 水平,底部分支 */
#else #else
# define SET_G1 "" # define SET_G1 "" // 不使用 G1 绘制框
# define RESET_G1 "" # define RESET_G1 ""
# define bSTART "" # define bSTART ""
# define bSTOP "" # define bSTOP ""
@ -152,107 +152,105 @@
#endif /* ^FANCY_BOXES */ #endif /* ^FANCY_BOXES */
/*********************** /***********************
* Misc terminal codes * * *
***********************/ ***********************/
#define TERM_HOME "\x1b[H" #define TERM_HOME "\x1b[H" // 移动光标到屏幕左上角
#define TERM_CLEAR TERM_HOME "\x1b[2J" #define TERM_CLEAR TERM_HOME "\x1b[2J" // 清除屏幕
#define cEOL "\x1b[0K" #define cEOL "\x1b[0K" // 清除光标到行尾
#define CURSOR_HIDE "\x1b[?25l" #define CURSOR_HIDE "\x1b[?25l" // 隐藏光标
#define CURSOR_SHOW "\x1b[?25h" #define CURSOR_SHOW "\x1b[?25h" // 显示光标
/************************ /************************
* Debug & error macros * * *
************************/ ************************/
/* Just print stuff to the appropriate stream. */ /* 只是将内容打印到适当的输出流。 */
#ifdef MESSAGES_TO_STDOUT #ifdef MESSAGES_TO_STDOUT
# define SAYF(x...) printf(x) # define SAYF(x...) printf(x) // 输出到标准输出
#else #else
# define SAYF(x...) fprintf(stderr, x) # define SAYF(x...) fprintf(stderr, x) // 输出到标准错误
#endif /* ^MESSAGES_TO_STDOUT */ #endif /* ^MESSAGES_TO_STDOUT */
/* Show a prefixed warning. */ /* 显示带前缀的警告信息。 */
#define WARNF(x...) do { \ #define WARNF(x...) do { \
SAYF(cYEL "[!] " cBRI "WARNING: " cRST x); \ SAYF(cYEL "[!] " cBRI "警告: " cRST x); \
SAYF(cRST "\n"); \ SAYF(cRST "\n"); \
} while (0) } while (0)
/* Show a prefixed "doing something" message. */ /* 显示带前缀的"正在做某事"消息。 */
#define ACTF(x...) do { \ #define ACTF(x...) do { \
SAYF(cLBL "[*] " cRST x); \ SAYF(cLBL "[*] " cRST x); \
SAYF(cRST "\n"); \ SAYF(cRST "\n"); \
} while (0) } while (0)
/* Show a prefixed "success" message. */ /* 显示带前缀的"成功"消息。 */
#define OKF(x...) do { \ #define OKF(x...) do { \
SAYF(cLGN "[+] " cRST x); \ SAYF(cLGN "[+] " cRST x); \
SAYF(cRST "\n"); \ SAYF(cRST "\n"); \
} while (0) } while (0)
/* Show a prefixed fatal error message (not used in afl). */ /* 显示带前缀的致命错误消息(未在 afl 中使用)。 */
#define BADF(x...) do { \ #define BADF(x...) do { \
SAYF(cLRD "\n[-] " cRST x); \ SAYF(cLRD "\n[-] " cRST x); \
SAYF(cRST "\n"); \ SAYF(cRST "\n"); \
} while (0) } while (0)
/* Die with a verbose non-OS fatal error message. */ /* 带有详细非操作系统致命错误消息退出程序。 */
#define FATAL(x...) do { \ #define FATAL(x...) do { \
SAYF(bSTOP RESET_G1 CURSOR_SHOW cRST cLRD "\n[-] PROGRAM ABORT : " \ SAYF(bSTOP RESET_G1 CURSOR_SHOW cRST cLRD "\n[-] 程序中止 : " \
cBRI x); \ cBRI x); \
SAYF(cLRD "\n Location : " cRST "%s(), %s:%u\n\n", \ SAYF(cLRD "\n 位置 : " cRST "%s(), %s:%u\n\n", \
__FUNCTION__, __FILE__, __LINE__); \ __FUNCTION__, __FILE__, __LINE__); \
exit(1); \ exit(1); \
} while (0) } while (0)
/* Die by calling abort() to provide a core dump. */ /* 通过调用 abort() 以提供核心转储而退出。 */
#define ABORT(x...) do { \ #define ABORT(x...) do { \
SAYF(bSTOP RESET_G1 CURSOR_SHOW cRST cLRD "\n[-] PROGRAM ABORT : " \ SAYF(bSTOP RESET_G1 CURSOR_SHOW cRST cLRD "\n[-] 程序中止 : " \
cBRI x); \ cBRI x); \
SAYF(cLRD "\n Stop location : " cRST "%s(), %s:%u\n\n", \ SAYF(cLRD "\n 停止位置 : " cRST "%s(), %s:%u\n\n", \
__FUNCTION__, __FILE__, __LINE__); \ __FUNCTION__, __FILE__, __LINE__); \
abort(); \ abort(); \
} while (0) } while (0)
/* Die while also including the output of perror(). */ /* 在包含 perror() 输出的同时终止程序。 */
#define PFATAL(x...) do { \ #define PFATAL(x...) do { \
fflush(stdout); \ fflush(stdout); \
SAYF(bSTOP RESET_G1 CURSOR_SHOW cRST cLRD "\n[-] SYSTEM ERROR : " \ SAYF(bSTOP RESET_G1 CURSOR_SHOW cRST cLRD "\n[-] 系统错误 : " \
cBRI x); \ cBRI x); \
SAYF(cLRD "\n Stop location : " cRST "%s(), %s:%u\n", \ SAYF(cLRD "\n 停止位置 : " cRST "%s(), %s:%u\n", \
__FUNCTION__, __FILE__, __LINE__); \ __FUNCTION__, __FILE__, __LINE__); \
SAYF(cLRD " OS message : " cRST "%s\n", strerror(errno)); \ SAYF(cLRD " 操作系统消息 : " cRST "%s\n", strerror(errno)); \
exit(1); \ exit(1); \
} while (0) } while (0)
/* Die with FAULT() or PFAULT() depending on the value of res (used to /* 根据 res 的值(用于解释 read()、write() 等的不同失败模式)调用 FATAL() 或 PFATAL()。 */
interpret different failure modes for read(), write(), etc). */
#define RPFATAL(res, x...) do { \ #define RPFATAL(res, x...) do { \
if (res < 0) PFATAL(x); else FATAL(x); \ if (res < 0) PFATAL(x); else FATAL(x); \
} while (0) } while (0)
/* Error-checking versions of read() and write() that call RPFATAL() as /* 检查错误的 read() 和 write() 的版本,在适当的情况下调用 RPFATAL()。 */
appropriate. */
#define ck_write(fd, buf, len, fn) do { \ #define ck_write(fd, buf, len, fn) do { \
u32 _len = (len); \ u32 _len = (len); \
s32 _res = write(fd, buf, _len); \ s32 _res = write(fd, buf, _len); \
if (_res != _len) RPFATAL(_res, "Short write to %s", fn); \ if (_res != _len) RPFATAL(_res, "对 %s 的短写入", fn); \
} while (0) } while (0)
#define ck_read(fd, buf, len, fn) do { \ #define ck_read(fd, buf, len, fn) do { \
u32 _len = (len); \ u32 _len = (len); \
s32 _res = read(fd, buf, _len); \ s32 _res = read(fd, buf, _len); \
if (_res != _len) RPFATAL(_res, "Short read from %s", fn); \ if (_res != _len) RPFATAL(_res, "对 %s 的短读取", fn); \
} while (0) } while (0)
#endif /* ! _HAVE_DEBUG_H */ #endif /* ! _HAVE_DEBUG_H */

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

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

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

@ -45,210 +45,453 @@ static u8** cc_params; /* Parameters passed to the real CC */
static u32 cc_par_cnt = 1; /* Param count, including argv0 */ static u32 cc_par_cnt = 1; /* Param count, including argv0 */
/* Try to find the runtime libraries. If that fails, abort. */ /*
*/
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); })"; // 调用持久化函数,并结束代码块
// 定义一个宏,用于初始化 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) { static void find_obj(u8* argv0) {
// 获取环境变量 AFL_PATH
u8 *afl_path = getenv("AFL_PATH"); u8 *afl_path = getenv("AFL_PATH");
u8 *slash, *tmp; u8 *slash, *tmp;
// 如果找到 AFL_PATH
if (afl_path) { if (afl_path) {
// 生成 afl-llvm-rt.o 的完整路径
tmp = alloc_printf("%s/afl-llvm-rt.o", afl_path); tmp = alloc_printf("%s/afl-llvm-rt.o", afl_path);
// 检查文件是否可读
if (!access(tmp, R_OK)) { if (!access(tmp, R_OK)) {
obj_path = afl_path; obj_path = afl_path; // 设置对象路径为 AFL_PATH
ck_free(tmp); ck_free(tmp); // 释放临时路径内存
return; return; // 找到文件,结束函数
} }
ck_free(tmp); ck_free(tmp); // 释放临时路径内存
} }
// 查找 argv0 中最后一个 '/' 的位置
slash = strrchr(argv0, '/'); slash = strrchr(argv0, '/');
// 如果找到 '/'
if (slash) { if (slash) {
u8 *dir; u8 *dir;
*slash = 0; *slash = 0; // 将 '/' 替换为结束符,以获取目录
dir = ck_strdup(argv0); dir = ck_strdup(argv0); // 复制目录名
*slash = '/'; *slash = '/'; // 恢复原来的 '/' 字符
// 生成 afl-llvm-rt.o 的完整路径
tmp = alloc_printf("%s/afl-llvm-rt.o", dir); tmp = alloc_printf("%s/afl-llvm-rt.o", dir);
// 检查文件是否可读
if (!access(tmp, R_OK)) { if (!access(tmp, R_OK)) {
obj_path = dir; obj_path = dir; // 设置对象路径为找到的目录
ck_free(tmp); ck_free(tmp); // 释放临时路径内存
return; return; // 找到文件,结束函数
} }
ck_free(tmp); ck_free(tmp); // 释放临时路径内存
ck_free(dir); ck_free(dir); // 释放目录名内存
} }
// 检查默认路径下的 afl-llvm-rt.o 是否可读
if (!access(AFL_PATH "/afl-llvm-rt.o", R_OK)) { if (!access(AFL_PATH "/afl-llvm-rt.o", R_OK)) {
obj_path = AFL_PATH; obj_path = AFL_PATH; // 设置对象路径为默认的 AFL_PATH
return; return; // 找到文件,结束函数
} }
// 如果都找不到,则抛出致命错误
FATAL("Unable to find 'afl-llvm-rt.o' or 'afl-llvm-pass.so'. Please set AFL_PATH"); FATAL("Unable to find 'afl-llvm-rt.o' or 'afl-llvm-pass.so'. Please set AFL_PATH");
} }
/* Copy argv to cc_params, making the necessary edits. */ /* 复制 argv 到 cc_params并进行必要的编辑。 */
static void edit_params(u32 argc, char** argv) { static void edit_params(u32 argc, char** argv) {
// 初始化标志变量
u8 fortify_set = 0, asan_set = 0, x_set = 0, bit_mode = 0; u8 fortify_set = 0, asan_set = 0, x_set = 0, bit_mode = 0;
u8 *name; u8 *name;
// 分配空间以存储参数
cc_params = ck_alloc((argc + 128) * sizeof(u8*)); cc_params = ck_alloc((argc + 128) * sizeof(u8*));
// 获取执行的程序名称
name = strrchr(argv[0], '/'); name = strrchr(argv[0], '/');
if (!name) name = argv[0]; else name++; if (!name) name = argv[0]; else name++;
// 根据程序名称选择合适的编译器
if (!strcmp(name, "afl-clang-fast++")) { if (!strcmp(name, "afl-clang-fast++")) {
u8* alt_cxx = getenv("AFL_CXX"); u8* alt_cxx = getenv("AFL_CXX"); // 获取环境变量 AFL_CXX
cc_params[0] = alt_cxx ? alt_cxx : (u8*)"clang++"; cc_params[0] = alt_cxx ? alt_cxx : (u8*)"clang++"; // 设置 C++ 编译器
} else { } else {
u8* alt_cc = getenv("AFL_CC"); u8* alt_cc = getenv("AFL_CC"); // 获取环境变量 AFL_CC
cc_params[0] = alt_cc ? alt_cc : (u8*)"clang"; cc_params[0] = alt_cc ? alt_cc : (u8*)"clang"; // 设置 C 编译器
} }
/* 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 #ifdef USE_TRACE_PC
// 启用跟踪 PC 的覆盖率
cc_params[cc_par_cnt++] = "-fsanitize-coverage=trace-pc-guard"; cc_params[cc_par_cnt++] = "-fsanitize-coverage=trace-pc-guard";
#ifndef __ANDROID__ #ifndef __ANDROID__
cc_params[cc_par_cnt++] = "-mllvm"; cc_params[cc_par_cnt++] = "-mllvm"; // LLVM 选项
cc_params[cc_par_cnt++] = "-sanitizer-coverage-block-threshold=0"; cc_params[cc_par_cnt++] = "-sanitizer-coverage-block-threshold=0"; // 设定阈值
#endif #endif
#else #else
// 加载 afl-llvm-pass.so 插件
cc_params[cc_par_cnt++] = "-Xclang"; cc_params[cc_par_cnt++] = "-Xclang";
cc_params[cc_par_cnt++] = "-load"; cc_params[cc_par_cnt++] = "-load";
cc_params[cc_par_cnt++] = "-Xclang"; cc_params[cc_par_cnt++] = "-Xclang";
cc_params[cc_par_cnt++] = alloc_printf("%s/afl-llvm-pass.so", obj_path); cc_params[cc_par_cnt++] = alloc_printf("%s/afl-llvm-pass.so", obj_path);
#endif /* ^USE_TRACE_PC */ #endif /* ^USE_TRACE_PC */
cc_params[cc_par_cnt++] = "-Qunused-arguments"; cc_params[cc_par_cnt++] = "-Qunused-arguments"; // 忽略未使用的参数
// 循环处理输入参数
while (--argc) { while (--argc) {
u8* cur = *(++argv); u8* cur = *(++argv); // 获取当前参数
// 检查位模式
if (!strcmp(cur, "-m32")) bit_mode = 32; if (!strcmp(cur, "-m32")) bit_mode = 32;
if (!strcmp(cur, "armv7a-linux-androideabi")) bit_mode = 32; if (!strcmp(cur, "armv7a-linux-androideabi")) bit_mode = 32;
if (!strcmp(cur, "-m64")) bit_mode = 64; if (!strcmp(cur, "-m64")) bit_mode = 64;
// 检查其他编译选项
if (!strcmp(cur, "-x")) x_set = 1; if (!strcmp(cur, "-x")) x_set = 1;
// 检查是否启用地址/内存的 sanitization
if (!strcmp(cur, "-fsanitize=address") || if (!strcmp(cur, "-fsanitize=address") ||
!strcmp(cur, "-fsanitize=memory")) asan_set = 1; !strcmp(cur, "-fsanitize=memory")) asan_set = 1;
// 检查 FORTIFY_SOURCE 是否启用
if (strstr(cur, "FORTIFY_SOURCE")) fortify_set = 1; if (strstr(cur, "FORTIFY_SOURCE")) fortify_set = 1;
// 跳过某些链接器选项
if (!strcmp(cur, "-Wl,-z,defs") || if (!strcmp(cur, "-Wl,-z,defs") ||
!strcmp(cur, "-Wl,--no-undefined")) continue; !strcmp(cur, "-Wl,--no-undefined")) continue;
// 将当前参数添加到 cc_params
cc_params[cc_par_cnt++] = cur; cc_params[cc_par_cnt++] = cur;
} }
// 如果启用了硬化选项
if (getenv("AFL_HARDEN")) { if (getenv("AFL_HARDEN")) {
cc_params[cc_par_cnt++] = "-fstack-protector-all"; // 启用栈保护
cc_params[cc_par_cnt++] = "-fstack-protector-all"; // 如果没有启用 FORTIFY_SOURCE 则添加对应宏定义
if (!fortify_set) if (!fortify_set)
cc_params[cc_par_cnt++] = "-D_FORTIFY_SOURCE=2"; cc_params[cc_par_cnt++] = "-D_FORTIFY_SOURCE=2";
} }
// 检查地址或内存 sanitization 的设置
if (!asan_set) { if (!asan_set) {
if (getenv("AFL_USE_ASAN")) { if (getenv("AFL_USE_ASAN")) {
if (getenv("AFL_USE_MSAN")) if (getenv("AFL_USE_MSAN"))
FATAL("ASAN and MSAN are mutually exclusive"); FATAL("ASAN and MSAN are mutually exclusive"); // 互斥检查
if (getenv("AFL_HARDEN")) if (getenv("AFL_HARDEN"))
FATAL("ASAN and AFL_HARDEN are mutually exclusive"); FATAL("ASAN and AFL_HARDEN are mutually exclusive"); // 互斥检查
cc_params[cc_par_cnt++] = "-U_FORTIFY_SOURCE"; // 禁用 FORTIFY_SOURCE
cc_params[cc_par_cnt++] = "-U_FORTIFY_SOURCE"; cc_params[cc_par_cnt++] = "-fsanitize=address"; // 启用地址条件检测
cc_params[cc_par_cnt++] = "-fsanitize=address";
} else if (getenv("AFL_USE_MSAN")) { } else if (getenv("AFL_USE_MSAN")) {
if (getenv("AFL_USE_ASAN")) if (getenv("AFL_USE_ASAN"))
FATAL("ASAN and MSAN are mutually exclusive"); FATAL("ASAN and MSAN are mutually exclusive"); // 互斥检查
if (getenv("AFL_HARDEN")) if (getenv("AFL_HARDEN"))
FATAL("MSAN and AFL_HARDEN are mutually exclusive"); FATAL("MSAN and AFL_HARDEN are mutually exclusive"); // 互斥检查
cc_params[cc_par_cnt++] = "-U_FORTIFY_SOURCE"; // 禁用 FORTIFY_SOURCE
cc_params[cc_par_cnt++] = "-U_FORTIFY_SOURCE"; cc_params[cc_par_cnt++] = "-fsanitize=memory"; // 启用内存条件检测
cc_params[cc_par_cnt++] = "-fsanitize=memory";
} }
} }
#ifdef USE_TRACE_PC #ifdef USE_TRACE_PC
// 检查 AFL_INST_RATIO 环境变量的设置
if (getenv("AFL_INST_RATIO")) if (getenv("AFL_INST_RATIO"))
FATAL("AFL_INST_RATIO not available at compile time with 'trace-pc'."); 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";
} #endif /* USE_TRACE_PC */
if (getenv("AFL_NO_BUILTIN")) { // 检查是否不优化
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")) {
// 禁用特定的内置字符串比较和内存比较函数
cc_params[cc_par_cnt++] = "-fno-builtin-strcmp"; cc_params[cc_par_cnt++] = "-fno-builtin-strcmp";
cc_params[cc_par_cnt++] = "-fno-builtin-strncmp"; cc_params[cc_par_cnt++] = "-fno-builtin-strncmp";
cc_params[cc_par_cnt++] = "-fno-builtin-strcasecmp"; cc_params[cc_par_cnt++] = "-fno-builtin-strcasecmp";
cc_params[cc_par_cnt++] = "-fno-builtin-strncasecmp"; cc_params[cc_par_cnt++] = "-fno-builtin-strncasecmp";
cc_params[cc_par_cnt++] = "-fno-builtin-memcmp"; 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_HAVE_MANUAL_CONTROL=1"; cc_params[cc_par_cnt++] = "-D__AFL_COMPILER=1"; // 标记为 AFL 编译器
cc_params[cc_par_cnt++] = "-D__AFL_COMPILER=1"; cc_params[cc_par_cnt++] = "-DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION=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.
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)=" // 添加 AFL 循环的实现
cc_params[cc_par_cnt++] = "-D__AFL_LOOP(_A)="
"({ static volatile char *_B __attribute__((used)); " "({ static volatile char *_B __attribute__((used)); "
" _B = (char*)\"" PERSIST_SIG "\"; " " _B = (char*)\"" PERSIST_SIG "\"; "
#ifdef __APPLE__ #ifdef __APPLE__
@ -258,9 +501,10 @@ static void edit_params(u32 argc, char** argv) {
"__attribute__((visibility(\"default\"))) " "__attribute__((visibility(\"default\"))) "
"int _L(unsigned int) __asm__(\"__afl_persistent_loop\"); " "int _L(unsigned int) __asm__(\"__afl_persistent_loop\"); "
#endif /* ^__APPLE__ */ #endif /* ^__APPLE__ */
"_L(_A); })"; "_L(_A); })"; // 定义 AFL 循环的宏
cc_params[cc_par_cnt++] = "-D__AFL_INIT()=" // 添加 AFL 初始化函数的实现
cc_params[cc_par_cnt++] = "-D__AFL_INIT()="
"do { static volatile char *_A __attribute__((used)); " "do { static volatile char *_A __attribute__((used)); "
" _A = (char*)\"" DEFER_SIG "\"; " " _A = (char*)\"" DEFER_SIG "\"; "
#ifdef __APPLE__ #ifdef __APPLE__
@ -270,58 +514,57 @@ static void edit_params(u32 argc, char** argv) {
"__attribute__((visibility(\"default\"))) " "__attribute__((visibility(\"default\"))) "
"void _I(void) __asm__(\"__afl_manual_init\"); " "void _I(void) __asm__(\"__afl_manual_init\"); "
#endif /* ^__APPLE__ */ #endif /* ^__APPLE__ */
"_I(); } while (0)"; "_I(); } while (0)"; // 定义 AFL 初始化的宏
if (x_set) { // 如果启用 -x 选项
cc_params[cc_par_cnt++] = "-x"; if (x_set) {
cc_params[cc_par_cnt++] = "none"; cc_params[cc_par_cnt++] = "-x"; // 添加 -x 选项
} cc_params[cc_par_cnt++] = "none"; // 设置为 none表示不对输入进行特定语言解析
}
#ifndef __ANDROID__ #ifndef __ANDROID__
switch (bit_mode) { // 根据位模式选择不同的运行时库文件
switch (bit_mode) {
case 0: case 0:
cc_params[cc_par_cnt++] = alloc_printf("%s/afl-llvm-rt.o", obj_path); cc_params[cc_par_cnt++] = alloc_printf("%s/afl-llvm-rt.o", obj_path); // 默认情况下使用通用的运行时文件
break; break;
case 32: case 32:
cc_params[cc_par_cnt++] = alloc_printf("%s/afl-llvm-rt-32.o", obj_path); 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)) if (access(cc_params[cc_par_cnt - 1], R_OK))
FATAL("-m32 is not supported by your compiler"); FATAL("-m32 is not supported by your compiler"); // 如果不可读则报错
break;
break;
case 64: case 64:
cc_params[cc_par_cnt++] = alloc_printf("%s/afl-llvm-rt-64.o", obj_path); 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)) if (access(cc_params[cc_par_cnt - 1], R_OK))
FATAL("-m64 is not supported by your compiler"); FATAL("-m64 is not supported by your compiler"); // 如果不可读则报错
break;
break;
}
#endif
cc_params[cc_par_cnt] = NULL;
} }
// 结束参数数组,以空指针结尾
cc_params[cc_par_cnt] = NULL;
/* Main entry point */
/* 主入口点 */
int main(int argc, char** argv) { int main(int argc, char** argv) {
// 检查标准错误是否连接到终端且 AFL_QUIET 环境变量未设置
if (isatty(2) && !getenv("AFL_QUIET")) { if (isatty(2) && !getenv("AFL_QUIET")) {
#ifdef USE_TRACE_PC #ifdef USE_TRACE_PC
// 如果启用了 USE_TRACE_PC打印版本信息
SAYF(cCYA "afl-clang-fast [tpcg] " cBRI VERSION cRST " by <lszekeres@google.com>\n"); SAYF(cCYA "afl-clang-fast [tpcg] " cBRI VERSION cRST " by <lszekeres@google.com>\n");
#else #else
// 否则,仅打印版本信息
SAYF(cCYA "afl-clang-fast " cBRI VERSION cRST " by <lszekeres@google.com>\n"); SAYF(cCYA "afl-clang-fast " cBRI VERSION cRST " by <lszekeres@google.com>\n");
#endif /* ^USE_TRACE_PC */ #endif /* ^USE_TRACE_PC */
} }
// 如果参数数量小于 2打印帮助信息并退出
if (argc < 2) { if (argc < 2) {
SAYF("\n" SAYF("\n"
@ -339,21 +582,23 @@ int main(int argc, char** argv) {
"AFL_HARDEN enables hardening optimizations in the compiled code.\n\n", "AFL_HARDEN enables hardening optimizations in the compiled code.\n\n",
BIN_PATH, BIN_PATH); BIN_PATH, BIN_PATH);
exit(1); exit(1); // 退出程序,返回错误码 1
} }
#ifndef __ANDROID__ #ifndef __ANDROID__
// 在非 Android 平台下调用 find_obj 函数查找运行时库
find_obj(argv[0]); find_obj(argv[0]);
#endif #endif
// 调用 edit_params 函数处理命令行参数并设置编译参数
edit_params(argc, argv); edit_params(argc, argv);
// 使用 execvp 执行指定的编译器命令
execvp(cc_params[0], (char**)cc_params); execvp(cc_params[0], (char**)cc_params);
// 如果 execvp 失败,抛出致命错误并打印相关信息
FATAL("Oops, failed to execute '%s' - check your PATH", cc_params[0]); FATAL("Oops, failed to execute '%s' - check your PATH", cc_params[0]);
return 0; return 0; // 正常结束程序
} }

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

Loading…
Cancel
Save