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",
"*.wxml": "html",
"*.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

@ -1,557 +1,519 @@
/*
Copyright 2013 Google LLC All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at:
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
/*
american fuzzy lop - wrapper for GNU as
---------------------------------------
Written and maintained by Michal Zalewski <lcamtuf@google.com>
The sole purpose of this wrapper is to preprocess assembly files generated
by GCC / clang and inject the instrumentation bits included from afl-as.h. It
is automatically invoked by the toolchain when compiling programs using
afl-gcc / afl-clang.
Note that it's an explicit non-goal to instrument hand-written assembly,
be it in separate .s files or in __asm__ blocks. The only aspiration this
utility has right now is to be able to skip them gracefully and allow the
compilation process to continue.
That said, see experimental/clang_asm_normalize/ for a solution that may
allow clang users to make things work even with hand-crafted assembly. Just
note that there is no equivalent for GCC.
*/
#define AFL_MAIN
#include "config.h"
#include "types.h"
#include "debug.h"
#include "alloc-inl.h"
#include "afl-as.h"
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <ctype.h>
#include <fcntl.h>
#include <sys/wait.h>
#include <sys/time.h>
static u8** as_params; /* Parameters passed to the real 'as' */
static u8* input_file; /* Originally specified input file */
static u8* modified_file; /* Instrumented file for the real 'as' */
static u8 be_quiet, /* Quiet mode (no stderr output) */
clang_mode, /* Running in clang mode? */
pass_thru, /* Just pass data through? */
just_version, /* Just show version? */
sanitizer; /* Using ASAN / MSAN */
static u32 inst_ratio = 100, /* Instrumentation probability (%) */
as_par_cnt = 1; /* Number of params to 'as' */
/* If we don't find --32 or --64 in the command line, default to
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
static u8 use_64bit = 1;
#else
static u8 use_64bit = 0;
#ifdef __APPLE__
# error "Sorry, 32-bit Apple platforms are not supported."
#define AFL_MAIN // 定义主程序宏
#include "config.h" // 包含配置文件
#include "types.h" // 包含类型定义
#include "debug.h" // 包含调试工具
#include "alloc-inl.h" // 包含内存分配工具
#include "afl-as.h" // 包含AFL汇编插桩头文件
#include <stdio.h> // 标准输入输出
#include <unistd.h> // POSIX标准库
#include <stdlib.h> // 标准库
#include <string.h> // 字符串处理
#include <time.h> // 时间处理
#include <ctype.h> // 字符处理
#include <fcntl.h> // 文件控制
#include <sys/wait.h> // 进程等待
#include <sys/time.h> // 时间处理
static u8** as_params; // 传递给真实 'as' 的参数
static u8* input_file; // 原始输入文件
static u8* modified_file; // 插桩后的文件
static u8 be_quiet, // 静默模式(不输出错误信息)
clang_mode, // 是否在clang模式下运行
pass_thru, // 是否直接传递数据
just_version, // 是否只显示版本
sanitizer; // 是否使用ASAN / MSAN
static u32 inst_ratio = 100, // 插桩概率(%
as_par_cnt = 1; // 传递给 'as' 的参数数量
// 如果命令行中没有找到 --32 或 --64 参数,则默认对编译该工具时使用的模式进行插桩。
// 这不是完美的,但对于大多数使用场景来说已经足够了。
#ifdef WORD_SIZE_64 // 如果是64位系统
static u8 use_64bit = 1; // 使用64位模式
#else // 否则
static u8 use_64bit = 0; // 使用32位模式
#ifdef __APPLE__ // 如果是苹果系统
# error "Sorry, 32-bit Apple platforms are not supported." // 不支持32位苹果平台
#endif /* __APPLE__ */
#endif /* ^WORD_SIZE_64 */
/* Examine and modify parameters to pass to 'as'. Note that the file name
is always the last parameter passed by GCC, so we exploit this property
to keep the code simple. */
// 检查并修改传递给 'as' 的参数。注意 GCC 总是将文件名作为最后一个参数传递给 'as'
// 因此我们利用这一特性来简化代码。
static void edit_params(int argc, char** argv) {
u8 *tmp_dir = getenv("TMPDIR"), *afl_as = getenv("AFL_AS");
u32 i;
#ifdef __APPLE__
u8 use_clang_as = 0;
/* On MacOS X, the Xcode cctool 'as' driver is a bit stale and does not work
with the code generated by newer versions of clang that are hand-built
by the user. See the thread here: http://goo.gl/HBWDtn.
To work around this, when using clang and running without AFL_AS
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
to Nico Weber for the idea. */
if (clang_mode && !afl_as) {
use_clang_as = 1;
afl_as = getenv("AFL_CC");
if (!afl_as) afl_as = getenv("AFL_CXX");
if (!afl_as) afl_as = "clang";
u8 *tmp_dir = getenv("TMPDIR"), *afl_as = getenv("AFL_AS"); // 获取临时目录和AFL_AS环境变量
u32 i; // 循环变量
#ifdef __APPLE__ // 如果是苹果系统
u8 use_clang_as = 0; // 是否使用clang作为汇编器
// 在 MacOS X 上Xcode cctool 'as' 驱动程序有点过时,无法处理由用户自己编译的较新版本的 clang
// 生成的代码。详见此线程http://goo.gl/HBWDtn.
// 为了绕过这一问题,当使用 clang 且未指定 AFL_AS 时,我们将实际调用 'clang -c' 而不是 'as -q'
// 来编译汇编文件.
// 虽然这两个工具不是命令行兼容的,但我们可以通过进行一些小的修改来让它们在某些情况下似乎可以很好地协同工作。
// 感谢 Nico Weber 提出这一思路。
if (clang_mode && !afl_as) { // 如果是clang模式且没有指定AFL_AS
use_clang_as = 1; // 使用clang作为汇编器
afl_as = getenv("AFL_CC"); // 获取AFL_CC环境变量
if (!afl_as) afl_as = getenv("AFL_CXX"); // 如果没有AFL_CC则获取AFL_CXX
if (!afl_as) afl_as = "clang"; // 如果都没有则默认使用clang
}
#endif /* __APPLE__ */
/* Although this is not documented, GCC also uses TEMP and TMP when TMPDIR
is not set. We need to check these non-standard variables to properly
handle the pass_thru logic later on. */
if (!tmp_dir) tmp_dir = getenv("TEMP");
if (!tmp_dir) tmp_dir = getenv("TMP");
if (!tmp_dir) tmp_dir = "/tmp";
as_params = ck_alloc((argc + 32) * sizeof(u8*));
as_params[0] = afl_as ? afl_as : (u8*)"as";
as_params[argc] = 0;
for (i = 1; i < argc - 1; i++) {
if (!strcmp(argv[i], "--64")) use_64bit = 1;
else if (!strcmp(argv[i], "--32")) use_64bit = 0;
#ifdef __APPLE__
/* The Apple case is a bit different... */
if (!strcmp(argv[i], "-arch") && i + 1 < argc) {
if (!strcmp(argv[i + 1], "x86_64")) use_64bit = 1;
else if (!strcmp(argv[i + 1], "i386"))
FATAL("Sorry, 32-bit Apple platforms are not supported.");
// 虽然这在文档中没有提及,但 GCC 实际上也使用 TEMP 和 TMP当 TMPDIR 未设置时)。
// 我们需要检查这些非常规变量以正确处理 pass_thru 逻辑。
if (!tmp_dir) tmp_dir = getenv("TEMP"); // 如果没有TMPDIR则获取TEMP
if (!tmp_dir) tmp_dir = getenv("TMP"); // 如果没有TEMP则获取TMP
if (!tmp_dir) tmp_dir = "/tmp"; // 如果都没有,则默认使用/tmp
as_params = ck_alloc((argc + 32) * sizeof(u8*)); // 分配参数数组内存
as_params[0] = afl_as ? afl_as : (u8*)"as"; // 设置第一个参数为AFL_AS或默认的as
as_params[argc] = 0; // 设置最后一个参数为NULL
for (i = 1; i < argc - 1; i++) { // 遍历参数
if (!strcmp(argv[i], "--64")) use_64bit = 1; // 如果参数是--64则使用64位模式
else if (!strcmp(argv[i], "--32")) use_64bit = 0; // 如果参数是--32则使用32位模式
#ifdef __APPLE__ // 如果是苹果系统
// MacOS X 的情况有点不同...
if (!strcmp(argv[i], "-arch") && i + 1 < argc) { // 如果参数是-arch
if (!strcmp(argv[i + 1], "x86_64")) use_64bit = 1; // 如果架构是x86_64则使用64位模式
else if (!strcmp(argv[i + 1], "i386")) // 如果架构是i386
FATAL("Sorry, 32-bit Apple platforms are not supported."); // 不支持32位苹果平台
}
/* Strip options that set the preference for a particular upstream
assembler in Xcode. */
if (clang_mode && (!strcmp(argv[i], "-q") || !strcmp(argv[i], "-Q")))
continue;
// 移除 Xcode 中设置特定上游汇编器的选项
if (clang_mode && (!strcmp(argv[i], "-q") || !strcmp(argv[i], "-Q"))) // 如果是clang模式且参数是-q或-Q
continue; // 跳过这些参数
#endif /* __APPLE__ */
as_params[as_par_cnt++] = argv[i];
as_params[as_par_cnt++] = argv[i]; // 将参数添加到as_params数组中
}
#ifdef __APPLE__
/* When calling clang as the upstream assembler, append -c -x assembler
and hope for the best. */
if (use_clang_as) {
as_params[as_par_cnt++] = "-c";
as_params[as_par_cnt++] = "-x";
as_params[as_par_cnt++] = "assembler";
#ifdef __APPLE__ // 如果是苹果系统
// 当调用 clang 作为上游汇编器时,追加 -c -x assembler 选项并希望一切顺利。
if (use_clang_as) { // 如果使用clang作为汇编器
as_params[as_par_cnt++] = "-c"; // 添加-c参数
as_params[as_par_cnt++] = "-x"; // 添加-x参数
as_params[as_par_cnt++] = "assembler"; // 添加assembler参数
}
#endif /* __APPLE__ */
input_file = argv[argc - 1];
if (input_file[0] == '-') {
if (!strcmp(input_file + 1, "-version")) {
just_version = 1;
modified_file = input_file;
goto wrap_things_up;
input_file = argv[argc - 1]; // 获取输入文件
if (input_file[0] == '-') { // 如果输入文件是标准输入
if (!strcmp(input_file + 1, "-version")) { // 如果参数是-version
just_version = 1; // 设置只显示版本
modified_file = input_file; // 设置修改后的文件为输入文件
goto wrap_things_up; // 跳转到结束处理
}
if (input_file[1]) FATAL("Incorrect use (not called through afl-gcc?)");
else input_file = NULL;
} else {
/* Check if this looks like a standard invocation as a part of an attempt
to compile a program, rather than using gcc on an ad-hoc .s file in
a format we may not understand. This works around an issue compiling
NSS. */
if (strncmp(input_file, tmp_dir, strlen(tmp_dir)) &&
strncmp(input_file, "/var/tmp/", 9) &&
strncmp(input_file, "/tmp/", 5)) pass_thru = 1;
if (input_file[1]) FATAL("Incorrect use (not called through afl-gcc?)"); // 如果输入文件不是标准输入
else input_file = NULL; // 否则设置为NULL
} else { // 如果输入文件不是标准输入
// 检查是否为标准调用,作为编译程序的一部分,而不是使用 gcc 对一个独立的 .s 文件进行编译。
// 这解决了在编译 NSS 时遇到的问题。
if (strncmp(input_file, tmp_dir, strlen(tmp_dir)) && // 如果输入文件不在临时目录
strncmp(input_file, "/var/tmp/", 9) && // 且不在/var/tmp/
strncmp(input_file, "/tmp/", 5)) pass_thru = 1; // 且不在/tmp/则设置为pass_thru模式
}
modified_file = alloc_printf("%s/.afl-%u-%u.s", tmp_dir, getpid(),
modified_file = alloc_printf("%s/.afl-%u-%u.s", tmp_dir, getpid(), // 生成修改后的文件名
(u32)time(NULL));
wrap_things_up:
as_params[as_par_cnt++] = modified_file;
as_params[as_par_cnt] = NULL;
wrap_things_up: // 结束处理
as_params[as_par_cnt++] = modified_file; // 将修改后的文件添加到参数数组
as_params[as_par_cnt] = NULL; // 设置最后一个参数为NULL
}
/* Process input file, generate modified_file. Insert instrumentation in all
the appropriate places. */
// 处理输入文件并生成 modified_file。在所有适当的位置插入插桩代码。
static void add_instrumentation(void) {
static u8 line[MAX_LINE];
FILE* inf;
FILE* outf;
s32 outfd;
u32 ins_lines = 0;
u8 instr_ok = 0, skip_csect = 0, skip_next_label = 0,
static u8 line[MAX_LINE]; // 读取文件的缓冲区
FILE* inf; // 输入文件指针
FILE* outf; // 输出文件指针
s32 outfd; // 输出文件描述符
u32 ins_lines = 0; // 插桩的行数
u8 instr_ok = 0, skip_csect = 0, skip_next_label = 0, // 插桩状态标志
skip_intel = 0, skip_app = 0, instrument_next = 0;
#ifdef __APPLE__
u8* colon_pos;
#ifdef __APPLE__ // 如果是苹果系统
u8* colon_pos; // 冒号位置
#endif /* __APPLE__ */
if (input_file) {
inf = fopen(input_file, "r");
if (!inf) PFATAL("Unable to read '%s'", input_file);
} else inf = stdin;
outfd = open(modified_file, O_WRONLY | O_EXCL | O_CREAT, 0600);
if (outfd < 0) PFATAL("Unable to write to '%s'", modified_file);
outf = fdopen(outfd, "w");
if (!outf) PFATAL("fdopen() failed");
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 (input_file) { // 如果有输入文件
inf = fopen(input_file, "r"); // 打开输入文件
if (!inf) PFATAL("Unable to read '%s'", input_file); // 如果打开失败则报错
} else inf = stdin; // 否则使用标准输入
outfd = open(modified_file, O_WRONLY | O_EXCL | O_CREAT, 0600); // 打开输出文件
if (outfd < 0) PFATAL("Unable to write to '%s'", modified_file); // 如果打开失败则报错
outf = fdopen(outfd, "w"); // 将文件描述符转换为文件指针
if (!outf) PFATAL("fdopen() failed"); // 如果转换失败则报错
while (fgets(line, MAX_LINE, inf)) { // 逐行读取输入文件
// 在某些情况下,我们希望在所有标签、宏、注释等之后再插入插桩跳板代码。
// 如果处于这一模式,且行以制表符开头,后跟一个字母,则现在插入跳板代码。
if (!pass_thru && !skip_intel && !skip_app && !skip_csect && instr_ok &&
instrument_next && line[0] == '\t' && isalpha(line[1])) {
fprintf(outf, use_64bit ? trampoline_fmt_64 : trampoline_fmt_32,
instrument_next && line[0] == '\t' && isalpha(line[1])) { // 如果满足插桩条件
fprintf(outf, use_64bit ? trampoline_fmt_64 : trampoline_fmt_32, // 插入跳板代码
R(MAP_SIZE));
instrument_next = 0;
ins_lines++;
instrument_next = 0; // 重置插桩标志
ins_lines++; // 增加插桩行数
}
/* Output the actual line, call it a day in pass-thru mode. */
fputs(line, outf);
if (pass_thru) continue;
/* All right, this is where the actual fun begins. For one, we only want to
instrument the .text section. So, let's keep track of that in processed
files - and let's set instr_ok accordingly. */
if (line[0] == '\t' && line[1] == '.') {
/* OpenBSD puts jump tables directly inline with the code, which is
a bit annoying. They use a specific format of p2align directives
around them, so we use that as a signal. */
if (!clang_mode && instr_ok && !strncmp(line + 2, "p2align ", 8) &&
isdigit(line[10]) && line[11] == '\n') skip_next_label = 1;
if (!strncmp(line + 2, "text\n", 5) ||
!strncmp(line + 2, "section\t.text", 13) ||
!strncmp(line + 2, "section\t__TEXT,__text", 21) ||
!strncmp(line + 2, "section __TEXT,__text", 21)) {
instr_ok = 1;
continue;
// 输出实际的行,在 pass-thru 模式下结束操作。
fputs(line, outf); // 输出当前行
if (pass_thru) continue; // 如果是pass-thru模式则跳过
// 现在开始真正的插桩操作。首先,我们只希望插桩 .text 部分。
// 因此,我们需要跟踪处理的汇编文件中各部分的状态,并据此设置 instr_ok。
if (line[0] == '\t' && line[1] == '.') { // 如果行以制表符和点开头
// OpenBSD 在代码中直接放置跳转表,这稍微有点麻烦。
// 它们使用特定格式的 p2align 指令围绕它们,因此我们可以使用该格式作为信号。
if (!clang_mode && instr_ok && !strncmp(line + 2, "p2align ", 8) && // 如果是OpenBSD的p2align指令
isdigit(line[10]) && line[11] == '\n') skip_next_label = 1; // 设置跳过下一个标签
if (!strncmp(line + 2, "text\n", 5) || // 如果是.text部分
!strncmp(line + 2, "section\t.text", 13) || // 或者section .text
!strncmp(line + 2, "section\t__TEXT,__text", 21) || // 或者section __TEXT,__text
!strncmp(line + 2, "section __TEXT,__text", 21)) { // 或者section __TEXT,__text
instr_ok = 1; // 设置插桩标志
continue; // 继续
}
if (!strncmp(line + 2, "section\t", 8) ||
!strncmp(line + 2, "section ", 8) ||
!strncmp(line + 2, "bss\n", 4) ||
!strncmp(line + 2, "data\n", 5)) {
instr_ok = 0;
continue;
if (!strncmp(line + 2, "section\t", 8) || // 如果是其他section
!strncmp(line + 2, "section ", 8) || // 或者其他section
!strncmp(line + 2, "bss\n", 4) || // 或者.bss部分
!strncmp(line + 2, "data\n", 5)) { // 或者.data部分
instr_ok = 0; // 重置插桩标志
continue; // 继续
}
}
/* Detect off-flavor assembly (rare, happens in gdb). When this is
encountered, we set skip_csect until the opposite directive is
seen, and we do not instrument. */
if (strstr(line, ".code")) {
if (strstr(line, ".code32")) skip_csect = use_64bit;
if (strstr(line, ".code64")) skip_csect = !use_64bit;
// 检测非常规汇编(罕见,例如在 gdb 中)。当遇到这种汇编时,我们设置 skip_csect
// 直到遇到相反的指令,此时我们不进行插桩。
if (strstr(line, ".code")) { // 如果行包含.code
if (strstr(line, ".code32")) skip_csect = use_64bit; // 如果是.code32则根据64位模式设置skip_csect
if (strstr(line, ".code64")) skip_csect = !use_64bit; // 如果是.code64则根据64位模式设置skip_csect
}
/* Detect syntax changes, as could happen with hand-written assembly.
Skip Intel blocks, resume instrumentation when back to AT&T. */
if (strstr(line, ".intel_syntax")) skip_intel = 1;
if (strstr(line, ".att_syntax")) skip_intel = 0;
/* Detect and skip ad-hoc __asm__ blocks, likewise skipping them. */
if (line[0] == '#' || line[1] == '#') {
if (strstr(line, "#APP")) skip_app = 1;
if (strstr(line, "#NO_APP")) skip_app = 0;
// 检测并跳过手写汇编块__asm__同样不进行插桩。
if (line[0] == '#' || line[1] == '#') { // 如果行以#开头
if (strstr(line, "#APP")) skip_app = 1; // 如果包含#APP则设置skip_app
if (strstr(line, "#NO_APP")) skip_app = 0; // 如果包含#NO_APP则重置skip_app
}
/* If 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)
^.L0: - GCC branch label
^.LBB0_0: - clang branch label (but only in clang mode)
^\tjnz foo - conditional branches
...but not:
^# BB#0: - clang comments
^ # BB#0: - ditto
^.Ltmp0: - clang non-branch labels
^.LC0 - GCC non-branch labels
^.LBB0_0: - ditto (when in GCC mode)
^\tjmp foo - non-conditional jumps
Additionally, clang and GCC on MacOS X follow a different convention
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;
/* Conditional branch instruction (jnz, etc). We append the instrumentation
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) {
fprintf(outf, use_64bit ? trampoline_fmt_64 : trampoline_fmt_32,
// 如果我们处于插桩模式,检查函数名或条件标签。这里逻辑有些复杂,但基本目标是捕获:
// ^main: - 函数入口点(总是插桩)
// ^.L0: - GCC 分支标签
// ^.LBB0_0: - clang 分支标签(但仅在 clang 模式下)
// ^\tjnz foo - 条件分支
// ...而不捕获:
// ^# BB#0: - clang 注释
// ^ # BB#0: - 同上
// ^.Ltmp0: - clang 非分支标签
// ^.LC0 - GCC 非分支标签
// ^.LBB0_0: - 同上(当处于 GCC 模式下)
// ^\tjmp foo - 非条件跳转
// 此外MacOS X 上的 clang 和 GCC 使用不同的标签格式,没有前导点,因此我们根据这一情况处理。
if (skip_intel || skip_app || skip_csect || !instr_ok || // 如果跳过插桩
line[0] == '#' || line[0] == ' ') continue; // 或者行以#或空格开头,则跳过
// 条件分支指令jnz 等)。我们会在分支之后插入插桩(以插桩不执行路径),
// 并在分支目标标签处插入(稍后处理)。
if (line[0] == '\t') { // 如果行以制表符开头
if (line[1] == 'j' && line[2] != 'm' && R(100) < inst_ratio) { // 如果是条件分支指令
fprintf(outf, use_64bit ? trampoline_fmt_64 : trampoline_fmt_32, // 插入跳板代码
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__
/* Apple: L<whatever><digit>: */
if ((colon_pos = strstr(line, ":"))) {
if (line[0] == 'L' && isdigit(*(colon_pos - 1))) {
#else
/* Everybody else: .L<whatever>: */
if (strstr(line, ":")) {
if (line[0] == '.') {
// 某类标签。这可能是分支目标,但我们需要小心处理不同的格式约定。
#ifdef __APPLE__ // 如果是苹果系统
// MacOS X: L<whatever><digit>:
if ((colon_pos = strstr(line, ":"))) { // 如果行包含冒号
if (line[0] == 'L' && isdigit(*(colon_pos - 1))) { // 如果标签以L开头且冒号前是数字
#else // 否则
// 其他人:.L<whatever>:
if (strstr(line, ":")) { // 如果行包含冒号
if (line[0] == '.') { // 如果标签以点开头
#endif /* __APPLE__ */
/* .L0: or LBB0_0: style jump destination */
#ifdef __APPLE__
/* Apple: L<num> / LBB<num> */
if ((isdigit(line[1]) || (clang_mode && !strncmp(line, "LBB", 3)))
&& R(100) < inst_ratio) {
#else
/* Apple: .L<num> / .LBB<num> */
if ((isdigit(line[2]) || (clang_mode && !strncmp(line + 1, "LBB", 3)))
&& R(100) < inst_ratio) {
// .L0: 或 LBB0_0: 风格的分支目标
#ifdef __APPLE__ // 如果是苹果系统
// MacOS X: L<num> / LBB<num>
if ((isdigit(line[1]) || (clang_mode && !strncmp(line, "LBB", 3))) // 如果标签是L<num>或LBB<num>
&& R(100) < inst_ratio) { // 并且随机数小于插桩概率
#else // 否则
// MacOS X: .L<num> / .LBB<num>
if ((isdigit(line[2]) || (clang_mode && !strncmp(line + 1, "LBB", 3))) // 如果标签是.L<num>或.LBB<num>
&& R(100) < inst_ratio) { // 并且随机数小于插桩概率
#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.
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
.Lfunc_begin0-style exception handling calculations (a problem on
MacOS X). */
if (!skip_next_label) instrument_next = 1; else skip_next_label = 0;
// 在仅需要在标签被引用时(非调用/跳转上下文)才添加代码的情况下可以进行优化。
// 这会引入两遍处理过程的复杂性(当使用 stdin 时尤其麻烦),并且通常只能带来不到 10% 的速度提升。
// 因为编译器通常不会生成不相关的函数内跳转。
// 我们使用延迟输出主要是为了避免干扰 MacOS X 上 .Lfunc_begin0 风格异常处理计算的问题。
if (!skip_next_label) instrument_next = 1; else skip_next_label = 0; // 设置插桩标志
}
} else {
/* Function label (always instrumented, deferred mode). */
instrument_next = 1;
} else { // 否则
// 函数标签(总是插桩,延迟模式)。
instrument_next = 1; // 设置插桩标志
}
}
}
if (ins_lines)
fputs(use_64bit ? main_payload_64 : main_payload_32, outf);
if (input_file) fclose(inf);
fclose(outf);
if (!be_quiet) {
if (!ins_lines) WARNF("No instrumentation targets found%s.",
if (ins_lines) // 如果有插桩行
fputs(use_64bit ? main_payload_64 : main_payload_32, outf); // 插入主插桩代码
if (input_file) fclose(inf); // 关闭输入文件
fclose(outf); // 关闭输出文件
if (!be_quiet) { // 如果不是静默模式
if (!ins_lines) WARNF("No instrumentation targets found%s.", // 如果没有插桩目标
pass_thru ? " (pass-thru mode)" : "");
else OKF("Instrumented %u locations (%s-bit, %s mode, ratio %u%%).",
else OKF("Instrumented %u locations (%s-bit, %s mode, ratio %u%%).", // 输出插桩信息
ins_lines, use_64bit ? "64" : "32",
getenv("AFL_HARDEN") ? "hardened" :
(sanitizer ? "ASAN/MSAN" : "non-hardened"),
inst_ratio);
}
}
/* Main entry point */
// 程序主入口点
int main(int argc, char** argv) {
s32 pid;
u32 rand_seed;
int status;
u8* inst_ratio_str = getenv("AFL_INST_RATIO");
struct timeval tv;
struct timezone tz;
clang_mode = !!getenv(CLANG_ENV_VAR);
if (isatty(2) && !getenv("AFL_QUIET")) {
SAYF(cCYA "afl-as " cBRI VERSION cRST " by <lcamtuf@google.com>\n");
} else be_quiet = 1;
if (argc < 2) {
s32 pid; // 进程ID
u32 rand_seed; // 随机种子
int status; // 进程状态
u8* inst_ratio_str = getenv("AFL_INST_RATIO"); // 获取插桩概率环境变量
struct timeval tv; // 时间结构
struct timezone tz; // 时区结构
clang_mode = !!getenv(CLANG_ENV_VAR); // 设置clang模式
if (isatty(2) && !getenv("AFL_QUIET")) { // 如果是终端且没有设置AFL_QUIET
SAYF(cCYA "afl-as " cBRI VERSION cRST " by <lcamtuf@google.com>\n"); // 输出版本信息
} else be_quiet = 1; // 否则设置为静默模式
if (argc < 2) { // 如果参数少于2个
SAYF("\n"
"This is a helper application for afl-fuzz. It is a wrapper around GNU 'as',\n"
"executed by the toolchain whenever using afl-gcc or afl-clang. You probably\n"
"don't want to run this program directly.\n\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"
"instrumenting every discovered branch.\n\n");
exit(1);
"instrumenting every discovered branch.\n\n"); // 输出帮助信息
exit(1); // 退出
}
gettimeofday(&tv, &tz);
rand_seed = tv.tv_sec ^ tv.tv_usec ^ getpid();
srandom(rand_seed);
edit_params(argc, argv);
if (inst_ratio_str) {
if (sscanf(inst_ratio_str, "%u", &inst_ratio) != 1 || inst_ratio > 100)
FATAL("Bad value of AFL_INST_RATIO (must be between 0 and 100)");
gettimeofday(&tv, &tz); // 获取当前时间
rand_seed = tv.tv_sec ^ tv.tv_usec ^ getpid(); // 生成随机种子
srandom(rand_seed); // 设置随机种子
edit_params(argc, argv); // 编辑参数
if (inst_ratio_str) { // 如果有插桩概率环境变量
if (sscanf(inst_ratio_str, "%u", &inst_ratio) != 1 || inst_ratio > 100) // 如果解析失败或大于100
FATAL("Bad value of AFL_INST_RATIO (must be between 0 and 100)"); // 报错
}
if (getenv(AS_LOOP_ENV_VAR))
FATAL("Endless loop when calling 'as' (remove '.' from your PATH)");
setenv(AS_LOOP_ENV_VAR, "1", 1);
/* When compiling with ASAN, we don't have a particularly elegant way to skip
ASAN-specific branches. But we can probabilistically compensate for
that... */
if (getenv("AFL_USE_ASAN") || getenv("AFL_USE_MSAN")) {
sanitizer = 1;
inst_ratio /= 3;
if (getenv(AS_LOOP_ENV_VAR)) // 如果设置了AS_LOOP_ENV_VAR
FATAL("Endless loop when calling 'as' (remove '.' from your PATH)"); // 报错
setenv(AS_LOOP_ENV_VAR, "1", 1); // 设置AS_LOOP_ENV_VAR
// 使用 ASAN 时,我们没有特别优雅的方法来跳过 ASAN 特定的分支。
// 但可以通过按概率补偿来解决这个问题...
if (getenv("AFL_USE_ASAN") || getenv("AFL_USE_MSAN")) { // 如果使用ASAN或MSAN
sanitizer = 1; // 设置sanitizer标志
inst_ratio /= 3; // 降低插桩概率
}
if (!just_version) add_instrumentation();
if (!(pid = fork())) {
execvp(as_params[0], (char**)as_params);
FATAL("Oops, failed to execute '%s' - check your PATH", as_params[0]);
if (!just_version) add_instrumentation(); // 如果不是只显示版本,则进行插桩
if (!(pid = fork())) { // 创建子进程
execvp(as_params[0], (char**)as_params); // 执行as命令
FATAL("Oops, failed to execute '%s' - check your PATH", as_params[0]); // 如果执行失败则报错
}
if (pid < 0) PFATAL("fork() failed");
if (waitpid(pid, &status, 0) <= 0) PFATAL("waitpid() failed");
if (!getenv("AFL_KEEP_ASSEMBLY")) unlink(modified_file);
exit(WEXITSTATUS(status));
}
if (pid < 0) PFATAL("fork() failed"); // 如果fork失败则报错
if (waitpid(pid, &status, 0) <= 0) PFATAL("waitpid() failed"); // 等待子进程结束
if (!getenv("AFL_KEEP_ASSEMBLY")) unlink(modified_file); // 如果没有设置AFL_KEEP_ASSEMBLY则删除修改后的文件
exit(WEXITSTATUS(status)); // 退出
}

File diff suppressed because it is too large Load Diff

@ -1,89 +1,91 @@
#!/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");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at:
# 根据 Apache 许可证 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. The tool will remove functionally redundant files and likely
# leave you with a much smaller set.
# - 在将大的输入文件用作 afl-fuzz 的种子之前筛选。
# 该工具将删除功能上冗余的文件,并可能
# 留下一个更小的集合。
#
# (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
# planning to feed it to more resource-intensive tools. The tool achieves
# this by removing all entries that used to trigger unique behaviors in the
# past, but have been made obsolete by later finds.
# - 最小化由 afl-fuzz 自然生成的语料库,
# 可能在计划将其供给更多资源密集型工具时。
# 该工具通过删除所有曾经触发独特行为的条目实现此目的,
# 但这些条目已被后来的结果取代。
#
# 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
# array sizes.
# 此脚本必须使用 bash因为其他 shell 可能对
# 数组大小有硬编码限制。
#
echo "corpus minimization tool for afl-fuzz by <lcamtuf@google.com>"
echo "为 afl-fuzz 提供的语料库最小化工具 <lcamtuf@google.com>" # 输出程序名称
echo
#########
# SETUP #
# 设置 #
#########
# Process command-line options...
# 处理命令行选项...
MEM_LIMIT=100
TIMEOUT=none
MEM_LIMIT=100 # 内存限制初始值为 100 MB
TIMEOUT=none # 超时初始值为无
# 取消设置以下变量
unset IN_DIR OUT_DIR STDIN_FILE EXTRA_PAR MEM_LIMIT_GIVEN \
AFL_CMIN_CRASHES_ONLY AFL_CMIN_ALLOW_ANY QEMU_MODE
# 解析命令行选项
while getopts "+i:o:f:m:t:eQC" opt; do
case "$opt" in
"i")
"i") # 输入目录选项
IN_DIR="$OPTARG"
;;
"o")
"o") # 输出目录选项
OUT_DIR="$OPTARG"
;;
"f")
"f") # 从中读取的模糊程序位置(标准输入)
STDIN_FILE="$OPTARG"
;;
"m")
"m") # 内存限制
MEM_LIMIT="$OPTARG"
MEM_LIMIT_GIVEN=1
;;
"t")
"t") # 超时时间
TIMEOUT="$OPTARG"
;;
"e")
"e") # 额外参数
EXTRA_PAR="$EXTRA_PAR -e"
;;
"C")
"C") # 仅保留崩溃输入
export AFL_CMIN_CRASHES_ONLY=1
;;
"Q")
"Q") # 使用仅二进制的仪器QEMU 模式)
EXTRA_PAR="$EXTRA_PAR -Q"
test "$MEM_LIMIT_GIVEN" = "" && MEM_LIMIT=250
QEMU_MODE=1
;;
"?")
"?") # 无效选项
exit 1
;;
@ -91,84 +93,84 @@ while getopts "+i:o:f:m:t:eQC" opt; do
done
shift $((OPTIND-1))
shift $((OPTIND-1)) # 移动位置参数
TARGET_BIN="$1"
TARGET_BIN="$1" # 目标二进制文件
# 检查必需参数是否缺失
if [ "$TARGET_BIN" = "" -o "$IN_DIR" = "" -o "$OUT_DIR" = "" ]; then
# 输出用法信息到标准错误
cat 1>&2 <<_EOF_
Usage: $0 [ options ] -- /path/to/target_app [ ... ]
使用: $0 [选项] -- /path/to/target_app [ ... ]
Required parameters:
所需参数:
-i dir - input directory with the starting corpus
-o dir - output directory for minimized files
-i dir - 包含起始语料库的输入目录
-o dir - 最小化文件的输出目录
Execution control settings:
执行控制设置:
-f file - location read by the fuzzed program (stdin)
-m megs - memory limit for child process ($MEM_LIMIT MB)
-t msec - run time limit for child process (none)
-Q - use binary-only instrumentation (QEMU mode)
-f file - 由模糊程序读取的位置(标准输入)
-m megs - 子进程的内存限制($MEM_LIMIT MB
-t msec - 子进程的运行时间限制(无)
-Q - 使用仅二进制的仪器QEMU 模式)
Minimization settings:
最小化设置:
-C - keep crashing inputs, reject everything else
-e - solve for edge coverage only, ignore hit counts
-C - 保留崩溃输入,拒绝其他所有内容
-e - 仅解决边缘覆盖,忽略命中计数
For additional tips, please consult docs/README.
有关其他提示,请参阅 docs/README。
_EOF_
exit 1
fi
# Do a sanity check to discourage the use of /tmp, since we can't really
# handle this safely from a shell script.
# 进行完整性检查,避免使用 /tmp因为我们无法安全处理它。
if [ "$AFL_ALLOW_TMP" = "" ]; then
echo "$IN_DIR" | grep -qE '^(/var)?/tmp/'
echo "$IN_DIR" | grep -qE '^(/var)?/tmp/' # 检查输入目录是否在/tmp
T1="$?"
echo "$TARGET_BIN" | grep -qE '^(/var)?/tmp/'
echo "$TARGET_BIN" | grep -qE '^(/var)?/tmp/' # 检查目标二进制文件是否在/tmp
T2="$?"
echo "$OUT_DIR" | grep -qE '^(/var)?/tmp/'
echo "$OUT_DIR" | grep -qE '^(/var)?/tmp/' # 检查输出目录是否在/tmp
T3="$?"
echo "$STDIN_FILE" | grep -qE '^(/var)?/tmp/'
echo "$STDIN_FILE" | grep -qE '^(/var)?/tmp/' # 检查标准输入文件是否在/tmp
T4="$?"
echo "$PWD" | grep -qE '^(/var)?/tmp/'
echo "$PWD" | grep -qE '^(/var)?/tmp/' # 检查当前工作目录是否在/tmp
T5="$?"
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
fi
fi
# If @@ is specified, but there's no -f, let's come up with a temporary input
# file name.
# 如果指定了 @@,但没有 -f创建一个临时输入文件名。
TRACE_DIR="$OUT_DIR/.traces"
if [ "$STDIN_FILE" = "" ]; then
if echo "$*" | grep -qF '@@'; then
STDIN_FILE="$TRACE_DIR/.cur_input"
STDIN_FILE="$TRACE_DIR/.cur_input" # 使用当前输入文件名
fi
fi
# Check for obvious errors.
# 检查明显的错误。
if [ ! "$MEM_LIMIT" = "none" ]; then
if [ "$MEM_LIMIT" -lt "5" ]; then
echo "[-] Error: dangerously low memory limit." 1>&2
echo "[-] 错误: 内存限制过低。" 1>&2
exit 1
fi
@ -177,7 +179,7 @@ fi
if [ ! "$TIMEOUT" = "none" ]; then
if [ "$TIMEOUT" -lt "10" ]; then
echo "[-] Error: dangerously low timeout." 1>&2
echo "[-] 错误: 超时过低。" 1>&2
exit 1
fi
@ -185,92 +187,91 @@ fi
if [ ! -f "$TARGET_BIN" -o ! -x "$TARGET_BIN" ]; then
TNEW="`which "$TARGET_BIN" 2>/dev/null`"
TNEW="`which "$TARGET_BIN" 2>/dev/null`" # 查找目标二进制文件的路径
if [ ! -f "$TNEW" -o ! -x "$TNEW" ]; then
echo "[-] Error: binary '$TARGET_BIN' not found or not executable." 1>&2
echo "[-] 错误: 未找到或不可执行的二进制文件 '$TARGET_BIN'。" 1>&2
exit 1
fi
TARGET_BIN="$TNEW"
TARGET_BIN="$TNEW" # 更新目标二进制文件路径
fi
if [ "$AFL_SKIP_BIN_CHECK" = "" -a "$QEMU_MODE" = "" ]; then
if ! grep -qF "__AFL_SHM_ID" "$TARGET_BIN"; then
echo "[-] Error: binary '$TARGET_BIN' doesn't appear to be instrumented." 1>&2
echo "[-] 错误: 二进制文件 '$TARGET_BIN' 似乎没有被仪器化。" 1>&2
exit 1
fi
fi
if [ ! -d "$IN_DIR" ]; then
echo "[-] Error: directory '$IN_DIR' not found." 1>&2
echo "[-] 错误: 目录 '$IN_DIR' 未找到。" 1>&2
exit 1
fi
test -d "$IN_DIR/queue" && IN_DIR="$IN_DIR/queue"
test -d "$IN_DIR/queue" && IN_DIR="$IN_DIR/queue" # 如果存在队列目录,则更新输入目录
find "$OUT_DIR" -name 'id[:_]*' -maxdepth 1 -exec rm -- {} \; 2>/dev/null
rm -rf "$TRACE_DIR" 2>/dev/null
find "$OUT_DIR" -name 'id[:_]*' -maxdepth 1 -exec rm -- {} \; 2>/dev/null # 删除输出目录中的旧轨迹
rm -rf "$TRACE_DIR" 2>/dev/null # 删除临时轨迹目录
rmdir "$OUT_DIR" 2>/dev/null
rmdir "$OUT_DIR" 2>/dev/null # 删除输出目录
if [ -d "$OUT_DIR" ]; then
echo "[-] Error: directory '$OUT_DIR' exists and is not empty - delete it first." 1>&2
echo "[-] 错误: 目录 '$OUT_DIR' 已存在且非空 - 请先删除它。" 1>&2
exit 1
fi
mkdir -m 700 -p "$TRACE_DIR" || exit 1
mkdir -m 700 -p "$TRACE_DIR" || exit 1 # 创建临时轨迹目录并设置权限
if [ ! "$STDIN_FILE" = "" ]; then
rm -f "$STDIN_FILE" || exit 1
touch "$STDIN_FILE" || exit 1
rm -f "$STDIN_FILE" || exit 1 # 删除旧的标准输入文件
touch "$STDIN_FILE" || exit 1 # 创建新的标准输入文件
fi
if [ "$AFL_PATH" = "" ]; then
SHOWMAP="${0%/afl-cmin}/afl-showmap"
SHOWMAP="${0%/afl-cmin}/afl-showmap" # 设置 afl-showmap 的路径
else
SHOWMAP="$AFL_PATH/afl-showmap"
SHOWMAP="$AFL_PATH/afl-showmap" # 使用 AFL_PATH 中指定的路径
fi
if [ ! -x "$SHOWMAP" ]; then
echo "[-] Error: can't find 'afl-showmap' - please set AFL_PATH." 1>&2
rm -rf "$TRACE_DIR"
echo "[-] 错误: 找不到 'afl-showmap' - 请设置 AFL_PATH。" 1>&2
rm -rf "$TRACE_DIR" # 删除临时轨迹目录
exit 1
fi
IN_COUNT=$((`ls -- "$IN_DIR" 2>/dev/null | wc -l`))
IN_COUNT=$((`ls -- "$IN_DIR" 2>/dev/null | wc -l`)) # 计算输入目录中的文件数量
if [ "$IN_COUNT" = "0" ]; then
echo "[+] Hmm, no inputs in the target directory. Nothing to be done."
rm -rf "$TRACE_DIR"
echo "[+] 嗯,目标目录中没有输入。无需处理。"
rm -rf "$TRACE_DIR" # 删除临时轨迹目录
exit 1
fi
FIRST_FILE=`ls "$IN_DIR" | head -1`
FIRST_FILE=`ls "$IN_DIR" | head -1` # 获取第一个文件名
# Make sure that we're not dealing with a directory.
# 确保不是处理目录。
if [ -d "$IN_DIR/$FIRST_FILE" ]; then
echo "[-] Error: The target directory contains subdirectories - please fix." 1>&2
rm -rf "$TRACE_DIR"
echo "[-] 错误: 目标目录包含子目录 - 请修复。" 1>&2
rm -rf "$TRACE_DIR" # 删除临时轨迹目录
exit 1
fi
# Check for the more efficient way to copy files...
# 检查复制文件的更有效方法...
if ln "$IN_DIR/$FIRST_FILE" "$TRACE_DIR/.link_test" 2>/dev/null; then
CP_TOOL=ln
CP_TOOL=ln # 如果可以链接,则设置复制工具为 ln
else
CP_TOOL=cp
CP_TOOL=cp # 否则使用 cp
fi
# Make sure that we can actually get anything out of afl-showmap before we
# waste too much time.
# 确保我们能从 afl-showmap 中获取任何信息,以免浪费时间。
echo "[*] Testing the target binary..."
echo "[*] 测试目标二进制文件..."
if [ "$STDIN_FILE" = "" ]; then
@ -278,43 +279,42 @@ if [ "$STDIN_FILE" = "" ]; then
else
cp "$IN_DIR/$FIRST_FILE" "$STDIN_FILE"
cp "$IN_DIR/$FIRST_FILE" "$STDIN_FILE" # 复制第一个文件到标准输入文件
AFL_CMIN_ALLOW_ANY=1 "$SHOWMAP" -m "$MEM_LIMIT" -t "$TIMEOUT" -o "$TRACE_DIR/.run_test" -Z $EXTRA_PAR -A "$STDIN_FILE" -- "$@" </dev/null
fi
FIRST_COUNT=$((`grep -c . "$TRACE_DIR/.run_test"`))
FIRST_COUNT=$((`grep -c . "$TRACE_DIR/.run_test"`)) # 计算运行测试的输出
if [ "$FIRST_COUNT" -gt "0" ]; then
echo "[+] OK, $FIRST_COUNT tuples recorded."
echo "[+] 好的,记录了 $FIRST_COUNT 个元组。"
else
echo "[-] Error: no instrumentation output detected (perhaps crash or timeout)." 1>&2
test "$AFL_KEEP_TRACES" = "" && rm -rf "$TRACE_DIR"
echo "[-] 错误: 未检测到仪器输出(可能崩溃或超时)。" 1>&2
test "$AFL_KEEP_TRACES" = "" && rm -rf "$TRACE_DIR" # 删除临时轨迹
exit 1
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
while read -r fn; do
while read -r fn; do # 逐行读取输入文件名
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"
@ -322,18 +322,16 @@ echo "[*] Obtaining traces for input files in '$IN_DIR'..."
else
while read -r fn; do
while read -r fn; do # 逐行读取输入文件名
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
done < <(ls "$IN_DIR")
fi
)
@ -341,121 +339,117 @@ echo "[*] Obtaining traces for input files in '$IN_DIR'..."
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 | \
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
# a shell script.
# 下一步是找到每个元组的最佳候选者。这里的“最佳”
# 指的是包含特定元组的最小输入文件。
# 经验表明这比更复杂的算法要好,
# 而这些算法在 shell 脚本中仍然可以执行。
echo "[*] Finding best candidates for each tuple..."
echo "[*] 寻找每个元组的最佳候选者..."
CUR=0
while read -r fn; do
while read -r fn; do # 逐行读取输入文件名
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
##############################
# 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
# only by tuple (-k 1,1) and configured to output only the first line for
# every key (-s -u), we end up with the smallest file for each tuple.
# 此时,我们有一个元组-文件对的文件,按文件大小升序排序
# (由于 ls -rS 的结果)。通过仅按元组排序 (-k 1,1)
# 并配置为对每个键的第一个输出行 (-s -u)
# 我们最终得到了每个元组的最小文件。
echo "[*] Sorting candidate list (be patient)..."
echo "[*] 排序候选列表(耐心等候)..."
sort -k1,1 -s -u "$TRACE_DIR/.candidate_list" | \
sed 's/^/BEST_FILE[/;s/ /]="/;s/$/"/' >"$TRACE_DIR/.candidate_script"
sed 's/^/BEST_FILE[/;s/ /]="/;s/$/"/' >"$TRACE_DIR/.candidate_script" # 创建候选脚本
if [ ! -s "$TRACE_DIR/.candidate_script" ]; then
echo "[-] Error: no traces obtained from test cases, check syntax!" 1>&2
echo "[-] 错误: 从测试用例中未获得轨迹,请检查语法!" 1>&2
test "$AFL_KEEP_TRACES" = "" && rm -rf "$TRACE_DIR"
exit 1
fi
# The sed command converted the sorted list to a shell script that populates
# BEST_FILE[tuple]="fname". Let's load that!
# sed 命令将排序后的列表转换为一个填充
# 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
touch "$TRACE_DIR/.already_have"
while read -r cnt tuple; do
touch "$TRACE_DIR/.already_have" # 创建已拥有文件
while read -r cnt tuple; do # 逐行读取元组和计数
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
sort -u "$TRACE_DIR/$FN" "$TRACE_DIR/.already_have" >"$TRACE_DIR/.tmp"
mv -f "$TRACE_DIR/.tmp" "$TRACE_DIR/.already_have"
if [ "$((CUR % 5))" = "0" ]; then # 每处理五个元组时进行一次排序
sort -u "$TRACE_DIR/$FN" "$TRACE_DIR/.already_have" >"$TRACE_DIR/.tmp" # 合并并去重
mv -f "$TRACE_DIR/.tmp" "$TRACE_DIR/.already_have" # 替换原有的已拥有文件
else
cat "$TRACE_DIR/$FN" >>"$TRACE_DIR/.already_have"
cat "$TRACE_DIR/$FN" >>"$TRACE_DIR/.already_have" # 否则直接追加到已拥有文件
fi
done <"$TRACE_DIR/.all_uniq"
done <"$TRACE_DIR/.all_uniq" # 从所有唯一元组读取数据
echo
OUT_COUNT=`ls -- "$OUT_DIR" | wc -l`
OUT_COUNT=`ls -- "$OUT_DIR" | wc -l` # 统计输出目录中的文件数量
if [ "$OUT_COUNT" = "1" ]; then
echo "[!] WARNING: All test cases had the same traces, check syntax!"
echo "[!] 警告: 所有测试用例具有相同的轨迹,请检查语法!"
fi
echo "[+] Narrowed down to $OUT_COUNT files, saved in '$OUT_DIR'."
echo "[+] 已缩小到 $OUT_COUNT 个文件,保存在 '$OUT_DIR' 中。" # 输出结果数量
echo
test "$AFL_KEEP_TRACES" = "" && rm -rf "$TRACE_DIR"
test "$AFL_KEEP_TRACES" = "" && rm -rf "$TRACE_DIR" # 删除临时轨迹目录(如果需要)
exit 0
exit 0 # 正常退出脚本

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

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

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

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

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

@ -36,19 +36,19 @@
#include "../types.h"
#ifndef PAGE_SIZE
# define PAGE_SIZE 4096
# define PAGE_SIZE 4096 // 定义页面大小为4096字节
#endif /* !PAGE_SIZE */
#ifndef MAP_ANONYMOUS
# define MAP_ANONYMOUS MAP_ANON
# define MAP_ANONYMOUS MAP_ANON // 定义MAP_ANONYMOUS为MAP_ANON用于匿名映射
#endif /* !MAP_ANONYMOUS */
/* Error / message handling: */
/* 错误/消息处理: */
#define DEBUGF(_x...) do { \
if (alloc_verbose) { \
if (++call_depth == 1) { \
fprintf(stderr, "[AFL] " _x); \
fprintf(stderr, "[AFL] " _x); // 输出调试信息
fprintf(stderr, "\n"); \
} \
call_depth--; \
@ -57,101 +57,97 @@
#define FATAL(_x...) do { \
if (++call_depth == 1) { \
fprintf(stderr, "*** [AFL] " _x); \
fprintf(stderr, "*** [AFL] " _x); // 输出致命错误信息
fprintf(stderr, " ***\n"); \
abort(); \
abort(); // 终止程序
} \
call_depth--; \
} while (0)
/* Macro to count the number of pages needed to store a buffer: */
/* 宏来计算存储缓冲区所需的页面数量: */
#define PG_COUNT(_l) (((_l) + (PAGE_SIZE - 1)) / PAGE_SIZE)
#define PG_COUNT(_l) (((_l) + (PAGE_SIZE - 1)) / PAGE_SIZE) // 计算所需页面数,向上取整
/* Canary & clobber bytes: */
#define ALLOC_CANARY 0xAACCAACC
#define ALLOC_CLOBBER 0xCC
#define ALLOC_CANARY 0xAACCAACC // 定义canary值
#define ALLOC_CLOBBER 0xCC // 定义clobber值
#define PTR_C(_p) (((u32*)(_p))[-1])
#define PTR_L(_p) (((u32*)(_p))[-2])
#define PTR_C(_p) (((u32*)(_p))[-1]) // 获取canary值的指针
#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 u8 alloc_verbose, /* Additional debug messages */
hard_fail, /* abort() when max_mem exceeded? */
no_calloc_over; /* abort() on calloc() overflows? */
static u32 max_mem = MAX_ALLOC; /* 允许的最大堆使用量 */
static u8 alloc_verbose, /* 是否显示额外的调试消息 */
hard_fail, /* 当超过max_mem时是否使用abort() */
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,
sets that tailing page to PROT_NONE, and then increments the return address
so that it is right-aligned to that boundary. Since it always uses mmap(),
the returned memory will be zeroed. */
/* 这是主要的分配函数。它分配比必要多一个页面的内存,
PROT_NONE
使使mmap()
*/
static void* __dislocator_alloc(size_t len) {
void* ret;
if (total_mem + len > max_mem || total_mem + len < total_mem) {
if (hard_fail)
FATAL("total allocs exceed %u MB", max_mem / 1024 / 1024);
FATAL("total allocs exceed %u MB", max_mem / 1024 / 1024); // 如果超过最大内存且hard_fail为真输出错误并终止程序
DEBUGF("total allocs exceed %u MB, returning NULL",
max_mem / 1024 / 1024);
max_mem / 1024 / 1024); // 如果超过最大内存且hard_fail为假输出调试信息并返回NULL
return NULL;
}
/* We will also store buffer length and a canary below the actual buffer, so
let's add 8 bytes for that. */
/* 我们还会在实际缓冲区下面存储缓冲区长度和canary
8 */
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 (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;
}
/* Set PROT_NONE on the last page. */
/* 在最后一个页面设置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;
/* Store allocation metadata. */
/* 存储分配元数据。 */
ret += 8;
PTR_L(ret) = len;
PTR_C(ret) = ALLOC_CANARY;
PTR_L(ret) = len; // 存储分配长度
PTR_C(ret) = ALLOC_CANARY; // 存储canary值
total_mem += len;
total_mem += len; // 增加已分配内存计数
return ret;
}
/* The "user-facing" wrapper for calloc(). This just checks for overflows and
displays debug messages if requested. */
/* 面向用户的calloc()包装器。这只是一个溢出检查和
*/
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;
/* Perform some sanity checks to detect obvious issues... */
/* 进行一些简单的检查,以检测明显的错误... */
if (elem_cnt && len / elem_cnt != elem_len) {
if (no_calloc_over) {
DEBUGF("calloc(%zu, %zu) would overflow, returning NULL", elem_len, elem_cnt);
DEBUGF("calloc(%zu, %zu) would overflow, returning NULL", elem_len, elem_cnt); // 如果no_calloc_over为真输出调试信息并返回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,
total_mem);
total_mem); // 输出调试信息
return ret;
}
/* The wrapper for malloc(). Roughly the same, also clobbers the returned
memory (unlike calloc(), malloc() is not guaranteed to return zeroed
memory). */
/* malloc()的包装器。大致相同,
calloc()malloc() */
void* malloc(size_t len) {
void* ret;
ret = __dislocator_alloc(len);
ret = __dislocator_alloc(len); // 调用内部分配函数
DEBUGF("malloc(%zu) = %p [%zu total]", len, ret, total_mem);
DEBUGF("malloc(%zu) = %p [%zu total]", len, ret, total_mem); // 输出调试信息
if (ret && len) memset(ret, ALLOC_CLOBBER, len);
if (ret && len) memset(ret, ALLOC_CLOBBER, len); // 使用clobber值填充内存
return ret;
@ -206,70 +200,84 @@ void* malloc(size_t len) {
read the canary. Not very graceful, but works, right? */
void free(void* ptr) {
// 定义一个变量len用于存储要释放的内存块的长度
u32 len;
u32 len;
DEBUGF("free(%p)", ptr);
if (!ptr) return;
if (PTR_C(ptr) != ALLOC_CANARY) FATAL("bad allocator canary on free()");
// 调试信息,打印正在释放的内存指针地址
DEBUGF("free(%p)", ptr);
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 is pretty straightforward, too. We forcibly reallocate the buffer,
move data, and then free (aka mprotect()) the original one. */
/* realloc函数用于重新分配内存其逻辑是
1.
2.
3. freefreemprotect
*/
void* realloc(void* ptr, size_t len) {
// 定义一个指针ret用于存储新分配的内存地址
void* ret;
void* ret;
ret = malloc(len);
if (ret && ptr) {
// 为新的长度分配内存分配失败时ret为NULL
ret = malloc(len);
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);
return ret;
// 调试信息,打印原始指针地址、新长度、新内存地址以及当前分配的总内存大小
DEBUGF("realloc(%p, %zu) = %p [%zu total]", ptr, len, ret, total_mem);
// 返回新分配的内存地址
return ret;
}
// __dislocator_init函数在程序加载时通过构造函数属性自动执行
__attribute__((constructor)) void __dislocator_init(void) {
// 定义一个临时变量tmp用于存储环境变量AFL_LD_LIMIT_MB的值
u8* tmp = getenv("AFL_LD_LIMIT_MB");
// 如果环境变量AFL_LD_LIMIT_MB存在则将其转换为max_mem的值以字节为单位
if (tmp) {
// atoi将字符串转换为整数乘以1024*1024表示将MB转换为字节
max_mem = atoi(tmp) * 1024 * 1024;
// 如果转换后的max_mem为0表示环境变量设置不正确程序将致命错误并退出
if (!max_mem) FATAL("Bad value for AFL_LD_LIMIT_MB");
}
u8* tmp = getenv("AFL_LD_LIMIT_MB");
if (tmp) {
max_mem = atoi(tmp) * 1024 * 1024;
if (!max_mem) FATAL("Bad value for AFL_LD_LIMIT_MB");
}
alloc_verbose = !!getenv("AFL_LD_VERBOSE");
hard_fail = !!getenv("AFL_LD_HARD_FAIL");
no_calloc_over = !!getenv("AFL_LD_NO_CALLOC_OVER");
// 检查环境变量AFL_LD_VERBOSE是否存在存在则将alloc_verbose设置为1否则为0
alloc_verbose = !!getenv("AFL_LD_VERBOSE");
// 检查环境变量AFL_LD_HARD_FAIL是否存在存在则将hard_fail设置为1否则为0
hard_fail = !!getenv("AFL_LD_HARD_FAIL");
// 检查环境变量AFL_LD_NO_CALLOC_OVER是否存在存在则将no_calloc_over设置为1否则为0
no_calloc_over = !!getenv("AFL_LD_NO_CALLOC_OVER");
}

@ -33,89 +33,95 @@
#include "../types.h"
#include "../config.h"
// 检查是否为Linux系统如果不是则报错
#ifndef __linux__
# error "Sorry, this library is Linux-specific for now!"
#endif /* !__linux__ */
/* Mapping data and such */
// 定义最大映射数量
#define MAX_MAPPINGS 1024
// 定义映射结构体,存储只读内存区域的起始和结束地址
static struct mapping {
void *st, *en;
} __tokencap_ro[MAX_MAPPINGS];
// 定义当前加载的只读映射数量
static u32 __tokencap_ro_cnt;
// 定义只读映射是否已加载标志
static u8 __tokencap_ro_loaded;
// 定义输出文件指针
static FILE* __tokencap_out_file;
/* Identify read-only regions in memory. Only parameters that fall into these
ranges are worth dumping when passed to strcmp() and so on. Read-write
regions are far more likely to contain user input instead. */
// 功能:加载只读内存区域的映射信息
// 通过读取/proc/self/maps文件识别出只读且不可写的内存区域并存储在__tokencap_ro数组中
static void __tokencap_load_mappings(void) {
u8 buf[MAX_LINE];
FILE* f = fopen("/proc/self/maps", "r");
u8 buf[MAX_LINE]; // 用于存储每行读取的内存映射信息
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)) {
u8 rf, wf;
void* st, *en;
u8 rf, wf; // rf表示是否可读wf表示是否可写
void* st, *en; // st表示内存区域的起始地址en表示内存区域的结束地址
// 解析每行内存映射信息,提取起始地址、结束地址、是否可读和是否可写
if (sscanf(buf, "%p-%p %c%c", &st, &en, &rf, &wf) != 4) continue;
if (wf == 'w' || rf != 'r') continue;
if (wf == 'w' || rf != 'r') continue; // 跳过可写或不可读的内存区域
// 将只读内存区域的起始和结束地址存储到数组中
__tokencap_ro[__tokencap_ro_cnt].st = (void*)st;
__tokencap_ro[__tokencap_ro_cnt].en = (void*)en;
// 如果已达到最大映射数量,则停止加载
if (++__tokencap_ro_cnt == MAX_MAPPINGS) break;
}
fclose(f);
fclose(f); // 关闭文件
}
/* Check an address against the list of read-only mappings. */
// 功能:检查给定地址是否位于只读内存区域
// 如果未加载映射信息则先调用__tokencap_load_mappings加载映射信息
static u8 __tokencap_is_ro(const void* ptr) {
u32 i;
if (!__tokencap_ro_loaded) __tokencap_load_mappings();
if (!__tokencap_ro_loaded) __tokencap_load_mappings(); // 如果只读映射未加载,则加载
// 遍历只读映射数组,检查给定地址是否在任一只读内存区域内
for (i = 0; i < __tokencap_ro_cnt; i++)
if (ptr >= __tokencap_ro[i].st && ptr <= __tokencap_ro[i].en) return 1;
return 0;
return 0; // 如果不在任何只读内存区域内则返回0
}
/* Dump an interesting token to output file, quoting and escaping it
properly. */
// 功能:将感兴趣的数据转储到输出文件中
// 数据会被正确引用和转义,例如,非打印字符会被转义为\xXX的形式
static void __tokencap_dump(const u8* ptr, size_t len, u8 is_text) {
u8 buf[MAX_AUTO_EXTRA * 4 + 1];
u8 buf[MAX_AUTO_EXTRA * 4 + 1]; // 存储转义后的数据
u32 i;
u32 pos = 0;
u32 pos = 0; // 当前写入buf的位置
// 如果数据长度不符合要求或输出文件未打开,则直接返回
if (len < MIN_AUTO_EXTRA || len > MAX_AUTO_EXTRA || !__tokencap_out_file)
return;
// 遍历数据,进行转义处理
for (i = 0; i < len; i++) {
// 如果是文本数据且遇到空字符,则停止处理
if (is_text && !ptr[i]) break;
// 根据字符类型进行处理
switch (ptr[i]) {
case 0 ... 31:
@ -123,189 +129,237 @@ static void __tokencap_dump(const u8* ptr, size_t len, u8 is_text) {
case '\"':
case '\\':
// 对于非打印字符、双引号和反斜杠进行转义
sprintf(buf + pos, "\\x%02x", ptr[i]);
pos += 4;
pos += 4; // 转义后的字符串长度为4
break;
default:
// 对于可打印字符直接复制到buf中
buf[pos++] = ptr[i];
}
}
buf[pos] = 0;
buf[pos] = 0; // 添加字符串结束符
// 将转义后的字符串写入输出文件
fprintf(__tokencap_out_file, "\"%s\"\n", buf);
}
/* Replacements for strcmp(), memcmp(), and so on. Note that these will be used
only if the target is compiled with -fno-builtins and linked dynamically. */
// 功能替换strcmp函数用于识别并转储只读内存区域中的字符串
// 如果目标程序编译时使用了-fno-builtins选项并动态链接则会使用此函数
#undef strcmp
int strcmp(const char* str1, const char* str2) {
// 检查str1是否位于只读内存区域如果是则转储其内容
if (__tokencap_is_ro(str1)) __tokencap_dump(str1, strlen(str1), 1);
// 检查str2是否位于只读内存区域如果是则转储其内容
if (__tokencap_is_ro(str2)) __tokencap_dump(str2, strlen(str2), 1);
// 实现strcmp函数的核心功能比较两个字符串
while (1) {
unsigned char c1 = *str1, c2 = *str2;
// 如果两个字符不同,则返回比较结果
if (c1 != c2) return (c1 > c2) ? 1 : -1;
// 如果遇到字符串结束符则返回0
if (!c1) return 0;
// 指向下一个字符
str1++; str2++;
}
}
// 取消定义原本的strncmp函数以便重新定义
#undef strncmp
// 自定义的strncmp函数用于比较两个字符串的前len个字符
int strncmp(const char* str1, const char* str2, size_t len) {
// 检查str1是否为只读内存如果是则输出内存内容
if (__tokencap_is_ro(str1)) __tokencap_dump(str1, len, 1);
// 检查str2是否为只读内存如果是则输出内存内容
if (__tokencap_is_ro(str2)) __tokencap_dump(str2, len, 1);
// 循环比较两个字符串的前len个字符
while (len--) {
unsigned char c1 = *str1, c2 = *str2;
unsigned char c1 = *str1, c2 = *str2; // 获取当前字符
// 如果str1的第一个字符为0即字符串结束则返回0
if (!c1) return 0;
// 如果当前字符不相等根据字符的ASCII值大小返回1或-1
if (c1 != c2) return (c1 > c2) ? 1 : -1;
str1++; str2++;
str1++; str2++; // 移动到下一个字符
}
// 如果循环结束说明前len个字符都相等返回0
return 0;
}
// 取消定义原本的strcasecmp函数以便重新定义
#undef strcasecmp
// 自定义的strcasecmp函数用于忽略大小写的字符串比较
int strcasecmp(const char* str1, const char* str2) {
// 检查str1是否为只读内存如果是则输出内存内容
if (__tokencap_is_ro(str1)) __tokencap_dump(str1, strlen(str1), 1);
// 检查str2是否为只读内存如果是则输出内存内容
if (__tokencap_is_ro(str2)) __tokencap_dump(str2, strlen(str2), 1);
// 无限循环,逐字符比较两个字符串(忽略大小写)
while (1) {
unsigned char c1 = tolower(*str1), c2 = tolower(*str2);
unsigned char c1 = tolower(*str1), c2 = tolower(*str2); // 转换为小写后比较
// 如果当前字符不相等根据字符的ASCII值大小返回1或-1
if (c1 != c2) return (c1 > c2) ? 1 : -1;
// 如果str1的第一个字符为0即字符串结束则返回0
if (!c1) return 0;
str1++; str2++;
str1++; str2++; // 移动到下一个字符
}
}
// 取消定义原本的strncasecmp函数以便重新定义
#undef strncasecmp
// 自定义的strncasecmp函数用于忽略大小写的字符串前len个字符比较
int strncasecmp(const char* str1, const char* str2, size_t len) {
// 检查str1是否为只读内存如果是则输出内存内容
if (__tokencap_is_ro(str1)) __tokencap_dump(str1, len, 1);
// 检查str2是否为只读内存如果是则输出内存内容
if (__tokencap_is_ro(str2)) __tokencap_dump(str2, len, 1);
// 循环比较两个字符串的前len个字符忽略大小写
while (len--) {
unsigned char c1 = tolower(*str1), c2 = tolower(*str2);
unsigned char c1 = tolower(*str1), c2 = tolower(*str2); // 转换为小写后比较
// 如果str1的第一个字符为0即字符串结束则返回0
if (!c1) return 0;
// 如果当前字符不相等根据字符的ASCII值大小返回1或-1
if (c1 != c2) return (c1 > c2) ? 1 : -1;
str1++; str2++;
str1++; str2++; // 移动到下一个字符
}
// 如果循环结束说明前len个字符都相等返回0
return 0;
}
// 取消定义原本的memcmp函数以便重新定义
#undef memcmp
// 自定义的memcmp函数用于比较两个内存区域的前len个字节
int memcmp(const void* mem1, const void* mem2, size_t len) {
// 检查mem1是否为只读内存如果是则输出内存内容
if (__tokencap_is_ro(mem1)) __tokencap_dump(mem1, len, 0);
// 检查mem2是否为只读内存如果是则输出内存内容
if (__tokencap_is_ro(mem2)) __tokencap_dump(mem2, len, 0);
// 循环比较两个内存区域的前len个字节
while (len--) {
unsigned char c1 = *(const char*)mem1, c2 = *(const char*)mem2;
unsigned char c1 = *(const char*)mem1, c2 = *(const char*)mem2; // 获取当前字节
// 如果当前字节不相等根据字节的ASCII值大小返回1或-1
if (c1 != c2) return (c1 > c2) ? 1 : -1;
mem1++; mem2++;
mem1++; mem2++; // 移动到下一个字节
}
// 如果循环结束说明前len个字节都相等返回0
return 0;
}
// 取消定义原本的strstr函数以便重新定义
#undef strstr
// 自定义的strstr函数用于在haystack字符串中查找needle字符串
char* strstr(const char* haystack, const char* needle) {
// 检查haystack是否为只读内存如果是则输出内存内容
if (__tokencap_is_ro(haystack))
__tokencap_dump(haystack, strlen(haystack), 1);
// 检查needle是否为只读内存如果是则输出内存内容
if (__tokencap_is_ro(needle))
__tokencap_dump(needle, strlen(needle), 1);
// 在haystack中查找needle
do {
const char* n = needle;
const char* h = haystack;
// 逐字符比较haystack和needle
while(*n && *h && *n == *h) n++, h++;
// 如果needle的所有字符都被匹配返回匹配开始的位置
if(!*n) return (char*)haystack;
} while (*(haystack++));
} while (*(haystack++)); // 移动到haystack的下一个字符并继续查找
// 如果没有找到needle返回NULL
return 0;
}
// 取消定义原本的strcasestr函数以便重新定义
#undef strcasestr
// 自定义的strcasestr函数用于忽略大小写的字符串查找
char* strcasestr(const char* haystack, const char* needle) {
// 检查haystack是否为只读内存如果是则输出内存内容
if (__tokencap_is_ro(haystack))
__tokencap_dump(haystack, strlen(haystack), 1);
// 检查needle是否为只读内存如果是则输出内存内容
if (__tokencap_is_ro(needle))
__tokencap_dump(needle, strlen(needle), 1);
// 在haystack中查找needle忽略大小写
do {
const char* n = needle;
const char* h = haystack;
// 逐字符比较haystack和needle忽略大小写
while(*n && *h && tolower(*n) == tolower(*h)) n++, h++;
// 如果needle的所有字符都被匹配返回匹配开始的位置
if(!*n) return (char*)haystack;
} while(*(haystack++));
} while(*(haystack++)); // 移动到haystack的下一个字符并继续查找
// 如果没有找到needle返回NULL
return 0;
}
// 程序启动时执行的初始化代码
/* Init code to open the output file (or default to stderr). */
__attribute__((constructor)) void __tokencap_init(void) {
u8* fn = getenv("AFL_TOKEN_FILE");
u8* fn = getenv("AFL_TOKEN_FILE"); // 获取环境变量AFL_TOKEN_FILE的值
// 如果环境变量存在,则以追加模式打开文件
if (fn) __tokencap_out_file = fopen(fn, "a");
// 如果文件打开失败,则将输出重定向到标准错误输出
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 */
/* 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) {
// 获取环境变量 AFL_PATH
u8 *afl_path = getenv("AFL_PATH");
u8 *slash, *tmp;
// 如果找到 AFL_PATH
if (afl_path) {
// 生成 afl-llvm-rt.o 的完整路径
tmp = alloc_printf("%s/afl-llvm-rt.o", afl_path);
// 检查文件是否可读
if (!access(tmp, R_OK)) {
obj_path = afl_path;
ck_free(tmp);
return;
obj_path = afl_path; // 设置对象路径为 AFL_PATH
ck_free(tmp); // 释放临时路径内存
return; // 找到文件,结束函数
}
ck_free(tmp);
ck_free(tmp); // 释放临时路径内存
}
// 查找 argv0 中最后一个 '/' 的位置
slash = strrchr(argv0, '/');
// 如果找到 '/'
if (slash) {
u8 *dir;
*slash = 0;
dir = ck_strdup(argv0);
*slash = '/';
*slash = 0; // 将 '/' 替换为结束符,以获取目录
dir = ck_strdup(argv0); // 复制目录名
*slash = '/'; // 恢复原来的 '/' 字符
// 生成 afl-llvm-rt.o 的完整路径
tmp = alloc_printf("%s/afl-llvm-rt.o", dir);
// 检查文件是否可读
if (!access(tmp, R_OK)) {
obj_path = dir;
ck_free(tmp);
return;
obj_path = dir; // 设置对象路径为找到的目录
ck_free(tmp); // 释放临时路径内存
return; // 找到文件,结束函数
}
ck_free(tmp);
ck_free(dir);
ck_free(tmp); // 释放临时路径内存
ck_free(dir); // 释放目录名内存
}
// 检查默认路径下的 afl-llvm-rt.o 是否可读
if (!access(AFL_PATH "/afl-llvm-rt.o", R_OK)) {
obj_path = AFL_PATH;
return;
obj_path = AFL_PATH; // 设置对象路径为默认的 AFL_PATH
return; // 找到文件,结束函数
}
// 如果都找不到,则抛出致命错误
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) {
// 初始化标志变量
u8 fortify_set = 0, asan_set = 0, x_set = 0, bit_mode = 0;
u8 *name;
// 分配空间以存储参数
cc_params = ck_alloc((argc + 128) * sizeof(u8*));
// 获取执行的程序名称
name = strrchr(argv[0], '/');
if (!name) name = argv[0]; else name++;
// 根据程序名称选择合适的编译器
if (!strcmp(name, "afl-clang-fast++")) {
u8* alt_cxx = getenv("AFL_CXX");
cc_params[0] = alt_cxx ? alt_cxx : (u8*)"clang++";
u8* alt_cxx = getenv("AFL_CXX"); // 获取环境变量 AFL_CXX
cc_params[0] = alt_cxx ? alt_cxx : (u8*)"clang++"; // 设置 C++ 编译器
} else {
u8* alt_cc = getenv("AFL_CC");
cc_params[0] = alt_cc ? alt_cc : (u8*)"clang";
u8* alt_cc = getenv("AFL_CC"); // 获取环境变量 AFL_CC
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
// 启用跟踪 PC 的覆盖率
cc_params[cc_par_cnt++] = "-fsanitize-coverage=trace-pc-guard";
#ifndef __ANDROID__
cc_params[cc_par_cnt++] = "-mllvm";
cc_params[cc_par_cnt++] = "-sanitizer-coverage-block-threshold=0";
cc_params[cc_par_cnt++] = "-mllvm"; // LLVM 选项
cc_params[cc_par_cnt++] = "-sanitizer-coverage-block-threshold=0"; // 设定阈值
#endif
#else
// 加载 afl-llvm-pass.so 插件
cc_params[cc_par_cnt++] = "-Xclang";
cc_params[cc_par_cnt++] = "-load";
cc_params[cc_par_cnt++] = "-Xclang";
cc_params[cc_par_cnt++] = alloc_printf("%s/afl-llvm-pass.so", obj_path);
#endif /* ^USE_TRACE_PC */
cc_params[cc_par_cnt++] = "-Qunused-arguments";
cc_params[cc_par_cnt++] = "-Qunused-arguments"; // 忽略未使用的参数
// 循环处理输入参数
while (--argc) {
u8* cur = *(++argv);
u8* cur = *(++argv); // 获取当前参数
// 检查位模式
if (!strcmp(cur, "-m32")) bit_mode = 32;
if (!strcmp(cur, "armv7a-linux-androideabi")) bit_mode = 32;
if (!strcmp(cur, "-m64")) bit_mode = 64;
// 检查其他编译选项
if (!strcmp(cur, "-x")) x_set = 1;
// 检查是否启用地址/内存的 sanitization
if (!strcmp(cur, "-fsanitize=address") ||
!strcmp(cur, "-fsanitize=memory")) asan_set = 1;
// 检查 FORTIFY_SOURCE 是否启用
if (strstr(cur, "FORTIFY_SOURCE")) fortify_set = 1;
// 跳过某些链接器选项
if (!strcmp(cur, "-Wl,-z,defs") ||
!strcmp(cur, "-Wl,--no-undefined")) continue;
// 将当前参数添加到 cc_params
cc_params[cc_par_cnt++] = cur;
}
// 如果启用了硬化选项
if (getenv("AFL_HARDEN")) {
cc_params[cc_par_cnt++] = "-fstack-protector-all"; // 启用栈保护
cc_params[cc_par_cnt++] = "-fstack-protector-all";
// 如果没有启用 FORTIFY_SOURCE 则添加对应宏定义
if (!fortify_set)
cc_params[cc_par_cnt++] = "-D_FORTIFY_SOURCE=2";
}
// 检查地址或内存 sanitization 的设置
if (!asan_set) {
if (getenv("AFL_USE_ASAN")) {
if (getenv("AFL_USE_MSAN"))
FATAL("ASAN and MSAN are mutually exclusive");
FATAL("ASAN and MSAN are mutually exclusive"); // 互斥检查
if (getenv("AFL_HARDEN"))
FATAL("ASAN and AFL_HARDEN are mutually exclusive");
cc_params[cc_par_cnt++] = "-U_FORTIFY_SOURCE";
cc_params[cc_par_cnt++] = "-fsanitize=address";
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"; // 启用地址条件检测
} else if (getenv("AFL_USE_MSAN")) {
if (getenv("AFL_USE_ASAN"))
FATAL("ASAN and MSAN are mutually exclusive");
FATAL("ASAN and MSAN are mutually exclusive"); // 互斥检查
if (getenv("AFL_HARDEN"))
FATAL("MSAN and AFL_HARDEN are mutually exclusive");
cc_params[cc_par_cnt++] = "-U_FORTIFY_SOURCE";
cc_params[cc_par_cnt++] = "-fsanitize=memory";
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"; // 启用内存条件检测
}
}
#ifdef USE_TRACE_PC
// 检查 AFL_INST_RATIO 环境变量的设置
if (getenv("AFL_INST_RATIO"))
FATAL("AFL_INST_RATIO not available at compile time with 'trace-pc'.");
#endif /* USE_TRACE_PC */
if (!getenv("AFL_DONT_OPTIMIZE")) {
cc_params[cc_par_cnt++] = "-g";
cc_params[cc_par_cnt++] = "-O3";
cc_params[cc_par_cnt++] = "-funroll-loops";
}
#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-strncmp";
cc_params[cc_par_cnt++] = "-fno-builtin-strcasecmp";
cc_params[cc_par_cnt++] = "-fno-builtin-strncasecmp";
cc_params[cc_par_cnt++] = "-fno-builtin-memcmp";
}
}
cc_params[cc_par_cnt++] = "-D__AFL_HAVE_MANUAL_CONTROL=1";
cc_params[cc_par_cnt++] = "-D__AFL_COMPILER=1";
cc_params[cc_par_cnt++] = "-DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION=1";
/* When the user tries to use persistent or deferred forkserver modes by
appending a single line to the program, we want to reliably inject a
signature into the binary (to be picked up by afl-fuzz) and we want
to call a function from the runtime .o file. This is unnecessarily
painful for three reasons:
1) We need to convince the compiler not to optimize out the signature.
This is done with __attribute__((used)).
2) We need to convince the linker, when called with -Wl,--gc-sections,
not to do the same. This is done by forcing an assignment to a
'volatile' pointer.
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.
*/
// 添加 AFL 相关的宏定义
cc_params[cc_par_cnt++] = "-D__AFL_HAVE_MANUAL_CONTROL=1"; // 手动控制
cc_params[cc_par_cnt++] = "-D__AFL_COMPILER=1"; // 标记为 AFL 编译器
cc_params[cc_par_cnt++] = "-DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION=1"; // 不适合生产的模糊构建模式
cc_params[cc_par_cnt++] = "-D__AFL_LOOP(_A)="
// 添加 AFL 循环的实现
cc_params[cc_par_cnt++] = "-D__AFL_LOOP(_A)="
"({ static volatile char *_B __attribute__((used)); "
" _B = (char*)\"" PERSIST_SIG "\"; "
#ifdef __APPLE__
@ -258,9 +501,10 @@ static void edit_params(u32 argc, char** argv) {
"__attribute__((visibility(\"default\"))) "
"int _L(unsigned int) __asm__(\"__afl_persistent_loop\"); "
#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)); "
" _A = (char*)\"" DEFER_SIG "\"; "
#ifdef __APPLE__
@ -270,58 +514,57 @@ static void edit_params(u32 argc, char** argv) {
"__attribute__((visibility(\"default\"))) "
"void _I(void) __asm__(\"__afl_manual_init\"); "
#endif /* ^__APPLE__ */
"_I(); } while (0)";
"_I(); } while (0)"; // 定义 AFL 初始化的宏
if (x_set) {
cc_params[cc_par_cnt++] = "-x";
cc_params[cc_par_cnt++] = "none";
}
// 如果启用 -x 选项
if (x_set) {
cc_params[cc_par_cnt++] = "-x"; // 添加 -x 选项
cc_params[cc_par_cnt++] = "none"; // 设置为 none表示不对输入进行特定语言解析
}
#ifndef __ANDROID__
switch (bit_mode) {
// 根据位模式选择不同的运行时库文件
switch (bit_mode) {
case 0:
cc_params[cc_par_cnt++] = alloc_printf("%s/afl-llvm-rt.o", obj_path);
break;
cc_params[cc_par_cnt++] = alloc_printf("%s/afl-llvm-rt.o", obj_path); // 默认情况下使用通用的运行时文件
break;
case 32:
cc_params[cc_par_cnt++] = alloc_printf("%s/afl-llvm-rt-32.o", obj_path);
if (access(cc_params[cc_par_cnt - 1], R_OK))
FATAL("-m32 is not supported by your compiler");
break;
cc_params[cc_par_cnt++] = alloc_printf("%s/afl-llvm-rt-32.o", obj_path); // 使用 32 位版本的运行时文件
// 检查此文件是否可读
if (access(cc_params[cc_par_cnt - 1], R_OK))
FATAL("-m32 is not supported by your compiler"); // 如果不可读则报错
break;
case 64:
cc_params[cc_par_cnt++] = alloc_printf("%s/afl-llvm-rt-64.o", obj_path);
if (access(cc_params[cc_par_cnt - 1], R_OK))
FATAL("-m64 is not supported by your compiler");
break;
}
#endif
cc_params[cc_par_cnt] = NULL;
cc_params[cc_par_cnt++] = alloc_printf("%s/afl-llvm-rt-64.o", obj_path); // 使用 64 位版本的运行时文件
// 检查此文件是否可读
if (access(cc_params[cc_par_cnt - 1], R_OK))
FATAL("-m64 is not supported by your compiler"); // 如果不可读则报错
break;
}
// 结束参数数组,以空指针结尾
cc_params[cc_par_cnt] = NULL;
/* Main entry point */
/* 主入口点 */
int main(int argc, char** argv) {
// 检查标准错误是否连接到终端且 AFL_QUIET 环境变量未设置
if (isatty(2) && !getenv("AFL_QUIET")) {
#ifdef USE_TRACE_PC
// 如果启用了 USE_TRACE_PC打印版本信息
SAYF(cCYA "afl-clang-fast [tpcg] " cBRI VERSION cRST " by <lszekeres@google.com>\n");
#else
// 否则,仅打印版本信息
SAYF(cCYA "afl-clang-fast " cBRI VERSION cRST " by <lszekeres@google.com>\n");
#endif /* ^USE_TRACE_PC */
}
// 如果参数数量小于 2打印帮助信息并退出
if (argc < 2) {
SAYF("\n"
@ -339,21 +582,23 @@ int main(int argc, char** argv) {
"AFL_HARDEN enables hardening optimizations in the compiled code.\n\n",
BIN_PATH, BIN_PATH);
exit(1);
exit(1); // 退出程序,返回错误码 1
}
#ifndef __ANDROID__
// 在非 Android 平台下调用 find_obj 函数查找运行时库
find_obj(argv[0]);
#endif
// 调用 edit_params 函数处理命令行参数并设置编译参数
edit_params(argc, argv);
// 使用 execvp 执行指定的编译器命令
execvp(cc_params[0], (char**)cc_params);
// 如果 execvp 失败,抛出致命错误并打印相关信息
FATAL("Oops, failed to execute '%s' - check your PATH", cc_params[0]);
return 0;
return 0; // 正常结束程序
}

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

Loading…
Cancel
Save