diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json new file mode 100644 index 0000000..cea4d3f --- /dev/null +++ b/.vscode/c_cpp_properties.json @@ -0,0 +1,18 @@ +{ + "configurations": [ + { + "name": "windows-gcc-x64", + "includePath": [ + "${workspaceFolder}/**" + ], + "compilerPath": "gcc", + "cStandard": "${default}", + "cppStandard": "${default}", + "intelliSenseMode": "windows-gcc-x64", + "compilerArgs": [ + "" + ] + } + ], + "version": 4 +} \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..c972a54 --- /dev/null +++ b/.vscode/launch.json @@ -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 + } + ] + } + ] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..925fab1 --- /dev/null +++ b/.vscode/settings.json @@ -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" + } +} \ No newline at end of file diff --git a/src/.vscode/settings.json b/src/.vscode/settings.json index 471970e..717327f 100644 --- a/src/.vscode/settings.json +++ b/src/.vscode/settings.json @@ -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" } } \ No newline at end of file diff --git a/src/afl-analyze.c b/src/afl-analyze.c index c97b73d..633f468 100644 --- a/src/afl-analyze.c +++ b/src/afl-analyze.c @@ -1,133 +1,133 @@ /* - Copyright 2016 Google LLC All rights reserved. + 版权所有 2016 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 - file format analyzer + American Fuzzy Lop - 文件格式分析器 ----------------------------------------- - Written and maintained by Michal Zalewski + 作者和维护者:Michal Zalewski - A nifty utility that grabs an input file and takes a stab at explaining - its structure by observing how changes to it affect the execution path. - - If the output scrolls past the edge of the screen, pipe it to 'less -r'. + 一个很酷的工具,它获取输入文件并尝试解释 + 其结构,通过观察对其进行更改如何影响执行路径。 + 如果输出超出了屏幕边缘,请将其管道到 'less -r'。 */ -#define AFL_MAIN -#include "android-ashmem.h" +#define AFL_MAIN // 定义 AFL_MAIN,表示这是 AFL 的主程序 +#include "android-ashmem.h" // 包含 Android 共享内存的头文件 -#include "config.h" -#include "types.h" -#include "debug.h" -#include "alloc-inl.h" -#include "hash.h" +#include "config.h" // 包含配置文件 +#include "types.h" // 包含自定义类型定义 +#include "debug.h" // 包含调试工具的头文件 +#include "alloc-inl.h" // 包含内存分配工具的头文件 +#include "hash.h" // 包含哈希函数的头文件 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include // 包含标准输入输出函数 +#include // 包含 Unix 标准函数 +#include // 包含标准库函数 +#include // 包含字符串处理函数 +#include // 包含时间相关函数 +#include // 包含错误处理相关函数 +#include // 包含信号处理相关函数 +#include // 包含目录操作相关函数 +#include // 包含文件控制函数 +#include // 包含字符处理函数 -#include -#include -#include -#include -#include -#include +#include // 包含进程管理相关函数 +#include // 包含时间和计时器相关函数 +#include // 包含共享内存相关函数 +#include // 包含文件状态相关函数 +#include // 包含数据类型相关函数 +#include // 包含资源管理相关函数 -static s32 child_pid; /* PID of the tested program */ +static s32 child_pid; // 被测试程序的进程 ID -static u8* trace_bits; /* SHM with instrumentation bitmap */ +static u8* trace_bits; // 用于存储仪器化位图的共享内存 -static u8 *in_file, /* Analyzer input test case */ - *prog_in, /* Targeted program input file */ - *target_path, /* Path to target binary */ - *doc_path; /* Path to docs */ +static u8* in_file, // 分析输入测试用例的文件 + *prog_in, // 目标程序输入文件 + *target_path, // 目标二进制文件路径 + *doc_path; // 文档路径 -static u8 *in_data; /* Input data for analysis */ +static u8* in_data; // 用于分析的输入数据 -static u32 in_len, /* Input data length */ - orig_cksum, /* Original checksum */ - total_execs, /* Total number of execs */ - exec_hangs, /* Total number of hangs */ - exec_tmout = EXEC_TIMEOUT; /* Exec timeout (ms) */ +static u32 in_len, // 输入数据长度 + orig_cksum, // 原始校验和 + total_execs, // 总执行次数 + exec_hangs, // 总挂起次数 + exec_tmout = EXEC_TIMEOUT; // 执行超时(毫秒) -static u64 mem_limit = MEM_LIMIT; /* Memory limit (MB) */ +static u64 mem_limit = MEM_LIMIT; // 内存限制(MB) -static s32 shm_id, /* ID of the SHM region */ - dev_null_fd = -1; /* FD to /dev/null */ +static s32 shm_id, // 共享内存区域的 ID + dev_null_fd = -1; // /dev/null 的文件描述符 -static u8 edges_only, /* Ignore hit counts? */ - use_hex_offsets, /* Show hex offsets? */ - use_stdin = 1; /* Use stdin for program input? */ +static u8 edges_only, // 是否忽略命中计数 + use_hex_offsets, // 是否显示十六进制偏移 + use_stdin = 1; // 是否使用标准输入作为程序输入 static volatile u8 - stop_soon, /* Ctrl-C pressed? */ - child_timed_out; /* Child timed out? */ - + stop_soon, // 是否按下 Ctrl-C + child_timed_out; // 子进程是否超时 -/* Constants used for describing byte behavior. */ +/* 用于描述字节行为的常量. */ -#define RESP_NONE 0x00 /* Changing byte is a no-op. */ -#define RESP_MINOR 0x01 /* Some changes have no effect. */ -#define RESP_VARIABLE 0x02 /* Changes produce variable paths. */ -#define RESP_FIXED 0x03 /* Changes produce fixed patterns. */ +#define RESP_NONE 0x00 // 改变字节是无操作。 +#define RESP_MINOR 0x01 // 一些更改没有影响。 +#define RESP_VARIABLE 0x02 // 更改产生可变路径。 +#define RESP_FIXED 0x03 // 更改产生固定模式。 -#define RESP_LEN 0x04 /* Potential length field */ -#define RESP_CKSUM 0x05 /* Potential checksum */ -#define RESP_SUSPECT 0x06 /* Potential "suspect" blob */ +#define RESP_LEN 0x04 // 潜在的长度字段 +#define RESP_CKSUM 0x05 // 潜在的校验和 +#define RESP_SUSPECT 0x06 // 潜在的“可疑”数据块 -/* Classify tuple counts. This is a slow & naive version, but good enough here. */ +/* 分类元组计数。这是一个慢而天真的版本,但足够好了。 */ static u8 count_class_lookup[256] = { - [0] = 0, - [1] = 1, - [2] = 2, - [3] = 4, - [4 ... 7] = 8, - [8 ... 15] = 16, - [16 ... 31] = 32, - [32 ... 127] = 64, - [128 ... 255] = 128 + [0] = 0, // 0个元组 + [1] = 1, // 1个元组 + [2] = 2, // 2个元组 + [3] = 4, // 3个元组 + [4 ... 7] = 8, // 4到7个元组 + [8 ... 15] = 16, // 8到15个元组 + [16 ... 31] = 32, // 16到31个元组 + [32 ... 127] = 64, // 32到127个元组 + [128 ... 255] = 128 // 128到255个元组 }; +/* 分类计数 */ + static void classify_counts(u8* mem) { - u32 i = MAP_SIZE; + u32 i = MAP_SIZE; // 初始化索引为映射大小 - if (edges_only) { + if (edges_only) { // 如果只计算边缘覆盖 while (i--) { - if (*mem) *mem = 1; + if (*mem) *mem = 1; // 将非零值标记为1 mem++; } - } else { + } else { // 如果不只计算边缘覆盖 while (i--) { - *mem = count_class_lookup[*mem]; + *mem = count_class_lookup[*mem]; // 使用查找表分类计数 mem++; } @@ -136,273 +136,265 @@ static void classify_counts(u8* mem) { } -/* See if any bytes are set in the bitmap. */ +/* 检查位图中是否有任何字节被设置。 */ static inline u8 anything_set(void) { - u32* ptr = (u32*)trace_bits; - u32 i = (MAP_SIZE >> 2); + u32* ptr = (u32*)trace_bits; // 将位图指针转换为32位无符号整型指针 + u32 i = (MAP_SIZE >> 2); // 计算32位整型的数量 - while (i--) if (*(ptr++)) return 1; + while (i--) if (*(ptr++)) return 1; // 如果有非零值,返回1 - return 0; + return 0; // 否则返回0 } -/* Get rid of shared memory and temp files (atexit handler). */ +/* 删除共享内存和临时文件(atexit处理程序)。 */ static void remove_shm(void) { - unlink(prog_in); /* Ignore errors */ - shmctl(shm_id, IPC_RMID, NULL); + unlink(prog_in); /* 忽略错误 */ + 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); + 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); // 注册退出时调用的处理函数 - 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); // 设置共享内存环境变量 - 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"); // 检查共享内存附加是否成功 } -/* Read initial file. */ +/* 读取初始文件。 */ static void read_initial_file(void) { - struct stat st; - s32 fd = open(in_file, O_RDONLY); + struct stat st; // 文件状态结构 + s32 fd = open(in_file, O_RDONLY); // 打开输入文件 - if (fd < 0) PFATAL("Unable to open '%s'", in_file); + if (fd < 0) PFATAL("Unable to open '%s'", in_file); // 检查文件是否成功打开 - if (fstat(fd, &st) || !st.st_size) - FATAL("Zero-sized input file."); + if (fstat(fd, &st) || !st.st_size) // 获取文件状态 + FATAL("Zero-sized input file."); // 检查文件大小是否为零 - if (st.st_size >= TMIN_MAX_FILE) + if (st.st_size >= TMIN_MAX_FILE) // 检查文件大小是否超过最大值 FATAL("Input file is too large (%u MB max)", TMIN_MAX_FILE / 1024 / 1024); - in_len = st.st_size; - in_data = ck_alloc_nozero(in_len); - - ck_read(fd, in_data, in_len, in_file); + in_len = st.st_size; // 设置输入数据长度 + in_data = ck_alloc_nozero(in_len); // 分配内存以存储输入数据 - close(fd); + ck_read(fd, in_data, in_len, in_file); // 读取输入文件数据 - OKF("Read %u byte%s from '%s'.", in_len, in_len == 1 ? "" : "s", in_file); + close(fd); // 关闭文件描述符 + OKF("Read %u byte%s from '%s'.", in_len, in_len == 1 ? "" : "s", in_file); // 输出读取的字节数 } -/* Write output file. */ +/* 写入输出文件。 */ static s32 write_to_file(u8* path, u8* mem, u32 len) { s32 ret; - unlink(path); /* Ignore errors */ + unlink(path); /* 忽略错误 */ // 删除旧文件(如果存在) - ret = open(path, O_RDWR | O_CREAT | O_EXCL, 0600); + ret = open(path, O_RDWR | O_CREAT | O_EXCL, 0600); // 创建新文件 - if (ret < 0) PFATAL("Unable to create '%s'", path); + if (ret < 0) PFATAL("Unable to create '%s'", path); // 检查文件创建是否成功 - ck_write(ret, mem, len, path); + ck_write(ret, mem, len, path); // 写入数据到新文件 - lseek(ret, 0, SEEK_SET); - - return ret; + lseek(ret, 0, SEEK_SET); // 将文件指针移动到文件开头 + return ret; // 返回文件描述符 } -/* Handle timeout signal. */ +/* 处理超时信号。 */ static void handle_timeout(int sig) { - child_timed_out = 1; - if (child_pid > 0) kill(child_pid, SIGKILL); + child_timed_out = 1; // 设置标志表示子进程超时 + if (child_pid > 0) kill(child_pid, SIGKILL); // 杀死子进程 } -/* Execute target application. Returns exec checksum, or 0 if program - times out. */ +/* 执行目标应用程序。返回执行校验和,如果程序超时则返回 0。 */ static u32 run_target(char** argv, u8* mem, u32 len, u8 first_run) { - static struct itimerval it; - int status = 0; + static struct itimerval it; // 定义计时器 + int status = 0; // 定义状态码 - s32 prog_in_fd; - u32 cksum; + s32 prog_in_fd; // 程序输入文件描述符 + u32 cksum; // 校验和 - memset(trace_bits, 0, MAP_SIZE); - MEM_BARRIER(); + memset(trace_bits, 0, MAP_SIZE); // 清空轨迹位图 + MEM_BARRIER(); // 保证内存操作的顺序 - prog_in_fd = write_to_file(prog_in, mem, len); + prog_in_fd = write_to_file(prog_in, mem, len); // 写入输入数据到临时文件 - child_pid = fork(); + child_pid = fork(); // 创建子进程 - if (child_pid < 0) PFATAL("fork() failed"); + if (child_pid < 0) PFATAL("fork() failed"); // 检查是否成功创建子进程 - if (!child_pid) { + if (!child_pid) { // 如果是子进程 - struct rlimit r; + struct rlimit r; // 定义资源限制结构 - if (dup2(use_stdin ? prog_in_fd : dev_null_fd, 0) < 0 || - dup2(dev_null_fd, 1) < 0 || - dup2(dev_null_fd, 2) < 0) { + if (dup2(use_stdin ? prog_in_fd : dev_null_fd, 0) < 0 || // 重定向标准输入 + dup2(dev_null_fd, 1) < 0 || // 重定向标准输出 + dup2(dev_null_fd, 2) < 0) { // 重定向标准错误 - *(u32*)trace_bits = EXEC_FAIL_SIG; - PFATAL("dup2() failed"); + *(u32*)trace_bits = EXEC_FAIL_SIG; // 设置执行失败信号 + PFATAL("dup2() failed"); // 检查重定向是否成功 } - close(dev_null_fd); - close(prog_in_fd); + close(dev_null_fd); // 关闭 /dev/null 文件描述符 + close(prog_in_fd); // 关闭程序输入文件描述符 - 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; // 设置资源限制 #ifdef RLIMIT_AS - - setrlimit(RLIMIT_AS, &r); /* Ignore errors */ - + setrlimit(RLIMIT_AS, &r); /* 忽略错误 */ #else - - setrlimit(RLIMIT_DATA, &r); /* Ignore errors */ - + setrlimit(RLIMIT_DATA, &r); /* 忽略错误 */ #endif /* ^RLIMIT_AS */ } - r.rlim_max = r.rlim_cur = 0; - setrlimit(RLIMIT_CORE, &r); /* Ignore errors */ + r.rlim_max = r.rlim_cur = 0; // 禁止核心转储 + setrlimit(RLIMIT_CORE, &r); /* 忽略错误 */ - execv(target_path, argv); + execv(target_path, argv); // 执行目标程序 - *(u32*)trace_bits = EXEC_FAIL_SIG; - exit(0); + *(u32*)trace_bits = EXEC_FAIL_SIG; // 设置执行失败信号 + exit(0); // 退出子进程 } - close(prog_in_fd); + close(prog_in_fd); // 关闭程序输入文件描述符 - /* Configure timeout, wait for child, cancel timeout. */ + /* 配置超时,等待子进程,取消超时。 */ - 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; + child_pid = 0; // 重置子进程 ID + it.it_value.tv_sec = 0; // 重置计时器秒数 + it.it_value.tv_usec = 0; // 重置计时器微秒数 - setitimer(ITIMER_REAL, &it, NULL); + setitimer(ITIMER_REAL, &it, NULL); // 取消计时器 - MEM_BARRIER(); + MEM_BARRIER(); // 保证内存操作的顺序 - /* Clean up bitmap, analyze exit condition, etc. */ + /* 清理位图,分析退出情况等。 */ - if (*(u32*)trace_bits == EXEC_FAIL_SIG) - FATAL("Unable to execute '%s'", argv[0]); + if (*(u32*)trace_bits == EXEC_FAIL_SIG) // 如果执行失败信号被设置 + FATAL("无法执行 '%s'", argv[0]); // 输出错误信息 - classify_counts(trace_bits); - total_execs++; + classify_counts(trace_bits); // 分类轨迹位图中的计数 + total_execs++; // 总执行次数加一 if (stop_soon) { - SAYF(cRST cLRD "\n+++ Analysis aborted by user +++\n" cRST); - exit(1); + SAYF(cRST cLRD "\n+++ 用户中止分析 +++\n" cRST); // 输出分析中止提示 + exit(1); // 退出程序 } - /* Always discard inputs that time out. */ + /* 始终丢弃超时的输入。 */ - if (child_timed_out) { + if (child_timed_out) { // 如果子进程超时 - exec_hangs++; - return 0; + exec_hangs++; // 挂起次数加一 + return 0; // 返回0表示超时 } - cksum = hash32(trace_bits, MAP_SIZE, HASH_CONST); + cksum = hash32(trace_bits, MAP_SIZE, HASH_CONST); // 计算轨迹位图的哈希值 - /* We don't actually care if the target is crashing or not, - except that when it does, the checksum should be different. */ + /* 我们实际上并不在乎目标程序是否崩溃, + 除非它崩溃,校验和应该是不同的。 */ - if (WIFSIGNALED(status) || - (WIFEXITED(status) && WEXITSTATUS(status) == MSAN_ERROR) || - (WIFEXITED(status) && WEXITSTATUS(status))) { + if (WIFSIGNALED(status) || // 如果进程被信号终止 + (WIFEXITED(status) && WEXITSTATUS(status) == MSAN_ERROR) || // 如果进程以错误状态退出 + (WIFEXITED(status) && WEXITSTATUS(status))) { // 如果进程以非零状态退出 - cksum ^= 0xffffffff; + cksum ^= 0xffffffff; // 取反校验和 } - if (first_run) orig_cksum = cksum; - - return cksum; + if (first_run) orig_cksum = cksum; // 如果是第一次运行,更新原始校验和 + return cksum; // 返回校验和 } -#ifdef USE_COLOR +/* 帮助函数,显示可读的字符。 */ -/* Helper function to display a human-readable character. */ +#ifdef USE_COLOR static void show_char(u8 val) { switch (val) { - case 0 ... 32: - case 127 ... 255: SAYF("#%02x", val); break; + case 0 ... 32: // 控制字符 + case 127 ... 255: SAYF("#%02x", val); break; // 显示字符的十六进制值 - default: SAYF(" %c ", val); + default: SAYF(" %c ", val); // 显示可读字符 } } -/* Show the legend */ +/* 显示图例 */ static void show_legend(void) { - SAYF(" " cLGR bgGRA " 01 " cRST " - no-op block " - cBLK bgLGN " 01 " cRST " - suspected length field\n" - " " cBRI bgGRA " 01 " cRST " - superficial content " - cBLK bgYEL " 01 " cRST " - suspected cksum or magic int\n" - " " cBLK bgCYA " 01 " cRST " - critical stream " - cBLK bgLRD " 01 " cRST " - suspected checksummed block\n" - " " cBLK bgMGN " 01 " cRST " - \"magic value\" section\n\n"); + SAYF(" " cLGR bgGRA " 01 " cRST " - 无操作块 " + cBLK bgLGN " 01 " cRST " - 可疑长度字段\n" + " " cBRI bgGRA " 01 " cRST " - 表面内容 " + cBLK bgYEL " 01 " cRST " - 可疑的校验和或魔法整数\n" + " " cBLK bgCYA " 01 " cRST " - 关键流 " + cBLK bgLRD " 01 " cRST " - 可疑的校验和块\n" + " " cBLK bgMGN " 01 " cRST " - “魔法值”部分\n\n"); // 显示不同类型的说明 } #endif /* USE_COLOR */ -/* Interpret and report a pattern in the input file. */ +/* 解释并报告输入文件中的模式。 */ static void dump_hex(u8* buf, u32 len, u8* b_data) { @@ -411,43 +403,43 @@ static void dump_hex(u8* buf, u32 len, u8* b_data) { for (i = 0; i < len; i++) { #ifdef USE_COLOR - u32 rlen = 1, off; + u32 rlen = 1, off; // 当前运行长度和偏移量 #else - u32 rlen = 1; + u32 rlen = 1; // 当前运行长度 #endif /* ^USE_COLOR */ - u8 rtype = b_data[i] & 0x0f; + u8 rtype = b_data[i] & 0x0f; // 获取当前类型 - /* Look ahead to determine the length of run. */ + /* 向前查看以确定运行长度。 */ - while (i + rlen < len && (b_data[i] >> 7) == (b_data[i + rlen] >> 7)) { + while (i + rlen < len && (b_data[i] >> 7) == (b_data[i + rlen] >> 7)) { // 检查相同类型的连续性 - if (rtype < (b_data[i + rlen] & 0x0f)) rtype = b_data[i + rlen] & 0x0f; - rlen++; + if (rtype < (b_data[i + rlen] & 0x0f)) rtype = b_data[i + rlen] & 0x0f; // 更新类型 + rlen++; // 增加长度 } - /* Try to do some further classification based on length & value. */ + /* 根据长度和数值进行进一步分类。 */ - if (rtype == RESP_FIXED) { + if (rtype == RESP_FIXED) { // 如果是固定类型 - switch (rlen) { + switch (rlen) { // 根据长度分类 - case 2: { + case 2: { // 长度为2 - u16 val = *(u16*)(in_data + i); + u16 val = *(u16*)(in_data + i); // 获取16位值 - /* Small integers may be length fields. */ + /* 小整数可能是长度字段。 */ if (val && (val <= in_len || SWAP16(val) <= in_len)) { - rtype = RESP_LEN; + rtype = RESP_LEN; // 更新为长度字段 break; } - /* Uniform integers may be checksums. */ + /* 均匀整数可能是校验和。 */ if (val && abs(in_data[i] - in_data[i + 1]) > 32) { - rtype = RESP_CKSUM; + rtype = RESP_CKSUM; // 更新为校验和 break; } @@ -455,23 +447,23 @@ static void dump_hex(u8* buf, u32 len, u8* b_data) { } - case 4: { + case 4: { // 长度为4 - u32 val = *(u32*)(in_data + i); + u32 val = *(u32*)(in_data + i); // 获取32位值 - /* Small integers may be length fields. */ + /* 小整数可能是长度字段。 */ if (val && (val <= in_len || SWAP32(val) <= in_len)) { - rtype = RESP_LEN; + rtype = RESP_LEN; // 更新为长度字段 break; } - /* Uniform integers may be checksums. */ + /* 均匀整数可能是校验和。 */ if (val && (in_data[i] >> 7 != in_data[i + 1] >> 7 || in_data[i] >> 7 != in_data[i + 2] >> 7 || in_data[i] >> 7 != in_data[i + 3] >> 7)) { - rtype = RESP_CKSUM; + rtype = RESP_CKSUM; // 更新为校验和 break; } @@ -479,240 +471,235 @@ static void dump_hex(u8* buf, u32 len, u8* b_data) { } - case 1: case 3: case 5 ... MAX_AUTO_EXTRA - 1: break; + case 1: case 3: case 5 ... MAX_AUTO_EXTRA - 1: break; // 处理其他长度 - default: rtype = RESP_SUSPECT; + default: rtype = RESP_SUSPECT; // 标记为可疑 } } - /* Print out the entire run. */ + /* 打印整个运行。 */ #ifdef USE_COLOR - for (off = 0; off < rlen; off++) { + for (off = 0; off < rlen; off++) { // 逐个输出每个字符 - /* Every 16 digits, display offset. */ + /* 每16个数字显示一次偏移。 */ - if (!((i + off) % 16)) { + if (!((i + off) % 16)) { // 检查是否需要显示偏移 - if (off) SAYF(cRST cLCY ">"); + if (off) SAYF(cRST cLCY ">"); // 显示分隔符 if (use_hex_offsets) - SAYF(cRST cGRA "%s[%06x] " cRST, (i + off) ? "\n" : "", i + off); + SAYF(cRST cGRA "%s[%06x] " cRST, (i + off) ? "\n" : "", i + off); // 显示偏移 else - SAYF(cRST cGRA "%s[%06u] " cRST, (i + off) ? "\n" : "", i + off); + SAYF(cRST cGRA "%s[%06u] " cRST, (i + off) ? "\n" : "", i + off); // 显示偏移 } - switch (rtype) { + switch (rtype) { // 根据类型设置颜色 - case RESP_NONE: SAYF(cLGR bgGRA); break; - case RESP_MINOR: SAYF(cBRI bgGRA); break; - case RESP_VARIABLE: SAYF(cBLK bgCYA); break; - case RESP_FIXED: SAYF(cBLK bgMGN); break; - case RESP_LEN: SAYF(cBLK bgLGN); break; - case RESP_CKSUM: SAYF(cBLK bgYEL); break; - case RESP_SUSPECT: SAYF(cBLK bgLRD); break; + case RESP_NONE: SAYF(cLGR bgGRA); break; // 无操作 + case RESP_MINOR: SAYF(cBRI bgGRA); break; // 小改动 + case RESP_VARIABLE: SAYF(cBLK bgCYA); break; // 可变 + case RESP_FIXED: SAYF(cBLK bgMGN); break; // 固定 + case RESP_LEN: SAYF(cBLK bgLGN); break; // 长度字段 + case RESP_CKSUM: SAYF(cBLK bgYEL); break; // 校验和 + case RESP_SUSPECT: SAYF(cBLK bgLRD); break; // 可疑 } - show_char(in_data[i + off]); + show_char(in_data[i + off]); // 输出字符 - if (off != rlen - 1 && (i + off + 1) % 16) SAYF(" "); else SAYF(cRST " "); + if (off != rlen - 1 && (i + off + 1) % 16) SAYF(" "); else SAYF(cRST " "); // 控制输出格式 } #else if (use_hex_offsets) - SAYF(" Offset %x, length %u: ", i, rlen); + SAYF(" 偏移 %x, 长度 %u: ", i, rlen); // 输出偏移和长度 else - SAYF(" Offset %u, length %u: ", i, rlen); + SAYF(" 偏移 %u, 长度 %u: ", i, rlen); // 输出偏移和长度 - switch (rtype) { + switch (rtype) { // 根据类型输出对应信息 - case RESP_NONE: SAYF("no-op block\n"); break; - case RESP_MINOR: SAYF("superficial content\n"); break; - case RESP_VARIABLE: SAYF("critical stream\n"); break; - case RESP_FIXED: SAYF("\"magic value\" section\n"); break; - case RESP_LEN: SAYF("suspected length field\n"); break; - case RESP_CKSUM: SAYF("suspected cksum or magic int\n"); break; - case RESP_SUSPECT: SAYF("suspected checksummed block\n"); break; + case RESP_NONE: SAYF("无操作块\n"); break; // 无操作 + case RESP_MINOR: SAYF("表面内容\n"); break; // 小改动 + case RESP_VARIABLE: SAYF("关键流\n"); break; // 关键流 + case RESP_FIXED: SAYF("\"魔法值\"部分\n"); break; // 固定部分 + case RESP_LEN: SAYF("可疑长度字段\n"); break; // 可疑长度字段 + case RESP_CKSUM: SAYF("可疑的校验和\n"); break; // 可疑校验和 + case RESP_SUSPECT: SAYF("可疑的校验块\n"); break; // 可疑校验块 } #endif /* ^USE_COLOR */ - i += rlen - 1; + i += rlen - 1; // 更新索引 } #ifdef USE_COLOR - SAYF(cRST "\n"); + SAYF(cRST "\n"); // 重置颜色 #endif /* USE_COLOR */ } - -/* Actually analyze! */ +/* 实际分析! */ static void analyze(char** argv) { u32 i; - u32 boring_len = 0, prev_xff = 0, prev_x01 = 0, prev_s10 = 0, prev_a10 = 0; + u32 boring_len = 0, prev_xff = 0, prev_x01 = 0, prev_s10 = 0, prev_a10 = 0; // 初始化之前的统计 - u8* b_data = ck_alloc(in_len + 1); - u8 seq_byte = 0; + u8* b_data = ck_alloc(in_len + 1); // 分配存储分析结果的内存 + u8 seq_byte = 0; // 初始化序列字节 - b_data[in_len] = 0xff; /* Intentional terminator. */ + b_data[in_len] = 0xff; /* 设置结尾标识符. */ - ACTF("Analyzing input file (this may take a while)...\n"); + ACTF("分析输入文件(这可能需要一段时间)...\n"); // 开始分析 #ifdef USE_COLOR - show_legend(); + show_legend(); // 显示图例 #endif /* USE_COLOR */ - for (i = 0; i < in_len; i++) { + for (i = 0; i < in_len; i++) { // 遍历输入数据的每一个字节 - u32 xor_ff, xor_01, sub_10, add_10; - u8 xff_orig, x01_orig, s10_orig, a10_orig; + u32 xor_ff, xor_01, sub_10, add_10; // 初始化不同操作的校验和 + u8 xff_orig, x01_orig, s10_orig, a10_orig; // 原始校验和 - /* Perform walking byte adjustments across the file. We perform four - operations designed to elicit some response from the underlying - code. */ + /* 对文件执行字节调整以获得响应。 */ - in_data[i] ^= 0xff; - xor_ff = run_target(argv, in_data, in_len, 0); + in_data[i] ^= 0xff; // 执行 XFF 操作(按位取反) + xor_ff = run_target(argv, in_data, in_len, 0); // 运行目标程序并获取校验和 - in_data[i] ^= 0xfe; - xor_01 = run_target(argv, in_data, in_len, 0); + in_data[i] ^= 0xfe; // 执行 X01 操作 + xor_01 = run_target(argv, in_data, in_len, 0); // 运行目标程序并获取校验和 - in_data[i] = (in_data[i] ^ 0x01) - 0x10; - sub_10 = run_target(argv, in_data, in_len, 0); + in_data[i] = (in_data[i] ^ 0x01) - 0x10; // 执行减操作 + sub_10 = run_target(argv, in_data, in_len, 0); // 运行目标程序并获取校验和 - in_data[i] += 0x20; - add_10 = run_target(argv, in_data, in_len, 0); - in_data[i] -= 0x10; + in_data[i] += 0x20; // 执行加操作 + add_10 = run_target(argv, in_data, in_len, 0); // 运行目标程序并获取校验和 + in_data[i] -= 0x10; // 恢复原始数据 - /* Classify current behavior. */ + /* 当前行为的分类。 */ - xff_orig = (xor_ff == orig_cksum); + xff_orig = (xor_ff == orig_cksum); // 检查是否与原始校验和相同 x01_orig = (xor_01 == orig_cksum); s10_orig = (sub_10 == orig_cksum); a10_orig = (add_10 == orig_cksum); - if (xff_orig && x01_orig && s10_orig && a10_orig) { + if (xff_orig && x01_orig && s10_orig && a10_orig) { // 所有操作都没有影响 + b_data[i] = RESP_NONE; // 标记为无操作 + boring_len++; // 无操作计数加一 - b_data[i] = RESP_NONE; - boring_len++; + } else if (xff_orig || x01_orig || s10_orig || a10_orig) { // 某些操作有影响 + b_data[i] = RESP_MINOR; // 标记为小改动 + boring_len++; // 无操作计数加一 - } else if (xff_orig || x01_orig || s10_orig || a10_orig) { - - b_data[i] = RESP_MINOR; - boring_len++; - - } else if (xor_ff == xor_01 && xor_ff == sub_10 && xor_ff == add_10) { - - b_data[i] = RESP_FIXED; - - } else b_data[i] = RESP_VARIABLE; + } else if (xor_ff == xor_01 && xor_ff == sub_10 && xor_ff == add_10) { // 所有结果相同 + b_data[i] = RESP_FIXED; // 标记为固定 + } else { + b_data[i] = RESP_VARIABLE; // 标记为变量 + } - /* When all checksums change, flip most significant bit of b_data. */ + /* 当所有校验和都改变时,翻转 b_data 的最高有效位。 */ if (prev_xff != xor_ff && prev_x01 != xor_01 && - prev_s10 != sub_10 && prev_a10 != add_10) seq_byte ^= 0x80; + prev_s10 != sub_10 && prev_a10 != add_10) { + seq_byte ^= 0x80; // 翻转序列字节的最高位 + } - b_data[i] |= seq_byte; + b_data[i] |= seq_byte; // 更新 b_data 对应字节 - prev_xff = xor_ff; + prev_xff = xor_ff; // 更新之前的校验和 prev_x01 = xor_01; prev_s10 = sub_10; - prev_a10 = add_10; - + prev_a10 = add_10; } - dump_hex(in_data, in_len, b_data); + dump_hex(in_data, in_len, b_data); // 输出十六进制字节信息 - SAYF("\n"); + SAYF("\n"); // 换行 - OKF("Analysis complete. Interesting bits: %0.02f%% of the input file.", - 100.0 - ((double)boring_len * 100) / in_len); + OKF("分析完成。 有趣的部分: %0.02f%% 的输入文件。", + 100.0 - ((double)boring_len * 100) / in_len); // 输出分析结果 - if (exec_hangs) - WARNF(cLRD "Encountered %u timeouts - results may be skewed." cRST, - exec_hangs); + if (exec_hangs) // 检查是否有挂起 + WARNF(cLRD "遇到 %u 个超时 - 结果可能会失真。" cRST, exec_hangs); - ck_free(b_data); + ck_free(b_data); // 释放 b_data 占用的内存 } - -/* Handle Ctrl-C and the like. */ +/* 处理 Ctrl-C 等信号。 */ static void handle_stop_sig(int sig) { - stop_soon = 1; + stop_soon = 1; // 设置停止标志 - if (child_pid > 0) kill(child_pid, SIGKILL); + if (child_pid > 0) kill(child_pid, SIGKILL); // 杀死子进程 } -/* Do basic preparations - persistent fds, filenames, etc. */ +/* 进行基本准备 - 持久文件描述符、文件名等。 */ static void set_up_environment(void) { u8* x; - dev_null_fd = open("/dev/null", O_RDWR); - if (dev_null_fd < 0) PFATAL("Unable to open /dev/null"); + dev_null_fd = open("/dev/null", O_RDWR); // 打开 /dev/null + if (dev_null_fd < 0) PFATAL("无法打开 /dev/null"); // 检查打开是否成功 - if (!prog_in) { + if (!prog_in) { // 如果程序输入未指定 - u8* use_dir = "."; + u8* use_dir = "."; // 默认使用当前目录 - if (access(use_dir, R_OK | W_OK | X_OK)) { + if (access(use_dir, R_OK | W_OK | X_OK)) { // 检查权限 - use_dir = getenv("TMPDIR"); - if (!use_dir) use_dir = "/tmp"; + use_dir = getenv("TMPDIR"); // 获取 TMPDIR 环境变量 + if (!use_dir) use_dir = "/tmp"; // 如果不存在,则使用/tmp } - prog_in = alloc_printf("%s/.afl-analyze-temp-%u", use_dir, getpid()); + prog_in = alloc_printf("%s/.afl-analyze-temp-%u", use_dir, getpid()); // 创建临时程序输入文件名 } - /* Set sane defaults... */ + /* 设置合理的默认值... */ - x = getenv("ASAN_OPTIONS"); + x = getenv("ASAN_OPTIONS"); // 获取 ASAN_OPTIONS 环境变量 - if (x) { + if (x) { // 如果环境变量存在 if (!strstr(x, "abort_on_error=1")) - FATAL("Custom ASAN_OPTIONS set without abort_on_error=1 - please fix!"); + FATAL("自定义 ASAN_OPTIONS 设置时未包含 abort_on_error=1 - 请修复!"); if (!strstr(x, "symbolize=0")) - FATAL("Custom ASAN_OPTIONS set without symbolize=0 - please fix!"); + FATAL("自定义 ASAN_OPTIONS 设置时未包含 symbolize=0 - 请修复!"); } - x = getenv("MSAN_OPTIONS"); + x = getenv("MSAN_OPTIONS"); // 获取 MSAN_OPTIONS 环境变量 - if (x) { + if (x) { // 如果环境变量存在 if (!strstr(x, "exit_code=" STRINGIFY(MSAN_ERROR))) - FATAL("Custom MSAN_OPTIONS set without exit_code=" - STRINGIFY(MSAN_ERROR) " - please fix!"); + FATAL("自定义 MSAN_OPTIONS 设置时未包含 exit_code=" + STRINGIFY(MSAN_ERROR) " - 请修复!"); if (!strstr(x, "symbolize=0")) - FATAL("Custom MSAN_OPTIONS set without symbolize=0 - please fix!"); + FATAL("自定义 MSAN_OPTIONS 设置时未包含 symbolize=0 - 请修复!"); } + // 设置默认的环境变量 setenv("ASAN_OPTIONS", "abort_on_error=1:" "detect_leaks=0:" "symbolize=0:" @@ -724,372 +711,348 @@ static void set_up_environment(void) { "allocator_may_return_null=1:" "msan_track_origins=0", 0); - if (getenv("AFL_PRELOAD")) { - setenv("LD_PRELOAD", getenv("AFL_PRELOAD"), 1); - setenv("DYLD_INSERT_LIBRARIES", getenv("AFL_PRELOAD"), 1); + if (getenv("AFL_PRELOAD")) { // 检查 AFL_PRELOAD 环境变量 + setenv("LD_PRELOAD", getenv("AFL_PRELOAD"), 1); // 设置预加载库 + setenv("DYLD_INSERT_LIBRARIES", getenv("AFL_PRELOAD"), 1); // 设置动态链接库 } } -/* Setup signal handlers, duh. */ +/* 设置信号处理程序。 */ static void setup_signal_handlers(void) { - struct sigaction sa; + struct sigaction sa; // 定义信号行为结构 - sa.sa_handler = NULL; - sa.sa_flags = SA_RESTART; - sa.sa_sigaction = NULL; + sa.sa_handler = NULL; // 初始化处理程序 + sa.sa_flags = SA_RESTART; // 设置重启标志 + sa.sa_sigaction = NULL; // 初始化信号动作 - sigemptyset(&sa.sa_mask); + 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); + sa.sa_handler = handle_stop_sig; // 设置处理Ctrl-C等信号的行为 + sigaction(SIGHUP, &sa, NULL); // 捕捉挂起信号 + sigaction(SIGINT, &sa, NULL); // 捕捉中断信号 + sigaction(SIGTERM, &sa, NULL); // 捕捉终止信号 - /* Exec timeout notifications. */ + /* 执行超时通知。 */ - sa.sa_handler = handle_timeout; - sigaction(SIGALRM, &sa, NULL); + 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; // 初始化循环索引 + u8* cwd = getcwd(NULL, 0); // 获取当前工作目录 - if (!cwd) PFATAL("getcwd() failed"); + if (!cwd) PFATAL("getcwd() failed"); // 检查获取工作目录是否成功 - while (argv[i]) { + while (argv[i]) { // 遍历参数 - u8* aa_loc = strstr(argv[i], "@@"); + u8* aa_loc = strstr(argv[i], "@@"); // 查找 @@ 字符串 - if (aa_loc) { + if (aa_loc) { // 如果找到 @@ u8 *aa_subst, *n_arg; - /* Be sure that we're always using fully-qualified paths. */ + /* 确保我们总是使用完全合格的路径。 */ - if (prog_in[0] == '/') aa_subst = prog_in; - else aa_subst = alloc_printf("%s/%s", cwd, prog_in); + if (prog_in[0] == '/') aa_subst = prog_in; // 直接使用绝对路径 + else aa_subst = alloc_printf("%s/%s", cwd, prog_in); // 拼接当前工作目录和输入文件 - /* Construct a replacement argv value. */ + /* 构造替换的参数值。 */ - *aa_loc = 0; - n_arg = alloc_printf("%s%s%s", argv[i], aa_subst, aa_loc + 2); - argv[i] = n_arg; - *aa_loc = '@'; + *aa_loc = 0; // 将 @@ 替换为字符串结束符 + n_arg = alloc_printf("%s%s%s", argv[i], aa_subst, aa_loc + 2); // 创建新的参数值 + argv[i] = n_arg; // 更新参数 + *aa_loc = '@'; // 还原 @@ - if (prog_in[0] != '/') ck_free(aa_subst); + if (prog_in[0] != '/') ck_free(aa_subst); // 释放临时分配的路径内存 } - i++; + i++; // 移动到下一个参数 } - free(cwd); /* not tracked */ + free(cwd); // 释放获取的工作目录内存 } -/* Display usage hints. */ +/* 显示用法提示。 */ static void usage(u8* argv0) { SAYF("\n%s [ options ] -- /path/to/target_app [ ... ]\n\n" - "Required parameters:\n\n" - - " -i file - input test case to be analyzed by the tool\n" + "所需参数:\n\n" - "Execution control settings:\n\n" + " -i file - 输入测试用例,由工具分析\n" - " -f file - input file read by the tested program (stdin)\n" - " -t msec - timeout for each run (%u ms)\n" - " -m megs - memory limit for child process (%u MB)\n" - " -Q - use binary-only instrumentation (QEMU mode)\n\n" + "执行控制设置:\n\n" - "Analysis settings:\n\n" + " -f file - 由被测程序读取的输入文件(stdin)\n" + " -t msec - 每次运行的超时时间(%u 毫秒)\n" + " -m megs - 子进程的内存限制(%u MB)\n" + " -Q - 使用仅二进制的仪器(QEMU 模式)\n\n" - " -e - look for edge coverage only, ignore hit counts\n\n" + "分析设置:\n\n" - "Other stuff:\n\n" + " -e - 仅查找边缘覆盖,忽略命中计数\n\n" - " -V - show version number and exit\n\n" + "其他信息:\n\n" - "For additional tips, please consult %s/README.\n\n", + " -V - 显示版本号并退出\n\n" - argv0, EXEC_TIMEOUT, MEM_LIMIT, doc_path); + "有关其他提示,请参阅 %s/README。\n\n", - exit(1); + argv0, EXEC_TIMEOUT, MEM_LIMIT, doc_path); // 输出程序使用说明和文档路径 + exit(1); // 退出程序 } -/* Find binary. */ +/* 查找二进制文件。 */ static void find_binary(u8* fname) { - u8* env_path = 0; - struct stat st; + u8* env_path = 0; // 环境路径 + struct stat st; // 文件状态结构 - if (strchr(fname, '/') || !(env_path = getenv("PATH"))) { + if (strchr(fname, '/') || !(env_path = getenv("PATH"))) { // 如果是绝对路径或没有 PATH 环境变量 - target_path = ck_strdup(fname); + target_path = ck_strdup(fname); // 直接使用文件名 - if (stat(target_path, &st) || !S_ISREG(st.st_mode) || + if (stat(target_path, &st) || !S_ISREG(st.st_mode) || // 检查文件是否存在且可执行 !(st.st_mode & 0111) || st.st_size < 4) - FATAL("Program '%s' not found or not executable", fname); + FATAL("程序 '%s' 未找到或不可执行", fname); // 输出错误信息 - } else { + } else { // 如果是相对路径 - while (env_path) { + while (env_path) { // 遍历环境变量 PATH - u8 *cur_elem, *delim = strchr(env_path, ':'); + u8 *cur_elem, *delim = strchr(env_path, ':'); // 查找下一个路径分隔符 - if (delim) { + if (delim) { // 如果找到分隔符 - cur_elem = ck_alloc(delim - env_path + 1); - memcpy(cur_elem, env_path, delim - env_path); - delim++; + cur_elem = ck_alloc(delim - env_path + 1); // 获取当前路径 + memcpy(cur_elem, env_path, delim - env_path); // 拷贝当前路径 + delim++; // 移动到下一个路径 - } else cur_elem = ck_strdup(env_path); + } else cur_elem = ck_strdup(env_path); // 如果没有找到,则直接拷贝 - env_path = delim; + env_path = delim; // 更新环境路径为下一个路径 - if (cur_elem[0]) - target_path = alloc_printf("%s/%s", cur_elem, fname); + if (cur_elem[0]) // 如果当前路径非空 + target_path = alloc_printf("%s/%s", cur_elem, fname); // 拼接路径和文件名 else - target_path = ck_strdup(fname); + target_path = ck_strdup(fname); // 如果当前路径为空,直接使用文件名 - ck_free(cur_elem); + ck_free(cur_elem); // 释放当前路径内存 - if (!stat(target_path, &st) && S_ISREG(st.st_mode) && - (st.st_mode & 0111) && st.st_size >= 4) break; + 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; + ck_free(target_path); // 释放目标路径内存 + target_path = 0; // 重置目标路径 } - if (!target_path) FATAL("Program '%s' not found or not executable", fname); + if (!target_path) FATAL("程序 '%s' 未找到或不可执行", fname); // 输出错误信息 } } -/* Fix up argv for QEMU. */ +/* 修复 argv 以供 QEMU 使用。 */ static char** get_qemu_argv(u8* own_loc, char** argv, int argc) { - char** new_argv = ck_alloc(sizeof(char*) * (argc + 4)); - u8 *tmp, *cp, *rsl, *own_copy; + char** new_argv = ck_alloc(sizeof(char*) * (argc + 4)); // 分配新的参数数组 + u8 *tmp, *cp, *rsl, *own_copy; // 定义临时变量 - /* Workaround for a QEMU stability glitch. */ + /* QEMU 稳定性解决方法。 */ - setenv("QEMU_LOG", "nochain", 1); + setenv("QEMU_LOG", "nochain", 1); // 设置环境变量 - memcpy(new_argv + 3, argv + 1, sizeof(char*) * argc); + memcpy(new_argv + 3, argv + 1, sizeof(char*) * argc); // 拷贝原始参数 - /* Now we need to actually find qemu for argv[0]. */ + /* 现在我们需要实际查找 qemu 作为argv[0]。 */ - new_argv[2] = target_path; - new_argv[1] = "--"; + new_argv[2] = target_path; // 设置目标路径为新参数 + new_argv[1] = "--"; // 添加参数分隔符 - tmp = getenv("AFL_PATH"); + tmp = getenv("AFL_PATH"); // 获取 AFL_PATH 环境变量 - if (tmp) { + if (tmp) { // 如果存在 AFL_PATH - cp = alloc_printf("%s/afl-qemu-trace", tmp); + cp = alloc_printf("%s/afl-qemu-trace", tmp); // 构造新的 qemu 路径 - if (access(cp, X_OK)) - FATAL("Unable to find '%s'", tmp); + if (access(cp, X_OK)) // 检查是否可执行 + FATAL("无法找到 '%s'", tmp); // 输出错误信息 - target_path = new_argv[0] = cp; - return new_argv; + target_path = new_argv[0] = cp; // 更新目标路径 + return new_argv; // 返回新的参数数组 } - own_copy = ck_strdup(own_loc); - rsl = strrchr(own_copy, '/'); + own_copy = ck_strdup(own_loc); // 复制当前路径 + rsl = strrchr(own_copy, '/'); // 查找最后一个路径分隔符 - if (rsl) { + if (rsl) { // 如果找到了路径分隔符 - *rsl = 0; + *rsl = 0; // 设置为字符串结束符 - cp = alloc_printf("%s/afl-qemu-trace", own_copy); - ck_free(own_copy); + cp = alloc_printf("%s/afl-qemu-trace", own_copy); // 构造新的 qemu 路径 + ck_free(own_copy); // 释放当前路径的内存 - if (!access(cp, X_OK)) { + if (!access(cp, X_OK)) { // 如果可执行 - target_path = new_argv[0] = cp; - return new_argv; + target_path = new_argv[0] = cp; // 更新目标路径 + return new_argv; // 返回新的参数数组 } - } else ck_free(own_copy); - - if (!access(BIN_PATH "/afl-qemu-trace", X_OK)) { - - target_path = new_argv[0] = BIN_PATH "/afl-qemu-trace"; - return new_argv; + } else ck_free(own_copy); // 如果没有找到路径分隔符,释放内存 + if (!access(BIN_PATH "/afl-qemu-trace", X_OK)) { // 检查预定义的 qemu 路径 + target_path = new_argv[0] = BIN_PATH "/afl-qemu-trace"; // 更新目标路径 + return new_argv; // 返回新的参数数组 } - FATAL("Unable to find 'afl-qemu-trace'."); + FATAL("无法找到 '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; - char** use_argv; + s32 opt; // 定义选项变量 + u8 mem_limit_given = 0, timeout_given = 0, qemu_mode = 0; // 初始化标志 + char** use_argv; // 用来存储使用的参数 - doc_path = access(DOC_PATH, F_OK) ? "docs" : DOC_PATH; + doc_path = access(DOC_PATH, F_OK) ? "docs" : DOC_PATH; // 设置文档路径 - SAYF(cCYA "afl-analyze " cBRI VERSION cRST " by \n"); + SAYF(cCYA "afl-analyze " cBRI VERSION cRST " by \n"); // 输出版本信息 - while ((opt = getopt(argc,argv,"+i:f:m:t:eQV")) > 0) + // 解析命令行参数 + while ((opt = getopt(argc, argv, "+i:f:m:t:eQV")) > 0) switch (opt) { - case 'i': - - if (in_file) FATAL("Multiple -i options not supported"); - in_file = optarg; + case 'i': // 处理 -i 选项 + if (in_file) FATAL("不支持多个 -i 选项"); + in_file = optarg; // 设置输入文件 break; - case 'f': - - if (prog_in) FATAL("Multiple -f options not supported"); - use_stdin = 0; - prog_in = optarg; + case 'f': // 处理 -f 选项 + if (prog_in) FATAL("不支持多个 -f 选项"); + use_stdin = 0; // 使用文件而非标准输入 + prog_in = optarg; // 设置程序输入 break; - case 'e': - - if (edges_only) FATAL("Multiple -e options not supported"); - edges_only = 1; + case 'e': // 处理 -e 选项 + if (edges_only) FATAL("不支持多个 -e 选项"); + edges_only = 1; // 设置为仅查找边缘覆盖 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"); + case 'm': { // 处理 -m 选项 + u8 suffix = 'M'; // 后缀初始化为 MB - } - - 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"); + if (mem_limit_given) FATAL("不支持多个 -m 选项"); + mem_limit_given = 1; // 标记已给定内存限制 + if (!strcmp(optarg, "none")) { // 如果参数为 "none" + mem_limit = 0; // 设置内存限制为 0 + break; } - break; + if (sscanf(optarg, "%llu%c", &mem_limit, &suffix) < 1 || // 解析内存限制 + optarg[0] == '-') FATAL("不正确的 -m 语法"); - case 't': + switch (suffix) { // 根据后缀设置内存限制 + case 'T': mem_limit *= 1024 * 1024; break; // 转换为字节 + case 'G': mem_limit *= 1024; break; // 转换为 MB + case 'k': mem_limit /= 1024; break; // 转换为 KB + case 'M': break; // MB,不需转换 + default: FATAL("不支持的后缀或不正确的 -m 语法"); + } - if (timeout_given) FATAL("Multiple -t options not supported"); - timeout_given = 1; + if (mem_limit < 5) FATAL("危险的低的 -m 值"); // 检查内存限制是否安全 - exec_tmout = atoi(optarg); + if (sizeof(rlim_t) == 4 && mem_limit > 2000) // 检查32位系统的限制 + FATAL("在32位系统上,-m 值超出范围"); + } + break; - if (exec_tmout < 10 || optarg[0] == '-') - FATAL("Dangerously low value of -t"); + case 't': // 处理 -t 选项 + if (timeout_given) FATAL("不支持多个 -t 选项"); + timeout_given = 1; // 标记超时已给定 + exec_tmout = atoi(optarg); // 设置执行超时时间 + if (exec_tmout < 10 || optarg[0] == '-') // 检查时间值 + FATAL("危险的低的 -t 值"); break; - case 'Q': - - if (qemu_mode) FATAL("Multiple -Q options not supported"); - if (!mem_limit_given) mem_limit = MEM_LIMIT_QEMU; - - qemu_mode = 1; + case 'Q': // 处理 -Q 选项 + if (qemu_mode) FATAL("不支持多个 -Q 选项"); + if (!mem_limit_given) mem_limit = MEM_LIMIT_QEMU; // 如果未给定内存限制,使用 QEMU 的默认值 + qemu_mode = 1; // 设置为 QEMU 模式 break; - case 'V': /* Show version number */ - - /* Version number has been printed already, just quit. */ - exit(0); + case 'V': // 显示版本号选项 + exit(0); // 直接退出 default: - - usage(argv[0]); - + usage(argv[0]); // 显示用法信息 } - if (optind == argc || !in_file) usage(argv[0]); + if (optind == argc || !in_file) usage(argv[0]); // 检查必要参数 - use_hex_offsets = !!getenv("AFL_ANALYZE_HEX"); + use_hex_offsets = !!getenv("AFL_ANALYZE_HEX"); // 检查环境变量是否设置 - setup_shm(); - setup_signal_handlers(); + setup_shm(); // 设置共享内存 + setup_signal_handlers(); // 设置信号处理程序 - set_up_environment(); + set_up_environment(); // 设置环境变量 - find_binary(argv[optind]); - detect_file_args(argv + optind); + find_binary(argv[optind]); // 查找目标二进制文件 + detect_file_args(argv + optind); // 检测命令行参数中的文件输入 - if (qemu_mode) - use_argv = get_qemu_argv(argv[0], argv + optind, argc - optind); + if (qemu_mode) // 如果是 QEMU 模式 + use_argv = get_qemu_argv(argv[0], argv + optind, argc - optind); // 获取 QEMU 兼容的参数数组 else - use_argv = argv + optind; + use_argv = argv + optind; // 直接使用传入的参数 SAYF("\n"); - read_initial_file(); - - ACTF("Performing dry run (mem limit = %llu MB, timeout = %u ms%s)...", - mem_limit, exec_tmout, edges_only ? ", edges only" : ""); + read_initial_file(); // 读取初始文件 - run_target(use_argv, in_data, in_len, 1); + ACTF("进行干运行(内存限制 = %llu MB,超时 = %u ms%s)...", + mem_limit, exec_tmout, edges_only ? ", 仅查找边缘" : ""); // 输出干运行信息 - if (child_timed_out) - FATAL("Target binary times out (adjusting -t may help)."); + run_target(use_argv, in_data, in_len, 1); // 运行目标程序进行第一次测试 - if (!anything_set()) FATAL("No instrumentation detected."); + if (child_timed_out) // 检查子进程是否超时 + FATAL("目标二进制程序超时(调整 -t 可能会有所帮助)。"); - analyze(use_argv); + if (!anything_set()) FATAL("未检测到任何仪器。"); // 输出未检测到仪器的错误信息 - OKF("We're done here. Have a nice day!\n"); + analyze(use_argv); // 开始分析输入文件 - exit(0); + OKF("我们结束了。祝你有美好的一天!\n"); // 输出结束信息 + exit(0); // 正常退出程序 } - diff --git a/src/afl-as.c b/src/afl-as.c index ea018ee..567054d 100644 --- a/src/afl-as.c +++ b/src/afl-as.c @@ -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 - + 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 -#include -#include -#include -#include -#include -#include - -#include -#include - -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 // 标准输入输出 +#include // POSIX标准库 +#include // 标准库 +#include // 字符串处理 +#include // 时间处理 +#include // 字符处理 +#include // 文件控制 + +#include // 进程等待 +#include // 时间处理 + +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: */ - - if ((colon_pos = strstr(line, ":"))) { - - if (line[0] == 'L' && isdigit(*(colon_pos - 1))) { - -#else - - /* Everybody else: .L: */ - - if (strstr(line, ":")) { - - if (line[0] == '.') { - + + // 某类标签。这可能是分支目标,但我们需要小心处理不同的格式约定。 + +#ifdef __APPLE__ // 如果是苹果系统 + + // MacOS X: L: + + if ((colon_pos = strstr(line, ":"))) { // 如果行包含冒号 + + if (line[0] == 'L' && isdigit(*(colon_pos - 1))) { // 如果标签以L开头且冒号前是数字 + +#else // 否则 + + // 其他人:.L: + + if (strstr(line, ":")) { // 如果行包含冒号 + + if (line[0] == '.') { // 如果标签以点开头 + #endif /* __APPLE__ */ - - /* .L0: or LBB0_0: style jump destination */ - -#ifdef __APPLE__ - - /* Apple: L / LBB */ - - if ((isdigit(line[1]) || (clang_mode && !strncmp(line, "LBB", 3))) - && R(100) < inst_ratio) { - -#else - - /* Apple: .L / .LBB */ - - if ((isdigit(line[2]) || (clang_mode && !strncmp(line + 1, "LBB", 3))) - && R(100) < inst_ratio) { - + + // .L0: 或 LBB0_0: 风格的分支目标 + +#ifdef __APPLE__ // 如果是苹果系统 + + // MacOS X: L / LBB + + if ((isdigit(line[1]) || (clang_mode && !strncmp(line, "LBB", 3))) // 如果标签是L或LBB + && R(100) < inst_ratio) { // 并且随机数小于插桩概率 + +#else // 否则 + + // MacOS X: .L / .LBB + + if ((isdigit(line[2]) || (clang_mode && !strncmp(line + 1, "LBB", 3))) // 如果标签是.L或.LBB + && 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 \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 \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)); // 退出 + +} \ No newline at end of file diff --git a/src/afl-as.h b/src/afl-as.h index 276b8a4..3275a70 100644 --- a/src/afl-as.h +++ b/src/afl-as.h @@ -39,11 +39,11 @@ */ -#ifndef _HAVE_AFL_AS_H -#define _HAVE_AFL_AS_H +#ifndef _HAVE_AFL_AS_H // 如果没有定义 _HAVE_AFL_AS_H +#define _HAVE_AFL_AS_H // 定义 _HAVE_AFL_AS_H -#include "config.h" -#include "types.h" +#include "config.h" // 包含配置文件 +#include "types.h" // 包含类型定义 /* ------------------ @@ -112,264 +112,264 @@ */ -static const u8* trampoline_fmt_32 = +static const u8* trampoline_fmt_32 = // 32位跳板代码模板 "\n" - "/* --- AFL TRAMPOLINE (32-BIT) --- */\n" + "/* --- AFL TRAMPOLINE (32-BIT) --- */\n" // 32位跳板代码开始 "\n" - ".align 4\n" + ".align 4\n" // 对齐到4字节 "\n" - "leal -16(%%esp), %%esp\n" - "movl %%edi, 0(%%esp)\n" - "movl %%edx, 4(%%esp)\n" - "movl %%ecx, 8(%%esp)\n" - "movl %%eax, 12(%%esp)\n" - "movl $0x%08x, %%ecx\n" - "call __afl_maybe_log\n" - "movl 12(%%esp), %%eax\n" - "movl 8(%%esp), %%ecx\n" - "movl 4(%%esp), %%edx\n" - "movl 0(%%esp), %%edi\n" - "leal 16(%%esp), %%esp\n" + "leal -16(%%esp), %%esp\n" // 调整栈指针,预留16字节空间 + "movl %%edi, 0(%%esp)\n" // 保存edi寄存器到栈 + "movl %%edx, 4(%%esp)\n" // 保存edx寄存器到栈 + "movl %%ecx, 8(%%esp)\n" // 保存ecx寄存器到栈 + "movl %%eax, 12(%%esp)\n" // 保存eax寄存器到栈 + "movl $0x%08x, %%ecx\n" // 将当前分支位置ID存入ecx + "call __afl_maybe_log\n" // 调用日志记录函数 + "movl 12(%%esp), %%eax\n" // 恢复eax寄存器 + "movl 8(%%esp), %%ecx\n" // 恢复ecx寄存器 + "movl 4(%%esp), %%edx\n" // 恢复edx寄存器 + "movl 0(%%esp), %%edi\n" // 恢复edi寄存器 + "leal 16(%%esp), %%esp\n" // 恢复栈指针 "\n" - "/* --- END --- */\n" + "/* --- END --- */\n" // 32位跳板代码结束 "\n"; -static const u8* trampoline_fmt_64 = +static const u8* trampoline_fmt_64 = // 64位跳板代码模板 "\n" - "/* --- AFL TRAMPOLINE (64-BIT) --- */\n" + "/* --- AFL TRAMPOLINE (64-BIT) --- */\n" // 64位跳板代码开始 "\n" - ".align 4\n" + ".align 4\n" // 对齐到4字节 "\n" - "leaq -(128+24)(%%rsp), %%rsp\n" - "movq %%rdx, 0(%%rsp)\n" - "movq %%rcx, 8(%%rsp)\n" - "movq %%rax, 16(%%rsp)\n" - "movq $0x%08x, %%rcx\n" - "call __afl_maybe_log\n" - "movq 16(%%rsp), %%rax\n" - "movq 8(%%rsp), %%rcx\n" - "movq 0(%%rsp), %%rdx\n" - "leaq (128+24)(%%rsp), %%rsp\n" + "leaq -(128+24)(%%rsp), %%rsp\n" // 调整栈指针,预留152字节空间 + "movq %%rdx, 0(%%rsp)\n" // 保存rdx寄存器到栈 + "movq %%rcx, 8(%%rsp)\n" // 保存rcx寄存器到栈 + "movq %%rax, 16(%%rsp)\n" // 保存rax寄存器到栈 + "movq $0x%08x, %%rcx\n" // 将当前分支位置ID存入rcx + "call __afl_maybe_log\n" // 调用日志记录函数 + "movq 16(%%rsp), %%rax\n" // 恢复rax寄存器 + "movq 8(%%rsp), %%rcx\n" // 恢复rcx寄存器 + "movq 0(%%rsp), %%rdx\n" // 恢复rdx寄存器 + "leaq (128+24)(%%rsp), %%rsp\n" // 恢复栈指针 "\n" - "/* --- END --- */\n" + "/* --- END --- */\n" // 64位跳板代码结束 "\n"; -static const u8* main_payload_32 = +static const u8* main_payload_32 = // 32位主插桩代码 "\n" - "/* --- AFL MAIN PAYLOAD (32-BIT) --- */\n" + "/* --- AFL MAIN PAYLOAD (32-BIT) --- */\n" // 32位主插桩代码开始 "\n" - ".text\n" - ".att_syntax\n" - ".code32\n" - ".align 8\n" + ".text\n" // 代码段 + ".att_syntax\n" // AT&T语法 + ".code32\n" // 32位代码 + ".align 8\n" // 对齐到8字节 "\n" - "__afl_maybe_log:\n" + "__afl_maybe_log:\n" // 日志记录函数 "\n" - " lahf\n" - " seto %al\n" + " lahf\n" // 将标志寄存器加载到AH + " seto %al\n" // 设置溢出标志到AL "\n" - " /* Check if SHM region is already mapped. */\n" + " /* Check if SHM region is already mapped. */\n" // 检查SHM区域是否已映射 "\n" - " movl __afl_area_ptr, %edx\n" - " testl %edx, %edx\n" - " je __afl_setup\n" + " movl __afl_area_ptr, %edx\n" // 加载SHM区域指针到EDX + " testl %edx, %edx\n" // 测试EDX是否为0 + " je __afl_setup\n" // 如果为0,跳转到设置代码 "\n" - "__afl_store:\n" + "__afl_store:\n" // 存储命中信息 "\n" " /* Calculate and store hit for the code location specified in ecx. There\n" " is a double-XOR way of doing this without tainting another register,\n" " and we use it on 64-bit systems; but it's slower for 32-bit ones. */\n" "\n" -#ifndef COVERAGE_ONLY - " movl __afl_prev_loc, %edi\n" - " xorl %ecx, %edi\n" - " shrl $1, %ecx\n" - " movl %ecx, __afl_prev_loc\n" -#else - " movl %ecx, %edi\n" +#ifndef COVERAGE_ONLY // 如果不是仅覆盖率模式 + " movl __afl_prev_loc, %edi\n" // 加载前一个位置到EDI + " xorl %ecx, %edi\n" // 计算当前和前一个位置的XOR + " shrl $1, %ecx\n" // 右移ECX,减少冲突 + " movl %ecx, __afl_prev_loc\n" // 存储当前位置 +#else // 否则 + " movl %ecx, %edi\n" // 直接使用ECX #endif /* ^!COVERAGE_ONLY */ "\n" -#ifdef SKIP_COUNTS - " orb $1, (%edx, %edi, 1)\n" -#else - " incb (%edx, %edi, 1)\n" +#ifdef SKIP_COUNTS // 如果跳过计数 + " orb $1, (%edx, %edi, 1)\n" // 设置位 +#else // 否则 + " incb (%edx, %edi, 1)\n" // 增加计数 #endif /* ^SKIP_COUNTS */ "\n" - "__afl_return:\n" + "__afl_return:\n" // 返回 "\n" - " addb $127, %al\n" - " sahf\n" - " ret\n" + " addb $127, %al\n" // 恢复溢出标志 + " sahf\n" // 将AH存储回标志寄存器 + " ret\n" // 返回 "\n" - ".align 8\n" + ".align 8\n" // 对齐到8字节 "\n" - "__afl_setup:\n" + "__afl_setup:\n" // 设置代码 "\n" - " /* Do not retry setup if we had previous failures. */\n" + " /* Do not retry setup if we had previous failures. */\n" // 如果之前失败,不再重试 "\n" - " cmpb $0, __afl_setup_failure\n" - " jne __afl_return\n" + " cmpb $0, __afl_setup_failure\n" // 检查设置失败标志 + " jne __afl_return\n" // 如果失败,跳转到返回 "\n" " /* Map SHM, jumping to __afl_setup_abort if something goes wrong.\n" " We do not save FPU/MMX/SSE registers here, but hopefully, nobody\n" - " will notice this early in the game. */\n" + " will notice this early in the game. */\n" // 映射SHM区域 "\n" - " pushl %eax\n" - " pushl %ecx\n" + " pushl %eax\n" // 保存EAX + " pushl %ecx\n" // 保存ECX "\n" - " pushl $.AFL_SHM_ENV\n" - " call getenv\n" - " addl $4, %esp\n" + " pushl $.AFL_SHM_ENV\n" // 压入SHM环境变量名 + " call getenv\n" // 调用getenv + " addl $4, %esp\n" // 恢复栈 "\n" - " testl %eax, %eax\n" - " je __afl_setup_abort\n" + " testl %eax, %eax\n" // 检查返回值 + " je __afl_setup_abort\n" // 如果为0,跳转到设置失败 "\n" - " pushl %eax\n" - " call atoi\n" - " addl $4, %esp\n" + " pushl %eax\n" // 保存返回值 + " call atoi\n" // 调用atoi + " addl $4, %esp\n" // 恢复栈 "\n" - " pushl $0 /* shmat flags */\n" - " pushl $0 /* requested addr */\n" - " pushl %eax /* SHM ID */\n" - " call shmat\n" - " addl $12, %esp\n" + " pushl $0 /* shmat flags */\n" // 压入shmat标志 + " pushl $0 /* requested addr */\n" // 压入请求地址 + " pushl %eax /* SHM ID */\n" // 压入SHM ID + " call shmat\n" // 调用shmat + " addl $12, %esp\n" // 恢复栈 "\n" - " cmpl $-1, %eax\n" - " je __afl_setup_abort\n" + " cmpl $-1, %eax\n" // 检查返回值 + " je __afl_setup_abort\n" // 如果为-1,跳转到设置失败 "\n" - " /* Store the address of the SHM region. */\n" + " /* Store the address of the SHM region. */\n" // 存储SHM区域地址 "\n" - " movl %eax, __afl_area_ptr\n" - " movl %eax, %edx\n" + " movl %eax, __afl_area_ptr\n" // 存储到全局变量 + " movl %eax, %edx\n" // 存储到EDX "\n" - " popl %ecx\n" - " popl %eax\n" + " popl %ecx\n" // 恢复ECX + " popl %eax\n" // 恢复EAX "\n" - "__afl_forkserver:\n" + "__afl_forkserver:\n" // 进入fork服务器模式 "\n" - " /* Enter the fork server mode to avoid the overhead of execve() calls. */\n" + " /* Enter the fork server mode to avoid the overhead of execve() calls. */\n" // 进入fork服务器模式 "\n" - " pushl %eax\n" - " pushl %ecx\n" - " pushl %edx\n" + " pushl %eax\n" // 保存EAX + " pushl %ecx\n" // 保存ECX + " pushl %edx\n" // 保存EDX "\n" " /* Phone home and tell the parent that we're OK. (Note that signals with\n" " no SA_RESTART will mess it up). If this fails, assume that the fd is\n" " closed because we were execve()d from an instrumented binary, or because\n" - " the parent doesn't want to use the fork server. */\n" + " the parent doesn't want to use the fork server. */\n" // 通知父进程 "\n" - " pushl $4 /* length */\n" - " pushl $__afl_temp /* data */\n" - " pushl $" STRINGIFY((FORKSRV_FD + 1)) " /* file desc */\n" - " call write\n" - " addl $12, %esp\n" + " pushl $4 /* length */\n" // 压入长度 + " pushl $__afl_temp /* data */\n" // 压入数据 + " pushl $" STRINGIFY((FORKSRV_FD + 1)) " /* file desc */\n" // 压入文件描述符 + " call write\n" // 调用write + " addl $12, %esp\n" // 恢复栈 "\n" - " cmpl $4, %eax\n" - " jne __afl_fork_resume\n" + " cmpl $4, %eax\n" // 检查返回值 + " jne __afl_fork_resume\n" // 如果不等于4,跳转到恢复 "\n" - "__afl_fork_wait_loop:\n" + "__afl_fork_wait_loop:\n" // fork等待循环 "\n" - " /* Wait for parent by reading from the pipe. Abort if read fails. */\n" + " /* Wait for parent by reading from the pipe. Abort if read fails. */\n" // 等待父进程 "\n" - " pushl $4 /* length */\n" - " pushl $__afl_temp /* data */\n" - " pushl $" STRINGIFY(FORKSRV_FD) " /* file desc */\n" - " call read\n" - " addl $12, %esp\n" + " pushl $4 /* length */\n" // 压入长度 + " pushl $__afl_temp /* data */\n" // 压入数据 + " pushl $" STRINGIFY(FORKSRV_FD) " /* file desc */\n" // 压入文件描述符 + " call read\n" // 调用read + " addl $12, %esp\n" // 恢复栈 "\n" - " cmpl $4, %eax\n" - " jne __afl_die\n" + " cmpl $4, %eax\n" // 检查返回值 + " jne __afl_die\n" // 如果不等于4,跳转到死亡 "\n" " /* Once woken up, create a clone of our process. This is an excellent use\n" " case for syscall(__NR_clone, 0, CLONE_PARENT), but glibc boneheadedly\n" " caches getpid() results and offers no way to update the value, breaking\n" - " abort(), raise(), and a bunch of other things :-( */\n" + " abort(), raise(), and a bunch of other things :-( */\n" // 创建子进程 "\n" - " call fork\n" + " call fork\n" // 调用fork "\n" - " cmpl $0, %eax\n" - " jl __afl_die\n" - " je __afl_fork_resume\n" + " cmpl $0, %eax\n" // 检查返回值 + " jl __afl_die\n" // 如果小于0,跳转到死亡 + " je __afl_fork_resume\n" // 如果等于0,跳转到恢复 "\n" - " /* In parent process: write PID to pipe, then wait for child. */\n" + " /* In parent process: write PID to pipe, then wait for child. */\n" // 在父进程中 "\n" - " movl %eax, __afl_fork_pid\n" + " movl %eax, __afl_fork_pid\n" // 存储子进程PID "\n" - " pushl $4 /* length */\n" - " pushl $__afl_fork_pid /* data */\n" - " pushl $" STRINGIFY((FORKSRV_FD + 1)) " /* file desc */\n" - " call write\n" - " addl $12, %esp\n" + " pushl $4 /* length */\n" // 压入长度 + " pushl $__afl_fork_pid /* data */\n" // 压入数据 + " pushl $" STRINGIFY((FORKSRV_FD + 1)) " /* file desc */\n" // 压入文件描述符 + " call write\n" // 调用write + " addl $12, %esp\n" // 恢复栈 "\n" - " pushl $0 /* no flags */\n" - " pushl $__afl_temp /* status */\n" - " pushl __afl_fork_pid /* PID */\n" - " call waitpid\n" - " addl $12, %esp\n" + " pushl $0 /* no flags */\n" // 压入标志 + " pushl $__afl_temp /* status */\n" // 压入状态 + " pushl __afl_fork_pid /* PID */\n" // 压入PID + " call waitpid\n" // 调用waitpid + " addl $12, %esp\n" // 恢复栈 "\n" - " cmpl $0, %eax\n" - " jle __afl_die\n" + " cmpl $0, %eax\n" // 检查返回值 + " jle __afl_die\n" // 如果小于等于0,跳转到死亡 "\n" - " /* Relay wait status to pipe, then loop back. */\n" + " /* Relay wait status to pipe, then loop back. */\n" // 传递等待状态 "\n" - " pushl $4 /* length */\n" - " pushl $__afl_temp /* data */\n" - " pushl $" STRINGIFY((FORKSRV_FD + 1)) " /* file desc */\n" - " call write\n" - " addl $12, %esp\n" + " pushl $4 /* length */\n" // 压入长度 + " pushl $__afl_temp /* data */\n" // 压入数据 + " pushl $" STRINGIFY((FORKSRV_FD + 1)) " /* file desc */\n" // 压入文件描述符 + " call write\n" // 调用write + " addl $12, %esp\n" // 恢复栈 "\n" - " jmp __afl_fork_wait_loop\n" + " jmp __afl_fork_wait_loop\n" // 跳转到等待循环 "\n" - "__afl_fork_resume:\n" + "__afl_fork_resume:\n" // 恢复 "\n" - " /* In child process: close fds, resume execution. */\n" + " /* In child process: close fds, resume execution. */\n" // 在子进程中 "\n" - " pushl $" STRINGIFY(FORKSRV_FD) "\n" - " call close\n" + " pushl $" STRINGIFY(FORKSRV_FD) "\n" // 压入文件描述符 + " call close\n" // 调用close "\n" - " pushl $" STRINGIFY((FORKSRV_FD + 1)) "\n" - " call close\n" + " pushl $" STRINGIFY((FORKSRV_FD + 1)) "\n" // 压入文件描述符 + " call close\n" // 调用close "\n" - " addl $8, %esp\n" + " addl $8, %esp\n" // 恢复栈 "\n" - " popl %edx\n" - " popl %ecx\n" - " popl %eax\n" - " jmp __afl_store\n" + " popl %edx\n" // 恢复EDX + " popl %ecx\n" // 恢复ECX + " popl %eax\n" // 恢复EAX + " jmp __afl_store\n" // 跳转到存储 "\n" - "__afl_die:\n" + "__afl_die:\n" // 死亡 "\n" - " xorl %eax, %eax\n" - " call _exit\n" + " xorl %eax, %eax\n" // 清零EAX + " call _exit\n" // 调用_exit "\n" - "__afl_setup_abort:\n" + "__afl_setup_abort:\n" // 设置失败 "\n" " /* Record setup failure so that we don't keep calling\n" - " shmget() / shmat() over and over again. */\n" + " shmget() / shmat() over and over again. */\n" // 记录设置失败 "\n" - " incb __afl_setup_failure\n" - " popl %ecx\n" - " popl %eax\n" - " jmp __afl_return\n" + " incb __afl_setup_failure\n" // 增加设置失败标志 + " popl %ecx\n" // 恢复ECX + " popl %eax\n" // 恢复EAX + " jmp __afl_return\n" // 跳转到返回 "\n" - ".AFL_VARS:\n" + ".AFL_VARS:\n" // AFL变量 "\n" - " .comm __afl_area_ptr, 4, 32\n" - " .comm __afl_setup_failure, 1, 32\n" -#ifndef COVERAGE_ONLY - " .comm __afl_prev_loc, 4, 32\n" + " .comm __afl_area_ptr, 4, 32\n" // 定义全局变量 + " .comm __afl_setup_failure, 1, 32\n" // 定义全局变量 +#ifndef COVERAGE_ONLY // 如果不是仅覆盖率模式 + " .comm __afl_prev_loc, 4, 32\n" // 定义全局变量 #endif /* !COVERAGE_ONLY */ - " .comm __afl_fork_pid, 4, 32\n" - " .comm __afl_temp, 4, 32\n" + " .comm __afl_fork_pid, 4, 32\n" // 定义全局变量 + " .comm __afl_temp, 4, 32\n" // 定义全局变量 "\n" - ".AFL_SHM_ENV:\n" - " .asciz \"" SHM_ENV_VAR "\"\n" + ".AFL_SHM_ENV:\n" // SHM环境变量 + " .asciz \"" SHM_ENV_VAR "\"\n" // 定义字符串 "\n" - "/* --- END --- */\n" + "/* --- END --- */\n" // 32位主插桩代码结束 "\n"; /* The OpenBSD hack is due to lahf and sahf not being recognized by some @@ -380,348 +380,348 @@ static const u8* main_payload_32 = to work around the crash issue with .lcomm and the fact that they don't recognize .string. */ -#ifdef __APPLE__ -# define CALL_L64(str) "call _" str "\n" -#else -# define CALL_L64(str) "call " str "@PLT\n" +#ifdef __APPLE__ // 如果是苹果系统 +# define CALL_L64(str) "call _" str "\n" // 定义调用宏 +#else // 否则 +# define CALL_L64(str) "call " str "@PLT\n" // 定义调用宏 #endif /* ^__APPLE__ */ -static const u8* main_payload_64 = +static const u8* main_payload_64 = // 64位主插桩代码 "\n" - "/* --- AFL MAIN PAYLOAD (64-BIT) --- */\n" + "/* --- AFL MAIN PAYLOAD (64-BIT) --- */\n" // 64位主插桩代码开始 "\n" - ".text\n" - ".att_syntax\n" - ".code64\n" - ".align 8\n" + ".text\n" // 代码段 + ".att_syntax\n" // AT&T语法 + ".code64\n" // 64位代码 + ".align 8\n" // 对齐到8字节 "\n" - "__afl_maybe_log:\n" + "__afl_maybe_log:\n" // 日志记录函数 "\n" -#if defined(__OpenBSD__) || (defined(__FreeBSD__) && (__FreeBSD__ < 9)) - " .byte 0x9f /* lahf */\n" -#else - " lahf\n" +#if defined(__OpenBSD__) || (defined(__FreeBSD__) && (__FreeBSD__ < 9)) // 如果是OpenBSD或旧版FreeBSD + " .byte 0x9f /* lahf */\n" // 使用字节码 +#else // 否则 + " lahf\n" // 加载标志寄存器 #endif /* ^__OpenBSD__, etc */ - " seto %al\n" + " seto %al\n" // 设置溢出标志 "\n" - " /* Check if SHM region is already mapped. */\n" + " /* Check if SHM region is already mapped. */\n" // 检查SHM区域是否已映射 "\n" - " movq __afl_area_ptr(%rip), %rdx\n" - " testq %rdx, %rdx\n" - " je __afl_setup\n" + " movq __afl_area_ptr(%rip), %rdx\n" // 加载SHM区域指针 + " testq %rdx, %rdx\n" // 测试RDX是否为0 + " je __afl_setup\n" // 如果为0,跳转到设置 "\n" - "__afl_store:\n" + "__afl_store:\n" // 存储命中信息 "\n" - " /* Calculate and store hit for the code location specified in rcx. */\n" + " /* Calculate and store hit for the code location specified in rcx. */\n" // 计算并存储命中信息 "\n" -#ifndef COVERAGE_ONLY - " xorq __afl_prev_loc(%rip), %rcx\n" - " xorq %rcx, __afl_prev_loc(%rip)\n" - " shrq $1, __afl_prev_loc(%rip)\n" +#ifndef COVERAGE_ONLY // 如果不是仅覆盖率模式 + " xorq __afl_prev_loc(%rip), %rcx\n" // 计算XOR + " xorq %rcx, __afl_prev_loc(%rip)\n" // 更新前一个位置 + " shrq $1, __afl_prev_loc(%rip)\n" // 右移减少冲突 #endif /* ^!COVERAGE_ONLY */ "\n" -#ifdef SKIP_COUNTS - " orb $1, (%rdx, %rcx, 1)\n" -#else - " incb (%rdx, %rcx, 1)\n" +#ifdef SKIP_COUNTS // 如果跳过计数 + " orb $1, (%rdx, %rcx, 1)\n" // 设置位 +#else // 否则 + " incb (%rdx, %rcx, 1)\n" // 增加计数 #endif /* ^SKIP_COUNTS */ "\n" - "__afl_return:\n" + "__afl_return:\n" // 返回 "\n" - " addb $127, %al\n" -#if defined(__OpenBSD__) || (defined(__FreeBSD__) && (__FreeBSD__ < 9)) - " .byte 0x9e /* sahf */\n" -#else - " sahf\n" + " addb $127, %al\n" // 恢复溢出标志 +#if defined(__OpenBSD__) || (defined(__FreeBSD__) && (__FreeBSD__ < 9)) // 如果是OpenBSD或旧版FreeBSD + " .byte 0x9e /* sahf */\n" // 使用字节码 +#else // 否则 + " sahf\n" // 存储标志寄存器 #endif /* ^__OpenBSD__, etc */ - " ret\n" + " ret\n" // 返回 "\n" - ".align 8\n" + ".align 8\n" // 对齐到8字节 "\n" - "__afl_setup:\n" + "__afl_setup:\n" // 设置 "\n" - " /* Do not retry setup if we had previous failures. */\n" + " /* Do not retry setup if we had previous failures. */\n" // 如果之前失败,不再重试 "\n" - " cmpb $0, __afl_setup_failure(%rip)\n" - " jne __afl_return\n" + " cmpb $0, __afl_setup_failure(%rip)\n" // 检查设置失败标志 + " jne __afl_return\n" // 如果失败,跳转到返回 "\n" - " /* Check out if we have a global pointer on file. */\n" + " /* Check out if we have a global pointer on file. */\n" // 检查是否有全局指针 "\n" -#ifndef __APPLE__ - " movq __afl_global_area_ptr@GOTPCREL(%rip), %rdx\n" - " movq (%rdx), %rdx\n" -#else - " movq __afl_global_area_ptr(%rip), %rdx\n" +#ifndef __APPLE__ // 如果不是苹果系统 + " movq __afl_global_area_ptr@GOTPCREL(%rip), %rdx\n" // 加载全局指针 + " movq (%rdx), %rdx\n" // 加载指针值 +#else // 否则 + " movq __afl_global_area_ptr(%rip), %rdx\n" // 加载全局指针 #endif /* !^__APPLE__ */ - " testq %rdx, %rdx\n" - " je __afl_setup_first\n" + " testq %rdx, %rdx\n" // 测试RDX是否为0 + " je __afl_setup_first\n" // 如果为0,跳转到首次设置 "\n" - " movq %rdx, __afl_area_ptr(%rip)\n" - " jmp __afl_store\n" + " movq %rdx, __afl_area_ptr(%rip)\n" // 存储指针 + " jmp __afl_store\n" // 跳转到存储 "\n" - "__afl_setup_first:\n" + "__afl_setup_first:\n" // 首次设置 "\n" " /* Save everything that is not yet saved and that may be touched by\n" - " getenv() and several other libcalls we'll be relying on. */\n" - "\n" - " leaq -352(%rsp), %rsp\n" - "\n" - " movq %rax, 0(%rsp)\n" - " movq %rcx, 8(%rsp)\n" - " movq %rdi, 16(%rsp)\n" - " movq %rsi, 32(%rsp)\n" - " movq %r8, 40(%rsp)\n" - " movq %r9, 48(%rsp)\n" - " movq %r10, 56(%rsp)\n" - " movq %r11, 64(%rsp)\n" - "\n" - " movq %xmm0, 96(%rsp)\n" - " movq %xmm1, 112(%rsp)\n" - " movq %xmm2, 128(%rsp)\n" - " movq %xmm3, 144(%rsp)\n" - " movq %xmm4, 160(%rsp)\n" - " movq %xmm5, 176(%rsp)\n" - " movq %xmm6, 192(%rsp)\n" - " movq %xmm7, 208(%rsp)\n" - " movq %xmm8, 224(%rsp)\n" - " movq %xmm9, 240(%rsp)\n" - " movq %xmm10, 256(%rsp)\n" - " movq %xmm11, 272(%rsp)\n" - " movq %xmm12, 288(%rsp)\n" - " movq %xmm13, 304(%rsp)\n" - " movq %xmm14, 320(%rsp)\n" - " movq %xmm15, 336(%rsp)\n" - "\n" - " /* Map SHM, jumping to __afl_setup_abort if something goes wrong. */\n" + " getenv() and several other libcalls we'll be relying on. */\n" // 保存所有可能被修改的寄存器 + "\n" + " leaq -352(%rsp), %rsp\n" // 调整栈指针 + "\n" + " movq %rax, 0(%rsp)\n" // 保存RAX + " movq %rcx, 8(%rsp)\n" // 保存RCX + " movq %rdi, 16(%rsp)\n" // 保存RDI + " movq %rsi, 32(%rsp)\n" // 保存RSI + " movq %r8, 40(%rsp)\n" // 保存R8 + " movq %r9, 48(%rsp)\n" // 保存R9 + " movq %r10, 56(%rsp)\n" // 保存R10 + " movq %r11, 64(%rsp)\n" // 保存R11 + "\n" + " movq %xmm0, 96(%rsp)\n" // 保存XMM0 + " movq %xmm1, 112(%rsp)\n" // 保存XMM1 + " movq %xmm2, 128(%rsp)\n" // 保存XMM2 + " movq %xmm3, 144(%rsp)\n" // 保存XMM3 + " movq %xmm4, 160(%rsp)\n" // 保存XMM4 + " movq %xmm5, 176(%rsp)\n" // 保存XMM5 + " movq %xmm6, 192(%rsp)\n" // 保存XMM6 + " movq %xmm7, 208(%rsp)\n" // 保存XMM7 + " movq %xmm8, 224(%rsp)\n" // 保存XMM8 + " movq %xmm9, 240(%rsp)\n" // 保存XMM9 + " movq %xmm10, 256(%rsp)\n" // 保存XMM10 + " movq %xmm11, 272(%rsp)\n" // 保存XMM11 + " movq %xmm12, 288(%rsp)\n" // 保存XMM12 + " movq %xmm13, 304(%rsp)\n" // 保存XMM13 + " movq %xmm14, 320(%rsp)\n" // 保存XMM14 + " movq %xmm15, 336(%rsp)\n" // 保存XMM15 + "\n" + " /* Map SHM, jumping to __afl_setup_abort if something goes wrong. */\n" // 映射SHM区域 "\n" " /* The 64-bit ABI requires 16-byte stack alignment. We'll keep the\n" - " original stack ptr in the callee-saved r12. */\n" + " original stack ptr in the callee-saved r12. */\n" // 64位ABI要求16字节对齐 "\n" - " pushq %r12\n" - " movq %rsp, %r12\n" - " subq $16, %rsp\n" - " andq $0xfffffffffffffff0, %rsp\n" + " pushq %r12\n" // 保存R12 + " movq %rsp, %r12\n" // 保存栈指针 + " subq $16, %rsp\n" // 调整栈指针 + " andq $0xfffffffffffffff0, %rsp\n" // 对齐栈 "\n" - " leaq .AFL_SHM_ENV(%rip), %rdi\n" - CALL_L64("getenv") + " leaq .AFL_SHM_ENV(%rip), %rdi\n" // 加载SHM环境变量名 + CALL_L64("getenv") // 调用getenv "\n" - " testq %rax, %rax\n" - " je __afl_setup_abort\n" + " testq %rax, %rax\n" // 检查返回值 + " je __afl_setup_abort\n" // 如果为0,跳转到设置失败 "\n" - " movq %rax, %rdi\n" - CALL_L64("atoi") + " movq %rax, %rdi\n" // 加载返回值 + CALL_L64("atoi") // 调用atoi "\n" - " xorq %rdx, %rdx /* shmat flags */\n" - " xorq %rsi, %rsi /* requested addr */\n" - " movq %rax, %rdi /* SHM ID */\n" - CALL_L64("shmat") + " xorq %rdx, %rdx /* shmat flags */\n" // 清零RDX + " xorq %rsi, %rsi /* requested addr */\n" // 清零RSI + " movq %rax, %rdi /* SHM ID */\n" // 加载SHM ID + CALL_L64("shmat") // 调用shmat "\n" - " cmpq $-1, %rax\n" - " je __afl_setup_abort\n" + " cmpq $-1, %rax\n" // 检查返回值 + " je __afl_setup_abort\n" // 如果为-1,跳转到设置失败 "\n" - " /* Store the address of the SHM region. */\n" + " /* Store the address of the SHM region. */\n" // 存储SHM区域地址 "\n" - " movq %rax, %rdx\n" - " movq %rax, __afl_area_ptr(%rip)\n" + " movq %rax, %rdx\n" // 存储到RDX + " movq %rax, __afl_area_ptr(%rip)\n" // 存储到全局变量 "\n" -#ifdef __APPLE__ - " movq %rax, __afl_global_area_ptr(%rip)\n" -#else - " movq __afl_global_area_ptr@GOTPCREL(%rip), %rdx\n" - " movq %rax, (%rdx)\n" +#ifdef __APPLE__ // 如果是苹果系统 + " movq %rax, __afl_global_area_ptr(%rip)\n" // 存储到全局变量 +#else // 否则 + " movq __afl_global_area_ptr@GOTPCREL(%rip), %rdx\n" // 加载全局指针 + " movq %rax, (%rdx)\n" // 存储到全局变量 #endif /* ^__APPLE__ */ - " movq %rax, %rdx\n" + " movq %rax, %rdx\n" // 存储到RDX "\n" - "__afl_forkserver:\n" + "__afl_forkserver:\n" // 进入fork服务器模式 "\n" " /* Enter the fork server mode to avoid the overhead of execve() calls. We\n" - " push rdx (area ptr) twice to keep stack alignment neat. */\n" + " push rdx (area ptr) twice to keep stack alignment neat. */\n" // 进入fork服务器模式 "\n" - " pushq %rdx\n" - " pushq %rdx\n" + " pushq %rdx\n" // 保存RDX + " pushq %rdx\n" // 保存RDX "\n" " /* Phone home and tell the parent that we're OK. (Note that signals with\n" " no SA_RESTART will mess it up). If this fails, assume that the fd is\n" " closed because we were execve()d from an instrumented binary, or because\n" - " the parent doesn't want to use the fork server. */\n" + " the parent doesn't want to use the fork server. */\n" // 通知父进程 "\n" - " movq $4, %rdx /* length */\n" - " leaq __afl_temp(%rip), %rsi /* data */\n" - " movq $" STRINGIFY((FORKSRV_FD + 1)) ", %rdi /* file desc */\n" - CALL_L64("write") + " movq $4, %rdx /* length */\n" // 设置长度 + " leaq __afl_temp(%rip), %rsi /* data */\n" // 加载数据 + " movq $" STRINGIFY((FORKSRV_FD + 1)) ", %rdi /* file desc */\n" // 加载文件描述符 + CALL_L64("write") // 调用write "\n" - " cmpq $4, %rax\n" - " jne __afl_fork_resume\n" + " cmpq $4, %rax\n" // 检查返回值 + " jne __afl_fork_resume\n" // 如果不等于4,跳转到恢复 "\n" - "__afl_fork_wait_loop:\n" + "__afl_fork_wait_loop:\n" // fork等待循环 "\n" - " /* Wait for parent by reading from the pipe. Abort if read fails. */\n" + " /* Wait for parent by reading from the pipe. Abort if read fails. */\n" // 等待父进程 "\n" - " movq $4, %rdx /* length */\n" - " leaq __afl_temp(%rip), %rsi /* data */\n" - " movq $" STRINGIFY(FORKSRV_FD) ", %rdi /* file desc */\n" - CALL_L64("read") - " cmpq $4, %rax\n" - " jne __afl_die\n" + " movq $4, %rdx /* length */\n" // 设置长度 + " leaq __afl_temp(%rip), %rsi /* data */\n" // 加载数据 + " movq $" STRINGIFY(FORKSRV_FD) ", %rdi /* file desc */\n" // 加载文件描述符 + CALL_L64("read") // 调用read + " cmpq $4, %rax\n" // 检查返回值 + " jne __afl_die\n" // 如果不等于4,跳转到死亡 "\n" " /* Once woken up, create a clone of our process. This is an excellent use\n" " case for syscall(__NR_clone, 0, CLONE_PARENT), but glibc boneheadedly\n" " caches getpid() results and offers no way to update the value, breaking\n" - " abort(), raise(), and a bunch of other things :-( */\n" + " abort(), raise(), and a bunch of other things :-( */\n" // 创建子进程 "\n" - CALL_L64("fork") - " cmpq $0, %rax\n" - " jl __afl_die\n" - " je __afl_fork_resume\n" + CALL_L64("fork") // 调用fork + " cmpq $0, %rax\n" // 检查返回值 + " jl __afl_die\n" // 如果小于0,跳转到死亡 + " je __afl_fork_resume\n" // 如果等于0,跳转到恢复 "\n" - " /* In parent process: write PID to pipe, then wait for child. */\n" + " /* In parent process: write PID to pipe, then wait for child. */\n" // 在父进程中 "\n" - " movl %eax, __afl_fork_pid(%rip)\n" + " movl %eax, __afl_fork_pid(%rip)\n" // 存储子进程PID "\n" - " movq $4, %rdx /* length */\n" - " leaq __afl_fork_pid(%rip), %rsi /* data */\n" - " movq $" STRINGIFY((FORKSRV_FD + 1)) ", %rdi /* file desc */\n" - CALL_L64("write") + " movq $4, %rdx /* length */\n" // 设置长度 + " leaq __afl_fork_pid(%rip), %rsi /* data */\n" // 加载数据 + " movq $" STRINGIFY((FORKSRV_FD + 1)) ", %rdi /* file desc */\n" // 加载文件描述符 + CALL_L64("write") // 调用write "\n" - " movq $0, %rdx /* no flags */\n" - " leaq __afl_temp(%rip), %rsi /* status */\n" - " movq __afl_fork_pid(%rip), %rdi /* PID */\n" - CALL_L64("waitpid") - " cmpq $0, %rax\n" - " jle __afl_die\n" + " movq $0, %rdx /* no flags */\n" // 设置标志 + " leaq __afl_temp(%rip), %rsi /* status */\n" // 加载状态 + " movq __afl_fork_pid(%rip), %rdi /* PID */\n" // 加载PID + CALL_L64("waitpid") // 调用waitpid + " cmpq $0, %rax\n" // 检查返回值 + " jle __afl_die\n" // 如果小于等于0,跳转到死亡 "\n" - " /* Relay wait status to pipe, then loop back. */\n" + " /* Relay wait status to pipe, then loop back. */\n" // 传递等待状态 "\n" - " movq $4, %rdx /* length */\n" - " leaq __afl_temp(%rip), %rsi /* data */\n" - " movq $" STRINGIFY((FORKSRV_FD + 1)) ", %rdi /* file desc */\n" - CALL_L64("write") + " movq $4, %rdx /* length */\n" // 设置长度 + " leaq __afl_temp(%rip), %rsi /* data */\n" // 加载数据 + " movq $" STRINGIFY((FORKSRV_FD + 1)) ", %rdi /* file desc */\n" // 加载文件描述符 + CALL_L64("write") // 调用write "\n" - " jmp __afl_fork_wait_loop\n" + " jmp __afl_fork_wait_loop\n" // 跳转到等待循环 "\n" - "__afl_fork_resume:\n" + "__afl_fork_resume:\n" // 恢复 "\n" - " /* In child process: close fds, resume execution. */\n" + " /* In child process: close fds, resume execution. */\n" // 在子进程中 "\n" - " movq $" STRINGIFY(FORKSRV_FD) ", %rdi\n" - CALL_L64("close") + " movq $" STRINGIFY(FORKSRV_FD) ", %rdi\n" // 加载文件描述符 + CALL_L64("close") // 调用close "\n" - " movq $" STRINGIFY((FORKSRV_FD + 1)) ", %rdi\n" - CALL_L64("close") + " movq $" STRINGIFY((FORKSRV_FD + 1)) ", %rdi\n" // 加载文件描述符 + CALL_L64("close") // 调用close "\n" - " popq %rdx\n" - " popq %rdx\n" + " popq %rdx\n" // 恢复RDX + " popq %rdx\n" // 恢复RDX "\n" - " movq %r12, %rsp\n" - " popq %r12\n" + " movq %r12, %rsp\n" // 恢复栈指针 + " popq %r12\n" // 恢复R12 "\n" - " movq 0(%rsp), %rax\n" - " movq 8(%rsp), %rcx\n" - " movq 16(%rsp), %rdi\n" - " movq 32(%rsp), %rsi\n" - " movq 40(%rsp), %r8\n" - " movq 48(%rsp), %r9\n" - " movq 56(%rsp), %r10\n" - " movq 64(%rsp), %r11\n" + " movq 0(%rsp), %rax\n" // 恢复RAX + " movq 8(%rsp), %rcx\n" // 恢复RCX + " movq 16(%rsp), %rdi\n" // 恢复RDI + " movq 32(%rsp), %rsi\n" // 恢复RSI + " movq 40(%rsp), %r8\n" // 恢复R8 + " movq 48(%rsp), %r9\n" // 恢复R9 + " movq 56(%rsp), %r10\n" // 恢复R10 + " movq 64(%rsp), %r11\n" // 恢复R11 "\n" - " movq 96(%rsp), %xmm0\n" - " movq 112(%rsp), %xmm1\n" - " movq 128(%rsp), %xmm2\n" - " movq 144(%rsp), %xmm3\n" - " movq 160(%rsp), %xmm4\n" - " movq 176(%rsp), %xmm5\n" - " movq 192(%rsp), %xmm6\n" - " movq 208(%rsp), %xmm7\n" - " movq 224(%rsp), %xmm8\n" - " movq 240(%rsp), %xmm9\n" - " movq 256(%rsp), %xmm10\n" - " movq 272(%rsp), %xmm11\n" - " movq 288(%rsp), %xmm12\n" - " movq 304(%rsp), %xmm13\n" - " movq 320(%rsp), %xmm14\n" - " movq 336(%rsp), %xmm15\n" + " movq 96(%rsp), %xmm0\n" // 恢复XMM0 + " movq 112(%rsp), %xmm1\n" // 恢复XMM1 + " movq 128(%rsp), %xmm2\n" // 恢复XMM2 + " movq 144(%rsp), %xmm3\n" // 恢复XMM3 + " movq 160(%rsp), %xmm4\n" // 恢复XMM4 + " movq 176(%rsp), %xmm5\n" // 恢复XMM5 + " movq 192(%rsp), %xmm6\n" // 恢复XMM6 + " movq 208(%rsp), %xmm7\n" // 恢复XMM7 + " movq 224(%rsp), %xmm8\n" // 恢复XMM8 + " movq 240(%rsp), %xmm9\n" // 恢复XMM9 + " movq 256(%rsp), %xmm10\n" // 恢复XMM10 + " movq 272(%rsp), %xmm11\n" // 恢复XMM11 + " movq 288(%rsp), %xmm12\n" // 恢复XMM12 + " movq 304(%rsp), %xmm13\n" // 恢复XMM13 + " movq 320(%rsp), %xmm14\n" // 恢复XMM14 + " movq 336(%rsp), %xmm15\n" // 恢复XMM15 "\n" - " leaq 352(%rsp), %rsp\n" + " leaq 352(%rsp), %rsp\n" // 恢复栈指针 "\n" - " jmp __afl_store\n" + " jmp __afl_store\n" // 跳转到存储 "\n" - "__afl_die:\n" + "__afl_die:\n" // 死亡 "\n" - " xorq %rax, %rax\n" - CALL_L64("_exit") + " xorq %rax, %rax\n" // 清零RAX + CALL_L64("_exit") // 调用_exit "\n" - "__afl_setup_abort:\n" + "__afl_setup_abort:\n" // 设置失败 "\n" " /* Record setup failure so that we don't keep calling\n" - " shmget() / shmat() over and over again. */\n" - "\n" - " incb __afl_setup_failure(%rip)\n" - "\n" - " movq %r12, %rsp\n" - " popq %r12\n" - "\n" - " movq 0(%rsp), %rax\n" - " movq 8(%rsp), %rcx\n" - " movq 16(%rsp), %rdi\n" - " movq 32(%rsp), %rsi\n" - " movq 40(%rsp), %r8\n" - " movq 48(%rsp), %r9\n" - " movq 56(%rsp), %r10\n" - " movq 64(%rsp), %r11\n" - "\n" - " movq 96(%rsp), %xmm0\n" - " movq 112(%rsp), %xmm1\n" - " movq 128(%rsp), %xmm2\n" - " movq 144(%rsp), %xmm3\n" - " movq 160(%rsp), %xmm4\n" - " movq 176(%rsp), %xmm5\n" - " movq 192(%rsp), %xmm6\n" - " movq 208(%rsp), %xmm7\n" - " movq 224(%rsp), %xmm8\n" - " movq 240(%rsp), %xmm9\n" - " movq 256(%rsp), %xmm10\n" - " movq 272(%rsp), %xmm11\n" - " movq 288(%rsp), %xmm12\n" - " movq 304(%rsp), %xmm13\n" - " movq 320(%rsp), %xmm14\n" - " movq 336(%rsp), %xmm15\n" - "\n" - " leaq 352(%rsp), %rsp\n" - "\n" - " jmp __afl_return\n" - "\n" - ".AFL_VARS:\n" - "\n" - -#ifdef __APPLE__ - - " .comm __afl_area_ptr, 8\n" -#ifndef COVERAGE_ONLY - " .comm __afl_prev_loc, 8\n" + " shmget() / shmat() over and over again. */\n" // 记录设置失败 + "\n" + " incb __afl_setup_failure(%rip)\n" // 增加设置失败标志 + "\n" + " movq %r12, %rsp\n" // 恢复栈指针 + " popq %r12\n" // 恢复R12 + "\n" + " movq 0(%rsp), %rax\n" // 恢复RAX + " movq 8(%rsp), %rcx\n" // 恢复RCX + " movq 16(%rsp), %rdi\n" // 恢复RDI + " movq 32(%rsp), %rsi\n" // 恢复RSI + " movq 40(%rsp), %r8\n" // 恢复R8 + " movq 48(%rsp), %r9\n" // 恢复R9 + " movq 56(%rsp), %r10\n" // 恢复R10 + " movq 64(%rsp), %r11\n" // 恢复R11 + "\n" + " movq 96(%rsp), %xmm0\n" // 恢复XMM0 + " movq 112(%rsp), %xmm1\n" // 恢复XMM1 + " movq 128(%rsp), %xmm2\n" // 恢复XMM2 + " movq 144(%rsp), %xmm3\n" // 恢复XMM3 + " movq 160(%rsp), %xmm4\n" // 恢复XMM4 + " movq 176(%rsp), %xmm5\n" // 恢复XMM5 + " movq 192(%rsp), %xmm6\n" // 恢复XMM6 + " movq 208(%rsp), %xmm7\n" // 恢复XMM7 + " movq 224(%rsp), %xmm8\n" // 恢复XMM8 + " movq 240(%rsp), %xmm9\n" // 恢复XMM9 + " movq 256(%rsp), %xmm10\n" // 恢复XMM10 + " movq 272(%rsp), %xmm11\n" // 恢复XMM11 + " movq 288(%rsp), %xmm12\n" // 恢复XMM12 + " movq 304(%rsp), %xmm13\n" // 恢复XMM13 + " movq 320(%rsp), %xmm14\n" // 恢复XMM14 + " movq 336(%rsp), %xmm15\n" // 恢复XMM15 + "\n" + " leaq 352(%rsp), %rsp\n" // 恢复栈指针 + "\n" + " jmp __afl_return\n" // 跳转到返回 + "\n" + ".AFL_VARS:\n" // AFL变量 + "\n" + +#ifdef __APPLE__ // 如果是苹果系统 + + " .comm __afl_area_ptr, 8\n" // 定义全局变量 +#ifndef COVERAGE_ONLY // 如果不是仅覆盖率模式 + " .comm __afl_prev_loc, 8\n" // 定义全局变量 #endif /* !COVERAGE_ONLY */ - " .comm __afl_fork_pid, 4\n" - " .comm __afl_temp, 4\n" - " .comm __afl_setup_failure, 1\n" + " .comm __afl_fork_pid, 4\n" // 定义全局变量 + " .comm __afl_temp, 4\n" // 定义全局变量 + " .comm __afl_setup_failure, 1\n" // 定义全局变量 -#else +#else // 否则 - " .lcomm __afl_area_ptr, 8\n" -#ifndef COVERAGE_ONLY - " .lcomm __afl_prev_loc, 8\n" + " .lcomm __afl_area_ptr, 8\n" // 定义局部变量 +#ifndef COVERAGE_ONLY // 如果不是仅覆盖率模式 + " .lcomm __afl_prev_loc, 8\n" // 定义局部变量 #endif /* !COVERAGE_ONLY */ - " .lcomm __afl_fork_pid, 4\n" - " .lcomm __afl_temp, 4\n" - " .lcomm __afl_setup_failure, 1\n" + " .lcomm __afl_fork_pid, 4\n" // 定义局部变量 + " .lcomm __afl_temp, 4\n" // 定义局部变量 + " .lcomm __afl_setup_failure, 1\n" // 定义局部变量 #endif /* ^__APPLE__ */ - " .comm __afl_global_area_ptr, 8, 8\n" + " .comm __afl_global_area_ptr, 8, 8\n" // 定义全局变量 "\n" - ".AFL_SHM_ENV:\n" - " .asciz \"" SHM_ENV_VAR "\"\n" + ".AFL_SHM_ENV:\n" // SHM环境变量 + " .asciz \"" SHM_ENV_VAR "\"\n" // 定义字符串 "\n" - "/* --- END --- */\n" + "/* --- END --- */\n" // 64位主插桩代码结束 "\n"; -#endif /* !_HAVE_AFL_AS_H */ +#endif /* !_HAVE_AFL_AS_H */ // 结束条件编译 \ No newline at end of file diff --git a/src/afl-cmin b/src/afl-cmin index e89b895..a28a24e 100644 --- a/src/afl-cmin +++ b/src/afl-cmin @@ -1,89 +1,91 @@ #!/usr/bin/env bash # -# american fuzzy lop - corpus minimization tool +# American Fuzzy Lop - 语料库最小化工具 # --------------------------------------------- # -# Written and maintained by Michal Zalewski +# 作者和维护者:Michal Zalewski # -# 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 " +echo "为 afl-fuzz 提供的语料库最小化工具 " # 输出程序名称 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" -- "$@" &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" -- "$@" "$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 # 正常退出脚本 diff --git a/src/afl-fuzz(2).c b/src/afl-fuzz(2).c new file mode 100644 index 0000000..7460086 --- /dev/null +++ b/src/afl-fuzz(2).c @@ -0,0 +1,8179 @@ +/* + 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 - fuzzer code + -------------------------------- + + Written and maintained by Michal Zalewski + + Forkserver design by Jann Horn + + This is the real deal: the program takes an instrumented binary and + attempts a variety of basic fuzzing tricks, paying close attention to + how they affect the execution path. + +*/ + +#define AFL_MAIN +#include "android-ashmem.h" +#define MESSAGES_TO_STDOUT + +#ifndef _GNU_SOURCE +#define _GNU_SOURCE +#endif +#define _FILE_OFFSET_BITS 64 + +#include "config.h" +#include "types.h" +#include "debug.h" +#include "alloc-inl.h" +#include "hash.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(__APPLE__) || defined(__FreeBSD__) || defined (__OpenBSD__) +# include +#endif /* __APPLE__ || __FreeBSD__ || __OpenBSD__ */ + +/* 针对具有 sched_setaffinity 的系统;目前仅限于 Linux,但可以希望... */ + +#ifdef __linux__ +# define HAVE_AFFINITY 1 +#endif /* __linux__ */ + +/* 在构建为库时导出一些变量的开关。对公众没有多大用处。 */ + +#ifdef AFL_LIB +# define EXP_ST +#else +# define EXP_ST static +#endif /* ^AFL_LIB */ + +/* 大量全局变量,主要用于状态 UI 和其他地方,在这些地方作为函数参数传递没有意义。 */ + +EXP_ST u8 *in_dir, /* 包含测试用例的输入目录 */ + *out_file, /* 要模糊测试的文件(如果有的话) */ + *out_dir, /* 工作和输出目录 */ + *sync_dir, /* 同步目录 */ + *sync_id, /* 模糊测试器ID */ + *use_banner, /* 显示横幅 */ + *in_bitmap, /* 输入位图 */ + *doc_path, /* 文档目录的路径 */ + *target_path, /* 目标二进制文件的路径 */ + *orig_cmdline; /* 原始命令行 */ + +EXP_ST u32 exec_tmout = EXEC_TIMEOUT; /* 可配置的执行超时(毫秒) */ +static u32 hang_tmout = EXEC_TIMEOUT; /* 用于检测挂起的超时(毫秒) */ + +EXP_ST u64 mem_limit = MEM_LIMIT; /* 子进程的内存限制(MB) */ + +EXP_ST u32 cpu_to_bind = 0; /* 要绑定的空闲CPU核心的ID */ + +static u32 stats_update_freq = 1; /* 统计更新频率(执行次数) */ + +EXP_ST u8 skip_deterministic, /* 是不是跳过确定性阶段? */ + force_deterministic, /* 强制执行确定性阶段? */ + use_splicing, /* 重新组合输入文件? */ + dumb_mode, /* 以非仪器化模式运行? */ + score_changed, /* 喜爱的评分是否改变? */ + kill_signal, /* 杀死子进程的信号 */ + resuming_fuzz, /* 恢复以前的模糊测试作业? */ + timeout_given, /* 是否给定了特定的超时? */ + cpu_to_bind_given, /* 是否给定了指定的 cpu_to_bind? */ + not_on_tty, /* 标准输出不是 tty */ + term_too_small, /* 终端尺寸过小 */ + uses_asan, /* 目标是否使用 ASAN? */ + no_forkserver, /* 是否禁用 forkserver? */ + crash_mode, /* 崩溃模式!是的! */ + in_place_resume, /* 尝试原地恢复? */ + auto_changed, /* 自动生成的标记是否更改? */ + no_cpu_meter_red, /* 状态屏幕的 风水 */ + no_arith, /* 跳过大多数算术操作 */ + shuffle_queue, /* 随机输入队列? */ + bitmap_changed = 1, /* 是否更新位图的时间? */ + qemu_mode, /* 是否在 QEMU 模式下运行? */ + skip_requested, /* 跳过请求,通过 SIGUSR1 发送 */ + run_over10m, /* 运行时间超过 10 分钟? */ + persistent_mode, /* 是否在持久模式下运行? */ + deferred_mode, /* 延迟 forkserver 模式? */ + fast_cal; /* 尝试更快校准? */ + +static s32 out_fd, /* 持久的输出文件描述符 */ + dev_urandom_fd = -1, /* 对 /dev/urandom 的持久文件描述符 */ + dev_null_fd = -1, /* 对 /dev/null 的持久文件描述符 */ + fsrv_ctl_fd, /* Fork 服务器控制管道(写) */ + fsrv_st_fd; /* Fork 服务器状态管道(读) */ + +static s32 forksrv_pid, /* Fork 服务器的 PID */ + child_pid = -1, /* 被模糊测试程序的 PID */ + out_dir_fd = -1; /* 锁文件的文件描述符 */ + +EXP_ST u8* trace_bits; /* 带有仪器的位图的共享内存 */ + +EXP_ST u8 virgin_bits[MAP_SIZE], /* 还未被模糊测试触及的区域 */ + virgin_tmout[MAP_SIZE], /* 我们在超时中未见的位 */ + virgin_crash[MAP_SIZE]; /* 我们在崩溃中未见的位 */ + +static u8 var_bytes[MAP_SIZE]; /* 似乎是可变的字节 */ + +static s32 shm_id; /* 共享内存区域的 ID */ + +static volatile u8 stop_soon, /* 按下了 Ctrl-C? */ + clear_screen = 1, /* 窗口被调整大小? */ + child_timed_out; /* 被跟踪的进程超时? */ + +EXP_ST u32 queued_paths, /* 排队测试用例的总数 */ + queued_variable, /* 具有可变行为的测试用例 */ + queued_at_start, /* 初始输入的总数 */ + queued_discovered, /* 此次运行中发现的项目 */ + queued_imported, /* 通过 -S 导入的项目 */ + queued_favored, /* 被认为是有利的路径 */ + queued_with_cov, /* 带有新覆盖字节的路径 */ + pending_not_fuzzed, /* 排队但尚未完成的测试用例 */ + pending_favored, /* 待处理的有利路径 */ + cur_skipped_paths, /* 当前周期中放弃的输入 */ + cur_depth, /* 当前路径深度 */ + max_depth, /* 最大路径深度 */ + useless_at_start, /* 无用的起始路径的数量 */ + var_byte_count, /* 具有可变行为的位图字节数 */ + current_entry, /* 当前队列条目的 ID */ + havoc_div = 1; /* 恶劣周期计数的除数 */ + +EXP_ST u64 total_crashes, /* 崩溃的总数 */ + unique_crashes, /* 具有唯一签名的崩溃 */ + total_tmouts, /* 超时的总数 */ + unique_tmouts, /* 具有唯一签名的超时 */ + unique_hangs, /* 具有唯一签名的挂起 */ + total_execs, /* 执行 execve() 调用的总数 */ + slowest_exec_ms, /* 非挂起的最慢测试用例(毫秒) */ + start_time, /* Unix 开始时间(毫秒) */ + last_path_time, /* 最近路径的时间(毫秒) */ + last_crash_time, /* 最近崩溃的时间(毫秒) */ + last_hang_time, /* 最近挂起的时间(毫秒) */ + last_crash_execs, /* 最近崩溃时的执行计数 */ + queue_cycle, /* 队列轮次计数 */ + cycles_wo_finds, /* 没有新路径的周期 */ + trim_execs, /* 用于修剪输入文件的执行次数 */ + bytes_trim_in, /* 进入修剪器的字节数 */ + bytes_trim_out, /* 从修剪器输出的字节数 */ + blocks_eff_total, /* 受效应器映射影响的块总数 */ + blocks_eff_select; /* 被选择为模糊测试的块 */ + +static u32 subseq_tmouts; /* 连续超时的次数 */ + +static u8 *stage_name = "init", /* 当前模糊阶段的名称 */ + *stage_short, /* 短阶段名称 */ + *syncing_party; /* 当前正在同步的对象 */ + +static s32 stage_cur, stage_max; /* 阶段进度 */ +static s32 splicing_with = -1; /* 正在与哪个测试用例拼接? */ + +static u32 master_id, master_max; /* 主实例的作业分割 */ + +static u32 syncing_case; /* 正在同步的案例编号... */ + +static s32 stage_cur_byte, /* 当前阶段操作的字节偏移 */ + stage_cur_val; /* 用于阶段操作的值 */ + +static u8 stage_val_type; /* 值的类型(STAGE_VAL_*) */ + +static u64 stage_finds[32], /* 每个模糊阶段找到的模式 */ + stage_cycles[32]; /* 每个模糊阶段的执行次数 */ + +static u32 rand_cnt; /* 随机数计数器 */ + +static u64 total_cal_us, /* 总校准时间(微秒) */ + total_cal_cycles; /* 总校准周期 */ + +static u64 total_bitmap_size, /* 所有位图的总位数 */ + total_bitmap_entries; /* 计数的位图数量 */ + +static s32 cpu_core_count; /* CPU核心数量 */ + +#ifdef HAVE_AFFINITY + +static s32 cpu_aff = -1; /* 选定的CPU核心 */ + +#endif /* HAVE_AFFINITY */ + +static FILE* plot_file; /* Gnuplot 输出文件 */ + +struct queue_entry { + + u8* fname; /* 测试用例的文件名 */ + u32 len; /* 输入长度 */ + + u8 cal_failed, /* 校准失败? */ + trim_done, /* 已修剪? */ + was_fuzzed, /* 是否已进行过模糊测试? */ + passed_det, /* 是否通过了确定性阶段? */ + has_new_cov, /* 是否触发了新的覆盖? */ + var_behavior, /* 是否具有可变行为? */ + favored, /* 当前是否被看作有利? */ + fs_redundant; /* 是否在文件系统中标记为冗余? */ + + u32 bitmap_size, /* 位图中设置的位数 */ + exec_cksum; /* 执行跟踪的校验和 */ + + u64 exec_us, /* 执行时间(微秒) */ + handicap, /* 排队周期落后的数量 */ + depth; /* 路径深度 */ + + u8* trace_mini; /* 如果保留的跟踪字节 */ + u32 tc_ref; /* 跟踪字节引用计数 */ + + struct queue_entry *next, /* 下一个元素(如果有的话) */ + *next_100; /* 100个元素之前的标记 */ + +}; + +static struct queue_entry *queue, /* 模糊测试队列(链表) */ + *queue_cur, /* 当前队列中的偏移 */ + *queue_top, /* 列表的顶部 */ + *q_prev100; /* 前一个100个的标记 */ + +static struct queue_entry* + top_rated[MAP_SIZE]; /* 用于位图字节的顶级条目 */ + +struct extra_data { + u8* data; /* 字典标记数据 */ + u32 len; /* 字典标记长度 */ + u32 hit_cnt; /* 在语料库中的使用计数 */ +}; + +static struct extra_data* extras; /* 用于模糊测试的额外标记 */ +static u32 extras_cnt; /* 读取的标记总数 */ + +static struct extra_data* a_extras; /* 自动选择的额外标记 */ +static u32 a_extras_cnt; /* 可用的额外标记总数 */ + +static u8* (*post_handler)(u8* buf, u32* len); + +/* 根据 config.h 的有趣值 */ + +static s8 interesting_8[] = { INTERESTING_8 }; +static s16 interesting_16[] = { INTERESTING_8, INTERESTING_16 }; +static s32 interesting_32[] = { INTERESTING_8, INTERESTING_16, INTERESTING_32 }; + + +/* 模糊测试阶段 */ + +enum { + /* 00 */ STAGE_FLIP1, + /* 01 */ STAGE_FLIP2, + /* 02 */ STAGE_FLIP4, + /* 03 */ STAGE_FLIP8, + /* 04 */ STAGE_FLIP16, + /* 05 */ STAGE_FLIP32, + /* 06 */ STAGE_ARITH8, + /* 07 */ STAGE_ARITH16, + /* 08 */ STAGE_ARITH32, + /* 09 */ STAGE_INTEREST8, + /* 10 */ STAGE_INTEREST16, + /* 11 */ STAGE_INTEREST32, + /* 12 */ STAGE_EXTRAS_UO, + /* 13 */ STAGE_EXTRAS_UI, + /* 14 */ STAGE_EXTRAS_AO, + /* 15 */ STAGE_HAVOC, + /* 16 */ STAGE_SPLICE +}; + +/* 阶段值类型 */ + +enum { + /* 00 */ STAGE_VAL_NONE, + /* 01 */ STAGE_VAL_LE, + /* 02 */ STAGE_VAL_BE +}; + +/* 执行状态故障码 */ + +enum { + /* 00 */ FAULT_NONE, + /* 01 */ FAULT_TMOUT, + /* 02 */ FAULT_CRASH, + /* 03 */ FAULT_ERROR, + /* 04 */ FAULT_NOINST, + /* 05 */ FAULT_NOBITS +}; + + +/* 获取当前 UNIX 时间(毫秒) */ + +static u64 get_cur_time(void) { + + struct timeval tv; + struct timezone tz; + + gettimeofday(&tv, &tz); + + return (tv.tv_sec * 1000ULL) + (tv.tv_usec / 1000); + +} + + +/* 获取当前 UNIX 时间(微秒) */ + +static u64 get_cur_time_us(void) { + + struct timeval tv; + struct timezone tz; + + gettimeofday(&tv, &tz); + + return (tv.tv_sec * 1000000ULL) + tv.tv_usec; + +} + + +/* 生成一个随机数(范围从 0 到 limit - 1)。这可能有轻微的偏差。 */ + +static inline u32 UR(u32 limit) { + + if (unlikely(!rand_cnt--)) { + + u32 seed[2]; + + ck_read(dev_urandom_fd, &seed, sizeof(seed), "/dev/urandom"); + + srandom(seed[0]); + rand_cnt = (RESEED_RNG / 2) + (seed[1] % RESEED_RNG); + + } + + return random() % limit; + +} + + +/* 随机打乱指针数组。可能会有轻微的偏差。 */ + +static void shuffle_ptrs(void** ptrs, u32 cnt) { + + u32 i; + + for (i = 0; i < cnt - 2; i++) { + + u32 j = i + UR(cnt - i); + void *s = ptrs[i]; + ptrs[i] = ptrs[j]; + ptrs[j] = s; + + } + +} + + +#ifdef HAVE_AFFINITY + +/* 构建一个绑定到特定核心的进程列表。如果找不到返回 -1。 + 假设最多 4k 个 CPU。 */ + +static void bind_to_free_cpu(void) { + + DIR* d; + struct dirent* de; + cpu_set_t c; + + u8 cpu_used[4096] = { 0 }; + u32 i; + + if (cpu_core_count < 2) return; + + if (getenv("AFL_NO_AFFINITY")) { + + WARNF("未绑定到 CPU 核心(设置了 AFL_NO_AFFINITY)。"); + return; + + } + + d = opendir("/proc"); + + if (!d) { + + WARNF("无法访问 /proc - 无法扫描空闲 CPU 核心。"); + return; + + } + + ACTF("检查 CPU 核心负载..."); + + /* 引入一些抖动,以防多个 AFL 任务同时执行相同的操作... */ + + usleep(R(1000) * 250); + + /* 扫描所有 /proc//status 条目,检查 Cpus_allowed_list。 + 使用 cpu_used[] 标记所有绑定到特定 CPU 的进程。这对某些特殊绑定设置可能失败, + 但在几乎所有真实世界的用例中都可能足够好。 */ + + while ((de = readdir(d))) { + + u8* fn; + FILE* f; + u8 tmp[MAX_LINE]; + u8 has_vmsize = 0; + + if (!isdigit(de->d_name[0])) continue; + + fn = alloc_printf("/proc/%s/status", de->d_name); + + if (!(f = fopen(fn, "r"))) { + ck_free(fn); + continue; + } + + while (fgets(tmp, MAX_LINE, f)) { + + u32 hval; + + /* 没有 VmSize 的进程可能是内核任务。 */ + + if (!strncmp(tmp, "VmSize:\t", 8)) has_vmsize = 1; + + if (!strncmp(tmp, "Cpus_allowed_list:\t", 19) && + !strchr(tmp, '-') && !strchr(tmp, ',') && + sscanf(tmp + 19, "%u", &hval) == 1 && hval < sizeof(cpu_used) && + has_vmsize) { + + cpu_used[hval] = 1; + break; + + } + + } + + ck_free(fn); + fclose(f); + + } + + closedir(d); + if (cpu_to_bind_given) { + + if (cpu_to_bind >= cpu_core_count) + FATAL("要绑定的 CPU 核心 ID 应该在 0 到 %u 之间", cpu_core_count - 1); + + if (cpu_used[cpu_to_bind]) + FATAL("要绑定的 CPU 核心 #%u 不可用!", cpu_to_bind); + + i = cpu_to_bind; + + } else { + + for (i = 0; i < cpu_core_count; i++) if (!cpu_used[i]) break; + + } + + if (i == cpu_core_count) { + + SAYF("\n" cLRD "[-] " cRST + "糟糕,似乎系统上所有 %u 个 CPU 核心都分配给了\n" + " 其他的 afl-fuzz 实例(或类似的 CPU 锁定任务)。在此机器上启动\n" + " 另一个模糊测试器可能是个坏主意,但如果您绝对确定,可以设置 AFL_NO_AFFINITY 并重试。\n", + cpu_core_count); + + FATAL("没有更多空闲的 CPU 核心"); + + } + + OKF("找到一个空闲的 CPU 核心,绑定到 #%u。", i); + + cpu_aff = i; + + CPU_ZERO(&c); + CPU_SET(i, &c); + + if (sched_setaffinity(0, sizeof(c), &c)) + PFATAL("sched_setaffinity 失败"); + +} + +#endif /* HAVE_AFFINITY */ + +#ifndef IGNORE_FINDS + +/* 辅助函数用于比较缓冲区;返回第一个和最后一个不同的偏移量。 + 我们用这个来找到拼接两个文件的合理位置。 */ + +static void locate_diffs(u8* ptr1, u8* ptr2, u32 len, s32* first, s32* last) { + + s32 f_loc = -1; + s32 l_loc = -1; + u32 pos; + + for (pos = 0; pos < len; pos++) { + + if (*(ptr1++) != *(ptr2++)) { + + if (f_loc == -1) f_loc = pos; + l_loc = pos; + + } + + } + + *first = f_loc; + *last = l_loc; + + return; + +} + +#endif /* !IGNORE_FINDS */ + + + +/* 描述整数。使用 12 个循环的静态缓冲区来返回值。 + 返回的值对于我们合理预期看到的所有整数应不超过五个字符。 */ + +static u8* DI(u64 val) { + + static u8 tmp[12][16]; + static u8 cur; + + cur = (cur + 1) % 12; + +#define CHK_FORMAT(_divisor, _limit_mult, _fmt, _cast) do { \ + if (val < (_divisor) * (_limit_mult)) { \ + sprintf(tmp[cur], _fmt, ((_cast)val) / (_divisor)); \ + return tmp[cur]; \ + } \ + } while (0) + + /* 0-9999 */ + CHK_FORMAT(1, 10000, "%llu", u64); + + /* 10.0k - 99.9k */ + CHK_FORMAT(1000, 99.95, "%0.01fk", double); + + /* 100k - 999k */ + CHK_FORMAT(1000, 1000, "%lluk", u64); + + /* 1.00M - 9.99M */ + CHK_FORMAT(1000 * 1000, 9.995, "%0.02fM", double); + + /* 10.0M - 99.9M */ + CHK_FORMAT(1000 * 1000, 99.95, "%0.01fM", double); + + /* 100M - 999M */ + CHK_FORMAT(1000 * 1000, 1000, "%lluM", u64); + + /* 1.00G - 9.99G */ + CHK_FORMAT(1000LL * 1000 * 1000, 9.995, "%0.02fG", double); + + /* 10.0G - 99.9G */ + CHK_FORMAT(1000LL * 1000 * 1000, 99.95, "%0.01fG", double); + + /* 100G - 999G */ + CHK_FORMAT(1000LL * 1000 * 1000, 1000, "%lluG", u64); + + /* 1.00T - 9.99T */ + CHK_FORMAT(1000LL * 1000 * 1000 * 1000, 9.995, "%0.02fT", double); + + /* 10.0T - 99.9T */ + CHK_FORMAT(1000LL * 1000 * 1000 * 1000, 99.95, "%0.01fT", double); + + /* 100T+ */ + strcpy(tmp[cur], "infty"); + return tmp[cur]; + +} + + +/* 描述浮点数。与上述类似,只是使用单个静态缓冲区。 */ + +static u8* DF(double val) { + + static u8 tmp[16]; + + if (val < 99.995) { + sprintf(tmp, "%0.02f", val); + return tmp; + } + + if (val < 999.95) { + sprintf(tmp, "%0.01f", val); + return tmp; + } + + return DI((u64)val); + +} + + +/* 将整数描述为内存大小。 */ + +static u8* DMS(u64 val) { + + static u8 tmp[12][16]; + static u8 cur; + + cur = (cur + 1) % 12; + + /* 0-9999 */ + CHK_FORMAT(1, 10000, "%llu B", u64); + + /* 10.0k - 99.9k */ + CHK_FORMAT(1024, 99.95, "%0.01f kB", double); + + /* 100k - 999k */ + CHK_FORMAT(1024, 1000, "%llu kB", u64); + + /* 1.00M - 9.99M */ + CHK_FORMAT(1024 * 1024, 9.995, "%0.02f MB", double); + + /* 10.0M - 99.9M */ + CHK_FORMAT(1024 * 1024, 99.95, "%0.01f MB", double); + + /* 100M - 999M */ + CHK_FORMAT(1024 * 1024, 1000, "%llu MB", u64); + + /* 1.00G - 9.99G */ + CHK_FORMAT(1024LL * 1024 * 1024, 9.995, "%0.02f GB", double); + + /* 10.0G - 99.9G */ + CHK_FORMAT(1024LL * 1024 * 1024, 99.95, "%0.01f GB", double); + + /* 100G - 999G */ + CHK_FORMAT(1024LL * 1024 * 1024, 1000, "%llu GB", u64); + + /* 1.00T - 9.99T */ + CHK_FORMAT(1024LL * 1024 * 1024 * 1024, 9.995, "%0.02f TB", double); + + /* 10.0T - 99.9T */ + CHK_FORMAT(1024LL * 1024 * 1024 * 1024, 99.95, "%0.01f TB", double); + +#undef CHK_FORMAT + + /* 100T+ */ + strcpy(tmp[cur], "infty"); + return tmp[cur]; + +} + + +/* 描述时间间隔。返回一个静态缓冲区,最多 34 个字符。 */ + +static u8* DTD(u64 cur_ms, u64 event_ms) { + + static u8 tmp[64]; + u64 delta; + s32 t_d, t_h, t_m, t_s; + + if (!event_ms) return "尚未看到任何"; + + delta = cur_ms - event_ms; + + t_d = delta / 1000 / 60 / 60 / 24; + t_h = (delta / 1000 / 60 / 60) % 24; + t_m = (delta / 1000 / 60) % 60; + t_s = (delta / 1000) % 60; + + sprintf(tmp, "%s 天, %u 小时, %u 分, %u 秒", DI(t_d), t_h, t_m, t_s); + return tmp; + +} + + +/* 为特定队列项标记确定性检查为完成。我们使用 .state 文件来避免 + 在恢复中断扫描时重复执行确定性模糊。 */ + +static void mark_as_det_done(struct queue_entry* q) { + + u8* fn = strrchr(q->fname, '/'); + s32 fd; + + fn = alloc_printf("%s/queue/.state/deterministic_done/%s", out_dir, fn + 1); + + fd = open(fn, O_WRONLY | O_CREAT | O_EXCL, 0600); + if (fd < 0) PFATAL("无法创建 '%s'", fn); + close(fd); + + ck_free(fn); + + q->passed_det = 1; + +} + + +/* 标记为变量。如果可能,创建符号链接以便于检查文件。 */ + +static void mark_as_variable(struct queue_entry* q) { + + u8 *fn = strrchr(q->fname, '/') + 1, *ldest; + + ldest = alloc_printf("../../%s", fn); + fn = alloc_printf("%s/queue/.state/variable_behavior/%s", out_dir, fn); + + if (symlink(ldest, fn)) { + + s32 fd = open(fn, O_WRONLY | O_CREAT | O_EXCL, 0600); + if (fd < 0) PFATAL("无法创建 '%s'", fn); + close(fd); + + } + + ck_free(ldest); + ck_free(fn); + + q->var_behavior = 1; + +} + + +/* 标记/取消标记为冗余(仅边缘)。这不用于恢复状态, + 但可能对于后处理数据集有用。 */ + +static void mark_as_redundant(struct queue_entry* q, u8 state) { + + u8* fn; + s32 fd; + + if (state == q->fs_redundant) return; + + q->fs_redundant = state; + + fn = strrchr(q->fname, '/'); + fn = alloc_printf("%s/queue/.state/redundant_edges/%s", out_dir, fn + 1); + + if (state) { + + fd = open(fn, O_WRONLY | O_CREAT | O_EXCL, 0600); + if (fd < 0) PFATAL("无法创建 '%s'", fn); + close(fd); + + } else { + + if (unlink(fn)) PFATAL("无法删除 '%s'", fn); + + } + + ck_free(fn); + +} + + +/* 将新的测试用例追加到队列中。 */ + +static void add_to_queue(u8* fname, u32 len, u8 passed_det) { + + struct queue_entry* q = ck_alloc(sizeof(struct queue_entry)); + + q->fname = fname; + q->len = len; + q->depth = cur_depth + 1; + q->passed_det = passed_det; + + if (q->depth > max_depth) max_depth = q->depth; + + if (queue_top) { + + queue_top->next = q; + queue_top = q; + + } else q_prev100 = queue = queue_top = q; + + queued_paths++; + pending_not_fuzzed++; + + cycles_wo_finds = 0; + + /* 为每第 100 个元素设置 next_100 指针(索引 0、100 等),以便更快的迭代。 */ + if ((queued_paths - 1) % 100 == 0 && queued_paths > 1) { + + q_prev100->next_100 = q; + q_prev100 = q; + + } + + last_path_time = get_cur_time(); + +} + + +/* 销毁整个队列。 */ + +EXP_ST void destroy_queue(void) { + + struct queue_entry *q = queue, *n; + + while (q) { + + n = q->next; + ck_free(q->fname); + ck_free(q->trace_mini); + ck_free(q); + q = n; + + } + +} + +/* Write bitmap to file. The bitmap is useful mostly for the secret + -B option, to focus a separate fuzzing session on a particular + interesting input without rediscovering all the others. */ + +EXP_ST void write_bitmap(void) { + + u8* fname; + s32 fd; + + if (!bitmap_changed) return; + bitmap_changed = 0; + + fname = alloc_printf("%s/fuzz_bitmap", out_dir); + fd = open(fname, O_WRONLY | O_CREAT | O_TRUNC, 0600); + + if (fd < 0) PFATAL("Unable to open '%s'", fname); + + ck_write(fd, virgin_bits, MAP_SIZE, fname); + + close(fd); + ck_free(fname); + +} + + +/* Read bitmap from file. This is for the -B option again. */ + +EXP_ST void read_bitmap(u8* fname) { + + s32 fd = open(fname, O_RDONLY); + + if (fd < 0) PFATAL("Unable to open '%s'", fname); + + ck_read(fd, virgin_bits, MAP_SIZE, fname); + + close(fd); + +} + + +/* Check if the current execution path brings anything new to the table. + Update virgin bits to reflect the finds. Returns 1 if the only change is + the hit-count for a particular tuple; 2 if there are new tuples seen. + Updates the map, so subsequent calls will always return 0. + + This function is called after every exec() on a fairly large buffer, so + it needs to be fast. We do this in 32-bit and 64-bit flavors. */ + +static inline u8 has_new_bits(u8* virgin_map) { + +#ifdef WORD_SIZE_64 + + u64* current = (u64*)trace_bits; + u64* virgin = (u64*)virgin_map; + + u32 i = (MAP_SIZE >> 3); + +#else + + u32* current = (u32*)trace_bits; + u32* virgin = (u32*)virgin_map; + + u32 i = (MAP_SIZE >> 2); + +#endif /* ^WORD_SIZE_64 */ + + u8 ret = 0; + + while (i--) { + + /* Optimize for (*current & *virgin) == 0 - i.e., no bits in current bitmap + that have not been already cleared from the virgin map - since this will + almost always be the case. */ + + if (unlikely(*current) && unlikely(*current & *virgin)) { + + if (likely(ret < 2)) { + + u8* cur = (u8*)current; + u8* vir = (u8*)virgin; + + /* Looks like we have not found any new bytes yet; see if any non-zero + bytes in current[] are pristine in virgin[]. */ + +#ifdef WORD_SIZE_64 + + if ((cur[0] && vir[0] == 0xff) || (cur[1] && vir[1] == 0xff) || + (cur[2] && vir[2] == 0xff) || (cur[3] && vir[3] == 0xff) || + (cur[4] && vir[4] == 0xff) || (cur[5] && vir[5] == 0xff) || + (cur[6] && vir[6] == 0xff) || (cur[7] && vir[7] == 0xff)) ret = 2; + else ret = 1; + +#else + + if ((cur[0] && vir[0] == 0xff) || (cur[1] && vir[1] == 0xff) || + (cur[2] && vir[2] == 0xff) || (cur[3] && vir[3] == 0xff)) ret = 2; + else ret = 1; + +#endif /* ^WORD_SIZE_64 */ + + } + + *virgin &= ~*current; + + } + + current++; + virgin++; + + } + + if (ret && virgin_map == virgin_bits) bitmap_changed = 1; + + return ret; + +} + + +/* Count the number of bits set in the provided bitmap. Used for the status + screen several times every second, does not have to be fast. */ + +static u32 count_bits(u8* mem) { + + u32* ptr = (u32*)mem; + u32 i = (MAP_SIZE >> 2); + u32 ret = 0; + + while (i--) { + + u32 v = *(ptr++); + + /* This gets called on the inverse, virgin bitmap; optimize for sparse + data. */ + + if (v == 0xffffffff) { + ret += 32; + continue; + } + + v -= ((v >> 1) & 0x55555555); + v = (v & 0x33333333) + ((v >> 2) & 0x33333333); + ret += (((v + (v >> 4)) & 0xF0F0F0F) * 0x01010101) >> 24; + + } + + return ret; + +} + + +#define FF(_b) (0xff << ((_b) << 3)) + +/* Count the number of bytes set in the bitmap. Called fairly sporadically, + mostly to update the status screen or calibrate and examine confirmed + new paths. */ + +static u32 count_bytes(u8* mem) { + + u32* ptr = (u32*)mem; + u32 i = (MAP_SIZE >> 2); + u32 ret = 0; + + while (i--) { + + u32 v = *(ptr++); + + if (!v) continue; + if (v & FF(0)) ret++; + if (v & FF(1)) ret++; + if (v & FF(2)) ret++; + if (v & FF(3)) ret++; + + } + + return ret; + +} + + +/* Count the number of non-255 bytes set in the bitmap. Used strictly for the + status screen, several calls per second or so. */ + +static u32 count_non_255_bytes(u8* mem) { + + u32* ptr = (u32*)mem; + u32 i = (MAP_SIZE >> 2); + u32 ret = 0; + + while (i--) { + + u32 v = *(ptr++); + + /* This is called on the virgin bitmap, so optimize for the most likely + case. */ + + if (v == 0xffffffff) continue; + if ((v & FF(0)) != FF(0)) ret++; + if ((v & FF(1)) != FF(1)) ret++; + if ((v & FF(2)) != FF(2)) ret++; + if ((v & FF(3)) != FF(3)) ret++; + + } + + return ret; + +} + + +/* Destructively simplify trace by eliminating hit count information + and replacing it with 0x80 or 0x01 depending on whether the tuple + is hit or not. Called on every new crash or timeout, should be + reasonably fast. */ + +static const u8 simplify_lookup[256] = { + + [0] = 1, + [1 ... 255] = 128 + +}; + +#ifdef WORD_SIZE_64 + +static void simplify_trace(u64* mem) { + + u32 i = MAP_SIZE >> 3; + + while (i--) { + + /* Optimize for sparse bitmaps. */ + + if (unlikely(*mem)) { + + u8* mem8 = (u8*)mem; + + mem8[0] = simplify_lookup[mem8[0]]; + mem8[1] = simplify_lookup[mem8[1]]; + mem8[2] = simplify_lookup[mem8[2]]; + mem8[3] = simplify_lookup[mem8[3]]; + mem8[4] = simplify_lookup[mem8[4]]; + mem8[5] = simplify_lookup[mem8[5]]; + mem8[6] = simplify_lookup[mem8[6]]; + mem8[7] = simplify_lookup[mem8[7]]; + + } else *mem = 0x0101010101010101ULL; + + mem++; + + } + +} + +#else + +static void simplify_trace(u32* mem) { + + u32 i = MAP_SIZE >> 2; + + while (i--) { + + /* Optimize for sparse bitmaps. */ + + if (unlikely(*mem)) { + + u8* mem8 = (u8*)mem; + + mem8[0] = simplify_lookup[mem8[0]]; + mem8[1] = simplify_lookup[mem8[1]]; + mem8[2] = simplify_lookup[mem8[2]]; + mem8[3] = simplify_lookup[mem8[3]]; + + } else *mem = 0x01010101; + + mem++; + } + +} + +#endif /* ^WORD_SIZE_64 */ + + +/* Destructively classify execution counts in a trace. This is used as a + preprocessing step for any newly acquired traces. Called on every exec, + must be fast. */ + +static const u8 count_class_lookup8[256] = { + + [0] = 0, + [1] = 1, + [2] = 2, + [3] = 4, + [4 ... 7] = 8, + [8 ... 15] = 16, + [16 ... 31] = 32, + [32 ... 127] = 64, + [128 ... 255] = 128 + +}; + +static u16 count_class_lookup16[65536]; + + +EXP_ST void init_count_class16(void) { + + u32 b1, b2; + + for (b1 = 0; b1 < 256; b1++) + for (b2 = 0; b2 < 256; b2++) + count_class_lookup16[(b1 << 8) + b2] = + (count_class_lookup8[b1] << 8) | + count_class_lookup8[b2]; + +} + + +#ifdef WORD_SIZE_64 + +static inline void classify_counts(u64* mem) { + + u32 i = MAP_SIZE >> 3; + + while (i--) { + + /* Optimize for sparse bitmaps. */ + + if (unlikely(*mem)) { + + u16* mem16 = (u16*)mem; + + mem16[0] = count_class_lookup16[mem16[0]]; + mem16[1] = count_class_lookup16[mem16[1]]; + mem16[2] = count_class_lookup16[mem16[2]]; + mem16[3] = count_class_lookup16[mem16[3]]; + + } + + mem++; + + } + +} + +#else + +static inline void classify_counts(u32* mem) { + + u32 i = MAP_SIZE >> 2; + + while (i--) { + + /* Optimize for sparse bitmaps. */ + + if (unlikely(*mem)) { + + u16* mem16 = (u16*)mem; + + mem16[0] = count_class_lookup16[mem16[0]]; + mem16[1] = count_class_lookup16[mem16[1]]; + + } + + mem++; + + } + +} + +#endif /* ^WORD_SIZE_64 */ + + +/* Get rid of shared memory (atexit handler). */ + +static void remove_shm(void) { + + shmctl(shm_id, IPC_RMID, NULL); + +} + + +/* Compact trace bytes into a smaller bitmap. We effectively just drop the + count information here. This is called only sporadically, for some + new paths. */ + +static void minimize_bits(u8* dst, u8* src) { + + u32 i = 0; + + while (i < MAP_SIZE) { + + if (*(src++)) dst[i >> 3] |= 1 << (i & 7); + i++; + + } + +} + + +/* When we bump into a new path, we call this to see if the path appears + more "favorable" than any of the existing ones. The purpose of the + "favorables" is to have a minimal set of paths that trigger all the bits + seen in the bitmap so far, and focus on fuzzing them at the expense of + the rest. + + The first step of the process is to maintain a list of top_rated[] entries + for every byte in the bitmap. We win that slot if there is no previous + contender, or if the contender has a more favorable speed x size factor. */ + +static void update_bitmap_score(struct queue_entry* q) { + + u32 i; + u64 fav_factor = q->exec_us * q->len; + + /* For every byte set in trace_bits[], see if there is a previous winner, + and how it compares to us. */ + + for (i = 0; i < MAP_SIZE; i++) + + if (trace_bits[i]) { + + if (top_rated[i]) { + + /* Faster-executing or smaller test cases are favored. */ + + if (fav_factor > top_rated[i]->exec_us * top_rated[i]->len) continue; + + /* Looks like we're going to win. Decrease ref count for the + previous winner, discard its trace_bits[] if necessary. */ + + if (!--top_rated[i]->tc_ref) { + ck_free(top_rated[i]->trace_mini); + top_rated[i]->trace_mini = 0; + } + + } + + /* Insert ourselves as the new winner. */ + + top_rated[i] = q; + q->tc_ref++; + + if (!q->trace_mini) { + q->trace_mini = ck_alloc(MAP_SIZE >> 3); + minimize_bits(q->trace_mini, trace_bits); + } + + score_changed = 1; + + } + +} + + +/* The second part of the mechanism discussed above is a routine that + goes over top_rated[] entries, and then sequentially grabs winners for + previously-unseen bytes (temp_v) and marks them as favored, at least + until the next run. The favored entries are given more air time during + all fuzzing steps. */ + +static void cull_queue(void) { + + struct queue_entry* q; + static u8 temp_v[MAP_SIZE >> 3]; + u32 i; + + if (dumb_mode || !score_changed) return; + + score_changed = 0; + + memset(temp_v, 255, MAP_SIZE >> 3); + + queued_favored = 0; + pending_favored = 0; + + q = queue; + + while (q) { + q->favored = 0; + q = q->next; + } + + /* Let's see if anything in the bitmap isn't captured in temp_v. + If yes, and if it has a top_rated[] contender, let's use it. */ + + for (i = 0; i < MAP_SIZE; i++) + if (top_rated[i] && (temp_v[i >> 3] & (1 << (i & 7)))) { + + u32 j = MAP_SIZE >> 3; + + /* Remove all bits belonging to the current entry from temp_v. */ + + while (j--) + if (top_rated[i]->trace_mini[j]) + temp_v[j] &= ~top_rated[i]->trace_mini[j]; + + top_rated[i]->favored = 1; + queued_favored++; + + if (!top_rated[i]->was_fuzzed) pending_favored++; + + } + + q = queue; + + while (q) { + mark_as_redundant(q, !q->favored); + q = q->next; + } + +} + + +/* Configure shared memory and virgin_bits. This is called at startup. */ + +EXP_ST void setup_shm(void) { + + u8* shm_str; + + if (!in_bitmap) memset(virgin_bits, 255, MAP_SIZE); + + memset(virgin_tmout, 255, MAP_SIZE); + memset(virgin_crash, 255, MAP_SIZE); + + shm_id = shmget(IPC_PRIVATE, MAP_SIZE, IPC_CREAT | IPC_EXCL | 0600); + + if (shm_id < 0) PFATAL("shmget() failed"); + + atexit(remove_shm); + + shm_str = alloc_printf("%d", shm_id); + + /* If somebody is asking us to fuzz instrumented binaries in dumb mode, + we don't want them to detect instrumentation, since we won't be sending + fork server commands. This should be replaced with better auto-detection + later on, perhaps? */ + + if (!dumb_mode) setenv(SHM_ENV_VAR, shm_str, 1); + + ck_free(shm_str); + + trace_bits = shmat(shm_id, NULL, 0); + + if (trace_bits == (void *)-1) PFATAL("shmat() failed"); + +} + + +/* Load postprocessor, if available. */ + +static void setup_post(void) { + + void* dh; + u8* fn = getenv("AFL_POST_LIBRARY"); + u32 tlen = 6; + + if (!fn) return; + + ACTF("Loading postprocessor from '%s'...", fn); + + dh = dlopen(fn, RTLD_NOW); + if (!dh) FATAL("%s", dlerror()); + + post_handler = dlsym(dh, "afl_postprocess"); + if (!post_handler) FATAL("Symbol 'afl_postprocess' not found."); + + /* Do a quick test. It's better to segfault now than later =) */ + + post_handler("hello", &tlen); + + OKF("Postprocessor installed successfully."); + +} + + +/* Read all testcases from the input directory, then queue them for testing. + Called at startup. */ + +static void read_testcases(void) { + + struct dirent **nl; + s32 nl_cnt; + u32 i; + u8* fn; + + /* Auto-detect non-in-place resumption attempts. */ + + fn = alloc_printf("%s/queue", in_dir); + if (!access(fn, F_OK)) in_dir = fn; else ck_free(fn); + + ACTF("Scanning '%s'...", in_dir); + + /* We use scandir() + alphasort() rather than readdir() because otherwise, + the ordering of test cases would vary somewhat randomly and would be + difficult to control. */ + + nl_cnt = scandir(in_dir, &nl, NULL, alphasort); + + if (nl_cnt < 0) { + + if (errno == ENOENT || errno == ENOTDIR) + + SAYF("\n" cLRD "[-] " cRST + "The input directory does not seem to be valid - try again. The fuzzer needs\n" + " one or more test case to start with - ideally, a small file under 1 kB\n" + " or so. The cases must be stored as regular files directly in the input\n" + " directory.\n"); + + PFATAL("Unable to open '%s'", in_dir); + + } + + if (shuffle_queue && nl_cnt > 1) { + + ACTF("Shuffling queue..."); + shuffle_ptrs((void**)nl, nl_cnt); + + } + + for (i = 0; i < nl_cnt; i++) { + + struct stat st; + + u8* fn = alloc_printf("%s/%s", in_dir, nl[i]->d_name); + u8* dfn = alloc_printf("%s/.state/deterministic_done/%s", in_dir, nl[i]->d_name); + + u8 passed_det = 0; + + free(nl[i]); /* not tracked */ + + if (lstat(fn, &st) || access(fn, R_OK)) + PFATAL("Unable to access '%s'", fn); + + /* This also takes care of . and .. */ + + if (!S_ISREG(st.st_mode) || !st.st_size || strstr(fn, "/README.testcases")) { + + ck_free(fn); + ck_free(dfn); + continue; + + } + + if (st.st_size > MAX_FILE) + FATAL("Test case '%s' is too big (%s, limit is %s)", fn, + DMS(st.st_size), DMS(MAX_FILE)); + + /* Check for metadata that indicates that deterministic fuzzing + is complete for this entry. We don't want to repeat deterministic + fuzzing when resuming aborted scans, because it would be pointless + and probably very time-consuming. */ + + if (!access(dfn, F_OK)) passed_det = 1; + ck_free(dfn); + + add_to_queue(fn, st.st_size, passed_det); + + } + + free(nl); /* not tracked */ + + if (!queued_paths) { + + SAYF("\n" cLRD "[-] " cRST + "Looks like there are no valid test cases in the input directory! The fuzzer\n" + " needs one or more test case to start with - ideally, a small file under\n" + " 1 kB or so. The cases must be stored as regular files directly in the\n" + " input directory.\n"); + + FATAL("No usable test cases in '%s'", in_dir); + + } + + last_path_time = 0; + queued_at_start = queued_paths; + +} + + +/* Helper function for load_extras. */ + +static int compare_extras_len(const void* p1, const void* p2) { + struct extra_data *e1 = (struct extra_data*)p1, + *e2 = (struct extra_data*)p2; + + return e1->len - e2->len; +} + +static int compare_extras_use_d(const void* p1, const void* p2) { + struct extra_data *e1 = (struct extra_data*)p1, + *e2 = (struct extra_data*)p2; + + return e2->hit_cnt - e1->hit_cnt; +} + + +/* Read extras from a file, sort by size. */ + +static void load_extras_file(u8* fname, u32* min_len, u32* max_len, + u32 dict_level) { + + FILE* f; + u8 buf[MAX_LINE]; + u8 *lptr; + u32 cur_line = 0; + + f = fopen(fname, "r"); + + if (!f) PFATAL("Unable to open '%s'", fname); + + while ((lptr = fgets(buf, MAX_LINE, f))) { + + u8 *rptr, *wptr; + u32 klen = 0; + + cur_line++; + + /* Trim on left and right. */ + + while (isspace(*lptr)) lptr++; + + rptr = lptr + strlen(lptr) - 1; + while (rptr >= lptr && isspace(*rptr)) rptr--; + rptr++; + *rptr = 0; + + /* Skip empty lines and comments. */ + + if (!*lptr || *lptr == '#') continue; + + /* All other lines must end with '"', which we can consume. */ + + rptr--; + + if (rptr < lptr || *rptr != '"') + FATAL("Malformed name=\"value\" pair in line %u.", cur_line); + + *rptr = 0; + + /* Skip alphanumerics and dashes (label). */ + + while (isalnum(*lptr) || *lptr == '_') lptr++; + + /* If @number follows, parse that. */ + + if (*lptr == '@') { + + lptr++; + if (atoi(lptr) > dict_level) continue; + while (isdigit(*lptr)) lptr++; + + } + + /* Skip whitespace and = signs. */ + + while (isspace(*lptr) || *lptr == '=') lptr++; + + /* Consume opening '"'. */ + + if (*lptr != '"') + FATAL("Malformed name=\"keyword\" pair in line %u.", cur_line); + + lptr++; + + if (!*lptr) FATAL("Empty keyword in line %u.", cur_line); + + /* Okay, let's allocate memory and copy data between "...", handling + \xNN escaping, \\, and \". */ + + extras = ck_realloc_block(extras, (extras_cnt + 1) * + sizeof(struct extra_data)); + + wptr = extras[extras_cnt].data = ck_alloc(rptr - lptr); + + while (*lptr) { + + char* hexdigits = "0123456789abcdef"; + + switch (*lptr) { + + case 1 ... 31: + case 128 ... 255: + FATAL("Non-printable characters in line %u.", cur_line); + + case '\\': + + lptr++; + + if (*lptr == '\\' || *lptr == '"') { + *(wptr++) = *(lptr++); + klen++; + break; + } + + if (*lptr != 'x' || !isxdigit(lptr[1]) || !isxdigit(lptr[2])) + FATAL("Invalid escaping (not \\xNN) in line %u.", cur_line); + + *(wptr++) = + ((strchr(hexdigits, tolower(lptr[1])) - hexdigits) << 4) | + (strchr(hexdigits, tolower(lptr[2])) - hexdigits); + + lptr += 3; + klen++; + + break; + + default: + + *(wptr++) = *(lptr++); + klen++; + + } + + } + + extras[extras_cnt].len = klen; + + if (extras[extras_cnt].len > MAX_DICT_FILE) + FATAL("Keyword too big in line %u (%s, limit is %s)", cur_line, + DMS(klen), DMS(MAX_DICT_FILE)); + + if (*min_len > klen) *min_len = klen; + if (*max_len < klen) *max_len = klen; + + extras_cnt++; + + } + + fclose(f); + +} + + +/* Read extras from the extras directory and sort them by size. */ + +static void load_extras(u8* dir) { + + DIR* d; + struct dirent* de; + u32 min_len = MAX_DICT_FILE, max_len = 0, dict_level = 0; + u8* x; + + /* If the name ends with @, extract level and continue. */ + + if ((x = strchr(dir, '@'))) { + + *x = 0; + dict_level = atoi(x + 1); + + } + + ACTF("Loading extra dictionary from '%s' (level %u)...", dir, dict_level); + + d = opendir(dir); + + if (!d) { + + if (errno == ENOTDIR) { + load_extras_file(dir, &min_len, &max_len, dict_level); + goto check_and_sort; + } + + PFATAL("Unable to open '%s'", dir); + + } + + if (x) FATAL("Dictionary levels not supported for directories."); + + while ((de = readdir(d))) { + + struct stat st; + u8* fn = alloc_printf("%s/%s", dir, de->d_name); + s32 fd; + + if (lstat(fn, &st) || access(fn, R_OK)) + PFATAL("Unable to access '%s'", fn); + + /* This also takes care of . and .. */ + if (!S_ISREG(st.st_mode) || !st.st_size) { + + ck_free(fn); + continue; + + } + + if (st.st_size > MAX_DICT_FILE) + FATAL("Extra '%s' is too big (%s, limit is %s)", fn, + DMS(st.st_size), DMS(MAX_DICT_FILE)); + + if (min_len > st.st_size) min_len = st.st_size; + if (max_len < st.st_size) max_len = st.st_size; + + extras = ck_realloc_block(extras, (extras_cnt + 1) * + sizeof(struct extra_data)); + + extras[extras_cnt].data = ck_alloc(st.st_size); + extras[extras_cnt].len = st.st_size; + + fd = open(fn, O_RDONLY); + + if (fd < 0) PFATAL("Unable to open '%s'", fn); + + ck_read(fd, extras[extras_cnt].data, st.st_size, fn); + + close(fd); + ck_free(fn); + + extras_cnt++; + + } + + closedir(d); + +check_and_sort: + + if (!extras_cnt) FATAL("No usable files in '%s'", dir); + + qsort(extras, extras_cnt, sizeof(struct extra_data), compare_extras_len); + + OKF("Loaded %u extra tokens, size range %s to %s.", extras_cnt, + DMS(min_len), DMS(max_len)); + + if (max_len > 32) + WARNF("Some tokens are relatively large (%s) - consider trimming.", + DMS(max_len)); + + if (extras_cnt > MAX_DET_EXTRAS) + WARNF("More than %u tokens - will use them probabilistically.", + MAX_DET_EXTRAS); + +} + + + + +/* Helper function for maybe_add_auto() */ + +static inline u8 memcmp_nocase(u8* m1, u8* m2, u32 len) { + + while (len--) if (tolower(*(m1++)) ^ tolower(*(m2++))) return 1; + return 0; + +} + + +/* Maybe add automatic extra. */ + +static void maybe_add_auto(u8* mem, u32 len) { + + u32 i; + + /* Allow users to specify that they don't want auto dictionaries. */ + + if (!MAX_AUTO_EXTRAS || !USE_AUTO_EXTRAS) return; + + /* Skip runs of identical bytes. */ + + for (i = 1; i < len; i++) + if (mem[0] ^ mem[i]) break; + + if (i == len) return; + + /* Reject builtin interesting values. */ + + if (len == 2) { + + i = sizeof(interesting_16) >> 1; + + while (i--) + if (*((u16*)mem) == interesting_16[i] || + *((u16*)mem) == SWAP16(interesting_16[i])) return; + + } + + if (len == 4) { + + i = sizeof(interesting_32) >> 2; + + while (i--) + if (*((u32*)mem) == interesting_32[i] || + *((u32*)mem) == SWAP32(interesting_32[i])) return; + + } + + /* Reject anything that matches existing extras. Do a case-insensitive + match. We optimize by exploiting the fact that extras[] are sorted + by size. */ + + for (i = 0; i < extras_cnt; i++) + if (extras[i].len >= len) break; + + for (; i < extras_cnt && extras[i].len == len; i++) + if (!memcmp_nocase(extras[i].data, mem, len)) return; + + /* Last but not least, check a_extras[] for matches. There are no + guarantees of a particular sort order. */ + + auto_changed = 1; + + for (i = 0; i < a_extras_cnt; i++) { + + if (a_extras[i].len == len && !memcmp_nocase(a_extras[i].data, mem, len)) { + + a_extras[i].hit_cnt++; + goto sort_a_extras; + + } + + } + + /* At this point, looks like we're dealing with a new entry. So, let's + append it if we have room. Otherwise, let's randomly evict some other + entry from the bottom half of the list. */ + + if (a_extras_cnt < MAX_AUTO_EXTRAS) { + + a_extras = ck_realloc_block(a_extras, (a_extras_cnt + 1) * + sizeof(struct extra_data)); + + a_extras[a_extras_cnt].data = ck_memdup(mem, len); + a_extras[a_extras_cnt].len = len; + a_extras_cnt++; + + } else { + + i = MAX_AUTO_EXTRAS / 2 + + UR((MAX_AUTO_EXTRAS + 1) / 2); + + ck_free(a_extras[i].data); + + a_extras[i].data = ck_memdup(mem, len); + a_extras[i].len = len; + a_extras[i].hit_cnt = 0; + + } + +sort_a_extras: + + /* First, sort all auto extras by use count, descending order. */ + + qsort(a_extras, a_extras_cnt, sizeof(struct extra_data), + compare_extras_use_d); + + /* Then, sort the top USE_AUTO_EXTRAS entries by size. */ + + qsort(a_extras, MIN(USE_AUTO_EXTRAS, a_extras_cnt), + sizeof(struct extra_data), compare_extras_len); + +} + + +/* Save automatically generated extras. */ + +static void save_auto(void) { + + u32 i; + + if (!auto_changed) return; + auto_changed = 0; + + for (i = 0; i < MIN(USE_AUTO_EXTRAS, a_extras_cnt); i++) { + + u8* fn = alloc_printf("%s/queue/.state/auto_extras/auto_%06u", out_dir, i); + s32 fd; + + fd = open(fn, O_WRONLY | O_CREAT | O_TRUNC, 0600); + + if (fd < 0) PFATAL("Unable to create '%s'", fn); + + ck_write(fd, a_extras[i].data, a_extras[i].len, fn); + + close(fd); + ck_free(fn); + + } + +} + + +/* Load automatically generated extras. */ + +static void load_auto(void) { + + u32 i; + + for (i = 0; i < USE_AUTO_EXTRAS; i++) { + + u8 tmp[MAX_AUTO_EXTRA + 1]; + u8* fn = alloc_printf("%s/.state/auto_extras/auto_%06u", in_dir, i); + s32 fd, len; + + fd = open(fn, O_RDONLY, 0600); + + if (fd < 0) { + + if (errno != ENOENT) PFATAL("Unable to open '%s'", fn); + ck_free(fn); + break; + + } + + /* We read one byte more to cheaply detect tokens that are too + long (and skip them). */ + + len = read(fd, tmp, MAX_AUTO_EXTRA + 1); + + if (len < 0) PFATAL("Unable to read from '%s'", fn); + + if (len >= MIN_AUTO_EXTRA && len <= MAX_AUTO_EXTRA) + maybe_add_auto(tmp, len); + + close(fd); + ck_free(fn); + + } + + if (i) OKF("Loaded %u auto-discovered dictionary tokens.", i); + else OKF("No auto-generated dictionary tokens to reuse."); + +} + + +/* Destroy extras. */ + +static void destroy_extras(void) { + + u32 i; + + for (i = 0; i < extras_cnt; i++) + ck_free(extras[i].data); + + ck_free(extras); + + for (i = 0; i < a_extras_cnt; i++) + ck_free(a_extras[i].data); + + ck_free(a_extras); + +} + + +/* Spin up fork server (instrumented mode only). The idea is explained here: + + http://lcamtuf.blogspot.com/2014/10/fuzzing-binaries-without-execve.html + + In essence, the instrumentation allows us to skip execve(), and just keep + cloning a stopped child. So, we just execute once, and then send commands + through a pipe. The other part of this logic is in afl-as.h. */ + +EXP_ST void init_forkserver(char** argv) { + + static struct itimerval it; + int st_pipe[2], ctl_pipe[2]; + int status; + s32 rlen; + + ACTF("Spinning up the fork server..."); + + if (pipe(st_pipe) || pipe(ctl_pipe)) PFATAL("pipe() failed"); + + forksrv_pid = fork(); + + if (forksrv_pid < 0) PFATAL("fork() failed"); + + if (!forksrv_pid) { + + struct rlimit r; + + /* Umpf. On OpenBSD, the default fd limit for root users is set to + soft 128. Let's try to fix that... */ + + if (!getrlimit(RLIMIT_NOFILE, &r) && r.rlim_cur < FORKSRV_FD + 2) { + + r.rlim_cur = FORKSRV_FD + 2; + setrlimit(RLIMIT_NOFILE, &r); /* Ignore errors */ + + } + + if (mem_limit) { + + r.rlim_max = r.rlim_cur = ((rlim_t)mem_limit) << 20; + +#ifdef RLIMIT_AS + + setrlimit(RLIMIT_AS, &r); /* Ignore errors */ + +#else + + /* This takes care of OpenBSD, which doesn't have RLIMIT_AS, but + according to reliable sources, RLIMIT_DATA covers anonymous + maps - so we should be getting good protection against OOM bugs. */ + + setrlimit(RLIMIT_DATA, &r); /* Ignore errors */ + +#endif /* ^RLIMIT_AS */ + + + } + + /* Dumping cores is slow and can lead to anomalies if SIGKILL is delivered + before the dump is complete. */ + + r.rlim_max = r.rlim_cur = 0; + + setrlimit(RLIMIT_CORE, &r); /* Ignore errors */ + + /* Isolate the process and configure standard descriptors. If out_file is + specified, stdin is /dev/null; otherwise, out_fd is cloned instead. */ + + setsid(); + + dup2(dev_null_fd, 1); + dup2(dev_null_fd, 2); + + if (out_file) { + + dup2(dev_null_fd, 0); + + } else { + + dup2(out_fd, 0); + close(out_fd); + + } + + /* Set up control and status pipes, close the unneeded original fds. */ + + if (dup2(ctl_pipe[0], FORKSRV_FD) < 0) PFATAL("dup2() failed"); + if (dup2(st_pipe[1], FORKSRV_FD + 1) < 0) PFATAL("dup2() failed"); + + close(ctl_pipe[0]); + close(ctl_pipe[1]); + close(st_pipe[0]); + close(st_pipe[1]); + + close(out_dir_fd); + close(dev_null_fd); + close(dev_urandom_fd); + close(fileno(plot_file)); + + /* This should improve performance a bit, since it stops the linker from + doing extra work post-fork(). */ + + if (!getenv("LD_BIND_LAZY")) setenv("LD_BIND_NOW", "1", 0); + + /* Set sane defaults for ASAN if nothing else specified. */ + + setenv("ASAN_OPTIONS", "abort_on_error=1:" + "detect_leaks=0:" + "symbolize=0:" + "allocator_may_return_null=1", 0); + + /* MSAN is tricky, because it doesn't support abort_on_error=1 at this + point. So, we do this in a very hacky way. */ + + setenv("MSAN_OPTIONS", "exit_code=" STRINGIFY(MSAN_ERROR) ":" + "symbolize=0:" + "abort_on_error=1:" + "allocator_may_return_null=1:" + "msan_track_origins=0", 0); + + execv(target_path, argv); + + /* Use a distinctive bitmap signature to tell the parent about execv() + falling through. */ + + *(u32*)trace_bits = EXEC_FAIL_SIG; + exit(0); + + } + + /* Close the unneeded endpoints. */ + + close(ctl_pipe[0]); + close(st_pipe[1]); + + fsrv_ctl_fd = ctl_pipe[1]; + fsrv_st_fd = st_pipe[0]; + + /* Wait for the fork server to come up, but don't wait too long. */ + + it.it_value.tv_sec = ((exec_tmout * FORK_WAIT_MULT) / 1000); + it.it_value.tv_usec = ((exec_tmout * FORK_WAIT_MULT) % 1000) * 1000; + + setitimer(ITIMER_REAL, &it, NULL); + + rlen = read(fsrv_st_fd, &status, 4); + + it.it_value.tv_sec = 0; + it.it_value.tv_usec = 0; + + setitimer(ITIMER_REAL, &it, NULL); + + /* If we have a four-byte "hello" message from the server, we're all set. + Otherwise, try to figure out what went wrong. */ + + if (rlen == 4) { + OKF("All right - fork server is up."); + return; + } + + if (child_timed_out) + FATAL("Timeout while initializing fork server (adjusting -t may help)"); + + if (waitpid(forksrv_pid, &status, 0) <= 0) + PFATAL("waitpid() failed"); + + if (WIFSIGNALED(status)) { + + if (mem_limit && mem_limit < 500 && uses_asan) { + + SAYF("\n" cLRD "[-] " cRST + "Whoops, the target binary crashed suddenly, before receiving any input\n" + " from the fuzzer! Since it seems to be built with ASAN and you have a\n" + " restrictive memory limit configured, this is expected; please read\n" + " %s/notes_for_asan.txt for help.\n", doc_path); + + } else if (!mem_limit) { + + SAYF("\n" cLRD "[-] " cRST + "Whoops, the target binary crashed suddenly, before receiving any input\n" + " from the fuzzer! There are several probable explanations:\n\n" + + " - The binary is just buggy and explodes entirely on its own. If so, you\n" + " need to fix the underlying problem or find a better replacement.\n\n" + +#ifdef __APPLE__ + + " - On MacOS X, the semantics of fork() syscalls are non-standard and may\n" + " break afl-fuzz performance optimizations when running platform-specific\n" + " targets. To fix this, set AFL_NO_FORKSRV=1 in the environment.\n\n" + +#endif /* __APPLE__ */ + + " - Less likely, there is a horrible bug in the fuzzer. If other options\n" + " fail, poke for troubleshooting tips.\n"); + + } else { + + SAYF("\n" cLRD "[-] " cRST + "Whoops, the target binary crashed suddenly, before receiving any input\n" + " from the fuzzer! There are several probable explanations:\n\n" + + " - The current memory limit (%s) is too restrictive, causing the\n" + " target to hit an OOM condition in the dynamic linker. Try bumping up\n" + " the limit with the -m setting in the command line. A simple way confirm\n" + " this diagnosis would be:\n\n" + +#ifdef RLIMIT_AS + " ( ulimit -Sv $[%llu << 10]; /path/to/fuzzed_app )\n\n" +#else + " ( ulimit -Sd $[%llu << 10]; /path/to/fuzzed_app )\n\n" +#endif /* ^RLIMIT_AS */ + + " Tip: you can use http://jwilk.net/software/recidivm to quickly\n" + " estimate the required amount of virtual memory for the binary.\n\n" + + " - The binary is just buggy and explodes entirely on its own. If so, you\n" + " need to fix the underlying problem or find a better replacement.\n\n" + +#ifdef __APPLE__ + + " - On MacOS X, the semantics of fork() syscalls are non-standard and may\n" + " break afl-fuzz performance optimizations when running platform-specific\n" + " targets. To fix this, set AFL_NO_FORKSRV=1 in the environment.\n\n" + +#endif /* __APPLE__ */ + + " - Less likely, there is a horrible bug in the fuzzer. If other options\n" + " fail, poke for troubleshooting tips.\n", + DMS(mem_limit << 20), mem_limit - 1); + + } + + FATAL("Fork server crashed with signal %d", WTERMSIG(status)); + + } + + if (*(u32*)trace_bits == EXEC_FAIL_SIG) + FATAL("Unable to execute target application ('%s')", argv[0]); + + if (mem_limit && mem_limit < 500 && uses_asan) { + + SAYF("\n" cLRD "[-] " cRST + "Hmm, looks like the target binary terminated before we could complete a\n" + " handshake with the injected code. Since it seems to be built with ASAN and\n" + " you have a restrictive memory limit configured, this is expected; please\n" + " read %s/notes_for_asan.txt for help.\n", doc_path); + + } else if (!mem_limit) { + + SAYF("\n" cLRD "[-] " cRST + "Hmm, looks like the target binary terminated before we could complete a\n" + " handshake with the injected code. Perhaps there is a horrible bug in the\n" + " fuzzer. Poke for troubleshooting tips.\n"); + + } else { + + SAYF("\n" cLRD "[-] " cRST + "Hmm, looks like the target binary terminated before we could complete a\n" + " handshake with the injected code. There are %s probable explanations:\n\n" + + "%s" + " - The current memory limit (%s) is too restrictive, causing an OOM\n" + " fault in the dynamic linker. This can be fixed with the -m option. A\n" + " simple way to confirm the diagnosis may be:\n\n" + +#ifdef RLIMIT_AS + " ( ulimit -Sv $[%llu << 10]; /path/to/fuzzed_app )\n\n" +#else + " ( ulimit -Sd $[%llu << 10]; /path/to/fuzzed_app )\n\n" +#endif /* ^RLIMIT_AS */ + + " Tip: you can use http://jwilk.net/software/recidivm to quickly\n" + " estimate the required amount of virtual memory for the binary.\n\n" + + " - Less likely, there is a horrible bug in the fuzzer. If other options\n" + " fail, poke for troubleshooting tips.\n", + getenv(DEFER_ENV_VAR) ? "three" : "two", + getenv(DEFER_ENV_VAR) ? + " - You are using deferred forkserver, but __AFL_INIT() is never\n" + " reached before the program terminates.\n\n" : "", + DMS(mem_limit << 20), mem_limit - 1); + + } + + FATAL("Fork server handshake failed"); + +} + + +/* Execute target application, monitoring for timeouts. Return status + information. The called program will update trace_bits[]. */ + +static u8 run_target(char** argv, u32 timeout) { + + static struct itimerval it; + static u32 prev_timed_out = 0; + static u64 exec_ms = 0; + + int status = 0; + u32 tb4; + + child_timed_out = 0; + + /* After this memset, trace_bits[] are effectively volatile, so we + must prevent any earlier operations from venturing into that + territory. */ + + memset(trace_bits, 0, MAP_SIZE); + MEM_BARRIER(); + + /* If we're running in "dumb" mode, we can't rely on the fork server + logic compiled into the target program, so we will just keep calling + execve(). There is a bit of code duplication between here and + init_forkserver(), but c'est la vie. */ + + if (dumb_mode == 1 || no_forkserver) { + + child_pid = fork(); + + if (child_pid < 0) PFATAL("fork() failed"); + + if (!child_pid) { + + struct rlimit r; + + if (mem_limit) { + + r.rlim_max = r.rlim_cur = ((rlim_t)mem_limit) << 20; + +#ifdef RLIMIT_AS + + setrlimit(RLIMIT_AS, &r); /* Ignore errors */ + +#else + + setrlimit(RLIMIT_DATA, &r); /* Ignore errors */ + +#endif /* ^RLIMIT_AS */ + + } + + r.rlim_max = r.rlim_cur = 0; + + setrlimit(RLIMIT_CORE, &r); /* Ignore errors */ + + /* Isolate the process and configure standard descriptors. If out_file is + specified, stdin is /dev/null; otherwise, out_fd is cloned instead. */ + + setsid(); + + dup2(dev_null_fd, 1); + dup2(dev_null_fd, 2); + + if (out_file) { + + dup2(dev_null_fd, 0); + + } else { + + dup2(out_fd, 0); + close(out_fd); + + } + + /* On Linux, would be faster to use O_CLOEXEC. Maybe TODO. */ + + close(dev_null_fd); + close(out_dir_fd); + close(dev_urandom_fd); + close(fileno(plot_file)); + + /* Set sane defaults for ASAN if nothing else specified. */ + + setenv("ASAN_OPTIONS", "abort_on_error=1:" + "detect_leaks=0:" + "symbolize=0:" + "allocator_may_return_null=1", 0); + + setenv("MSAN_OPTIONS", "exit_code=" STRINGIFY(MSAN_ERROR) ":" + "symbolize=0:" + "msan_track_origins=0", 0); + + execv(target_path, argv); + + /* Use a distinctive bitmap value to tell the parent about execv() + falling through. */ + + *(u32*)trace_bits = EXEC_FAIL_SIG; + exit(0); + + } + + } else { + + s32 res; + + /* In non-dumb mode, we have the fork server up and running, so simply + tell it to have at it, and then read back PID. */ + + if ((res = write(fsrv_ctl_fd, &prev_timed_out, 4)) != 4) { + + if (stop_soon) return 0; + RPFATAL(res, "Unable to request new process from fork server (OOM?)"); + + } + + if ((res = read(fsrv_st_fd, &child_pid, 4)) != 4) { + + if (stop_soon) return 0; + RPFATAL(res, "Unable to request new process from fork server (OOM?)"); + + } + + if (child_pid <= 0) FATAL("Fork server is misbehaving (OOM?)"); + + } + + /* Configure timeout, as requested by user, then wait for child to terminate. */ + + it.it_value.tv_sec = (timeout / 1000); + it.it_value.tv_usec = (timeout % 1000) * 1000; + + setitimer(ITIMER_REAL, &it, NULL); + + /* The SIGALRM handler simply kills the child_pid and sets child_timed_out. */ + + if (dumb_mode == 1 || no_forkserver) { + + if (waitpid(child_pid, &status, 0) <= 0) PFATAL("waitpid() failed"); + + } else { + + s32 res; + + if ((res = read(fsrv_st_fd, &status, 4)) != 4) { + + if (stop_soon) return 0; + RPFATAL(res, "Unable to communicate with fork server (OOM?)"); + + } + + } + + if (!WIFSTOPPED(status)) child_pid = 0; + + getitimer(ITIMER_REAL, &it); + exec_ms = (u64) timeout - (it.it_value.tv_sec * 1000 + + it.it_value.tv_usec / 1000); + + it.it_value.tv_sec = 0; + it.it_value.tv_usec = 0; + + setitimer(ITIMER_REAL, &it, NULL); + + total_execs++; + + /* Any subsequent operations on trace_bits must not be moved by the + compiler below this point. Past this location, trace_bits[] behave + very normally and do not have to be treated as volatile. */ + + MEM_BARRIER(); + + tb4 = *(u32*)trace_bits; + +#ifdef WORD_SIZE_64 + classify_counts((u64*)trace_bits); +#else + classify_counts((u32*)trace_bits); +#endif /* ^WORD_SIZE_64 */ + + prev_timed_out = child_timed_out; + + /* Report outcome to caller. */ + + if (WIFSIGNALED(status) && !stop_soon) { + + kill_signal = WTERMSIG(status); + + if (child_timed_out && kill_signal == SIGKILL) return FAULT_TMOUT; + + return FAULT_CRASH; + + } + + /* A somewhat nasty hack for MSAN, which doesn't support abort_on_error and + must use a special exit code. */ + + if (uses_asan && WEXITSTATUS(status) == MSAN_ERROR) { + kill_signal = 0; + return FAULT_CRASH; + } + + if ((dumb_mode == 1 || no_forkserver) && tb4 == EXEC_FAIL_SIG) + return FAULT_ERROR; + + /* It makes sense to account for the slowest units only if the testcase was run + under the user defined timeout. */ + if (!(timeout > exec_tmout) && (slowest_exec_ms < exec_ms)) { + slowest_exec_ms = exec_ms; + } + + return FAULT_NONE; + +} + + +/* Write modified data to file for testing. If out_file is set, the old file + is unlinked and a new one is created. Otherwise, out_fd is rewound and + truncated. */ + +static void write_to_testcase(void* mem, u32 len) { + + s32 fd = out_fd; + + if (out_file) { + + unlink(out_file); /* Ignore errors. */ + + fd = open(out_file, O_WRONLY | O_CREAT | O_EXCL, 0600); + + if (fd < 0) PFATAL("Unable to create '%s'", out_file); + + } else lseek(fd, 0, SEEK_SET); + + ck_write(fd, mem, len, out_file); + + if (!out_file) { + + if (ftruncate(fd, len)) PFATAL("ftruncate() failed"); + lseek(fd, 0, SEEK_SET); + + } else close(fd); + +} + + +/* The same, but with an adjustable gap. Used for trimming. */ + +static void write_with_gap(void* mem, u32 len, u32 skip_at, u32 skip_len) { + + s32 fd = out_fd; + u32 tail_len = len - skip_at - skip_len; + + if (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); + + } else lseek(fd, 0, SEEK_SET); + + if (skip_at) ck_write(fd, mem, skip_at, out_file); + + if (tail_len) ck_write(fd, mem + skip_at + skip_len, tail_len, out_file); + + if (!out_file) { + + if (ftruncate(fd, len - skip_len)) PFATAL("ftruncate() failed"); + lseek(fd, 0, SEEK_SET); + + } else close(fd); + +} + + +static void show_stats(void); + +/* Calibrate a new test case. This is done when processing the input directory + to warn about flaky or otherwise problematic test cases early on; and when + new paths are discovered to detect variable behavior and so on. */ + +static u8 calibrate_case(char** argv, struct queue_entry* q, u8* use_mem, + u32 handicap, u8 from_queue) { + + static u8 first_trace[MAP_SIZE]; + + u8 fault = 0, new_bits = 0, var_detected = 0, hnb = 0, + first_run = (q->exec_cksum == 0); + + u64 start_us, stop_us; + + s32 old_sc = stage_cur, old_sm = stage_max; + u32 use_tmout = exec_tmout; + u8* old_sn = stage_name; + + /* Be a bit more generous about timeouts when resuming sessions, or when + trying to calibrate already-added finds. This helps avoid trouble due + to intermittent latency. */ + + if (!from_queue || resuming_fuzz) + use_tmout = MAX(exec_tmout + CAL_TMOUT_ADD, + exec_tmout * CAL_TMOUT_PERC / 100); + + q->cal_failed++; + + stage_name = "calibration"; + stage_max = fast_cal ? 3 : CAL_CYCLES; + + /* Make sure the forkserver is up before we do anything, and let's not + count its spin-up time toward binary calibration. */ + + if (dumb_mode != 1 && !no_forkserver && !forksrv_pid) + init_forkserver(argv); + + if (q->exec_cksum) { + + memcpy(first_trace, trace_bits, MAP_SIZE); + hnb = has_new_bits(virgin_bits); + if (hnb > new_bits) new_bits = hnb; + + } + + start_us = get_cur_time_us(); + + for (stage_cur = 0; stage_cur < stage_max; stage_cur++) { + + u32 cksum; + + if (!first_run && !(stage_cur % stats_update_freq)) show_stats(); + + write_to_testcase(use_mem, q->len); + + fault = run_target(argv, use_tmout); + + /* stop_soon is set by the handler for Ctrl+C. When it's pressed, + we want to bail out quickly. */ + + if (stop_soon || fault != crash_mode) goto abort_calibration; + + if (!dumb_mode && !stage_cur && !count_bytes(trace_bits)) { + fault = FAULT_NOINST; + goto abort_calibration; + } + + cksum = hash32(trace_bits, MAP_SIZE, HASH_CONST); + + if (q->exec_cksum != cksum) { + + hnb = has_new_bits(virgin_bits); + if (hnb > new_bits) new_bits = hnb; + + if (q->exec_cksum) { + + u32 i; + + for (i = 0; i < MAP_SIZE; i++) { + + if (!var_bytes[i] && first_trace[i] != trace_bits[i]) { + + var_bytes[i] = 1; + stage_max = CAL_CYCLES_LONG; + + } + + } + + var_detected = 1; + + } else { + + q->exec_cksum = cksum; + memcpy(first_trace, trace_bits, MAP_SIZE); + + } + + } + + } + + stop_us = get_cur_time_us(); + + total_cal_us += stop_us - start_us; + total_cal_cycles += stage_max; + + /* OK, let's collect some stats about the performance of this test case. + This is used for fuzzing air time calculations in calculate_score(). */ + + q->exec_us = (stop_us - start_us) / stage_max; + q->bitmap_size = count_bytes(trace_bits); + q->handicap = handicap; + q->cal_failed = 0; + + total_bitmap_size += q->bitmap_size; + total_bitmap_entries++; + + update_bitmap_score(q); + + /* If this case didn't result in new output from the instrumentation, tell + parent. This is a non-critical problem, but something to warn the user + about. */ + + if (!dumb_mode && first_run && !fault && !new_bits) fault = FAULT_NOBITS; + +abort_calibration: + + if (new_bits == 2 && !q->has_new_cov) { + q->has_new_cov = 1; + queued_with_cov++; + } + + /* Mark variable paths. */ + + if (var_detected) { + + var_byte_count = count_bytes(var_bytes); + + if (!q->var_behavior) { + mark_as_variable(q); + queued_variable++; + } + + } + + stage_name = old_sn; + stage_cur = old_sc; + stage_max = old_sm; + + if (!first_run) show_stats(); + + return fault; + +} + + +/* Examine map coverage. Called once, for first test case. */ + +static void check_map_coverage(void) { + + u32 i; + + if (count_bytes(trace_bits) < 100) return; + + for (i = (1 << (MAP_SIZE_POW2 - 1)); i < MAP_SIZE; i++) + if (trace_bits[i]) return; + + WARNF("Recompile binary with newer version of afl to improve coverage!"); + +} + + +/* 执行所有测试用例的干运行,以确认应用程序按预期工作。 + 这仅在初始输入时执行,并且只执行一次。 */ +static void perform_dry_run(char** argv) { + + struct queue_entry* q = queue; // 获取队列中的第一个测试用例 + u32 cal_failures = 0; // 记录校准失败的次数 + u8* skip_crashes = getenv("AFL_SKIP_CRASHES"); // 获取环境变量AFL_SKIP_CRASHES的值 + + while (q) { // 遍历队列中的所有测试用例 + + u8* use_mem; // 用于存储读取的文件内容 + u8 res; // 存储校准结果 + s32 fd; // 文件描述符 + + u8* fn = strrchr(q->fname, '/') + 1; // 获取文件名(去掉路径部分) + + ACTF("Attempting dry run with '%s'...", fn); // 打印日志,表示正在尝试干运行 + + fd = open(q->fname, O_RDONLY); // 打开测试用例文件 + if (fd < 0) PFATAL("Unable to open '%s'", q->fname); // 如果打开失败,输出错误信息并退出 + + use_mem = ck_alloc_nozero(q->len); // 分配内存以存储文件内容 + + if (read(fd, use_mem, q->len) != q->len) // 读取文件内容 + FATAL("Short read from '%s'", q->fname); // 如果读取失败,输出错误信息并退出 + + close(fd); // 关闭文件 + + res = calibrate_case(argv, q, use_mem, 0, 1); // 校准测试用例 + ck_free(use_mem); // 释放内存 + + if (stop_soon) return; // 如果收到停止信号,立即返回 + + if (res == crash_mode || res == FAULT_NOBITS) // 如果校准结果是崩溃模式或无位图 + SAYF(cGRA " len = %u, map size = %u, exec speed = %llu us\n" cRST, + q->len, q->bitmap_size, q->exec_us); // 打印测试用例的长度、位图大小和执行时间 + + switch (res) { // 根据校准结果进行处理 + + case FAULT_NONE: // 如果校准结果正常 + + if (q == queue) check_map_coverage(); // 如果是队列中的第一个测试用例,检查位图覆盖率 + + if (crash_mode) FATAL("Test case '%s' does *NOT* crash", fn); // 如果是崩溃模式,但测试用例没有崩溃,输出错误信息并退出 + + break; + + case FAULT_TMOUT: // 如果校准结果超时 + + if (timeout_given) { // 如果设置了超时 + + /* 命令行中的 -t nn+ 语法将 timeout_given 设置为 '2', + 并指示 afl-fuzz 容忍但跳过超时的队列条目。 */ + + if (timeout_given > 1) { // 如果 timeout_given 大于 1 + WARNF("Test case results in a timeout (skipping)"); // 输出警告信息 + q->cal_failed = CAL_CHANCES; // 标记测试用例为校准失败 + cal_failures++; // 增加校准失败计数 + break; + } + + SAYF("\n" cLRD "[-] " cRST + "The program took more than %u ms to process one of the initial test cases.\n" + " Usually, the right thing to do is to relax the -t option - or to delete it\n" + " altogether and allow the fuzzer to auto-calibrate. That said, if you know\n" + " what you are doing and want to simply skip the unruly test cases, append\n" + " '+' at the end of the value passed to -t ('-t %u+').\n", exec_tmout, + exec_tmout); // 输出建议信息 + + FATAL("Test case '%s' results in a timeout", fn); // 输出错误信息并退出 + + } else { // 如果没有设置超时 + + SAYF("\n" cLRD "[-] " cRST + "The program took more than %u ms to process one of the initial test cases.\n" + " This is bad news; raising the limit with the -t option is possible, but\n" + " will probably make the fuzzing process extremely slow.\n\n" + + " If this test case is just a fluke, the other option is to just avoid it\n" + " altogether, and find one that is less of a CPU hog.\n", exec_tmout); // 输出建议信息 + + FATAL("Test case '%s' results in a timeout", fn); // 输出错误信息并退出 + + } + + case FAULT_CRASH: // 如果校准结果崩溃 + + if (crash_mode) break; // 如果是崩溃模式,直接跳过 + + if (skip_crashes) { // 如果设置了跳过崩溃 + WARNF("Test case results in a crash (skipping)"); // 输出警告信息 + q->cal_failed = CAL_CHANCES; // 标记测试用例为校准失败 + cal_failures++; // 增加校准失败计数 + break; + } + + if (mem_limit) { // 如果设置了内存限制 + + SAYF("\n" cLRD "[-] " cRST + "Oops, the program crashed with one of the test cases provided. There are\n" + " several possible explanations:\n\n" + + " - The test case causes known crashes under normal working conditions. If\n" + " so, please remove it. The fuzzer should be seeded with interesting\n" + " inputs - but not ones that cause an outright crash.\n\n" + + " - The current memory limit (%s) is too low for this program, causing\n" + " it to die due to OOM when parsing valid files. To fix this, try\n" + " bumping it up with the -m setting in the command line. If in doubt,\n" + " try something along the lines of:\n\n" + +#ifdef RLIMIT_AS + " ( ulimit -Sv $[%llu << 10]; /path/to/binary [...] for troubleshooting tips.\n", + DMS(mem_limit << 20), mem_limit - 1, doc_path); // 输出建议信息 + + } else { // 如果没有设置内存限制 + + SAYF("\n" cLRD "[-] " cRST + "Oops, the program crashed with one of the test cases provided. There are\n" + " several possible explanations:\n\n" + + " - The test case causes known crashes under normal working conditions. If\n" + " so, please remove it. The fuzzer should be seeded with interesting\n" + " inputs - but not ones that cause an outright crash.\n\n" + +#ifdef __APPLE__ + + " - On MacOS X, the semantics of fork() syscalls are non-standard and may\n" + " break afl-fuzz performance optimizations when running platform-specific\n" + " binaries. To fix this, set AFL_NO_FORKSRV=1 in the environment.\n\n" + +#endif /* __APPLE__ */ + + " - Least likely, there is a horrible bug in the fuzzer. If other options\n" + " fail, poke for troubleshooting tips.\n"); // 输出建议信息 + + } + + FATAL("Test case '%s' results in a crash", fn); // 输出错误信息并退出 + + case FAULT_ERROR: // 如果校准结果错误 + + FATAL("Unable to execute target application ('%s')", argv[0]); // 输出错误信息并退出 + + case FAULT_NOINST: // 如果校准结果无插桩 + + FATAL("No instrumentation detected"); // 输出错误信息并退出 + + case FAULT_NOBITS: // 如果校准结果无位图 + + useless_at_start++; // 增加无用测试用例计数 + + if (!in_bitmap && !shuffle_queue) + WARNF("No new instrumentation output, test case may be useless."); // 输出警告信息 + + break; + + } + + if (q->var_behavior) WARNF("Instrumentation output varies across runs."); // 如果测试用例的行为不一致,输出警告信息 + + q = q->next; // 移动到下一个测试用例 + + } + + if (cal_failures) { // 如果有校准失败 + + if (cal_failures == queued_paths) // 如果所有测试用例都失败 + FATAL("All test cases time out%s, giving up!", + skip_crashes ? " or crash" : ""); // 输出错误信息并退出 + + WARNF("Skipped %u test cases (%0.02f%%) due to timeouts%s.", cal_failures, + ((double)cal_failures) * 100 / queued_paths, + skip_crashes ? " or crashes" : ""); // 输出警告信息 + + if (cal_failures * 5 > queued_paths) // 如果失败率过高 + WARNF(cLRD "High percentage of rejected test cases, check settings!"); // 输出警告信息 + + } + + OKF("All test cases processed."); // 输出成功信息 + +} + + +/* 辅助函数:如果可能,使用 link(),否则使用 copy()。 */ +static void link_or_copy(u8* old_path, u8* new_path) { + + s32 i = link(old_path, new_path); // 尝试创建硬链接 + s32 sfd, dfd; + u8* tmp; + + if (!i) return; // 如果成功创建硬链接,直接返回 + + sfd = open(old_path, O_RDONLY); // 打开源文件 + if (sfd < 0) PFATAL("Unable to open '%s'", old_path); // 如果打开失败,输出错误信息并退出 + + dfd = open(new_path, O_WRONLY | O_CREAT | O_EXCL, 0600); // 创建目标文件 + if (dfd < 0) PFATAL("Unable to create '%s'", new_path); // 如果创建失败,输出错误信息并退出 + + tmp = ck_alloc(64 * 1024); // 分配临时缓冲区 + + while ((i = read(sfd, tmp, 64 * 1024)) > 0) // 读取源文件内容 + ck_write(dfd, tmp, i, new_path); // 写入目标文件 + + if (i < 0) PFATAL("read() failed"); // 如果读取失败,输出错误信息并退出 + + ck_free(tmp); // 释放临时缓冲区 + close(sfd); // 关闭源文件 + close(dfd); // 关闭目标文件 + +} + + + +static void nuke_resume_dir(void); + +/* 创建输入测试用例的硬链接,在输出目录中选择合适的名称并进行相应的重命名。 */ + +static void pivot_inputs(void) { + + struct queue_entry* q = queue; + u32 id = 0; + + ACTF("Creating hard links for all input files..."); + + while (q) { + + u8 *nfn, *rsl = strrchr(q->fname, '/'); + u32 orig_id; + + if (!rsl) rsl = q->fname; else rsl++; + + /* 如果原始文件名符合语法并且记录的 ID 与我们将分配的相符,则使用原始文件名。 + 这对于恢复模糊测试运行很有价值。 */ + +#ifndef SIMPLE_FILES +# define CASE_PREFIX "id:" +#else +# define CASE_PREFIX "id_" +#endif /* ^!SIMPLE_FILES */ + + if (!strncmp(rsl, CASE_PREFIX, 3) && + sscanf(rsl + 3, "%06u", &orig_id) == 1 && orig_id == id) { + + u8* src_str; + u32 src_id; + + resuming_fuzz = 1; + nfn = alloc_printf("%s/queue/%s", out_dir, rsl); + + /* 既然如此,我们还尝试找到父项并确定此条目的适当深度。 */ + + src_str = strchr(rsl + 3, ':'); + + if (src_str && sscanf(src_str + 1, "%06u", &src_id) == 1) { + + struct queue_entry* s = queue; + while (src_id-- && s) s = s->next; + if (s) q->depth = s->depth + 1; + + if (max_depth < q->depth) max_depth = q->depth; + + } + + } else { + + /* 没有找到 - 发明一个新名称,同时捕捉原始名称作为子字符串。 */ + +#ifndef SIMPLE_FILES + + u8* use_name = strstr(rsl, ",orig:"); + + if (use_name) use_name += 6; else use_name = rsl; + nfn = alloc_printf("%s/queue/id:%06u,orig:%s", out_dir, id, use_name); + +#else + + nfn = alloc_printf("%s/queue/id_%06u", out_dir, id); + +#endif /* ^!SIMPLE_FILES */ + + } + + /* 切换到新的队列条目。 */ + + link_or_copy(q->fname, nfn); + ck_free(q->fname); + q->fname = nfn; + + /* 确保传递的通过检测值也被保留。 */ + + if (q->passed_det) mark_as_det_done(q); + + q = q->next; + id++; + + } + + if (in_place_resume) nuke_resume_dir(); + +} + + +#ifndef SIMPLE_FILES + +/* 为新的测试用例构造文件名,捕获导致其发现的操作。使用静态缓冲区。 */ + +static u8* describe_op(u8 hnb) { + + static u8 ret[256]; + + if (syncing_party) { + + sprintf(ret, "sync:%s,src:%06u", syncing_party, syncing_case); + + } else { + + sprintf(ret, "src:%06u", current_entry); + + if (splicing_with >= 0) + sprintf(ret + strlen(ret), "+%06u", splicing_with); + + sprintf(ret + strlen(ret), ",op:%s", stage_short); + + if (stage_cur_byte >= 0) { + + sprintf(ret + strlen(ret), ",pos:%u", stage_cur_byte); + + if (stage_val_type != STAGE_VAL_NONE) + sprintf(ret + strlen(ret), ",val:%s%+d", + (stage_val_type == STAGE_VAL_BE) ? "be:" : "", + stage_cur_val); + + } else sprintf(ret + strlen(ret), ",rep:%u", stage_cur_val); + + } + + if (hnb == 2) strcat(ret, ",+cov"); + + return ret; + +} + +#endif /* !SIMPLE_FILES */ + + +/* 写入陪伴崩溃目录的消息 :-) */ + +static void write_crash_readme(void) { + + u8* fn = alloc_printf("%s/crashes/README.txt", out_dir); + s32 fd; + FILE* f; + + fd = open(fn, O_WRONLY | O_CREAT | O_EXCL, 0600); + ck_free(fn); + + /* 此处出错不会导致程序崩溃 - 这将是不礼貌的。 */ + + if (fd < 0) return; + + f = fdopen(fd, "w"); + + if (!f) { + close(fd); + return; + } + + fprintf(f, "Command line used to find this crash:\n\n" + + "%s\n\n" + + "If you can't reproduce a bug outside of afl-fuzz, be sure to set the same\n" + "memory limit. The limit used for this fuzzing session was %s.\n\n" + + "Need a tool to minimize test cases before investigating the crashes or sending\n" + "them to a vendor? Check out the afl-tmin that comes with the fuzzer!\n\n" + + "Found any cool bugs in open-source tools using afl-fuzz? If yes, please drop\n" + "me a mail at once the issues are fixed - I'd love to\n" + "add your finds to the gallery at:\n\n" + + " http://lcamtuf.coredump.cx/afl/\n\n" + + "Thanks :-)\n", + + orig_cmdline, DMS(mem_limit << 20)); /* 忽略错误 */ + + fclose(f); + +} + + +/* 检查在常规模糊测试期间 execve() 的结果是否有趣, + 如果有,则保存或排队输入测试用例以进行进一步分析。如果保存了条目,返回 1,否则返回 0。 */ + +static u8 save_if_interesting(char** argv, void* mem, u32 len, u8 fault) { + + u8 *fn = ""; + u8 hnb; + s32 fd; + u8 keeping = 0, res; + + if (fault == crash_mode) { + + /* 仅当映射中有新位时才保留,将其添加到队列以供 + 未来的模糊测试等。 */ + + if (!(hnb = has_new_bits(virgin_bits))) { + if (crash_mode) total_crashes++; + return 0; + } + +#ifndef SIMPLE_FILES + + fn = alloc_printf("%s/queue/id:%06u,%s", out_dir, queued_paths, + describe_op(hnb)); + +#else + + fn = alloc_printf("%s/queue/id_%06u", out_dir, queued_paths); + +#endif /* ^!SIMPLE_FILES */ + + add_to_queue(fn, len, 0); + + if (hnb == 2) { + queue_top->has_new_cov = 1; + queued_with_cov++; + } + + queue_top->exec_cksum = hash32(trace_bits, MAP_SIZE, HASH_CONST); + + /* 尝试在线标定;这也会在成功时调用 update_bitmap_score()。 */ + + res = calibrate_case(argv, queue_top, mem, queue_cycle - 1, 0); + + if (res == FAULT_ERROR) + FATAL("Unable to execute target application"); + + fd = open(fn, O_WRONLY | O_CREAT | O_EXCL, 0600); + if (fd < 0) PFATAL("Unable to create '%s'", fn); + ck_write(fd, mem, len, fn); + close(fd); + + keeping = 1; + + } + + switch (fault) { + + case FAULT_TMOUT: + + /* 超时并不是非常有趣,但我们仍然有义务保留 + 一些样本。我们使用挂起特定位图中 + 新位的存在作为独特性的信号。在“愚蠢”模式下,我们 + 直接保留所有内容。 */ + + total_tmouts++; + + if (unique_hangs >= KEEP_UNIQUE_HANG) return keeping; + + if (!dumb_mode) { + +#ifdef WORD_SIZE_64 + simplify_trace((u64*)trace_bits); +#else + simplify_trace((u32*)trace_bits); +#endif /* ^WORD_SIZE_64 */ + + if (!has_new_bits(virgin_tmout)) return keeping; + + } + + unique_tmouts++; + + /* 在保存之前,我们确保它是一个真正的挂起,通过 + 以更宽松的超时重新运行目标(除非默认超时 + 已经很宽松)。 */ + + if (exec_tmout < hang_tmout) { + + u8 new_fault; + write_to_testcase(mem, len); + new_fault = run_target(argv, hang_tmout); + + /* 一种角落情况,有用户报告造成:增加 + 超时实际会发现一个崩溃。确保我们不会丢弃它。 */ + + if (!stop_soon && new_fault == FAULT_CRASH) goto keep_as_crash; + + if (stop_soon || new_fault != FAULT_TMOUT) return keeping; + + } + +#ifndef SIMPLE_FILES + + fn = alloc_printf("%s/hangs/id:%06llu,%s", out_dir, + unique_hangs, describe_op(0)); + +#else + + fn = alloc_printf("%s/hangs/id_%06llu", out_dir, + unique_hangs); + +#endif /* ^!SIMPLE_FILES */ + + unique_hangs++; + + last_hang_time = get_cur_time(); + + break; + + case FAULT_CRASH: +keep_as_crash: + + /* This is handled in a manner roughly similar to timeouts, + except for slightly different limits and no need to re-run test + cases. */ + + total_crashes++; + + if (unique_crashes >= KEEP_UNIQUE_CRASH) return keeping; + + if (!dumb_mode) { + +#ifdef WORD_SIZE_64 + simplify_trace((u64*)trace_bits); +#else + simplify_trace((u32*)trace_bits); +#endif /* ^WORD_SIZE_64 */ + + if (!has_new_bits(virgin_crash)) return keeping; + + } + + if (!unique_crashes) write_crash_readme(); + +#ifndef SIMPLE_FILES + + fn = alloc_printf("%s/crashes/id:%06llu,sig:%02u,%s", out_dir, + unique_crashes, kill_signal, describe_op(0)); + +#else + + fn = alloc_printf("%s/crashes/id_%06llu_%02u", out_dir, unique_crashes, + kill_signal); + +#endif /* ^!SIMPLE_FILES */ + + unique_crashes++; + + last_crash_time = get_cur_time(); + last_crash_execs = total_execs; + + break; + + case FAULT_ERROR: FATAL("Unable to execute target application"); + + default: return keeping; + + } + + /* If we're here, we apparently want to save the crash or hang + test case, too. */ + + fd = open(fn, O_WRONLY | O_CREAT | O_EXCL, 0600); + if (fd < 0) PFATAL("Unable to create '%s'", fn); + ck_write(fd, mem, len, fn); + close(fd); + + ck_free(fn); + + return keeping; + +} + + +/* When resuming, try to find the queue position to start from. This makes sense + only when resuming, and when we can find the original fuzzer_stats. */ + +static u32 find_start_position(void) { + + static u8 tmp[4096]; /* Ought to be enough for anybody. */ + + u8 *fn, *off; + s32 fd, i; + u32 ret; + + if (!resuming_fuzz) return 0; + + if (in_place_resume) fn = alloc_printf("%s/fuzzer_stats", out_dir); + else fn = alloc_printf("%s/../fuzzer_stats", in_dir); + + fd = open(fn, O_RDONLY); + ck_free(fn); + + if (fd < 0) return 0; + + i = read(fd, tmp, sizeof(tmp) - 1); (void)i; /* Ignore errors */ + close(fd); + + off = strstr(tmp, "cur_path : "); + if (!off) return 0; + + ret = atoi(off + 20); + if (ret >= queued_paths) ret = 0; + return ret; + +} + + +/* The same, but for timeouts. The idea is that when resuming sessions without + -t given, we don't want to keep auto-scaling the timeout over and over + again to prevent it from growing due to random flukes. */ + +static void find_timeout(void) { + + static u8 tmp[4096]; /* Ought to be enough for anybody. */ + + u8 *fn, *off; + s32 fd, i; + u32 ret; + + if (!resuming_fuzz) return; + + if (in_place_resume) fn = alloc_printf("%s/fuzzer_stats", out_dir); + else fn = alloc_printf("%s/../fuzzer_stats", in_dir); + + fd = open(fn, O_RDONLY); + ck_free(fn); + + if (fd < 0) return; + + i = read(fd, tmp, sizeof(tmp) - 1); (void)i; /* Ignore errors */ + close(fd); + + off = strstr(tmp, "exec_timeout : "); + if (!off) return; + + ret = atoi(off + 20); + if (ret <= 4) return; + + exec_tmout = ret; + timeout_given = 3; + +} + + +/* Update stats file for unattended monitoring. */ + +static void write_stats_file(double bitmap_cvg, double stability, double eps) { + + static double last_bcvg, last_stab, last_eps; + static struct rusage usage; + + u8* fn = alloc_printf("%s/fuzzer_stats", out_dir); + s32 fd; + FILE* f; + + fd = open(fn, O_WRONLY | O_CREAT | O_TRUNC, 0600); + + if (fd < 0) PFATAL("Unable to create '%s'", fn); + + ck_free(fn); + + f = fdopen(fd, "w"); + + if (!f) PFATAL("fdopen() failed"); + + /* Keep last values in case we're called from another context + where exec/sec stats and such are not readily available. */ + + if (!bitmap_cvg && !stability && !eps) { + bitmap_cvg = last_bcvg; + stability = last_stab; + eps = last_eps; + } else { + last_bcvg = bitmap_cvg; + last_stab = stability; + last_eps = eps; + } + + fprintf(f, "start_time : %llu\n" + "last_update : %llu\n" + "fuzzer_pid : %u\n" + "cycles_done : %llu\n" + "execs_done : %llu\n" + "execs_per_sec : %0.02f\n" + "paths_total : %u\n" + "paths_favored : %u\n" + "paths_found : %u\n" + "paths_imported : %u\n" + "max_depth : %u\n" + "cur_path : %u\n" /* Must match find_start_position() */ + "pending_favs : %u\n" + "pending_total : %u\n" + "variable_paths : %u\n" + "stability : %0.02f%%\n" + "bitmap_cvg : %0.02f%%\n" + "unique_crashes : %llu\n" + "unique_hangs : %llu\n" + "last_path : %llu\n" + "last_crash : %llu\n" + "last_hang : %llu\n" + "execs_since_crash : %llu\n" + "exec_timeout : %u\n" /* Must match find_timeout() */ + "afl_banner : %s\n" + "afl_version : " VERSION "\n" + "target_mode : %s%s%s%s%s%s%s\n" + "command_line : %s\n" + "slowest_exec_ms : %llu\n", + start_time / 1000, get_cur_time() / 1000, getpid(), + queue_cycle ? (queue_cycle - 1) : 0, total_execs, eps, + queued_paths, queued_favored, queued_discovered, queued_imported, + max_depth, current_entry, pending_favored, pending_not_fuzzed, + queued_variable, stability, bitmap_cvg, unique_crashes, + unique_hangs, last_path_time / 1000, last_crash_time / 1000, + last_hang_time / 1000, total_execs - last_crash_execs, + exec_tmout, use_banner, + qemu_mode ? "qemu " : "", dumb_mode ? " dumb " : "", + no_forkserver ? "no_forksrv " : "", crash_mode ? "crash " : "", + persistent_mode ? "persistent " : "", deferred_mode ? "deferred " : "", + (qemu_mode || dumb_mode || no_forkserver || crash_mode || + persistent_mode || deferred_mode) ? "" : "default", + orig_cmdline, slowest_exec_ms); + /* ignore errors */ + + /* Get rss value from the children + We must have killed the forkserver process and called waitpid + before calling getrusage */ + if (getrusage(RUSAGE_CHILDREN, &usage)) { + WARNF("getrusage failed"); + } else if (usage.ru_maxrss == 0) { + fprintf(f, "peak_rss_mb : not available while afl is running\n"); + } else { +#ifdef __APPLE__ + fprintf(f, "peak_rss_mb : %zu\n", usage.ru_maxrss >> 20); +#else + fprintf(f, "peak_rss_mb : %zu\n", usage.ru_maxrss >> 10); +#endif /* ^__APPLE__ */ + } + + fclose(f); + +} + + +/* Update the plot file if there is a reason to. */ + +static void maybe_update_plot_file(double bitmap_cvg, double eps) { + + static u32 prev_qp, prev_pf, prev_pnf, prev_ce, prev_md; + static u64 prev_qc, prev_uc, prev_uh; + + if (prev_qp == queued_paths && prev_pf == pending_favored && + prev_pnf == pending_not_fuzzed && prev_ce == current_entry && + prev_qc == queue_cycle && prev_uc == unique_crashes && + prev_uh == unique_hangs && prev_md == max_depth) return; + + prev_qp = queued_paths; + prev_pf = pending_favored; + prev_pnf = pending_not_fuzzed; + prev_ce = current_entry; + prev_qc = queue_cycle; + prev_uc = unique_crashes; + prev_uh = unique_hangs; + prev_md = max_depth; + + /* Fields in the file: + + unix_time, cycles_done, cur_path, paths_total, paths_not_fuzzed, + favored_not_fuzzed, unique_crashes, unique_hangs, max_depth, + execs_per_sec */ + + fprintf(plot_file, + "%llu, %llu, %u, %u, %u, %u, %0.02f%%, %llu, %llu, %u, %0.02f\n", + get_cur_time() / 1000, queue_cycle - 1, current_entry, queued_paths, + pending_not_fuzzed, pending_favored, bitmap_cvg, unique_crashes, + unique_hangs, max_depth, eps); /* ignore errors */ + + fflush(plot_file); + +} + + + +/* A helper function for maybe_delete_out_dir(), deleting all prefixed + files in a directory. */ + +static u8 delete_files(u8* path, u8* prefix) { + + DIR* d; + struct dirent* d_ent; + + d = opendir(path); + + if (!d) return 0; + + while ((d_ent = readdir(d))) { + + if (d_ent->d_name[0] != '.' && (!prefix || + !strncmp(d_ent->d_name, prefix, strlen(prefix)))) { + + u8* fname = alloc_printf("%s/%s", path, d_ent->d_name); + if (unlink(fname)) PFATAL("Unable to delete '%s'", fname); + ck_free(fname); + + } + + } + + closedir(d); + + return !!rmdir(path); + +} + + +/* Get the number of runnable processes, with some simple smoothing. */ + +static double get_runnable_processes(void) { + + static double res; + +#if defined(__APPLE__) || defined(__FreeBSD__) || defined (__OpenBSD__) + + /* I don't see any portable sysctl or so that would quickly give us the + number of runnable processes; the 1-minute load average can be a + semi-decent approximation, though. */ + + if (getloadavg(&res, 1) != 1) return 0; + +#else + + /* On Linux, /proc/stat is probably the best way; load averages are + computed in funny ways and sometimes don't reflect extremely short-lived + processes well. */ + + FILE* f = fopen("/proc/stat", "r"); + u8 tmp[1024]; + u32 val = 0; + + if (!f) return 0; + + while (fgets(tmp, sizeof(tmp), f)) { + + if (!strncmp(tmp, "procs_running ", 14) || + !strncmp(tmp, "procs_blocked ", 14)) val += atoi(tmp + 14); + + } + + fclose(f); + + if (!res) { + + res = val; + + } else { + + res = res * (1.0 - 1.0 / AVG_SMOOTHING) + + ((double)val) * (1.0 / AVG_SMOOTHING); + + } + +#endif /* ^(__APPLE__ || __FreeBSD__ || __OpenBSD__) */ + + return res; + +} + + +/* Delete the temporary directory used for in-place session resume. */ + +static void nuke_resume_dir(void) { + + u8* fn; + + fn = alloc_printf("%s/_resume/.state/deterministic_done", out_dir); + if (delete_files(fn, CASE_PREFIX)) goto dir_cleanup_failed; + ck_free(fn); + + fn = alloc_printf("%s/_resume/.state/auto_extras", out_dir); + if (delete_files(fn, "auto_")) goto dir_cleanup_failed; + ck_free(fn); + + fn = alloc_printf("%s/_resume/.state/redundant_edges", out_dir); + if (delete_files(fn, CASE_PREFIX)) goto dir_cleanup_failed; + ck_free(fn); + + fn = alloc_printf("%s/_resume/.state/variable_behavior", out_dir); + if (delete_files(fn, CASE_PREFIX)) goto dir_cleanup_failed; + ck_free(fn); + + fn = alloc_printf("%s/_resume/.state", out_dir); + if (rmdir(fn) && errno != ENOENT) goto dir_cleanup_failed; + ck_free(fn); + + fn = alloc_printf("%s/_resume", out_dir); + if (delete_files(fn, CASE_PREFIX)) goto dir_cleanup_failed; + ck_free(fn); + + return; + +dir_cleanup_failed: + + FATAL("_resume directory cleanup failed"); + +} + + +/* Delete fuzzer output directory if we recognize it as ours, if the fuzzer + is not currently running, and if the last run time isn't too great. */ + +static void maybe_delete_out_dir(void) { + + FILE* f; + u8 *fn = alloc_printf("%s/fuzzer_stats", out_dir); + + /* See if the output directory is locked. If yes, bail out. If not, + create a lock that will persist for the lifetime of the process + (this requires leaving the descriptor open).*/ + + out_dir_fd = open(out_dir, O_RDONLY); + if (out_dir_fd < 0) PFATAL("Unable to open '%s'", out_dir); + +#ifndef __sun + + if (flock(out_dir_fd, LOCK_EX | LOCK_NB) && errno == EWOULDBLOCK) { + + SAYF("\n" cLRD "[-] " cRST + "Looks like the job output directory is being actively used by another\n" + " instance of afl-fuzz. You will need to choose a different %s\n" + " or stop the other process first.\n", + sync_id ? "fuzzer ID" : "output location"); + + FATAL("Directory '%s' is in use", out_dir); + + } + +#endif /* !__sun */ + + f = fopen(fn, "r"); + + if (f) { + + u64 start_time, last_update; + + if (fscanf(f, "start_time : %llu\n" + "last_update : %llu\n", &start_time, &last_update) != 2) + FATAL("Malformed data in '%s'", fn); + + fclose(f); + + /* Let's see how much work is at stake. */ + + if (!in_place_resume && last_update - start_time > OUTPUT_GRACE * 60) { + + SAYF("\n" cLRD "[-] " cRST + "The job output directory already exists and contains the results of more\n" + " than %u minutes worth of fuzzing. To avoid data loss, afl-fuzz will *NOT*\n" + " automatically delete this data for you.\n\n" + + " If you wish to start a new session, remove or rename the directory manually,\n" + " or specify a different output location for this job. To resume the old\n" + " session, put '-' as the input directory in the command line ('-i -') and\n" + " try again.\n", OUTPUT_GRACE); + + FATAL("At-risk data found in '%s'", out_dir); + + } + + } + + ck_free(fn); + + /* The idea for in-place resume is pretty simple: we temporarily move the old + queue/ to a new location that gets deleted once import to the new queue/ + is finished. If _resume/ already exists, the current queue/ may be + incomplete due to an earlier abort, so we want to use the old _resume/ + dir instead, and we let rename() fail silently. */ + + if (in_place_resume) { + + u8* orig_q = alloc_printf("%s/queue", out_dir); + + in_dir = alloc_printf("%s/_resume", out_dir); + + rename(orig_q, in_dir); /* Ignore errors */ + + OKF("Output directory exists, will attempt session resume."); + + ck_free(orig_q); + + } else { + + OKF("Output directory exists but deemed OK to reuse."); + + } + + ACTF("Deleting old session data..."); + + /* Okay, let's get the ball rolling! First, we need to get rid of the entries + in /.synced/.../id:*, if any are present. */ + + if (!in_place_resume) { + + fn = alloc_printf("%s/.synced", out_dir); + if (delete_files(fn, NULL)) goto dir_cleanup_failed; + ck_free(fn); + + } + + /* Next, we need to clean up /queue/.state/ subdirectories: */ + + fn = alloc_printf("%s/queue/.state/deterministic_done", out_dir); + if (delete_files(fn, CASE_PREFIX)) goto dir_cleanup_failed; + ck_free(fn); + + fn = alloc_printf("%s/queue/.state/auto_extras", out_dir); + if (delete_files(fn, "auto_")) goto dir_cleanup_failed; + ck_free(fn); + + fn = alloc_printf("%s/queue/.state/redundant_edges", out_dir); + if (delete_files(fn, CASE_PREFIX)) goto dir_cleanup_failed; + ck_free(fn); + + fn = alloc_printf("%s/queue/.state/variable_behavior", out_dir); + if (delete_files(fn, CASE_PREFIX)) goto dir_cleanup_failed; + ck_free(fn); + + /* Then, get rid of the .state subdirectory itself (should be empty by now) + and everything matching /queue/id:*. */ + + fn = alloc_printf("%s/queue/.state", out_dir); + if (rmdir(fn) && errno != ENOENT) goto dir_cleanup_failed; + ck_free(fn); + + fn = alloc_printf("%s/queue", out_dir); + if (delete_files(fn, CASE_PREFIX)) goto dir_cleanup_failed; + ck_free(fn); + + /* All right, let's do /crashes/id:* and /hangs/id:*. */ + + if (!in_place_resume) { + + fn = alloc_printf("%s/crashes/README.txt", out_dir); + unlink(fn); /* Ignore errors */ + ck_free(fn); + + } + + fn = alloc_printf("%s/crashes", out_dir); + + /* Make backup of the crashes directory if it's not empty and if we're + doing in-place resume. */ + + if (in_place_resume && rmdir(fn)) { + + time_t cur_t = time(0); + struct tm* t = localtime(&cur_t); + +#ifndef SIMPLE_FILES + + u8* nfn = alloc_printf("%s.%04u-%02u-%02u-%02u:%02u:%02u", fn, + t->tm_year + 1900, t->tm_mon + 1, t->tm_mday, + t->tm_hour, t->tm_min, t->tm_sec); + +#else + + u8* nfn = alloc_printf("%s_%04u%02u%02u%02u%02u%02u", fn, + t->tm_year + 1900, t->tm_mon + 1, t->tm_mday, + t->tm_hour, t->tm_min, t->tm_sec); + +#endif /* ^!SIMPLE_FILES */ + + rename(fn, nfn); /* Ignore errors. */ + ck_free(nfn); + + } + + if (delete_files(fn, CASE_PREFIX)) goto dir_cleanup_failed; + ck_free(fn); + + fn = alloc_printf("%s/hangs", out_dir); + + /* Backup hangs, too. */ + + if (in_place_resume && rmdir(fn)) { + + time_t cur_t = time(0); + struct tm* t = localtime(&cur_t); + +#ifndef SIMPLE_FILES + + u8* nfn = alloc_printf("%s.%04u-%02u-%02u-%02u:%02u:%02u", fn, + t->tm_year + 1900, t->tm_mon + 1, t->tm_mday, + t->tm_hour, t->tm_min, t->tm_sec); + +#else + + u8* nfn = alloc_printf("%s_%04u%02u%02u%02u%02u%02u", fn, + t->tm_year + 1900, t->tm_mon + 1, t->tm_mday, + t->tm_hour, t->tm_min, t->tm_sec); + +#endif /* ^!SIMPLE_FILES */ + + rename(fn, nfn); /* Ignore errors. */ + ck_free(nfn); + + } + + if (delete_files(fn, CASE_PREFIX)) goto dir_cleanup_failed; + ck_free(fn); + + /* And now, for some finishing touches. */ + + fn = alloc_printf("%s/.cur_input", out_dir); + if (unlink(fn) && errno != ENOENT) goto dir_cleanup_failed; + ck_free(fn); + + fn = alloc_printf("%s/fuzz_bitmap", out_dir); + if (unlink(fn) && errno != ENOENT) goto dir_cleanup_failed; + ck_free(fn); + + if (!in_place_resume) { + fn = alloc_printf("%s/fuzzer_stats", out_dir); + if (unlink(fn) && errno != ENOENT) goto dir_cleanup_failed; + ck_free(fn); + } + + fn = alloc_printf("%s/plot_data", out_dir); + if (unlink(fn) && errno != ENOENT) goto dir_cleanup_failed; + ck_free(fn); + + OKF("Output dir cleanup successful."); + + /* Wow... is that all? If yes, celebrate! */ + + return; + +dir_cleanup_failed: + + SAYF("\n" cLRD "[-] " cRST + "Whoops, the fuzzer tried to reuse your output directory, but bumped into\n" + " some files that shouldn't be there or that couldn't be removed - so it\n" + " decided to abort! This happened while processing this path:\n\n" + + " %s\n\n" + " Please examine and manually delete the files, or specify a different\n" + " output location for the tool.\n", fn); + + FATAL("Output directory cleanup failed"); + +} + + +static void check_term_size(void); + + +/* A spiffy retro stats screen! This is called every stats_update_freq + execve() calls, plus in several other circumstances. */ + +static void show_stats(void) { + + static u64 last_stats_ms, last_plot_ms, last_ms, last_execs; + static double avg_exec; + double t_byte_ratio, stab_ratio; + + u64 cur_ms; + u32 t_bytes, t_bits; + + u32 banner_len, banner_pad; + u8 tmp[256]; + + cur_ms = get_cur_time(); + + /* If not enough time has passed since last UI update, bail out. */ + + if (cur_ms - last_ms < 1000 / UI_TARGET_HZ) return; + + /* Check if we're past the 10 minute mark. */ + + if (cur_ms - start_time > 10 * 60 * 1000) run_over10m = 1; + + /* Calculate smoothed exec speed stats. */ + + if (!last_execs) { + + avg_exec = ((double)total_execs) * 1000 / (cur_ms - start_time); + + } else { + + double cur_avg = ((double)(total_execs - last_execs)) * 1000 / + (cur_ms - last_ms); + + /* If there is a dramatic (5x+) jump in speed, reset the indicator + more quickly. */ + + if (cur_avg * 5 < avg_exec || cur_avg / 5 > avg_exec) + avg_exec = cur_avg; + + avg_exec = avg_exec * (1.0 - 1.0 / AVG_SMOOTHING) + + cur_avg * (1.0 / AVG_SMOOTHING); + + } + + last_ms = cur_ms; + last_execs = total_execs; + + /* Tell the callers when to contact us (as measured in execs). */ + + stats_update_freq = avg_exec / (UI_TARGET_HZ * 10); + if (!stats_update_freq) stats_update_freq = 1; + + /* Do some bitmap stats. */ + + t_bytes = count_non_255_bytes(virgin_bits); + t_byte_ratio = ((double)t_bytes * 100) / MAP_SIZE; + + if (t_bytes) + stab_ratio = 100 - ((double)var_byte_count) * 100 / t_bytes; + else + stab_ratio = 100; + + /* Roughly every minute, update fuzzer stats and save auto tokens. */ + + if (cur_ms - last_stats_ms > STATS_UPDATE_SEC * 1000) { + + last_stats_ms = cur_ms; + write_stats_file(t_byte_ratio, stab_ratio, avg_exec); + save_auto(); + write_bitmap(); + + } + + /* Every now and then, write plot data. */ + + if (cur_ms - last_plot_ms > PLOT_UPDATE_SEC * 1000) { + + last_plot_ms = cur_ms; + maybe_update_plot_file(t_byte_ratio, avg_exec); + + } + + /* Honor AFL_EXIT_WHEN_DONE and AFL_BENCH_UNTIL_CRASH. */ + + if (!dumb_mode && cycles_wo_finds > 100 && !pending_not_fuzzed && + getenv("AFL_EXIT_WHEN_DONE")) stop_soon = 2; + + if (total_crashes && getenv("AFL_BENCH_UNTIL_CRASH")) stop_soon = 2; + + /* If we're not on TTY, bail out. */ + + if (not_on_tty) return; + + /* Compute some mildly useful bitmap stats. */ + + t_bits = (MAP_SIZE << 3) - count_bits(virgin_bits); + + /* Now, for the visuals... */ + + if (clear_screen) { + + SAYF(TERM_CLEAR CURSOR_HIDE); + clear_screen = 0; + + check_term_size(); + + } + + SAYF(TERM_HOME); + + if (term_too_small) { + + SAYF(cBRI "Your terminal is too small to display the UI.\n" + "Please resize terminal window to at least 80x25.\n" cRST); + + return; + + } + + /* Let's start by drawing a centered banner. */ + + banner_len = (crash_mode ? 24 : 22) + strlen(VERSION) + strlen(use_banner); + banner_pad = (80 - banner_len) / 2; + memset(tmp, ' ', banner_pad); + + sprintf(tmp + banner_pad, "%s " cLCY VERSION cLGN + " (%s)", crash_mode ? cPIN "peruvian were-rabbit" : + cYEL "american fuzzy lop", use_banner); + + SAYF("\n%s\n\n", tmp); + + /* "Handy" shortcuts for drawing boxes... */ + +#define bSTG bSTART cGRA +#define bH2 bH bH +#define bH5 bH2 bH2 bH +#define bH10 bH5 bH5 +#define bH20 bH10 bH10 +#define bH30 bH20 bH10 +#define SP5 " " +#define SP10 SP5 SP5 +#define SP20 SP10 SP10 + + /* Lord, forgive me this. */ + + SAYF(SET_G1 bSTG bLT bH bSTOP cCYA " process timing " bSTG bH30 bH5 bH2 bHB + bH bSTOP cCYA " overall results " bSTG bH5 bRT "\n"); + + if (dumb_mode) { + + strcpy(tmp, cRST); + + } else { + + u64 min_wo_finds = (cur_ms - last_path_time) / 1000 / 60; + + /* First queue cycle: don't stop now! */ + if (queue_cycle == 1 || min_wo_finds < 15) strcpy(tmp, cMGN); else + + /* Subsequent cycles, but we're still making finds. */ + if (cycles_wo_finds < 25 || min_wo_finds < 30) strcpy(tmp, cYEL); else + + /* No finds for a long time and no test cases to try. */ + if (cycles_wo_finds > 100 && !pending_not_fuzzed && min_wo_finds > 120) + strcpy(tmp, cLGN); + + /* Default: cautiously OK to stop? */ + else strcpy(tmp, cLBL); + + } + + SAYF(bV bSTOP " run time : " cRST "%-34s " bSTG bV bSTOP + " cycles done : %s%-5s " bSTG bV "\n", + DTD(cur_ms, start_time), tmp, DI(queue_cycle - 1)); + + /* We want to warn people about not seeing new paths after a full cycle, + except when resuming fuzzing or running in non-instrumented mode. */ + + if (!dumb_mode && (last_path_time || resuming_fuzz || queue_cycle == 1 || + in_bitmap || crash_mode)) { + + SAYF(bV bSTOP " last new path : " cRST "%-34s ", + DTD(cur_ms, last_path_time)); + + } else { + + if (dumb_mode) + + SAYF(bV bSTOP " last new path : " cPIN "n/a" cRST + " (non-instrumented mode) "); + + else + + SAYF(bV bSTOP " last new path : " cRST "none yet " cLRD + "(odd, check syntax!) "); + + } + + SAYF(bSTG bV bSTOP " total paths : " cRST "%-5s " bSTG bV "\n", + DI(queued_paths)); + + /* Highlight crashes in red if found, denote going over the KEEP_UNIQUE_CRASH + limit with a '+' appended to the count. */ + + sprintf(tmp, "%s%s", DI(unique_crashes), + (unique_crashes >= KEEP_UNIQUE_CRASH) ? "+" : ""); + + SAYF(bV bSTOP " last uniq crash : " cRST "%-34s " bSTG bV bSTOP + " uniq crashes : %s%-6s " bSTG bV "\n", + DTD(cur_ms, last_crash_time), unique_crashes ? cLRD : cRST, + tmp); + + sprintf(tmp, "%s%s", DI(unique_hangs), + (unique_hangs >= KEEP_UNIQUE_HANG) ? "+" : ""); + + SAYF(bV bSTOP " last uniq hang : " cRST "%-34s " bSTG bV bSTOP + " uniq hangs : " cRST "%-6s " bSTG bV "\n", + DTD(cur_ms, last_hang_time), tmp); + + SAYF(bVR bH bSTOP cCYA " cycle progress " bSTG bH20 bHB bH bSTOP cCYA + " map coverage " bSTG bH bHT bH20 bH2 bH bVL "\n"); + + /* This gets funny because we want to print several variable-length variables + together, but then cram them into a fixed-width field - so we need to + put them in a temporary buffer first. */ + + sprintf(tmp, "%s%s (%0.02f%%)", DI(current_entry), + queue_cur->favored ? "" : "*", + ((double)current_entry * 100) / queued_paths); + + SAYF(bV bSTOP " now processing : " cRST "%-17s " bSTG bV bSTOP, tmp); + + sprintf(tmp, "%0.02f%% / %0.02f%%", ((double)queue_cur->bitmap_size) * + 100 / MAP_SIZE, t_byte_ratio); + + SAYF(" map density : %s%-21s " bSTG bV "\n", t_byte_ratio > 70 ? cLRD : + ((t_bytes < 200 && !dumb_mode) ? cPIN : cRST), tmp); + + sprintf(tmp, "%s (%0.02f%%)", DI(cur_skipped_paths), + ((double)cur_skipped_paths * 100) / queued_paths); + + SAYF(bV bSTOP " paths timed out : " cRST "%-17s " bSTG bV, tmp); + + sprintf(tmp, "%0.02f bits/tuple", + t_bytes ? (((double)t_bits) / t_bytes) : 0); + + SAYF(bSTOP " count coverage : " cRST "%-21s " bSTG bV "\n", tmp); + + SAYF(bVR bH bSTOP cCYA " stage progress " bSTG bH20 bX bH bSTOP cCYA + " findings in depth " bSTG bH20 bVL "\n"); + + sprintf(tmp, "%s (%0.02f%%)", DI(queued_favored), + ((double)queued_favored) * 100 / queued_paths); + + /* Yeah... it's still going on... halp? */ + + SAYF(bV bSTOP " now trying : " cRST "%-21s " bSTG bV bSTOP + " favored paths : " cRST "%-22s " bSTG bV "\n", stage_name, tmp); + + if (!stage_max) { + + sprintf(tmp, "%s/-", DI(stage_cur)); + + } else { + + sprintf(tmp, "%s/%s (%0.02f%%)", DI(stage_cur), DI(stage_max), + ((double)stage_cur) * 100 / stage_max); + + } + + SAYF(bV bSTOP " stage execs : " cRST "%-21s " bSTG bV bSTOP, tmp); + + sprintf(tmp, "%s (%0.02f%%)", DI(queued_with_cov), + ((double)queued_with_cov) * 100 / queued_paths); + + SAYF(" new edges on : " cRST "%-22s " bSTG bV "\n", tmp); + + sprintf(tmp, "%s (%s%s unique)", DI(total_crashes), DI(unique_crashes), + (unique_crashes >= KEEP_UNIQUE_CRASH) ? "+" : ""); + + if (crash_mode) { + + SAYF(bV bSTOP " total execs : " cRST "%-21s " bSTG bV bSTOP + " new crashes : %s%-22s " bSTG bV "\n", DI(total_execs), + unique_crashes ? cLRD : cRST, tmp); + + } else { + + SAYF(bV bSTOP " total execs : " cRST "%-21s " bSTG bV bSTOP + " total crashes : %s%-22s " bSTG bV "\n", DI(total_execs), + unique_crashes ? cLRD : cRST, tmp); + + } + + /* Show a warning about slow execution. */ + + if (avg_exec < 100) { + + sprintf(tmp, "%s/sec (%s)", DF(avg_exec), avg_exec < 20 ? + "zzzz..." : "slow!"); + + SAYF(bV bSTOP " exec speed : " cLRD "%-21s ", tmp); + + } else { + + sprintf(tmp, "%s/sec", DF(avg_exec)); + SAYF(bV bSTOP " exec speed : " cRST "%-21s ", tmp); + + } + + sprintf(tmp, "%s (%s%s unique)", DI(total_tmouts), DI(unique_tmouts), + (unique_hangs >= KEEP_UNIQUE_HANG) ? "+" : ""); + + SAYF (bSTG bV bSTOP " total tmouts : " cRST "%-22s " bSTG bV "\n", tmp); + + /* Aaaalmost there... hold on! */ + + SAYF(bVR bH cCYA bSTOP " fuzzing strategy yields " bSTG bH10 bH bHT bH10 + bH5 bHB bH bSTOP cCYA " path geometry " bSTG bH5 bH2 bH bVL "\n"); + + if (skip_deterministic) { + + strcpy(tmp, "n/a, n/a, n/a"); + + } else { + + sprintf(tmp, "%s/%s, %s/%s, %s/%s", + DI(stage_finds[STAGE_FLIP1]), DI(stage_cycles[STAGE_FLIP1]), + DI(stage_finds[STAGE_FLIP2]), DI(stage_cycles[STAGE_FLIP2]), + DI(stage_finds[STAGE_FLIP4]), DI(stage_cycles[STAGE_FLIP4])); + + } + + SAYF(bV bSTOP " bit flips : " cRST "%-37s " bSTG bV bSTOP " levels : " + cRST "%-10s " bSTG bV "\n", tmp, DI(max_depth)); + + if (!skip_deterministic) + sprintf(tmp, "%s/%s, %s/%s, %s/%s", + DI(stage_finds[STAGE_FLIP8]), DI(stage_cycles[STAGE_FLIP8]), + DI(stage_finds[STAGE_FLIP16]), DI(stage_cycles[STAGE_FLIP16]), + DI(stage_finds[STAGE_FLIP32]), DI(stage_cycles[STAGE_FLIP32])); + + SAYF(bV bSTOP " byte flips : " cRST "%-37s " bSTG bV bSTOP " pending : " + cRST "%-10s " bSTG bV "\n", tmp, DI(pending_not_fuzzed)); + + if (!skip_deterministic) + sprintf(tmp, "%s/%s, %s/%s, %s/%s", + DI(stage_finds[STAGE_ARITH8]), DI(stage_cycles[STAGE_ARITH8]), + DI(stage_finds[STAGE_ARITH16]), DI(stage_cycles[STAGE_ARITH16]), + DI(stage_finds[STAGE_ARITH32]), DI(stage_cycles[STAGE_ARITH32])); + + SAYF(bV bSTOP " arithmetics : " cRST "%-37s " bSTG bV bSTOP " pend fav : " + cRST "%-10s " bSTG bV "\n", tmp, DI(pending_favored)); + + if (!skip_deterministic) + sprintf(tmp, "%s/%s, %s/%s, %s/%s", + DI(stage_finds[STAGE_INTEREST8]), DI(stage_cycles[STAGE_INTEREST8]), + DI(stage_finds[STAGE_INTEREST16]), DI(stage_cycles[STAGE_INTEREST16]), + DI(stage_finds[STAGE_INTEREST32]), DI(stage_cycles[STAGE_INTEREST32])); + + SAYF(bV bSTOP " known ints : " cRST "%-37s " bSTG bV bSTOP " own finds : " + cRST "%-10s " bSTG bV "\n", tmp, DI(queued_discovered)); + + if (!skip_deterministic) + sprintf(tmp, "%s/%s, %s/%s, %s/%s", + DI(stage_finds[STAGE_EXTRAS_UO]), DI(stage_cycles[STAGE_EXTRAS_UO]), + DI(stage_finds[STAGE_EXTRAS_UI]), DI(stage_cycles[STAGE_EXTRAS_UI]), + DI(stage_finds[STAGE_EXTRAS_AO]), DI(stage_cycles[STAGE_EXTRAS_AO])); + + SAYF(bV bSTOP " dictionary : " cRST "%-37s " bSTG bV bSTOP + " imported : " cRST "%-10s " bSTG bV "\n", tmp, + sync_id ? DI(queued_imported) : (u8*)"n/a"); + + sprintf(tmp, "%s/%s, %s/%s", + DI(stage_finds[STAGE_HAVOC]), DI(stage_cycles[STAGE_HAVOC]), + DI(stage_finds[STAGE_SPLICE]), DI(stage_cycles[STAGE_SPLICE])); + + SAYF(bV bSTOP " havoc : " cRST "%-37s " bSTG bV bSTOP, tmp); + + if (t_bytes) sprintf(tmp, "%0.02f%%", stab_ratio); + else strcpy(tmp, "n/a"); + + SAYF(" stability : %s%-10s " bSTG bV "\n", (stab_ratio < 85 && var_byte_count > 40) + ? cLRD : ((queued_variable && (!persistent_mode || var_byte_count > 20)) + ? cMGN : cRST), tmp); + + if (!bytes_trim_out) { + + sprintf(tmp, "n/a, "); + + } else { + + sprintf(tmp, "%0.02f%%/%s, ", + ((double)(bytes_trim_in - bytes_trim_out)) * 100 / bytes_trim_in, + DI(trim_execs)); + + } + + if (!blocks_eff_total) { + + u8 tmp2[128]; + + sprintf(tmp2, "n/a"); + strcat(tmp, tmp2); + + } else { + + u8 tmp2[128]; + + sprintf(tmp2, "%0.02f%%", + ((double)(blocks_eff_total - blocks_eff_select)) * 100 / + blocks_eff_total); + + strcat(tmp, tmp2); + + } + + SAYF(bV bSTOP " trim : " cRST "%-37s " bSTG bVR bH20 bH2 bH2 bRB "\n" + bLB bH30 bH20 bH2 bH bRB bSTOP cRST RESET_G1, tmp); + + /* Provide some CPU utilization stats. */ + + if (cpu_core_count) { + + double cur_runnable = get_runnable_processes(); + u32 cur_utilization = cur_runnable * 100 / cpu_core_count; + + u8* cpu_color = cCYA; + + /* If we could still run one or more processes, use green. */ + + if (cpu_core_count > 1 && cur_runnable + 1 <= cpu_core_count) + cpu_color = cLGN; + + /* If we're clearly oversubscribed, use red. */ + + if (!no_cpu_meter_red && cur_utilization >= 150) cpu_color = cLRD; + +#ifdef HAVE_AFFINITY + + if (cpu_aff >= 0) { + + SAYF(SP10 cGRA "[cpu%03u:%s%3u%%" cGRA "]\r" cRST, + MIN(cpu_aff, 999), cpu_color, + MIN(cur_utilization, 999)); + + } else { + + SAYF(SP10 cGRA " [cpu:%s%3u%%" cGRA "]\r" cRST, + cpu_color, MIN(cur_utilization, 999)); + + } + +#else + + SAYF(SP10 cGRA " [cpu:%s%3u%%" cGRA "]\r" cRST, + cpu_color, MIN(cur_utilization, 999)); + +#endif /* ^HAVE_AFFINITY */ + + } else SAYF("\r"); + + /* Hallelujah! */ + + fflush(0); + +} + + +/* Display quick statistics at the end of processing the input directory, + plus a bunch of warnings. Some calibration stuff also ended up here, + along with several hardcoded constants. Maybe clean up eventually. */ + +static void show_init_stats(void) { + + struct queue_entry* q = queue; + u32 min_bits = 0, max_bits = 0; + u64 min_us = 0, max_us = 0; + u64 avg_us = 0; + u32 max_len = 0; + + if (total_cal_cycles) avg_us = total_cal_us / total_cal_cycles; + + while (q) { + + if (!min_us || q->exec_us < min_us) min_us = q->exec_us; + if (q->exec_us > max_us) max_us = q->exec_us; + + if (!min_bits || q->bitmap_size < min_bits) min_bits = q->bitmap_size; + if (q->bitmap_size > max_bits) max_bits = q->bitmap_size; + + if (q->len > max_len) max_len = q->len; + + q = q->next; + + } + + SAYF("\n"); + + if (avg_us > (qemu_mode ? 50000 : 10000)) + WARNF(cLRD "The target binary is pretty slow! See %s/perf_tips.txt.", + doc_path); + + /* Let's keep things moving with slow binaries. */ + + if (avg_us > 50000) havoc_div = 10; /* 0-19 execs/sec */ + else if (avg_us > 20000) havoc_div = 5; /* 20-49 execs/sec */ + else if (avg_us > 10000) havoc_div = 2; /* 50-100 execs/sec */ + + if (!resuming_fuzz) { + + if (max_len > 50 * 1024) + WARNF(cLRD "Some test cases are huge (%s) - see %s/perf_tips.txt!", + DMS(max_len), doc_path); + else if (max_len > 10 * 1024) + WARNF("Some test cases are big (%s) - see %s/perf_tips.txt.", + DMS(max_len), doc_path); + + if (useless_at_start && !in_bitmap) + WARNF(cLRD "Some test cases look useless. Consider using a smaller set."); + + if (queued_paths > 100) + WARNF(cLRD "You probably have far too many input files! Consider trimming down."); + else if (queued_paths > 20) + WARNF("You have lots of input files; try starting small."); + + } + + OKF("Here are some useful stats:\n\n" + + cGRA " Test case count : " cRST "%u favored, %u variable, %u total\n" + cGRA " Bitmap range : " cRST "%u to %u bits (average: %0.02f bits)\n" + cGRA " Exec timing : " cRST "%s to %s us (average: %s us)\n", + queued_favored, queued_variable, queued_paths, min_bits, max_bits, + ((double)total_bitmap_size) / (total_bitmap_entries ? total_bitmap_entries : 1), + DI(min_us), DI(max_us), DI(avg_us)); + + if (!timeout_given) { + + /* Figure out the appropriate timeout. The basic idea is: 5x average or + 1x max, rounded up to EXEC_TM_ROUND ms and capped at 1 second. + + If the program is slow, the multiplier is lowered to 2x or 3x, because + random scheduler jitter is less likely to have any impact, and because + our patience is wearing thin =) */ + + if (avg_us > 50000) exec_tmout = avg_us * 2 / 1000; + else if (avg_us > 10000) exec_tmout = avg_us * 3 / 1000; + else exec_tmout = avg_us * 5 / 1000; + + exec_tmout = MAX(exec_tmout, max_us / 1000); + exec_tmout = (exec_tmout + EXEC_TM_ROUND) / EXEC_TM_ROUND * EXEC_TM_ROUND; + + if (exec_tmout > EXEC_TIMEOUT) exec_tmout = EXEC_TIMEOUT; + + ACTF("No -t option specified, so I'll use exec timeout of %u ms.", + exec_tmout); + + timeout_given = 1; + + } else if (timeout_given == 3) { + + ACTF("Applying timeout settings from resumed session (%u ms).", exec_tmout); + + } + + /* In dumb mode, re-running every timing out test case with a generous time + limit is very expensive, so let's select a more conservative default. */ + + if (dumb_mode && !getenv("AFL_HANG_TMOUT")) + hang_tmout = MIN(EXEC_TIMEOUT, exec_tmout * 2 + 100); + + OKF("All set and ready to roll!"); + +} + + +/* Find first power of two greater or equal to val (assuming val under + 2^31). */ + +static u32 next_p2(u32 val) { + + u32 ret = 1; + while (val > ret) ret <<= 1; + return ret; + +} + + +/* Trim all new test cases to save cycles when doing deterministic checks. The + trimmer uses power-of-two increments somewhere between 1/16 and 1/1024 of + file size, to keep the stage short and sweet. */ + +static u8 trim_case(char** argv, struct queue_entry* q, u8* in_buf) { + + static u8 tmp[64]; + static u8 clean_trace[MAP_SIZE]; + + u8 needs_write = 0, fault = 0; + u32 trim_exec = 0; + u32 remove_len; + u32 len_p2; + + /* Although the trimmer will be less useful when variable behavior is + detected, it will still work to some extent, so we don't check for + this. */ + + if (q->len < 5) return 0; + + stage_name = tmp; + bytes_trim_in += q->len; + + /* Select initial chunk len, starting with large steps. */ + + len_p2 = next_p2(q->len); + + remove_len = MAX(len_p2 / TRIM_START_STEPS, TRIM_MIN_BYTES); + + /* Continue until the number of steps gets too high or the stepover + gets too small. */ + + while (remove_len >= MAX(len_p2 / TRIM_END_STEPS, TRIM_MIN_BYTES)) { + + u32 remove_pos = remove_len; + + sprintf(tmp, "trim %s/%s", DI(remove_len), DI(remove_len)); + + stage_cur = 0; + stage_max = q->len / remove_len; + + while (remove_pos < q->len) { + + u32 trim_avail = MIN(remove_len, q->len - remove_pos); + u32 cksum; + + write_with_gap(in_buf, q->len, remove_pos, trim_avail); + + fault = run_target(argv, exec_tmout); + trim_execs++; + + if (stop_soon || fault == FAULT_ERROR) goto abort_trimming; + + /* Note that we don't keep track of crashes or hangs here; maybe TODO? */ + + cksum = hash32(trace_bits, MAP_SIZE, HASH_CONST); + + /* If the deletion had no impact on the trace, make it permanent. This + isn't perfect for variable-path inputs, but we're just making a + best-effort pass, so it's not a big deal if we end up with false + negatives every now and then. */ + + if (cksum == q->exec_cksum) { + + u32 move_tail = q->len - remove_pos - trim_avail; + + q->len -= trim_avail; + len_p2 = next_p2(q->len); + + memmove(in_buf + remove_pos, in_buf + remove_pos + trim_avail, + move_tail); + + /* Let's save a clean trace, which will be needed by + update_bitmap_score once we're done with the trimming stuff. */ + + if (!needs_write) { + + needs_write = 1; + memcpy(clean_trace, trace_bits, MAP_SIZE); + + } + + } else remove_pos += remove_len; + + /* Since this can be slow, update the screen every now and then. */ + + if (!(trim_exec++ % stats_update_freq)) show_stats(); + stage_cur++; + + } + + remove_len >>= 1; + + } + + /* If we have made changes to in_buf, we also need to update the on-disk + version of the test case. */ + + if (needs_write) { + + s32 fd; + + unlink(q->fname); /* ignore errors */ + + fd = open(q->fname, O_WRONLY | O_CREAT | O_EXCL, 0600); + + if (fd < 0) PFATAL("Unable to create '%s'", q->fname); + + ck_write(fd, in_buf, q->len, q->fname); + close(fd); + + memcpy(trace_bits, clean_trace, MAP_SIZE); + update_bitmap_score(q); + + } + +abort_trimming: + + bytes_trim_out += q->len; + return fault; + +} + + +/* Write a modified test case, run program, process results. Handle + error conditions, returning 1 if it's time to bail out. This is + a helper function for fuzz_one(). */ + +EXP_ST u8 common_fuzz_stuff(char** argv, u8* out_buf, u32 len) { + + u8 fault; + + if (post_handler) { + + out_buf = post_handler(out_buf, &len); + if (!out_buf || !len) return 0; + + } + + write_to_testcase(out_buf, len); + + fault = run_target(argv, exec_tmout); + + if (stop_soon) return 1; + + if (fault == FAULT_TMOUT) { + + if (subseq_tmouts++ > TMOUT_LIMIT) { + cur_skipped_paths++; + return 1; + } + + } else subseq_tmouts = 0; + + /* Users can hit us with SIGUSR1 to request the current input + to be abandoned. */ + + if (skip_requested) { + + skip_requested = 0; + cur_skipped_paths++; + return 1; + + } + + /* This handles FAULT_ERROR for us: */ + + queued_discovered += save_if_interesting(argv, out_buf, len, fault); + + if (!(stage_cur % stats_update_freq) || stage_cur + 1 == stage_max) + show_stats(); + + return 0; + +} + + +/* Helper to choose random block len for block operations in fuzz_one(). + Doesn't return zero, provided that max_len is > 0. */ + +static u32 choose_block_len(u32 limit) { + + u32 min_value, max_value; + u32 rlim = MIN(queue_cycle, 3); + + if (!run_over10m) rlim = 1; + + switch (UR(rlim)) { + + case 0: min_value = 1; + max_value = HAVOC_BLK_SMALL; + break; + + case 1: min_value = HAVOC_BLK_SMALL; + max_value = HAVOC_BLK_MEDIUM; + break; + + default: + + if (UR(10)) { + + min_value = HAVOC_BLK_MEDIUM; + max_value = HAVOC_BLK_LARGE; + + } else { + + min_value = HAVOC_BLK_LARGE; + max_value = HAVOC_BLK_XL; + + } + + } + + if (min_value >= limit) min_value = 1; + + return min_value + UR(MIN(max_value, limit) - min_value + 1); + +} + + +/* Calculate case desirability score to adjust the length of havoc fuzzing. + A helper function for fuzz_one(). Maybe some of these constants should + go into config.h. */ + +static u32 calculate_score(struct queue_entry* q) { + + u32 avg_exec_us = total_cal_us / total_cal_cycles; + u32 avg_bitmap_size = total_bitmap_size / total_bitmap_entries; + u32 perf_score = 100; + + /* Adjust score based on execution speed of this path, compared to the + global average. Multiplier ranges from 0.1x to 3x. Fast inputs are + less expensive to fuzz, so we're giving them more air time. */ + + if (q->exec_us * 0.1 > avg_exec_us) perf_score = 10; + else if (q->exec_us * 0.25 > avg_exec_us) perf_score = 25; + else if (q->exec_us * 0.5 > avg_exec_us) perf_score = 50; + else if (q->exec_us * 0.75 > avg_exec_us) perf_score = 75; + else if (q->exec_us * 4 < avg_exec_us) perf_score = 300; + else if (q->exec_us * 3 < avg_exec_us) perf_score = 200; + else if (q->exec_us * 2 < avg_exec_us) perf_score = 150; + + /* Adjust score based on bitmap size. The working theory is that better + coverage translates to better targets. Multiplier from 0.25x to 3x. */ + + if (q->bitmap_size * 0.3 > avg_bitmap_size) perf_score *= 3; + else if (q->bitmap_size * 0.5 > avg_bitmap_size) perf_score *= 2; + else if (q->bitmap_size * 0.75 > avg_bitmap_size) perf_score *= 1.5; + else if (q->bitmap_size * 3 < avg_bitmap_size) perf_score *= 0.25; + else if (q->bitmap_size * 2 < avg_bitmap_size) perf_score *= 0.5; + else if (q->bitmap_size * 1.5 < avg_bitmap_size) perf_score *= 0.75; + + /* Adjust score based on handicap. Handicap is proportional to how late + in the game we learned about this path. Latecomers are allowed to run + for a bit longer until they catch up with the rest. */ + + if (q->handicap >= 4) { + + perf_score *= 4; + q->handicap -= 4; + + } else if (q->handicap) { + + perf_score *= 2; + q->handicap--; + + } + + /* Final adjustment based on input depth, under the assumption that fuzzing + deeper test cases is more likely to reveal stuff that can't be + discovered with traditional fuzzers. */ + + switch (q->depth) { + + case 0 ... 3: break; + case 4 ... 7: perf_score *= 2; break; + case 8 ... 13: perf_score *= 3; break; + case 14 ... 25: perf_score *= 4; break; + default: perf_score *= 5; + + } + + /* Make sure that we don't go over limit. */ + + if (perf_score > HAVOC_MAX_MULT * 100) perf_score = HAVOC_MAX_MULT * 100; + + return perf_score; + +} + + +/* Helper function to see if a particular change (xor_val = old ^ new) could + be a product of deterministic bit flips with the lengths and stepovers + attempted by afl-fuzz. This is used to avoid dupes in some of the + deterministic fuzzing operations that follow bit flips. We also + return 1 if xor_val is zero, which implies that the old and attempted new + values are identical and the exec would be a waste of time. */ + +static u8 could_be_bitflip(u32 xor_val) { + + u32 sh = 0; + + if (!xor_val) return 1; + + /* Shift left until first bit set. */ + + while (!(xor_val & 1)) { sh++; xor_val >>= 1; } + + /* 1-, 2-, and 4-bit patterns are OK anywhere. */ + + if (xor_val == 1 || xor_val == 3 || xor_val == 15) return 1; + + /* 8-, 16-, and 32-bit patterns are OK only if shift factor is + divisible by 8, since that's the stepover for these ops. */ + + if (sh & 7) return 0; + + if (xor_val == 0xff || xor_val == 0xffff || xor_val == 0xffffffff) + return 1; + + return 0; + +} + + +/* Helper function to see if a particular value is reachable through + arithmetic operations. Used for similar purposes. */ + +static u8 could_be_arith(u32 old_val, u32 new_val, u8 blen) { + + u32 i, ov = 0, nv = 0, diffs = 0; + + if (old_val == new_val) return 1; + + /* See if one-byte adjustments to any byte could produce this result. */ + + for (i = 0; i < blen; i++) { + + u8 a = old_val >> (8 * i), + b = new_val >> (8 * i); + + if (a != b) { diffs++; ov = a; nv = b; } + + } + + /* If only one byte differs and the values are within range, return 1. */ + + if (diffs == 1) { + + if ((u8)(ov - nv) <= ARITH_MAX || + (u8)(nv - ov) <= ARITH_MAX) return 1; + + } + + if (blen == 1) return 0; + + /* See if two-byte adjustments to any byte would produce this result. */ + + diffs = 0; + + for (i = 0; i < blen / 2; i++) { + + u16 a = old_val >> (16 * i), + b = new_val >> (16 * i); + + if (a != b) { diffs++; ov = a; nv = b; } + + } + + /* If only one word differs and the values are within range, return 1. */ + + if (diffs == 1) { + + if ((u16)(ov - nv) <= ARITH_MAX || + (u16)(nv - ov) <= ARITH_MAX) return 1; + + ov = SWAP16(ov); nv = SWAP16(nv); + + if ((u16)(ov - nv) <= ARITH_MAX || + (u16)(nv - ov) <= ARITH_MAX) return 1; + + } + + /* Finally, let's do the same thing for dwords. */ + + if (blen == 4) { + + if ((u32)(old_val - new_val) <= ARITH_MAX || + (u32)(new_val - old_val) <= ARITH_MAX) return 1; + + new_val = SWAP32(new_val); + old_val = SWAP32(old_val); + + if ((u32)(old_val - new_val) <= ARITH_MAX || + (u32)(new_val - old_val) <= ARITH_MAX) return 1; + + } + + return 0; + +} + + +/* Last but not least, a similar helper to see if insertion of an + interesting integer is redundant given the insertions done for + shorter blen. The last param (check_le) is set if the caller + already executed LE insertion for current blen and wants to see + if BE variant passed in new_val is unique. */ + +static u8 could_be_interest(u32 old_val, u32 new_val, u8 blen, u8 check_le) { + + u32 i, j; + + if (old_val == new_val) return 1; + + /* See if one-byte insertions from interesting_8 over old_val could + produce new_val. */ + + for (i = 0; i < blen; i++) { + + for (j = 0; j < sizeof(interesting_8); j++) { + + u32 tval = (old_val & ~(0xff << (i * 8))) | + (((u8)interesting_8[j]) << (i * 8)); + + if (new_val == tval) return 1; + + } + + } + + /* Bail out unless we're also asked to examine two-byte LE insertions + as a preparation for BE attempts. */ + + if (blen == 2 && !check_le) return 0; + + /* See if two-byte insertions over old_val could give us new_val. */ + + for (i = 0; i < blen - 1; i++) { + + for (j = 0; j < sizeof(interesting_16) / 2; j++) { + + u32 tval = (old_val & ~(0xffff << (i * 8))) | + (((u16)interesting_16[j]) << (i * 8)); + + if (new_val == tval) return 1; + + /* Continue here only if blen > 2. */ + + if (blen > 2) { + + tval = (old_val & ~(0xffff << (i * 8))) | + (SWAP16(interesting_16[j]) << (i * 8)); + + if (new_val == tval) return 1; + + } + + } + + } + + if (blen == 4 && check_le) { + + /* See if four-byte insertions could produce the same result + (LE only). */ + + for (j = 0; j < sizeof(interesting_32) / 4; j++) + if (new_val == (u32)interesting_32[j]) return 1; + + } + + return 0; + +} + + +/* Take the current entry from the queue, fuzz it for a while. This + function is a tad too long... returns 0 if fuzzed successfully, 1 if + skipped or bailed out. */ + +static u8 fuzz_one(char** argv) { + + s32 len, fd, temp_len, i, j; + u8 *in_buf, *out_buf, *orig_in, *ex_tmp, *eff_map = 0; + u64 havoc_queued, orig_hit_cnt, new_hit_cnt; + u32 splice_cycle = 0, perf_score = 100, orig_perf, prev_cksum, eff_cnt = 1; + + u8 ret_val = 1, doing_det = 0; + + u8 a_collect[MAX_AUTO_EXTRA]; + u32 a_len = 0; + +#ifdef IGNORE_FINDS + + /* In IGNORE_FINDS mode, skip any entries that weren't in the + initial data set. */ + + if (queue_cur->depth > 1) return 1; + +#else + + if (pending_favored) { + + /* If we have any favored, non-fuzzed new arrivals in the queue, + possibly skip to them at the expense of already-fuzzed or non-favored + cases. */ + + if ((queue_cur->was_fuzzed || !queue_cur->favored) && + UR(100) < SKIP_TO_NEW_PROB) return 1; + + } else if (!dumb_mode && !queue_cur->favored && queued_paths > 10) { + + /* Otherwise, still possibly skip non-favored cases, albeit less often. + The odds of skipping stuff are higher for already-fuzzed inputs and + lower for never-fuzzed entries. */ + + if (queue_cycle > 1 && !queue_cur->was_fuzzed) { + + if (UR(100) < SKIP_NFAV_NEW_PROB) return 1; + + } else { + + if (UR(100) < SKIP_NFAV_OLD_PROB) return 1; + + } + + } + +#endif /* ^IGNORE_FINDS */ + + if (not_on_tty) { + ACTF("Fuzzing test case #%u (%u total, %llu uniq crashes found)...", + current_entry, queued_paths, unique_crashes); + fflush(stdout); + } + + /* Map the test case into memory. */ + + fd = open(queue_cur->fname, O_RDONLY); + + if (fd < 0) PFATAL("Unable to open '%s'", queue_cur->fname); + + len = queue_cur->len; + + orig_in = in_buf = mmap(0, len, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0); + + if (orig_in == MAP_FAILED) PFATAL("Unable to mmap '%s'", queue_cur->fname); + + close(fd); + + /* We could mmap() out_buf as MAP_PRIVATE, but we end up clobbering every + single byte anyway, so it wouldn't give us any performance or memory usage + benefits. */ + + out_buf = ck_alloc_nozero(len); + + subseq_tmouts = 0; + + cur_depth = queue_cur->depth; + + /******************************************* + * CALIBRATION (only if failed earlier on) * + *******************************************/ + + if (queue_cur->cal_failed) { + + u8 res = FAULT_TMOUT; + + if (queue_cur->cal_failed < CAL_CHANCES) { + + /* Reset exec_cksum to tell calibrate_case to re-execute the testcase + avoiding the usage of an invalid trace_bits. + For more info: https://github.com/AFLplusplus/AFLplusplus/pull/425 */ + + queue_cur->exec_cksum = 0; + + res = calibrate_case(argv, queue_cur, in_buf, queue_cycle - 1, 0); + + if (res == FAULT_ERROR) + FATAL("Unable to execute target application"); + + } + + if (stop_soon || res != crash_mode) { + cur_skipped_paths++; + goto abandon_entry; + } + + } + + /************ + * TRIMMING * + ************/ + + if (!dumb_mode && !queue_cur->trim_done) { + + u8 res = trim_case(argv, queue_cur, in_buf); + + if (res == FAULT_ERROR) + FATAL("Unable to execute target application"); + + if (stop_soon) { + cur_skipped_paths++; + goto abandon_entry; + } + + /* Don't retry trimming, even if it failed. */ + + queue_cur->trim_done = 1; + + if (len != queue_cur->len) len = queue_cur->len; + + } + + memcpy(out_buf, in_buf, len); + + /********************* + * PERFORMANCE SCORE * + *********************/ + + orig_perf = perf_score = calculate_score(queue_cur); + + /* Skip right away if -d is given, if we have done deterministic fuzzing on + this entry ourselves (was_fuzzed), or if it has gone through deterministic + testing in earlier, resumed runs (passed_det). */ + + if (skip_deterministic || queue_cur->was_fuzzed || queue_cur->passed_det) + goto havoc_stage; + + /* Skip deterministic fuzzing if exec path checksum puts this out of scope + for this master instance. */ + + if (master_max && (queue_cur->exec_cksum % master_max) != master_id - 1) + goto havoc_stage; + + doing_det = 1; + + /********************************************* + * SIMPLE BITFLIP (+dictionary construction) * + *********************************************/ + +#define FLIP_BIT(_ar, _b) do { \ + u8* _arf = (u8*)(_ar); \ + u32 _bf = (_b); \ + _arf[(_bf) >> 3] ^= (128 >> ((_bf) & 7)); \ + } while (0) + + /* Single walking bit. */ + + stage_short = "flip1"; + stage_max = len << 3; + stage_name = "bitflip 1/1"; + + stage_val_type = STAGE_VAL_NONE; + + orig_hit_cnt = queued_paths + unique_crashes; + + prev_cksum = queue_cur->exec_cksum; + + for (stage_cur = 0; stage_cur < stage_max; stage_cur++) { + + stage_cur_byte = stage_cur >> 3; + + FLIP_BIT(out_buf, stage_cur); + + if (common_fuzz_stuff(argv, out_buf, len)) goto abandon_entry; + + FLIP_BIT(out_buf, stage_cur); + + /* While flipping the least significant bit in every byte, pull of an extra + trick to detect possible syntax tokens. In essence, the idea is that if + you have a binary blob like this: + + xxxxxxxxIHDRxxxxxxxx + + ...and changing the leading and trailing bytes causes variable or no + changes in program flow, but touching any character in the "IHDR" string + always produces the same, distinctive path, it's highly likely that + "IHDR" is an atomically-checked magic value of special significance to + the fuzzed format. + + We do this here, rather than as a separate stage, because it's a nice + way to keep the operation approximately "free" (i.e., no extra execs). + + Empirically, performing the check when flipping the least significant bit + is advantageous, compared to doing it at the time of more disruptive + changes, where the program flow may be affected in more violent ways. + + The caveat is that we won't generate dictionaries in the -d mode or -S + mode - but that's probably a fair trade-off. + + This won't work particularly well with paths that exhibit variable + behavior, but fails gracefully, so we'll carry out the checks anyway. + + */ + + if (!dumb_mode && (stage_cur & 7) == 7) { + + u32 cksum = hash32(trace_bits, MAP_SIZE, HASH_CONST); + + if (stage_cur == stage_max - 1 && cksum == prev_cksum) { + + /* If at end of file and we are still collecting a string, grab the + final character and force output. */ + + if (a_len < MAX_AUTO_EXTRA) a_collect[a_len] = out_buf[stage_cur >> 3]; + a_len++; + + if (a_len >= MIN_AUTO_EXTRA && a_len <= MAX_AUTO_EXTRA) + maybe_add_auto(a_collect, a_len); + + } else if (cksum != prev_cksum) { + + /* Otherwise, if the checksum has changed, see if we have something + worthwhile queued up, and collect that if the answer is yes. */ + + if (a_len >= MIN_AUTO_EXTRA && a_len <= MAX_AUTO_EXTRA) + maybe_add_auto(a_collect, a_len); + + a_len = 0; + prev_cksum = cksum; + + } + + /* Continue collecting string, but only if the bit flip actually made + any difference - we don't want no-op tokens. */ + + if (cksum != queue_cur->exec_cksum) { + + if (a_len < MAX_AUTO_EXTRA) a_collect[a_len] = out_buf[stage_cur >> 3]; + a_len++; + + } + + } + + } + + new_hit_cnt = queued_paths + unique_crashes; + + stage_finds[STAGE_FLIP1] += new_hit_cnt - orig_hit_cnt; + stage_cycles[STAGE_FLIP1] += stage_max; + + /* Two walking bits. */ + + stage_name = "bitflip 2/1"; + stage_short = "flip2"; + stage_max = (len << 3) - 1; + + orig_hit_cnt = new_hit_cnt; + + for (stage_cur = 0; stage_cur < stage_max; stage_cur++) { + + stage_cur_byte = stage_cur >> 3; + + FLIP_BIT(out_buf, stage_cur); + FLIP_BIT(out_buf, stage_cur + 1); + + if (common_fuzz_stuff(argv, out_buf, len)) goto abandon_entry; + + FLIP_BIT(out_buf, stage_cur); + FLIP_BIT(out_buf, stage_cur + 1); + + } + + new_hit_cnt = queued_paths + unique_crashes; + + stage_finds[STAGE_FLIP2] += new_hit_cnt - orig_hit_cnt; + stage_cycles[STAGE_FLIP2] += stage_max; + + /* Four walking bits. */ + + stage_name = "bitflip 4/1"; + stage_short = "flip4"; + stage_max = (len << 3) - 3; + + orig_hit_cnt = new_hit_cnt; + + for (stage_cur = 0; stage_cur < stage_max; stage_cur++) { + + stage_cur_byte = stage_cur >> 3; + + FLIP_BIT(out_buf, stage_cur); + FLIP_BIT(out_buf, stage_cur + 1); + FLIP_BIT(out_buf, stage_cur + 2); + FLIP_BIT(out_buf, stage_cur + 3); + + if (common_fuzz_stuff(argv, out_buf, len)) goto abandon_entry; + + FLIP_BIT(out_buf, stage_cur); + FLIP_BIT(out_buf, stage_cur + 1); + FLIP_BIT(out_buf, stage_cur + 2); + FLIP_BIT(out_buf, stage_cur + 3); + + } + + new_hit_cnt = queued_paths + unique_crashes; + + stage_finds[STAGE_FLIP4] += new_hit_cnt - orig_hit_cnt; + stage_cycles[STAGE_FLIP4] += stage_max; + + /* Effector map setup. These macros calculate: + + EFF_APOS - position of a particular file offset in the map. + EFF_ALEN - length of a map with a particular number of bytes. + EFF_SPAN_ALEN - map span for a sequence of bytes. + + */ + +#define EFF_APOS(_p) ((_p) >> EFF_MAP_SCALE2) +#define EFF_REM(_x) ((_x) & ((1 << EFF_MAP_SCALE2) - 1)) +#define EFF_ALEN(_l) (EFF_APOS(_l) + !!EFF_REM(_l)) +#define EFF_SPAN_ALEN(_p, _l) (EFF_APOS((_p) + (_l) - 1) - EFF_APOS(_p) + 1) + + /* Initialize effector map for the next step (see comments below). Always + flag first and last byte as doing something. */ + + eff_map = ck_alloc(EFF_ALEN(len)); + eff_map[0] = 1; + + if (EFF_APOS(len - 1) != 0) { + eff_map[EFF_APOS(len - 1)] = 1; + eff_cnt++; + } + + /* Walking byte. */ + + stage_name = "bitflip 8/8"; + stage_short = "flip8"; + stage_max = len; + + orig_hit_cnt = new_hit_cnt; + + for (stage_cur = 0; stage_cur < stage_max; stage_cur++) { + + stage_cur_byte = stage_cur; + + out_buf[stage_cur] ^= 0xFF; + + if (common_fuzz_stuff(argv, out_buf, len)) goto abandon_entry; + + /* We also use this stage to pull off a simple trick: we identify + bytes that seem to have no effect on the current execution path + even when fully flipped - and we skip them during more expensive + deterministic stages, such as arithmetics or known ints. */ + + if (!eff_map[EFF_APOS(stage_cur)]) { + + u32 cksum; + + /* If in dumb mode or if the file is very short, just flag everything + without wasting time on checksums. */ + + if (!dumb_mode && len >= EFF_MIN_LEN) + cksum = hash32(trace_bits, MAP_SIZE, HASH_CONST); + else + cksum = ~queue_cur->exec_cksum; + + if (cksum != queue_cur->exec_cksum) { + eff_map[EFF_APOS(stage_cur)] = 1; + eff_cnt++; + } + + } + + out_buf[stage_cur] ^= 0xFF; + + } + + /* If the effector map is more than EFF_MAX_PERC dense, just flag the + whole thing as worth fuzzing, since we wouldn't be saving much time + anyway. */ + + if (eff_cnt != EFF_ALEN(len) && + eff_cnt * 100 / EFF_ALEN(len) > EFF_MAX_PERC) { + + memset(eff_map, 1, EFF_ALEN(len)); + + blocks_eff_select += EFF_ALEN(len); + + } else { + + blocks_eff_select += eff_cnt; + + } + + blocks_eff_total += EFF_ALEN(len); + + new_hit_cnt = queued_paths + unique_crashes; + + stage_finds[STAGE_FLIP8] += new_hit_cnt - orig_hit_cnt; + stage_cycles[STAGE_FLIP8] += stage_max; + + /* Two walking bytes. */ + + if (len < 2) goto skip_bitflip; + + stage_name = "bitflip 16/8"; + stage_short = "flip16"; + stage_cur = 0; + stage_max = len - 1; + + orig_hit_cnt = new_hit_cnt; + + for (i = 0; i < len - 1; i++) { + + /* Let's consult the effector map... */ + + if (!eff_map[EFF_APOS(i)] && !eff_map[EFF_APOS(i + 1)]) { + stage_max--; + continue; + } + + stage_cur_byte = i; + + *(u16*)(out_buf + i) ^= 0xFFFF; + + if (common_fuzz_stuff(argv, out_buf, len)) goto abandon_entry; + stage_cur++; + + *(u16*)(out_buf + i) ^= 0xFFFF; + + + } + + new_hit_cnt = queued_paths + unique_crashes; + + stage_finds[STAGE_FLIP16] += new_hit_cnt - orig_hit_cnt; + stage_cycles[STAGE_FLIP16] += stage_max; + + if (len < 4) goto skip_bitflip; + + /* Four walking bytes. */ + + stage_name = "bitflip 32/8"; + stage_short = "flip32"; + stage_cur = 0; + stage_max = len - 3; + + orig_hit_cnt = new_hit_cnt; + + for (i = 0; i < len - 3; i++) { + + /* Let's consult the effector map... */ + if (!eff_map[EFF_APOS(i)] && !eff_map[EFF_APOS(i + 1)] && + !eff_map[EFF_APOS(i + 2)] && !eff_map[EFF_APOS(i + 3)]) { + stage_max--; + continue; + } + + stage_cur_byte = i; + + *(u32*)(out_buf + i) ^= 0xFFFFFFFF; + + if (common_fuzz_stuff(argv, out_buf, len)) goto abandon_entry; + stage_cur++; + + *(u32*)(out_buf + i) ^= 0xFFFFFFFF; + + } + + new_hit_cnt = queued_paths + unique_crashes; + + stage_finds[STAGE_FLIP32] += new_hit_cnt - orig_hit_cnt; + stage_cycles[STAGE_FLIP32] += stage_max; + +skip_bitflip: + + if (no_arith) goto skip_arith; + + /********************** + * ARITHMETIC INC/DEC * + **********************/ + + /* 8-bit arithmetics. */ + + stage_name = "arith 8/8"; + stage_short = "arith8"; + stage_cur = 0; + stage_max = 2 * len * ARITH_MAX; + + stage_val_type = STAGE_VAL_LE; + + orig_hit_cnt = new_hit_cnt; + + for (i = 0; i < len; i++) { + + u8 orig = out_buf[i]; + + /* Let's consult the effector map... */ + + if (!eff_map[EFF_APOS(i)]) { + stage_max -= 2 * ARITH_MAX; + continue; + } + + stage_cur_byte = i; + + for (j = 1; j <= ARITH_MAX; j++) { + + u8 r = orig ^ (orig + j); + + /* Do arithmetic operations only if the result couldn't be a product + of a bitflip. */ + + if (!could_be_bitflip(r)) { + + stage_cur_val = j; + out_buf[i] = orig + j; + + if (common_fuzz_stuff(argv, out_buf, len)) goto abandon_entry; + stage_cur++; + + } else stage_max--; + + r = orig ^ (orig - j); + + if (!could_be_bitflip(r)) { + + stage_cur_val = -j; + out_buf[i] = orig - j; + + if (common_fuzz_stuff(argv, out_buf, len)) goto abandon_entry; + stage_cur++; + + } else stage_max--; + + out_buf[i] = orig; + + } + + } + + new_hit_cnt = queued_paths + unique_crashes; + + stage_finds[STAGE_ARITH8] += new_hit_cnt - orig_hit_cnt; + stage_cycles[STAGE_ARITH8] += stage_max; + + /* 16-bit arithmetics, both endians. */ + + if (len < 2) goto skip_arith; + + stage_name = "arith 16/8"; + stage_short = "arith16"; + stage_cur = 0; + stage_max = 4 * (len - 1) * ARITH_MAX; + + orig_hit_cnt = new_hit_cnt; + + for (i = 0; i < len - 1; i++) { + + u16 orig = *(u16*)(out_buf + i); + + /* Let's consult the effector map... */ + + if (!eff_map[EFF_APOS(i)] && !eff_map[EFF_APOS(i + 1)]) { + stage_max -= 4 * ARITH_MAX; + continue; + } + + stage_cur_byte = i; + + for (j = 1; j <= ARITH_MAX; j++) { + + u16 r1 = orig ^ (orig + j), + r2 = orig ^ (orig - j), + r3 = orig ^ SWAP16(SWAP16(orig) + j), + r4 = orig ^ SWAP16(SWAP16(orig) - j); + + /* Try little endian addition and subtraction first. Do it only + if the operation would affect more than one byte (hence the + & 0xff overflow checks) and if it couldn't be a product of + a bitflip. */ + + stage_val_type = STAGE_VAL_LE; + + if ((orig & 0xff) + j > 0xff && !could_be_bitflip(r1)) { + + stage_cur_val = j; + *(u16*)(out_buf + i) = orig + j; + + if (common_fuzz_stuff(argv, out_buf, len)) goto abandon_entry; + stage_cur++; + + } else stage_max--; + + if ((orig & 0xff) < j && !could_be_bitflip(r2)) { + + stage_cur_val = -j; + *(u16*)(out_buf + i) = orig - j; + + if (common_fuzz_stuff(argv, out_buf, len)) goto abandon_entry; + stage_cur++; + + } else stage_max--; + + /* Big endian comes next. Same deal. */ + + stage_val_type = STAGE_VAL_BE; + + + if ((orig >> 8) + j > 0xff && !could_be_bitflip(r3)) { + + stage_cur_val = j; + *(u16*)(out_buf + i) = SWAP16(SWAP16(orig) + j); + + if (common_fuzz_stuff(argv, out_buf, len)) goto abandon_entry; + stage_cur++; + + } else stage_max--; + + if ((orig >> 8) < j && !could_be_bitflip(r4)) { + + stage_cur_val = -j; + *(u16*)(out_buf + i) = SWAP16(SWAP16(orig) - j); + + if (common_fuzz_stuff(argv, out_buf, len)) goto abandon_entry; + stage_cur++; + + } else stage_max--; + + *(u16*)(out_buf + i) = orig; + + } + + } + + new_hit_cnt = queued_paths + unique_crashes; + + stage_finds[STAGE_ARITH16] += new_hit_cnt - orig_hit_cnt; + stage_cycles[STAGE_ARITH16] += stage_max; + + /* 32-bit arithmetics, both endians. */ + + if (len < 4) goto skip_arith; + + stage_name = "arith 32/8"; + stage_short = "arith32"; + stage_cur = 0; + stage_max = 4 * (len - 3) * ARITH_MAX; + + orig_hit_cnt = new_hit_cnt; + + for (i = 0; i < len - 3; i++) { + + u32 orig = *(u32*)(out_buf + i); + + /* Let's consult the effector map... */ + + if (!eff_map[EFF_APOS(i)] && !eff_map[EFF_APOS(i + 1)] && + !eff_map[EFF_APOS(i + 2)] && !eff_map[EFF_APOS(i + 3)]) { + stage_max -= 4 * ARITH_MAX; + continue; + } + + stage_cur_byte = i; + + for (j = 1; j <= ARITH_MAX; j++) { + + u32 r1 = orig ^ (orig + j), + r2 = orig ^ (orig - j), + r3 = orig ^ SWAP32(SWAP32(orig) + j), + r4 = orig ^ SWAP32(SWAP32(orig) - j); + + /* Little endian first. Same deal as with 16-bit: we only want to + try if the operation would have effect on more than two bytes. */ + + stage_val_type = STAGE_VAL_LE; + + if ((orig & 0xffff) + j > 0xffff && !could_be_bitflip(r1)) { + + stage_cur_val = j; + *(u32*)(out_buf + i) = orig + j; + + if (common_fuzz_stuff(argv, out_buf, len)) goto abandon_entry; + stage_cur++; + + } else stage_max--; + + if ((orig & 0xffff) < j && !could_be_bitflip(r2)) { + + stage_cur_val = -j; + *(u32*)(out_buf + i) = orig - j; + + if (common_fuzz_stuff(argv, out_buf, len)) goto abandon_entry; + stage_cur++; + + } else stage_max--; + + /* Big endian next. */ + + stage_val_type = STAGE_VAL_BE; + + if ((SWAP32(orig) & 0xffff) + j > 0xffff && !could_be_bitflip(r3)) { + + stage_cur_val = j; + *(u32*)(out_buf + i) = SWAP32(SWAP32(orig) + j); + + if (common_fuzz_stuff(argv, out_buf, len)) goto abandon_entry; + stage_cur++; + + } else stage_max--; + + if ((SWAP32(orig) & 0xffff) < j && !could_be_bitflip(r4)) { + + stage_cur_val = -j; + *(u32*)(out_buf + i) = SWAP32(SWAP32(orig) - j); + + if (common_fuzz_stuff(argv, out_buf, len)) goto abandon_entry; + stage_cur++; + + } else stage_max--; + + *(u32*)(out_buf + i) = orig; + + } + + } + + new_hit_cnt = queued_paths + unique_crashes; + + stage_finds[STAGE_ARITH32] += new_hit_cnt - orig_hit_cnt; + stage_cycles[STAGE_ARITH32] += stage_max; + +skip_arith: + + /********************** + * INTERESTING VALUES * + **********************/ + + stage_name = "interest 8/8"; + stage_short = "int8"; + stage_cur = 0; + stage_max = len * sizeof(interesting_8); + + stage_val_type = STAGE_VAL_LE; + + orig_hit_cnt = new_hit_cnt; + + /* Setting 8-bit integers. */ + + for (i = 0; i < len; i++) { + + u8 orig = out_buf[i]; + + /* Let's consult the effector map... */ + + if (!eff_map[EFF_APOS(i)]) { + stage_max -= sizeof(interesting_8); + continue; + } + + stage_cur_byte = i; + + for (j = 0; j < sizeof(interesting_8); j++) { + + /* Skip if the value could be a product of bitflips or arithmetics. */ + + if (could_be_bitflip(orig ^ (u8)interesting_8[j]) || + could_be_arith(orig, (u8)interesting_8[j], 1)) { + stage_max--; + continue; + } + + stage_cur_val = interesting_8[j]; + out_buf[i] = interesting_8[j]; + + if (common_fuzz_stuff(argv, out_buf, len)) goto abandon_entry; + + out_buf[i] = orig; + stage_cur++; + + } + + } + + new_hit_cnt = queued_paths + unique_crashes; + + stage_finds[STAGE_INTEREST8] += new_hit_cnt - orig_hit_cnt; + stage_cycles[STAGE_INTEREST8] += stage_max; + + /* Setting 16-bit integers, both endians. */ + + if (no_arith || len < 2) goto skip_interest; + + stage_name = "interest 16/8"; + stage_short = "int16"; + stage_cur = 0; + stage_max = 2 * (len - 1) * (sizeof(interesting_16) >> 1); + + orig_hit_cnt = new_hit_cnt; + + for (i = 0; i < len - 1; i++) { + + u16 orig = *(u16*)(out_buf + i); + + /* Let's consult the effector map... */ + + if (!eff_map[EFF_APOS(i)] && !eff_map[EFF_APOS(i + 1)]) { + stage_max -= sizeof(interesting_16); + continue; + } + + stage_cur_byte = i; + + for (j = 0; j < sizeof(interesting_16) / 2; j++) { + + stage_cur_val = interesting_16[j]; + + /* Skip if this could be a product of a bitflip, arithmetics, + or single-byte interesting value insertion. */ + + if (!could_be_bitflip(orig ^ (u16)interesting_16[j]) && + !could_be_arith(orig, (u16)interesting_16[j], 2) && + !could_be_interest(orig, (u16)interesting_16[j], 2, 0)) { + + stage_val_type = STAGE_VAL_LE; + + *(u16*)(out_buf + i) = interesting_16[j]; + + if (common_fuzz_stuff(argv, out_buf, len)) goto abandon_entry; + stage_cur++; + + } else stage_max--; + + if ((u16)interesting_16[j] != SWAP16(interesting_16[j]) && + !could_be_bitflip(orig ^ SWAP16(interesting_16[j])) && + !could_be_arith(orig, SWAP16(interesting_16[j]), 2) && + !could_be_interest(orig, SWAP16(interesting_16[j]), 2, 1)) { + + stage_val_type = STAGE_VAL_BE; + + *(u16*)(out_buf + i) = SWAP16(interesting_16[j]); + if (common_fuzz_stuff(argv, out_buf, len)) goto abandon_entry; + stage_cur++; + + } else stage_max--; + + } + + *(u16*)(out_buf + i) = orig; + + } + + new_hit_cnt = queued_paths + unique_crashes; + + stage_finds[STAGE_INTEREST16] += new_hit_cnt - orig_hit_cnt; + stage_cycles[STAGE_INTEREST16] += stage_max; + + if (len < 4) goto skip_interest; + + /* Setting 32-bit integers, both endians. */ + + stage_name = "interest 32/8"; + stage_short = "int32"; + stage_cur = 0; + stage_max = 2 * (len - 3) * (sizeof(interesting_32) >> 2); + + orig_hit_cnt = new_hit_cnt; + + for (i = 0; i < len - 3; i++) { + + u32 orig = *(u32*)(out_buf + i); + + /* Let's consult the effector map... */ + + if (!eff_map[EFF_APOS(i)] && !eff_map[EFF_APOS(i + 1)] && + !eff_map[EFF_APOS(i + 2)] && !eff_map[EFF_APOS(i + 3)]) { + stage_max -= sizeof(interesting_32) >> 1; + continue; + } + + stage_cur_byte = i; + + for (j = 0; j < sizeof(interesting_32) / 4; j++) { + + stage_cur_val = interesting_32[j]; + + /* Skip if this could be a product of a bitflip, arithmetics, + or word interesting value insertion. */ + + if (!could_be_bitflip(orig ^ (u32)interesting_32[j]) && + !could_be_arith(orig, interesting_32[j], 4) && + !could_be_interest(orig, interesting_32[j], 4, 0)) { + + stage_val_type = STAGE_VAL_LE; + + *(u32*)(out_buf + i) = interesting_32[j]; + + if (common_fuzz_stuff(argv, out_buf, len)) goto abandon_entry; + stage_cur++; + + } else stage_max--; + + if ((u32)interesting_32[j] != SWAP32(interesting_32[j]) && + !could_be_bitflip(orig ^ SWAP32(interesting_32[j])) && + !could_be_arith(orig, SWAP32(interesting_32[j]), 4) && + !could_be_interest(orig, SWAP32(interesting_32[j]), 4, 1)) { + + stage_val_type = STAGE_VAL_BE; + + *(u32*)(out_buf + i) = SWAP32(interesting_32[j]); + if (common_fuzz_stuff(argv, out_buf, len)) goto abandon_entry; + stage_cur++; + + } else stage_max--; + + } + + *(u32*)(out_buf + i) = orig; + + } + + new_hit_cnt = queued_paths + unique_crashes; + + stage_finds[STAGE_INTEREST32] += new_hit_cnt - orig_hit_cnt; + stage_cycles[STAGE_INTEREST32] += stage_max; + +skip_interest: + + /******************** + * DICTIONARY STUFF * + ********************/ + + if (!extras_cnt) goto skip_user_extras; + + /* Overwrite with user-supplied extras. */ + + stage_name = "user extras (over)"; + stage_short = "ext_UO"; + stage_cur = 0; + stage_max = extras_cnt * len; + + stage_val_type = STAGE_VAL_NONE; + + orig_hit_cnt = new_hit_cnt; + + for (i = 0; i < len; i++) { + + u32 last_len = 0; + + stage_cur_byte = i; + + /* Extras are sorted by size, from smallest to largest. This means + that we don't have to worry about restoring the buffer in + between writes at a particular offset determined by the outer + loop. */ + + for (j = 0; j < extras_cnt; j++) { + + /* Skip extras probabilistically if extras_cnt > MAX_DET_EXTRAS. Also + skip them if there's no room to insert the payload, if the token + is redundant, or if its entire span has no bytes set in the effector + map. */ + + if ((extras_cnt > MAX_DET_EXTRAS && UR(extras_cnt) >= MAX_DET_EXTRAS) || + extras[j].len > len - i || + !memcmp(extras[j].data, out_buf + i, extras[j].len) || + !memchr(eff_map + EFF_APOS(i), 1, EFF_SPAN_ALEN(i, extras[j].len))) { + + stage_max--; + continue; + + } + + last_len = extras[j].len; + memcpy(out_buf + i, extras[j].data, last_len); + + if (common_fuzz_stuff(argv, out_buf, len)) goto abandon_entry; + + stage_cur++; + + } + + /* Restore all the clobbered memory. */ + memcpy(out_buf + i, in_buf + i, last_len); + + } + + new_hit_cnt = queued_paths + unique_crashes; + + stage_finds[STAGE_EXTRAS_UO] += new_hit_cnt - orig_hit_cnt; + stage_cycles[STAGE_EXTRAS_UO] += stage_max; + + /* Insertion of user-supplied extras. */ + + stage_name = "user extras (insert)"; + stage_short = "ext_UI"; + stage_cur = 0; + stage_max = extras_cnt * (len + 1); + + orig_hit_cnt = new_hit_cnt; + + ex_tmp = ck_alloc(len + MAX_DICT_FILE); + + for (i = 0; i <= len; i++) { + + stage_cur_byte = i; + + for (j = 0; j < extras_cnt; j++) { + + if (len + extras[j].len > MAX_FILE) { + stage_max--; + continue; + } + + /* Insert token */ + memcpy(ex_tmp + i, extras[j].data, extras[j].len); + + /* Copy tail */ + memcpy(ex_tmp + i + extras[j].len, out_buf + i, len - i); + + if (common_fuzz_stuff(argv, ex_tmp, len + extras[j].len)) { + ck_free(ex_tmp); + goto abandon_entry; + } + + stage_cur++; + + } + + /* Copy head */ + ex_tmp[i] = out_buf[i]; + + } + + ck_free(ex_tmp); + + new_hit_cnt = queued_paths + unique_crashes; + + stage_finds[STAGE_EXTRAS_UI] += new_hit_cnt - orig_hit_cnt; + stage_cycles[STAGE_EXTRAS_UI] += stage_max; + +skip_user_extras: + + if (!a_extras_cnt) goto skip_extras; + + stage_name = "auto extras (over)"; + stage_short = "ext_AO"; + stage_cur = 0; + stage_max = MIN(a_extras_cnt, USE_AUTO_EXTRAS) * len; + + stage_val_type = STAGE_VAL_NONE; + + orig_hit_cnt = new_hit_cnt; + + for (i = 0; i < len; i++) { + + u32 last_len = 0; + + stage_cur_byte = i; + + for (j = 0; j < MIN(a_extras_cnt, USE_AUTO_EXTRAS); j++) { + + /* See the comment in the earlier code; extras are sorted by size. */ + + if (a_extras[j].len > len - i || + !memcmp(a_extras[j].data, out_buf + i, a_extras[j].len) || + !memchr(eff_map + EFF_APOS(i), 1, EFF_SPAN_ALEN(i, a_extras[j].len))) { + + stage_max--; + continue; + + } + + last_len = a_extras[j].len; + memcpy(out_buf + i, a_extras[j].data, last_len); + + if (common_fuzz_stuff(argv, out_buf, len)) goto abandon_entry; + + stage_cur++; + + } + + /* Restore all the clobbered memory. */ + memcpy(out_buf + i, in_buf + i, last_len); + + } + + new_hit_cnt = queued_paths + unique_crashes; + + stage_finds[STAGE_EXTRAS_AO] += new_hit_cnt - orig_hit_cnt; + stage_cycles[STAGE_EXTRAS_AO] += stage_max; + +skip_extras: + + /* If we made this to here without jumping to havoc_stage or abandon_entry, + we're properly done with deterministic steps and can mark it as such + in the .state/ directory. */ + + if (!queue_cur->passed_det) mark_as_det_done(queue_cur); + + /**************** + * RANDOM HAVOC * + ****************/ + +havoc_stage: + + stage_cur_byte = -1; + + /* The havoc stage mutation code is also invoked when splicing files; if the + splice_cycle variable is set, generate different descriptions and such. */ + + if (!splice_cycle) { + + stage_name = "havoc"; + stage_short = "havoc"; + stage_max = (doing_det ? HAVOC_CYCLES_INIT : HAVOC_CYCLES) * + perf_score / havoc_div / 100; + + } else { + + static u8 tmp[32]; + + perf_score = orig_perf; + + sprintf(tmp, "splice %u", splice_cycle); + stage_name = tmp; + stage_short = "splice"; + stage_max = SPLICE_HAVOC * perf_score / havoc_div / 100; + + } + + if (stage_max < HAVOC_MIN) stage_max = HAVOC_MIN; + + temp_len = len; + + orig_hit_cnt = queued_paths + unique_crashes; + + havoc_queued = queued_paths; + + /* We essentially just do several thousand runs (depending on perf_score) + where we take the input file and make random stacked tweaks. */ + + for (stage_cur = 0; stage_cur < stage_max; stage_cur++) { + + u32 use_stacking = 1 << (1 + UR(HAVOC_STACK_POW2)); + + stage_cur_val = use_stacking; + + for (i = 0; i < use_stacking; i++) { + + switch (UR(15 + ((extras_cnt + a_extras_cnt) ? 2 : 0))) { + + case 0: + + /* Flip a single bit somewhere. Spooky! */ + + FLIP_BIT(out_buf, UR(temp_len << 3)); + break; + + case 1: + + /* Set byte to interesting value. */ + + out_buf[UR(temp_len)] = interesting_8[UR(sizeof(interesting_8))]; + break; + + case 2: + + /* Set word to interesting value, randomly choosing endian. */ + + if (temp_len < 2) break; + + if (UR(2)) { + + *(u16*)(out_buf + UR(temp_len - 1)) = + interesting_16[UR(sizeof(interesting_16) >> 1)]; + + } else { + + *(u16*)(out_buf + UR(temp_len - 1)) = SWAP16( + interesting_16[UR(sizeof(interesting_16) >> 1)]); + + } + + break; + + case 3: + + /* Set dword to interesting value, randomly choosing endian. */ + + if (temp_len < 4) break; + + if (UR(2)) { + + *(u32*)(out_buf + UR(temp_len - 3)) = + interesting_32[UR(sizeof(interesting_32) >> 2)]; + + } else { + + *(u32*)(out_buf + UR(temp_len - 3)) = SWAP32( + interesting_32[UR(sizeof(interesting_32) >> 2)]); + + } + + break; + + case 4: + + /* Randomly subtract from byte. */ + + out_buf[UR(temp_len)] -= 1 + UR(ARITH_MAX); + break; + + case 5: + + /* Randomly add to byte. */ + + out_buf[UR(temp_len)] += 1 + UR(ARITH_MAX); + break; + + case 6: + + /* Randomly subtract from word, random endian. */ + + if (temp_len < 2) break; + + if (UR(2)) { + + u32 pos = UR(temp_len - 1); + + *(u16*)(out_buf + pos) -= 1 + UR(ARITH_MAX); + + } else { + + u32 pos = UR(temp_len - 1); + u16 num = 1 + UR(ARITH_MAX); + + *(u16*)(out_buf + pos) = + SWAP16(SWAP16(*(u16*)(out_buf + pos)) - num); + + } + + break; + + case 7: + + /* Randomly add to word, random endian. */ + + if (temp_len < 2) break; + + if (UR(2)) { + + u32 pos = UR(temp_len - 1); + + *(u16*)(out_buf + pos) += 1 + UR(ARITH_MAX); + + } else { + + u32 pos = UR(temp_len - 1); + u16 num = 1 + UR(ARITH_MAX); + + *(u16*)(out_buf + pos) = + SWAP16(SWAP16(*(u16*)(out_buf + pos)) + num); + + } + + break; + + case 8: + + /* Randomly subtract from dword, random endian. */ + + if (temp_len < 4) break; + + if (UR(2)) { + + u32 pos = UR(temp_len - 3); + + *(u32*)(out_buf + pos) -= 1 + UR(ARITH_MAX); + + } else { + + u32 pos = UR(temp_len - 3); + u32 num = 1 + UR(ARITH_MAX); + + *(u32*)(out_buf + pos) = + SWAP32(SWAP32(*(u32*)(out_buf + pos)) - num); + + } + + break; + + case 9: + + /* Randomly add to dword, random endian. */ + + if (temp_len < 4) break; + + if (UR(2)) { + + u32 pos = UR(temp_len - 3); + + *(u32*)(out_buf + pos) += 1 + UR(ARITH_MAX); + + } else { + + u32 pos = UR(temp_len - 3); + u32 num = 1 + UR(ARITH_MAX); + + *(u32*)(out_buf + pos) = + SWAP32(SWAP32(*(u32*)(out_buf + pos)) + num); + + } + + break; + + case 10: + + /* Just set a random byte to a random value. Because, + why not. We use XOR with 1-255 to eliminate the + possibility of a no-op. */ + + out_buf[UR(temp_len)] ^= 1 + UR(255); + break; + + case 11 ... 12: { + + /* Delete bytes. We're making this a bit more likely + than insertion (the next option) in hopes of keeping + files reasonably small. */ + + u32 del_from, del_len; + + if (temp_len < 2) break; + + /* Don't delete too much. */ + + del_len = choose_block_len(temp_len - 1); + + del_from = UR(temp_len - del_len + 1); + + memmove(out_buf + del_from, out_buf + del_from + del_len, + temp_len - del_from - del_len); + + temp_len -= del_len; + + break; + + } + + case 13: + + if (temp_len + HAVOC_BLK_XL < MAX_FILE) { + + /* Clone bytes (75%) or insert a block of constant bytes (25%). */ + + u8 actually_clone = UR(4); + u32 clone_from, clone_to, clone_len; + u8* new_buf; + + if (actually_clone) { + + clone_len = choose_block_len(temp_len); + clone_from = UR(temp_len - clone_len + 1); + + } else { + + clone_len = choose_block_len(HAVOC_BLK_XL); + clone_from = 0; + + } + + clone_to = UR(temp_len); + + new_buf = ck_alloc_nozero(temp_len + clone_len); + + /* Head */ + + memcpy(new_buf, out_buf, clone_to); + + /* Inserted part */ + + if (actually_clone) + memcpy(new_buf + clone_to, out_buf + clone_from, clone_len); + else + memset(new_buf + clone_to, + UR(2) ? UR(256) : out_buf[UR(temp_len)], clone_len); + + /* Tail */ + memcpy(new_buf + clone_to + clone_len, out_buf + clone_to, + temp_len - clone_to); + + ck_free(out_buf); + out_buf = new_buf; + temp_len += clone_len; + + } + + break; + + case 14: { + + /* Overwrite bytes with a randomly selected chunk (75%) or fixed + bytes (25%). */ + + u32 copy_from, copy_to, copy_len; + + if (temp_len < 2) break; + + copy_len = choose_block_len(temp_len - 1); + + copy_from = UR(temp_len - copy_len + 1); + copy_to = UR(temp_len - copy_len + 1); + + if (UR(4)) { + + if (copy_from != copy_to) + memmove(out_buf + copy_to, out_buf + copy_from, copy_len); + + } else memset(out_buf + copy_to, + UR(2) ? UR(256) : out_buf[UR(temp_len)], copy_len); + + break; + + } + + /* Values 15 and 16 can be selected only if there are any extras + present in the dictionaries. */ + + case 15: { + + /* Overwrite bytes with an extra. */ + + if (!extras_cnt || (a_extras_cnt && UR(2))) { + + /* No user-specified extras or odds in our favor. Let's use an + auto-detected one. */ + + u32 use_extra = UR(a_extras_cnt); + u32 extra_len = a_extras[use_extra].len; + u32 insert_at; + + if (extra_len > temp_len) break; + + insert_at = UR(temp_len - extra_len + 1); + memcpy(out_buf + insert_at, a_extras[use_extra].data, extra_len); + + } else { + + /* No auto extras or odds in our favor. Use the dictionary. */ + + u32 use_extra = UR(extras_cnt); + u32 extra_len = extras[use_extra].len; + u32 insert_at; + + if (extra_len > temp_len) break; + + insert_at = UR(temp_len - extra_len + 1); + memcpy(out_buf + insert_at, extras[use_extra].data, extra_len); + + } + + break; + + } + + case 16: { + + u32 use_extra, extra_len, insert_at = UR(temp_len + 1); + u8* new_buf; + + /* Insert an extra. Do the same dice-rolling stuff as for the + previous case. */ + + if (!extras_cnt || (a_extras_cnt && UR(2))) { + + use_extra = UR(a_extras_cnt); + extra_len = a_extras[use_extra].len; + + if (temp_len + extra_len >= MAX_FILE) break; + + new_buf = ck_alloc_nozero(temp_len + extra_len); + + /* Head */ + memcpy(new_buf, out_buf, insert_at); + + /* Inserted part */ + memcpy(new_buf + insert_at, a_extras[use_extra].data, extra_len); + + } else { + + use_extra = UR(extras_cnt); + extra_len = extras[use_extra].len; + + if (temp_len + extra_len >= MAX_FILE) break; + + new_buf = ck_alloc_nozero(temp_len + extra_len); + + /* Head */ + memcpy(new_buf, out_buf, insert_at); + + /* Inserted part */ + memcpy(new_buf + insert_at, extras[use_extra].data, extra_len); + + } + + /* Tail */ + memcpy(new_buf + insert_at + extra_len, out_buf + insert_at, + temp_len - insert_at); + + ck_free(out_buf); + out_buf = new_buf; + temp_len += extra_len; + + break; + + } + + } + + } + + if (common_fuzz_stuff(argv, out_buf, temp_len)) + goto abandon_entry; + + /* out_buf might have been mangled a bit, so let's restore it to its + original size and shape. */ + + if (temp_len < len) out_buf = ck_realloc(out_buf, len); + temp_len = len; + memcpy(out_buf, in_buf, len); + + /* If we're finding new stuff, let's run for a bit longer, limits + permitting. */ + + if (queued_paths != havoc_queued) { + + if (perf_score <= HAVOC_MAX_MULT * 100) { + stage_max *= 2; + perf_score *= 2; + } + + havoc_queued = queued_paths; + + } + + } + + new_hit_cnt = queued_paths + unique_crashes; + + if (!splice_cycle) { + stage_finds[STAGE_HAVOC] += new_hit_cnt - orig_hit_cnt; + stage_cycles[STAGE_HAVOC] += stage_max; + } else { + stage_finds[STAGE_SPLICE] += new_hit_cnt - orig_hit_cnt; + stage_cycles[STAGE_SPLICE] += stage_max; + } + +#ifndef IGNORE_FINDS + + /************ + * SPLICING * + ************/ + + /* This is a last-resort strategy triggered by a full round with no findings. + It takes the current input file, randomly selects another input, and + splices them together at some offset, then relies on the havoc + code to mutate that blob. */ + +retry_splicing: + + if (use_splicing && splice_cycle++ < SPLICE_CYCLES && + queued_paths > 1 && queue_cur->len > 1) { + + struct queue_entry* target; + u32 tid, split_at; + u8* new_buf; + s32 f_diff, l_diff; + + /* First of all, if we've modified in_buf for havoc, let's clean that + up... */ + + if (in_buf != orig_in) { + ck_free(in_buf); + in_buf = orig_in; + len = queue_cur->len; + } + + /* Pick a random queue entry and seek to it. Don't splice with yourself. */ + + do { tid = UR(queued_paths); } while (tid == current_entry); + + splicing_with = tid; + target = queue; + + while (tid >= 100) { target = target->next_100; tid -= 100; } + while (tid--) target = target->next; + + /* Make sure that the target has a reasonable length. */ + + while (target && (target->len < 2 || target == queue_cur)) { + target = target->next; + splicing_with++; + } + + if (!target) goto retry_splicing; + + /* Read the testcase into a new buffer. */ + + fd = open(target->fname, O_RDONLY); + + if (fd < 0) PFATAL("Unable to open '%s'", target->fname); + + new_buf = ck_alloc_nozero(target->len); + + ck_read(fd, new_buf, target->len, target->fname); + + close(fd); + + /* Find a suitable splicing location, somewhere between the first and + the last differing byte. Bail out if the difference is just a single + byte or so. */ + + locate_diffs(in_buf, new_buf, MIN(len, target->len), &f_diff, &l_diff); + + if (f_diff < 0 || l_diff < 2 || f_diff == l_diff) { + ck_free(new_buf); + goto retry_splicing; + } + + /* Split somewhere between the first and last differing byte. */ + + split_at = f_diff + UR(l_diff - f_diff); + + /* Do the thing. */ + + len = target->len; + memcpy(new_buf, in_buf, split_at); + in_buf = new_buf; + + ck_free(out_buf); + out_buf = ck_alloc_nozero(len); + memcpy(out_buf, in_buf, len); + + goto havoc_stage; + + } + +#endif /* !IGNORE_FINDS */ + + ret_val = 0; + +abandon_entry: + + splicing_with = -1; + + /* Update pending_not_fuzzed count if we made it through the calibration + cycle and have not seen this entry before. */ + + if (!stop_soon && !queue_cur->cal_failed && !queue_cur->was_fuzzed) { + queue_cur->was_fuzzed = 1; + pending_not_fuzzed--; + if (queue_cur->favored) pending_favored--; + } + + munmap(orig_in, queue_cur->len); + + if (in_buf != orig_in) ck_free(in_buf); + ck_free(out_buf); + ck_free(eff_map); + + return ret_val; + +#undef FLIP_BIT + +} + + +/* Grab interesting test cases from other fuzzers. */ + +static void sync_fuzzers(char** argv) { + + DIR* sd; + struct dirent* sd_ent; + u32 sync_cnt = 0; + + sd = opendir(sync_dir); + if (!sd) PFATAL("Unable to open '%s'", sync_dir); + + stage_max = stage_cur = 0; + cur_depth = 0; + + /* Look at the entries created for every other fuzzer in the sync directory. */ + + while ((sd_ent = readdir(sd))) { + + static u8 stage_tmp[128]; + + DIR* qd; + struct dirent* qd_ent; + u8 *qd_path, *qd_synced_path; + u32 min_accept = 0, next_min_accept; + + s32 id_fd; + + /* Skip dot files and our own output directory. */ + + if (sd_ent->d_name[0] == '.' || !strcmp(sync_id, sd_ent->d_name)) continue; + + /* Skip anything that doesn't have a queue/ subdirectory. */ + + qd_path = alloc_printf("%s/%s/queue", sync_dir, sd_ent->d_name); + + if (!(qd = opendir(qd_path))) { + ck_free(qd_path); + continue; + } + + /* Retrieve the ID of the last seen test case. */ + + qd_synced_path = alloc_printf("%s/.synced/%s", out_dir, sd_ent->d_name); + + id_fd = open(qd_synced_path, O_RDWR | O_CREAT, 0600); + + if (id_fd < 0) PFATAL("Unable to create '%s'", qd_synced_path); + + if (read(id_fd, &min_accept, sizeof(u32)) > 0) + lseek(id_fd, 0, SEEK_SET); + + next_min_accept = min_accept; + + /* Show stats */ + + sprintf(stage_tmp, "sync %u", ++sync_cnt); + stage_name = stage_tmp; + stage_cur = 0; + stage_max = 0; + + /* For every file queued by this fuzzer, parse ID and see if we have looked at + it before; exec a test case if not. */ + + while ((qd_ent = readdir(qd))) { + + u8* path; + s32 fd; + struct stat st; + + if (qd_ent->d_name[0] == '.' || + sscanf(qd_ent->d_name, CASE_PREFIX "%06u", &syncing_case) != 1 || + syncing_case < min_accept) continue; + + /* OK, sounds like a new one. Let's give it a try. */ + + if (syncing_case >= next_min_accept) + next_min_accept = syncing_case + 1; + + path = alloc_printf("%s/%s", qd_path, qd_ent->d_name); + + /* Allow this to fail in case the other fuzzer is resuming or so... */ + + fd = open(path, O_RDONLY); + + if (fd < 0) { + ck_free(path); + continue; + } + + if (fstat(fd, &st)) PFATAL("fstat() failed"); + + /* Ignore zero-sized or oversized files. */ + + if (st.st_size && st.st_size <= MAX_FILE) { + + u8 fault; + u8* mem = mmap(0, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0); + + if (mem == MAP_FAILED) PFATAL("Unable to mmap '%s'", path); + + /* See what happens. We rely on save_if_interesting() to catch major + errors and save the test case. */ + + write_to_testcase(mem, st.st_size); + + fault = run_target(argv, exec_tmout); + + if (stop_soon) return; + + syncing_party = sd_ent->d_name; + queued_imported += save_if_interesting(argv, mem, st.st_size, fault); + syncing_party = 0; + + munmap(mem, st.st_size); + + if (!(stage_cur++ % stats_update_freq)) show_stats(); + + } + + ck_free(path); + close(fd); + + } + + ck_write(id_fd, &next_min_accept, sizeof(u32), qd_synced_path); + + close(id_fd); + closedir(qd); + ck_free(qd_path); + ck_free(qd_synced_path); + + } + + closedir(sd); + +} + + +/* Handle stop signal (Ctrl-C, etc). */ + +static void handle_stop_sig(int sig) { + + stop_soon = 1; + + if (child_pid > 0) kill(child_pid, SIGKILL); + if (forksrv_pid > 0) kill(forksrv_pid, SIGKILL); + +} + + +/* Handle skip request (SIGUSR1). */ + +static void handle_skipreq(int sig) { + + skip_requested = 1; + +} + +/* Handle timeout (SIGALRM). */ + +static void handle_timeout(int sig) { + + if (child_pid > 0) { + + child_timed_out = 1; + kill(child_pid, SIGKILL); + + } else if (child_pid == -1 && forksrv_pid > 0) { + + child_timed_out = 1; + kill(forksrv_pid, SIGKILL); + + } + +} + + +/* Do a PATH search and find target binary to see that it exists and + isn't a shell script - a common and painful mistake. We also check for + a valid ELF header and for evidence of AFL instrumentation. */ + +EXP_ST void check_binary(u8* fname) { + + u8* env_path = 0; + struct stat st; + + s32 fd; + u8* f_data; + u32 f_len = 0; + + ACTF("Validating target binary..."); + + if (strchr(fname, '/') || !(env_path = getenv("PATH"))) { + + target_path = ck_strdup(fname); + if (stat(target_path, &st) || !S_ISREG(st.st_mode) || + !(st.st_mode & 0111) || (f_len = st.st_size) < 4) + FATAL("Program '%s' not found or not executable", fname); + + } else { + + 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); + + 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) && (f_len = st.st_size) >= 4) break; + + ck_free(target_path); + target_path = 0; + + } + + if (!target_path) FATAL("Program '%s' not found or not executable", fname); + + } + + if (getenv("AFL_SKIP_BIN_CHECK")) return; + + /* Check for blatant user errors. */ + + if ((!strncmp(target_path, "/tmp/", 5) && !strchr(target_path + 5, '/')) || + (!strncmp(target_path, "/var/tmp/", 9) && !strchr(target_path + 9, '/'))) + FATAL("Please don't keep binaries in /tmp or /var/tmp"); + + fd = open(target_path, O_RDONLY); + + if (fd < 0) PFATAL("Unable to open '%s'", target_path); + + f_data = mmap(0, f_len, PROT_READ, MAP_PRIVATE, fd, 0); + + if (f_data == MAP_FAILED) PFATAL("Unable to mmap file '%s'", target_path); + + close(fd); + + if (f_data[0] == '#' && f_data[1] == '!') { + + SAYF("\n" cLRD "[-] " cRST + "Oops, the target binary looks like a shell script. Some build systems will\n" + " sometimes generate shell stubs for dynamically linked programs; try static\n" + " library mode (./configure --disable-shared) if that's the case.\n\n" + + " Another possible cause is that you are actually trying to use a shell\n" + " wrapper around the fuzzed component. Invoking shell can slow down the\n" + " fuzzing process by a factor of 20x or more; it's best to write the wrapper\n" + " in a compiled language instead.\n"); + + FATAL("Program '%s' is a shell script", target_path); + + } + +#ifndef __APPLE__ + + if (f_data[0] != 0x7f || memcmp(f_data + 1, "ELF", 3)) + FATAL("Program '%s' is not an ELF binary", target_path); + +#else + + if (f_data[0] != 0xCF || f_data[1] != 0xFA || f_data[2] != 0xED) + FATAL("Program '%s' is not a 64-bit Mach-O binary", target_path); + +#endif /* ^!__APPLE__ */ + + if (!qemu_mode && !dumb_mode && + !memmem(f_data, f_len, SHM_ENV_VAR, strlen(SHM_ENV_VAR) + 1)) { + + SAYF("\n" cLRD "[-] " cRST + "Looks like the target binary is not instrumented! The fuzzer depends on\n" + " compile-time instrumentation to isolate interesting test cases while\n" + " mutating the input data. For more information, and for tips on how to\n" + " instrument binaries, please see %s/README.\n\n" + + " When source code is not available, you may be able to leverage QEMU\n" + " mode support. Consult the README for tips on how to enable this.\n" + + " (It is also possible to use afl-fuzz as a traditional, \"dumb\" fuzzer.\n" + " For that, you can use the -n option - but expect much worse results.)\n", + doc_path); + + FATAL("No instrumentation detected"); + + } + + if (qemu_mode && + memmem(f_data, f_len, SHM_ENV_VAR, strlen(SHM_ENV_VAR) + 1)) { + + SAYF("\n" cLRD "[-] " cRST + "This program appears to be instrumented with afl-gcc, but is being run in\n" + " QEMU mode (-Q). This is probably not what you want - this setup will be\n" + " slow and offer no practical benefits.\n"); + + FATAL("Instrumentation found in -Q mode"); + + } + + if (memmem(f_data, f_len, "libasan.so", 10) || + memmem(f_data, f_len, "__msan_init", 11)) uses_asan = 1; + + /* Detect persistent & deferred init signatures in the binary. */ + + if (memmem(f_data, f_len, PERSIST_SIG, strlen(PERSIST_SIG) + 1)) { + + OKF(cPIN "Persistent mode binary detected."); + setenv(PERSIST_ENV_VAR, "1", 1); + persistent_mode = 1; + + } else if (getenv("AFL_PERSISTENT")) { + + WARNF("AFL_PERSISTENT is no longer supported and may misbehave!"); + + } + + if (memmem(f_data, f_len, DEFER_SIG, strlen(DEFER_SIG) + 1)) { + + OKF(cPIN "Deferred forkserver binary detected."); + setenv(DEFER_ENV_VAR, "1", 1); + deferred_mode = 1; + + } else if (getenv("AFL_DEFER_FORKSRV")) { + + WARNF("AFL_DEFER_FORKSRV is no longer supported and may misbehave!"); + + } + + if (munmap(f_data, f_len)) PFATAL("unmap() failed"); + +} + + +/* Trim and possibly create a banner for the run. */ + +static void fix_up_banner(u8* name) { + + if (!use_banner) { + + if (sync_id) { + + use_banner = sync_id; + + } else { + + u8* trim = strrchr(name, '/'); + if (!trim) use_banner = name; else use_banner = trim + 1; + + } + + } + + if (strlen(use_banner) > 40) { + + u8* tmp = ck_alloc(44); + sprintf(tmp, "%.40s...", use_banner); + use_banner = tmp; + + } + +} + + +/* Check if we're on TTY. */ + +static void check_if_tty(void) { + + struct winsize ws; + + if (getenv("AFL_NO_UI")) { + OKF("Disabling the UI because AFL_NO_UI is set."); + not_on_tty = 1; + return; + } + + if (ioctl(1, TIOCGWINSZ, &ws)) { + + if (errno == ENOTTY) { + OKF("Looks like we're not running on a tty, so I'll be a bit less verbose."); + not_on_tty = 1; + } + + return; + } + +} + + +/* Check terminal dimensions after resize. */ + +static void check_term_size(void) { + + struct winsize ws; + + term_too_small = 0; + + if (ioctl(1, TIOCGWINSZ, &ws)) return; + + if (ws.ws_row == 0 && ws.ws_col == 0) return; + if (ws.ws_row < 25 || ws.ws_col < 80) term_too_small = 1; + +} + + + +/* Display usage hints. */ + +static void usage(u8* argv0) { + + SAYF("\n%s [ options ] -- /path/to/fuzzed_app [ ... ]\n\n" + + "Required parameters:\n\n" + + " -i dir - input directory with test cases\n" + " -o dir - output directory for fuzzer findings\n\n" + + "Execution control settings:\n\n" + + " -f file - location read by the fuzzed program (stdin)\n" + " -t msec - timeout for each run (auto-scaled, 50-%u ms)\n" + " -m megs - memory limit for child process (%u MB)\n" + " -Q - use binary-only instrumentation (QEMU mode)\n\n" + + "Fuzzing behavior settings:\n\n" + + " -d - quick & dirty mode (skips deterministic steps)\n" + " -n - fuzz without instrumentation (dumb mode)\n" + " -x dir - optional fuzzer dictionary (see README)\n\n" + + "Other stuff:\n\n" + + " -T text - text banner to show on the screen\n" + " -M / -S id - distributed mode (see parallel_fuzzing.txt)\n" + " -C - crash exploration mode (the peruvian rabbit thing)\n" + " -V - show version number and exit\n\n" + " -b cpu_id - bind the fuzzing process to the specified CPU core\n\n" + + "For additional tips, please consult %s/README.\n\n", + + argv0, EXEC_TIMEOUT, MEM_LIMIT, doc_path); + + exit(1); + +} + + +/* Prepare output directories and fds. */ + +EXP_ST void setup_dirs_fds(void) { + + u8* tmp; + s32 fd; + + ACTF("Setting up output directories..."); + + if (sync_id && mkdir(sync_dir, 0700) && errno != EEXIST) + PFATAL("Unable to create '%s'", sync_dir); + + if (mkdir(out_dir, 0700)) { + + if (errno != EEXIST) PFATAL("Unable to create '%s'", out_dir); + + maybe_delete_out_dir(); + + } else { + + if (in_place_resume) + FATAL("Resume attempted but old output directory not found"); + + out_dir_fd = open(out_dir, O_RDONLY); + +#ifndef __sun + + if (out_dir_fd < 0 || flock(out_dir_fd, LOCK_EX | LOCK_NB)) + PFATAL("Unable to flock() output directory."); + +#endif /* !__sun */ + + } + + /* Queue directory for any starting & discovered paths. */ + + tmp = alloc_printf("%s/queue", out_dir); + if (mkdir(tmp, 0700)) PFATAL("Unable to create '%s'", tmp); + ck_free(tmp); + + /* Top-level directory for queue metadata used for session + resume and related tasks. */ + + tmp = alloc_printf("%s/queue/.state/", out_dir); + if (mkdir(tmp, 0700)) PFATAL("Unable to create '%s'", tmp); + ck_free(tmp); + + /* Directory for flagging queue entries that went through + deterministic fuzzing in the past. */ + + tmp = alloc_printf("%s/queue/.state/deterministic_done/", out_dir); + if (mkdir(tmp, 0700)) PFATAL("Unable to create '%s'", tmp); + ck_free(tmp); + + /* Directory with the auto-selected dictionary entries. */ + + tmp = alloc_printf("%s/queue/.state/auto_extras/", out_dir); + if (mkdir(tmp, 0700)) PFATAL("Unable to create '%s'", tmp); + ck_free(tmp); + + /* The set of paths currently deemed redundant. */ + + tmp = alloc_printf("%s/queue/.state/redundant_edges/", out_dir); + if (mkdir(tmp, 0700)) PFATAL("Unable to create '%s'", tmp); + ck_free(tmp); + + /* The set of paths showing variable behavior. */ + + tmp = alloc_printf("%s/queue/.state/variable_behavior/", out_dir); + if (mkdir(tmp, 0700)) PFATAL("Unable to create '%s'", tmp); + ck_free(tmp); + + /* Sync directory for keeping track of cooperating fuzzers. */ + + if (sync_id) { + + tmp = alloc_printf("%s/.synced/", out_dir); + + if (mkdir(tmp, 0700) && (!in_place_resume || errno != EEXIST)) + PFATAL("Unable to create '%s'", tmp); + + ck_free(tmp); + + } + + /* All recorded crashes. */ + + tmp = alloc_printf("%s/crashes", out_dir); + if (mkdir(tmp, 0700)) PFATAL("Unable to create '%s'", tmp); + ck_free(tmp); + + /* All recorded hangs. */ + + tmp = alloc_printf("%s/hangs", out_dir); + if (mkdir(tmp, 0700)) PFATAL("Unable to create '%s'", tmp); + ck_free(tmp); + + /* Generally useful file descriptors. */ + + dev_null_fd = open("/dev/null", O_RDWR); + if (dev_null_fd < 0) PFATAL("Unable to open /dev/null"); + + dev_urandom_fd = open("/dev/urandom", O_RDONLY); + if (dev_urandom_fd < 0) PFATAL("Unable to open /dev/urandom"); + + /* Gnuplot output file. */ + + tmp = alloc_printf("%s/plot_data", out_dir); + fd = open(tmp, O_WRONLY | O_CREAT | O_EXCL, 0600); + if (fd < 0) PFATAL("Unable to create '%s'", tmp); + ck_free(tmp); + + plot_file = fdopen(fd, "w"); + if (!plot_file) PFATAL("fdopen() failed"); + + fprintf(plot_file, "# unix_time, cycles_done, cur_path, paths_total, " + "pending_total, pending_favs, map_size, unique_crashes, " + "unique_hangs, max_depth, execs_per_sec\n"); + /* ignore errors */ + +} + + +/* Setup the output file for fuzzed data, if not using -f. */ + +EXP_ST void setup_stdio_file(void) { + + u8* fn = alloc_printf("%s/.cur_input", out_dir); + + unlink(fn); /* Ignore errors */ + + out_fd = open(fn, O_RDWR | O_CREAT | O_EXCL, 0600); + + if (out_fd < 0) PFATAL("Unable to create '%s'", fn); + + ck_free(fn); + +} + + +/* Make sure that core dumps don't go to a program. */ + +static void check_crash_handling(void) { + +#ifdef __APPLE__ + + /* Yuck! There appears to be no simple C API to query for the state of + loaded daemons on MacOS X, and I'm a bit hesitant to do something + more sophisticated, such as disabling crash reporting via Mach ports, + until I get a box to test the code. So, for now, we check for crash + reporting the awful way. */ + + if (system("launchctl list 2>/dev/null | grep -q '\\.ReportCrash$'")) return; + + SAYF("\n" cLRD "[-] " cRST + "Whoops, your system is configured to forward crash notifications to an\n" + " external crash reporting utility. This will cause issues due to the\n" + " extended delay between the fuzzed binary malfunctioning and this fact\n" + " being relayed to the fuzzer via the standard waitpid() API.\n\n" + " To avoid having crashes misinterpreted as timeouts, please run the\n" + " following commands:\n\n" + + " SL=/System/Library; PL=com.apple.ReportCrash\n" + " launchctl unload -w ${SL}/LaunchAgents/${PL}.plist\n" + " sudo launchctl unload -w ${SL}/LaunchDaemons/${PL}.Root.plist\n"); + + if (!getenv("AFL_I_DONT_CARE_ABOUT_MISSING_CRASHES")) + FATAL("Crash reporter detected"); + +#else + + /* This is Linux specific, but I don't think there's anything equivalent on + *BSD, so we can just let it slide for now. */ + + s32 fd = open("/proc/sys/kernel/core_pattern", O_RDONLY); + u8 fchar; + + if (fd < 0) return; + + ACTF("Checking core_pattern..."); + + if (read(fd, &fchar, 1) == 1 && fchar == '|') { + + SAYF("\n" cLRD "[-] " cRST + "Hmm, your system is configured to send core dump notifications to an\n" + " external utility. This will cause issues: there will be an extended delay\n" + " between stumbling upon a crash and having this information relayed to the\n" + " fuzzer via the standard waitpid() API.\n\n" + + " To avoid having crashes misinterpreted as timeouts, please log in as root\n" + " and temporarily modify /proc/sys/kernel/core_pattern, like so:\n\n" + + " echo core >/proc/sys/kernel/core_pattern\n"); + + if (!getenv("AFL_I_DONT_CARE_ABOUT_MISSING_CRASHES")) + FATAL("Pipe at the beginning of 'core_pattern'"); + + } + + close(fd); + +#endif /* ^__APPLE__ */ + +} + + +/* Check CPU governor. */ + +static void check_cpu_governor(void) { + + FILE* f; + u8 tmp[128]; + u64 min = 0, max = 0; + + if (getenv("AFL_SKIP_CPUFREQ")) return; + + f = fopen("/sys/devices/system/cpu/cpu0/cpufreq/scaling_governor", "r"); + if (!f) return; + + ACTF("Checking CPU scaling governor..."); + + if (!fgets(tmp, 128, f)) PFATAL("fgets() failed"); + + fclose(f); + + if (!strncmp(tmp, "perf", 4)) return; + + f = fopen("/sys/devices/system/cpu/cpu0/cpufreq/scaling_min_freq", "r"); + + if (f) { + if (fscanf(f, "%llu", &min) != 1) min = 0; + fclose(f); + } + + f = fopen("/sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq", "r"); + + if (f) { + if (fscanf(f, "%llu", &max) != 1) max = 0; + fclose(f); + } + + if (min == max) return; + + SAYF("\n" cLRD "[-] " cRST + "Whoops, your system uses on-demand CPU frequency scaling, adjusted\n" + " between %llu and %llu MHz. Unfortunately, the scaling algorithm in the\n" + " kernel is imperfect and can miss the short-lived processes spawned by\n" + " afl-fuzz. To keep things moving, run these commands as root:\n\n" + + " cd /sys/devices/system/cpu\n" + " echo performance | tee cpu*/cpufreq/scaling_governor\n\n" + + " You can later go back to the original state by replacing 'performance' with\n" + " 'ondemand'. If you don't want to change the settings, set AFL_SKIP_CPUFREQ\n" + " to make afl-fuzz skip this check - but expect some performance drop.\n", + min / 1024, max / 1024); + + FATAL("Suboptimal CPU scaling governor"); + +} + + +/* Count the number of logical CPU cores. */ + +static void get_core_count(void) { + + u32 cur_runnable = 0; + +#if defined(__APPLE__) || defined(__FreeBSD__) || defined (__OpenBSD__) + + size_t s = sizeof(cpu_core_count); + + /* On *BSD systems, we can just use a sysctl to get the number of CPUs. */ + +#ifdef __APPLE__ + + if (sysctlbyname("hw.logicalcpu", &cpu_core_count, &s, NULL, 0) < 0) + return; + +#else + + int s_name[2] = { CTL_HW, HW_NCPU }; + + if (sysctl(s_name, 2, &cpu_core_count, &s, NULL, 0) < 0) return; + +#endif /* ^__APPLE__ */ + +#else + +#ifdef HAVE_AFFINITY + + cpu_core_count = sysconf(_SC_NPROCESSORS_ONLN); + +#else + + FILE* f = fopen("/proc/stat", "r"); + u8 tmp[1024]; + + if (!f) return; + + while (fgets(tmp, sizeof(tmp), f)) + if (!strncmp(tmp, "cpu", 3) && isdigit(tmp[3])) cpu_core_count++; + + fclose(f); + +#endif /* ^HAVE_AFFINITY */ + +#endif /* ^(__APPLE__ || __FreeBSD__ || __OpenBSD__) */ + + if (cpu_core_count > 0) { + + cur_runnable = (u32)get_runnable_processes(); + +#if defined(__APPLE__) || defined(__FreeBSD__) || defined (__OpenBSD__) + + /* Add ourselves, since the 1-minute average doesn't include that yet. */ + + cur_runnable++; + +#endif /* __APPLE__ || __FreeBSD__ || __OpenBSD__ */ + + OKF("You have %u CPU core%s and %u runnable tasks (utilization: %0.0f%%).", + cpu_core_count, cpu_core_count > 1 ? "s" : "", + cur_runnable, cur_runnable * 100.0 / cpu_core_count); + + if (cpu_core_count > 1) { + + if (cur_runnable > cpu_core_count * 1.5) { + + WARNF("System under apparent load, performance may be spotty."); + + } else if (cur_runnable + 1 <= cpu_core_count) { + + OKF("Try parallel jobs - see %s/parallel_fuzzing.txt.", doc_path); + + } + + } + + } else { + + cpu_core_count = 0; + WARNF("Unable to figure out the number of CPU cores."); + + } + +} + + +/* Validate and fix up out_dir and sync_dir when using -S. */ + +static void fix_up_sync(void) { + + u8* x = sync_id; + + if (dumb_mode) + FATAL("-S / -M and -n are mutually exclusive"); + + if (skip_deterministic) { + + if (force_deterministic) + FATAL("use -S instead of -M -d"); + else + FATAL("-S already implies -d"); + + } + + while (*x) { + + if (!isalnum(*x) && *x != '_' && *x != '-') + FATAL("Non-alphanumeric fuzzer ID specified via -S or -M"); + + x++; + + } + + if (strlen(sync_id) > 32) FATAL("Fuzzer ID too long"); + + x = alloc_printf("%s/%s", out_dir, sync_id); + + sync_dir = out_dir; + out_dir = x; + + if (!force_deterministic) { + skip_deterministic = 1; + use_splicing = 1; + } + +} + + +/* Handle screen resize (SIGWINCH). */ + +static void handle_resize(int sig) { + clear_screen = 1; +} + + +/* Check ASAN options. */ + +static void check_asan_opts(void) { + u8* x = getenv("ASAN_OPTIONS"); + + if (x) { + + if (!strstr(x, "abort_on_error=1")) + FATAL("Custom ASAN_OPTIONS set without abort_on_error=1 - please fix!"); + + if (!strstr(x, "symbolize=0")) + FATAL("Custom ASAN_OPTIONS set without symbolize=0 - please fix!"); + + } + + x = getenv("MSAN_OPTIONS"); + + if (x) { + + if (!strstr(x, "exit_code=" STRINGIFY(MSAN_ERROR))) + FATAL("Custom MSAN_OPTIONS set without exit_code=" + STRINGIFY(MSAN_ERROR) " - please fix!"); + + if (!strstr(x, "symbolize=0")) + FATAL("Custom MSAN_OPTIONS set without symbolize=0 - please fix!"); + + } + +} + + +/* Detect @@ in args. */ + +EXP_ST void detect_file_args(char** argv) { + + u32 i = 0; + u8* cwd = getcwd(NULL, 0); + + if (!cwd) PFATAL("getcwd() failed"); + + while (argv[i]) { + + u8* aa_loc = strstr(argv[i], "@@"); + + if (aa_loc) { + + u8 *aa_subst, *n_arg; + + /* If we don't have a file name chosen yet, use a safe default. */ + + if (!out_file) + out_file = alloc_printf("%s/.cur_input", out_dir); + + /* Be sure that we're always using fully-qualified paths. */ + + if (out_file[0] == '/') aa_subst = out_file; + else aa_subst = alloc_printf("%s/%s", cwd, out_file); + + /* Construct a replacement argv value. */ + + *aa_loc = 0; + n_arg = alloc_printf("%s%s%s", argv[i], aa_subst, aa_loc + 2); + argv[i] = n_arg; + *aa_loc = '@'; + + if (out_file[0] != '/') ck_free(aa_subst); + + } + + i++; + + } + + free(cwd); /* not tracked */ + +} + + +/* Set up signal handlers. More complicated that needs to be, because libc on + Solaris doesn't resume interrupted reads(), sets SA_RESETHAND when you call + siginterrupt(), and does other unnecessary things. */ + +EXP_ST void setup_signal_handlers(void) { + + struct sigaction sa; + + sa.sa_handler = NULL; + sa.sa_flags = SA_RESTART; + sa.sa_sigaction = NULL; + + 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); + + /* Window resize */ + + sa.sa_handler = handle_resize; + sigaction(SIGWINCH, &sa, NULL); + + /* SIGUSR1: skip entry */ + + sa.sa_handler = handle_skipreq; + sigaction(SIGUSR1, &sa, NULL); + + /* Things we don't care about. */ + + sa.sa_handler = SIG_IGN; + sigaction(SIGTSTP, &sa, NULL); + sigaction(SIGPIPE, &sa, NULL); + +} + + +/* Rewrite argv for QEMU. */ + +static char** get_qemu_argv(u8* own_loc, char** argv, int argc) { + + char** new_argv = ck_alloc(sizeof(char*) * (argc + 4)); + u8 *tmp, *cp, *rsl, *own_copy; + + /* Workaround for a QEMU stability glitch. */ + + setenv("QEMU_LOG", "nochain", 1); + + memcpy(new_argv + 3, argv + 1, sizeof(char*) * argc); + + new_argv[2] = target_path; + new_argv[1] = "--"; + + /* Now we need to actually find the QEMU binary to put in argv[0]. */ + + tmp = getenv("AFL_PATH"); + + if (tmp) { + + cp = alloc_printf("%s/afl-qemu-trace", tmp); + + if (access(cp, X_OK)) + FATAL("Unable to find '%s'", tmp); + + target_path = new_argv[0] = cp; + return new_argv; + + } + + own_copy = ck_strdup(own_loc); + rsl = strrchr(own_copy, '/'); + + if (rsl) { + + *rsl = 0; + + cp = alloc_printf("%s/afl-qemu-trace", own_copy); + ck_free(own_copy); + + if (!access(cp, X_OK)) { + + target_path = new_argv[0] = cp; + return new_argv; + + } + + } else ck_free(own_copy); + + if (!access(BIN_PATH "/afl-qemu-trace", X_OK)) { + + target_path = new_argv[0] = ck_strdup(BIN_PATH "/afl-qemu-trace"); + return new_argv; + + } + + SAYF("\n" cLRD "[-] " cRST + "Oops, unable to find the 'afl-qemu-trace' binary. The binary must be built\n" + " separately by following the instructions in qemu_mode/README.qemu. If you\n" + " already have the binary installed, you may need to specify AFL_PATH in the\n" + " environment.\n\n" + + " Of course, even without QEMU, afl-fuzz can still work with binaries that are\n" + " instrumented at compile time with afl-gcc. It is also possible to use it as a\n" + " traditional \"dumb\" fuzzer by specifying '-n' in the command line.\n"); + + FATAL("Failed to locate 'afl-qemu-trace'."); + +} + + +/* Make a copy of the current command line. */ + +static void save_cmdline(u32 argc, char** argv) { + + u32 len = 1, i; + u8* buf; + + for (i = 0; i < argc; i++) + len += strlen(argv[i]) + 1; + + buf = orig_cmdline = ck_alloc(len); + + for (i = 0; i < argc; i++) { + + u32 l = strlen(argv[i]); + + memcpy(buf, argv[i], l); + buf += l; + + if (i != argc - 1) *(buf++) = ' '; + + } + + *buf = 0; + +} + + +#ifndef AFL_LIB + +/* Main entry point */ + +int main(int argc, char** argv) { + + s32 opt; + u64 prev_queued = 0; + u32 sync_interval_cnt = 0, seek_to; + u8 *extras_dir = 0; + u8 mem_limit_given = 0; + u8 exit_1 = !!getenv("AFL_BENCH_JUST_ONE"); + char** use_argv; + + struct timeval tv; + struct timezone tz; + + SAYF(cCYA "afl-fuzz " cBRI VERSION cRST " by \n"); + + doc_path = access(DOC_PATH, F_OK) ? "docs" : DOC_PATH; + + gettimeofday(&tv, &tz); + srandom(tv.tv_sec ^ tv.tv_usec ^ getpid()); + + // argv 处理 + while ((opt = getopt(argc, argv, "+i:o:f:m:b:t:T:dnCB:S:M:x:QV")) > 0) + + switch (opt) { + + case 'i': /* input dir */ + + // 初始 corpus 目录 + if (in_dir) FATAL("Multiple -i options not supported"); + in_dir = optarg; + + // 若使用 "-i -",则表示 in-place resume + if (!strcmp(in_dir, "-")) in_place_resume = 1; + + break; + + case 'o': /* output dir */ + + if (out_dir) FATAL("Multiple -o options not supported"); + out_dir = optarg; + break; + + case 'M': { /* master sync ID */ + + u8* c; + + if (sync_id) FATAL("Multiple -S or -M options not supported"); + sync_id = ck_strdup(optarg); + + if ((c = strchr(sync_id, ':'))) { + + *c = 0; + + if (sscanf(c + 1, "%u/%u", &master_id, &master_max) != 2 || + !master_id || !master_max || master_id > master_max || + master_max > 1000000) FATAL("Bogus master ID passed to -M"); + + } + + force_deterministic = 1; + + } + + break; + + case 'S': + + if (sync_id) FATAL("Multiple -S or -M options not supported"); + sync_id = ck_strdup(optarg); + break; + + case 'f': /* target file */ + + if (out_file) FATAL("Multiple -f options not supported"); + out_file = optarg; + break; + + case 'x': /* dictionary */ + + if (extras_dir) FATAL("Multiple -x options not supported"); + extras_dir = optarg; + break; + + case 't': { /* timeout */ + + u8 suffix = 0; + + if (timeout_given) FATAL("Multiple -t options not supported"); + + if (sscanf(optarg, "%u%c", &exec_tmout, &suffix) < 1 || + optarg[0] == '-') FATAL("Bad syntax used for -t"); + + if (exec_tmout < 5) FATAL("Dangerously low value of -t"); + + if (suffix == '+') timeout_given = 2; else timeout_given = 1; + + break; + + } + + case 'm': { /* mem limit */ + + 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 'b': { /* bind CPU core */ + + if (cpu_to_bind_given) FATAL("Multiple -b options not supported"); + cpu_to_bind_given = 1; + + if (sscanf(optarg, "%u", &cpu_to_bind) < 1 || + optarg[0] == '-') FATAL("Bad syntax used for -b"); + + break; + + } + + case 'd': /* skip deterministic */ + + if (skip_deterministic) FATAL("Multiple -d options not supported"); + skip_deterministic = 1; + use_splicing = 1; + break; + + case 'B': /* load bitmap */ + + /* This is a secret undocumented option! It is useful if you find + an interesting test case during a normal fuzzing process, and want + to mutate it without rediscovering any of the test cases already + found during an earlier run. + + To use this mode, you need to point -B to the fuzz_bitmap produced + by an earlier run for the exact same binary... and that's it. + + I only used this once or twice to get variants of a particular + file, so I'm not making this an official setting. */ + + if (in_bitmap) FATAL("Multiple -B options not supported"); + + in_bitmap = optarg; + read_bitmap(in_bitmap); + break; + + case 'C': /* crash mode */ + + if (crash_mode) FATAL("Multiple -C options not supported"); + crash_mode = FAULT_CRASH; + break; + + case 'n': /* dumb mode */ + + if (dumb_mode) FATAL("Multiple -n options not supported"); + if (getenv("AFL_DUMB_FORKSRV")) dumb_mode = 2; else dumb_mode = 1; + + break; + + case 'T': /* banner */ + + if (use_banner) FATAL("Multiple -T options not supported"); + use_banner = optarg; + break; + + case 'Q': /* QEMU mode */ + + if (qemu_mode) FATAL("Multiple -Q options not supported"); + qemu_mode = 1; + + if (!mem_limit_given) mem_limit = MEM_LIMIT_QEMU; + + break; + + case 'V': /* Show version number */ + + /* Version number has been printed already, just quit. */ + exit(0); + + default: + + usage(argv[0]); + + } + + if (optind == argc || !in_dir || !out_dir) usage(argv[0]); + + setup_signal_handlers(); + check_asan_opts(); + + if (sync_id) fix_up_sync(); + + if (!strcmp(in_dir, out_dir)) + FATAL("Input and output directories can't be the same"); + + if (dumb_mode) { + + if (crash_mode) FATAL("-C and -n are mutually exclusive"); + if (qemu_mode) FATAL("-Q and -n are mutually exclusive"); + + } + + if (getenv("AFL_NO_FORKSRV")) no_forkserver = 1; + if (getenv("AFL_NO_CPU_RED")) no_cpu_meter_red = 1; + if (getenv("AFL_NO_ARITH")) no_arith = 1; + if (getenv("AFL_SHUFFLE_QUEUE")) shuffle_queue = 1; + if (getenv("AFL_FAST_CAL")) fast_cal = 1; + + if (getenv("AFL_HANG_TMOUT")) { + hang_tmout = atoi(getenv("AFL_HANG_TMOUT")); + if (!hang_tmout) FATAL("Invalid value of AFL_HANG_TMOUT"); + } + + if (dumb_mode == 2 && no_forkserver) + FATAL("AFL_DUMB_FORKSRV and AFL_NO_FORKSRV are mutually exclusive"); + + if (getenv("AFL_PRELOAD")) { + setenv("LD_PRELOAD", getenv("AFL_PRELOAD"), 1); + setenv("DYLD_INSERT_LIBRARIES", getenv("AFL_PRELOAD"), 1); + } + + if (getenv("AFL_LD_PRELOAD")) + FATAL("Use AFL_PRELOAD instead of AFL_LD_PRELOAD"); + + save_cmdline(argc, argv); + + fix_up_banner(argv[optind]); + + check_if_tty(); + + get_core_count(); + +#ifdef HAVE_AFFINITY + bind_to_free_cpu(); +#endif /* HAVE_AFFINITY */ + + check_crash_handling(); + check_cpu_governor(); + + setup_post(); + setup_shm(); + init_count_class16(); + + setup_dirs_fds(); + read_testcases(); + load_auto(); + + pivot_inputs(); + + if (extras_dir) load_extras(extras_dir); + + if (!timeout_given) find_timeout(); + + detect_file_args(argv + optind + 1); + + if (!out_file) setup_stdio_file(); + + check_binary(argv[optind]); + + start_time = get_cur_time(); + + if (qemu_mode) + use_argv = get_qemu_argv(argv[0], argv + optind, argc - optind); + else + use_argv = argv + optind; + + perform_dry_run(use_argv); + + cull_queue(); + + show_init_stats(); + + seek_to = find_start_position(); + + write_stats_file(0, 0, 0); + save_auto(); + + if (stop_soon) goto stop_fuzzing; + + /* Woop woop woop */ + + if (!not_on_tty) { + sleep(4); + start_time += 4000; + if (stop_soon) goto stop_fuzzing; + } + + while (1) { + + u8 skipped_fuzz; + + cull_queue(); + + if (!queue_cur) { + + queue_cycle++; + current_entry = 0; + cur_skipped_paths = 0; + queue_cur = queue; + + while (seek_to) { + current_entry++; + seek_to--; + queue_cur = queue_cur->next; + } + + show_stats(); + + if (not_on_tty) { + ACTF("Entering queue cycle %llu.", queue_cycle); + fflush(stdout); + } + + /* If we had a full queue cycle with no new finds, try + recombination strategies next. */ + + if (queued_paths == prev_queued) { + + if (use_splicing) cycles_wo_finds++; else use_splicing = 1; + + } else cycles_wo_finds = 0; + + prev_queued = queued_paths; + + if (sync_id && queue_cycle == 1 && getenv("AFL_IMPORT_FIRST")) + sync_fuzzers(use_argv); + + } + + skipped_fuzz = fuzz_one(use_argv); + + if (!stop_soon && sync_id && !skipped_fuzz) { + + if (!(sync_interval_cnt++ % SYNC_INTERVAL)) + sync_fuzzers(use_argv); + + } + + if (!stop_soon && exit_1) stop_soon = 2; + + if (stop_soon) break; + + queue_cur = queue_cur->next; + current_entry++; + + } + + if (queue_cur) show_stats(); + + /* If we stopped programmatically, we kill the forkserver and the current runner. + If we stopped manually, this is done by the signal handler. */ + if (stop_soon == 2) { + if (child_pid > 0) kill(child_pid, SIGKILL); + if (forksrv_pid > 0) kill(forksrv_pid, SIGKILL); + } + /* Now that we've killed the forkserver, we wait for it to be able to get rusage stats. */ + if (waitpid(forksrv_pid, NULL, 0) <= 0) { + WARNF("error waitpid\n"); + } + + write_bitmap(); + write_stats_file(0, 0, 0); + save_auto(); + +stop_fuzzing: + + SAYF(CURSOR_SHOW cLRD "\n\n+++ Testing aborted %s +++\n" cRST, + stop_soon == 2 ? "programmatically" : "by user"); + + /* Running for more than 30 minutes but still doing first cycle? */ + + if (queue_cycle == 1 && get_cur_time() - start_time > 30 * 60 * 1000) { + + SAYF("\n" cYEL "[!] " cRST + "Stopped during the first cycle, results may be incomplete.\n" + " (For info on resuming, see %s/README.)\n", doc_path); + + } + + fclose(plot_file); + destroy_queue(); + destroy_extras(); + ck_free(target_path); + ck_free(sync_id); + + alloc_report(); + + OKF("We're done here. Have a nice day!\n"); + + exit(0); + +} + +#endif /* !AFL_LIB */ + diff --git a/src/afl-fuzz.c b/src/afl-fuzz.c index 338d251..ed45a09 100644 --- a/src/afl-fuzz.c +++ b/src/afl-fuzz.c @@ -71,15 +71,13 @@ # include #endif /* __APPLE__ || __FreeBSD__ || __OpenBSD__ */ -/* For systems that have sched_setaffinity; right now just Linux, but one - can hope... */ +/* 针对具有 sched_setaffinity 的系统;目前仅限于 Linux,但可以希望... */ #ifdef __linux__ # define HAVE_AFFINITY 1 #endif /* __linux__ */ -/* A toggle to export some variables when building as a library. Not very - useful for the general public. */ +/* 在构建为库时导出一些变量的开关。对公众没有多大用处。 */ #ifdef AFL_LIB # define EXP_ST @@ -87,215 +85,214 @@ # define EXP_ST static #endif /* ^AFL_LIB */ -/* Lots of globals, but mostly for the status UI and other things where it - really makes no sense to haul them around as function parameters. */ - - -EXP_ST u8 *in_dir, /* Input directory with test cases */ - *out_file, /* File to fuzz, if any */ - *out_dir, /* Working & output directory */ - *sync_dir, /* Synchronization directory */ - *sync_id, /* Fuzzer ID */ - *use_banner, /* Display banner */ - *in_bitmap, /* Input bitmap */ - *doc_path, /* Path to documentation dir */ - *target_path, /* Path to target binary */ - *orig_cmdline; /* Original command line */ - -EXP_ST u32 exec_tmout = EXEC_TIMEOUT; /* Configurable exec timeout (ms) */ -static u32 hang_tmout = EXEC_TIMEOUT; /* Timeout used for hang det (ms) */ - -EXP_ST u64 mem_limit = MEM_LIMIT; /* Memory cap for child (MB) */ - -EXP_ST u32 cpu_to_bind = 0; /* id of free CPU core to bind */ - -static u32 stats_update_freq = 1; /* Stats update frequency (execs) */ - -EXP_ST u8 skip_deterministic, /* Skip deterministic stages? */ - force_deterministic, /* Force deterministic stages? */ - use_splicing, /* Recombine input files? */ - dumb_mode, /* Run in non-instrumented mode? */ - score_changed, /* Scoring for favorites changed? */ - kill_signal, /* Signal that killed the child */ - resuming_fuzz, /* Resuming an older fuzzing job? */ - timeout_given, /* Specific timeout given? */ - cpu_to_bind_given, /* Specified cpu_to_bind given? */ - not_on_tty, /* stdout is not a tty */ - term_too_small, /* terminal dimensions too small */ - uses_asan, /* Target uses ASAN? */ - no_forkserver, /* Disable forkserver? */ - crash_mode, /* Crash mode! Yeah! */ - in_place_resume, /* Attempt in-place resume? */ - auto_changed, /* Auto-generated tokens changed? */ - no_cpu_meter_red, /* Feng shui on the status screen */ - no_arith, /* Skip most arithmetic ops */ - shuffle_queue, /* Shuffle input queue? */ - bitmap_changed = 1, /* Time to update bitmap? */ - qemu_mode, /* Running in QEMU mode? */ - skip_requested, /* Skip request, via SIGUSR1 */ - run_over10m, /* Run time over 10 minutes? */ - persistent_mode, /* Running in persistent mode? */ - deferred_mode, /* Deferred forkserver mode? */ - fast_cal; /* Try to calibrate faster? */ - -static s32 out_fd, /* Persistent fd for out_file */ - dev_urandom_fd = -1, /* Persistent fd for /dev/urandom */ - dev_null_fd = -1, /* Persistent fd for /dev/null */ - fsrv_ctl_fd, /* Fork server control pipe (write) */ - fsrv_st_fd; /* Fork server status pipe (read) */ - -static s32 forksrv_pid, /* PID of the fork server */ - child_pid = -1, /* PID of the fuzzed program */ - out_dir_fd = -1; /* FD of the lock file */ - -EXP_ST u8* trace_bits; /* SHM with instrumentation bitmap */ - -EXP_ST u8 virgin_bits[MAP_SIZE], /* Regions yet untouched by fuzzing */ - virgin_tmout[MAP_SIZE], /* Bits we haven't seen in tmouts */ - virgin_crash[MAP_SIZE]; /* Bits we haven't seen in crashes */ - -static u8 var_bytes[MAP_SIZE]; /* Bytes that appear to be variable */ - -static s32 shm_id; /* ID of the SHM region */ - -static volatile u8 stop_soon, /* Ctrl-C pressed? */ - clear_screen = 1, /* Window resized? */ - child_timed_out; /* Traced process timed out? */ - -EXP_ST u32 queued_paths, /* Total number of queued testcases */ - queued_variable, /* Testcases with variable behavior */ - queued_at_start, /* Total number of initial inputs */ - queued_discovered, /* Items discovered during this run */ - queued_imported, /* Items imported via -S */ - queued_favored, /* Paths deemed favorable */ - queued_with_cov, /* Paths with new coverage bytes */ - pending_not_fuzzed, /* Queued but not done yet */ - pending_favored, /* Pending favored paths */ - cur_skipped_paths, /* Abandoned inputs in cur cycle */ - cur_depth, /* Current path depth */ - max_depth, /* Max path depth */ - useless_at_start, /* Number of useless starting paths */ - var_byte_count, /* Bitmap bytes with var behavior */ - current_entry, /* Current queue entry ID */ - havoc_div = 1; /* Cycle count divisor for havoc */ - -EXP_ST u64 total_crashes, /* Total number of crashes */ - unique_crashes, /* Crashes with unique signatures */ - total_tmouts, /* Total number of timeouts */ - unique_tmouts, /* Timeouts with unique signatures */ - unique_hangs, /* Hangs with unique signatures */ - total_execs, /* Total execve() calls */ - slowest_exec_ms, /* Slowest testcase non hang in ms */ - start_time, /* Unix start time (ms) */ - last_path_time, /* Time for most recent path (ms) */ - last_crash_time, /* Time for most recent crash (ms) */ - last_hang_time, /* Time for most recent hang (ms) */ - last_crash_execs, /* Exec counter at last crash */ - queue_cycle, /* Queue round counter */ - cycles_wo_finds, /* Cycles without any new paths */ - trim_execs, /* Execs done to trim input files */ - bytes_trim_in, /* Bytes coming into the trimmer */ - bytes_trim_out, /* Bytes coming outa the trimmer */ - blocks_eff_total, /* Blocks subject to effector maps */ - blocks_eff_select; /* Blocks selected as fuzzable */ - -static u32 subseq_tmouts; /* Number of timeouts in a row */ - -static u8 *stage_name = "init", /* Name of the current fuzz stage */ - *stage_short, /* Short stage name */ - *syncing_party; /* Currently syncing with... */ - -static s32 stage_cur, stage_max; /* Stage progression */ -static s32 splicing_with = -1; /* Splicing with which test case? */ - -static u32 master_id, master_max; /* Master instance job splitting */ - -static u32 syncing_case; /* Syncing with case #... */ - -static s32 stage_cur_byte, /* Byte offset of current stage op */ - stage_cur_val; /* Value used for stage op */ - -static u8 stage_val_type; /* Value type (STAGE_VAL_*) */ - -static u64 stage_finds[32], /* Patterns found per fuzz stage */ - stage_cycles[32]; /* Execs per fuzz stage */ - -static u32 rand_cnt; /* Random number counter */ - -static u64 total_cal_us, /* Total calibration time (us) */ - total_cal_cycles; /* Total calibration cycles */ - -static u64 total_bitmap_size, /* Total bit count for all bitmaps */ - total_bitmap_entries; /* Number of bitmaps counted */ - -static s32 cpu_core_count; /* CPU core count */ +/* 大量全局变量,主要用于状态 UI 和其他地方,在这些地方作为函数参数传递没有意义。 */ + +EXP_ST u8 *in_dir, /* 包含测试用例的输入目录 */ + *out_file, /* 要模糊测试的文件(如果有的话) */ + *out_dir, /* 工作和输出目录 */ + *sync_dir, /* 同步目录 */ + *sync_id, /* 模糊测试器ID */ + *use_banner, /* 显示横幅 */ + *in_bitmap, /* 输入位图 */ + *doc_path, /* 文档目录的路径 */ + *target_path, /* 目标二进制文件的路径 */ + *orig_cmdline; /* 原始命令行 */ + +EXP_ST u32 exec_tmout = EXEC_TIMEOUT; /* 可配置的执行超时(毫秒) */ +static u32 hang_tmout = EXEC_TIMEOUT; /* 用于检测挂起的超时(毫秒) */ + +EXP_ST u64 mem_limit = MEM_LIMIT; /* 子进程的内存限制(MB) */ + +EXP_ST u32 cpu_to_bind = 0; /* 要绑定的空闲CPU核心的ID */ + +static u32 stats_update_freq = 1; /* 统计更新频率(执行次数) */ + +EXP_ST u8 skip_deterministic, /* 是不是跳过确定性阶段? */ + force_deterministic, /* 强制执行确定性阶段? */ + use_splicing, /* 重新组合输入文件? */ + dumb_mode, /* 以非仪器化模式运行? */ + score_changed, /* 喜爱的评分是否改变? */ + kill_signal, /* 杀死子进程的信号 */ + resuming_fuzz, /* 恢复以前的模糊测试作业? */ + timeout_given, /* 是否给定了特定的超时? */ + cpu_to_bind_given, /* 是否给定了指定的 cpu_to_bind? */ + not_on_tty, /* 标准输出不是 tty */ + term_too_small, /* 终端尺寸过小 */ + uses_asan, /* 目标是否使用 ASAN? */ + no_forkserver, /* 是否禁用 forkserver? */ + crash_mode, /* 崩溃模式!是的! */ + in_place_resume, /* 尝试原地恢复? */ + auto_changed, /* 自动生成的标记是否更改? */ + no_cpu_meter_red, /* 状态屏幕的 风水 */ + no_arith, /* 跳过大多数算术操作 */ + shuffle_queue, /* 随机输入队列? */ + bitmap_changed = 1, /* 是否更新位图的时间? */ + qemu_mode, /* 是否在 QEMU 模式下运行? */ + skip_requested, /* 跳过请求,通过 SIGUSR1 发送 */ + run_over10m, /* 运行时间超过 10 分钟? */ + persistent_mode, /* 是否在持久模式下运行? */ + deferred_mode, /* 延迟 forkserver 模式? */ + fast_cal; /* 尝试更快校准? */ + +static s32 out_fd, /* 持久的输出文件描述符 */ + dev_urandom_fd = -1, /* 对 /dev/urandom 的持久文件描述符 */ + dev_null_fd = -1, /* 对 /dev/null 的持久文件描述符 */ + fsrv_ctl_fd, /* Fork 服务器控制管道(写) */ + fsrv_st_fd; /* Fork 服务器状态管道(读) */ + +static s32 forksrv_pid, /* Fork 服务器的 PID */ + child_pid = -1, /* 被模糊测试程序的 PID */ + out_dir_fd = -1; /* 锁文件的文件描述符 */ + +EXP_ST u8* trace_bits; /* 带有仪器的位图的共享内存 */ + +EXP_ST u8 virgin_bits[MAP_SIZE], /* 还未被模糊测试触及的区域 */ + virgin_tmout[MAP_SIZE], /* 我们在超时中未见的位 */ + virgin_crash[MAP_SIZE]; /* 我们在崩溃中未见的位 */ + +static u8 var_bytes[MAP_SIZE]; /* 似乎是可变的字节 */ + +static s32 shm_id; /* 共享内存区域的 ID */ + +static volatile u8 stop_soon, /* 按下了 Ctrl-C? */ + clear_screen = 1, /* 窗口被调整大小? */ + child_timed_out; /* 被跟踪的进程超时? */ + +EXP_ST u32 queued_paths, /* 排队测试用例的总数 */ + queued_variable, /* 具有可变行为的测试用例 */ + queued_at_start, /* 初始输入的总数 */ + queued_discovered, /* 此次运行中发现的项目 */ + queued_imported, /* 通过 -S 导入的项目 */ + queued_favored, /* 被认为是有利的路径 */ + queued_with_cov, /* 带有新覆盖字节的路径 */ + pending_not_fuzzed, /* 排队但尚未完成的测试用例 */ + pending_favored, /* 待处理的有利路径 */ + cur_skipped_paths, /* 当前周期中放弃的输入 */ + cur_depth, /* 当前路径深度 */ + max_depth, /* 最大路径深度 */ + useless_at_start, /* 无用的起始路径的数量 */ + var_byte_count, /* 具有可变行为的位图字节数 */ + current_entry, /* 当前队列条目的 ID */ + havoc_div = 1; /* 恶劣周期计数的除数 */ + +EXP_ST u64 total_crashes, /* 崩溃的总数 */ + unique_crashes, /* 具有唯一签名的崩溃 */ + total_tmouts, /* 超时的总数 */ + unique_tmouts, /* 具有唯一签名的超时 */ + unique_hangs, /* 具有唯一签名的挂起 */ + total_execs, /* 执行 execve() 调用的总数 */ + slowest_exec_ms, /* 非挂起的最慢测试用例(毫秒) */ + start_time, /* Unix 开始时间(毫秒) */ + last_path_time, /* 最近路径的时间(毫秒) */ + last_crash_time, /* 最近崩溃的时间(毫秒) */ + last_hang_time, /* 最近挂起的时间(毫秒) */ + last_crash_execs, /* 最近崩溃时的执行计数 */ + queue_cycle, /* 队列轮次计数 */ + cycles_wo_finds, /* 没有新路径的周期 */ + trim_execs, /* 用于修剪输入文件的执行次数 */ + bytes_trim_in, /* 进入修剪器的字节数 */ + bytes_trim_out, /* 从修剪器输出的字节数 */ + blocks_eff_total, /* 受效应器映射影响的块总数 */ + blocks_eff_select; /* 被选择为模糊测试的块 */ + +static u32 subseq_tmouts; /* 连续超时的次数 */ + +static u8 *stage_name = "init", /* 当前模糊阶段的名称 */ + *stage_short, /* 短阶段名称 */ + *syncing_party; /* 当前正在同步的对象 */ + +static s32 stage_cur, stage_max; /* 阶段进度 */ +static s32 splicing_with = -1; /* 正在与哪个测试用例拼接? */ + +static u32 master_id, master_max; /* 主实例的作业分割 */ + +static u32 syncing_case; /* 正在同步的案例编号... */ + +static s32 stage_cur_byte, /* 当前阶段操作的字节偏移 */ + stage_cur_val; /* 用于阶段操作的值 */ + +static u8 stage_val_type; /* 值的类型(STAGE_VAL_*) */ + +static u64 stage_finds[32], /* 每个模糊阶段找到的模式 */ + stage_cycles[32]; /* 每个模糊阶段的执行次数 */ + +static u32 rand_cnt; /* 随机数计数器 */ + +static u64 total_cal_us, /* 总校准时间(微秒) */ + total_cal_cycles; /* 总校准周期 */ + +static u64 total_bitmap_size, /* 所有位图的总位数 */ + total_bitmap_entries; /* 计数的位图数量 */ + +static s32 cpu_core_count; /* CPU核心数量 */ #ifdef HAVE_AFFINITY -static s32 cpu_aff = -1; /* Selected CPU core */ +static s32 cpu_aff = -1; /* 选定的CPU核心 */ #endif /* HAVE_AFFINITY */ -static FILE* plot_file; /* Gnuplot output file */ +static FILE* plot_file; /* Gnuplot 输出文件 */ struct queue_entry { - u8* fname; /* File name for the test case */ - u32 len; /* Input length */ + u8* fname; /* 测试用例的文件名 */ + u32 len; /* 输入长度 */ - u8 cal_failed, /* Calibration failed? */ - trim_done, /* Trimmed? */ - was_fuzzed, /* Had any fuzzing done yet? */ - passed_det, /* Deterministic stages passed? */ - has_new_cov, /* Triggers new coverage? */ - var_behavior, /* Variable behavior? */ - favored, /* Currently favored? */ - fs_redundant; /* Marked as redundant in the fs? */ + u8 cal_failed, /* 校准失败? */ + trim_done, /* 已修剪? */ + was_fuzzed, /* 是否已进行过模糊测试? */ + passed_det, /* 是否通过了确定性阶段? */ + has_new_cov, /* 是否触发了新的覆盖? */ + var_behavior, /* 是否具有可变行为? */ + favored, /* 当前是否被看作有利? */ + fs_redundant; /* 是否在文件系统中标记为冗余? */ - u32 bitmap_size, /* Number of bits set in bitmap */ - exec_cksum; /* Checksum of the execution trace */ + u32 bitmap_size, /* 位图中设置的位数 */ + exec_cksum; /* 执行跟踪的校验和 */ - u64 exec_us, /* Execution time (us) */ - handicap, /* Number of queue cycles behind */ - depth; /* Path depth */ + u64 exec_us, /* 执行时间(微秒) */ + handicap, /* 排队周期落后的数量 */ + depth; /* 路径深度 */ - u8* trace_mini; /* Trace bytes, if kept */ - u32 tc_ref; /* Trace bytes ref count */ + u8* trace_mini; /* 如果保留的跟踪字节 */ + u32 tc_ref; /* 跟踪字节引用计数 */ - struct queue_entry *next, /* Next element, if any */ - *next_100; /* 100 elements ahead */ + struct queue_entry *next, /* 下一个元素(如果有的话) */ + *next_100; /* 100个元素之前的标记 */ }; -static struct queue_entry *queue, /* Fuzzing queue (linked list) */ - *queue_cur, /* Current offset within the queue */ - *queue_top, /* Top of the list */ - *q_prev100; /* Previous 100 marker */ +static struct queue_entry *queue, /* 模糊测试队列(链表) */ + *queue_cur, /* 当前队列中的偏移 */ + *queue_top, /* 列表的顶部 */ + *q_prev100; /* 前一个100个的标记 */ static struct queue_entry* - top_rated[MAP_SIZE]; /* Top entries for bitmap bytes */ + top_rated[MAP_SIZE]; /* 用于位图字节的顶级条目 */ struct extra_data { - u8* data; /* Dictionary token data */ - u32 len; /* Dictionary token length */ - u32 hit_cnt; /* Use count in the corpus */ + u8* data; /* 字典标记数据 */ + u32 len; /* 字典标记长度 */ + u32 hit_cnt; /* 在语料库中的使用计数 */ }; -static struct extra_data* extras; /* Extra tokens to fuzz with */ -static u32 extras_cnt; /* Total number of tokens read */ +static struct extra_data* extras; /* 用于模糊测试的额外标记 */ +static u32 extras_cnt; /* 读取的标记总数 */ -static struct extra_data* a_extras; /* Automatically selected extras */ -static u32 a_extras_cnt; /* Total number of tokens available */ +static struct extra_data* a_extras; /* 自动选择的额外标记 */ +static u32 a_extras_cnt; /* 可用的额外标记总数 */ static u8* (*post_handler)(u8* buf, u32* len); -/* Interesting values, as per config.h */ +/* 根据 config.h 的有趣值 */ static s8 interesting_8[] = { INTERESTING_8 }; static s16 interesting_16[] = { INTERESTING_8, INTERESTING_16 }; static s32 interesting_32[] = { INTERESTING_8, INTERESTING_16, INTERESTING_32 }; -/* Fuzzing stages */ + +/* 模糊测试阶段 */ enum { /* 00 */ STAGE_FLIP1, @@ -317,7 +314,7 @@ enum { /* 16 */ STAGE_SPLICE }; -/* Stage value types */ +/* 阶段值类型 */ enum { /* 00 */ STAGE_VAL_NONE, @@ -325,7 +322,7 @@ enum { /* 02 */ STAGE_VAL_BE }; -/* Execution status fault codes */ +/* 执行状态故障码 */ enum { /* 00 */ FAULT_NONE, @@ -337,7 +334,7 @@ enum { }; -/* Get unix time in milliseconds */ +/* 获取当前 UNIX 时间(毫秒) */ static u64 get_cur_time(void) { @@ -351,7 +348,7 @@ static u64 get_cur_time(void) { } -/* Get unix time in microseconds */ +/* 获取当前 UNIX 时间(微秒) */ static u64 get_cur_time_us(void) { @@ -365,8 +362,7 @@ static u64 get_cur_time_us(void) { } -/* Generate a random number (from 0 to limit - 1). This may - have slight bias. */ +/* 生成一个随机数(范围从 0 到 limit - 1)。这可能有轻微的偏差。 */ static inline u32 UR(u32 limit) { @@ -386,7 +382,7 @@ static inline u32 UR(u32 limit) { } -/* Shuffle an array of pointers. Might be slightly biased. */ +/* 随机打乱指针数组。可能会有轻微的偏差。 */ static void shuffle_ptrs(void** ptrs, u32 cnt) { @@ -406,8 +402,8 @@ static void shuffle_ptrs(void** ptrs, u32 cnt) { #ifdef HAVE_AFFINITY -/* Build a list of processes bound to specific cores. Returns -1 if nothing - can be found. Assumes an upper bound of 4k CPUs. */ +/* 构建一个绑定到特定核心的进程列表。如果找不到返回 -1。 + 假设最多 4k 个 CPU。 */ static void bind_to_free_cpu(void) { @@ -422,7 +418,7 @@ static void bind_to_free_cpu(void) { if (getenv("AFL_NO_AFFINITY")) { - WARNF("Not binding to a CPU core (AFL_NO_AFFINITY set)."); + WARNF("未绑定到 CPU 核心(设置了 AFL_NO_AFFINITY)。"); return; } @@ -431,22 +427,20 @@ static void bind_to_free_cpu(void) { if (!d) { - WARNF("Unable to access /proc - can't scan for free CPU cores."); + WARNF("无法访问 /proc - 无法扫描空闲 CPU 核心。"); return; } - ACTF("Checking CPU core loadout..."); + ACTF("检查 CPU 核心负载..."); - /* Introduce some jitter, in case multiple AFL tasks are doing the same - thing at the same time... */ + /* 引入一些抖动,以防多个 AFL 任务同时执行相同的操作... */ usleep(R(1000) * 250); - /* Scan all /proc//status entries, checking for Cpus_allowed_list. - Flag all processes bound to a specific CPU using cpu_used[]. This will - fail for some exotic binding setups, but is likely good enough in almost - all real-world use cases. */ + /* 扫描所有 /proc//status 条目,检查 Cpus_allowed_list。 + 使用 cpu_used[] 标记所有绑定到特定 CPU 的进程。这对某些特殊绑定设置可能失败, + 但在几乎所有真实世界的用例中都可能足够好。 */ while ((de = readdir(d))) { @@ -468,7 +462,7 @@ static void bind_to_free_cpu(void) { u32 hval; - /* Processes without VmSize are probably kernel tasks. */ + /* 没有 VmSize 的进程可能是内核任务。 */ if (!strncmp(tmp, "VmSize:\t", 8)) has_vmsize = 1; @@ -493,10 +487,10 @@ static void bind_to_free_cpu(void) { if (cpu_to_bind_given) { if (cpu_to_bind >= cpu_core_count) - FATAL("The CPU core id to bind should be between 0 and %u", cpu_core_count - 1); + FATAL("要绑定的 CPU 核心 ID 应该在 0 到 %u 之间", cpu_core_count - 1); if (cpu_used[cpu_to_bind]) - FATAL("The CPU core #%u to bind is not free!", cpu_to_bind); + FATAL("要绑定的 CPU 核心 #%u 不可用!", cpu_to_bind); i = cpu_to_bind; @@ -509,17 +503,16 @@ static void bind_to_free_cpu(void) { if (i == cpu_core_count) { SAYF("\n" cLRD "[-] " cRST - "Uh-oh, looks like all %u CPU cores on your system are allocated to\n" - " other instances of afl-fuzz (or similar CPU-locked tasks). Starting\n" - " another fuzzer on this machine is probably a bad plan, but if you are\n" - " absolutely sure, you can set AFL_NO_AFFINITY and try again.\n", + "糟糕,似乎系统上所有 %u 个 CPU 核心都分配给了\n" + " 其他的 afl-fuzz 实例(或类似的 CPU 锁定任务)。在此机器上启动\n" + " 另一个模糊测试器可能是个坏主意,但如果您绝对确定,可以设置 AFL_NO_AFFINITY 并重试。\n", cpu_core_count); - FATAL("No more free CPU cores"); + FATAL("没有更多空闲的 CPU 核心"); } - OKF("Found a free CPU core, binding to #%u.", i); + OKF("找到一个空闲的 CPU 核心,绑定到 #%u。", i); cpu_aff = i; @@ -527,7 +520,7 @@ static void bind_to_free_cpu(void) { CPU_SET(i, &c); if (sched_setaffinity(0, sizeof(c), &c)) - PFATAL("sched_setaffinity failed"); + PFATAL("sched_setaffinity 失败"); } @@ -535,8 +528,8 @@ static void bind_to_free_cpu(void) { #ifndef IGNORE_FINDS -/* Helper function to compare buffers; returns first and last differing offset. We - use this to find reasonable locations for splicing two files. */ +/* 辅助函数用于比较缓冲区;返回第一个和最后一个不同的偏移量。 + 我们用这个来找到拼接两个文件的合理位置。 */ static void locate_diffs(u8* ptr1, u8* ptr2, u32 len, s32* first, s32* last) { @@ -565,9 +558,9 @@ static void locate_diffs(u8* ptr1, u8* ptr2, u32 len, s32* first, s32* last) { #endif /* !IGNORE_FINDS */ -/* Describe integer. Uses 12 cyclic static buffers for return values. The value - returned should be five characters or less for all the integers we reasonably - expect to see. */ + +/* 描述整数。使用 12 个循环的静态缓冲区来返回值。 + 返回的值对于我们合理预期看到的所有整数应不超过五个字符。 */ static u8* DI(u64 val) { @@ -610,7 +603,7 @@ static u8* DI(u64 val) { /* 100G - 999G */ CHK_FORMAT(1000LL * 1000 * 1000, 1000, "%lluG", u64); - /* 1.00T - 9.99G */ + /* 1.00T - 9.99T */ CHK_FORMAT(1000LL * 1000 * 1000 * 1000, 9.995, "%0.02fT", double); /* 10.0T - 99.9T */ @@ -623,8 +616,7 @@ static u8* DI(u64 val) { } -/* Describe float. Similar to the above, except with a single - static buffer. */ +/* 描述浮点数。与上述类似,只是使用单个静态缓冲区。 */ static u8* DF(double val) { @@ -645,7 +637,7 @@ static u8* DF(double val) { } -/* Describe integer as memory size. */ +/* 将整数描述为内存大小。 */ static u8* DMS(u64 val) { @@ -681,7 +673,7 @@ static u8* DMS(u64 val) { /* 100G - 999G */ CHK_FORMAT(1024LL * 1024 * 1024, 1000, "%llu GB", u64); - /* 1.00T - 9.99G */ + /* 1.00T - 9.99T */ CHK_FORMAT(1024LL * 1024 * 1024 * 1024, 9.995, "%0.02f TB", double); /* 10.0T - 99.9T */ @@ -696,7 +688,7 @@ static u8* DMS(u64 val) { } -/* Describe time delta. Returns one static buffer, 34 chars of less. */ +/* 描述时间间隔。返回一个静态缓冲区,最多 34 个字符。 */ static u8* DTD(u64 cur_ms, u64 event_ms) { @@ -704,7 +696,7 @@ static u8* DTD(u64 cur_ms, u64 event_ms) { u64 delta; s32 t_d, t_h, t_m, t_s; - if (!event_ms) return "none seen yet"; + if (!event_ms) return "尚未看到任何"; delta = cur_ms - event_ms; @@ -713,15 +705,14 @@ static u8* DTD(u64 cur_ms, u64 event_ms) { t_m = (delta / 1000 / 60) % 60; t_s = (delta / 1000) % 60; - sprintf(tmp, "%s days, %u hrs, %u min, %u sec", DI(t_d), t_h, t_m, t_s); + sprintf(tmp, "%s 天, %u 小时, %u 分, %u 秒", DI(t_d), t_h, t_m, t_s); return tmp; } -/* Mark deterministic checks as done for a particular queue entry. We use the - .state file to avoid repeating deterministic fuzzing when resuming aborted - scans. */ +/* 为特定队列项标记确定性检查为完成。我们使用 .state 文件来避免 + 在恢复中断扫描时重复执行确定性模糊。 */ static void mark_as_det_done(struct queue_entry* q) { @@ -731,7 +722,7 @@ static void mark_as_det_done(struct queue_entry* q) { fn = alloc_printf("%s/queue/.state/deterministic_done/%s", out_dir, fn + 1); fd = open(fn, O_WRONLY | O_CREAT | O_EXCL, 0600); - if (fd < 0) PFATAL("Unable to create '%s'", fn); + if (fd < 0) PFATAL("无法创建 '%s'", fn); close(fd); ck_free(fn); @@ -741,8 +732,7 @@ static void mark_as_det_done(struct queue_entry* q) { } -/* Mark as variable. Create symlinks if possible to make it easier to examine - the files. */ +/* 标记为变量。如果可能,创建符号链接以便于检查文件。 */ static void mark_as_variable(struct queue_entry* q) { @@ -754,7 +744,7 @@ static void mark_as_variable(struct queue_entry* q) { if (symlink(ldest, fn)) { s32 fd = open(fn, O_WRONLY | O_CREAT | O_EXCL, 0600); - if (fd < 0) PFATAL("Unable to create '%s'", fn); + if (fd < 0) PFATAL("无法创建 '%s'", fn); close(fd); } @@ -767,8 +757,8 @@ static void mark_as_variable(struct queue_entry* q) { } -/* Mark / unmark as redundant (edge-only). This is not used for restoring state, - but may be useful for post-processing datasets. */ +/* 标记/取消标记为冗余(仅边缘)。这不用于恢复状态, + 但可能对于后处理数据集有用。 */ static void mark_as_redundant(struct queue_entry* q, u8 state) { @@ -785,12 +775,12 @@ static void mark_as_redundant(struct queue_entry* q, u8 state) { if (state) { fd = open(fn, O_WRONLY | O_CREAT | O_EXCL, 0600); - if (fd < 0) PFATAL("Unable to create '%s'", fn); + if (fd < 0) PFATAL("无法创建 '%s'", fn); close(fd); } else { - if (unlink(fn)) PFATAL("Unable to remove '%s'", fn); + if (unlink(fn)) PFATAL("无法删除 '%s'", fn); } @@ -799,7 +789,7 @@ static void mark_as_redundant(struct queue_entry* q, u8 state) { } -/* Append new test case to the queue. */ +/* 将新的测试用例追加到队列中。 */ static void add_to_queue(u8* fname, u32 len, u8 passed_det) { @@ -824,7 +814,7 @@ static void add_to_queue(u8* fname, u32 len, u8 passed_det) { cycles_wo_finds = 0; - /* Set next_100 pointer for every 100th element (index 0, 100, etc) to allow faster iteration. */ + /* 为每第 100 个元素设置 next_100 指针(索引 0、100 等),以便更快的迭代。 */ if ((queued_paths - 1) % 100 == 0 && queued_paths > 1) { q_prev100->next_100 = q; @@ -837,7 +827,7 @@ static void add_to_queue(u8* fname, u32 len, u8 passed_det) { } -/* Destroy the entire queue. */ +/* 销毁整个队列。 */ EXP_ST void destroy_queue(void) { @@ -855,7 +845,6 @@ EXP_ST void destroy_queue(void) { } - /* Write bitmap to file. The bitmap is useful mostly for the secret -B option, to focus a separate fuzzing session on a particular interesting input without rediscovering all the others. */ @@ -3519,14 +3508,17 @@ static void write_stats_file(double bitmap_cvg, double stability, double eps) { static void maybe_update_plot_file(double bitmap_cvg, double eps) { + // 静态变量,用于存储上一次的状态值 static u32 prev_qp, prev_pf, prev_pnf, prev_ce, prev_md; static u64 prev_qc, prev_uc, prev_uh; + // 如果当前状态与上一次状态相同,则直接返回,避免重复更新 if (prev_qp == queued_paths && prev_pf == pending_favored && prev_pnf == pending_not_fuzzed && prev_ce == current_entry && prev_qc == queue_cycle && prev_uc == unique_crashes && prev_uh == unique_hangs && prev_md == max_depth) return; + // 更新上一次的状态值为当前状态值 prev_qp = queued_paths; prev_pf = pending_favored; prev_pnf = pending_not_fuzzed; @@ -3536,56 +3528,25 @@ static void maybe_update_plot_file(double bitmap_cvg, double eps) { prev_uh = unique_hangs; prev_md = max_depth; - /* Fields in the file: - + /* 文件中的字段: unix_time, cycles_done, cur_path, paths_total, paths_not_fuzzed, favored_not_fuzzed, unique_crashes, unique_hangs, max_depth, execs_per_sec */ + // 将当前状态写入绘图文件 fprintf(plot_file, "%llu, %llu, %u, %u, %u, %u, %0.02f%%, %llu, %llu, %u, %0.02f\n", get_cur_time() / 1000, queue_cycle - 1, current_entry, queued_paths, pending_not_fuzzed, pending_favored, bitmap_cvg, unique_crashes, - unique_hangs, max_depth, eps); /* ignore errors */ + unique_hangs, max_depth, eps); /* 忽略错误 */ + // 刷新文件缓冲区,确保数据写入文件 fflush(plot_file); } -/* A helper function for maybe_delete_out_dir(), deleting all prefixed - files in a directory. */ - -static u8 delete_files(u8* path, u8* prefix) { - - DIR* d; - struct dirent* d_ent; - - d = opendir(path); - - if (!d) return 0; - - while ((d_ent = readdir(d))) { - - if (d_ent->d_name[0] != '.' && (!prefix || - !strncmp(d_ent->d_name, prefix, strlen(prefix)))) { - - u8* fname = alloc_printf("%s/%s", path, d_ent->d_name); - if (unlink(fname)) PFATAL("Unable to delete '%s'", fname); - ck_free(fname); - - } - - } - - closedir(d); - - return !!rmdir(path); - -} - - /* Get the number of runnable processes, with some simple smoothing. */ static double get_runnable_processes(void) { @@ -3598,7 +3559,7 @@ static double get_runnable_processes(void) { number of runnable processes; the 1-minute load average can be a semi-decent approximation, though. */ - if (getloadavg(&res, 1) != 1) return 0; + if (getloadavg(&res, 1) != 1) return 0; // 获取1分钟的负载平均值,如果失败则返回0 #else @@ -3606,74 +3567,74 @@ static double get_runnable_processes(void) { computed in funny ways and sometimes don't reflect extremely short-lived processes well. */ - FILE* f = fopen("/proc/stat", "r"); - u8 tmp[1024]; - u32 val = 0; + FILE* f = fopen("/proc/stat", "r"); // 打开/proc/stat文件以读取系统统计信息 + u8 tmp[1024]; // 用于存储读取的每一行内容 + u32 val = 0; // 用于存储当前运行或阻塞的进程数 - if (!f) return 0; + if (!f) return 0; // 如果文件打开失败,返回0 - while (fgets(tmp, sizeof(tmp), f)) { + while (fgets(tmp, sizeof(tmp), f)) { // 逐行读取文件内容 - if (!strncmp(tmp, "procs_running ", 14) || - !strncmp(tmp, "procs_blocked ", 14)) val += atoi(tmp + 14); + if (!strncmp(tmp, "procs_running ", 14) || // 如果行以"procs_running "开头 + !strncmp(tmp, "procs_blocked ", 14)) val += atoi(tmp + 14); // 或者以"procs_blocked "开头,提取数值并累加 } - fclose(f); + fclose(f); // 关闭文件 - if (!res) { + if (!res) { // 如果res为0(即第一次计算) - res = val; + res = val; // 直接将当前值赋给res - } else { + } else { // 否则,进行平滑处理 - res = res * (1.0 - 1.0 / AVG_SMOOTHING) + + res = res * (1.0 - 1.0 / AVG_SMOOTHING) + // 使用平滑公式计算新的res值 ((double)val) * (1.0 / AVG_SMOOTHING); } #endif /* ^(__APPLE__ || __FreeBSD__ || __OpenBSD__) */ - return res; + return res; // 返回计算得到的res值 } /* Delete the temporary directory used for in-place session resume. */ -static void nuke_resume_dir(void) { +static void nuke_resume_dir(void) { // 删除用于恢复会话的临时目录 - u8* fn; + u8* fn; // 用于存储文件路径 - fn = alloc_printf("%s/_resume/.state/deterministic_done", out_dir); - if (delete_files(fn, CASE_PREFIX)) goto dir_cleanup_failed; - ck_free(fn); + fn = alloc_printf("%s/_resume/.state/deterministic_done", out_dir); // 构建文件路径 + if (delete_files(fn, CASE_PREFIX)) goto dir_cleanup_failed; // 删除文件,如果失败则跳转到清理失败处理 + ck_free(fn); // 释放内存 - fn = alloc_printf("%s/_resume/.state/auto_extras", out_dir); - if (delete_files(fn, "auto_")) goto dir_cleanup_failed; - ck_free(fn); + fn = alloc_printf("%s/_resume/.state/auto_extras", out_dir); // 构建文件路径 + if (delete_files(fn, "auto_")) goto dir_cleanup_failed; // 删除文件,如果失败则跳转到清理失败处理 + ck_free(fn); // 释放内存 - fn = alloc_printf("%s/_resume/.state/redundant_edges", out_dir); - if (delete_files(fn, CASE_PREFIX)) goto dir_cleanup_failed; - ck_free(fn); + fn = alloc_printf("%s/_resume/.state/redundant_edges", out_dir); // 构建文件路径 + if (delete_files(fn, CASE_PREFIX)) goto dir_cleanup_failed; // 删除文件,如果失败则跳转到清理失败处理 + ck_free(fn); // 释放内存 - fn = alloc_printf("%s/_resume/.state/variable_behavior", out_dir); - if (delete_files(fn, CASE_PREFIX)) goto dir_cleanup_failed; - ck_free(fn); + fn = alloc_printf("%s/_resume/.state/variable_behavior", out_dir); // 构建文件路径 + if (delete_files(fn, CASE_PREFIX)) goto dir_cleanup_failed; // 删除文件,如果失败则跳转到清理失败处理 + ck_free(fn); // 释放内存 - fn = alloc_printf("%s/_resume/.state", out_dir); - if (rmdir(fn) && errno != ENOENT) goto dir_cleanup_failed; - ck_free(fn); + fn = alloc_printf("%s/_resume/.state", out_dir); // 构建文件路径 + if (rmdir(fn) && errno != ENOENT) goto dir_cleanup_failed; // 删除目录,如果失败且错误不是目录不存在则跳转到清理失败处理 + ck_free(fn); // 释放内存 - fn = alloc_printf("%s/_resume", out_dir); - if (delete_files(fn, CASE_PREFIX)) goto dir_cleanup_failed; - ck_free(fn); + fn = alloc_printf("%s/_resume", out_dir); // 构建文件路径 + if (delete_files(fn, CASE_PREFIX)) goto dir_cleanup_failed; // 删除文件,如果失败则跳转到清理失败处理 + ck_free(fn); // 释放内存 - return; + return; // 成功完成清理,返回 -dir_cleanup_failed: +dir_cleanup_failed: // 清理失败处理 - FATAL("_resume directory cleanup failed"); + FATAL("_resume directory cleanup failed"); // 输出致命错误信息并终止程序 } @@ -3681,51 +3642,52 @@ dir_cleanup_failed: /* Delete fuzzer output directory if we recognize it as ours, if the fuzzer is not currently running, and if the last run time isn't too great. */ -static void maybe_delete_out_dir(void) { +static void maybe_delete_out_dir(void) { // 可能删除fuzzer输出目录 - FILE* f; - u8 *fn = alloc_printf("%s/fuzzer_stats", out_dir); + FILE* f; // 文件指针 + u8 *fn = alloc_printf("%s/fuzzer_stats", out_dir); // 构建文件路径 /* See if the output directory is locked. If yes, bail out. If not, create a lock that will persist for the lifetime of the process (this requires leaving the descriptor open).*/ - out_dir_fd = open(out_dir, O_RDONLY); - if (out_dir_fd < 0) PFATAL("Unable to open '%s'", out_dir); + out_dir_fd = open(out_dir, O_RDONLY); // 打开输出目录 + if (out_dir_fd < 0) PFATAL("Unable to open '%s'", out_dir); // 如果打开失败,输出致命错误信息并终止程序 -#ifndef __sun - if (flock(out_dir_fd, LOCK_EX | LOCK_NB) && errno == EWOULDBLOCK) { +#ifndef __sun // 如果不是在SunOS系统上 - SAYF("\n" cLRD "[-] " cRST + if (flock(out_dir_fd, LOCK_EX | LOCK_NB) && errno == EWOULDBLOCK) { // 尝试对输出目录文件描述符加锁,如果锁被占用且错误码为EWOULDBLOCK + + SAYF("\n" cLRD "[-] " cRST // 输出错误信息 "Looks like the job output directory is being actively used by another\n" " instance of afl-fuzz. You will need to choose a different %s\n" " or stop the other process first.\n", - sync_id ? "fuzzer ID" : "output location"); + sync_id ? "fuzzer ID" : "output location"); // 根据sync_id选择输出不同的提示信息 - FATAL("Directory '%s' is in use", out_dir); + FATAL("Directory '%s' is in use", out_dir); // 输出致命错误信息并退出 } -#endif /* !__sun */ +#endif /* !__sun */ // 结束条件编译 - f = fopen(fn, "r"); + f = fopen(fn, "r"); // 打开文件fn以读取 - if (f) { + if (f) { // 如果文件成功打开 - u64 start_time, last_update; + u64 start_time, last_update; // 定义两个64位无符号整数变量 - if (fscanf(f, "start_time : %llu\n" + if (fscanf(f, "start_time : %llu\n" // 从文件中读取start_time和last_update "last_update : %llu\n", &start_time, &last_update) != 2) - FATAL("Malformed data in '%s'", fn); + FATAL("Malformed data in '%s'", fn); // 如果读取失败,输出致命错误信息并退出 - fclose(f); + fclose(f); // 关闭文件 - /* Let's see how much work is at stake. */ + /* Let's see how much work is at stake. */ // 检查是否有大量工作数据 - if (!in_place_resume && last_update - start_time > OUTPUT_GRACE * 60) { + if (!in_place_resume && last_update - start_time > OUTPUT_GRACE * 60) { // 如果不是原地恢复模式且时间差超过OUTPUT_GRACE分钟 - SAYF("\n" cLRD "[-] " cRST + SAYF("\n" cLRD "[-] " cRST // 输出警告信息 "The job output directory already exists and contains the results of more\n" " than %u minutes worth of fuzzing. To avoid data loss, afl-fuzz will *NOT*\n" " automatically delete this data for you.\n\n" @@ -3735,94 +3697,94 @@ static void maybe_delete_out_dir(void) { " session, put '-' as the input directory in the command line ('-i -') and\n" " try again.\n", OUTPUT_GRACE); - FATAL("At-risk data found in '%s'", out_dir); + FATAL("At-risk data found in '%s'", out_dir); // 输出致命错误信息并退出 } } - ck_free(fn); + ck_free(fn); // 释放fn的内存 /* The idea for in-place resume is pretty simple: we temporarily move the old queue/ to a new location that gets deleted once import to the new queue/ is finished. If _resume/ already exists, the current queue/ may be incomplete due to an earlier abort, so we want to use the old _resume/ - dir instead, and we let rename() fail silently. */ + dir instead, and we let rename() fail silently. */ // 原地恢复的逻辑 - if (in_place_resume) { + if (in_place_resume) { // 如果是原地恢复模式 - u8* orig_q = alloc_printf("%s/queue", out_dir); + u8* orig_q = alloc_printf("%s/queue", out_dir); // 分配内存并格式化字符串 - in_dir = alloc_printf("%s/_resume", out_dir); + in_dir = alloc_printf("%s/_resume", out_dir); // 分配内存并格式化字符串 - rename(orig_q, in_dir); /* Ignore errors */ + rename(orig_q, in_dir); /* Ignore errors */ // 将orig_q重命名为in_dir,忽略错误 - OKF("Output directory exists, will attempt session resume."); + OKF("Output directory exists, will attempt session resume."); // 输出成功信息 - ck_free(orig_q); + ck_free(orig_q); // 释放orig_q的内存 - } else { + } else { // 如果不是原地恢复模式 - OKF("Output directory exists but deemed OK to reuse."); + OKF("Output directory exists but deemed OK to reuse."); // 输出成功信息 } - ACTF("Deleting old session data..."); + ACTF("Deleting old session data..."); // 输出动作信息 /* Okay, let's get the ball rolling! First, we need to get rid of the entries - in /.synced/.../id:*, if any are present. */ + in /.synced/.../id:*, if any are present. */ // 开始清理旧会话数据 - if (!in_place_resume) { + if (!in_place_resume) { // 如果不是原地恢复模式 - fn = alloc_printf("%s/.synced", out_dir); - if (delete_files(fn, NULL)) goto dir_cleanup_failed; - ck_free(fn); + fn = alloc_printf("%s/.synced", out_dir); // 分配内存并格式化字符串 + if (delete_files(fn, NULL)) goto dir_cleanup_failed; // 删除文件,如果失败则跳转到dir_cleanup_failed + ck_free(fn); // 释放fn的内存 } + /* Next, we need to clean up /queue/.state/ subdirectories: */ fn = alloc_printf("%s/queue/.state/deterministic_done", out_dir); - if (delete_files(fn, CASE_PREFIX)) goto dir_cleanup_failed; + if (delete_files(fn, CASE_PREFIX)) goto dir_cleanup_failed; // 删除确定性测试完成标志文件 ck_free(fn); fn = alloc_printf("%s/queue/.state/auto_extras", out_dir); - if (delete_files(fn, "auto_")) goto dir_cleanup_failed; + if (delete_files(fn, "auto_")) goto dir_cleanup_failed; // 删除自动生成的额外文件 ck_free(fn); fn = alloc_printf("%s/queue/.state/redundant_edges", out_dir); - if (delete_files(fn, CASE_PREFIX)) goto dir_cleanup_failed; + if (delete_files(fn, CASE_PREFIX)) goto dir_cleanup_failed; // 删除冗余边文件 ck_free(fn); fn = alloc_printf("%s/queue/.state/variable_behavior", out_dir); - if (delete_files(fn, CASE_PREFIX)) goto dir_cleanup_failed; + if (delete_files(fn, CASE_PREFIX)) goto dir_cleanup_failed; // 删除变量行为文件 ck_free(fn); - /* Then, get rid of the .state subdirectory itself (should be empty by now) - and everything matching /queue/id:*. */ + /* 然后,删除.state子目录本身(此时应该为空) + 并删除所有匹配/queue/id:*的文件。 */ fn = alloc_printf("%s/queue/.state", out_dir); - if (rmdir(fn) && errno != ENOENT) goto dir_cleanup_failed; + if (rmdir(fn) && errno != ENOENT) goto dir_cleanup_failed; // 删除.state目录 ck_free(fn); fn = alloc_printf("%s/queue", out_dir); - if (delete_files(fn, CASE_PREFIX)) goto dir_cleanup_failed; + if (delete_files(fn, CASE_PREFIX)) goto dir_cleanup_failed; // 删除queue目录下的文件 ck_free(fn); - /* All right, let's do /crashes/id:* and /hangs/id:*. */ + /* 接下来,处理/crashes/id:*和/hangs/id:*。 */ if (!in_place_resume) { fn = alloc_printf("%s/crashes/README.txt", out_dir); - unlink(fn); /* Ignore errors */ + unlink(fn); /* 忽略错误 */ // 删除crashes目录下的README.txt文件 ck_free(fn); } fn = alloc_printf("%s/crashes", out_dir); - /* Make backup of the crashes directory if it's not empty and if we're - doing in-place resume. */ + /* 如果crashes目录不为空且正在进行原地恢复,则备份crashes目录。 */ if (in_place_resume && rmdir(fn)) { @@ -3843,17 +3805,17 @@ static void maybe_delete_out_dir(void) { #endif /* ^!SIMPLE_FILES */ - rename(fn, nfn); /* Ignore errors. */ + rename(fn, nfn); /* 忽略错误。 */ // 重命名crashes目录以备份 ck_free(nfn); } - if (delete_files(fn, CASE_PREFIX)) goto dir_cleanup_failed; + if (delete_files(fn, CASE_PREFIX)) goto dir_cleanup_failed; // 删除crashes目录下的文件 ck_free(fn); fn = alloc_printf("%s/hangs", out_dir); - /* Backup hangs, too. */ + /* 同样备份hangs目录。 */ if (in_place_resume && rmdir(fn)) { @@ -3874,53 +3836,49 @@ static void maybe_delete_out_dir(void) { #endif /* ^!SIMPLE_FILES */ - rename(fn, nfn); /* Ignore errors. */ + rename(fn, nfn); /* 忽略错误。 */ // 重命名hangs目录以备份 ck_free(nfn); } - if (delete_files(fn, CASE_PREFIX)) goto dir_cleanup_failed; + if (delete_files(fn, CASE_PREFIX)) goto dir_cleanup_failed; // 删除hangs目录下的文件 ck_free(fn); - /* And now, for some finishing touches. */ + /* 现在,进行一些收尾工作。 */ fn = alloc_printf("%s/.cur_input", out_dir); - if (unlink(fn) && errno != ENOENT) goto dir_cleanup_failed; + if (unlink(fn) && errno != ENOENT) goto dir_cleanup_failed; // 删除当前输入文件 ck_free(fn); fn = alloc_printf("%s/fuzz_bitmap", out_dir); - if (unlink(fn) && errno != ENOENT) goto dir_cleanup_failed; + if (unlink(fn) && errno != ENOENT) goto dir_cleanup_failed; // 删除fuzz位图文件 ck_free(fn); if (!in_place_resume) { fn = alloc_printf("%s/fuzzer_stats", out_dir); - if (unlink(fn) && errno != ENOENT) goto dir_cleanup_failed; + if (unlink(fn) && errno != ENOENT) goto dir_cleanup_failed; // 删除fuzzer统计文件 ck_free(fn); } fn = alloc_printf("%s/plot_data", out_dir); - if (unlink(fn) && errno != ENOENT) goto dir_cleanup_failed; + if (unlink(fn) && errno != ENOENT) goto dir_cleanup_failed; // 删除绘图数据文件 ck_free(fn); - OKF("Output dir cleanup successful."); + OKF("Output dir cleanup successful."); // 输出目录清理成功 - /* Wow... is that all? If yes, celebrate! */ + /* 哇...这就完了吗?如果是,庆祝一下! */ return; dir_cleanup_failed: SAYF("\n" cLRD "[-] " cRST - "Whoops, the fuzzer tried to reuse your output directory, but bumped into\n" - " some files that shouldn't be there or that couldn't be removed - so it\n" - " decided to abort! This happened while processing this path:\n\n" + "哎呀,fuzzer试图重用你的输出目录,但遇到了一些不应该存在的文件或无法删除的文件 - 所以它决定中止!这发生在处理以下路径时:\n\n" " %s\n\n" - " Please examine and manually delete the files, or specify a different\n" - " output location for the tool.\n", fn); - - FATAL("Output directory cleanup failed"); + " 请检查并手动删除这些文件,或为工具指定一个不同的输出位置。\n", fn); // 输出目录清理失败的错误信息 + FATAL("Output directory cleanup failed"); // 输出目录清理失败 } @@ -3932,148 +3890,137 @@ static void check_term_size(void); static void show_stats(void) { - static u64 last_stats_ms, last_plot_ms, last_ms, last_execs; - static double avg_exec; - double t_byte_ratio, stab_ratio; - - u64 cur_ms; - u32 t_bytes, t_bits; + static u64 last_stats_ms, last_plot_ms, last_ms, last_execs; // 上一次统计时间、绘图时间、UI更新时间、执行次数 + static double avg_exec; // 平均执行速度 + double t_byte_ratio, stab_ratio; // 字节覆盖率和稳定性比率 - u32 banner_len, banner_pad; - u8 tmp[256]; + u64 cur_ms; // 当前时间 + u32 t_bytes, t_bits; // 非255字节数和非255位数 - cur_ms = get_cur_time(); + u32 banner_len, banner_pad; // 横幅长度和填充 + u8 tmp[256]; // 临时缓冲区 - /* If not enough time has passed since last UI update, bail out. */ + cur_ms = get_cur_time(); // 获取当前时间 + /* 如果距离上一次UI更新的时间不足,直接返回 */ if (cur_ms - last_ms < 1000 / UI_TARGET_HZ) return; - /* Check if we're past the 10 minute mark. */ - + /* 检查是否已经运行超过10分钟 */ if (cur_ms - start_time > 10 * 60 * 1000) run_over10m = 1; - /* Calculate smoothed exec speed stats. */ - + /* 计算平滑的执行速度统计 */ if (!last_execs) { - avg_exec = ((double)total_execs) * 1000 / (cur_ms - start_time); + avg_exec = ((double)total_execs) * 1000 / (cur_ms - start_time); // 初始平均执行速度 } else { double cur_avg = ((double)(total_execs - last_execs)) * 1000 / - (cur_ms - last_ms); - - /* If there is a dramatic (5x+) jump in speed, reset the indicator - more quickly. */ + (cur_ms - last_ms); // 当前平均执行速度 + /* 如果速度有显著变化(5倍以上),快速重置指示器 */ if (cur_avg * 5 < avg_exec || cur_avg / 5 > avg_exec) avg_exec = cur_avg; avg_exec = avg_exec * (1.0 - 1.0 / AVG_SMOOTHING) + - cur_avg * (1.0 / AVG_SMOOTHING); + cur_avg * (1.0 / AVG_SMOOTHING); // 平滑处理 } - last_ms = cur_ms; - last_execs = total_execs; - - /* Tell the callers when to contact us (as measured in execs). */ + last_ms = cur_ms; // 更新上一次UI更新时间 + last_execs = total_execs; // 更新上一次执行次数 + /* 告诉调用者何时联系我们(以执行次数衡量) */ stats_update_freq = avg_exec / (UI_TARGET_HZ * 10); if (!stats_update_freq) stats_update_freq = 1; - /* Do some bitmap stats. */ - - t_bytes = count_non_255_bytes(virgin_bits); - t_byte_ratio = ((double)t_bytes * 100) / MAP_SIZE; + /* 计算一些位图统计 */ + t_bytes = count_non_255_bytes(virgin_bits); // 计算非255字节数 + t_byte_ratio = ((double)t_bytes * 100) / MAP_SIZE; // 计算字节覆盖率 if (t_bytes) - stab_ratio = 100 - ((double)var_byte_count) * 100 / t_bytes; + stab_ratio = 100 - ((double)var_byte_count) * 100 / t_bytes; // 计算稳定性比率 else stab_ratio = 100; - /* Roughly every minute, update fuzzer stats and save auto tokens. */ - + /* 大约每分钟更新一次fuzzer统计并保存自动生成的token */ if (cur_ms - last_stats_ms > STATS_UPDATE_SEC * 1000) { last_stats_ms = cur_ms; - write_stats_file(t_byte_ratio, stab_ratio, avg_exec); - save_auto(); - write_bitmap(); + write_stats_file(t_byte_ratio, stab_ratio, avg_exec); // 写入统计文件 + save_auto(); // 保存自动生成的token + write_bitmap(); // 写入位图 } - /* Every now and then, write plot data. */ - + /* 每隔一段时间,写入绘图数据 */ if (cur_ms - last_plot_ms > PLOT_UPDATE_SEC * 1000) { last_plot_ms = cur_ms; - maybe_update_plot_file(t_byte_ratio, avg_exec); + maybe_update_plot_file(t_byte_ratio, avg_exec); // 更新绘图文件 } - /* Honor AFL_EXIT_WHEN_DONE and AFL_BENCH_UNTIL_CRASH. */ - + /* 处理AFL_EXIT_WHEN_DONE和AFL_BENCH_UNTIL_CRASH环境变量 */ if (!dumb_mode && cycles_wo_finds > 100 && !pending_not_fuzzed && getenv("AFL_EXIT_WHEN_DONE")) stop_soon = 2; if (total_crashes && getenv("AFL_BENCH_UNTIL_CRASH")) stop_soon = 2; - /* If we're not on TTY, bail out. */ - + /* 如果不是在TTY上运行,直接返回 */ if (not_on_tty) return; - /* Compute some mildly useful bitmap stats. */ - - t_bits = (MAP_SIZE << 3) - count_bits(virgin_bits); - - /* Now, for the visuals... */ + /* 计算一些有用的位图统计 */ + t_bits = (MAP_SIZE << 3) - count_bits(virgin_bits); // 计算非255位数 + /* 现在,处理视觉效果... */ if (clear_screen) { - SAYF(TERM_CLEAR CURSOR_HIDE); + SAYF(TERM_CLEAR CURSOR_HIDE); // 清屏并隐藏光标 clear_screen = 0; - check_term_size(); + check_term_size(); // 检查终端大小 } - SAYF(TERM_HOME); - if (term_too_small) { + SAYF(TERM_HOME); // 将光标移动到终端窗口的左上角 + + if (term_too_small) { // 检查终端窗口是否太小 SAYF(cBRI "Your terminal is too small to display the UI.\n" - "Please resize terminal window to at least 80x25.\n" cRST); + "Please resize terminal window to at least 80x25.\n" cRST); // 输出提示信息,要求用户调整终端窗口大小 - return; + return; // 如果终端窗口太小,直接返回,不继续绘制UI } - /* Let's start by drawing a centered banner. */ + /* Let's start by drawing a centered banner. */ // 开始绘制居中的横幅 - banner_len = (crash_mode ? 24 : 22) + strlen(VERSION) + strlen(use_banner); - banner_pad = (80 - banner_len) / 2; - memset(tmp, ' ', banner_pad); + banner_len = (crash_mode ? 24 : 22) + strlen(VERSION) + strlen(use_banner); // 计算横幅的总长度,根据是否处于crash_mode调整基础长度 + banner_pad = (80 - banner_len) / 2; // 计算横幅两侧的填充空格数,使横幅居中 + memset(tmp, ' ', banner_pad); // 在tmp数组的前banner_pad个位置填充空格 sprintf(tmp + banner_pad, "%s " cLCY VERSION cLGN " (%s)", crash_mode ? cPIN "peruvian were-rabbit" : - cYEL "american fuzzy lop", use_banner); + cYEL "american fuzzy lop", use_banner); // 格式化横幅内容,包含版本号和banner信息,根据crash_mode选择不同的显示文本 + + SAYF("\n%s\n\n", tmp); // 输出横幅内容,前后添加换行符 - SAYF("\n%s\n\n", tmp); + /* "Handy" shortcuts for drawing boxes... */ // 定义绘制框的快捷方式 - /* "Handy" shortcuts for drawing boxes... */ +#define bSTG bSTART cGRA // 定义bSTG为bSTART和cGRA的组合,用于绘制框的起始部分 +#define bH2 bH bH // 定义bH2为两个bH的组合,用于绘制水平线 +#define bH5 bH2 bH2 bH // 定义bH5为两个bH2和一个bH的组合,用于绘制更长的水平线 +#define bH10 bH5 bH5 // 定义bH10为两个bH5的组合,用于绘制更长的水平线 +#define bH20 bH10 bH10 // 定义bH20为两个bH10的组合,用于绘制更长的水平线 +#define bH30 bH20 bH10 // 定义bH30为一个bH20和一个bH10的组合,用于绘制更长的水平线 +#define SP5 " " // 定义SP5为5个空格,用于填充空白 +#define SP10 SP5 SP5 // 定义SP10为两个SP5的组合,用于填充更多空白 +#define SP20 SP10 SP10 // 定义SP20为两个SP10的组合,用于填充更多空白 -#define bSTG bSTART cGRA -#define bH2 bH bH -#define bH5 bH2 bH2 bH -#define bH10 bH5 bH5 -#define bH20 bH10 bH10 -#define bH30 bH20 bH10 -#define SP5 " " -#define SP10 SP5 SP5 -#define SP20 SP10 SP10 + /* Lord, forgive me this. */ // 注释:主啊,请原谅我这样做(可能是开发者对代码复杂性的调侃) - /* Lord, forgive me this. */ SAYF(SET_G1 bSTG bLT bH bSTOP cCYA " process timing " bSTG bH30 bH5 bH2 bHB bH bSTOP cCYA " overall results " bSTG bH5 bRT "\n"); @@ -4405,42 +4352,51 @@ static void show_stats(void) { static void show_init_stats(void) { - struct queue_entry* q = queue; - u32 min_bits = 0, max_bits = 0; - u64 min_us = 0, max_us = 0; - u64 avg_us = 0; - u32 max_len = 0; + struct queue_entry* q = queue; // 指向队列的当前条目 + u32 min_bits = 0, max_bits = 0; // 最小和最大位图大小 + u64 min_us = 0, max_us = 0; // 最小和最大执行时间(微秒) + u64 avg_us = 0; // 平均执行时间(微秒) + u32 max_len = 0; // 最大测试用例长度 + // 计算平均执行时间 if (total_cal_cycles) avg_us = total_cal_us / total_cal_cycles; + // 遍历队列中的所有条目 while (q) { + // 更新最小执行时间 if (!min_us || q->exec_us < min_us) min_us = q->exec_us; + // 更新最大执行时间 if (q->exec_us > max_us) max_us = q->exec_us; + // 更新最小位图大小 if (!min_bits || q->bitmap_size < min_bits) min_bits = q->bitmap_size; + // 更新最大位图大小 if (q->bitmap_size > max_bits) max_bits = q->bitmap_size; + // 更新最大测试用例长度 if (q->len > max_len) max_len = q->len; - q = q->next; + q = q->next; // 移动到下一个队列条目 } - SAYF("\n"); + SAYF("\n"); // 输出换行符 + // 如果平均执行时间较长,发出警告 if (avg_us > (qemu_mode ? 50000 : 10000)) WARNF(cLRD "The target binary is pretty slow! See %s/perf_tips.txt.", doc_path); - /* Let's keep things moving with slow binaries. */ - + /* 根据平均执行时间调整 havoc_div 的值,以保持测试的进度 */ if (avg_us > 50000) havoc_div = 10; /* 0-19 execs/sec */ else if (avg_us > 20000) havoc_div = 5; /* 20-49 execs/sec */ else if (avg_us > 10000) havoc_div = 2; /* 50-100 execs/sec */ + // 如果不是恢复模式,检查并发出相关警告 if (!resuming_fuzz) { + // 如果测试用例过大,发出警告 if (max_len > 50 * 1024) WARNF(cLRD "Some test cases are huge (%s) - see %s/perf_tips.txt!", DMS(max_len), doc_path); @@ -4448,9 +4404,11 @@ static void show_init_stats(void) { WARNF("Some test cases are big (%s) - see %s/perf_tips.txt.", DMS(max_len), doc_path); + // 如果初始测试用例无用,发出警告 if (useless_at_start && !in_bitmap) WARNF(cLRD "Some test cases look useless. Consider using a smaller set."); + // 如果输入文件过多,发出警告 if (queued_paths > 100) WARNF(cLRD "You probably have far too many input files! Consider trimming down."); else if (queued_paths > 20) @@ -4458,6 +4416,7 @@ static void show_init_stats(void) { } + // 输出有用的统计信息 OKF("Here are some useful stats:\n\n" cGRA " Test case count : " cRST "%u favored, %u variable, %u total\n" @@ -4467,349 +4426,336 @@ static void show_init_stats(void) { ((double)total_bitmap_size) / (total_bitmap_entries ? total_bitmap_entries : 1), DI(min_us), DI(max_us), DI(avg_us)); + // 如果没有指定超时时间,计算并设置默认超时时间 if (!timeout_given) { - /* Figure out the appropriate timeout. The basic idea is: 5x average or - 1x max, rounded up to EXEC_TM_ROUND ms and capped at 1 second. + /* 计算适当的超时时间。基本思路是:5倍平均时间或1倍最大时间, + 向上取整到 EXEC_TM_ROUND 毫秒,并限制在1秒内。 - If the program is slow, the multiplier is lowered to 2x or 3x, because - random scheduler jitter is less likely to have any impact, and because - our patience is wearing thin =) */ + 如果程序较慢,乘数降低到2倍或3倍,因为随机调度抖动不太可能产生影响, + 而且我们的耐心已经不足了 =) */ - if (avg_us > 50000) exec_tmout = avg_us * 2 / 1000; - else if (avg_us > 10000) exec_tmout = avg_us * 3 / 1000; - else exec_tmout = avg_us * 5 / 1000; + if (avg_us > 50000) exec_tmout = avg_us * 2 / 1000; // 如果平均时间大于50000微秒,设置超时为2倍平均时间 + else if (avg_us > 10000) exec_tmout = avg_us * 3 / 1000; // 如果平均时间大于10000微秒,设置超时为3倍平均时间 + else exec_tmout = avg_us * 5 / 1000; // 否则设置超时为5倍平均时间 - exec_tmout = MAX(exec_tmout, max_us / 1000); - exec_tmout = (exec_tmout + EXEC_TM_ROUND) / EXEC_TM_ROUND * EXEC_TM_ROUND; + exec_tmout = MAX(exec_tmout, max_us / 1000); // 取最大值,确保超时时间不小于最大执行时间 + exec_tmout = (exec_tmout + EXEC_TM_ROUND) / EXEC_TM_ROUND * EXEC_TM_ROUND; // 向上取整到 EXEC_TM_ROUND 毫秒 - if (exec_tmout > EXEC_TIMEOUT) exec_tmout = EXEC_TIMEOUT; + if (exec_tmout > EXEC_TIMEOUT) exec_tmout = EXEC_TIMEOUT; // 限制超时时间不超过1秒 ACTF("No -t option specified, so I'll use exec timeout of %u ms.", - exec_tmout); + exec_tmout); // 输出设置的超时时间 - timeout_given = 1; + timeout_given = 1; // 标记超时时间已设置 } else if (timeout_given == 3) { - ACTF("Applying timeout settings from resumed session (%u ms).", exec_tmout); + ACTF("Applying timeout settings from resumed session (%u ms).", exec_tmout); // 应用恢复会话中的超时设置 } - /* In dumb mode, re-running every timing out test case with a generous time - limit is very expensive, so let's select a more conservative default. */ + /* 在 dumb 模式下,重新运行每个超时的测试用例非常昂贵, + 因此选择一个更保守的默认值。 */ if (dumb_mode && !getenv("AFL_HANG_TMOUT")) - hang_tmout = MIN(EXEC_TIMEOUT, exec_tmout * 2 + 100); + hang_tmout = MIN(EXEC_TIMEOUT, exec_tmout * 2 + 100); // 设置 hang_tmout 为较小的值 - OKF("All set and ready to roll!"); + OKF("All set and ready to roll!"); // 输出准备就绪信息 } -/* Find first power of two greater or equal to val (assuming val under - 2^31). */ +/* 找到大于或等于 val 的第一个2的幂(假设 val 小于 2^31)。 */ static u32 next_p2(u32 val) { - u32 ret = 1; - while (val > ret) ret <<= 1; - return ret; + u32 ret = 1; // 初始化 ret 为1 + while (val > ret) ret <<= 1; // 左移直到 ret >= val + return ret; // 返回结果 } + /* Trim all new test cases to save cycles when doing deterministic checks. The trimmer uses power-of-two increments somewhere between 1/16 and 1/1024 of file size, to keep the stage short and sweet. */ static u8 trim_case(char** argv, struct queue_entry* q, u8* in_buf) { - static u8 tmp[64]; - static u8 clean_trace[MAP_SIZE]; + static u8 tmp[64]; // 用于存储临时字符串 + static u8 clean_trace[MAP_SIZE]; // 用于存储干净的trace数据 - u8 needs_write = 0, fault = 0; - u32 trim_exec = 0; - u32 remove_len; - u32 len_p2; + u8 needs_write = 0, fault = 0; // needs_write标记是否需要写入文件,fault标记是否发生错误 + u32 trim_exec = 0; // 记录trim操作的执行次数 + u32 remove_len; // 每次删除的字节长度 + u32 len_p2; // 输入长度的下一个2的幂次方 - /* Although the trimmer will be less useful when variable behavior is - detected, it will still work to some extent, so we don't check for - this. */ + /* 虽然当检测到可变行为时,trimmer的用处会减少,但它仍然会在一定程度上起作用,因此我们不检查这一点。 */ - if (q->len < 5) return 0; + if (q->len < 5) return 0; // 如果输入长度小于5,直接返回,不进行trim操作 - stage_name = tmp; - bytes_trim_in += q->len; + stage_name = tmp; // 设置当前阶段名称为trim + bytes_trim_in += q->len; // 累加输入的总字节数 - /* Select initial chunk len, starting with large steps. */ + /* 选择初始的删除长度,从较大的步长开始。 */ - len_p2 = next_p2(q->len); + len_p2 = next_p2(q->len); // 计算输入长度的下一个2的幂次方 - remove_len = MAX(len_p2 / TRIM_START_STEPS, TRIM_MIN_BYTES); + remove_len = MAX(len_p2 / TRIM_START_STEPS, TRIM_MIN_BYTES); // 计算初始删除长度 - /* Continue until the number of steps gets too high or the stepover - gets too small. */ + /* 继续直到步数过高或步长过小。 */ while (remove_len >= MAX(len_p2 / TRIM_END_STEPS, TRIM_MIN_BYTES)) { - u32 remove_pos = remove_len; + u32 remove_pos = remove_len; // 删除的起始位置 - sprintf(tmp, "trim %s/%s", DI(remove_len), DI(remove_len)); + sprintf(tmp, "trim %s/%s", DI(remove_len), DI(remove_len)); // 格式化阶段名称 - stage_cur = 0; - stage_max = q->len / remove_len; + stage_cur = 0; // 当前阶段执行的次数 + stage_max = q->len / remove_len; // 当前阶段的最大执行次数 - while (remove_pos < q->len) { + while (remove_pos < q->len) { // 遍历输入数据 - u32 trim_avail = MIN(remove_len, q->len - remove_pos); - u32 cksum; + u32 trim_avail = MIN(remove_len, q->len - remove_pos); // 计算实际可删除的字节数 + u32 cksum; // 用于存储trace的校验和 - write_with_gap(in_buf, q->len, remove_pos, trim_avail); + write_with_gap(in_buf, q->len, remove_pos, trim_avail); // 写入带有删除部分的数据 - fault = run_target(argv, exec_tmout); - trim_execs++; + fault = run_target(argv, exec_tmout); // 运行目标程序 + trim_execs++; // 增加trim操作的执行次数 - if (stop_soon || fault == FAULT_ERROR) goto abort_trimming; + if (stop_soon || fault == FAULT_ERROR) goto abort_trimming; // 如果发生错误或停止信号,跳转到abort_trimming - /* Note that we don't keep track of crashes or hangs here; maybe TODO? */ + /* 注意,我们这里不跟踪崩溃或挂起;也许TODO? */ - cksum = hash32(trace_bits, MAP_SIZE, HASH_CONST); + cksum = hash32(trace_bits, MAP_SIZE, HASH_CONST); // 计算trace的校验和 - /* If the deletion had no impact on the trace, make it permanent. This - isn't perfect for variable-path inputs, but we're just making a - best-effort pass, so it's not a big deal if we end up with false - negatives every now and then. */ + /* 如果删除操作对trace没有影响,则将其永久化。对于可变路径输入,这并不完美,但我们只是尽力而为, + 所以偶尔出现假阴性也没关系。 */ - if (cksum == q->exec_cksum) { + if (cksum == q->exec_cksum) { // 如果校验和与之前的相同 - u32 move_tail = q->len - remove_pos - trim_avail; + u32 move_tail = q->len - remove_pos - trim_avail; // 计算需要移动的尾部字节数 - q->len -= trim_avail; - len_p2 = next_p2(q->len); + q->len -= trim_avail; // 更新输入长度 + len_p2 = next_p2(q->len); // 重新计算下一个2的幂次方 memmove(in_buf + remove_pos, in_buf + remove_pos + trim_avail, - move_tail); + move_tail); // 移动数据,覆盖删除的部分 - /* Let's save a clean trace, which will be needed by - update_bitmap_score once we're done with the trimming stuff. */ + /* 保存一个干净的trace,以便在trim操作完成后更新bitmap_score。 */ - if (!needs_write) { + if (!needs_write) { // 如果还没有保存过干净的trace - needs_write = 1; - memcpy(clean_trace, trace_bits, MAP_SIZE); + needs_write = 1; // 标记需要写入文件 + memcpy(clean_trace, trace_bits, MAP_SIZE); // 保存当前的trace } - } else remove_pos += remove_len; + } else remove_pos += remove_len; // 如果校验和不同,继续下一个删除位置 - /* Since this can be slow, update the screen every now and then. */ + /* 由于这个过程可能很慢,所以每隔一段时间更新屏幕显示。 */ - if (!(trim_exec++ % stats_update_freq)) show_stats(); - stage_cur++; + if (!(trim_exec++ % stats_update_freq)) show_stats(); // 更新统计信息 + stage_cur++; // 增加当前阶段的执行次数 } - remove_len >>= 1; + remove_len >>= 1; // 将删除长度减半 } - /* If we have made changes to in_buf, we also need to update the on-disk - version of the test case. */ + /* 如果对in_buf进行了修改,还需要更新磁盘上的测试用例文件。 */ - if (needs_write) { + if (needs_write) { // 如果需要写入文件 s32 fd; - unlink(q->fname); /* ignore errors */ + unlink(q->fname); /* 忽略错误 */ // 删除旧文件 - fd = open(q->fname, O_WRONLY | O_CREAT | O_EXCL, 0600); + fd = open(q->fname, O_WRONLY | O_CREAT | O_EXCL, 0600); // 创建新文件 - if (fd < 0) PFATAL("Unable to create '%s'", q->fname); + if (fd < 0) PFATAL("Unable to create '%s'", q->fname); // 如果创建失败,报错 - ck_write(fd, in_buf, q->len, q->fname); - close(fd); + ck_write(fd, in_buf, q->len, q->fname); // 写入数据 + close(fd); // 关闭文件 - memcpy(trace_bits, clean_trace, MAP_SIZE); - update_bitmap_score(q); + memcpy(trace_bits, clean_trace, MAP_SIZE); // 恢复干净的trace + update_bitmap_score(q); // 更新bitmap_score } abort_trimming: - bytes_trim_out += q->len; - return fault; + bytes_trim_out += q->len; // 累加输出的总字节数 + return fault; // 返回错误状态 } -/* Write a modified test case, run program, process results. Handle - error conditions, returning 1 if it's time to bail out. This is - a helper function for fuzz_one(). */ +/* 写入修改后的测试用例,运行程序,处理结果。处理错误条件,如果应该退出则返回1。 + 这是fuzz_one()的辅助函数。 */ EXP_ST u8 common_fuzz_stuff(char** argv, u8* out_buf, u32 len) { u8 fault; - if (post_handler) { + if (post_handler) { // 如果有后处理函数 - out_buf = post_handler(out_buf, &len); - if (!out_buf || !len) return 0; + out_buf = post_handler(out_buf, &len); // 调用后处理函数 + if (!out_buf || !len) return 0; // 如果处理后数据为空,返回0 } - write_to_testcase(out_buf, len); + write_to_testcase(out_buf, len); // 写入测试用例 - fault = run_target(argv, exec_tmout); + fault = run_target(argv, exec_tmout); // 运行目标程序 - if (stop_soon) return 1; + if (stop_soon) return 1; // 如果收到停止信号,返回1 - if (fault == FAULT_TMOUT) { + if (fault == FAULT_TMOUT) { // 如果发生超时 - if (subseq_tmouts++ > TMOUT_LIMIT) { - cur_skipped_paths++; - return 1; + if (subseq_tmouts++ > TMOUT_LIMIT) { // 如果连续超时次数超过限制 + cur_skipped_paths++; // 增加跳过的路径数 + return 1; // 返回1 } - } else subseq_tmouts = 0; + } else subseq_tmouts = 0; // 否则重置连续超时次数 - /* Users can hit us with SIGUSR1 to request the current input - to be abandoned. */ + /* 用户可以通过SIGUSR1信号请求放弃当前输入。 */ - if (skip_requested) { + if (skip_requested) { // 如果收到跳过请求 - skip_requested = 0; - cur_skipped_paths++; - return 1; + skip_requested = 0; // 重置跳过请求 + cur_skipped_paths++; // 增加跳过的路径数 + return 1; // 返回1 } - /* This handles FAULT_ERROR for us: */ + /* 这为我们处理FAULT_ERROR: */ - queued_discovered += save_if_interesting(argv, out_buf, len, fault); + queued_discovered += save_if_interesting(argv, out_buf, len, fault); // 保存有趣的测试用例 - if (!(stage_cur % stats_update_freq) || stage_cur + 1 == stage_max) + if (!(stage_cur % stats_update_freq) || stage_cur + 1 == stage_max) // 更新统计信息 show_stats(); - return 0; + return 0; // 返回0 } + /* Helper to choose random block len for block operations in fuzz_one(). Doesn't return zero, provided that max_len is > 0. */ static u32 choose_block_len(u32 limit) { u32 min_value, max_value; - u32 rlim = MIN(queue_cycle, 3); + u32 rlim = MIN(queue_cycle, 3); // 限制随机选择的范围,最大为3 - if (!run_over10m) rlim = 1; + if (!run_over10m) rlim = 1; // 如果运行时间未超过10分钟,限制随机选择的范围为1 - switch (UR(rlim)) { + switch (UR(rlim)) { // 根据随机数选择块长度的范围 - case 0: min_value = 1; - max_value = HAVOC_BLK_SMALL; + case 0: min_value = 1; // 最小值为1 + max_value = HAVOC_BLK_SMALL; // 最大值为HAVOC_BLK_SMALL break; - case 1: min_value = HAVOC_BLK_SMALL; - max_value = HAVOC_BLK_MEDIUM; + case 1: min_value = HAVOC_BLK_SMALL; // 最小值为HAVOC_BLK_SMALL + max_value = HAVOC_BLK_MEDIUM; // 最大值为HAVOC_BLK_MEDIUM break; default: - if (UR(10)) { + if (UR(10)) { // 90%的概率选择中等大小的块 - min_value = HAVOC_BLK_MEDIUM; - max_value = HAVOC_BLK_LARGE; + min_value = HAVOC_BLK_MEDIUM; // 最小值为HAVOC_BLK_MEDIUM + max_value = HAVOC_BLK_LARGE; // 最大值为HAVOC_BLK_LARGE - } else { + } else { // 10%的概率选择大块 - min_value = HAVOC_BLK_LARGE; - max_value = HAVOC_BLK_XL; + min_value = HAVOC_BLK_LARGE; // 最小值为HAVOC_BLK_LARGE + max_value = HAVOC_BLK_XL; // 最大值为HAVOC_BLK_XL } } - if (min_value >= limit) min_value = 1; + if (min_value >= limit) min_value = 1; // 如果最小值超过限制,则重置为1 - return min_value + UR(MIN(max_value, limit) - min_value + 1); + return min_value + UR(MIN(max_value, limit) - min_value + 1); // 返回在[min_value, max_value]范围内的随机长度 } -/* Calculate case desirability score to adjust the length of havoc fuzzing. - A helper function for fuzz_one(). Maybe some of these constants should - go into config.h. */ +/* 计算案例的期望得分,以调整havoc fuzzing的长度。 + 这是fuzz_one()的辅助函数。也许其中一些常量应该放在config.h中。 */ static u32 calculate_score(struct queue_entry* q) { - u32 avg_exec_us = total_cal_us / total_cal_cycles; - u32 avg_bitmap_size = total_bitmap_size / total_bitmap_entries; - u32 perf_score = 100; + u32 avg_exec_us = total_cal_us / total_cal_cycles; // 计算平均执行时间 + u32 avg_bitmap_size = total_bitmap_size / total_bitmap_entries; // 计算平均位图大小 + u32 perf_score = 100; // 初始得分为100 - /* Adjust score based on execution speed of this path, compared to the - global average. Multiplier ranges from 0.1x to 3x. Fast inputs are - less expensive to fuzz, so we're giving them more air time. */ + /* 根据路径的执行速度调整得分,与全局平均速度相比。乘数范围从0.1x到3x。 + 执行速度快的输入fuzz成本较低,因此我们给予它们更多的执行时间。 */ - if (q->exec_us * 0.1 > avg_exec_us) perf_score = 10; + if (q->exec_us * 0.1 > avg_exec_us) perf_score = 10; // 如果执行时间远高于平均,得分降低 else if (q->exec_us * 0.25 > avg_exec_us) perf_score = 25; else if (q->exec_us * 0.5 > avg_exec_us) perf_score = 50; else if (q->exec_us * 0.75 > avg_exec_us) perf_score = 75; - else if (q->exec_us * 4 < avg_exec_us) perf_score = 300; + else if (q->exec_us * 4 < avg_exec_us) perf_score = 300; // 如果执行时间远低于平均,得分增加 else if (q->exec_us * 3 < avg_exec_us) perf_score = 200; else if (q->exec_us * 2 < avg_exec_us) perf_score = 150; - /* Adjust score based on bitmap size. The working theory is that better - coverage translates to better targets. Multiplier from 0.25x to 3x. */ + /* 根据位图大小调整得分。理论是更好的覆盖率意味着更好的目标。乘数从0.25x到3x。 */ - if (q->bitmap_size * 0.3 > avg_bitmap_size) perf_score *= 3; + if (q->bitmap_size * 0.3 > avg_bitmap_size) perf_score *= 3; // 如果位图大小远高于平均,得分增加 else if (q->bitmap_size * 0.5 > avg_bitmap_size) perf_score *= 2; else if (q->bitmap_size * 0.75 > avg_bitmap_size) perf_score *= 1.5; - else if (q->bitmap_size * 3 < avg_bitmap_size) perf_score *= 0.25; + else if (q->bitmap_size * 3 < avg_bitmap_size) perf_score *= 0.25; // 如果位图大小远低于平均,得分降低 else if (q->bitmap_size * 2 < avg_bitmap_size) perf_score *= 0.5; else if (q->bitmap_size * 1.5 < avg_bitmap_size) perf_score *= 0.75; - /* Adjust score based on handicap. Handicap is proportional to how late - in the game we learned about this path. Latecomers are allowed to run - for a bit longer until they catch up with the rest. */ + /* 根据handicap调整得分。handicap与我们在游戏中了解到此路径的时间成正比。 + 后来者被允许运行更长时间,直到他们赶上其他人。 */ - if (q->handicap >= 4) { + if (q->handicap >= 4) { // 如果handicap大于等于4,得分增加4倍 perf_score *= 4; q->handicap -= 4; - } else if (q->handicap) { + } else if (q->handicap) { // 如果handicap大于0,得分增加2倍 perf_score *= 2; q->handicap--; } - /* Final adjustment based on input depth, under the assumption that fuzzing - deeper test cases is more likely to reveal stuff that can't be - discovered with traditional fuzzers. */ + /* 根据输入深度进行最终调整,假设fuzzing更深的测试用例更有可能揭示传统fuzzer无法发现的内容。 */ switch (q->depth) { - case 0 ... 3: break; - case 4 ... 7: perf_score *= 2; break; - case 8 ... 13: perf_score *= 3; break; - case 14 ... 25: perf_score *= 4; break; - default: perf_score *= 5; + case 0 ... 3: break; // 深度为0到3时不调整得分 + case 4 ... 7: perf_score *= 2; break; // 深度为4到7时得分增加2倍 + case 8 ... 13: perf_score *= 3; break; // 深度为8到13时得分增加3倍 + case 14 ... 25: perf_score *= 4; break; // 深度为14到25时得分增加4倍 + default: perf_score *= 5; // 深度大于25时得分增加5倍 } - /* Make sure that we don't go over limit. */ + /* 确保得分不超过限制。 */ - if (perf_score > HAVOC_MAX_MULT * 100) perf_score = HAVOC_MAX_MULT * 100; + if (perf_score > HAVOC_MAX_MULT * 100) perf_score = HAVOC_MAX_MULT * 100; // 如果得分超过最大限制,则设置为最大限制 - return perf_score; + return perf_score; // 返回最终得分 } + /* Helper function to see if a particular change (xor_val = old ^ new) could be a product of deterministic bit flips with the lengths and stepovers attempted by afl-fuzz. This is used to avoid dupes in some of the @@ -4821,19 +4767,19 @@ static u8 could_be_bitflip(u32 xor_val) { u32 sh = 0; - if (!xor_val) return 1; + if (!xor_val) return 1; // 如果xor_val为0,返回1,表示可能是位翻转 /* Shift left until first bit set. */ - + // 左移直到找到第一个置位(1)的位 while (!(xor_val & 1)) { sh++; xor_val >>= 1; } /* 1-, 2-, and 4-bit patterns are OK anywhere. */ - + // 1位、2位和4位的模式在任何地方都是允许的 if (xor_val == 1 || xor_val == 3 || xor_val == 15) return 1; /* 8-, 16-, and 32-bit patterns are OK only if shift factor is divisible by 8, since that's the stepover for these ops. */ - + // 8位、16位和32位的模式只有在移位因子能被8整除时才允许,因为这些操作的步长是8 if (sh & 7) return 0; if (xor_val == 0xff || xor_val == 0xffff || xor_val == 0xffffffff) @@ -4846,26 +4792,26 @@ static u8 could_be_bitflip(u32 xor_val) { /* Helper function to see if a particular value is reachable through arithmetic operations. Used for similar purposes. */ - +// 辅助函数,用于判断某个值是否可以通过算术操作达到。用于类似的目的。 static u8 could_be_arith(u32 old_val, u32 new_val, u8 blen) { u32 i, ov = 0, nv = 0, diffs = 0; - if (old_val == new_val) return 1; + if (old_val == new_val) return 1; // 如果旧值和新值相同,返回1 /* See if one-byte adjustments to any byte could produce this result. */ - + // 检查是否可以通过对任何字节进行单字节调整来产生这个结果 for (i = 0; i < blen; i++) { u8 a = old_val >> (8 * i), b = new_val >> (8 * i); - if (a != b) { diffs++; ov = a; nv = b; } + if (a != b) { diffs++; ov = a; nv = b; } // 记录不同的字节数和对应的旧值和新值 } /* If only one byte differs and the values are within range, return 1. */ - + // 如果只有一个字节不同且值在范围内,返回1 if (diffs == 1) { if ((u8)(ov - nv) <= ARITH_MAX || @@ -4873,10 +4819,10 @@ static u8 could_be_arith(u32 old_val, u32 new_val, u8 blen) { } - if (blen == 1) return 0; + if (blen == 1) return 0; // 如果blen为1,返回0 /* See if two-byte adjustments to any byte would produce this result. */ - + // 检查是否可以通过对任何字节进行双字节调整来产生这个结果 diffs = 0; for (i = 0; i < blen / 2; i++) { @@ -4884,18 +4830,18 @@ static u8 could_be_arith(u32 old_val, u32 new_val, u8 blen) { u16 a = old_val >> (16 * i), b = new_val >> (16 * i); - if (a != b) { diffs++; ov = a; nv = b; } + if (a != b) { diffs++; ov = a; nv = b; } // 记录不同的字节数和对应的旧值和新值 } /* If only one word differs and the values are within range, return 1. */ - + // 如果只有一个字不同且值在范围内,返回1 if (diffs == 1) { if ((u16)(ov - nv) <= ARITH_MAX || (u16)(nv - ov) <= ARITH_MAX) return 1; - ov = SWAP16(ov); nv = SWAP16(nv); + ov = SWAP16(ov); nv = SWAP16(nv); // 交换字节顺序 if ((u16)(ov - nv) <= ARITH_MAX || (u16)(nv - ov) <= ARITH_MAX) return 1; @@ -4903,13 +4849,13 @@ static u8 could_be_arith(u32 old_val, u32 new_val, u8 blen) { } /* Finally, let's do the same thing for dwords. */ - + // 最后,对双字进行同样的操作 if (blen == 4) { if ((u32)(old_val - new_val) <= ARITH_MAX || (u32)(new_val - old_val) <= ARITH_MAX) return 1; - new_val = SWAP32(new_val); + new_val = SWAP32(new_val); // 交换字节顺序 old_val = SWAP32(old_val); if ((u32)(old_val - new_val) <= ARITH_MAX || @@ -4922,6 +4868,7 @@ static u8 could_be_arith(u32 old_val, u32 new_val, u8 blen) { } + /* Last but not least, a similar helper to see if insertion of an interesting integer is redundant given the insertions done for shorter blen. The last param (check_le) is set if the caller @@ -4932,47 +4879,56 @@ static u8 could_be_interest(u32 old_val, u32 new_val, u8 blen, u8 check_le) { u32 i, j; + // 如果旧值和新值相同,直接返回1 if (old_val == new_val) return 1; - /* See if one-byte insertions from interesting_8 over old_val could - produce new_val. */ + /* 检查是否可以通过在old_val中插入interesting_8中的单字节值来生成new_val */ + // 遍历每个字节位置 for (i = 0; i < blen; i++) { + // 遍历interesting_8数组中的每个单字节值 for (j = 0; j < sizeof(interesting_8); j++) { + // 将old_val的第i个字节替换为interesting_8[j],生成临时值tval u32 tval = (old_val & ~(0xff << (i * 8))) | (((u8)interesting_8[j]) << (i * 8)); + // 如果tval等于new_val,返回1 if (new_val == tval) return 1; } } - /* Bail out unless we're also asked to examine two-byte LE insertions - as a preparation for BE attempts. */ + /* 如果blen为2且不需要检查小端序(check_le为0),则直接返回0 */ if (blen == 2 && !check_le) return 0; - /* See if two-byte insertions over old_val could give us new_val. */ + /* 检查是否可以通过在old_val中插入interesting_16中的双字节值来生成new_val */ + // 遍历每个双字节位置 for (i = 0; i < blen - 1; i++) { + // 遍历interesting_16数组中的每个双字节值 for (j = 0; j < sizeof(interesting_16) / 2; j++) { + // 将old_val的第i个双字节替换为interesting_16[j],生成临时值tval u32 tval = (old_val & ~(0xffff << (i * 8))) | (((u16)interesting_16[j]) << (i * 8)); + // 如果tval等于new_val,返回1 if (new_val == tval) return 1; - /* Continue here only if blen > 2. */ + /* 如果blen大于2,继续检查交换字节序后的值 */ if (blen > 2) { + // 将old_val的第i个双字节替换为interesting_16[j]的字节交换值,生成临时值tval tval = (old_val & ~(0xffff << (i * 8))) | (SWAP16(interesting_16[j]) << (i * 8)); + // 如果tval等于new_val,返回1 if (new_val == tval) return 1; } @@ -4981,24 +4937,26 @@ static u8 could_be_interest(u32 old_val, u32 new_val, u8 blen, u8 check_le) { } + // 如果blen为4且需要检查小端序(check_le为1),则检查四字节插入 if (blen == 4 && check_le) { - /* See if four-byte insertions could produce the same result - (LE only). */ + /* 检查是否可以通过插入interesting_32中的四字节值来生成new_val(仅小端序) */ + // 遍历interesting_32数组中的每个四字节值 for (j = 0; j < sizeof(interesting_32) / 4; j++) + // 如果new_val等于interesting_32[j],返回1 if (new_val == (u32)interesting_32[j]) return 1; } + // 如果没有找到匹配的值,返回0 return 0; } -/* Take the current entry from the queue, fuzz it for a while. This - function is a tad too long... returns 0 if fuzzed successfully, 1 if - skipped or bailed out. */ +/* 从队列中取出当前条目,对其进行模糊测试。这个函数有点长... + 如果模糊测试成功返回0,如果跳过或放弃返回1。 */ static u8 fuzz_one(char** argv) { @@ -5012,30 +4970,26 @@ static u8 fuzz_one(char** argv) { u8 a_collect[MAX_AUTO_EXTRA]; u32 a_len = 0; -#ifdef IGNORE_FINDS - /* In IGNORE_FINDS mode, skip any entries that weren't in the - initial data set. */ +#ifdef IGNORE_FINDS + /* 在 IGNORE_FINDS 模式下,跳过任何不在初始数据集中的条目。 */ if (queue_cur->depth > 1) return 1; #else if (pending_favored) { - /* If we have any favored, non-fuzzed new arrivals in the queue, - possibly skip to them at the expense of already-fuzzed or non-favored - cases. */ - + /* 如果我们有任何被标记为 favored 且未被 fuzz 的新条目, + 可能会跳过已经 fuzz 过或非 favored 的条目。 */ if ((queue_cur->was_fuzzed || !queue_cur->favored) && UR(100) < SKIP_TO_NEW_PROB) return 1; } else if (!dumb_mode && !queue_cur->favored && queued_paths > 10) { - /* Otherwise, still possibly skip non-favored cases, albeit less often. - The odds of skipping stuff are higher for already-fuzzed inputs and - lower for never-fuzzed entries. */ - + /* 否则,仍然可能跳过非 favored 的条目,尽管频率较低。 + 对于已经 fuzz 过的输入,跳过的概率较高, + 对于从未 fuzz 过的条目,跳过的概率较低。 */ if (queue_cycle > 1 && !queue_cur->was_fuzzed) { if (UR(100) < SKIP_NFAV_NEW_PROB) return 1; @@ -5051,13 +5005,13 @@ static u8 fuzz_one(char** argv) { #endif /* ^IGNORE_FINDS */ if (not_on_tty) { + /* 如果不是在终端上运行,输出当前 fuzz 的测试用例信息。 */ ACTF("Fuzzing test case #%u (%u total, %llu uniq crashes found)...", current_entry, queued_paths, unique_crashes); fflush(stdout); } - /* Map the test case into memory. */ - + /* 将测试用例映射到内存中。 */ fd = open(queue_cur->fname, O_RDONLY); if (fd < 0) PFATAL("Unable to open '%s'", queue_cur->fname); @@ -5070,10 +5024,8 @@ static u8 fuzz_one(char** argv) { close(fd); - /* We could mmap() out_buf as MAP_PRIVATE, but we end up clobbering every - single byte anyway, so it wouldn't give us any performance or memory usage - benefits. */ - + /* 我们可以将 out_buf 映射为 MAP_PRIVATE,但由于我们会覆盖每个字节, + 因此这样做不会带来任何性能或内存使用上的好处。 */ out_buf = ck_alloc_nozero(len); subseq_tmouts = 0; @@ -5081,7 +5033,7 @@ static u8 fuzz_one(char** argv) { cur_depth = queue_cur->depth; /******************************************* - * CALIBRATION (only if failed earlier on) * + * 校准(仅在之前失败时进行) * *******************************************/ if (queue_cur->cal_failed) { @@ -5090,10 +5042,9 @@ static u8 fuzz_one(char** argv) { if (queue_cur->cal_failed < CAL_CHANCES) { - /* Reset exec_cksum to tell calibrate_case to re-execute the testcase - avoiding the usage of an invalid trace_bits. - For more info: https://github.com/AFLplusplus/AFLplusplus/pull/425 */ - + /* 重置 exec_cksum,以便 calibrate_case 重新执行测试用例, + 避免使用无效的 trace_bits。 + 更多信息:https://github.com/AFLplusplus/AFLplusplus/pull/425 */ queue_cur->exec_cksum = 0; res = calibrate_case(argv, queue_cur, in_buf, queue_cycle - 1, 0); @@ -5111,7 +5062,7 @@ static u8 fuzz_one(char** argv) { } /************ - * TRIMMING * + * 修剪 * ************/ if (!dumb_mode && !queue_cur->trim_done) { @@ -5126,8 +5077,7 @@ static u8 fuzz_one(char** argv) { goto abandon_entry; } - /* Don't retry trimming, even if it failed. */ - + /* 即使修剪失败,也不要重试。 */ queue_cur->trim_done = 1; if (len != queue_cur->len) len = queue_cur->len; @@ -5137,28 +5087,26 @@ static u8 fuzz_one(char** argv) { memcpy(out_buf, in_buf, len); /********************* - * PERFORMANCE SCORE * + * 性能评分 * *********************/ orig_perf = perf_score = calculate_score(queue_cur); - /* Skip right away if -d is given, if we have done deterministic fuzzing on - this entry ourselves (was_fuzzed), or if it has gone through deterministic - testing in earlier, resumed runs (passed_det). */ - + /* 如果指定了 -d 参数,或者我们已经对该条目进行了确定性 fuzz(was_fuzzed), + 或者它在之前的恢复运行中已经通过了确定性测试(passed_det), + 则立即跳过。 */ if (skip_deterministic || queue_cur->was_fuzzed || queue_cur->passed_det) goto havoc_stage; - /* Skip deterministic fuzzing if exec path checksum puts this out of scope - for this master instance. */ - + /* 如果执行路径校验和将该条目排除在主实例的范围内, + 则跳过确定性 fuzz。 */ if (master_max && (queue_cur->exec_cksum % master_max) != master_id - 1) goto havoc_stage; doing_det = 1; /********************************************* - * SIMPLE BITFLIP (+dictionary construction) * + * 简单的位翻转(+字典构建) * *********************************************/ #define FLIP_BIT(_ar, _b) do { \ @@ -5167,90 +5115,76 @@ static u8 fuzz_one(char** argv) { _arf[(_bf) >> 3] ^= (128 >> ((_bf) & 7)); \ } while (0) + /* Single walking bit. */ - stage_short = "flip1"; - stage_max = len << 3; - stage_name = "bitflip 1/1"; + stage_short = "flip1"; // 设置当前阶段的简短名称为 "flip1" + stage_max = len << 3; // 计算最大阶段数,len 是输入数据的长度,左移3位相当于乘以8(每个字节8位) + stage_name = "bitflip 1/1"; // 设置当前阶段的完整名称为 "bitflip 1/1" - stage_val_type = STAGE_VAL_NONE; + stage_val_type = STAGE_VAL_NONE; // 设置当前阶段的值类型为无(STAGE_VAL_NONE) - orig_hit_cnt = queued_paths + unique_crashes; + orig_hit_cnt = queued_paths + unique_crashes; // 记录当前阶段的初始命中计数,包括已排队的路径和唯一的崩溃数 - prev_cksum = queue_cur->exec_cksum; + prev_cksum = queue_cur->exec_cksum; // 记录当前队列项的校验和 - for (stage_cur = 0; stage_cur < stage_max; stage_cur++) { + for (stage_cur = 0; stage_cur < stage_max; stage_cur++) { // 遍历每个位 - stage_cur_byte = stage_cur >> 3; + stage_cur_byte = stage_cur >> 3; // 计算当前字节的位置,stage_cur 右移3位相当于除以8 - FLIP_BIT(out_buf, stage_cur); + FLIP_BIT(out_buf, stage_cur); // 翻转当前位 - if (common_fuzz_stuff(argv, out_buf, len)) goto abandon_entry; + if (common_fuzz_stuff(argv, out_buf, len)) goto abandon_entry; // 执行模糊测试,如果返回非零值则跳转到 abandon_entry - FLIP_BIT(out_buf, stage_cur); + FLIP_BIT(out_buf, stage_cur); // 恢复当前位的翻转 - /* While flipping the least significant bit in every byte, pull of an extra - trick to detect possible syntax tokens. In essence, the idea is that if - you have a binary blob like this: + /* 在翻转每个字节的最低位时,执行一个额外的技巧来检测可能的语法标记。本质上,这个想法是,如果你有一个像这样的二进制数据块: xxxxxxxxIHDRxxxxxxxx - ...and changing the leading and trailing bytes causes variable or no - changes in program flow, but touching any character in the "IHDR" string - always produces the same, distinctive path, it's highly likely that - "IHDR" is an atomically-checked magic value of special significance to - the fuzzed format. + ...并且改变前导和尾随字节会导致程序流程的变化或不变化,但触摸 "IHDR" 字符串中的任何字符总是产生相同的、独特的路径,那么很可能 "IHDR" 是一个原子检查的魔法值,对模糊测试的格式具有特殊意义。 - We do this here, rather than as a separate stage, because it's a nice - way to keep the operation approximately "free" (i.e., no extra execs). - - Empirically, performing the check when flipping the least significant bit - is advantageous, compared to doing it at the time of more disruptive - changes, where the program flow may be affected in more violent ways. + 我们在这里执行这个操作,而不是作为一个单独的阶段,因为这是一种保持操作近似“免费”的好方法(即,没有额外的执行)。 - The caveat is that we won't generate dictionaries in the -d mode or -S - mode - but that's probably a fair trade-off. + 经验表明,在翻转最低有效位时执行检查是有利的,相比于在更破坏性的更改时执行,因为程序流程可能会以更剧烈的方式受到影响。 - This won't work particularly well with paths that exhibit variable - behavior, but fails gracefully, so we'll carry out the checks anyway. + 需要注意的是,我们不会在 -d 模式或 -S 模式下生成字典 - 但这可能是一个公平的权衡。 - */ + 这对于表现出可变行为的路径效果不佳,但会优雅地失败,因此我们仍然会执行这些检查。 + */ - if (!dumb_mode && (stage_cur & 7) == 7) { + if (!dumb_mode && (stage_cur & 7) == 7) { // 如果不是 dumb_mode 并且当前位是字节的最后一位 - u32 cksum = hash32(trace_bits, MAP_SIZE, HASH_CONST); + u32 cksum = hash32(trace_bits, MAP_SIZE, HASH_CONST); // 计算当前跟踪位的校验和 - if (stage_cur == stage_max - 1 && cksum == prev_cksum) { + if (stage_cur == stage_max - 1 && cksum == prev_cksum) { // 如果当前位是最后一位且校验和未改变 - /* If at end of file and we are still collecting a string, grab the - final character and force output. */ + /* 如果在文件末尾并且我们仍在收集字符串,则获取最后一个字符并强制输出。 */ - if (a_len < MAX_AUTO_EXTRA) a_collect[a_len] = out_buf[stage_cur >> 3]; - a_len++; + if (a_len < MAX_AUTO_EXTRA) a_collect[a_len] = out_buf[stage_cur >> 3]; // 如果自动收集数组未满,则添加当前字节 + a_len++; // 增加自动收集数组的长度 - if (a_len >= MIN_AUTO_EXTRA && a_len <= MAX_AUTO_EXTRA) - maybe_add_auto(a_collect, a_len); + if (a_len >= MIN_AUTO_EXTRA && a_len <= MAX_AUTO_EXTRA) // 如果自动收集数组的长度在最小和最大范围内 + maybe_add_auto(a_collect, a_len); // 可能将自动收集的字符串添加到字典中 - } else if (cksum != prev_cksum) { + } else if (cksum != prev_cksum) { // 如果校验和已改变 - /* Otherwise, if the checksum has changed, see if we have something - worthwhile queued up, and collect that if the answer is yes. */ + /* 否则,如果校验和已改变,检查是否有值得排队的内容,如果有则收集。 */ - if (a_len >= MIN_AUTO_EXTRA && a_len <= MAX_AUTO_EXTRA) - maybe_add_auto(a_collect, a_len); + if (a_len >= MIN_AUTO_EXTRA && a_len <= MAX_AUTO_EXTRA) // 如果自动收集数组的长度在最小和最大范围内 + maybe_add_auto(a_collect, a_len); // 可能将自动收集的字符串添加到字典中 - a_len = 0; - prev_cksum = cksum; + a_len = 0; // 重置自动收集数组的长度 + prev_cksum = cksum; // 更新前一个校验和 } - /* Continue collecting string, but only if the bit flip actually made - any difference - we don't want no-op tokens. */ + /* 继续收集字符串,但仅在位翻转实际上产生了任何差异时 - 我们不希望无操作标记。 */ - if (cksum != queue_cur->exec_cksum) { + if (cksum != queue_cur->exec_cksum) { // 如果校验和与当前队列项的校验和不同 - if (a_len < MAX_AUTO_EXTRA) a_collect[a_len] = out_buf[stage_cur >> 3]; - a_len++; + if (a_len < MAX_AUTO_EXTRA) a_collect[a_len] = out_buf[stage_cur >> 3]; // 如果自动收集数组未满,则添加当前字节 + a_len++; // 增加自动收集数组的长度 } @@ -5258,68 +5192,69 @@ static u8 fuzz_one(char** argv) { } - new_hit_cnt = queued_paths + unique_crashes; + new_hit_cnt = queued_paths + unique_crashes; // 计算新的命中计数 - stage_finds[STAGE_FLIP1] += new_hit_cnt - orig_hit_cnt; - stage_cycles[STAGE_FLIP1] += stage_max; + stage_finds[STAGE_FLIP1] += new_hit_cnt - orig_hit_cnt; // 更新当前阶段的发现计数 + stage_cycles[STAGE_FLIP1] += stage_max; // 更新当前阶段的循环计数 - /* Two walking bits. */ + /* 两个连续的位翻转。 */ - stage_name = "bitflip 2/1"; - stage_short = "flip2"; - stage_max = (len << 3) - 1; + stage_name = "bitflip 2/1"; // 设置当前阶段的完整名称为 "bitflip 2/1" + stage_short = "flip2"; // 设置当前阶段的简短名称为 "flip2" + stage_max = (len << 3) - 1; // 计算最大阶段数,len 是输入数据的长度,左移3位相当于乘以8,减去1 - orig_hit_cnt = new_hit_cnt; + orig_hit_cnt = new_hit_cnt; // 记录当前阶段的初始命中计数 - for (stage_cur = 0; stage_cur < stage_max; stage_cur++) { + for (stage_cur = 0; stage_cur < stage_max; stage_cur++) { // 遍历每个位 - stage_cur_byte = stage_cur >> 3; + stage_cur_byte = stage_cur >> 3; // 计算当前字节的位置 - FLIP_BIT(out_buf, stage_cur); - FLIP_BIT(out_buf, stage_cur + 1); + FLIP_BIT(out_buf, stage_cur); // 翻转当前位 + FLIP_BIT(out_buf, stage_cur + 1); // 翻转下一个位 - if (common_fuzz_stuff(argv, out_buf, len)) goto abandon_entry; + if (common_fuzz_stuff(argv, out_buf, len)) goto abandon_entry; // 执行模糊测试,如果返回非零值则跳转到 abandon_entry - FLIP_BIT(out_buf, stage_cur); - FLIP_BIT(out_buf, stage_cur + 1); + FLIP_BIT(out_buf, stage_cur); // 恢复当前位的翻转 + FLIP_BIT(out_buf, stage_cur + 1); // 恢复下一个位的翻转 } - new_hit_cnt = queued_paths + unique_crashes; + new_hit_cnt = queued_paths + unique_crashes; // 计算新的命中计数 - stage_finds[STAGE_FLIP2] += new_hit_cnt - orig_hit_cnt; - stage_cycles[STAGE_FLIP2] += stage_max; + stage_finds[STAGE_FLIP2] += new_hit_cnt - orig_hit_cnt; // 更新当前阶段的发现计数 + stage_cycles[STAGE_FLIP2] += stage_max; // 更新当前阶段的循环计数 - /* Four walking bits. */ + /* 四个连续的位翻转。 */ - stage_name = "bitflip 4/1"; - stage_short = "flip4"; - stage_max = (len << 3) - 3; + stage_name = "bitflip 4/1"; // 设置当前阶段的完整名称为 "bitflip 4/1" + stage_short = "flip4"; // 设置当前阶段的简短名称为 "flip4" + stage_max = (len << 3) - 3; // 计算最大阶段数,len 是输入数据的长度,左移3位相当于乘以8,减去3 - orig_hit_cnt = new_hit_cnt; + orig_hit_cnt = new_hit_cnt; // 记录当前阶段的初始命中计数 - for (stage_cur = 0; stage_cur < stage_max; stage_cur++) { + for (stage_cur = 0; stage_cur < stage_max; stage_cur++) { // 遍历每个位 - stage_cur_byte = stage_cur >> 3; + stage_cur_byte = stage_cur >> 3; // 计算当前字节的位置 - FLIP_BIT(out_buf, stage_cur); - FLIP_BIT(out_buf, stage_cur + 1); - FLIP_BIT(out_buf, stage_cur + 2); - FLIP_BIT(out_buf, stage_cur + 3); + FLIP_BIT(out_buf, stage_cur); // 翻转当前位 + FLIP_BIT(out_buf, stage_cur + 1); // 翻转下一个位 + FLIP_BIT(out_buf, stage_cur + 2); // 翻转下下个位 + FLIP_BIT(out_buf, stage_cur + 3); // 翻转下下下个位 - if (common_fuzz_stuff(argv, out_buf, len)) goto abandon_entry; + if (common_fuzz_stuff(argv, out_buf, len)) goto abandon_entry; // 执行模糊测试,如果返回非零值则跳转到 abandon_entry - FLIP_BIT(out_buf, stage_cur); - FLIP_BIT(out_buf, stage_cur + 1); - FLIP_BIT(out_buf, stage_cur + 2); - FLIP_BIT(out_buf, stage_cur + 3); + FLIP_BIT(out_buf, stage_cur); // 恢复当前位的翻转 + FLIP_BIT(out_buf, stage_cur + 1); // 恢复下一个位的翻转 + FLIP_BIT(out_buf, stage_cur + 2); // 恢复下下个位的翻转 + FLIP_BIT(out_buf, stage_cur + 3); // 恢复下下下个位的翻转 } - new_hit_cnt = queued_paths + unique_crashes; + new_hit_cnt = queued_paths + unique_crashes; // 计算新的命中计数 + + stage_finds[STAGE_FLIP4] += new_hit_cnt - orig_hit_cnt; // 更新当前阶段的发现计数 + stage_cycles[STAGE_FLIP4] += stage_max; // 更新当前阶段的循环计数 - stage_finds[STAGE_FLIP4] += new_hit_cnt - orig_hit_cnt; - stage_cycles[STAGE_FLIP4] += stage_max; /* Effector map setup. These macros calculate: @@ -5329,614 +5264,633 @@ static u8 fuzz_one(char** argv) { */ -#define EFF_APOS(_p) ((_p) >> EFF_MAP_SCALE2) -#define EFF_REM(_x) ((_x) & ((1 << EFF_MAP_SCALE2) - 1)) -#define EFF_ALEN(_l) (EFF_APOS(_l) + !!EFF_REM(_l)) -#define EFF_SPAN_ALEN(_p, _l) (EFF_APOS((_p) + (_l) - 1) - EFF_APOS(_p) + 1) +#define EFF_APOS(_p) ((_p) >> EFF_MAP_SCALE2) // 计算位置 _p 在 effector map 中的索引 +#define EFF_REM(_x) ((_x) & ((1 << EFF_MAP_SCALE2) - 1)) // 计算位置 _x 在 effector map 中的偏移量 +#define EFF_ALEN(_l) (EFF_APOS(_l) + !!EFF_REM(_l)) // 计算长度 _l 对应的 effector map 的长度 +#define EFF_SPAN_ALEN(_p, _l) (EFF_APOS((_p) + (_l) - 1) - EFF_APOS(_p) + 1) // 计算从位置 _p 开始长度为 _l 的跨度在 effector map 中的长度 - /* Initialize effector map for the next step (see comments below). Always - flag first and last byte as doing something. */ + /* 初始化 effector map 用于下一步(见下方注释)。始终标记第一个和最后一个字节为有效。 */ - eff_map = ck_alloc(EFF_ALEN(len)); - eff_map[0] = 1; + eff_map = ck_alloc(EFF_ALEN(len)); // 分配 effector map 的内存 + eff_map[0] = 1; // 标记第一个字节为有效 - if (EFF_APOS(len - 1) != 0) { - eff_map[EFF_APOS(len - 1)] = 1; - eff_cnt++; + if (EFF_APOS(len - 1) != 0) { // 如果最后一个字节不在第一个块中 + eff_map[EFF_APOS(len - 1)] = 1; // 标记最后一个字节为有效 + eff_cnt++; // 增加有效字节计数 } - /* Walking byte. */ + /* 单字节翻转阶段 */ - stage_name = "bitflip 8/8"; - stage_short = "flip8"; - stage_max = len; + stage_name = "bitflip 8/8"; // 设置阶段名称为 "bitflip 8/8" + stage_short = "flip8"; // 设置阶段简称为 "flip8" + stage_max = len; // 设置阶段最大值为输入长度 - orig_hit_cnt = new_hit_cnt; + orig_hit_cnt = new_hit_cnt; // 记录当前阶段的命中计数 - for (stage_cur = 0; stage_cur < stage_max; stage_cur++) { + for (stage_cur = 0; stage_cur < stage_max; stage_cur++) { // 遍历每个字节 - stage_cur_byte = stage_cur; + stage_cur_byte = stage_cur; // 记录当前字节位置 - out_buf[stage_cur] ^= 0xFF; + out_buf[stage_cur] ^= 0xFF; // 翻转当前字节的所有位 - if (common_fuzz_stuff(argv, out_buf, len)) goto abandon_entry; + if (common_fuzz_stuff(argv, out_buf, len)) goto abandon_entry; // 执行模糊测试 - /* We also use this stage to pull off a simple trick: we identify - bytes that seem to have no effect on the current execution path - even when fully flipped - and we skip them during more expensive - deterministic stages, such as arithmetics or known ints. */ + /* 我们还利用这个阶段来执行一个简单的技巧:识别那些即使完全翻转也对当前执行路径没有影响的字节, + 并在更昂贵的确定性阶段(如算术或已知整数)中跳过它们。 */ - if (!eff_map[EFF_APOS(stage_cur)]) { + if (!eff_map[EFF_APOS(stage_cur)]) { // 如果当前字节在 effector map 中未被标记为有效 u32 cksum; - /* If in dumb mode or if the file is very short, just flag everything - without wasting time on checksums. */ + /* 如果在 dumb 模式或文件非常短的情况下,直接标记所有字节而不浪费时间计算校验和。 */ - if (!dumb_mode && len >= EFF_MIN_LEN) - cksum = hash32(trace_bits, MAP_SIZE, HASH_CONST); + if (!dumb_mode && len >= EFF_MIN_LEN) // 如果不是 dumb 模式且长度大于等于最小长度 + cksum = hash32(trace_bits, MAP_SIZE, HASH_CONST); // 计算校验和 else - cksum = ~queue_cur->exec_cksum; + cksum = ~queue_cur->exec_cksum; // 使用队列当前项的校验和的补码 - if (cksum != queue_cur->exec_cksum) { - eff_map[EFF_APOS(stage_cur)] = 1; - eff_cnt++; + if (cksum != queue_cur->exec_cksum) { // 如果校验和与当前执行校验和不匹配 + eff_map[EFF_APOS(stage_cur)] = 1; // 标记当前字节为有效 + eff_cnt++; // 增加有效字节计数 } } - out_buf[stage_cur] ^= 0xFF; + out_buf[stage_cur] ^= 0xFF; // 恢复当前字节的原始值 } - /* If the effector map is more than EFF_MAX_PERC dense, just flag the - whole thing as worth fuzzing, since we wouldn't be saving much time - anyway. */ + /* 如果 effector map 的密度超过 EFF_MAX_PERC,则将整个 map 标记为值得模糊测试, + 因为无论如何我们都不会节省太多时间。 */ if (eff_cnt != EFF_ALEN(len) && - eff_cnt * 100 / EFF_ALEN(len) > EFF_MAX_PERC) { + eff_cnt * 100 / EFF_ALEN(len) > EFF_MAX_PERC) { // 如果有效字节比例超过最大百分比 - memset(eff_map, 1, EFF_ALEN(len)); + memset(eff_map, 1, EFF_ALEN(len)); // 将整个 effector map 标记为有效 - blocks_eff_select += EFF_ALEN(len); + blocks_eff_select += EFF_ALEN(len); // 增加有效块选择计数 } else { - blocks_eff_select += eff_cnt; + blocks_eff_select += eff_cnt; // 增加有效块选择计数 } - blocks_eff_total += EFF_ALEN(len); + blocks_eff_total += EFF_ALEN(len); // 增加总有效块计数 - new_hit_cnt = queued_paths + unique_crashes; + new_hit_cnt = queued_paths + unique_crashes; // 更新命中计数 - stage_finds[STAGE_FLIP8] += new_hit_cnt - orig_hit_cnt; - stage_cycles[STAGE_FLIP8] += stage_max; + stage_finds[STAGE_FLIP8] += new_hit_cnt - orig_hit_cnt; // 更新阶段发现计数 + stage_cycles[STAGE_FLIP8] += stage_max; // 更新阶段循环计数 - /* Two walking bytes. */ + /* 双字节翻转阶段 */ - if (len < 2) goto skip_bitflip; + if (len < 2) goto skip_bitflip; // 如果长度小于 2,跳过双字节翻转阶段 - stage_name = "bitflip 16/8"; - stage_short = "flip16"; - stage_cur = 0; - stage_max = len - 1; + stage_name = "bitflip 16/8"; // 设置阶段名称为 "bitflip 16/8" + stage_short = "flip16"; // 设置阶段简称为 "flip16" + stage_cur = 0; // 重置当前阶段索引 + stage_max = len - 1; // 设置阶段最大值为长度减一 - orig_hit_cnt = new_hit_cnt; + orig_hit_cnt = new_hit_cnt; // 记录当前阶段的命中计数 - for (i = 0; i < len - 1; i++) { + for (i = 0; i < len - 1; i++) { // 遍历每对字节 - /* Let's consult the effector map... */ + /* 让我们参考 effector map... */ - if (!eff_map[EFF_APOS(i)] && !eff_map[EFF_APOS(i + 1)]) { - stage_max--; - continue; + if (!eff_map[EFF_APOS(i)] && !eff_map[EFF_APOS(i + 1)]) { // 如果当前字节和下一个字节在 effector map 中未被标记为有效 + stage_max--; // 减少阶段最大值 + continue; // 跳过当前字节对 } - stage_cur_byte = i; - - *(u16*)(out_buf + i) ^= 0xFFFF; + stage_cur_byte = i; // 记录当前字节位置 - if (common_fuzz_stuff(argv, out_buf, len)) goto abandon_entry; - stage_cur++; + *(u16*)(out_buf + i) ^= 0xFFFF; // 翻转当前字节对的所有位 - *(u16*)(out_buf + i) ^= 0xFFFF; + if (common_fuzz_stuff(argv, out_buf, len)) goto abandon_entry; // 执行模糊测试 + stage_cur++; // 增加当前阶段索引 + *(u16*)(out_buf + i) ^= 0xFFFF; // 恢复当前字节对的原始值 } - new_hit_cnt = queued_paths + unique_crashes; + new_hit_cnt = queued_paths + unique_crashes; // 更新命中计数 - stage_finds[STAGE_FLIP16] += new_hit_cnt - orig_hit_cnt; - stage_cycles[STAGE_FLIP16] += stage_max; + stage_finds[STAGE_FLIP16] += new_hit_cnt - orig_hit_cnt; // 更新阶段发现计数 + stage_cycles[STAGE_FLIP16] += stage_max; // 更新阶段循环计数 - if (len < 4) goto skip_bitflip; + if (len < 4) goto skip_bitflip; // 如果长度小于 4,跳过四字节翻转阶段 - /* Four walking bytes. */ + /* 四字节翻转阶段 */ - stage_name = "bitflip 32/8"; - stage_short = "flip32"; - stage_cur = 0; - stage_max = len - 3; + stage_name = "bitflip 32/8"; // 设置阶段名称为 "bitflip 32/8" + stage_short = "flip32"; // 设置阶段简称为 "flip32" + stage_cur = 0; // 重置当前阶段索引 + stage_max = len - 3; // 设置阶段最大值为长度减三 - orig_hit_cnt = new_hit_cnt; + orig_hit_cnt = new_hit_cnt; // 记录当前阶段的命中计数 - for (i = 0; i < len - 3; i++) { + for (i = 0; i < len - 3; i++) { // 遍历每四个字节 - /* Let's consult the effector map... */ + /* 让我们参考 effector map... */ if (!eff_map[EFF_APOS(i)] && !eff_map[EFF_APOS(i + 1)] && - !eff_map[EFF_APOS(i + 2)] && !eff_map[EFF_APOS(i + 3)]) { - stage_max--; - continue; + !eff_map[EFF_APOS(i + 2)] && !eff_map[EFF_APOS(i + 3)]) { // 如果当前四个字节在 effector map 中未被标记为有效 + stage_max--; // 减少阶段最大值 + continue; // 跳过当前四个字节 } - stage_cur_byte = i; + stage_cur_byte = i; // 记录当前字节位置 - *(u32*)(out_buf + i) ^= 0xFFFFFFFF; + *(u32*)(out_buf + i) ^= 0xFFFFFFFF; // 翻转当前四个字节的所有位 - if (common_fuzz_stuff(argv, out_buf, len)) goto abandon_entry; - stage_cur++; + if (common_fuzz_stuff(argv, out_buf, len)) goto abandon_entry; // 执行模糊测试 + stage_cur++; // 增加当前阶段索引 - *(u32*)(out_buf + i) ^= 0xFFFFFFFF; + *(u32*)(out_buf + i) ^= 0xFFFFFFFF; // 恢复当前四个字节的原始值 } - new_hit_cnt = queued_paths + unique_crashes; + new_hit_cnt = queued_paths + unique_crashes; // 更新命中计数 - stage_finds[STAGE_FLIP32] += new_hit_cnt - orig_hit_cnt; - stage_cycles[STAGE_FLIP32] += stage_max; + stage_finds[STAGE_FLIP32] += new_hit_cnt - orig_hit_cnt; // 更新阶段发现计数 + stage_cycles[STAGE_FLIP32] += stage_max; // 更新阶段循环计数 skip_bitflip: - if (no_arith) goto skip_arith; + if (no_arith) goto skip_arith; // 如果不需要进行算术操作,跳过算术操作阶段 /********************** * ARITHMETIC INC/DEC * **********************/ - /* 8-bit arithmetics. */ + /* 8-bit arithmetics. */ // 8位算术操作 - stage_name = "arith 8/8"; - stage_short = "arith8"; - stage_cur = 0; - stage_max = 2 * len * ARITH_MAX; + stage_name = "arith 8/8"; // 当前阶段的名称 + stage_short = "arith8"; // 当前阶段的简称 + stage_cur = 0; // 当前阶段的进度 + stage_max = 2 * len * ARITH_MAX; // 当前阶段的最大进度 - stage_val_type = STAGE_VAL_LE; + stage_val_type = STAGE_VAL_LE; // 当前阶段的值类型(小端序) - orig_hit_cnt = new_hit_cnt; + orig_hit_cnt = new_hit_cnt; // 保存当前的命中计数 - for (i = 0; i < len; i++) { + for (i = 0; i < len; i++) { // 遍历输入缓冲区中的每个字节 - u8 orig = out_buf[i]; + u8 orig = out_buf[i]; // 获取当前字节的原始值 - /* Let's consult the effector map... */ + /* Let's consult the effector map... */ // 检查effector map - if (!eff_map[EFF_APOS(i)]) { - stage_max -= 2 * ARITH_MAX; - continue; + if (!eff_map[EFF_APOS(i)]) { // 如果当前字节不在effector map中 + stage_max -= 2 * ARITH_MAX; // 减少最大进度 + continue; // 跳过当前字节 } - stage_cur_byte = i; + stage_cur_byte = i; // 记录当前处理的字节位置 - for (j = 1; j <= ARITH_MAX; j++) { + for (j = 1; j <= ARITH_MAX; j++) { // 遍历ARITH_MAX范围内的值 - u8 r = orig ^ (orig + j); + u8 r = orig ^ (orig + j); // 计算原始值与增加j后的值的异或结果 /* Do arithmetic operations only if the result couldn't be a product - of a bitflip. */ + of a bitflip. */ // 只有在结果不可能是位翻转的情况下才进行算术操作 - if (!could_be_bitflip(r)) { + if (!could_be_bitflip(r)) { // 如果结果不可能是位翻转 - stage_cur_val = j; - out_buf[i] = orig + j; + stage_cur_val = j; // 记录当前的值 + out_buf[i] = orig + j; // 修改当前字节的值 - if (common_fuzz_stuff(argv, out_buf, len)) goto abandon_entry; - stage_cur++; + if (common_fuzz_stuff(argv, out_buf, len)) goto abandon_entry; // 调用fuzz函数,如果返回非零则退出 + stage_cur++; // 增加当前阶段的进度 - } else stage_max--; + } else stage_max--; // 否则减少最大进度 - r = orig ^ (orig - j); + r = orig ^ (orig - j); // 计算原始值与减少j后的值的异或结果 - if (!could_be_bitflip(r)) { + if (!could_be_bitflip(r)) { // 如果结果不可能是位翻转 - stage_cur_val = -j; - out_buf[i] = orig - j; + stage_cur_val = -j; // 记录当前的值(负数) + out_buf[i] = orig - j; // 修改当前字节的值 - if (common_fuzz_stuff(argv, out_buf, len)) goto abandon_entry; - stage_cur++; + if (common_fuzz_stuff(argv, out_buf, len)) goto abandon_entry; // 调用fuzz函数,如果返回非零则退出 + stage_cur++; // 增加当前阶段的进度 - } else stage_max--; + } else stage_max--; // 否则减少最大进度 - out_buf[i] = orig; + out_buf[i] = orig; // 恢复当前字节的原始值 } } + + // 计算新命中计数,是排队路径和唯一崩溃的总和 new_hit_cnt = queued_paths + unique_crashes; + // 更新当前阶段的发现计数和周期 stage_finds[STAGE_ARITH8] += new_hit_cnt - orig_hit_cnt; stage_cycles[STAGE_ARITH8] += stage_max; - /* 16-bit arithmetics, both endians. */ + /* 16位算术运算,支持两种字节序。 */ + // 如果长度小于2,跳过算术操作 if (len < 2) goto skip_arith; + // 设置当前阶段的名称与短名称 stage_name = "arith 16/8"; stage_short = "arith16"; - stage_cur = 0; - stage_max = 4 * (len - 1) * ARITH_MAX; + stage_cur = 0; // 当前计数器初始化为0 + stage_max = 4 * (len - 1) * ARITH_MAX; // 最大操作数计算 + // 保存原始的命中计数 orig_hit_cnt = new_hit_cnt; + // 遍历每个字节,进行算术操作 for (i = 0; i < len - 1; i++) { + // 读取当前字节和下一个字节作为16位数 u16 orig = *(u16*)(out_buf + i); - /* Let's consult the effector map... */ - + // 查看效应映射表 if (!eff_map[EFF_APOS(i)] && !eff_map[EFF_APOS(i + 1)]) { - stage_max -= 4 * ARITH_MAX; - continue; + stage_max -= 4 * ARITH_MAX; // 减少最大值 + continue; // 继续下一次循环 } + // 当前操作的字节索引 stage_cur_byte = i; + // 尝试进行加法和减法操作 for (j = 1; j <= ARITH_MAX; j++) { + // 进行加法和异或操作 u16 r1 = orig ^ (orig + j), r2 = orig ^ (orig - j), r3 = orig ^ SWAP16(SWAP16(orig) + j), r4 = orig ^ SWAP16(SWAP16(orig) - j); - /* Try little endian addition and subtraction first. Do it only - if the operation would affect more than one byte (hence the - & 0xff overflow checks) and if it couldn't be a product of - a bitflip. */ - + // 尝试小端加法 stage_val_type = STAGE_VAL_LE; + // 判断加法是否溢出,并且结果不应该是位翻转 if ((orig & 0xff) + j > 0xff && !could_be_bitflip(r1)) { + stage_cur_val = j; // 设置当前值为j + *(u16*)(out_buf + i) = orig + j; // 更新输出缓冲区 - stage_cur_val = j; - *(u16*)(out_buf + i) = orig + j; - + // 执行模糊测试 if (common_fuzz_stuff(argv, out_buf, len)) goto abandon_entry; - stage_cur++; - - } else stage_max--; + stage_cur++; // 成功计数 - if ((orig & 0xff) < j && !could_be_bitflip(r2)) { + } else stage_max--; // 若不成功,则减少最大周期 - stage_cur_val = -j; - *(u16*)(out_buf + i) = orig - j; + // 尝试小端减法 + if ((orig & 0xff) < j && !could_be_bitflip(r2)) { + stage_cur_val = -j; // 设置当前值为-j + *(u16*)(out_buf + i) = orig - j; // 更新输出缓冲区 + // 执行模糊测试 if (common_fuzz_stuff(argv, out_buf, len)) goto abandon_entry; - stage_cur++; + stage_cur++; // 成功计数 - } else stage_max--; - - /* Big endian comes next. Same deal. */ + } else stage_max--; // 若不成功,则减少最大周期 + // 尝试大端加法 stage_val_type = STAGE_VAL_BE; - + // 判断加法是否溢出,并且结果不应该是位翻转 if ((orig >> 8) + j > 0xff && !could_be_bitflip(r3)) { + stage_cur_val = j; // 设置当前值为j + *(u16*)(out_buf + i) = SWAP16(SWAP16(orig) + j); // 更新输出缓冲区 - stage_cur_val = j; - *(u16*)(out_buf + i) = SWAP16(SWAP16(orig) + j); - + // 执行模糊测试 if (common_fuzz_stuff(argv, out_buf, len)) goto abandon_entry; - stage_cur++; + stage_cur++; // 成功计数 - } else stage_max--; + } else stage_max--; // 若不成功,则减少最大周期 + // 尝试大端减法 if ((orig >> 8) < j && !could_be_bitflip(r4)) { + stage_cur_val = -j; // 设置当前值为-j + *(u16*)(out_buf + i) = SWAP16(SWAP16(orig) - j); // 更新输出缓冲区 - stage_cur_val = -j; - *(u16*)(out_buf + i) = SWAP16(SWAP16(orig) - j); - + // 执行模糊测试 if (common_fuzz_stuff(argv, out_buf, len)) goto abandon_entry; - stage_cur++; + stage_cur++; // 成功计数 - } else stage_max--; + } else stage_max--; // 若不成功,则减少最大周期 + // 恢复原值 *(u16*)(out_buf + i) = orig; } } + // 更新命中计数 new_hit_cnt = queued_paths + unique_crashes; + // 更新当前阶段的发现计数和周期 stage_finds[STAGE_ARITH16] += new_hit_cnt - orig_hit_cnt; stage_cycles[STAGE_ARITH16] += stage_max; - /* 32-bit arithmetics, both endians. */ + /* 32位算术运算,支持两种字节序。 */ + // 如果长度小于4,跳过算术操作 if (len < 4) goto skip_arith; + // 设置当前阶段的名称与短名称 stage_name = "arith 32/8"; stage_short = "arith32"; - stage_cur = 0; - stage_max = 4 * (len - 3) * ARITH_MAX; + stage_cur = 0; // 当前计数器初始化为0 + stage_max = 4 * (len - 3) * ARITH_MAX; // 最大操作数计算 + // 保存原始的命中计数 orig_hit_cnt = new_hit_cnt; + // 遍历每个字节,进行算术操作 for (i = 0; i < len - 3; i++) { + // 读取当前字节和接下来三个字节作为32位数 u32 orig = *(u32*)(out_buf + i); - /* Let's consult the effector map... */ - + // 查看效应映射表 if (!eff_map[EFF_APOS(i)] && !eff_map[EFF_APOS(i + 1)] && !eff_map[EFF_APOS(i + 2)] && !eff_map[EFF_APOS(i + 3)]) { - stage_max -= 4 * ARITH_MAX; - continue; + stage_max -= 4 * ARITH_MAX; // 减少最大值 + continue; // 继续下一次循环 } + // 当前操作的字节索引 stage_cur_byte = i; + // 尝试进行加法和减法操作 for (j = 1; j <= ARITH_MAX; j++) { + // 进行加法和异或操作 u32 r1 = orig ^ (orig + j), r2 = orig ^ (orig - j), r3 = orig ^ SWAP32(SWAP32(orig) + j), r4 = orig ^ SWAP32(SWAP32(orig) - j); - /* Little endian first. Same deal as with 16-bit: we only want to - try if the operation would have effect on more than two bytes. */ - + // 尝试小端加法 stage_val_type = STAGE_VAL_LE; + // 判断加法是否溢出,并且结果不应该是位翻转 if ((orig & 0xffff) + j > 0xffff && !could_be_bitflip(r1)) { - stage_cur_val = j; - *(u32*)(out_buf + i) = orig + j; + stage_cur_val = j; // 设置当前值为j + *(u32*)(out_buf + i) = orig + j; // 更新输出缓冲区 + // 执行模糊测试 if (common_fuzz_stuff(argv, out_buf, len)) goto abandon_entry; - stage_cur++; + stage_cur++; // 成功计数 - } else stage_max--; + } else stage_max--; // 若不成功,则减少最大周期 + // 尝试小端减法 if ((orig & 0xffff) < j && !could_be_bitflip(r2)) { - stage_cur_val = -j; - *(u32*)(out_buf + i) = orig - j; + stage_cur_val = -j; // 设置当前值为-j + *(u32*)(out_buf + i) = orig - j; // 更新输出缓冲区 + // 执行模糊测试 if (common_fuzz_stuff(argv, out_buf, len)) goto abandon_entry; - stage_cur++; - - } else stage_max--; + stage_cur++; // 成功计数 - /* Big endian next. */ + } else stage_max--; // 若不成功,则减少最大周期 + // 尝试大端加法 stage_val_type = STAGE_VAL_BE; + // 判断加法是否溢出,并且结果不应该是位翻转 if ((SWAP32(orig) & 0xffff) + j > 0xffff && !could_be_bitflip(r3)) { - stage_cur_val = j; - *(u32*)(out_buf + i) = SWAP32(SWAP32(orig) + j); + stage_cur_val = j; // 设置当前值为j + *(u32*)(out_buf + i) = SWAP32(SWAP32(orig) + j); // 更新输出缓冲区 + // 执行模糊测试 if (common_fuzz_stuff(argv, out_buf, len)) goto abandon_entry; - stage_cur++; + stage_cur++; // 成功计数 - } else stage_max--; + } else stage_max--; // 若不成功,则减少最大周期 + // 尝试大端减法 if ((SWAP32(orig) & 0xffff) < j && !could_be_bitflip(r4)) { - stage_cur_val = -j; - *(u32*)(out_buf + i) = SWAP32(SWAP32(orig) - j); + stage_cur_val = -j; // 设置当前值为-j + *(u32*)(out_buf + i) = SWAP32(SWAP32(orig) - j); // 更新输出缓冲区 + // 执行模糊测试 if (common_fuzz_stuff(argv, out_buf, len)) goto abandon_entry; - stage_cur++; + stage_cur++; // 成功计数 - } else stage_max--; + } else stage_max--; // 若不成功,则减少最大周期 + // 恢复原值 *(u32*)(out_buf + i) = orig; } } + // 更新命中计数 new_hit_cnt = queued_paths + unique_crashes; + // 更新当前阶段的发现计数和周期 stage_finds[STAGE_ARITH32] += new_hit_cnt - orig_hit_cnt; stage_cycles[STAGE_ARITH32] += stage_max; + skip_arith: /********************** * INTERESTING VALUES * **********************/ - stage_name = "interest 8/8"; - stage_short = "int8"; - stage_cur = 0; - stage_max = len * sizeof(interesting_8); + stage_name = "interest 8/8"; // 当前阶段名称:8位整数的有趣值 + stage_short = "int8"; // 当前阶段缩写 + stage_cur = 0; // 当前阶段计数器 + stage_max = len * sizeof(interesting_8); // 当前阶段最大操作次数 - stage_val_type = STAGE_VAL_LE; + stage_val_type = STAGE_VAL_LE; // 当前值类型:小端 - orig_hit_cnt = new_hit_cnt; + orig_hit_cnt = new_hit_cnt; // 记录原始命中计数 - /* Setting 8-bit integers. */ + /* 设置8位整数。 */ - for (i = 0; i < len; i++) { + for (i = 0; i < len; i++) { // 遍历输入缓冲区的每个字节 - u8 orig = out_buf[i]; + u8 orig = out_buf[i]; // 保存原始字节值 - /* Let's consult the effector map... */ + /* 咨询效应地图... */ - if (!eff_map[EFF_APOS(i)]) { - stage_max -= sizeof(interesting_8); - continue; + if (!eff_map[EFF_APOS(i)]) { // 如果当前字节位置没有效应 + stage_max -= sizeof(interesting_8); // 减少最大操作次数 + continue; // 跳过当前循环 } - stage_cur_byte = i; + stage_cur_byte = i; // 设置当前字节位置 - for (j = 0; j < sizeof(interesting_8); j++) { + for (j = 0; j < sizeof(interesting_8); j++) { // 遍历8位有趣值 - /* Skip if the value could be a product of bitflips or arithmetics. */ + /* 如果值可能是经过位翻转或算术操作的结果,则跳过。 */ - if (could_be_bitflip(orig ^ (u8)interesting_8[j]) || - could_be_arith(orig, (u8)interesting_8[j], 1)) { - stage_max--; - continue; + if (could_be_bitflip(orig ^ (u8)interesting_8[j]) || // 检查位翻转 + could_be_arith(orig, (u8)interesting_8[j], 1)) { // 检查算术 + stage_max--; // 减少最大操作次数 + continue; // 跳过当前循环 } - stage_cur_val = interesting_8[j]; - out_buf[i] = interesting_8[j]; + stage_cur_val = interesting_8[j]; // 设置当前值为有趣值 + out_buf[i] = interesting_8[j]; // 更新输出缓冲区 - if (common_fuzz_stuff(argv, out_buf, len)) goto abandon_entry; + if (common_fuzz_stuff(argv, out_buf, len)) goto abandon_entry; // 进行模糊测试,若失败则放弃当前条目 - out_buf[i] = orig; - stage_cur++; + out_buf[i] = orig; // 恢复原始字节 + stage_cur++; // 当前阶段计数器增加 } } - new_hit_cnt = queued_paths + unique_crashes; + new_hit_cnt = queued_paths + unique_crashes; // 更新新的命中计数 - stage_finds[STAGE_INTEREST8] += new_hit_cnt - orig_hit_cnt; - stage_cycles[STAGE_INTEREST8] += stage_max; + stage_finds[STAGE_INTEREST8] += new_hit_cnt - orig_hit_cnt; // 记录阶段发现的次数 + stage_cycles[STAGE_INTEREST8] += stage_max; // 更新阶段循环次数 - /* Setting 16-bit integers, both endians. */ + /* 设置16位整数,支持两种字节序。 */ - if (no_arith || len < 2) goto skip_interest; + if (no_arith || len < 2) goto skip_interest; // 如果禁止算术或长度小于2,跳过 - stage_name = "interest 16/8"; - stage_short = "int16"; - stage_cur = 0; - stage_max = 2 * (len - 1) * (sizeof(interesting_16) >> 1); + stage_name = "interest 16/8"; // 当前阶段名称:16位整数的有趣值 + stage_short = "int16"; // 当前阶段缩写 + stage_cur = 0; // 当前阶段计数器 + stage_max = 2 * (len - 1) * (sizeof(interesting_16) >> 1); // 最大操作次数 - orig_hit_cnt = new_hit_cnt; + orig_hit_cnt = new_hit_cnt; // 记录原始命中计数 - for (i = 0; i < len - 1; i++) { + for (i = 0; i < len - 1; i++) { // 遍历输入缓冲区的每两个字节 - u16 orig = *(u16*)(out_buf + i); + u16 orig = *(u16*)(out_buf + i); // 保存原始16位值 - /* Let's consult the effector map... */ + /* 咨询效应地图... */ - if (!eff_map[EFF_APOS(i)] && !eff_map[EFF_APOS(i + 1)]) { - stage_max -= sizeof(interesting_16); - continue; + if (!eff_map[EFF_APOS(i)] && !eff_map[EFF_APOS(i + 1)]) { // 如果两个字节位置没有效应 + stage_max -= sizeof(interesting_16); // 减少最大操作次数 + continue; // 跳过当前循环 } - stage_cur_byte = i; + stage_cur_byte = i; // 设置当前字节位置 - for (j = 0; j < sizeof(interesting_16) / 2; j++) { + for (j = 0; j < sizeof(interesting_16) / 2; j++) { // 遍历16位有趣值 - stage_cur_val = interesting_16[j]; + stage_cur_val = interesting_16[j]; // 设置当前值为有趣值 - /* Skip if this could be a product of a bitflip, arithmetics, - or single-byte interesting value insertion. */ + /* 如果这可能是位翻转、算术、或单字节有趣值插入的结果,则跳过。 */ - if (!could_be_bitflip(orig ^ (u16)interesting_16[j]) && - !could_be_arith(orig, (u16)interesting_16[j], 2) && - !could_be_interest(orig, (u16)interesting_16[j], 2, 0)) { + if (!could_be_bitflip(orig ^ (u16)interesting_16[j]) && // 检查位翻转 + !could_be_arith(orig, (u16)interesting_16[j], 2) && // 检查算术 + !could_be_interest(orig, (u16)interesting_16[j], 2, 0)) { // 检查有趣值插入 - stage_val_type = STAGE_VAL_LE; + stage_val_type = STAGE_VAL_LE; // 设置值类型为小端 - *(u16*)(out_buf + i) = interesting_16[j]; + *(u16*)(out_buf + i) = interesting_16[j]; // 更新输出缓冲区 - if (common_fuzz_stuff(argv, out_buf, len)) goto abandon_entry; - stage_cur++; + if (common_fuzz_stuff(argv, out_buf, len)) goto abandon_entry; // 进行模糊测试 + stage_cur++; // 当前阶段计数器增加 - } else stage_max--; + } else stage_max--; // 否则减少最大操作次数 - if ((u16)interesting_16[j] != SWAP16(interesting_16[j]) && - !could_be_bitflip(orig ^ SWAP16(interesting_16[j])) && - !could_be_arith(orig, SWAP16(interesting_16[j]), 2) && - !could_be_interest(orig, SWAP16(interesting_16[j]), 2, 1)) { + if ((u16)interesting_16[j] != SWAP16(interesting_16[j]) && // 检查是否需要交换字节 + !could_be_bitflip(orig ^ SWAP16(interesting_16[j])) && // 检查位翻转 + !could_be_arith(orig, SWAP16(interesting_16[j]), 2) && // 检查算术 + !could_be_interest(orig, SWAP16(interesting_16[j]), 2, 1)) { // 检查有趣值插入 - stage_val_type = STAGE_VAL_BE; + stage_val_type = STAGE_VAL_BE; // 设置值类型为大端 - *(u16*)(out_buf + i) = SWAP16(interesting_16[j]); - if (common_fuzz_stuff(argv, out_buf, len)) goto abandon_entry; - stage_cur++; + *(u16*)(out_buf + i) = SWAP16(interesting_16[j]); // 更新为交换字节的有趣值 + if (common_fuzz_stuff(argv, out_buf, len)) goto abandon_entry; // 进行模糊测试 + stage_cur++; // 当前阶段计数器增加 - } else stage_max--; + } else stage_max--; // 否则减少最大操作次数 } - *(u16*)(out_buf + i) = orig; + *(u16*)(out_buf + i) = orig; // 恢复原始16位值 } - new_hit_cnt = queued_paths + unique_crashes; + new_hit_cnt = queued_paths + unique_crashes; // 更新新的命中计数 - stage_finds[STAGE_INTEREST16] += new_hit_cnt - orig_hit_cnt; - stage_cycles[STAGE_INTEREST16] += stage_max; + stage_finds[STAGE_INTEREST16] += new_hit_cnt - orig_hit_cnt; // 记录阶段发现的次数 + stage_cycles[STAGE_INTEREST16] += stage_max; // 更新阶段循环次数 - if (len < 4) goto skip_interest; + if (len < 4) goto skip_interest; // 如果长度小于4,跳过 - /* Setting 32-bit integers, both endians. */ + /* 设置32位整数,支持两种字节序。 */ - stage_name = "interest 32/8"; - stage_short = "int32"; - stage_cur = 0; - stage_max = 2 * (len - 3) * (sizeof(interesting_32) >> 2); + stage_name = "interest 32/8"; // 当前阶段名称:32位整数的有趣值 + stage_short = "int32"; // 当前阶段缩写 + stage_cur = 0; // 当前阶段计数器 + stage_max = 2 * (len - 3) * (sizeof(interesting_32) >> 2); // 最大操作次数 - orig_hit_cnt = new_hit_cnt; + orig_hit_cnt = new_hit_cnt; // 记录原始命中计数 - for (i = 0; i < len - 3; i++) { + for (i = 0; i < len - 3; i++) { // 遍历输入缓冲区的每四个字节 - u32 orig = *(u32*)(out_buf + i); + u32 orig = *(u32*)(out_buf + i); // 保存原始32位值 - /* Let's consult the effector map... */ + /* 咨询效应地图... */ if (!eff_map[EFF_APOS(i)] && !eff_map[EFF_APOS(i + 1)] && - !eff_map[EFF_APOS(i + 2)] && !eff_map[EFF_APOS(i + 3)]) { - stage_max -= sizeof(interesting_32) >> 1; - continue; + !eff_map[EFF_APOS(i + 2)] && !eff_map[EFF_APOS(i + 3)]) { // 如果四个字节位置没有效应 + stage_max -= sizeof(interesting_32) >> 1; // 减少最大操作次数 + continue; // 跳过当前循环 } - stage_cur_byte = i; + stage_cur_byte = i; // 设置当前字节位置 - for (j = 0; j < sizeof(interesting_32) / 4; j++) { + for (j = 0; j < sizeof(interesting_32) / 4; j++) { // 遍历32位有趣值 - stage_cur_val = interesting_32[j]; + stage_cur_val = interesting_32[j]; // 设置当前值为有趣值 - /* Skip if this could be a product of a bitflip, arithmetics, - or word interesting value insertion. */ + /* 如果这可能是位翻转、算术、或字有趣值插入的结果,则跳过。 */ - if (!could_be_bitflip(orig ^ (u32)interesting_32[j]) && - !could_be_arith(orig, interesting_32[j], 4) && - !could_be_interest(orig, interesting_32[j], 4, 0)) { + if (!could_be_bitflip(orig ^ (u32)interesting_32[j]) && // 检查位翻转 + !could_be_arith(orig, interesting_32[j], 4) && // 检查算术 + !could_be_interest(orig, interesting_32[j], 4, 0)) { // 检查有趣值插入 - stage_val_type = STAGE_VAL_LE; + stage_val_type = STAGE_VAL_LE; // 设置值类型为小端 - *(u32*)(out_buf + i) = interesting_32[j]; + *(u32*)(out_buf + i) = interesting_32[j]; // 更新输出缓冲区 - if (common_fuzz_stuff(argv, out_buf, len)) goto abandon_entry; - stage_cur++; + if (common_fuzz_stuff(argv, out_buf, len)) goto abandon_entry; // 进行模糊测试 + stage_cur++; // 当前阶段计数器增加 - } else stage_max--; + } else stage_max--; // 否则减少最大操作次数 - if ((u32)interesting_32[j] != SWAP32(interesting_32[j]) && - !could_be_bitflip(orig ^ SWAP32(interesting_32[j])) && - !could_be_arith(orig, SWAP32(interesting_32[j]), 4) && - !could_be_interest(orig, SWAP32(interesting_32[j]), 4, 1)) { + if ((u32)interesting_32[j] != SWAP32(interesting_32[j]) && // 检查是否需要交换字节 + !could_be_bitflip(orig ^ SWAP32(interesting_32[j])) && // 检查位翻转 + !could_be_arith(orig, SWAP32(interesting_32[j]), 4) && // 检查算术 + !could_be_interest(orig, SWAP32(interesting_32[j]), 4, 1)) { // 检查有趣值插入 - stage_val_type = STAGE_VAL_BE; + stage_val_type = STAGE_VAL_BE; // 设置值类型为大端 - *(u32*)(out_buf + i) = SWAP32(interesting_32[j]); - if (common_fuzz_stuff(argv, out_buf, len)) goto abandon_entry; - stage_cur++; + *(u32*)(out_buf + i) = SWAP32(interesting_32[j]); // 更新为交换字节的有趣值 + if (common_fuzz_stuff(argv, out_buf, len)) goto abandon_entry; // 进行模糊测试 + stage_cur++; // 当前阶段计数器增加 - } else stage_max--; + } else stage_max--; // 否则减少最大操作次数 } - *(u32*)(out_buf + i) = orig; + *(u32*)(out_buf + i) = orig; // 恢复原始32位值 } - new_hit_cnt = queued_paths + unique_crashes; + new_hit_cnt = queued_paths + unique_crashes; // 更新新的命中计数 - stage_finds[STAGE_INTEREST32] += new_hit_cnt - orig_hit_cnt; - stage_cycles[STAGE_INTEREST32] += stage_max; + stage_finds[STAGE_INTEREST32] += new_hit_cnt - orig_hit_cnt; // 记录阶段发现的次数 + stage_cycles[STAGE_INTEREST32] += stage_max; // 更新阶段循环次数 skip_interest: @@ -5944,633 +5898,582 @@ skip_interest: * DICTIONARY STUFF * ********************/ - if (!extras_cnt) goto skip_user_extras; + if (!extras_cnt) goto skip_user_extras; // 如果没有用户提供的额外数据,跳过用户额外数据的处理 /* Overwrite with user-supplied extras. */ - stage_name = "user extras (over)"; - stage_short = "ext_UO"; - stage_cur = 0; - stage_max = extras_cnt * len; + stage_name = "user extras (over)"; // 设置当前阶段的名称 + stage_short = "ext_UO"; // 设置当前阶段的简称 + stage_cur = 0; // 初始化当前阶段的计数器 + stage_max = extras_cnt * len; // 设置当前阶段的最大迭代次数 - stage_val_type = STAGE_VAL_NONE; + stage_val_type = STAGE_VAL_NONE; // 设置当前阶段的值类型为无 - orig_hit_cnt = new_hit_cnt; + orig_hit_cnt = new_hit_cnt; // 保存当前的命中计数 - for (i = 0; i < len; i++) { + for (i = 0; i < len; i++) { // 遍历输入缓冲区的每个字节 - u32 last_len = 0; + u32 last_len = 0; // 记录上一次写入的额外数据长度 - stage_cur_byte = i; + stage_cur_byte = i; // 设置当前处理的字节位置 /* Extras are sorted by size, from smallest to largest. This means that we don't have to worry about restoring the buffer in between writes at a particular offset determined by the outer loop. */ - for (j = 0; j < extras_cnt; j++) { + for (j = 0; j < extras_cnt; j++) { // 遍历所有用户提供的额外数据 /* Skip extras probabilistically if extras_cnt > MAX_DET_EXTRAS. Also skip them if there's no room to insert the payload, if the token is redundant, or if its entire span has no bytes set in the effector map. */ - if ((extras_cnt > MAX_DET_EXTRAS && UR(extras_cnt) >= MAX_DET_EXTRAS) || - extras[j].len > len - i || - !memcmp(extras[j].data, out_buf + i, extras[j].len) || - !memchr(eff_map + EFF_APOS(i), 1, EFF_SPAN_ALEN(i, extras[j].len))) { + if ((extras_cnt > MAX_DET_EXTRAS && UR(extras_cnt) >= MAX_DET_EXTRAS) || // 如果额外数据过多,随机跳过 + extras[j].len > len - i || // 如果额外数据长度超出剩余缓冲区长度,跳过 + !memcmp(extras[j].data, out_buf + i, extras[j].len) || // 如果额外数据与当前缓冲区内容相同,跳过 + !memchr(eff_map + EFF_APOS(i), 1, EFF_SPAN_ALEN(i, extras[j].len))) { // 如果额外数据所在的区域没有有效字节,跳过 - stage_max--; - continue; + stage_max--; // 减少最大迭代次数 + continue; // 跳过当前额外数据 } - last_len = extras[j].len; - memcpy(out_buf + i, extras[j].data, last_len); + last_len = extras[j].len; // 记录当前额外数据的长度 + memcpy(out_buf + i, extras[j].data, last_len); // 将额外数据复制到输出缓冲区 - if (common_fuzz_stuff(argv, out_buf, len)) goto abandon_entry; + if (common_fuzz_stuff(argv, out_buf, len)) goto abandon_entry; // 执行模糊测试,如果失败则跳转到abandon_entry - stage_cur++; + stage_cur++; // 增加当前阶段的计数器 } /* Restore all the clobbered memory. */ - memcpy(out_buf + i, in_buf + i, last_len); + memcpy(out_buf + i, in_buf + i, last_len); // 恢复被覆盖的内存 } - new_hit_cnt = queued_paths + unique_crashes; + new_hit_cnt = queued_paths + unique_crashes; // 更新命中计数 - stage_finds[STAGE_EXTRAS_UO] += new_hit_cnt - orig_hit_cnt; - stage_cycles[STAGE_EXTRAS_UO] += stage_max; + stage_finds[STAGE_EXTRAS_UO] += new_hit_cnt - orig_hit_cnt; // 更新当前阶段的发现计数 + stage_cycles[STAGE_EXTRAS_UO] += stage_max; // 更新当前阶段的循环计数 /* Insertion of user-supplied extras. */ - stage_name = "user extras (insert)"; - stage_short = "ext_UI"; - stage_cur = 0; - stage_max = extras_cnt * (len + 1); + stage_name = "user extras (insert)"; // 设置当前阶段的名称 + stage_short = "ext_UI"; // 设置当前阶段的简称 + stage_cur = 0; // 初始化当前阶段的计数器 + stage_max = extras_cnt * (len + 1); // 设置当前阶段的最大迭代次数 - orig_hit_cnt = new_hit_cnt; + orig_hit_cnt = new_hit_cnt; // 保存当前的命中计数 - ex_tmp = ck_alloc(len + MAX_DICT_FILE); + ex_tmp = ck_alloc(len + MAX_DICT_FILE); // 分配临时缓冲区 - for (i = 0; i <= len; i++) { + for (i = 0; i <= len; i++) { // 遍历输入缓冲区的每个字节 - stage_cur_byte = i; + stage_cur_byte = i; // 设置当前处理的字节位置 - for (j = 0; j < extras_cnt; j++) { + for (j = 0; j < extras_cnt; j++) { // 遍历所有用户提供的额外数据 - if (len + extras[j].len > MAX_FILE) { + if (len + extras[j].len > MAX_FILE) { // 如果插入额外数据后超出最大文件大小,跳过 stage_max--; continue; } /* Insert token */ - memcpy(ex_tmp + i, extras[j].data, extras[j].len); + memcpy(ex_tmp + i, extras[j].data, extras[j].len); // 将额外数据插入到临时缓冲区 /* Copy tail */ - memcpy(ex_tmp + i + extras[j].len, out_buf + i, len - i); + memcpy(ex_tmp + i + extras[j].len, out_buf + i, len - i); // 复制剩余的数据到临时缓冲区 - if (common_fuzz_stuff(argv, ex_tmp, len + extras[j].len)) { + if (common_fuzz_stuff(argv, ex_tmp, len + extras[j].len)) { // 执行模糊测试,如果失败则跳转到abandon_entry ck_free(ex_tmp); goto abandon_entry; } - stage_cur++; + stage_cur++; // 增加当前阶段的计数器 } /* Copy head */ - ex_tmp[i] = out_buf[i]; + ex_tmp[i] = out_buf[i]; // 复制当前字节到临时缓冲区 } - ck_free(ex_tmp); + ck_free(ex_tmp); // 释放临时缓冲区 - new_hit_cnt = queued_paths + unique_crashes; + new_hit_cnt = queued_paths + unique_crashes; // 更新命中计数 - stage_finds[STAGE_EXTRAS_UI] += new_hit_cnt - orig_hit_cnt; - stage_cycles[STAGE_EXTRAS_UI] += stage_max; + stage_finds[STAGE_EXTRAS_UI] += new_hit_cnt - orig_hit_cnt; // 更新当前阶段的发现计数 + stage_cycles[STAGE_EXTRAS_UI] += stage_max; // 更新当前阶段的循环计数 skip_user_extras: - if (!a_extras_cnt) goto skip_extras; + if (!a_extras_cnt) goto skip_extras; // 如果没有自动生成的额外数据,跳过自动额外数据的处理 - stage_name = "auto extras (over)"; - stage_short = "ext_AO"; - stage_cur = 0; - stage_max = MIN(a_extras_cnt, USE_AUTO_EXTRAS) * len; + stage_name = "auto extras (over)"; // 设置当前阶段的名称 + stage_short = "ext_AO"; // 设置当前阶段的简称 + stage_cur = 0; // 初始化当前阶段的计数器 + stage_max = MIN(a_extras_cnt, USE_AUTO_EXTRAS) * len; // 设置当前阶段的最大迭代次数 - stage_val_type = STAGE_VAL_NONE; + stage_val_type = STAGE_VAL_NONE; // 设置当前阶段的值类型为无 - orig_hit_cnt = new_hit_cnt; + orig_hit_cnt = new_hit_cnt; // 保存当前的命中计数 - for (i = 0; i < len; i++) { + for (i = 0; i < len; i++) { // 遍历输入缓冲区的每个字节 - u32 last_len = 0; + u32 last_len = 0; // 记录上一次写入的额外数据长度 - stage_cur_byte = i; + stage_cur_byte = i; // 设置当前处理的字节位置 - for (j = 0; j < MIN(a_extras_cnt, USE_AUTO_EXTRAS); j++) { + for (j = 0; j < MIN(a_extras_cnt, USE_AUTO_EXTRAS); j++) { // 遍历所有自动生成的额外数据 /* See the comment in the earlier code; extras are sorted by size. */ - if (a_extras[j].len > len - i || - !memcmp(a_extras[j].data, out_buf + i, a_extras[j].len) || - !memchr(eff_map + EFF_APOS(i), 1, EFF_SPAN_ALEN(i, a_extras[j].len))) { + if (a_extras[j].len > len - i || // 如果额外数据长度超出剩余缓冲区长度,跳过 + !memcmp(a_extras[j].data, out_buf + i, a_extras[j].len) || // 如果额外数据与当前缓冲区内容相同,跳过 + !memchr(eff_map + EFF_APOS(i), 1, EFF_SPAN_ALEN(i, a_extras[j].len))) { // 如果额外数据所在的区域没有有效字节,跳过 - stage_max--; - continue; + stage_max--; // 减少最大迭代次数 + continue; // 跳过当前额外数据 } - last_len = a_extras[j].len; - memcpy(out_buf + i, a_extras[j].data, last_len); + last_len = a_extras[j].len; // 记录当前额外数据的长度 + memcpy(out_buf + i, a_extras[j].data, last_len); // 将额外数据复制到输出缓冲区 - if (common_fuzz_stuff(argv, out_buf, len)) goto abandon_entry; + if (common_fuzz_stuff(argv, out_buf, len)) goto abandon_entry; // 执行模糊测试,如果失败则跳转到abandon_entry - stage_cur++; + stage_cur++; // 增加当前阶段的计数器 } /* Restore all the clobbered memory. */ - memcpy(out_buf + i, in_buf + i, last_len); + memcpy(out_buf + i, in_buf + i, last_len); // 恢复被覆盖的内存 + + } + + new_hit_cnt = queued_paths + unique_crashes; // 更新命中计数 + + stage_finds[STAGE_EXTRAS_AO] += new_hit_cnt - orig_hit_cnt; // 更新当前阶段的发现计数 + stage_cycles[STAGE_EXTRAS_AO] += stage_max; // 更新当前阶段的循环计数 + + /* Insertion of auto-generated extras. */ + + stage_name = "auto extras (insert)"; // 设置当前阶段的名称 + stage_short = "ext_AI"; // 设置当前阶段的简称 + stage_cur = 0; // 初始化当前阶段的计数器 + stage_max = MIN(a_extras_cnt, USE_AUTO_EXTRAS) * (len + 1); // 设置当前阶段的最大迭代次数 + + orig_hit_cnt = new_hit_cnt; // 保存当前的命中计数 + + ex_tmp = ck_alloc(len + MAX_DICT_FILE); // 分配临时缓冲区 + + for (i = 0; i <= len; i++) { // 遍历输入缓冲区的每个字节 + + stage_cur_byte = i; // 设置当前处理的字节位置 + + for (j = 0; j < MIN(a_extras_cnt, USE_AUTO_EXTRAS); j++) { // 遍历所有自动生成的额外数据 + + if (len + a_extras[j].len > MAX_FILE) { // 如果插入额外数据后超出最大文件大小,跳过 + stage_max--; + continue; + } + + /* Insert token */ + memcpy(ex_tmp + i, a_extras[j].data, a_extras[j].len); // 将额外数据插入到临时缓冲区 + + /* Copy tail */ + memcpy(ex_tmp + i + a_extras[j].len, out_buf + i, len - i); // 复制剩余的数据到临时缓冲区 + + if (common_fuzz_stuff(argv, ex_tmp, len + a_extras[j].len)) { // 执行模糊测试,如果失败则跳转到abandon_entry + ck_free(ex_tmp); + goto abandon_entry; + } + + stage_cur++; // 增加当前阶段的计数器 + + } + + /* Copy head */ + ex_tmp[i] = out_buf[i]; // 复制当前字节到临时缓冲区 } + ck_free(ex_tmp); // 释放临时缓冲区 + new_hit_cnt = queued_paths + unique_crashes; - stage_finds[STAGE_EXTRAS_AO] += new_hit_cnt - orig_hit_cnt; - stage_cycles[STAGE_EXTRAS_AO] += stage_max; + stage_finds[STAGE_EXTRAS_AO] += new_hit_cnt - orig_hit_cnt; // 更新STAGE_EXTRAS_AO阶段的发现计数 + stage_cycles[STAGE_EXTRAS_AO] += stage_max; // 更新STAGE_EXTRAS_AO阶段的循环计数 -skip_extras: +skip_extras: // 跳过额外的处理步骤 /* If we made this to here without jumping to havoc_stage or abandon_entry, we're properly done with deterministic steps and can mark it as such - in the .state/ directory. */ + in the .state/ directory. */ // 如果程序执行到这里而没有跳转到havoc_stage或abandon_entry,说明我们已经完成了确定性步骤,可以在.state/目录中标记为已完成 - if (!queue_cur->passed_det) mark_as_det_done(queue_cur); + if (!queue_cur->passed_det) mark_as_det_done(queue_cur); // 如果当前队列项尚未标记为完成确定性步骤,则标记为完成 /**************** * RANDOM HAVOC * - ****************/ + ****************/ // 随机破坏阶段 -havoc_stage: +havoc_stage: // 随机破坏阶段的标签 - stage_cur_byte = -1; + stage_cur_byte = -1; // 初始化当前字节位置为-1 /* The havoc stage mutation code is also invoked when splicing files; if the - splice_cycle variable is set, generate different descriptions and such. */ + splice_cycle variable is set, generate different descriptions and such. */ // 当拼接文件时也会调用随机破坏阶段的变异代码;如果splice_cycle变量被设置,生成不同的描述等 - if (!splice_cycle) { + if (!splice_cycle) { // 如果没有进行文件拼接 - stage_name = "havoc"; - stage_short = "havoc"; + stage_name = "havoc"; // 设置阶段名称为"havoc" + stage_short = "havoc"; // 设置阶段简称为"havoc" stage_max = (doing_det ? HAVOC_CYCLES_INIT : HAVOC_CYCLES) * - perf_score / havoc_div / 100; + perf_score / havoc_div / 100; // 根据是否进行确定性测试和性能评分计算最大阶段循环次数 - } else { - - static u8 tmp[32]; + } else { // 如果进行文件拼接 - perf_score = orig_perf; + static u8 tmp[32]; // 定义一个静态的32字节缓冲区 - sprintf(tmp, "splice %u", splice_cycle); - stage_name = tmp; - stage_short = "splice"; - stage_max = SPLICE_HAVOC * perf_score / havoc_div / 100; + perf_score = orig_perf; // 恢复原始性能评分 + sprintf(tmp, "splice %u", splice_cycle); // 格式化拼接周期的描述 + stage_name = tmp; // 设置阶段名称为拼接周期的描述 + stage_short = "splice"; // 设置阶段简称为"splice" + stage_max = SPLICE_HAVOC * perf_score / havoc_div / 100; // 根据拼接周期的性能评分计算最大阶段循环次数 } - if (stage_max < HAVOC_MIN) stage_max = HAVOC_MIN; + if (stage_max < HAVOC_MIN) stage_max = HAVOC_MIN; // 如果最大阶段循环次数小于HAVOC_MIN,则将其设置为HAVOC_MIN - temp_len = len; + temp_len = len; // 将临时长度设置为输入缓冲区的长度 - orig_hit_cnt = queued_paths + unique_crashes; + orig_hit_cnt = queued_paths + unique_crashes; // 记录当前已排队的路径和唯一崩溃的总数 - havoc_queued = queued_paths; + havoc_queued = queued_paths; // 记录当前已排队的路径数 /* We essentially just do several thousand runs (depending on perf_score) - where we take the input file and make random stacked tweaks. */ + where we take the input file and make random stacked tweaks. */ // 我们基本上会进行数千次运行(取决于性能评分),对输入文件进行随机的堆叠调整 - for (stage_cur = 0; stage_cur < stage_max; stage_cur++) { + for (stage_cur = 0; stage_cur < stage_max; stage_cur++) { // 遍历最大阶段循环次数 - u32 use_stacking = 1 << (1 + UR(HAVOC_STACK_POW2)); + u32 use_stacking = 1 << (1 + UR(HAVOC_STACK_POW2)); // 随机生成一个堆叠次数,用于决定每次循环中进行多少次变异 - stage_cur_val = use_stacking; - - for (i = 0; i < use_stacking; i++) { + stage_cur_val = use_stacking; // 记录当前堆叠次数 - switch (UR(15 + ((extras_cnt + a_extras_cnt) ? 2 : 0))) { + for (i = 0; i < use_stacking; i++) { // 遍历堆叠次数 - case 0: + switch (UR(15 + ((extras_cnt + a_extras_cnt) ? 2 : 0))) { // 随机选择一个变异操作 - /* Flip a single bit somewhere. Spooky! */ + case 0: // 操作0:翻转一个随机位 - FLIP_BIT(out_buf, UR(temp_len << 3)); + /* Flip a single bit somewhere. Spooky! */ // 翻转某个随机位 + FLIP_BIT(out_buf, UR(temp_len << 3)); // 在输出缓冲区中随机翻转一个位 break; - case 1: + case 1: // 操作1:将随机字节设置为一个有趣的值 - /* Set byte to interesting value. */ - - out_buf[UR(temp_len)] = interesting_8[UR(sizeof(interesting_8))]; + /* Set byte to interesting value. */ // 将随机字节设置为一个有趣的值 + out_buf[UR(temp_len)] = interesting_8[UR(sizeof(interesting_8))]; // 从interesting_8数组中随机选择一个值并设置到随机位置 break; - case 2: + case 2: // 操作2:将随机字设置为一个有趣的值,随机选择字节序 - /* Set word to interesting value, randomly choosing endian. */ + /* Set word to interesting value, randomly choosing endian. */ // 将随机字设置为一个有趣的值,随机选择字节序 + if (temp_len < 2) break; // 如果缓冲区长度小于2,跳过 - if (temp_len < 2) break; + if (UR(2)) { // 随机选择字节序 + *(u16*)(out_buf + UR(temp_len - 1)) = interesting_16[UR(sizeof(interesting_16) >> 1)]; // 使用小端序设置值 + } else { + *(u16*)(out_buf + UR(temp_len - 1)) = SWAP16(interesting_16[UR(sizeof(interesting_16) >> 1)]); // 使用大端序设置值 + } + break; - if (UR(2)) { + case 3: // 操作3:将随机双字设置为一个有趣的值,随机选择字节序 - *(u16*)(out_buf + UR(temp_len - 1)) = - interesting_16[UR(sizeof(interesting_16) >> 1)]; + /* Set dword to interesting value, randomly choosing endian. */ // 将随机双字设置为一个有趣的值,随机选择字节序 + if (temp_len < 4) break; // 如果缓冲区长度小于4,跳过 + if (UR(2)) { // 随机选择字节序 + *(u32*)(out_buf + UR(temp_len - 3)) = interesting_32[UR(sizeof(interesting_32) >> 2)]; // 使用小端序设置值 } else { - - *(u16*)(out_buf + UR(temp_len - 1)) = SWAP16( - interesting_16[UR(sizeof(interesting_16) >> 1)]); - + *(u32*)(out_buf + UR(temp_len - 3)) = SWAP32(interesting_32[UR(sizeof(interesting_32) >> 2)]); // 使用大端序设置值 } - break; - case 3: + case 4: // 操作4:随机减少一个字节的值 - /* Set dword to interesting value, randomly choosing endian. */ + /* Randomly subtract from byte. */ // 随机减少一个字节的值 + out_buf[UR(temp_len)] -= 1 + UR(ARITH_MAX); // 随机减少一个字节的值 + break; - if (temp_len < 4) break; + case 5: // 操作5:随机增加一个字节的值 - if (UR(2)) { - - *(u32*)(out_buf + UR(temp_len - 3)) = - interesting_32[UR(sizeof(interesting_32) >> 2)]; + /* Randomly add to byte. */ // 随机增加一个字节的值 + out_buf[UR(temp_len)] += 1 + UR(ARITH_MAX); // 随机增加一个字节的值 + break; - } else { + case 6: // 操作6:随机减少一个字的值,随机选择字节序 - *(u32*)(out_buf + UR(temp_len - 3)) = SWAP32( - interesting_32[UR(sizeof(interesting_32) >> 2)]); + /* Randomly subtract from word, random endian. */ // 随机减少一个字的值,随机选择字节序 + if (temp_len < 2) break; // 如果缓冲区长度小于2,跳过 + if (UR(2)) { // 随机选择字节序 + u32 pos = UR(temp_len - 1); // 随机选择位置 + *(u16*)(out_buf + pos) -= 1 + UR(ARITH_MAX); // 减少字的值 + } else { + u32 pos = UR(temp_len - 1); // 随机选择位置 + u16 num = 1 + UR(ARITH_MAX); // 随机生成减少的值 + *(u16*)(out_buf + pos) = SWAP16(SWAP16(*(u16*)(out_buf + pos)) - num); // 使用大端序减少字的值 } - break; - case 4: + case 7: // 操作7:随机增加一个字的值,随机选择字节序 - /* Randomly subtract from byte. */ + /* Randomly add to word, random endian. */ // 随机增加一个字的值,随机选择字节序 + if (temp_len < 2) break; // 如果缓冲区长度小于2,跳过 - out_buf[UR(temp_len)] -= 1 + UR(ARITH_MAX); + if (UR(2)) { // 随机选择字节序 + u32 pos = UR(temp_len - 1); // 随机选择位置 + *(u16*)(out_buf + pos) += 1 + UR(ARITH_MAX); // 增加字的值 + } else { + u32 pos = UR(temp_len - 1); // 随机选择位置 + u16 num = 1 + UR(ARITH_MAX); // 随机生成增加的值 + *(u16*)(out_buf + pos) = SWAP16(SWAP16(*(u16*)(out_buf + pos)) + num); // 使用大端序增加字的值 + } break; - case 5: + case 8: // 操作8:随机减少一个双字的值,随机选择字节序 - /* Randomly add to byte. */ + /* Randomly subtract from dword, random endian. */ // 随机减少一个双字的值,随机选择字节序 + if (temp_len < 4) break; // 如果缓冲区长度小于4,跳过 - out_buf[UR(temp_len)] += 1 + UR(ARITH_MAX); + if (UR(2)) { // 随机选择字节序 + u32 pos = UR(temp_len - 3); // 随机选择位置 + *(u32*)(out_buf + pos) -= 1 + UR(ARITH_MAX); // 减少双字的值 + } else { + u32 pos = UR(temp_len - 3); // 随机选择位置 + u32 num = 1 + UR(ARITH_MAX); // 随机生成减少的值 + *(u32*)(out_buf + pos) = SWAP32(SWAP32(*(u32*)(out_buf + pos)) - num); // 使用大端序减少双字的值 + } break; - case 6: + case 9: // 操作9:随机增加一个双字的值,随机选择字节序 - /* Randomly subtract from word, random endian. */ + /* Randomly add to dword, random endian. */ // 随机增加一个双字的值,随机选择字节序 + if (temp_len < 4) break; // 如果缓冲区长度小于4,跳过 - if (temp_len < 2) break; + if (UR(2)) { // 随机选择字节序 + u32 pos = UR(temp_len - 3); // 随机选择位置 + *(u32*)(out_buf + pos) += 1 + UR(ARITH_MAX); // 增加双字的值 + } else { + u32 pos = UR(temp_len - 3); // 随机选择位置 + u32 num = 1 + UR(ARITH_MAX); // 随机生成增加的值 + *(u32*)(out_buf + pos) = SWAP32(SWAP32(*(u32*)(out_buf + pos)) + num); // 使用大端序增加双字的值 + } + break; - if (UR(2)) { + case 10: // 操作10:随机异或一个字节的值 - u32 pos = UR(temp_len - 1); + /* Just set a random byte to a random value. Because, + why not. We use XOR with 1-255 to eliminate the + possibility of a no-op. */ // 随机异或一个字节的值,避免无操作 + out_buf[UR(temp_len)] ^= 1 + UR(255); // 随机异或一个字节的值 + break; - *(u16*)(out_buf + pos) -= 1 + UR(ARITH_MAX); + case 11 ... 12: { // 操作11-12:删除随机长度的字节 - } else { + /* Delete bytes. We're making this a bit more likely + than insertion (the next option) in hopes of keeping + files reasonably small. */ // 删除字节,比插入操作更频繁,以保持文件大小合理 + u32 del_from, del_len; // 定义删除的起始位置和长度 - u32 pos = UR(temp_len - 1); - u16 num = 1 + UR(ARITH_MAX); + if (temp_len < 2) break; // 如果缓冲区长度小于2,跳过 - *(u16*)(out_buf + pos) = - SWAP16(SWAP16(*(u16*)(out_buf + pos)) - num); + /* Don't delete too much. */ // 不要删除太多 + del_len = choose_block_len(temp_len - 1); // 随机选择删除的长度 + del_from = UR(temp_len - del_len + 1); // 随机选择删除的起始位置 + memmove(out_buf + del_from, out_buf + del_from + del_len, temp_len - del_from - del_len); // 移动缓冲区内容以删除字节 + temp_len -= del_len; // 更新缓冲区长度 + break; } - break; - - case 7: + case 13: // 操作13:克隆或插入随机长度的字节 - /* Randomly add to word, random endian. */ + if (temp_len + HAVOC_BLK_XL < MAX_FILE) { // 如果缓冲区长度加上最大块长度小于最大文件大小 - if (temp_len < 2) break; + /* Clone bytes (75%) or insert a block of constant bytes (25%). */ // 克隆字节(75%)或插入一个常量字节块(25%) + u8 actually_clone = UR(4); // 随机决定是否克隆 + u32 clone_from, clone_to, clone_len; // 定义克隆的起始位置、目标位置和长度 + u8* new_buf; // 定义新的缓冲区 - if (UR(2)) { + if (actually_clone) { // 如果选择克隆 + clone_len = choose_block_len(temp_len); // 随机选择克隆的长度 + clone_from = UR(temp_len - clone_len + 1); // 随机选择克隆的起始位置 + } else { // 如果选择插入常量字节块 + clone_len = choose_block_len(HAVOC_BLK_XL); // 随机选择插入的长度 + clone_from = 0; // 起始位置为0 + } - u32 pos = UR(temp_len - 1); + clone_to = UR(temp_len); // 随机选择目标位置 - *(u16*)(out_buf + pos) += 1 + UR(ARITH_MAX); + new_buf = ck_alloc_nozero(temp_len + clone_len); // 分配新的缓冲区 - } else { + /* Head */ // 复制头部 + memcpy(new_buf, out_buf, clone_to); - u32 pos = UR(temp_len - 1); - u16 num = 1 + UR(ARITH_MAX); + /* Inserted part */ // 插入部分 + if (actually_clone) + memcpy(new_buf + clone_to, out_buf + clone_from, clone_len); // 克隆字节 + else + memset(new_buf + clone_to, UR(2) ? UR(256) : out_buf[UR(temp_len)], clone_len); // 插入常量字节块 - *(u16*)(out_buf + pos) = - SWAP16(SWAP16(*(u16*)(out_buf + pos)) + num); + /* Tail */ // 复制尾部 + memcpy(new_buf + clone_to + clone_len, out_buf + clone_to, temp_len - clone_to); + ck_free(out_buf); // 释放旧的缓冲区 + out_buf = new_buf; // 更新缓冲区指针 + temp_len += clone_len; // 更新缓冲区长度 } - break; - case 8: + case 14: { // 操作14:覆盖随机长度的字节 - /* Randomly subtract from dword, random endian. */ + /* Overwrite bytes with a randomly selected chunk (75%) or fixed + bytes (25%). */ // 用随机选择的块(75%)或固定字节(25%)覆盖字节 + u32 copy_from, copy_to, copy_len; // 定义覆盖的起始位置、目标位置和长度 - if (temp_len < 4) break; + if (temp_len < 2) break; // 如果缓冲区长度小于2,跳过 - if (UR(2)) { - - u32 pos = UR(temp_len - 3); - - *(u32*)(out_buf + pos) -= 1 + UR(ARITH_MAX); - - } else { - - u32 pos = UR(temp_len - 3); - u32 num = 1 + UR(ARITH_MAX); - - *(u32*)(out_buf + pos) = - SWAP32(SWAP32(*(u32*)(out_buf + pos)) - num); - - } - - break; - - case 9: - - /* Randomly add to dword, random endian. */ - - if (temp_len < 4) break; - - if (UR(2)) { - - u32 pos = UR(temp_len - 3); - - *(u32*)(out_buf + pos) += 1 + UR(ARITH_MAX); - - } else { - - u32 pos = UR(temp_len - 3); - u32 num = 1 + UR(ARITH_MAX); - - *(u32*)(out_buf + pos) = - SWAP32(SWAP32(*(u32*)(out_buf + pos)) + num); - - } - - break; - - case 10: - - /* Just set a random byte to a random value. Because, - why not. We use XOR with 1-255 to eliminate the - possibility of a no-op. */ - - out_buf[UR(temp_len)] ^= 1 + UR(255); - break; - - case 11 ... 12: { - - /* Delete bytes. We're making this a bit more likely - than insertion (the next option) in hopes of keeping - files reasonably small. */ - - u32 del_from, del_len; - - if (temp_len < 2) break; - - /* Don't delete too much. */ - - del_len = choose_block_len(temp_len - 1); - - del_from = UR(temp_len - del_len + 1); - - memmove(out_buf + del_from, out_buf + del_from + del_len, - temp_len - del_from - del_len); - - temp_len -= del_len; - - break; - - } - - case 13: - - if (temp_len + HAVOC_BLK_XL < MAX_FILE) { - - /* Clone bytes (75%) or insert a block of constant bytes (25%). */ - - u8 actually_clone = UR(4); - u32 clone_from, clone_to, clone_len; - u8* new_buf; - - if (actually_clone) { - - clone_len = choose_block_len(temp_len); - clone_from = UR(temp_len - clone_len + 1); - - } else { - - clone_len = choose_block_len(HAVOC_BLK_XL); - clone_from = 0; - - } - - clone_to = UR(temp_len); - - new_buf = ck_alloc_nozero(temp_len + clone_len); - - /* Head */ - - memcpy(new_buf, out_buf, clone_to); - - /* Inserted part */ - - if (actually_clone) - memcpy(new_buf + clone_to, out_buf + clone_from, clone_len); - else - memset(new_buf + clone_to, - UR(2) ? UR(256) : out_buf[UR(temp_len)], clone_len); - - /* Tail */ - memcpy(new_buf + clone_to + clone_len, out_buf + clone_to, - temp_len - clone_to); - - ck_free(out_buf); - out_buf = new_buf; - temp_len += clone_len; - - } - - break; - - case 14: { - - /* Overwrite bytes with a randomly selected chunk (75%) or fixed - bytes (25%). */ - - u32 copy_from, copy_to, copy_len; - - if (temp_len < 2) break; - - copy_len = choose_block_len(temp_len - 1); - - copy_from = UR(temp_len - copy_len + 1); - copy_to = UR(temp_len - copy_len + 1); - - if (UR(4)) { + copy_len = choose_block_len(temp_len - 1); // 随机选择覆盖的长度 + copy_from = UR(temp_len - copy_len + 1); // 随机选择覆盖的起始位置 + copy_to = UR(temp_len - copy_len + 1); // 随机选择目标位置 + if (UR(4)) { // 75%的概率使用随机选择的块覆盖 if (copy_from != copy_to) - memmove(out_buf + copy_to, out_buf + copy_from, copy_len); - - } else memset(out_buf + copy_to, - UR(2) ? UR(256) : out_buf[UR(temp_len)], copy_len); - + memmove(out_buf + copy_to, out_buf + copy_from, copy_len); // 移动缓冲区内容以覆盖字节 + } else { // 25%的概率使用固定字节覆盖 + memset(out_buf + copy_to, UR(2) ? UR(256) : out_buf[UR(temp_len)], copy_len); // 使用固定字节覆盖 + } break; - } /* Values 15 and 16 can be selected only if there are any extras - present in the dictionaries. */ + present in the dictionaries. */ // 只有在字典中存在额外数据时,才能选择操作15和16 - case 15: { + case 15: { // 操作15:用额外数据覆盖字节 - /* Overwrite bytes with an extra. */ - - if (!extras_cnt || (a_extras_cnt && UR(2))) { + /* Overwrite bytes with an extra. */ // 用额外数据覆盖字节 + if (!extras_cnt || (a_extras_cnt && UR(2))) { // 如果没有用户指定的额外数据,或者有自动检测的额外数据且随机选择 /* No user-specified extras or odds in our favor. Let's use an - auto-detected one. */ - - u32 use_extra = UR(a_extras_cnt); - u32 extra_len = a_extras[use_extra].len; - u32 insert_at; - - if (extra_len > temp_len) break; - - insert_at = UR(temp_len - extra_len + 1); - memcpy(out_buf + insert_at, a_extras[use_extra].data, extra_len); - - } else { + auto-detected one. */ // 使用自动检测的额外数据 + u32 use_extra = UR(a_extras_cnt); // 随机选择一个自动检测的额外数据 + u32 extra_len = a_extras[use_extra].len; // 获取额外数据的长度 + u32 insert_at; // 定义插入位置 - /* No auto extras or odds in our favor. Use the dictionary. */ + if (extra_len > temp_len) break; // 如果额外数据长度大于缓冲区长度,跳过 - u32 use_extra = UR(extras_cnt); - u32 extra_len = extras[use_extra].len; - u32 insert_at; + insert_at = UR(temp_len - extra_len + 1); // 随机选择插入位置 + memcpy(out_buf + insert_at, a_extras[use_extra].data, extra_len); // 用额外数据覆盖字节 + } else { // 如果有用户指定的额外数据且随机选择 - if (extra_len > temp_len) break; + /* No auto extras or odds in our favor. Use the dictionary. */ // 使用用户指定的额外数据 + u32 use_extra = UR(extras_cnt); // 随机选择一个用户指定的额外数据 + u32 extra_len = extras[use_extra].len; // 获取额外数据的长度 + u32 insert_at; // 定义插入位置 - insert_at = UR(temp_len - extra_len + 1); - memcpy(out_buf + insert_at, extras[use_extra].data, extra_len); + if (extra_len > temp_len) break; // 如果额外数据长度大于缓冲区长度,跳过 + insert_at = UR(temp_len - extra_len + 1); // 随机选择插入位置 + memcpy(out_buf + insert_at, extras[use_extra].data, extra_len); // 用额外数据覆盖字节 } - break; - } - case 16: { + case 16: { // 操作16:插入额外数据 - u32 use_extra, extra_len, insert_at = UR(temp_len + 1); - u8* new_buf; + u32 use_extra, extra_len, insert_at = UR(temp_len + 1); // 定义额外数据、长度和插入位置 + u8* new_buf; // 定义新的缓冲区 /* Insert an extra. Do the same dice-rolling stuff as for the - previous case. */ - - if (!extras_cnt || (a_extras_cnt && UR(2))) { + previous case. */ // 插入额外数据,与操作15类似 + if (!extras_cnt || (a_extras_cnt && UR(2))) { // 如果没有用户指定的额外数据,或者有自动检测的额外数据且随机选择 - use_extra = UR(a_extras_cnt); - extra_len = a_extras[use_extra].len; + use_extra = UR(a_extras_cnt); // 随机选择一个自动检测的额外数据 + extra_len = a_extras[use_extra].len; // 获取额外数据的长度 - if (temp_len + extra_len >= MAX_FILE) break; + if (temp_len + extra_len >= MAX_FILE) break; // 如果插入后缓冲区长度超过最大文件大小,跳过 - new_buf = ck_alloc_nozero(temp_len + extra_len); + new_buf = ck_alloc_nozero(temp_len + extra_len); // 分配新的缓冲区 - /* Head */ + /* Head */ // 复制头部 memcpy(new_buf, out_buf, insert_at); - /* Inserted part */ - memcpy(new_buf + insert_at, a_extras[use_extra].data, extra_len); + /* Inserted part */ // 插入部分 + memcpy(new_buf + insert_at, a_extras[use_extra].data, extra_len); // 插入自动检测的额外数据 + } else { // 如果有用户指定的额外数据且随机选择 - } else { + use_extra = UR(extras_cnt); // 随机选择一个用户指定的额外数据 + extra_len = extras[use_extra].len; // 获取额外数据的长度 - use_extra = UR(extras_cnt); - extra_len = extras[use_extra].len; + if (temp_len + extra_len >= MAX_FILE) break; // 如果插入后缓冲区长度超过最大文件大小,跳过 - if (temp_len + extra_len >= MAX_FILE) break; + new_buf = ck_alloc_nozero(temp_len + extra_len); // 分配新的缓冲区 - new_buf = ck_alloc_nozero(temp_len + extra_len); - - /* Head */ + /* Head */ // 复制头部 memcpy(new_buf, out_buf, insert_at); - /* Inserted part */ - memcpy(new_buf + insert_at, extras[use_extra].data, extra_len); - + /* Inserted part */ // 插入部分 + memcpy(new_buf + insert_at, extras[use_extra].data, extra_len); // 插入用户指定的额外数据 } - /* Tail */ - memcpy(new_buf + insert_at + extra_len, out_buf + insert_at, - temp_len - insert_at); - - ck_free(out_buf); - out_buf = new_buf; - temp_len += extra_len; + /* Tail */ // 复制尾部 + memcpy(new_buf + insert_at + extra_len, out_buf + insert_at, temp_len - insert_at); + ck_free(out_buf); // 释放旧的缓冲区 + out_buf = new_buf; // 更新缓冲区指针 + temp_len += extra_len; // 更新缓冲区长度 break; - } - } - } - if (common_fuzz_stuff(argv, out_buf, temp_len)) - goto abandon_entry; + if (common_fuzz_stuff(argv, out_buf, temp_len)) // 调用通用模糊测试函数处理当前缓冲区 + goto abandon_entry; // 如果返回非零值,跳转到abandon_entry /* out_buf might have been mangled a bit, so let's restore it to its - original size and shape. */ - - if (temp_len < len) out_buf = ck_realloc(out_buf, len); - temp_len = len; - memcpy(out_buf, in_buf, len); + original size and shape. */ // out_buf可能已被修改,因此将其恢复到原始大小和形状 + if (temp_len < len) out_buf = ck_realloc(out_buf, len); // 如果缓冲区长度小于原始长度,重新分配内存 + temp_len = len; // 恢复缓冲区长度 + memcpy(out_buf, in_buf, len); // 将输入缓冲区内容复制回输出缓冲区 /* If we're finding new stuff, let's run for a bit longer, limits - permitting. */ - - if (queued_paths != havoc_queued) { - - if (perf_score <= HAVOC_MAX_MULT * 100) { - stage_max *= 2; - perf_score *= 2; + permitting. */ // 如果发现了新的路径,允许在限制范围内运行更长时间 + if (queued_paths != havoc_queued) { // 如果已排队的路径数发生变化 + if (perf_score <= HAVOC_MAX_MULT * 100) { // 如果性能评分小于等于HAVOC_MAX_MULT * 100 + stage_max *= 2; // 将最大阶段循环次数翻倍 + perf_score *= 2; // 将性能评分翻倍 } - - havoc_queued = queued_paths; - + havoc_queued = queued_paths; // 更新已排队的路径数 } - } - new_hit_cnt = queued_paths + unique_crashes; + new_hit_cnt = queued_paths + unique_crashes; // 计算新的命中计数 - if (!splice_cycle) { - stage_finds[STAGE_HAVOC] += new_hit_cnt - orig_hit_cnt; - stage_cycles[STAGE_HAVOC] += stage_max; - } else { - stage_finds[STAGE_SPLICE] += new_hit_cnt - orig_hit_cnt; - stage_cycles[STAGE_SPLICE] += stage_max; + if (!splice_cycle) { // 如果没有进行文件拼接 + stage_finds[STAGE_HAVOC] += new_hit_cnt - orig_hit_cnt; // 更新HAVOC阶段的发现计数 + stage_cycles[STAGE_HAVOC] += stage_max; // 更新HAVOC阶段的循环计数 + } else { // 如果进行了文件拼接 + stage_finds[STAGE_SPLICE] += new_hit_cnt - orig_hit_cnt; // 更新SPLICE阶段的发现计数 + stage_cycles[STAGE_SPLICE] += stage_max; // 更新SPLICE阶段的循环计数 } -#ifndef IGNORE_FINDS +#ifndef IGNORE_FINDS // 如果没有定义IGNORE_FINDS + + /************ * SPLICING * @@ -6581,288 +6484,282 @@ havoc_stage: splices them together at some offset, then relies on the havoc code to mutate that blob. */ -retry_splicing: +retry_splicing: // 重试拼接的标签 + // 检查是否能使用拼接,并且拼接循环次数小于预设的拼接次数, + // 同时队列中的路径数大于1且当前路径长度大于1 if (use_splicing && splice_cycle++ < SPLICE_CYCLES && queued_paths > 1 && queue_cur->len > 1) { - struct queue_entry* target; - u32 tid, split_at; - u8* new_buf; - s32 f_diff, l_diff; + struct queue_entry* target; // 目标队列条目 + u32 tid, split_at; // 目标 ID 和分割位置 + u8* new_buf; // 新缓冲区 + s32 f_diff, l_diff; // 第一个和最后一个不同字节的位置 - /* First of all, if we've modified in_buf for havoc, let's clean that - up... */ + /* 首先,如果我们在进行 havoc 变异时修改了 in_buf,则需要清理它... */ - if (in_buf != orig_in) { - ck_free(in_buf); - in_buf = orig_in; - len = queue_cur->len; + if (in_buf != orig_in) { // 如果 in_buf 不是原始输入 + ck_free(in_buf); // 释放 in_buf + in_buf = orig_in; // 恢复为原始输入 + len = queue_cur->len; // 更新当前长度 } - /* Pick a random queue entry and seek to it. Don't splice with yourself. */ + /* 随机选择一个队列条目,并寻址它。不能与自己拼接。 */ - do { tid = UR(queued_paths); } while (tid == current_entry); + do { tid = UR(queued_paths); } while (tid == current_entry); // 随机选择一个不同的条目 - splicing_with = tid; - target = queue; + splicing_with = tid; // 设置拼接目标 ID + target = queue; // 从队列的起始条目开始 + // 根据 tid 找到目标条目 while (tid >= 100) { target = target->next_100; tid -= 100; } while (tid--) target = target->next; - /* Make sure that the target has a reasonable length. */ + /* 确保目标条目有一个合理的长度。 */ while (target && (target->len < 2 || target == queue_cur)) { - target = target->next; - splicing_with++; + target = target->next; // 如果目标长度不合理或与当前条目相同,则继续寻找下一个 + splicing_with++; // 增加拼接计数 } - if (!target) goto retry_splicing; + if (!target) goto retry_splicing; // 如果没有找到合适的目标,则重试 - /* Read the testcase into a new buffer. */ + /* 将测试用例读入一个新的缓冲区。 */ - fd = open(target->fname, O_RDONLY); + fd = open(target->fname, O_RDONLY); // 打开目标文件 - if (fd < 0) PFATAL("Unable to open '%s'", target->fname); + if (fd < 0) PFATAL("Unable to open '%s'", target->fname); // 如果打开失败则报错 - new_buf = ck_alloc_nozero(target->len); + new_buf = ck_alloc_nozero(target->len); // 分配新缓冲区 - ck_read(fd, new_buf, target->len, target->fname); + ck_read(fd, new_buf, target->len, target->fname); // 从文件中读取数据到新的缓冲区 - close(fd); + close(fd); // 关闭文件描述符 - /* Find a suitable splicing location, somewhere between the first and - the last differing byte. Bail out if the difference is just a single - byte or so. */ + /* 找到一个合适的拼接位置,在第一个和最后一个不同字节之间。 + 如果差异只有一个字节或很小,则退出。 */ - locate_diffs(in_buf, new_buf, MIN(len, target->len), &f_diff, &l_diff); + locate_diffs(in_buf, new_buf, MIN(len, target->len), &f_diff, &l_diff); // 找到不同字节的位置 + // 检查差异是否有效 if (f_diff < 0 || l_diff < 2 || f_diff == l_diff) { - ck_free(new_buf); - goto retry_splicing; + ck_free(new_buf); // 释放新的缓冲区 + goto retry_splicing; // 重试拼接 } - /* Split somewhere between the first and last differing byte. */ + /* 在第一个和最后一个不同字节之间分割。 */ - split_at = f_diff + UR(l_diff - f_diff); + split_at = f_diff + UR(l_diff - f_diff); // 随机选择分割位置 - /* Do the thing. */ + /* 进行拼接。 */ - len = target->len; - memcpy(new_buf, in_buf, split_at); - in_buf = new_buf; + len = target->len; // 更新长度为目标条目长度 + memcpy(new_buf, in_buf, split_at); // 复制前半部分 + in_buf = new_buf; // 更新 in_buf 为新缓冲区 - ck_free(out_buf); - out_buf = ck_alloc_nozero(len); - memcpy(out_buf, in_buf, len); + ck_free(out_buf); // 释放输出缓冲区 + out_buf = ck_alloc_nozero(len); // 分配新的输出缓冲区 + memcpy(out_buf, in_buf, len); // 复制内容到输出缓冲区 - goto havoc_stage; + goto havoc_stage; // 跳转到 havoc 阶段 } -#endif /* !IGNORE_FINDS */ +#endif /* !IGNORE_FINDS */ // 结束条件编译 - ret_val = 0; + ret_val = 0; // 返回值初始化为0 -abandon_entry: +abandon_entry: // 放弃当前条目的标签 - splicing_with = -1; + splicing_with = -1; // 重置拼接目标 ID - /* Update pending_not_fuzzed count if we made it through the calibration - cycle and have not seen this entry before. */ + /* 如果我们通过了校准周期并且之前未见过此条目,则更新未模糊计数。 */ if (!stop_soon && !queue_cur->cal_failed && !queue_cur->was_fuzzed) { - queue_cur->was_fuzzed = 1; - pending_not_fuzzed--; - if (queue_cur->favored) pending_favored--; + queue_cur->was_fuzzed = 1; // 标记为已模糊 + pending_not_fuzzed--; // 减少未模糊计数 + if (queue_cur->favored) pending_favored--; // 如果是受青睐的条目,则减少相应计数 } - munmap(orig_in, queue_cur->len); - - if (in_buf != orig_in) ck_free(in_buf); - ck_free(out_buf); - ck_free(eff_map); + munmap(orig_in, queue_cur->len); // 解除映射原始输入 - return ret_val; + if (in_buf != orig_in) ck_free(in_buf); // 如果 in_buf 不是原始输入,则释放 + ck_free(out_buf); // 释放输出缓冲区 + ck_free(eff_map); // 释放有效映射 -#undef FLIP_BIT + return ret_val; // 返回结果 -} /* Grab interesting test cases from other fuzzers. */ static void sync_fuzzers(char** argv) { + // 定义同步模糊测试器的静态函数,接受命令行参数 - DIR* sd; - struct dirent* sd_ent; - u32 sync_cnt = 0; + DIR* sd; // 定义目录指针,用于同步目录 + struct dirent* sd_ent; // 定义目录项结构,用于读取目录条目 + u32 sync_cnt = 0; // 初始化同步计数器 - sd = opendir(sync_dir); - if (!sd) PFATAL("Unable to open '%s'", sync_dir); + sd = opendir(sync_dir); // 打开同步目录 + if (!sd) PFATAL("Unable to open '%s'", sync_dir); // 如果打开失败,输出错误信息并终止程序 - stage_max = stage_cur = 0; - cur_depth = 0; + stage_max = stage_cur = 0; // 初始化阶段最大值和当前值 + cur_depth = 0; // 初始化当前深度 - /* Look at the entries created for every other fuzzer in the sync directory. */ + /* 查看为每个其他模糊测试器在同步目录创建的条目。 */ - while ((sd_ent = readdir(sd))) { + while ((sd_ent = readdir(sd))) { // 读取同步目录中的每个条目 - static u8 stage_tmp[128]; + static u8 stage_tmp[128]; // 用于临时存储阶段名称 - DIR* qd; - struct dirent* qd_ent; - u8 *qd_path, *qd_synced_path; - u32 min_accept = 0, next_min_accept; + DIR* qd; // 定义另一个目录指针 + struct dirent* qd_ent; // 定义另一个目录项结构 + u8 *qd_path, *qd_synced_path; // 定义队列目录和同步路径 + u32 min_accept = 0, next_min_accept; // 定义接受的最小值和下一个最小接受值 - s32 id_fd; + s32 id_fd; // 定义文件描述符变量 - /* Skip dot files and our own output directory. */ + /* 跳过点文件和我们自己的输出目录。 */ - if (sd_ent->d_name[0] == '.' || !strcmp(sync_id, sd_ent->d_name)) continue; + if (sd_ent->d_name[0] == '.' || !strcmp(sync_id, sd_ent->d_name)) continue; // 跳过点文件和自身同步ID - /* Skip anything that doesn't have a queue/ subdirectory. */ + /* 跳过没有 queue/ 子目录的条目。 */ - qd_path = alloc_printf("%s/%s/queue", sync_dir, sd_ent->d_name); + qd_path = alloc_printf("%s/%s/queue", sync_dir, sd_ent->d_name); // 为队列目录分配路径 - if (!(qd = opendir(qd_path))) { - ck_free(qd_path); - continue; - } + if (!(qd = opendir(qd_path))) { // 打开队列目录 + ck_free(qd_path); // 释放路径内存 + continue; // 跳过当前条目 + } - /* Retrieve the ID of the last seen test case. */ + /* 获取最后一个已见测试用例的ID。 */ - qd_synced_path = alloc_printf("%s/.synced/%s", out_dir, sd_ent->d_name); + qd_synced_path = alloc_printf("%s/.synced/%s", out_dir, sd_ent->d_name); // 为同步文件分配路径 - id_fd = open(qd_synced_path, O_RDWR | O_CREAT, 0600); + id_fd = open(qd_synced_path, O_RDWR | O_CREAT, 0600); // 打开或创建同步文件 - if (id_fd < 0) PFATAL("Unable to create '%s'", qd_synced_path); + if (id_fd < 0) PFATAL("Unable to create '%s'", qd_synced_path); // 如果打开失败,输出错误信息并终止程序 - if (read(id_fd, &min_accept, sizeof(u32)) > 0) - lseek(id_fd, 0, SEEK_SET); + if (read(id_fd, &min_accept, sizeof(u32)) > 0) + lseek(id_fd, 0, SEEK_SET); // 如果读取成功,设置文件指针到开头 - next_min_accept = min_accept; + next_min_accept = min_accept; // 将下一个最小接受值设为当前的最小接受值 - /* Show stats */ + /* 显示统计信息 */ - sprintf(stage_tmp, "sync %u", ++sync_cnt); - stage_name = stage_tmp; - stage_cur = 0; - stage_max = 0; + sprintf(stage_tmp, "sync %u", ++sync_cnt); // 格式化当前同步阶段名称 + stage_name = stage_tmp; // 设置阶段名称 + stage_cur = 0; // 当前阶段计数器设为0 + stage_max = 0; // 当前阶段最大值设为0 - /* For every file queued by this fuzzer, parse ID and see if we have looked at - it before; exec a test case if not. */ + /* 对于这个模糊测试器排队的每个文件,解析ID并查看我们是否看过它; + 如果没有,则执行测试用例。 */ - while ((qd_ent = readdir(qd))) { + while ((qd_ent = readdir(qd))) { // 读取队列目录中的每个条目 - u8* path; - s32 fd; - struct stat st; + u8* path; // 定义路径变量 + s32 fd; // 定义文件描述符 + struct stat st; // 定义文件状态结构 - if (qd_ent->d_name[0] == '.' || - sscanf(qd_ent->d_name, CASE_PREFIX "%06u", &syncing_case) != 1 || - syncing_case < min_accept) continue; + if (qd_ent->d_name[0] == '.' || // 跳过点文件 + sscanf(qd_ent->d_name, CASE_PREFIX "%06u", &syncing_case) != 1 || // 解析文件名中的ID + syncing_case < min_accept) continue; // 如果ID小于最小接受值,则跳过 - /* OK, sounds like a new one. Let's give it a try. */ + /* 好的,听起来像是一个新的文件。我们来试试。 */ - if (syncing_case >= next_min_accept) - next_min_accept = syncing_case + 1; + if (syncing_case >= next_min_accept) // 检查是否是下一个要接受的测试用例 + next_min_accept = syncing_case + 1; // 更新下一个最小接受值 - path = alloc_printf("%s/%s", qd_path, qd_ent->d_name); + path = alloc_printf("%s/%s", qd_path, qd_ent->d_name); // 为当前文件分配路径 - /* Allow this to fail in case the other fuzzer is resuming or so... */ + /* 在其他模糊测试器恢复时允许此失败... */ - fd = open(path, O_RDONLY); + fd = open(path, O_RDONLY); // 以只读方式打开文件 - if (fd < 0) { - ck_free(path); - continue; - } + if (fd < 0) { // 如果打开失败 + ck_free(path); // 释放路径内存 + continue; // 跳过当前条目 + } - if (fstat(fd, &st)) PFATAL("fstat() failed"); + if (fstat(fd, &st)) PFATAL("fstat() failed"); // 获取文件状态,如果失败则终止程序 - /* Ignore zero-sized or oversized files. */ + /* 忽略零大小或超大文件。 */ - if (st.st_size && st.st_size <= MAX_FILE) { + if (st.st_size && st.st_size <= MAX_FILE) { // 检查文件大小 - u8 fault; - u8* mem = mmap(0, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0); + u8 fault; // 定义故障变量 + u8* mem = mmap(0, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0); // 将文件映射到内存 - if (mem == MAP_FAILED) PFATAL("Unable to mmap '%s'", path); + if (mem == MAP_FAILED) PFATAL("Unable to mmap '%s'", path); // 如果映射失败,输出错误信息并终止程序 - /* See what happens. We rely on save_if_interesting() to catch major - errors and save the test case. */ + /* 看看会发生什么。我们依赖于 save_if_interesting() 来捕获主要 + 错误并保存测试用例。 */ - write_to_testcase(mem, st.st_size); + write_to_testcase(mem, st.st_size); // 写入测试用例 - fault = run_target(argv, exec_tmout); + fault = run_target(argv, exec_tmout); // 运行目标程序并获取故障信息 - if (stop_soon) return; + if (stop_soon) return; // 如果需要停止,返回 - syncing_party = sd_ent->d_name; - queued_imported += save_if_interesting(argv, mem, st.st_size, fault); - syncing_party = 0; + syncing_party = sd_ent->d_name; // 记录当前同步方 + queued_imported += save_if_interesting(argv, mem, st.st_size, fault); // 保存有趣的测试用例 + syncing_party = 0; // 重置当前同步方 - munmap(mem, st.st_size); + munmap(mem, st.st_size); // 解除内存映射 - if (!(stage_cur++ % stats_update_freq)) show_stats(); + if (!(stage_cur++ % stats_update_freq)) show_stats(); // 更新统计信息 - } + } - ck_free(path); - close(fd); + ck_free(path); // 释放路径内存 + close(fd); // 关闭文件描述符 - } + } - ck_write(id_fd, &next_min_accept, sizeof(u32), qd_synced_path); + ck_write(id_fd, &next_min_accept, sizeof(u32), qd_synced_path); // 将下一个最小接受值写入同步文件 - close(id_fd); - closedir(qd); - ck_free(qd_path); - ck_free(qd_synced_path); - - } + close(id_fd); // 关闭文件描述符 + closedir(qd); // 关闭队列目录 + ck_free(qd_path); // 释放队列路径内存 + ck_free(qd_synced_path); // 释放同步路径内存 + + } - closedir(sd); + closedir(sd); // 关闭同步目录 } - -/* Handle stop signal (Ctrl-C, etc). */ +/* 处理停止信号 (Ctrl-C 等)。 */ static void handle_stop_sig(int sig) { + stop_soon = 1; // 设置停止标志 - stop_soon = 1; - - if (child_pid > 0) kill(child_pid, SIGKILL); - if (forksrv_pid > 0) kill(forksrv_pid, SIGKILL); + if (child_pid > 0) kill(child_pid, SIGKILL); // 如果子进程存在,强制杀死子进程 + if (forksrv_pid > 0) kill(forksrv_pid, SIGKILL); // 如果子服务器存在,强制杀死子服务器 } - -/* Handle skip request (SIGUSR1). */ +/* 处理跳过请求 (SIGUSR1)。 */ static void handle_skipreq(int sig) { - - skip_requested = 1; + skip_requested = 1; // 设置跳过请求标志 } -/* Handle timeout (SIGALRM). */ +/* 处理超时 (SIGALRM)。 */ static void handle_timeout(int sig) { + if (child_pid > 0) { // 如果子进程存在 - if (child_pid > 0) { - - child_timed_out = 1; - kill(child_pid, SIGKILL); + child_timed_out = 1; // 设置计时器超时标志 + kill(child_pid, SIGKILL); // 强制杀死子进程 - } else if (child_pid == -1 && forksrv_pid > 0) { + } else if (child_pid == -1 && forksrv_pid > 0) { // 如果没有子进程且子服务器存在 - child_timed_out = 1; - kill(forksrv_pid, SIGKILL); + child_timed_out = 1; // 设置计时器超时标志 + kill(forksrv_pid, SIGKILL); // 强制杀死子服务器 - } + } } @@ -6872,310 +6769,341 @@ static void handle_timeout(int sig) { a valid ELF header and for evidence of AFL instrumentation. */ EXP_ST void check_binary(u8* fname) { + + u8* env_path = 0; // 用于存储环境变量 PATH 的路径 + struct stat st; // 用于获取文件状态的信息 - u8* env_path = 0; - struct stat st; + s32 fd; // 文件描述符 + u8* f_data; // 用于存储映射的文件数据 + u32 f_len = 0; // 文件长度初始化为 0 - s32 fd; - u8* f_data; - u32 f_len = 0; + ACTF("Validating target binary..."); // 输出正在验证目标二进制文件的消息 - ACTF("Validating target binary..."); + // 如果文件名中包含 '/' 或者 PATH 环境变量不存在 + if (strchr(fname, '/') || !(env_path = getenv("PATH"))) { - if (strchr(fname, '/') || !(env_path = getenv("PATH"))) { + target_path = ck_strdup(fname); // 复制文件名到目标路径 + if (stat(target_path, &st) || !S_ISREG(st.st_mode) || // 检查文件是否存在且为常规文件 + !(st.st_mode & 0111) || (f_len = st.st_size) < 4) // 检查是否可执行且文件大小大于等于 4 + FATAL("Program '%s' not found or not executable", fname); // 如果不符合要求,报错 - target_path = ck_strdup(fname); - if (stat(target_path, &st) || !S_ISREG(st.st_mode) || - !(st.st_mode & 0111) || (f_len = st.st_size) < 4) - FATAL("Program '%s' not found or not executable", fname); - - } else { + } else { - while (env_path) { + // 如果 PATH 环境变量存在,则遍历每个路径 + while (env_path) { - u8 *cur_elem, *delim = strchr(env_path, ':'); + u8 *cur_elem, *delim = strchr(env_path, ':'); // 获取当前路径元素和分隔符 - if (delim) { + if (delim) { - cur_elem = ck_alloc(delim - env_path + 1); - memcpy(cur_elem, env_path, delim - env_path); - delim++; + cur_elem = ck_alloc(delim - env_path + 1); // 分配内存存储当前路径元素 + memcpy(cur_elem, env_path, delim - env_path); // 拷贝当前路径 + delim++; // 移动到下一个元素 - } else cur_elem = ck_strdup(env_path); + } else cur_elem = ck_strdup(env_path); // 如果没有分隔符,直接复制当前路径 - env_path = delim; + env_path = delim; // 移动到下一个环境变量 - if (cur_elem[0]) - target_path = alloc_printf("%s/%s", cur_elem, fname); - else - target_path = ck_strdup(fname); + // 如果路径元素不为空 + if (cur_elem[0]) + target_path = alloc_printf("%s/%s", cur_elem, fname); // 组合成完整路径 + else + target_path = ck_strdup(fname); // 目标路径为文件名 - ck_free(cur_elem); + ck_free(cur_elem); // 释放当前路径元素的内存 - if (!stat(target_path, &st) && S_ISREG(st.st_mode) && - (st.st_mode & 0111) && (f_len = st.st_size) >= 4) break; + // 检查当前目标路径是否有效 + if (!stat(target_path, &st) && S_ISREG(st.st_mode) && + (st.st_mode & 0111) && (f_len = st.st_size) >= 4) break; // 找到有效的路径则跳出循环 - ck_free(target_path); - target_path = 0; + ck_free(target_path); // 如果未找到有效路径,则释放内存 + target_path = 0; // 重置目标路径 - } + } - if (!target_path) FATAL("Program '%s' not found or not executable", fname); + // 如果没有找到有效路径,则报错 + if (!target_path) FATAL("Program '%s' not found or not executable", fname); - } + } - if (getenv("AFL_SKIP_BIN_CHECK")) return; + // 如果环境变量 AFL_SKIP_BIN_CHECK 存在,则跳过二进制检查 + if (getenv("AFL_SKIP_BIN_CHECK")) return; - /* Check for blatant user errors. */ + /* 检查用户的明显错误 */ - if ((!strncmp(target_path, "/tmp/", 5) && !strchr(target_path + 5, '/')) || - (!strncmp(target_path, "/var/tmp/", 9) && !strchr(target_path + 9, '/'))) - FATAL("Please don't keep binaries in /tmp or /var/tmp"); + // 检查是否将二进制文件放在 /tmp 或 /var/tmp 目录下 + if ((!strncmp(target_path, "/tmp/", 5) && !strchr(target_path + 5, '/')) || + (!strncmp(target_path, "/var/tmp/", 9) && !strchr(target_path + 9, '/'))) + FATAL("Please don't keep binaries in /tmp or /var/tmp"); // 报错提示 - fd = open(target_path, O_RDONLY); + fd = open(target_path, O_RDONLY); // 打开目标文件 - if (fd < 0) PFATAL("Unable to open '%s'", target_path); + if (fd < 0) PFATAL("Unable to open '%s'", target_path); // 如果打开失败,报错 - f_data = mmap(0, f_len, PROT_READ, MAP_PRIVATE, fd, 0); + f_data = mmap(0, f_len, PROT_READ, MAP_PRIVATE, fd, 0); // 将文件数据映射到内存中 - if (f_data == MAP_FAILED) PFATAL("Unable to mmap file '%s'", target_path); + if (f_data == MAP_FAILED) PFATAL("Unable to mmap file '%s'", target_path); // 如果映射失败,报错 - close(fd); + close(fd); // 关闭文件描述符 - if (f_data[0] == '#' && f_data[1] == '!') { + // 检查是否为脚本文件 + if (f_data[0] == '#' && f_data[1] == '!') { - SAYF("\n" cLRD "[-] " cRST - "Oops, the target binary looks like a shell script. Some build systems will\n" - " sometimes generate shell stubs for dynamically linked programs; try static\n" - " library mode (./configure --disable-shared) if that's the case.\n\n" + SAYF("\n" cLRD "[-] " cRST + "Oops, the target binary looks like a shell script. Some build systems will\n" + " sometimes generate shell stubs for dynamically linked programs; try static\n" + " library mode (./configure --disable-shared) if that's the case.\n\n" - " Another possible cause is that you are actually trying to use a shell\n" - " wrapper around the fuzzed component. Invoking shell can slow down the\n" - " fuzzing process by a factor of 20x or more; it's best to write the wrapper\n" - " in a compiled language instead.\n"); + " Another possible cause is that you are actually trying to use a shell\n" + " wrapper around the fuzzed component. Invoking shell can slow down the\n" + " fuzzing process by a factor of 20x or more; it's best to write the wrapper\n" + " in a compiled language instead.\n"); - FATAL("Program '%s' is a shell script", target_path); + FATAL("Program '%s' is a shell script", target_path); // 报错,表示目标是一个 shell 脚本 - } + } #ifndef __APPLE__ - if (f_data[0] != 0x7f || memcmp(f_data + 1, "ELF", 3)) - FATAL("Program '%s' is not an ELF binary", target_path); + // 检查是否为 ELF 文件 + if (f_data[0] != 0x7f || memcmp(f_data + 1, "ELF", 3)) + FATAL("Program '%s' is not an ELF binary", target_path); // 报错,表示不是 ELF 文件 #else - if (f_data[0] != 0xCF || f_data[1] != 0xFA || f_data[2] != 0xED) - FATAL("Program '%s' is not a 64-bit Mach-O binary", target_path); + // 检查是否为 64 位 Mach-O 文件 + if (f_data[0] != 0xCF || f_data[1] != 0xFA || f_data[2] != 0xED) + FATAL("Program '%s' is not a 64-bit Mach-O binary", target_path); // 报错,表示不是 Mach-O 文件 #endif /* ^!__APPLE__ */ - if (!qemu_mode && !dumb_mode && - !memmem(f_data, f_len, SHM_ENV_VAR, strlen(SHM_ENV_VAR) + 1)) { + // 检查是否存在仪器化环境变量 + if (!qemu_mode && !dumb_mode && + !memmem(f_data, f_len, SHM_ENV_VAR, strlen(SHM_ENV_VAR) + 1)) { - SAYF("\n" cLRD "[-] " cRST - "Looks like the target binary is not instrumented! The fuzzer depends on\n" - " compile-time instrumentation to isolate interesting test cases while\n" - " mutating the input data. For more information, and for tips on how to\n" - " instrument binaries, please see %s/README.\n\n" + SAYF("\n" cLRD "[-] " cRST + "Looks like the target binary is not instrumented! The fuzzer depends on\n" + " compile-time instrumentation to isolate interesting test cases while\n" + " mutating the input data. For more information, and for tips on how to\n" + " instrument binaries, please see %s/README.\n\n" - " When source code is not available, you may be able to leverage QEMU\n" - " mode support. Consult the README for tips on how to enable this.\n" + " When source code is not available, you may be able to leverage QEMU\n" + " mode support. Consult the README for tips on how to enable this.\n" - " (It is also possible to use afl-fuzz as a traditional, \"dumb\" fuzzer.\n" - " For that, you can use the -n option - but expect much worse results.)\n", - doc_path); + " (It is also possible to use afl-fuzz as a traditional, \"dumb\" fuzzer.\n" + " For that, you can use the -n option - but expect much worse results.)\n", + doc_path); - FATAL("No instrumentation detected"); + FATAL("No instrumentation detected"); // 报错,表示没有检测到仪器化 - } + } - if (qemu_mode && - memmem(f_data, f_len, SHM_ENV_VAR, strlen(SHM_ENV_VAR) + 1)) { + // 检查在 QEMU 模式中是否发现仪器化 + if (qemu_mode && + memmem(f_data, f_len, SHM_ENV_VAR, strlen(SHM_ENV_VAR) + 1)) { - SAYF("\n" cLRD "[-] " cRST - "This program appears to be instrumented with afl-gcc, but is being run in\n" - " QEMU mode (-Q). This is probably not what you want - this setup will be\n" - " slow and offer no practical benefits.\n"); + SAYF("\n" cLRD "[-] " cRST + "This program appears to be instrumented with afl-gcc, but is being run in\n" + " QEMU mode (-Q). This is probably not what you want - this setup will be\n" + " slow and offer no practical benefits.\n"); - FATAL("Instrumentation found in -Q mode"); + FATAL("Instrumentation found in -Q mode"); // 报错,表示在 QEMU 模式中发现了仪器化 - } - - if (memmem(f_data, f_len, "libasan.so", 10) || - memmem(f_data, f_len, "__msan_init", 11)) uses_asan = 1; + } - /* Detect persistent & deferred init signatures in the binary. */ + // 检查是否使用了 ASAN + if (memmem(f_data, f_len, "libasan.so", 10) || + memmem(f_data, f_len, "__msan_init", 11)) uses_asan = 1; - if (memmem(f_data, f_len, PERSIST_SIG, strlen(PERSIST_SIG) + 1)) { + // 检测持久模式和延迟初始化的标志 + if (memmem(f_data, f_len, PERSIST_SIG, strlen(PERSIST_SIG) + 1)) { - OKF(cPIN "Persistent mode binary detected."); - setenv(PERSIST_ENV_VAR, "1", 1); - persistent_mode = 1; + OKF(cPIN "Persistent mode binary detected."); // 输出发现持久模式的消息 + setenv(PERSIST_ENV_VAR, "1", 1); // 设置环境变量表示持久模式 + persistent_mode = 1; // 更新持久模式标志 - } else if (getenv("AFL_PERSISTENT")) { + } else if (getenv("AFL_PERSISTENT")) { - WARNF("AFL_PERSISTENT is no longer supported and may misbehave!"); + WARNF("AFL_PERSISTENT is no longer supported and may misbehave!"); // 发出警告 - } + } - if (memmem(f_data, f_len, DEFER_SIG, strlen(DEFER_SIG) + 1)) { + if (memmem(f_data, f_len, DEFER_SIG, strlen(DEFER_SIG) + 1)) { - OKF(cPIN "Deferred forkserver binary detected."); - setenv(DEFER_ENV_VAR, "1", 1); - deferred_mode = 1; + OKF(cPIN "Deferred forkserver binary detected."); // 输出发现延迟 forkserver 的消息 + setenv(DEFER_ENV_VAR, "1", 1); // 设置环境变量表示延迟模式 + deferred_mode = 1; // 更新延迟模式标志 - } else if (getenv("AFL_DEFER_FORKSRV")) { + } else if (getenv("AFL_DEFER_FORKSRV")) { - WARNF("AFL_DEFER_FORKSRV is no longer supported and may misbehave!"); + WARNF("AFL_DEFER_FORKSRV is no longer supported and may misbehave!"); // 发出警告 - } + } - if (munmap(f_data, f_len)) PFATAL("unmap() failed"); + if (munmap(f_data, f_len)) PFATAL("unmap() failed"); // 解映射内存,如果失败则报错 } + /* Trim and possibly create a banner for the run. */ static void fix_up_banner(u8* name) { - + // 如果 use_banner 未设置 if (!use_banner) { - + // 如果 sync_id 存在,则使用 sync_id 作为 use_banner if (sync_id) { - use_banner = sync_id; - } else { - + // 否则,从 name 中提取最后一个 '/' 后的部分作为 use_banner u8* trim = strrchr(name, '/'); if (!trim) use_banner = name; else use_banner = trim + 1; - } - } + // 如果 use_banner 的长度超过 40 个字符,则截断并添加 "..." 后缀 if (strlen(use_banner) > 40) { - - u8* tmp = ck_alloc(44); - sprintf(tmp, "%.40s...", use_banner); - use_banner = tmp; - + u8* tmp = ck_alloc(44); // 分配 44 字节的内存 + sprintf(tmp, "%.40s...", use_banner); // 格式化字符串,保留前 40 个字符并添加 "..." + use_banner = tmp; // 更新 use_banner 为截断后的字符串 } - } - -/* Check if we're on TTY. */ - +/* 检查是否在 TTY 上运行 */ static void check_if_tty(void) { + struct winsize ws; // 用于存储终端窗口大小信息的结构体 - struct winsize ws; - + // 如果设置了 AFL_NO_UI 环境变量,则禁用 UI if (getenv("AFL_NO_UI")) { OKF("Disabling the UI because AFL_NO_UI is set."); - not_on_tty = 1; + not_on_tty = 1; // 标记不在 TTY 上运行 return; } + // 尝试获取终端窗口大小 if (ioctl(1, TIOCGWINSZ, &ws)) { - + // 如果 errno 为 ENOTTY,表示不在 TTY 上运行 if (errno == ENOTTY) { OKF("Looks like we're not running on a tty, so I'll be a bit less verbose."); - not_on_tty = 1; + not_on_tty = 1; // 标记不在 TTY 上运行 } - return; } - } - -/* Check terminal dimensions after resize. */ - +/* 检查终端尺寸是否在调整后发生变化 */ static void check_term_size(void) { + struct winsize ws; // 用于存储终端窗口大小信息的结构体 - struct winsize ws; - - term_too_small = 0; + term_too_small = 0; // 初始化终端尺寸是否过小的标志 + // 尝试获取终端窗口大小 if (ioctl(1, TIOCGWINSZ, &ws)) return; + // 如果终端窗口的行数或列数为 0,则返回 if (ws.ws_row == 0 && ws.ws_col == 0) return; - if (ws.ws_row < 25 || ws.ws_col < 80) term_too_small = 1; + // 如果终端窗口的行数小于 25 或列数小于 80,则标记为终端尺寸过小 + if (ws.ws_row < 25 || ws.ws_col < 80) term_too_small = 1; } + /* Display usage hints. */ -static void usage(u8* argv0) { +static void usage(u8* argv0) { // 定义一个静态函数usage,接收一个指向u8类型的指针argv0(程序名) - SAYF("\n%s [ options ] -- /path/to/fuzzed_app [ ... ]\n\n" + SAYF("\n%s [ options ] -- /path/to/fuzzed_app [ ... ]\n\n" // 输出使用说明的开头,包括程序名和命令格式 - "Required parameters:\n\n" + "Required parameters:\n\n" // 指出必须的参数部分 - " -i dir - input directory with test cases\n" - " -o dir - output directory for fuzzer findings\n\n" + " -i dir - input directory with test cases\n" // 输入目录选项,指定测试用例的存放位置 + " -o dir - output directory for fuzzer findings\n\n" // 输出目录选项,指定模糊测试结果的存放位置 - "Execution control settings:\n\n" + "Execution control settings:\n\n" // 执行控制设置部分 - " -f file - location read by the fuzzed program (stdin)\n" - " -t msec - timeout for each run (auto-scaled, 50-%u ms)\n" - " -m megs - memory limit for child process (%u MB)\n" - " -Q - use binary-only instrumentation (QEMU mode)\n\n" - - "Fuzzing behavior settings:\n\n" + " -f file - location read by the fuzzed program (stdin)\n" // 输入文件选项,指定模糊程序读取数据的位置 + " -t msec - timeout for each run (auto-scaled, 50-%u ms)\n" // 超时选项,设置每次运行的超时时间 + " -m megs - memory limit for child process (%u MB)\n" // 内存限制选项,限制子进程的内存使用 + " -Q - use binary-only instrumentation (QEMU mode)\n\n" // 二进制模式选项,使用仅限于二进制的插桩模式(QEMU) + + "Fuzzing behavior settings:\n\n" // 模糊�������� + +```c +static void usage(u8* argv0) { // 定义一个静态函数usage,用于显示程序的用法信息,argv0是程序的名称 + + SAYF("\n%s [ options ] -- /path/to/fuzzed_app [ ... ]\n\n" // 输出程序的用法格式,%s会被替换为argv0 + + "Required parameters:\n\n" // 输出“必需参数”标题 + + " -i dir - input directory with test cases\n" // 指定输入目录,包含测试用例 + " -o dir - output directory for fuzzer findings\n\n" // 指定输出目录,用于存放fuzzer的发现结果 - " -d - quick & dirty mode (skips deterministic steps)\n" - " -n - fuzz without instrumentation (dumb mode)\n" - " -x dir - optional fuzzer dictionary (see README)\n\n" + "Execution control settings:\n\n" // 输出“执行控制设置”标题 - "Other stuff:\n\n" + " -f file - location read by the fuzzed program (stdin)\n" // 指定被fuzz程序读取的文件位置(默认为stdin) + " -t msec - timeout for each run (auto-scaled, 50-%u ms)\n" // 指定每次运行的超时时间(自动调整,范围50到%u毫秒) + " -m megs - memory limit for child process (%u MB)\n" // 指定子进程的内存限制(%u MB) + " -Q - use binary-only instrumentation (QEMU mode)\n\n" // 使用仅二进制插桩(QEMU模式) - " -T text - text banner to show on the screen\n" - " -M / -S id - distributed mode (see parallel_fuzzing.txt)\n" - " -C - crash exploration mode (the peruvian rabbit thing)\n" - " -V - show version number and exit\n\n" - " -b cpu_id - bind the fuzzing process to the specified CPU core\n\n" + "Fuzzing behavior settings:\n\n" // 输出“Fuzzing行为设置”标题 - "For additional tips, please consult %s/README.\n\n", + " -d - quick & dirty mode (skips deterministic steps)\n" // 启用快速模式(跳过确定性步骤) + " -n - fuzz without instrumentation (dumb mode)\n" // 不使用插桩进行fuzz(傻瓜模式) + " -x dir - optional fuzzer dictionary (see README)\n\n" // 指定可选的fuzzer字典目录(参见README) - argv0, EXEC_TIMEOUT, MEM_LIMIT, doc_path); + "Other stuff:\n\n" // 输出“其他内容”标题 - exit(1); + " -T text - text banner to show on the screen\n" // 指定在屏幕上显示的文本横幅 + " -M / -S id - distributed mode (see parallel_fuzzing.txt)\n" // 分布式模式(参见parallel_fuzzing.txt) + " -C - crash exploration mode (the peruvian rabbit thing)\n" // 崩溃探索模式(“秘鲁兔子”模式) + " -V - show version number and exit\n\n" // 显示版本号并退出 + " -b cpu_id - bind the fuzzing process to the specified CPU core\n\n" // 将fuzzing进程绑定到指定的CPU核心 + + "For additional tips, please consult %s/README.\n\n", // 提示用户查阅%s/README以获取更多信息 + + argv0, EXEC_TIMEOUT, MEM_LIMIT, doc_path); // 替换格式字符串中的占位符,argv0是程序名,EXEC_TIMEOUT和MEM_LIMIT是常量,doc_path是文档路径 + + exit(1); // 退出程序,返回状态码1 } -/* Prepare output directories and fds. */ -EXP_ST void setup_dirs_fds(void) { +/* Prepare output directories and fds. */ +EXP_ST void setup_dirs_fds(void) { // 定义函数,设置输出目录和文件描述符 - u8* tmp; - s32 fd; + u8* tmp; // 声明临时指针变量,存储路径 + s32 fd; // 声明文件描述符变量 - ACTF("Setting up output directories..."); + ACTF("Setting up output directories..."); // 输出设置目录的提示信息 + // 如果启用了同步标识且尝试创建同步目录,若出错且错误不是"已存在"则致命错误 if (sync_id && mkdir(sync_dir, 0700) && errno != EEXIST) PFATAL("Unable to create '%s'", sync_dir); + // 尝试创建输出目录 if (mkdir(out_dir, 0700)) { + // 如果创建目录出错且错误不是"已存在",则致命错误 if (errno != EEXIST) PFATAL("Unable to create '%s'", out_dir); + // 可能删除输出目录的旧内容 maybe_delete_out_dir(); } else { + // 如果尝试恢复状态,但未找到旧的输出目录,则致命错误 if (in_place_resume) FATAL("Resume attempted but old output directory not found"); + // 打开输出目录,获取文件描述符 out_dir_fd = open(out_dir, O_RDONLY); -#ifndef __sun +#ifndef __sun // 如果不是Sun系统 + // 如果输出目录文件描述符小于0或加锁失败,则致命错误 if (out_dir_fd < 0 || flock(out_dir_fd, LOCK_EX | LOCK_NB)) PFATAL("Unable to flock() output directory."); @@ -7183,50 +7111,57 @@ EXP_ST void setup_dirs_fds(void) { } - /* Queue directory for any starting & discovered paths. */ + /* 为任何起始和发现的路径排队的目录。 */ + // 为队列目录分配内存并创建目录 tmp = alloc_printf("%s/queue", out_dir); - if (mkdir(tmp, 0700)) PFATAL("Unable to create '%s'", tmp); - ck_free(tmp); + if (mkdir(tmp, 0700)) PFATAL("Unable to create '%s'", tmp); // 创建失败则致命错误 + ck_free(tmp); // 释放临时变量 - /* Top-level directory for queue metadata used for session - resume and related tasks. */ + /* 用于会话恢复和相关任务的队列元数据的顶层目录。 */ + // 创建队列状态目录 tmp = alloc_printf("%s/queue/.state/", out_dir); if (mkdir(tmp, 0700)) PFATAL("Unable to create '%s'", tmp); ck_free(tmp); - /* Directory for flagging queue entries that went through - deterministic fuzzing in the past. */ + /* 标记过去经历过确定性模糊测试的队列条目的目录。 */ + // 创建确定性完成目录 tmp = alloc_printf("%s/queue/.state/deterministic_done/", out_dir); if (mkdir(tmp, 0700)) PFATAL("Unable to create '%s'", tmp); ck_free(tmp); - /* Directory with the auto-selected dictionary entries. */ + /* 自动选择字典条目的目录。 */ + // 创建自动附加条目目录 tmp = alloc_printf("%s/queue/.state/auto_extras/", out_dir); if (mkdir(tmp, 0700)) PFATAL("Unable to create '%s'", tmp); ck_free(tmp); - /* The set of paths currently deemed redundant. */ + /* 当前被认为是冗余的路径集合。 */ + // 创建冗余边缘目录 tmp = alloc_printf("%s/queue/.state/redundant_edges/", out_dir); if (mkdir(tmp, 0700)) PFATAL("Unable to create '%s'", tmp); ck_free(tmp); - /* The set of paths showing variable behavior. */ + /* 显示可变行为的路径集合。 */ + // 创建可变行为目录 tmp = alloc_printf("%s/queue/.state/variable_behavior/", out_dir); if (mkdir(tmp, 0700)) PFATAL("Unable to create '%s'", tmp); ck_free(tmp); - /* Sync directory for keeping track of cooperating fuzzers. */ + /* 用于跟踪合作模糊测试工具的同步目录。 */ + // 如果启用了同步标识 if (sync_id) { + // 分配同步目录路径 tmp = alloc_printf("%s/.synced/", out_dir); + // 创建同步目录,出错则致命错误 if (mkdir(tmp, 0700) && (!in_place_resume || errno != EEXIST)) PFATAL("Unable to create '%s'", tmp); @@ -7234,66 +7169,76 @@ EXP_ST void setup_dirs_fds(void) { } - /* All recorded crashes. */ + /* 所有记录下来的崩溃。 */ + // 创建崩溃目录 tmp = alloc_printf("%s/crashes", out_dir); if (mkdir(tmp, 0700)) PFATAL("Unable to create '%s'", tmp); ck_free(tmp); - /* All recorded hangs. */ + /* 所有记录的挂起。 */ + // 创建挂起目录 tmp = alloc_printf("%s/hangs", out_dir); if (mkdir(tmp, 0700)) PFATAL("Unable to create '%s'", tmp); ck_free(tmp); - /* Generally useful file descriptors. */ + /* 一般实用的文件描述符。 */ + // 打开/dev/null,以便丢弃数据 dev_null_fd = open("/dev/null", O_RDWR); - if (dev_null_fd < 0) PFATAL("Unable to open /dev/null"); + if (dev_null_fd < 0) PFATAL("Unable to open /dev/null"); // 打开失败则致命错误 + // 打开/dev/urandom,以便获取随机数据 dev_urandom_fd = open("/dev/urandom", O_RDONLY); - if (dev_urandom_fd < 0) PFATAL("Unable to open /dev/urandom"); + if (dev_urandom_fd < 0) PFATAL("Unable to open /dev/urandom"); // 打开失败则致命错误 - /* Gnuplot output file. */ + /* Gnuplot输出文件。 */ + // 创建plot_data文件以存储绘图数据 tmp = alloc_printf("%s/plot_data", out_dir); - fd = open(tmp, O_WRONLY | O_CREAT | O_EXCL, 0600); - if (fd < 0) PFATAL("Unable to create '%s'", tmp); - ck_free(tmp); + fd = open(tmp, O_WRONLY | O_CREAT | O_EXCL, 0600); // 以写入方式打开文件 + if (fd < 0) PFATAL("Unable to create '%s'", tmp); // 打开失败则致命错误 + ck_free(tmp); // 释放临时变量 + // 将文件描述符转换为文件流 plot_file = fdopen(fd, "w"); - if (!plot_file) PFATAL("fdopen() failed"); + if (!plot_file) PFATAL("fdopen() failed"); // 转换失败则致命错误 + // 输出文件首行注释 fprintf(plot_file, "# unix_time, cycles_done, cur_path, paths_total, " "pending_total, pending_favs, map_size, unique_crashes, " "unique_hangs, max_depth, execs_per_sec\n"); - /* ignore errors */ + /* 忽略错误 */ } -/* Setup the output file for fuzzed data, if not using -f. */ +/* 如果不使用 -f,设置模糊数据的输出文件。 */ EXP_ST void setup_stdio_file(void) { + // 分配当前输入文件的路径 u8* fn = alloc_printf("%s/.cur_input", out_dir); - unlink(fn); /* Ignore errors */ + unlink(fn); /* 忽略错误 */ // 删除当前输入文件(如果存在) + // 打开当前输入文件 out_fd = open(fn, O_RDWR | O_CREAT | O_EXCL, 0600); + + if (out_fd < 0) PFATAL("Unable to create '%s'", fn); // 打开失败则致命错误 - if (out_fd < 0) PFATAL("Unable to create '%s'", fn); - - ck_free(fn); + ck_free(fn); // 释放临时变量 } + /* Make sure that core dumps don't go to a program. */ -static void check_crash_handling(void) { +static void check_crash_handling(void) { // 定义一个静态函数,检查崩溃处理机制 -#ifdef __APPLE__ +#ifdef __APPLE__ // 如果操作系统是苹果的 macOS /* Yuck! There appears to be no simple C API to query for the state of loaded daemons on MacOS X, and I'm a bit hesitant to do something @@ -7301,9 +7246,9 @@ static void check_crash_handling(void) { until I get a box to test the code. So, for now, we check for crash reporting the awful way. */ - if (system("launchctl list 2>/dev/null | grep -q '\\.ReportCrash$'")) return; + if (system("launchctl list 2>/dev/null | grep -q '\\.ReportCrash'")) return; // 检查是否有崩溃报告进程在运行,如果有则直接返回 - SAYF("\n" cLRD "[-] " cRST + SAYF("\n" cLRD "[-] " cRST // 输出警告信息,通知用户有崩溃报告工具 "Whoops, your system is configured to forward crash notifications to an\n" " external crash reporting utility. This will cause issues due to the\n" " extended delay between the fuzzed binary malfunctioning and this fact\n" @@ -7315,24 +7260,24 @@ static void check_crash_handling(void) { " launchctl unload -w ${SL}/LaunchAgents/${PL}.plist\n" " sudo launchctl unload -w ${SL}/LaunchDaemons/${PL}.Root.plist\n"); - if (!getenv("AFL_I_DONT_CARE_ABOUT_MISSING_CRASHES")) + if (!getenv("AFL_I_DONT_CARE_ABOUT_MISSING_CRASHES")) // 如果环境变量未设置,则报告错误 FATAL("Crash reporter detected"); -#else +#else // 如果不是 macOS /* This is Linux specific, but I don't think there's anything equivalent on *BSD, so we can just let it slide for now. */ - s32 fd = open("/proc/sys/kernel/core_pattern", O_RDONLY); - u8 fchar; + s32 fd = open("/proc/sys/kernel/core_pattern", O_RDONLY); // 打开核心转储模式文件 + u8 fchar; // 文件字符变量 - if (fd < 0) return; + if (fd < 0) return; // 如果文件打开失败,则返回 - ACTF("Checking core_pattern..."); + ACTF("Checking core_pattern..."); // 检查核心转储模式 - if (read(fd, &fchar, 1) == 1 && fchar == '|') { + if (read(fd, &fchar, 1) == 1 && fchar == '|') { // 判断核心模式是否以管道符开头 - SAYF("\n" cLRD "[-] " cRST + SAYF("\n" cLRD "[-] " cRST // 输出警告信息 "Hmm, your system is configured to send core dump notifications to an\n" " external utility. This will cause issues: there will be an extended delay\n" " between stumbling upon a crash and having this information relayed to the\n" @@ -7343,56 +7288,54 @@ static void check_crash_handling(void) { " echo core >/proc/sys/kernel/core_pattern\n"); - if (!getenv("AFL_I_DONT_CARE_ABOUT_MISSING_CRASHES")) + if (!getenv("AFL_I_DONT_CARE_ABOUT_MISSING_CRASHES")) // 如果环境变量未设置,则报告错误 FATAL("Pipe at the beginning of 'core_pattern'"); } - close(fd); + close(fd); // 关闭文件描述符 #endif /* ^__APPLE__ */ } - -/* Check CPU governor. */ - +/* Check CPU governor. */ // 检查CPU调节器 static void check_cpu_governor(void) { - FILE* f; - u8 tmp[128]; - u64 min = 0, max = 0; + FILE* f; // 文件指针 + u8 tmp[128]; // 临时存储变量 + u64 min = 0, max = 0; // 最小和最大频率 - if (getenv("AFL_SKIP_CPUFREQ")) return; + if (getenv("AFL_SKIP_CPUFREQ")) return; // 如果设置了跳过CPU频率检查的环境变量,则返回 - f = fopen("/sys/devices/system/cpu/cpu0/cpufreq/scaling_governor", "r"); - if (!f) return; + f = fopen("/sys/devices/system/cpu/cpu0/cpufreq/scaling_governor", "r"); // 打开CPU调节器文件 + if (!f) return; // 如果文件打开失败,则返回 - ACTF("Checking CPU scaling governor..."); + ACTF("Checking CPU scaling governor..."); // 检查CPU频率调节模式 - if (!fgets(tmp, 128, f)) PFATAL("fgets() failed"); + if (!fgets(tmp, 128, f)) PFATAL("fgets() failed"); // 读取调节模式,如果失败则报告致命错误 - fclose(f); + fclose(f); // 关闭文件 - if (!strncmp(tmp, "perf", 4)) return; + if (!strncmp(tmp, "perf", 4)) return; // 如果调节模式是“perf”,则返回 - f = fopen("/sys/devices/system/cpu/cpu0/cpufreq/scaling_min_freq", "r"); + f = fopen("/sys/devices/system/cpu/cpu0/cpufreq/scaling_min_freq", "r"); // 打开最小频率文件 - if (f) { - if (fscanf(f, "%llu", &min) != 1) min = 0; - fclose(f); + if (f) { // 如果文件打开成功 + if (fscanf(f, "%llu", &min) != 1) min = 0; // 读取最小频率,如果失败则设为0 + fclose(f); // 关闭文件 } - f = fopen("/sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq", "r"); + f = fopen("/sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq", "r"); // 打开最大频率文件 - if (f) { - if (fscanf(f, "%llu", &max) != 1) max = 0; - fclose(f); + if (f) { // 如果文件打开成功 + if (fscanf(f, "%llu", &max) != 1) max = 0; // 读取最大频率,如果失败则设为0 + fclose(f); // 关闭文件 } - if (min == max) return; + if (min == max) return; // 如果最小和最大频率相同,则返回 - SAYF("\n" cLRD "[-] " cRST + SAYF("\n" cLRD "[-] " cRST // 输出警告信息,提示CPU频率调节模式不佳 "Whoops, your system uses on-demand CPU frequency scaling, adjusted\n" " between %llu and %llu MHz. Unfortunately, the scaling algorithm in the\n" " kernel is imperfect and can miss the short-lived processes spawned by\n" @@ -7404,94 +7347,103 @@ static void check_cpu_governor(void) { " You can later go back to the original state by replacing 'performance' with\n" " 'ondemand'. If you don't want to change the settings, set AFL_SKIP_CPUFREQ\n" " to make afl-fuzz skip this check - but expect some performance drop.\n", - min / 1024, max / 1024); - - FATAL("Suboptimal CPU scaling governor"); + min / 1024, max / 1024); // 输出最小最大频率的值转为GHz + FATAL("Suboptimal CPU scaling governor"); // 报告致命错误 } + /* Count the number of logical CPU cores. */ static void get_core_count(void) { - u32 cur_runnable = 0; + u32 cur_runnable = 0; // 当前可运行的任务数 #if defined(__APPLE__) || defined(__FreeBSD__) || defined (__OpenBSD__) + // 如果是 macOS、FreeBSD 或 OpenBSD 系统 - size_t s = sizeof(cpu_core_count); + size_t s = sizeof(cpu_core_count); // 获取 cpu_core_count 的大小 /* On *BSD systems, we can just use a sysctl to get the number of CPUs. */ + // 在 *BSD 系统上,我们可以使用 sysctl 来获取 CPU 数量 #ifdef __APPLE__ + // 如果是 macOS 系统 if (sysctlbyname("hw.logicalcpu", &cpu_core_count, &s, NULL, 0) < 0) - return; + return; // 使用 sysctlbyname 获取逻辑 CPU 数量,失败则返回 #else + // 如果是 FreeBSD 或 OpenBSD 系统 - int s_name[2] = { CTL_HW, HW_NCPU }; + int s_name[2] = { CTL_HW, HW_NCPU }; // 定义 sysctl 参数 - if (sysctl(s_name, 2, &cpu_core_count, &s, NULL, 0) < 0) return; + if (sysctl(s_name, 2, &cpu_core_count, &s, NULL, 0) < 0) return; // 使用 sysctl 获取 CPU 数量,失败则返回 #endif /* ^__APPLE__ */ #else + // 如果不是 macOS、FreeBSD 或 OpenBSD 系统 #ifdef HAVE_AFFINITY + // 如果支持 CPU 亲和性 - cpu_core_count = sysconf(_SC_NPROCESSORS_ONLN); + cpu_core_count = sysconf(_SC_NPROCESSORS_ONLN); // 使用 sysconf 获取在线 CPU 数量 #else + // 如果不支持 CPU 亲和性 - FILE* f = fopen("/proc/stat", "r"); - u8 tmp[1024]; + FILE* f = fopen("/proc/stat", "r"); // 打开 /proc/stat 文件 + u8 tmp[1024]; // 定义缓冲区 - if (!f) return; + if (!f) return; // 如果文件打开失败,返回 - while (fgets(tmp, sizeof(tmp), f)) - if (!strncmp(tmp, "cpu", 3) && isdigit(tmp[3])) cpu_core_count++; + while (fgets(tmp, sizeof(tmp), f)) // 逐行读取文件内容 + if (!strncmp(tmp, "cpu", 3) && isdigit(tmp[3])) cpu_core_count++; // 如果行以 "cpu" 开头且后面是数字,增加 CPU 核心计数 - fclose(f); + fclose(f); // 关闭文件 #endif /* ^HAVE_AFFINITY */ #endif /* ^(__APPLE__ || __FreeBSD__ || __OpenBSD__) */ - if (cpu_core_count > 0) { + if (cpu_core_count > 0) { // 如果成功获取到 CPU 核心数量 - cur_runnable = (u32)get_runnable_processes(); + cur_runnable = (u32)get_runnable_processes(); // 获取当前可运行的任务数 #if defined(__APPLE__) || defined(__FreeBSD__) || defined (__OpenBSD__) + // 如果是 macOS、FreeBSD 或 OpenBSD 系统 /* Add ourselves, since the 1-minute average doesn't include that yet. */ + // 添加当前进程,因为 1 分钟的平均值尚未包含它 - cur_runnable++; + cur_runnable++; // 增加当前可运行任务数 #endif /* __APPLE__ || __FreeBSD__ || __OpenBSD__ */ OKF("You have %u CPU core%s and %u runnable tasks (utilization: %0.0f%%).", - cpu_core_count, cpu_core_count > 1 ? "s" : "", - cur_runnable, cur_runnable * 100.0 / cpu_core_count); + cpu_core_count, cpu_core_count > 1 ? "s" : "", // 输出 CPU 核心数和可运行任务数 + cur_runnable, cur_runnable * 100.0 / cpu_core_count); // 计算并输出 CPU 利用率 - if (cpu_core_count > 1) { + if (cpu_core_count > 1) { // 如果 CPU 核心数大于 1 - if (cur_runnable > cpu_core_count * 1.5) { + if (cur_runnable > cpu_core_count * 1.5) { // 如果可运行任务数超过 CPU 核心数的 1.5 倍 - WARNF("System under apparent load, performance may be spotty."); + WARNF("System under apparent load, performance may be spotty."); // 警告系统负载较高,性能可能不稳定 - } else if (cur_runnable + 1 <= cpu_core_count) { + } else if (cur_runnable + 1 <= cpu_core_count) { // 如果可运行任务数加 1 小于等于 CPU 核心数 - OKF("Try parallel jobs - see %s/parallel_fuzzing.txt.", doc_path); + OKF("Try parallel jobs - see %s/parallel_fuzzing.txt.", doc_path); // 建议尝试并行任务 } } - } else { + } else { // 如果无法获取 CPU 核心数量 - cpu_core_count = 0; - WARNF("Unable to figure out the number of CPU cores."); + cpu_core_count = 0; // 将 CPU 核心数量设为 0 + WARNF("Unable to figure out the number of CPU cores."); // 警告无法获取 CPU 核心数量 } @@ -7500,131 +7452,146 @@ static void get_core_count(void) { /* Validate and fix up out_dir and sync_dir when using -S. */ +// 修正同步设置的函数 static void fix_up_sync(void) { + + u8* x = sync_id; // 声明指针x指向同步ID - u8* x = sync_id; - + // 检查是否处于傻瓜模式,若是,显示错误信息并终止 if (dumb_mode) FATAL("-S / -M and -n are mutually exclusive"); + // 如果跳过确定性选项被设置 if (skip_deterministic) { - + + // 如果强制确定性被设置,显示错误信息建议使用-S if (force_deterministic) FATAL("use -S instead of -M -d"); else - FATAL("-S already implies -d"); - + FATAL("-S already implies -d"); // -S已经隐含了-d选项 } + // 验证同步ID中的字符是否有效 while (*x) { - + + // 如果字符不是字母数字或下划线和横线,显示错误信息并终止 if (!isalnum(*x) && *x != '_' && *x != '-') FATAL("Non-alphanumeric fuzzer ID specified via -S or -M"); - x++; - + x++; // 移动到下一个字符 } + // 如果同步ID的长度超过32,显示错误信息并终止 if (strlen(sync_id) > 32) FATAL("Fuzzer ID too long"); + // 分配内存,将输出目录和同步ID组合成新的目录字符串 x = alloc_printf("%s/%s", out_dir, sync_id); + // 设置同步目录 sync_dir = out_dir; + // 更新输出目录为新创建的目录 out_dir = x; + // 如果未强制确定性 if (!force_deterministic) { - skip_deterministic = 1; - use_splicing = 1; + skip_deterministic = 1; // 设置跳过确定性标志 + use_splicing = 1; // 启用拼接功能 } - } - -/* Handle screen resize (SIGWINCH). */ - +/* 处理屏幕大小变化 (SIGWINCH) 的信号 */ static void handle_resize(int sig) { - clear_screen = 1; + clear_screen = 1; // 标记需要清屏 } /* Check ASAN options. */ static void check_asan_opts(void) { + // 获取环境变量 ASAN_OPTIONS 的值 u8* x = getenv("ASAN_OPTIONS"); + // 如果 ASAN_OPTIONS 环境变量存在 if (x) { + // 检查 ASAN_OPTIONS 中是否包含 abort_on_error=1,如果不包含则报错 if (!strstr(x, "abort_on_error=1")) FATAL("Custom ASAN_OPTIONS set without abort_on_error=1 - please fix!"); + // 检查 ASAN_OPTIONS 中是否包含 symbolize=0,如果不包含则报错 if (!strstr(x, "symbolize=0")) FATAL("Custom ASAN_OPTIONS set without symbolize=0 - please fix!"); } + // 获取环境变量 MSAN_OPTIONS 的值 x = getenv("MSAN_OPTIONS"); + // 如果 MSAN_OPTIONS 环境变量存在 if (x) { + // 检查 MSAN_OPTIONS 中是否包含 exit_code=MSAN_ERROR,如果不包含则报错 if (!strstr(x, "exit_code=" STRINGIFY(MSAN_ERROR))) FATAL("Custom MSAN_OPTIONS set without exit_code=" STRINGIFY(MSAN_ERROR) " - please fix!"); + // 检查 MSAN_OPTIONS 中是否包含 symbolize=0,如果不包含则报错 if (!strstr(x, "symbolize=0")) FATAL("Custom MSAN_OPTIONS set without symbolize=0 - please fix!"); } -} +} + /* Detect @@ in args. */ -EXP_ST void detect_file_args(char** argv) { +EXP_ST void detect_file_args(char** argv) { // 定义一个函数detect_file_args,接受一个字符指针数组argv - u32 i = 0; - u8* cwd = getcwd(NULL, 0); + u32 i = 0; // 初始化索引变量i为0 + u8* cwd = getcwd(NULL, 0); // 获取当前工作目录,并将结果存储在cwd中 - if (!cwd) PFATAL("getcwd() failed"); + if (!cwd) PFATAL("getcwd() failed"); // 如果获取目前工作目录失败,调用PFATAL输出错误信息并终止程序 - while (argv[i]) { + while (argv[i]) { // 当argv[i]不为空(即还有参数时) - u8* aa_loc = strstr(argv[i], "@@"); + u8* aa_loc = strstr(argv[i], "@@"); // 查找当前参数中是否包含"@@" - if (aa_loc) { + if (aa_loc) { // 如果找到了"@@" - u8 *aa_subst, *n_arg; + u8 *aa_subst, *n_arg; // 定义两个指针变量aa_subst和n_arg - /* If we don't have a file name chosen yet, use a safe default. */ + /* 如果还没有选择文件名,使用一个安全的默认值。 */ - if (!out_file) - out_file = alloc_printf("%s/.cur_input", out_dir); + if (!out_file) // 如果out_file为空 + out_file = alloc_printf("%s/.cur_input", out_dir); // 分配内存并设置默认文件名 - /* Be sure that we're always using fully-qualified paths. */ + /* 确保我们始终使用绝对路径。 */ - if (out_file[0] == '/') aa_subst = out_file; - else aa_subst = alloc_printf("%s/%s", cwd, out_file); + if (out_file[0] == '/') aa_subst = out_file; // 如果out_file是绝对路径,直接赋值给aa_subst + else aa_subst = alloc_printf("%s/%s", cwd, out_file); // 否则,将当前工作目录与out_file组合成绝对路径 - /* Construct a replacement argv value. */ + /* 构造一个替换的argv值。 */ - *aa_loc = 0; - n_arg = alloc_printf("%s%s%s", argv[i], aa_subst, aa_loc + 2); - argv[i] = n_arg; - *aa_loc = '@'; + *aa_loc = 0; // 将"@@"的第一个字符替换为null,分隔字符串 + n_arg = alloc_printf("%s%s%s", argv[i], aa_subst, aa_loc + 2); // 创建新的参数,通过拼接原参数和替换路径 + argv[i] = n_arg; // 将新的参数赋值回argv[i] + *aa_loc = '@'; // 恢复"@@"的位置,为下一次循环准备 - if (out_file[0] != '/') ck_free(aa_subst); + if (out_file[0] != '/') ck_free(aa_subst); // 如果out_file不是绝对路径,释放分配的内存 } - i++; + i++; // 移动到下一个参数 } - free(cwd); /* not tracked */ - + free(cwd); /* not tracked */ // 释放cwd指针指向的内存,不跟踪该内存 } + /* Set up signal handlers. More complicated that needs to be, because libc on Solaris doesn't resume interrupted reads(), sets SA_RESETHAND when you call siginterrupt(), and does other unnecessary things. */ @@ -7633,87 +7600,100 @@ EXP_ST void setup_signal_handlers(void) { struct sigaction sa; - sa.sa_handler = NULL; - sa.sa_flags = SA_RESTART; - sa.sa_sigaction = NULL; + sa.sa_handler = NULL; // 初始化信号处理函数为空 + sa.sa_flags = SA_RESTART; // 设置信号处理标志为SA_RESTART,使被中断的系统调用自动重启 + sa.sa_sigaction = NULL; // 初始化信号处理函数指针为空 - sigemptyset(&sa.sa_mask); + 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); + sa.sa_handler = handle_stop_sig; // 设置处理停止信号的函数 + sigaction(SIGHUP, &sa, NULL); // 设置SIGHUP信号的处理函数 + sigaction(SIGINT, &sa, NULL); // 设置SIGINT信号的处理函数 + sigaction(SIGTERM, &sa, NULL); // 设置SIGTERM信号的处理函数 /* Exec timeout notifications. */ - sa.sa_handler = handle_timeout; - sigaction(SIGALRM, &sa, NULL); + sa.sa_handler = handle_timeout; // 设置处理超时信号的函数 + sigaction(SIGALRM, &sa, NULL); // 设置SIGALRM信号的处理函数 /* Window resize */ - sa.sa_handler = handle_resize; - sigaction(SIGWINCH, &sa, NULL); + sa.sa_handler = handle_resize; // 设置处理窗口大小改变信号的函数 + sigaction(SIGWINCH, &sa, NULL); // 设置SIGWINCH信号的处理函数 /* SIGUSR1: skip entry */ - sa.sa_handler = handle_skipreq; - sigaction(SIGUSR1, &sa, NULL); + sa.sa_handler = handle_skipreq; // 设置处理SIGUSR1信号的函数 + sigaction(SIGUSR1, &sa, NULL); // 设置SIGUSR1信号的处理函数 /* Things we don't care about. */ - sa.sa_handler = SIG_IGN; - sigaction(SIGTSTP, &sa, NULL); - sigaction(SIGPIPE, &sa, NULL); + sa.sa_handler = SIG_IGN; // 设置忽略信号的处理方式 + sigaction(SIGTSTP, &sa, NULL); // 忽略SIGTSTP信号 + sigaction(SIGPIPE, &sa, NULL); // 忽略SIGPIPE信号 } + /* Rewrite argv for QEMU. */ static char** get_qemu_argv(u8* own_loc, char** argv, int argc) { + // 分配新的参数数组,大小为原始参数数量加上4个额外参数 char** new_argv = ck_alloc(sizeof(char*) * (argc + 4)); u8 *tmp, *cp, *rsl, *own_copy; - /* Workaround for a QEMU stability glitch. */ - + /* 设置环境变量 QEMU_LOG 为 "nochain",以解决 QEMU 的稳定性问题 */ setenv("QEMU_LOG", "nochain", 1); + // 将原始参数从 argv[1] 开始复制到 new_argv[3] 开始的位置 memcpy(new_argv + 3, argv + 1, sizeof(char*) * argc); + // 设置 new_argv[2] 为目标路径,new_argv[1] 为 "--" new_argv[2] = target_path; new_argv[1] = "--"; - /* Now we need to actually find the QEMU binary to put in argv[0]. */ + /* 现在需要找到 QEMU 二进制文件并将其放入 new_argv[0] */ + // 获取环境变量 AFL_PATH 的值 tmp = getenv("AFL_PATH"); if (tmp) { + // 构建 QEMU 二进制文件的路径 cp = alloc_printf("%s/afl-qemu-trace", tmp); + // 检查该路径是否可执行 if (access(cp, X_OK)) FATAL("Unable to find '%s'", tmp); + // 设置目标路径和 new_argv[0] 为找到的 QEMU 二进制文件路径 target_path = new_argv[0] = cp; return new_argv; } + // 复制 own_loc 字符串 own_copy = ck_strdup(own_loc); + // 查找最后一个 '/' 字符的位置 rsl = strrchr(own_copy, '/'); if (rsl) { + // 将 '/' 替换为字符串结束符 *rsl = 0; + // 构建 QEMU 二进制文件的路径 cp = alloc_printf("%s/afl-qemu-trace", own_copy); ck_free(own_copy); + // 检查该路径是否可执行 if (!access(cp, X_OK)) { + // 设置目标路径和 new_argv[0] 为找到的 QEMU 二进制文件路径 target_path = new_argv[0] = cp; return new_argv; @@ -7721,13 +7701,16 @@ static char** get_qemu_argv(u8* own_loc, char** argv, int argc) { } else ck_free(own_copy); + // 检查默认路径下的 QEMU 二进制文件是否可执行 if (!access(BIN_PATH "/afl-qemu-trace", X_OK)) { + // 设置目标路径和 new_argv[0] 为默认路径下的 QEMU 二进制文件路径 target_path = new_argv[0] = ck_strdup(BIN_PATH "/afl-qemu-trace"); return new_argv; } + // 如果找不到 QEMU 二进制文件,输出错误信息 SAYF("\n" cLRD "[-] " cRST "Oops, unable to find the 'afl-qemu-trace' binary. The binary must be built\n" " separately by following the instructions in qemu_mode/README.qemu. If you\n" @@ -7738,43 +7721,43 @@ static char** get_qemu_argv(u8* own_loc, char** argv, int argc) { " instrumented at compile time with afl-gcc. It is also possible to use it as a\n" " traditional \"dumb\" fuzzer by specifying '-n' in the command line.\n"); + // 抛出致命错误,提示无法找到 QEMU 二进制文件 FATAL("Failed to locate 'afl-qemu-trace'."); } -/* Make a copy of the current command line. */ - +// 函数: save_cmdline +// 功能: 保存当前命令行的参数 static void save_cmdline(u32 argc, char** argv) { - u32 len = 1, i; - u8* buf; + u32 len = 1, i; // 初始化长度为1,存储循环变量i + u8* buf; // 定义字符指针,用于保存命令行字符串 + // 计算所有命令行参数的总长度 for (i = 0; i < argc; i++) - len += strlen(argv[i]) + 1; - + len += strlen(argv[i]) + 1; // 每个参数长度加1(用于空格) + + // 分配内存,保存命令行参数 buf = orig_cmdline = ck_alloc(len); + // 复制命令行参数到分配的缓冲区 for (i = 0; i < argc; i++) { - u32 l = strlen(argv[i]); + u32 l = strlen(argv[i]); // 获取当前参数的长度 - memcpy(buf, argv[i], l); - buf += l; - - if (i != argc - 1) *(buf++) = ' '; + memcpy(buf, argv[i], l); // 复制参数到缓冲区 + buf += l; // 移动缓冲区指针 + // 如果不是最后一个参数,添加空格 + if (i != argc - 1) *(buf++) = ' '; } - *buf = 0; - + *buf = 0; // 在字符串末尾添加终止符 } - -#ifndef AFL_LIB - -/* Main entry point */ - +#ifndef AFL_LIB // 如果没有定义 AFL_LIB +// 主函数,程序的入口点 int main(int argc, char** argv) { s32 opt; @@ -7788,94 +7771,114 @@ int main(int argc, char** argv) { struct timeval tv; struct timezone tz; - SAYF(cCYA "afl-fuzz " cBRI VERSION cRST " by \n"); - - doc_path = access(DOC_PATH, F_OK) ? "docs" : DOC_PATH; - - gettimeofday(&tv, &tz); - srandom(tv.tv_sec ^ tv.tv_usec ^ getpid()); - - // argv 处理 - while ((opt = getopt(argc, argv, "+i:o:f:m:b:t:T:dnCB:S:M:x:QV")) > 0) - - switch (opt) { - - case 'i': /* input dir */ - - // 初始 corpus 目录 - if (in_dir) FATAL("Multiple -i options not supported"); - in_dir = optarg; - - // 若使用 "-i -",则表示 in-place resume - if (!strcmp(in_dir, "-")) in_place_resume = 1; - - break; - - case 'o': /* output dir */ - - if (out_dir) FATAL("Multiple -o options not supported"); - out_dir = optarg; - break; - - case 'M': { /* master sync ID */ - - u8* c; - - if (sync_id) FATAL("Multiple -S or -M options not supported"); - sync_id = ck_strdup(optarg); - - if ((c = strchr(sync_id, ':'))) { - - *c = 0; - - if (sscanf(c + 1, "%u/%u", &master_id, &master_max) != 2 || - !master_id || !master_max || master_id > master_max || - master_max > 1000000) FATAL("Bogus master ID passed to -M"); + SAYF(cCYA "afl-fuzz " cBRI VERSION cRST " by \n"); +} + /*************************************************************** + * 函数/方法: 主程序的参数处理 + * 描述: 该段代码负责解析命令行参数,并对输入输出目录、文件、同步ID以及超时等选项进行处理和验证。 + ***************************************************************/ + + doc_path = access(DOC_PATH, F_OK) ? "docs" : DOC_PATH; + + gettimeofday(&tv, &tz); + srandom(tv.tv_sec ^ tv.tv_usec ^ getpid()); + + // argv 处理 + while ((opt = getopt(argc, argv, "+i:o:f:m:b:t:T:dnCB:S:M:x:QV")) > 0) + + switch (opt) { + + case 'i': /* input dir */ + + // 初始 corpus 目录 + if (in_dir) FATAL("Multiple -i options not supported"); + in_dir = optarg; + + // 若使用 "-i -",则表示 in-place resume + if (!strcmp(in_dir, "-")) in_place_resume = 1; + + break; + + /* 处理输出目录的命令行选项 */ + case 'o': /* output dir */ + + if (out_dir) FATAL("Multiple -o options not supported"); + out_dir = optarg; + break; + + case 'M': { /* master sync ID */ + // 处理选项 'M',用于设定主同步 ID + + u8* c; + + if (sync_id) FATAL("Multiple -S or -M options not supported"); + sync_id = ck_strdup(optarg); + + if ((c = strchr(sync_id, ':'))) { + // 解析同步ID中的主ID和最大ID + + *c = 0; + + if (sscanf(c + 1, "%u/%u", &master_id, &master_max) != 2 || + !master_id || !master_max || master_id > master_max || + master_max > 1000000) FATAL("Bogus master ID passed to -M"); + + } + + force_deterministic = 1; + // 强制使用确定性执行 + } - - force_deterministic = 1; - - } - - break; - - case 'S': - - if (sync_id) FATAL("Multiple -S or -M options not supported"); - sync_id = ck_strdup(optarg); - break; - - case 'f': /* target file */ - - if (out_file) FATAL("Multiple -f options not supported"); - out_file = optarg; - break; - - case 'x': /* dictionary */ - - if (extras_dir) FATAL("Multiple -x options not supported"); - extras_dir = optarg; - break; - - case 't': { /* timeout */ - - u8 suffix = 0; - - if (timeout_given) FATAL("Multiple -t options not supported"); - - if (sscanf(optarg, "%u%c", &exec_tmout, &suffix) < 1 || - optarg[0] == '-') FATAL("Bad syntax used for -t"); - - if (exec_tmout < 5) FATAL("Dangerously low value of -t"); - - if (suffix == '+') timeout_given = 2; else timeout_given = 1; - + break; - - } + + // 处理-S选项的代码逻辑 + /************************************************************************************ + * 该代码处理不同命令行选项的解析,包括同步ID、目标文件和字典目录等。 * + * 当检测到重复的选项时,程序会调用FATAL函数报错。 * + ************************************************************************************/ + case 'S': + + if (sync_id) FATAL("Multiple -S or -M options not supported"); + sync_id = ck_strdup(optarg); + break; + + case 'f': /* target file */ + + if (out_file) FATAL("Multiple -f options not supported"); + out_file = optarg; + break; + + case 'x': /* dictionary */ + + if (extras_dir) FATAL("Multiple -x options not supported"); + extras_dir = optarg; + break; + + /************************************** + * 处理命令行选项中与超时相关的参数 + * 支持-t选项,用于设置执行超时时间 + **************************************/ + case 't': { /* timeout */ + + u8 suffix = 0; + + if (timeout_given) FATAL("Multiple -t options not supported"); + + if (sscanf(optarg, "%u%c", &exec_tmout, &suffix) < 1 || + optarg[0] == '-') FATAL("Bad syntax used for -t"); + + if (exec_tmout < 5) FATAL("Dangerously low value of -t"); + + if (suffix == '+') timeout_given = 2; else timeout_given = 1; + + break; + + } case 'm': { /* mem limit */ + // 处理内存限制选项的相关逻辑 u8 suffix = 'M'; @@ -7883,15 +7886,16 @@ int main(int argc, char** argv) { mem_limit_given = 1; if (!strcmp(optarg, "none")) { - + // 如果选项参数为"none",则将内存限制设置为0 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; @@ -7903,8 +7907,10 @@ int main(int argc, char** argv) { } + // 检查内存限制值是否低于安全阈值 if (mem_limit < 5) FATAL("Dangerously low value of -m"); + // 在32位系统上检查内存限制的范围 if (sizeof(rlim_t) == 4 && mem_limit > 2000) FATAL("Value of -m out of range on 32-bit systems"); @@ -7913,10 +7919,11 @@ int main(int argc, char** argv) { break; case 'b': { /* bind CPU core */ - + // 处理 CPU 绑定选项,确保只能使用一次 if (cpu_to_bind_given) FATAL("Multiple -b options not supported"); cpu_to_bind_given = 1; + // 解析用户输入的 CPU 核心编号,检查输入合法性 if (sscanf(optarg, "%u", &cpu_to_bind) < 1 || optarg[0] == '-') FATAL("Bad syntax used for -b"); @@ -7924,6 +7931,7 @@ int main(int argc, char** argv) { } + /* 处理命令行参数,选择跳过确定性测试的选项 */ case 'd': /* skip deterministic */ if (skip_deterministic) FATAL("Multiple -d options not supported"); @@ -7932,24 +7940,19 @@ int main(int argc, char** argv) { break; case 'B': /* load bitmap */ - - /* This is a secret undocumented option! It is useful if you find - an interesting test case during a normal fuzzing process, and want - to mutate it without rediscovering any of the test cases already - found during an earlier run. - - To use this mode, you need to point -B to the fuzz_bitmap produced - by an earlier run for the exact same binary... and that's it. - - I only used this once or twice to get variants of a particular - file, so I'm not making this an official setting. */ - + /* + 该代码段处理加载位图的选项。 + 位图功能是一个未文档化的选项,适用于在正常模糊测试过程中找到有趣的测试用例后,进行变异而不重新发现先前运行中已找到的测试用例。 + 使用此模式时,需要将 -B 指向之前运行相同二进制文件生成的 fuzz_bitmap。 + 该功能仅在少数几次中使用过,因此不做为官方设置。 + */ if (in_bitmap) FATAL("Multiple -B options not supported"); in_bitmap = optarg; read_bitmap(in_bitmap); break; + /* 处理命令行参数选项 'C',用于设置崩溃模式 */ case 'C': /* crash mode */ if (crash_mode) FATAL("Multiple -C options not supported"); @@ -7957,27 +7960,34 @@ int main(int argc, char** argv) { break; case 'n': /* dumb mode */ - + // 处理-dumb模式的选项,当程序收到'-n'时触发 + if (dumb_mode) FATAL("Multiple -n options not supported"); if (getenv("AFL_DUMB_FORKSRV")) dumb_mode = 2; else dumb_mode = 1; break; + /* 处理命令行选项 */ case 'T': /* banner */ + /* 检查是否支持多个 -T 选项 */ if (use_banner) FATAL("Multiple -T options not supported"); use_banner = optarg; break; + // 处理QEMU模式的代码段 + // 如果启用QEMU模式,检查是否已指定多个-Q选项 + // 如果未指定内存限制,则使用默认的QEMU内存限制 + // 在处理完成后退出switch语句 + case 'Q': /* QEMU mode */ + + if (qemu_mode) FATAL("Multiple -Q options not supported"); + qemu_mode = 1; + + if (!mem_limit_given) mem_limit = MEM_LIMIT_QEMU; + + break; - case 'Q': /* QEMU mode */ - - if (qemu_mode) FATAL("Multiple -Q options not supported"); - qemu_mode = 1; - - if (!mem_limit_given) mem_limit = MEM_LIMIT_QEMU; - - break; - + /* 处理选项 'V' 的情况,显示版本号 */ case 'V': /* Show version number */ /* Version number has been printed already, just quit. */ @@ -7989,44 +7999,57 @@ int main(int argc, char** argv) { } - if (optind == argc || !in_dir || !out_dir) usage(argv[0]); - - setup_signal_handlers(); - check_asan_opts(); - - if (sync_id) fix_up_sync(); - - if (!strcmp(in_dir, out_dir)) - FATAL("Input and output directories can't be the same"); - - if (dumb_mode) { - - if (crash_mode) FATAL("-C and -n are mutually exclusive"); - if (qemu_mode) FATAL("-Q and -n are mutually exclusive"); - - } - - if (getenv("AFL_NO_FORKSRV")) no_forkserver = 1; - if (getenv("AFL_NO_CPU_RED")) no_cpu_meter_red = 1; - if (getenv("AFL_NO_ARITH")) no_arith = 1; - if (getenv("AFL_SHUFFLE_QUEUE")) shuffle_queue = 1; - if (getenv("AFL_FAST_CAL")) fast_cal = 1; - - if (getenv("AFL_HANG_TMOUT")) { - hang_tmout = atoi(getenv("AFL_HANG_TMOUT")); - if (!hang_tmout) FATAL("Invalid value of AFL_HANG_TMOUT"); - } - - if (dumb_mode == 2 && no_forkserver) - FATAL("AFL_DUMB_FORKSRV and AFL_NO_FORKSRV are mutually exclusive"); + // 检查命令行参数以及输入输出目录是否有效,若无效则显示使用方法 + if (optind == argc || !in_dir || !out_dir) usage(argv[0]); + + // 设置信号处理函数 + setup_signal_handlers(); + // 检查地址卫生选项 + check_asan_opts(); + + // 处理同步ID和目录检查的逻辑 + // 该段代码主要用于验证输入输出目录及模式的有效性 + // 在不同的运行模式下进行相应的错误处理 + + if (sync_id) fix_up_sync(); + + if (!strcmp(in_dir, out_dir)) + FATAL("Input and output directories can't be the same"); + + if (dumb_mode) { + + if (crash_mode) FATAL("-C and -n are mutually exclusive"); + if (qemu_mode) FATAL("-Q and -n are mutually exclusive"); + + } - if (getenv("AFL_PRELOAD")) { - setenv("LD_PRELOAD", getenv("AFL_PRELOAD"), 1); - setenv("DYLD_INSERT_LIBRARIES", getenv("AFL_PRELOAD"), 1); - } + // 该代码段用于读取环境变量并根据其值设置相应的标志和超时值 + // 主要功能是启动时配置这些参数以调整程序的行为 + if (getenv("AFL_NO_FORKSRV")) no_forkserver = 1; + if (getenv("AFL_NO_CPU_RED")) no_cpu_meter_red = 1; + if (getenv("AFL_NO_ARITH")) no_arith = 1; + if (getenv("AFL_SHUFFLE_QUEUE")) shuffle_queue = 1; + if (getenv("AFL_FAST_CAL")) fast_cal = 1; + + if (getenv("AFL_HANG_TMOUT")) { + hang_tmout = atoi(getenv("AFL_HANG_TMOUT")); + if (!hang_tmout) FATAL("Invalid value of AFL_HANG_TMOUT"); + } - if (getenv("AFL_LD_PRELOAD")) - FATAL("Use AFL_PRELOAD instead of AFL_LD_PRELOAD"); + // 此函数用于检查环境变量和启动模式的合法性 + // 如果在不支持的模式下使用互斥选项,则会引发致命错误 + // 处理环境变量以确保正确的库加载 + + if (dumb_mode == 2 && no_forkserver) + FATAL("AFL_DUMB_FORKSRV and AFL_NO_FORKSRV are mutually exclusive"); + + if (getenv("AFL_PRELOAD")) { + setenv("LD_PRELOAD", getenv("AFL_PRELOAD"), 1); + setenv("DYLD_INSERT_LIBRARIES", getenv("AFL_PRELOAD"), 1); + } + + if (getenv("AFL_LD_PRELOAD")) + FATAL("Use AFL_PRELOAD instead of AFL_LD_PRELOAD"); save_cmdline(argc, argv); @@ -8037,104 +8060,142 @@ int main(int argc, char** argv) { get_core_count(); #ifdef HAVE_AFFINITY + // 将当前进程绑定到一个空闲的CPU上,以优化性能 bind_to_free_cpu(); #endif /* HAVE_AFFINITY */ +/* 检查崩溃处理机制 */ check_crash_handling(); +/* 检查CPU调节器设置 */ check_cpu_governor(); +/* 设置后处理操作 */ setup_post(); +/* 设置共享内存 */ setup_shm(); +/* 初始化计数类16 */ init_count_class16(); +/* 设置目录和文件描述符 */ setup_dirs_fds(); +/* 读取测试用例 */ read_testcases(); +/* 加载自动化设置 */ load_auto(); +/* 处理输入的转换 */ pivot_inputs(); + // 此代码段为 afl-fuzz.c 文件中的初始化和参数处理部分 + // 主要功能是配置执行环境并检查传入的文件参数 + // 包括加载额外目录、查找超时、检测文件参数、设置输出文件、检查二进制文件及获取当前时间 + + if (extras_dir) load_extras(extras_dir); + + if (!timeout_given) find_timeout(); + + detect_file_args(argv + optind + 1); + + if (!out_file) setup_stdio_file(); + + check_binary(argv[optind]); + + start_time = get_cur_time(); + + if (qemu_mode) + use_argv = get_qemu_argv(argv[0], argv + optind, argc - optind); + else + use_argv = argv + optind; - if (extras_dir) load_extras(extras_dir); - - if (!timeout_given) find_timeout(); - - detect_file_args(argv + optind + 1); - - if (!out_file) setup_stdio_file(); - - check_binary(argv[optind]); - - start_time = get_cur_time(); - - if (qemu_mode) - use_argv = get_qemu_argv(argv[0], argv + optind, argc - optind); - else - use_argv = argv + optind; - - perform_dry_run(use_argv); - - cull_queue(); - - show_init_stats(); - - seek_to = find_start_position(); - - write_stats_file(0, 0, 0); - save_auto(); - - if (stop_soon) goto stop_fuzzing; - - /* Woop woop woop */ - - if (!not_on_tty) { - sleep(4); - start_time += 4000; + /* + * 此函数实现了模糊测试的初始步骤,包括执行干运行、清理队列、 + * 显示初始化统计信息、寻找起始位置并写入统计文件。 + */ + perform_dry_run(use_argv); + + cull_queue(); + + show_init_stats(); + + seek_to = find_start_position(); + + write_stats_file(0, 0, 0); + save_auto(); + + /* + * 如果状态指示测试需要停止,则跳转到停止模糊测试的标签。 + */ if (stop_soon) goto stop_fuzzing; - } + + /* Woop woop woop */ + + /* + * 如果不是在终端上运行,则暂停4秒,增加开始时间。 + */ + if (!not_on_tty) { + sleep(4); + start_time += 4000; + /* + * 再次检查是否需要停止模糊测试。 + */ + if (stop_soon) goto stop_fuzzing; + } + // 该段代码实现了一个持续循环,处理模糊测试队列中的条目。 while (1) { - - u8 skipped_fuzz; - - cull_queue(); - - if (!queue_cur) { - - queue_cycle++; - current_entry = 0; - cur_skipped_paths = 0; - queue_cur = queue; - - while (seek_to) { - current_entry++; - seek_to--; - queue_cur = queue_cur->next; - } - - show_stats(); - - if (not_on_tty) { - ACTF("Entering queue cycle %llu.", queue_cycle); - fflush(stdout); - } + + u8 skipped_fuzz; + + // 清理当前模糊测试队列 + cull_queue(); + + if (!queue_cur) { + + // 如果当前队列为空,增加队列周期计数 + queue_cycle++; + current_entry = 0; + cur_skipped_paths = 0; + queue_cur = queue; + + // 根据指定的跳过路径数量,定位到队列中的特定条目 + while (seek_to) { + current_entry++; + seek_to--; + queue_cur = queue_cur->next; + } + + // 显示当前状态统计 + show_stats(); + + if (not_on_tty) { + // 在非终端模式下,输出当前队列周期信息 + ACTF("Entering queue cycle %llu.", queue_cycle); + fflush(stdout); + } /* If we had a full queue cycle with no new finds, try recombination strategies next. */ + // 检查当前队列路径是否与前一个队列路径相同 if (queued_paths == prev_queued) { + // 根据是否使用拼接,决定增加无发现循环次数或启用拼接 if (use_splicing) cycles_wo_finds++; else use_splicing = 1; } else cycles_wo_finds = 0; + // 更新前一个队列路径值 prev_queued = queued_paths; + // 检查同步ID,队列循环和环境变量以同步模糊测试器 if (sync_id && queue_cycle == 1 && getenv("AFL_IMPORT_FIRST")) sync_fuzzers(use_argv); } + // 执行模糊测试 skipped_fuzz = fuzz_one(use_argv); + // 如果没有停止标志且没有跳过模糊测试,根据同步间隔决定是否同步 if (!stop_soon && sync_id && !skipped_fuzz) { if (!(sync_interval_cnt++ % SYNC_INTERVAL)) @@ -8142,38 +8203,47 @@ int main(int argc, char** argv) { } + // 如果没有停止标志且 exit_1 为真,则设置停止标志 if (!stop_soon && exit_1) stop_soon = 2; + // 如果设置了停止标志,则跳出循环 if (stop_soon) break; - queue_cur = queue_cur->next; - current_entry++; - - } - - if (queue_cur) show_stats(); + // 更新当前队列指针和当前条目计数 + queue_cur = queue_cur->next; + current_entry++; - /* If we stopped programmatically, we kill the forkserver and the current runner. - If we stopped manually, this is done by the signal handler. */ - if (stop_soon == 2) { - if (child_pid > 0) kill(child_pid, SIGKILL); - if (forksrv_pid > 0) kill(forksrv_pid, SIGKILL); - } - /* Now that we've killed the forkserver, we wait for it to be able to get rusage stats. */ - if (waitpid(forksrv_pid, NULL, 0) <= 0) { - WARNF("error waitpid\n"); } - write_bitmap(); - write_stats_file(0, 0, 0); - save_auto(); + /* + * 该段代码用于处理程序终止的情况,包括被程序控制的终止和手动终止。 + * 在终止时会杀死子进程和forkserver,并等待forkserver获取资源使用统计信息。 + * 此外,还会写入位图和统计文件以保存当前状态。 + */ + if (queue_cur) show_stats(); + + /* If we stopped programmatically, we kill the forkserver and the current runner. + If we stopped manually, this is done by the signal handler. */ + if (stop_soon == 2) { + if (child_pid > 0) kill(child_pid, SIGKILL); + if (forksrv_pid > 0) kill(forksrv_pid, SIGKILL); + } + /* Now that we've killed the forkserver, we wait for it to be able to get rusage stats. */ + if (waitpid(forksrv_pid, NULL, 0) <= 0) { + WARNF("error waitpid\n"); + } + + write_bitmap(); + write_stats_file(0, 0, 0); + save_auto(); +/* 停止模糊测试的函数 */ stop_fuzzing: SAYF(CURSOR_SHOW cLRD "\n\n+++ Testing aborted %s +++\n" cRST, stop_soon == 2 ? "programmatically" : "by user"); - /* Running for more than 30 minutes but still doing first cycle? */ + /* 运行超过30分钟但仍在进行第一次周期? */ if (queue_cycle == 1 && get_cur_time() - start_time > 30 * 60 * 1000) { diff --git a/src/afl-gcc.c b/src/afl-gcc.c index 2e6dffc..e443eb5 100644 --- a/src/afl-gcc.c +++ b/src/afl-gcc.c @@ -47,79 +47,73 @@ #include #include -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!这个 -仪器将工作(令人惊讶)。唉,未处理的异常确实如此 -不调用abort(),因此需要修改afl-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_SOURCE和ASAN/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 \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; diff --git a/src/afl-gotcpu.c b/src/afl-gotcpu.c index 630b7ff..cb5c30d 100644 --- a/src/afl-gotcpu.c +++ b/src/afl-gotcpu.c @@ -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 \n"); + SAYF(cCYA "afl-gotcpu " cBRI VERSION cRST " by \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; diff --git a/src/afl-plot b/src/afl-plot index 78825a9..355dd00 100644 --- a/src/afl-plot +++ b/src/afl-plot @@ -1,170 +1,175 @@ #!/bin/sh # -# american fuzzy lop - Advanced Persistent Graphing +# American Fuzzy Lop - 高级持久图形化工具 # ------------------------------------------------- # -# Written and maintained by Michal Zalewski -# Based on a design & prototype by Michael Rash. +# 作者和维护者:Michal Zalewski +# 基于 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 " +echo "为 afl-fuzz 提供的进度绘图工具 " # 输出程序名称 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_ - - - + # 显示 banner 信息 + # 显示输入目录 + # 显示生成日期
Banner:$BANNER
Directory:$1
Generated on:`date`
Banner:$BANNER
Directory:$1
Generated on:`date`

-

-

- +

# 显示高频图像 +

# 显示低频图像 + # 显示执行速度图像 _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 # 正常退出程序 diff --git a/src/afl-showmap.c b/src/afl-showmap.c index 4af6518..f9b667f 100644 --- a/src/afl-showmap.c +++ b/src/afl-showmap.c @@ -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 \n"); - + SAYF(cCYA "afl-showmap " cBRI VERSION cRST " by \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 > 2); + u32 i = (MAP_SIZE >> 2); // 初始化计数器,MAP_SIZE 是位图的大小,右移2位表示以32位为单位处理 - if (!mask) return; + if (!mask) return; // 如果没有掩码,直接返回 - while (i--) { + while (i--) { // 遍历位图中的每个32位块 - *mem &= ~*mask; - mem++; - mask++; + *mem &= ~*mask; // 对当前32位块应用掩码的反码 + mem++; // 移动到下一个32位块 + mask++; // 移动到下一个掩码块 } } - -/* See if any bytes are set in the bitmap. */ - +/* 检查位图中是否有任何字节被设置 */ static inline u8 anything_set(void) { - u32* ptr = (u32*)trace_bits; - u32 i = (MAP_SIZE >> 2); + u32* ptr = (u32*)trace_bits; // 将位图指针转换为32位指针 + u32 i = (MAP_SIZE >> 2); // 初始化计数器,MAP_SIZE 是位图的大小,右移2位表示以32位为单位处理 - while (i--) if (*(ptr++)) return 1; + while (i--) if (*(ptr++)) return 1; // 遍历位图中的每个32位块,如果有任何一个块不为0,则返回1 - return 0; + return 0; // 如果所有块都为0,则返回0 } - - -/* Get rid of shared memory and temp files (atexit handler). */ - +/* 清理共享内存和临时文件(atexit处理程序) */ static void remove_shm(void) { - if (prog_in) unlink(prog_in); /* Ignore errors */ - shmctl(shm_id, IPC_RMID, NULL); + if (prog_in) unlink(prog_in); /* 忽略错误,删除临时文件 */ + 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); + 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"); // 如果附加失败,输出错误信息并退出 } - -/* Read initial file. */ - +/* 读取初始文件 */ static void read_initial_file(void) { - struct stat st; - s32 fd = open(in_file, O_RDONLY); + struct stat st; // 用于存储文件状态信息 + s32 fd = open(in_file, O_RDONLY); // 以只读模式打开输入文件 - if (fd < 0) PFATAL("Unable to open '%s'", in_file); + if (fd < 0) PFATAL("Unable to open '%s'", in_file); // 如果文件打开失败,输出错误信息并退出 - if (fstat(fd, &st) || !st.st_size) - FATAL("Zero-sized input file."); + if (fstat(fd, &st) || !st.st_size) // 获取文件状态信息,并检查文件大小是否为0 + FATAL("Zero-sized input file."); // 如果文件大小为0,输出错误信息并退出 - if (st.st_size >= TMIN_MAX_FILE) - FATAL("Input file is too large (%u MB max)", TMIN_MAX_FILE / 1024 / 1024); + if (st.st_size >= TMIN_MAX_FILE) // 检查文件大小是否超过最大允许大小 + FATAL("Input file is too large (%u MB max)", TMIN_MAX_FILE / 1024 / 1024); // 如果文件过大,输出错误信息并退出 - in_len = st.st_size; - in_data = ck_alloc_nozero(in_len); + in_len = st.st_size; // 记录文件大小 + in_data = ck_alloc_nozero(in_len); // 分配与文件大小相同的内存空间 - ck_read(fd, in_data, in_len, in_file); + ck_read(fd, in_data, in_len, in_file); // 将文件内容读取到分配的内存中 - close(fd); + close(fd); // 关闭文件描述符 - OKF("Read %u byte%s from '%s'.", in_len, in_len == 1 ? "" : "s", in_file); + OKF("Read %u byte%s from '%s'.", in_len, in_len == 1 ? "" : "s", in_file); // 输出成功读取文件的信息 } - -/* Write output file. */ - +/* 将数据写入文件 */ static s32 write_to_file(u8* path, u8* mem, u32 len) { s32 ret; - unlink(path); /* Ignore errors */ + unlink(path); /* 忽略错误,删除目标文件(如果存在) */ - ret = open(path, O_RDWR | O_CREAT | O_EXCL, 0600); + ret = open(path, O_RDWR | O_CREAT | O_EXCL, 0600); // 以读写模式创建新文件,权限为0600(仅所有者可读写) - if (ret < 0) PFATAL("Unable to create '%s'", path); + if (ret < 0) PFATAL("Unable to create '%s'", path); // 如果文件创建失败,输出错误信息并退出 - ck_write(ret, mem, len, path); + ck_write(ret, mem, len, path); // 将内存中的数据写入文件 - lseek(ret, 0, SEEK_SET); + lseek(ret, 0, SEEK_SET); // 将文件指针重置到文件开头 - return ret; + return ret; // 返回文件描述符 } - -/* Handle timeout signal. */ - +/* 处理超时信号 */ static void handle_timeout(int sig) { - child_timed_out = 1; - if (child_pid > 0) kill(child_pid, SIGKILL); + child_timed_out = 1; // 设置超时标志 + if (child_pid > 0) kill(child_pid, SIGKILL); // 如果子进程存在,发送SIGKILL信号终止子进程 } -/* Execute target application. Returns 0 if the changes are a dud, or - 1 if they should be kept. */ - +/* 执行目标应用程序。如果更改无效返回0,否则返回1 */ static u8 run_target(char** argv, u8* mem, u32 len, u8 first_run) { - static struct itimerval it; - int status = 0; + static struct itimerval it; // 用于设置定时器的结构体 + int status = 0; // 用于存储子进程的退出状态 - s32 prog_in_fd; - u32 cksum; + s32 prog_in_fd; // 文件描述符,用于存储输入文件 + u32 cksum; // 校验和(未使用) - memset(trace_bits, 0, MAP_SIZE); - MEM_BARRIER(); + memset(trace_bits, 0, MAP_SIZE); // 清空位图,用于存储执行路径信息 + MEM_BARRIER(); // 内存屏障,确保内存操作顺序 - prog_in_fd = write_to_file(prog_in, mem, len); + prog_in_fd = write_to_file(prog_in, mem, len); // 将输入数据写入临时文件 - 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 (dup2(use_stdin ? prog_in_fd : dev_null_fd, 0) < 0 || - dup2(dev_null_fd, 1) < 0 || - dup2(dev_null_fd, 2) < 0) { + // 重定向标准输入、输出和错误流 + if (dup2(use_stdin ? prog_in_fd : dev_null_fd, 0) < 0 || // 重定向标准输入 + dup2(dev_null_fd, 1) < 0 || // 重定向标准输出到/dev/null + dup2(dev_null_fd, 2) < 0) { // 重定向标准错误到/dev/null - *(u32*)trace_bits = EXEC_FAIL_SIG; - PFATAL("dup2() failed"); + *(u32*)trace_bits = EXEC_FAIL_SIG; // 如果重定向失败,设置执行失败标志 + PFATAL("dup2() failed"); // 输出错误信息并退出 } - close(dev_null_fd); - close(prog_in_fd); + close(dev_null_fd); // 关闭/dev/null文件描述符 + close(prog_in_fd); // 关闭输入文件描述符 - setsid(); + setsid(); // 创建新的会话,使子进程脱离终端控制 - 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; // 将内存限制转换为字节 #ifdef RLIMIT_AS - setrlimit(RLIMIT_AS, &r); /* Ignore errors */ + setrlimit(RLIMIT_AS, &r); /* 忽略错误,设置地址空间限制 */ #else - setrlimit(RLIMIT_DATA, &r); /* Ignore errors */ + setrlimit(RLIMIT_DATA, &r); /* 忽略错误,设置数据段限制 */ #endif /* ^RLIMIT_AS */ } - r.rlim_max = r.rlim_cur = 0; - setrlimit(RLIMIT_CORE, &r); /* Ignore errors */ + r.rlim_max = r.rlim_cur = 0; // 设置核心转储文件大小为0,禁止生成核心转储 + setrlimit(RLIMIT_CORE, &r); /* 忽略错误,设置核心转储限制 */ - execv(target_path, argv); + execv(target_path, argv); // 执行目标程序 - *(u32*)trace_bits = EXEC_FAIL_SIG; - exit(0); + *(u32*)trace_bits = EXEC_FAIL_SIG; // 如果execv失败,设置执行失败标志 + exit(0); // 退出子进程 } - close(prog_in_fd); + close(prog_in_fd); // 父进程关闭输入文件描述符 - /* Configure timeout, wait for child, cancel timeout. */ + /* 配置超时,等待子进程,取消超时 */ - 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; + child_pid = 0; // 重置子进程ID + it.it_value.tv_sec = 0; // 重置定时器的秒数 + it.it_value.tv_usec = 0; // 重置定时器的微秒数 - setitimer(ITIMER_REAL, &it, NULL); + setitimer(ITIMER_REAL, &it, NULL); // 取消定时器 - MEM_BARRIER(); + MEM_BARRIER(); // 内存屏障,确保内存操作顺序 - /* Clean up bitmap, analyze exit condition, etc. */ + /* 清理位图,分析退出条件等 */ - if (*(u32*)trace_bits == EXEC_FAIL_SIG) - 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); - apply_mask((u32*)trace_bits, (u32*)mask_bitmap); - total_execs++; + classify_counts(trace_bits); // 对位图中的计数值进行分类 + apply_mask((u32*)trace_bits, (u32*)mask_bitmap); // 对位图应用掩码 + total_execs++; // 增加总执行次数计数器 - if (stop_soon) { + if (stop_soon) { // 如果用户请求停止 - SAYF(cRST cLRD "\n+++ Minimization aborted by user +++\n" cRST); - close(write_to_file(out_file, in_data, in_len)); - exit(1); + SAYF(cRST cLRD "\n+++ Minimization aborted by user +++\n" cRST); // 输出用户中止信息 + close(write_to_file(out_file, in_data, in_len)); // 将当前输入数据写入输出文件 + exit(1); // 退出程序 } - /* Always discard inputs that time out. */ + /* 始终丢弃超时的输入 */ - if (child_timed_out) { + if (child_timed_out) { // 如果子进程超时 - missed_hangs++; - return 0; + missed_hangs++; // 增加超时计数器 + return 0; // 返回0,表示输入无效 } - /* Handle crashing inputs depending on current mode. */ - if (WIFSIGNALED(status) || - (WIFEXITED(status) && WEXITSTATUS(status) == MSAN_ERROR) || - (WIFEXITED(status) && WEXITSTATUS(status) && exit_crash)) { + /* 根据当前模式处理崩溃的输入 */ + + if (WIFSIGNALED(status) || // 如果子进程因信号终止 + (WIFEXITED(status) && WEXITSTATUS(status) == MSAN_ERROR) || // 或者子进程退出且退出状态为MSAN错误 + (WIFEXITED(status) && WEXITSTATUS(status) && exit_crash)) { // 或者子进程退出且退出状态非0且启用了崩溃模式 - if (first_run) crash_mode = 1; + if (first_run) crash_mode = 1; // 如果是第一次运行,设置崩溃模式 - if (crash_mode) { + if (crash_mode) { // 如果处于崩溃模式 - if (!exact_mode) return 1; + if (!exact_mode) return 1; // 如果不是精确模式,返回1(表示输入有效) - } else { + } else { // 如果不是崩溃模式 - missed_crashes++; - return 0; + missed_crashes++; // 增加崩溃计数器 + return 0; // 返回0(表示输入无效) } - } else + } else // 如果子进程正常退出且没有崩溃 - /* Handle non-crashing inputs appropriately. */ + /* 根据当前模式处理非崩溃的输入 */ - if (crash_mode) { + if (crash_mode) { // 如果处于崩溃模式 - missed_paths++; - return 0; + missed_paths++; // 增加路径计数器 + return 0; // 返回0(表示输入无效) } - cksum = hash32(trace_bits, MAP_SIZE, HASH_CONST); + cksum = hash32(trace_bits, MAP_SIZE, HASH_CONST); // 计算位图的校验和 - if (first_run) orig_cksum = cksum; + if (first_run) orig_cksum = cksum; // 如果是第一次运行,记录原始校验和 - if (orig_cksum == cksum) return 1; + if (orig_cksum == cksum) return 1; // 如果当前校验和与原始校验和相同,返回1(表示输入有效) - missed_paths++; - return 0; + missed_paths++; // 增加路径计数器 + return 0; // 返回0(表示输入无效) } - -/* Find first power of two greater or equal to val. */ - +/* 找到大于或等于val的第一个2的幂 */ static u32 next_p2(u32 val) { - u32 ret = 1; - while (val > ret) ret <<= 1; - return ret; + u32 ret = 1; // 初始化结果为1 + while (val > ret) ret <<= 1; // 不断左移,直到结果大于或等于val + return ret; // 返回结果 } - -/* Actually minimize! */ - +/* 实际地进行最小化 */ static void minimize(char** argv) { - static u32 alpha_map[256]; + static u32 alpha_map[256]; // 用于存储字符频率的数组 - u8* tmp_buf = ck_alloc_nozero(in_len); - u32 orig_len = in_len, stage_o_len; + u8* tmp_buf = ck_alloc_nozero(in_len); // 分配与输入数据大小相同的临时缓冲区 + u32 orig_len = in_len, stage_o_len; // 记录原始输入长度和当前阶段的长度 - u32 del_len, set_len, del_pos, set_pos, i, alpha_size, cur_pass = 0; - u32 syms_removed, alpha_del0 = 0, alpha_del1, alpha_del2, alpha_d_total = 0; - u8 changed_any, prev_del; + u32 del_len, set_len, del_pos, set_pos, i, alpha_size, cur_pass = 0; // 各种临时变量 + u32 syms_removed, alpha_del0 = 0, alpha_del1, alpha_del2, alpha_d_total = 0; // 用于统计删除的字符数量 + u8 changed_any, prev_del; // 标志变量,用于记录是否有变化 /*********************** * BLOCK NORMALIZATION * ***********************/ - set_len = next_p2(in_len / TMIN_SET_STEPS); - set_pos = 0; + set_len = next_p2(in_len / TMIN_SET_STEPS); // 计算块大小 + set_pos = 0; // 初始化块起始位置 - if (set_len < TMIN_SET_MIN_SIZE) set_len = TMIN_SET_MIN_SIZE; + if (set_len < TMIN_SET_MIN_SIZE) set_len = TMIN_SET_MIN_SIZE; // 如果块大小小于最小允许值,设置为最小允许值 - ACTF(cBRI "Stage #0: " cRST "One-time block normalization..."); + ACTF(cBRI "Stage #0: " cRST "One-time block normalization..."); // 输出阶段信息 - while (set_pos < in_len) { + while (set_pos < in_len) { // 遍历输入数据 - u8 res; - u32 use_len = MIN(set_len, in_len - set_pos); + u8 res; // 用于存储运行目标程序的结果 + u32 use_len = MIN(set_len, in_len - set_pos); // 计算当前块的实际长度 - for (i = 0; i < use_len; i++) + for (i = 0; i < use_len; i++) // 检查当前块是否全为'0' if (in_data[set_pos + i] != '0') break; - if (i != use_len) { + if (i != use_len) { // 如果当前块不全为'0' - memcpy(tmp_buf, in_data, in_len); - memset(tmp_buf + set_pos, '0', use_len); + memcpy(tmp_buf, in_data, in_len); // 复制输入数据到临时缓冲区 + memset(tmp_buf + set_pos, '0', use_len); // 将当前块替换为'0' - res = run_target(argv, tmp_buf, in_len, 0); + res = run_target(argv, tmp_buf, in_len, 0); // 运行目标程序 - if (res) { + if (res) { // 如果运行结果有效 - memset(in_data + set_pos, '0', use_len); - changed_any = 1; - alpha_del0 += use_len; + memset(in_data + set_pos, '0', use_len); // 将输入数据中的当前块替换为'0' + changed_any = 1; // 设置变化标志 + alpha_del0 += use_len; // 增加删除的字符数量 } } - set_pos += set_len; + set_pos += set_len; // 移动到下一个块 } - alpha_d_total += alpha_del0; + alpha_d_total += alpha_del0; // 更新总删除字符数量 OKF("Block normalization complete, %u byte%s replaced.", alpha_del0, - alpha_del0 == 1 ? "" : "s"); + alpha_del0 == 1 ? "" : "s"); // 输出块归一化完成信息 next_pass: - ACTF(cYEL "--- " cBRI "Pass #%u " cYEL "---", ++cur_pass); - changed_any = 0; + ACTF(cYEL "--- " cBRI "Pass #%u " cYEL "---", ++cur_pass); // 输出当前阶段信息 + changed_any = 0; // 重置变化标志 + /****************** * BLOCK DELETION * ******************/ - del_len = next_p2(in_len / TRIM_START_STEPS); - stage_o_len = in_len; + del_len = next_p2(in_len / TRIM_START_STEPS); // 计算初始删除块大小 + stage_o_len = in_len; // 记录当前阶段的输入长度 - ACTF(cBRI "Stage #1: " cRST "Removing blocks of data..."); + ACTF(cBRI "Stage #1: " cRST "Removing blocks of data..."); // 输出阶段信息 next_del_blksize: - if (!del_len) del_len = 1; - del_pos = 0; - prev_del = 1; + if (!del_len) del_len = 1; // 如果删除块大小为0,设置为1 + del_pos = 0; // 初始化删除块的起始位置 + prev_del = 1; // 初始化前一个块是否被删除的标志 SAYF(cGRA " Block length = %u, remaining size = %u\n" cRST, - del_len, in_len); + del_len, in_len); // 输出当前删除块大小和剩余输入长度 - while (del_pos < in_len) { + while (del_pos < in_len) { // 遍历输入数据 - u8 res; - s32 tail_len; + u8 res; // 用于存储运行目标程序的结果 + s32 tail_len; // 用于存储删除块后的尾部长度 - tail_len = in_len - del_pos - del_len; - if (tail_len < 0) tail_len = 0; + tail_len = in_len - del_pos - del_len; // 计算删除块后的尾部长度 + if (tail_len < 0) tail_len = 0; // 如果尾部长度为负,设置为0 - /* If we have processed at least one full block (initially, prev_del == 1), - and we did so without deleting the previous one, and we aren't at the - very end of the buffer (tail_len > 0), and the current block is the same - as the previous one... skip this step as a no-op. */ + /* 如果我们已经处理了一个完整的块(最初,prev_del == 1), + 并且没有删除前一个块,且我们不在缓冲区的末尾(tail_len > 0), + 并且当前块与前一个块相同... 则跳过此步骤作为无操作 */ if (!prev_del && tail_len && !memcmp(in_data + del_pos - del_len, - in_data + del_pos, del_len)) { + in_data + del_pos, del_len)) { // 如果当前块与前一个块相同 - del_pos += del_len; + del_pos += del_len; // 跳过当前块 continue; } - prev_del = 0; + prev_del = 0; // 重置前一个块是否被删除的标志 - /* Head */ - memcpy(tmp_buf, in_data, del_pos); + /* 头部 */ + memcpy(tmp_buf, in_data, del_pos); // 复制删除块前的数据到临时缓冲区 - /* Tail */ - memcpy(tmp_buf + del_pos, in_data + del_pos + del_len, tail_len); + /* 尾部 */ + memcpy(tmp_buf + del_pos, in_data + del_pos + del_len, tail_len); // 复制删除块后的数据到临时缓冲区 - res = run_target(argv, tmp_buf, del_pos + tail_len, 0); + res = run_target(argv, tmp_buf, del_pos + tail_len, 0); // 运行目标程序 - if (res) { + if (res) { // 如果运行结果有效 - memcpy(in_data, tmp_buf, del_pos + tail_len); - prev_del = 1; - in_len = del_pos + tail_len; + memcpy(in_data, tmp_buf, del_pos + tail_len); // 将临时缓冲区的内容复制回输入数据 + prev_del = 1; // 设置前一个块被删除的标志 + in_len = del_pos + tail_len; // 更新输入长度 - changed_any = 1; + changed_any = 1; // 设置变化标志 - } else del_pos += del_len; + } else del_pos += del_len; // 如果运行结果无效,跳过当前块 } - if (del_len > 1 && in_len >= 1) { + if (del_len > 1 && in_len >= 1) { // 如果删除块大小大于1且输入长度大于等于1 - del_len /= 2; - goto next_del_blksize; + del_len /= 2; // 将删除块大小减半 + goto next_del_blksize; // 继续处理下一个块大小 } - OKF("Block removal complete, %u bytes deleted.", stage_o_len - in_len); + OKF("Block removal complete, %u bytes deleted.", stage_o_len - in_len); // 输出块删除完成信息 - if (!in_len && changed_any) - WARNF(cLRD "Down to zero bytes - check the command line and mem limit!" cRST); + if (!in_len && changed_any) // 如果输入长度为0且发生了变化 + WARNF(cLRD "Down to zero bytes - check the command line and mem limit!" cRST); // 输出警告信息 + + if (cur_pass > 1 && !changed_any) goto finalize_all; // 如果当前阶段大于1且没有变化,跳转到最终处理 - if (cur_pass > 1 && !changed_any) goto finalize_all; /************************* * ALPHABET MINIMIZATION * *************************/ - alpha_size = 0; - alpha_del1 = 0; - syms_removed = 0; + alpha_size = 0; // 初始化字符种类数量 + alpha_del1 = 0; // 初始化字符删除数量 + syms_removed = 0; // 初始化符号删除数量 - memset(alpha_map, 0, 256 * sizeof(u32)); + memset(alpha_map, 0, 256 * sizeof(u32)); // 初始化字符频率映射表 - for (i = 0; i < in_len; i++) { - if (!alpha_map[in_data[i]]) alpha_size++; - alpha_map[in_data[i]]++; + for (i = 0; i < in_len; i++) { // 遍历输入数据 + if (!alpha_map[in_data[i]]) alpha_size++; // 如果当前字符未记录过,增加字符种类数量 + alpha_map[in_data[i]]++; // 增加当前字符的频率 } - ACTF(cBRI "Stage #2: " cRST "Minimizing symbols (%u code point%s)...", + ACTF(cBRI "Stage #2: " cRST "Minimizing symbols (%u code point%s)...", // 输出阶段信息 alpha_size, alpha_size == 1 ? "" : "s"); - for (i = 0; i < 256; i++) { + for (i = 0; i < 256; i++) { // 遍历所有可能的字符(0-255) - u32 r; - u8 res; + u32 r; // 用于遍历输入数据的索引 + u8 res; // 用于存储运行目标程序的结果 - if (i == '0' || !alpha_map[i]) continue; + if (i == '0' || !alpha_map[i]) continue; // 如果当前字符是'0'或未出现在输入数据中,跳过 - memcpy(tmp_buf, in_data, in_len); + memcpy(tmp_buf, in_data, in_len); // 复制输入数据到临时缓冲区 - for (r = 0; r < in_len; r++) - if (tmp_buf[r] == i) tmp_buf[r] = '0'; + for (r = 0; r < in_len; r++) // 遍历输入数据 + if (tmp_buf[r] == i) tmp_buf[r] = '0'; // 将当前字符替换为'0' - res = run_target(argv, tmp_buf, in_len, 0); + res = run_target(argv, tmp_buf, in_len, 0); // 运行目标程序 - if (res) { + if (res) { // 如果运行结果有效 - memcpy(in_data, tmp_buf, in_len); - syms_removed++; - alpha_del1 += alpha_map[i]; - changed_any = 1; + memcpy(in_data, tmp_buf, in_len); // 将临时缓冲区的内容复制回输入数据 + syms_removed++; // 增加符号删除数量 + alpha_del1 += alpha_map[i]; // 增加字符删除数量 + changed_any = 1; // 设置变化标志 } } - alpha_d_total += alpha_del1; + alpha_d_total += alpha_del1; // 更新总字符删除数量 - OKF("Symbol minimization finished, %u symbol%s (%u byte%s) replaced.", + OKF("Symbol minimization finished, %u symbol%s (%u byte%s) replaced.", // 输出符号最小化完成信息 syms_removed, syms_removed == 1 ? "" : "s", alpha_del1, alpha_del1 == 1 ? "" : "s"); @@ -609,41 +591,41 @@ next_del_blksize: * CHARACTER MINIMIZATION * **************************/ - alpha_del2 = 0; + alpha_del2 = 0; // 初始化字符删除数量 - ACTF(cBRI "Stage #3: " cRST "Character minimization..."); + ACTF(cBRI "Stage #3: " cRST "Character minimization..."); // 输出阶段信息 - memcpy(tmp_buf, in_data, in_len); + memcpy(tmp_buf, in_data, in_len); // 复制输入数据到临时缓冲区 - for (i = 0; i < in_len; i++) { + for (i = 0; i < in_len; i++) { // 遍历输入数据 - u8 res, orig = tmp_buf[i]; + u8 res, orig = tmp_buf[i]; // 用于存储运行目标程序的结果和原始字符 - if (orig == '0') continue; - tmp_buf[i] = '0'; + if (orig == '0') continue; // 如果当前字符是'0',跳过 + tmp_buf[i] = '0'; // 将当前字符替换为'0' - res = run_target(argv, tmp_buf, in_len, 0); + res = run_target(argv, tmp_buf, in_len, 0); // 运行目标程序 - if (res) { + if (res) { // 如果运行结果有效 - in_data[i] = '0'; - alpha_del2++; - changed_any = 1; + in_data[i] = '0'; // 将输入数据中的当前字符替换为'0' + alpha_del2++; // 增加字符删除数量 + changed_any = 1; // 设置变化标志 - } else tmp_buf[i] = orig; + } else tmp_buf[i] = orig; // 如果运行结果无效,恢复原始字符 } - alpha_d_total += alpha_del2; + alpha_d_total += alpha_del2; // 更新总字符删除数量 - OKF("Character minimization done, %u byte%s replaced.", + OKF("Character minimization done, %u byte%s replaced.", // 输出字符最小化完成信息 alpha_del2, alpha_del2 == 1 ? "" : "s"); - if (changed_any) goto next_pass; + if (changed_any) goto next_pass; // 如果有变化,跳转到下一个阶段 finalize_all: - SAYF("\n" + SAYF("\n" // 输出最终结果 cGRA " File size reduced by : " cRST "%0.02f%% (to %u byte%s)\n" cGRA " Characters simplified : " cRST "%0.02f%%\n" cGRA " Number of execs done : " cRST "%u\n" @@ -653,164 +635,142 @@ finalize_all: total_execs, missed_paths, missed_crashes, missed_hangs ? cLRD : "", missed_hangs); - if (total_execs > 50 && missed_hangs * 10 > total_execs) - WARNF(cLRD "Frequent timeouts - results may be skewed." cRST); + if (total_execs > 50 && missed_hangs * 10 > total_execs) // 如果超时次数过多 + WARNF(cLRD "Frequent timeouts - results may be skewed." cRST); // 输出警告信息 } - - -/* Handle Ctrl-C and the like. */ - +/* 处理Ctrl-C等信号 */ static void handle_stop_sig(int sig) { - stop_soon = 1; + stop_soon = 1; // 设置停止标志 - if (child_pid > 0) kill(child_pid, SIGKILL); + if (child_pid > 0) kill(child_pid, SIGKILL); // 如果子进程存在,发送SIGKILL信号终止子进程 } - -/* Do basic preparations - persistent fds, filenames, etc. */ - +/* 进行基本的准备工作 - 持久性文件描述符,文件名等 */ static void set_up_environment(void) { - u8* x; + u8* x; // 临时变量 - dev_null_fd = open("/dev/null", O_RDWR); - if (dev_null_fd < 0) PFATAL("Unable to open /dev/null"); + dev_null_fd = open("/dev/null", O_RDWR); // 打开/dev/null文件 + if (dev_null_fd < 0) PFATAL("Unable to open /dev/null"); // 如果打开失败,输出错误信息并退出 - if (!prog_in) { + if (!prog_in) { // 如果临时文件路径未设置 - u8* use_dir = "."; + u8* use_dir = "."; // 默认使用当前目录 - if (access(use_dir, R_OK | W_OK | X_OK)) { + if (access(use_dir, R_OK | W_OK | X_OK)) { // 检查当前目录是否可读写 - use_dir = getenv("TMPDIR"); - if (!use_dir) use_dir = "/tmp"; + use_dir = getenv("TMPDIR"); // 尝试获取环境变量TMPDIR + if (!use_dir) use_dir = "/tmp"; // 如果未设置,使用/tmp目录 } - prog_in = alloc_printf("%s/.afl-tmin-temp-%u", use_dir, getpid()); + prog_in = alloc_printf("%s/.afl-tmin-temp-%u", use_dir, getpid()); // 生成临时文件路径 } - /* Set sane defaults... */ - x = getenv("ASAN_OPTIONS"); + /* 设置合理的默认值... */ + x = getenv("ASAN_OPTIONS"); // 获取环境变量ASAN_OPTIONS的值 - if (x) { - - if (!strstr(x, "abort_on_error=1")) - FATAL("Custom ASAN_OPTIONS set without abort_on_error=1 - please fix!"); - - if (!strstr(x, "symbolize=0")) - FATAL("Custom ASAN_OPTIONS set without symbolize=0 - please fix!"); + if (x) { // 如果ASAN_OPTIONS被设置 + if (!strstr(x, "abort_on_error=1")) // 检查是否包含abort_on_error=1 + FATAL("Custom ASAN_OPTIONS set without abort_on_error=1 - please fix!"); // 如果没有设置,输出错误信息并退出 + if (!strstr(x, "symbolize=0")) // 检查是否包含symbolize=0 + FATAL("Custom ASAN_OPTIONS set without symbolize=0 - please fix!"); // 如果没有设置,输出错误信息并退出 } - x = getenv("MSAN_OPTIONS"); - - if (x) { + x = getenv("MSAN_OPTIONS"); // 获取环境变量MSAN_OPTIONS的值 - if (!strstr(x, "exit_code=" STRINGIFY(MSAN_ERROR))) - FATAL("Custom MSAN_OPTIONS set without exit_code=" - STRINGIFY(MSAN_ERROR) " - please fix!"); - - if (!strstr(x, "symbolize=0")) - FATAL("Custom MSAN_OPTIONS set without symbolize=0 - please fix!"); + if (x) { // 如果MSAN_OPTIONS被设置 + if (!strstr(x, "exit_code=" STRINGIFY(MSAN_ERROR))) // 检查是否包含正确的exit_code + FATAL("Custom MSAN_OPTIONS set without exit_code=" STRINGIFY(MSAN_ERROR) " - please fix!"); // 如果没有设置,输出错误信息并退出 + if (!strstr(x, "symbolize=0")) // 检查是否包含symbolize=0 + FATAL("Custom MSAN_OPTIONS set without symbolize=0 - please fix!"); // 如果没有设置,输出错误信息并退出 } - setenv("ASAN_OPTIONS", "abort_on_error=1:" - "detect_leaks=0:" - "symbolize=0:" - "allocator_may_return_null=1", 0); - - setenv("MSAN_OPTIONS", "exit_code=" STRINGIFY(MSAN_ERROR) ":" - "symbolize=0:" - "abort_on_error=1:" - "allocator_may_return_null=1:" - "msan_track_origins=0", 0); - - if (getenv("AFL_PRELOAD")) { - setenv("LD_PRELOAD", getenv("AFL_PRELOAD"), 1); - setenv("DYLD_INSERT_LIBRARIES", getenv("AFL_PRELOAD"), 1); + // 设置ASAN和MSAN的默认选项 + setenv("ASAN_OPTIONS", "abort_on_error=1:" // 设置ASAN选项:遇到错误时终止程序 + "detect_leaks=0:" // 禁用内存泄漏检测 + "symbolize=0:" // 禁止符号化堆栈跟踪 + "allocator_may_return_null=1", 0); // 允许分配器返回NULL + + setenv("MSAN_OPTIONS", "exit_code=" STRINGIFY(MSAN_ERROR) ":" // 设置MSAN选项:指定退出代码 + "symbolize=0:" // 禁止符号化堆栈跟踪 + "abort_on_error=1:" // 遇到错误时终止程序 + "allocator_may_return_null=1:" // 允许分配器返回NULL + "msan_track_origins=0", 0); // 禁用原始数据跟踪 + + if (getenv("AFL_PRELOAD")) { // 如果设置了AFL_PRELOAD环境变量 + setenv("LD_PRELOAD", getenv("AFL_PRELOAD"), 1); // 设置LD_PRELOAD环境变量用于动态链接 + setenv("DYLD_INSERT_LIBRARIES", getenv("AFL_PRELOAD"), 1); // 对于macOS,设置DYLD_INSERT_LIBRARIES用于动态链接 } } - -/* Setup signal handlers, duh. */ - +/* 设置信号处理程序 */ static void setup_signal_handlers(void) { - struct sigaction sa; - - sa.sa_handler = NULL; - sa.sa_flags = SA_RESTART; - sa.sa_sigaction = NULL; + struct sigaction sa; // 定义信号操作结构体 - sigemptyset(&sa.sa_mask); + sa.sa_handler = NULL; // 初始化处理程序为NULL + sa.sa_flags = SA_RESTART; // 设置标志以重新启动被信号中断的系统调用 + sa.sa_sigaction = NULL; // 使用默认的信号处理程序 - /* Various ways of saying "stop". */ + sigemptyset(&sa.sa_mask); // 初始化信号屏蔽字 - sa.sa_handler = handle_stop_sig; - sigaction(SIGHUP, &sa, NULL); - sigaction(SIGINT, &sa, NULL); - sigaction(SIGTERM, &sa, NULL); + /* 各种方式表示“停止”。 */ + sa.sa_handler = handle_stop_sig; // 将处理程序设置为自定义的停止信号处理器 + sigaction(SIGHUP, &sa, NULL); // 注册SIGHUP信号 + sigaction(SIGINT, &sa, NULL); // 注册SIGINT信号 + sigaction(SIGTERM, &sa, NULL); // 注册SIGTERM信号 - /* Exec timeout notifications. */ - - sa.sa_handler = handle_timeout; - sigaction(SIGALRM, &sa, NULL); + /* 执行超时通知。 */ + sa.sa_handler = handle_timeout; // 将处理程序设置为自定义的超时信号处理器 + sigaction(SIGALRM, &sa, NULL); // 注册SIGALRM信号 } - -/* Detect @@ in args. */ - +/* 检测参数中的@@ */ static void detect_file_args(char** argv) { - u32 i = 0; - u8* cwd = getcwd(NULL, 0); + u32 i = 0; // 初始化参数索引 + u8* cwd = getcwd(NULL, 0); // 获取当前工作目录 - if (!cwd) PFATAL("getcwd() failed"); + if (!cwd) PFATAL("getcwd() failed"); // 如果获取当前工作目录失败,输出错误信息并退出 - while (argv[i]) { + while (argv[i]) { // 遍历参数列表 + u8* aa_loc = strstr(argv[i], "@@"); // 查找参数中是否存在@@ - u8* aa_loc = strstr(argv[i], "@@"); + if (aa_loc) { // 如果找到@@ + u8 *aa_subst, *n_arg; // 临时变量 - if (aa_loc) { + /* 确保总是使用全路径。 */ + if (prog_in[0] == '/') aa_subst = prog_in; // 如果prog_in是绝对路径 + else aa_subst = alloc_printf("%s/%s", cwd, prog_in); // 否则生成完整路径 - u8 *aa_subst, *n_arg; + /* 构造替换的argv值 */ + *aa_loc = 0; // 将@@进行分割 + n_arg = alloc_printf("%s%s%s", argv[i], aa_subst, aa_loc + 2); // 生成替换后的参数 + argv[i] = n_arg; // 更新参数列表 + *aa_loc = '@'; // 还原@@ - /* Be sure that we're always using fully-qualified paths. */ - - if (prog_in[0] == '/') aa_subst = prog_in; - else aa_subst = alloc_printf("%s/%s", cwd, prog_in); - - /* Construct a replacement argv value. */ - - *aa_loc = 0; - n_arg = alloc_printf("%s%s%s", argv[i], aa_subst, aa_loc + 2); - argv[i] = n_arg; - *aa_loc = '@'; - - if (prog_in[0] != '/') ck_free(aa_subst); + if (prog_in[0] != '/') ck_free(aa_subst); // 释放临时变量内存(如果需要) } - i++; + i++; // 继续处理下一个参数 } - free(cwd); /* not tracked */ - -} - + free(cwd); /* not tracked */ // 释放当前工作目录字符串,不跟踪内存 -/* Display usage hints. */ +/* 显示使用提示 */ static void usage(u8* argv0) { @@ -845,329 +805,303 @@ static void usage(u8* argv0) { } - -/* Find binary. */ +/* 查找二进制文件 */ static void find_binary(u8* fname) { - u8* env_path = 0; - struct stat st; + u8* env_path = 0; // 用于存储环境变量PATH的值 + struct stat st; // 用于获取文件状态信息 + // 如果文件名包含斜杠或环境变量PATH未设置 if (strchr(fname, '/') || !(env_path = getenv("PATH"))) { - target_path = ck_strdup(fname); + target_path = ck_strdup(fname); // 直接将文件名复制到目标路径 - 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); + // 检查目标路径是否为一个可执行文件 + if (stat(target_path, &st) || !S_ISREG(st.st_mode) || // 如果文件不存在或不是常规文件 + !(st.st_mode & 0111) || st.st_size < 4) // 如果文件不可执行或文件大小小于4字节 + FATAL("Program '%s' not found or not executable", fname); // 输出错误信息并退出 - } else { + } else { // 如果文件名不包含斜杠且环境变量PATH已设置 - while (env_path) { + while (env_path) { // 遍历环境变量PATH中的每个路径 - u8 *cur_elem, *delim = strchr(env_path, ':'); + u8 *cur_elem, *delim = strchr(env_path, ':'); // 查找下一个路径分隔符 + // 如果找到分隔符 if (delim) { + cur_elem = ck_alloc(delim - env_path + 1); // 分配当前路径的内存 + memcpy(cur_elem, env_path, delim - env_path); // 复制当前路径 + delim++; // 移动到下一个路径的开始位置 - cur_elem = ck_alloc(delim - env_path + 1); - memcpy(cur_elem, env_path, delim - env_path); - delim++; + } else cur_elem = ck_strdup(env_path); // 没有找到分隔符,复制剩余的路径 - } else cur_elem = ck_strdup(env_path); - - env_path = delim; + env_path = delim; // 更新环境变量路径 + // 构建目标路径 if (cur_elem[0]) - target_path = alloc_printf("%s/%s", cur_elem, fname); + target_path = alloc_printf("%s/%s", cur_elem, fname); // 拼接当前路径和文件名 else - target_path = ck_strdup(fname); + target_path = ck_strdup(fname); // 如果当前路径为空,直接使用文件名 - ck_free(cur_elem); + ck_free(cur_elem); // 释放临时路径内存 - if (!stat(target_path, &st) && S_ISREG(st.st_mode) && - (st.st_mode & 0111) && st.st_size >= 4) break; + // 检查构建的目标路径是否存在且可执行 + if (!stat(target_path, &st) && S_ISREG(st.st_mode) && // 如果目标路径存在且是常规文件 + (st.st_mode & 0111) && st.st_size >= 4) break; // 以及可执行且文件大小大于等于4字节 - ck_free(target_path); - target_path = 0; + ck_free(target_path); // 如果未找到可执行文件,释放目标路径内存 + target_path = 0; // 重置目标路径 } - if (!target_path) FATAL("Program '%s' not found or not executable", fname); + // 如果没有找到可执行的程序 + 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) { - char** new_argv = ck_alloc(sizeof(char*) * (argc + 4)); - u8 *tmp, *cp, *rsl, *own_copy; + char** new_argv = ck_alloc(sizeof(char*) * (argc + 4)); // 分配内存以存储新的argv,包括额外的参数 + u8 *tmp, *cp, *rsl, *own_copy; // 临时变量 - /* Workaround for a QEMU stability glitch. */ - - setenv("QEMU_LOG", "nochain", 1); + // 为QEMU稳定性问题的解决提供了一个临时的解决方案。 + setenv("QEMU_LOG", "nochain", 1); // 禁用链式日志 + // 将原始argv参数复制到新的argv中 memcpy(new_argv + 3, argv + 1, sizeof(char*) * argc); - /* Now we need to actually find qemu for argv[0]. */ - - new_argv[2] = target_path; - new_argv[1] = "--"; + // 现在我们需要实际地找到qemu来运行argv[0] + new_argv[2] = target_path; // 设置新argv的第二个元素为目标路径 + new_argv[1] = "--"; // 将" -- "添加到argv中 + // 尝试从环境变量AFL_PATH获取QEMU的路径 tmp = getenv("AFL_PATH"); - if (tmp) { - - cp = alloc_printf("%s/afl-qemu-trace", tmp); + if (tmp) { // 如果AFL_PATH环境变量被设置 + cp = alloc_printf("%s/afl-qemu-trace", tmp); // 构造QEMU的路径 - if (access(cp, X_OK)) - FATAL("Unable to find '%s'", tmp); + if (access(cp, X_OK)) // 检查该路径是否可执行 + FATAL("Unable to find '%s'", tmp); // 如果不可执行,输出错误信息并退出 - target_path = new_argv[0] = cp; - return new_argv; + target_path = new_argv[0] = cp; // 将目标路径设置为新argv的第一个元素 + return new_argv; // 返回新的argv } - own_copy = ck_strdup(own_loc); - rsl = strrchr(own_copy, '/'); - - if (rsl) { - - *rsl = 0; - - cp = alloc_printf("%s/afl-qemu-trace", own_copy); - ck_free(own_copy); + // 处理own_loc来查找qemu + own_copy = ck_strdup(own_loc); // 复制own_loc字符串 + rsl = strrchr(own_copy, '/'); // 获取最后一个斜杠的位置 - if (!access(cp, X_OK)) { + if (rsl) { // 如果找到斜杠 + *rsl = 0; // 将其替换为字符串结束符 - target_path = new_argv[0] = cp; - return new_argv; + cp = alloc_printf("%s/afl-qemu-trace", own_copy); // 构造QEMU路径 + ck_free(own_copy); // 释放复制的字符串内存 + if (!access(cp, X_OK)) { // 如果路径可执行 + target_path = new_argv[0] = cp; // 设置目标路径 + return new_argv; // 返回新的argv } - } else ck_free(own_copy); - - if (!access(BIN_PATH "/afl-qemu-trace", X_OK)) { - - target_path = new_argv[0] = BIN_PATH "/afl-qemu-trace"; - return new_argv; + } else ck_free(own_copy); // 如果未找到斜杠,释放复制的字符串内存 + // 检查QEMU的默认路径 + if (!access(BIN_PATH "/afl-qemu-trace", X_OK)) { // 检查是否能够访问默认路径 + target_path = new_argv[0] = BIN_PATH "/afl-qemu-trace"; // 设置目标路径 + return new_argv; // 返回新的argv } + // 如果找不到QEMU的路径,输出错误信息并退出 FATAL("Unable to find 'afl-qemu-trace'."); } -/* Read mask bitmap from file. This is for the -B option. */ +/* 从文件读取掩码位图。这是为-B选项服务的 */ static void read_bitmap(u8* fname) { - s32 fd = open(fname, O_RDONLY); + s32 fd = open(fname, O_RDONLY); // 以只读模式打开位图文件 - if (fd < 0) PFATAL("Unable to open '%s'", fname); + if (fd < 0) PFATAL("Unable to open '%s'", fname); // 如果打开失败,输出错误信息并退出 - ck_read(fd, mask_bitmap, MAP_SIZE, fname); + ck_read(fd, mask_bitmap, MAP_SIZE, fname); // 读取位图数据到mask_bitmap数组中,确保读取的字节数与MAP_SIZE相等 - close(fd); + close(fd); // 关闭文件描述符 } - - -/* Main entry point */ - +/* 主入口点 */ int main(int argc, char** argv) { - s32 opt; - u8 mem_limit_given = 0, timeout_given = 0, qemu_mode = 0; - char** use_argv; - - doc_path = access(DOC_PATH, F_OK) ? "docs" : DOC_PATH; + s32 opt; // 用于存储命令行选项的变量 + u8 mem_limit_given = 0, timeout_given = 0, qemu_mode = 0; // 初始化选项标志 + char** use_argv; // 用于存储可变参数的数组 - SAYF(cCYA "afl-tmin " cBRI VERSION cRST " by \n"); + doc_path = access(DOC_PATH, F_OK) ? "docs" : DOC_PATH; // 检查文档路径是否存在,如果不存在,则使用默认文档路径 - while ((opt = getopt(argc,argv,"+i:o:f:m:t:B:xeQV")) > 0) + SAYF(cCYA "afl-tmin " cBRI VERSION cRST " by \n"); // 输出程序的版本信息 + while ((opt = getopt(argc, argv, "+i:o:f:m:t:B:xeQV")) > 0) // 解析命令行选项 switch (opt) { - - case 'i': - - if (in_file) FATAL("Multiple -i options not supported"); - in_file = optarg; + case 'i': // 输入文件选项 + if (in_file) FATAL("Multiple -i options not supported"); // 如果已经设置了输入文件,输出错误信息 + in_file = optarg; // 保存输入文件路径 break; - case 'o': - - if (out_file) FATAL("Multiple -o options not supported"); - out_file = optarg; + case 'o': // 输出文件选项 + if (out_file) FATAL("Multiple -o options not supported"); // 如果已经设置了输出文件,输出错误信息 + out_file = optarg; // 保存输出文件路径 break; - case 'f': - - if (prog_in) FATAL("Multiple -f options not supported"); - use_stdin = 0; - prog_in = optarg; + case 'f': // 处理程序输入选项 + if (prog_in) FATAL("Multiple -f options not supported"); // 如果已经设置了程序输入,输出错误信息 + use_stdin = 0; // 设置不使用标准输入 + prog_in = optarg; // 保存程序输入路径 break; - case 'e': - - if (edges_only) FATAL("Multiple -e options not supported"); - edges_only = 1; + case 'e': // 仅处理边缘选项 + if (edges_only) FATAL("Multiple -e options not supported"); // 如果已经启用边缘处理,输出错误信息 + edges_only = 1; // 启用边缘处理 break; - case 'x': - - if (exit_crash) FATAL("Multiple -x options not supported"); - exit_crash = 1; + case 'x': // 退出崩溃选项 + if (exit_crash) FATAL("Multiple -x options not supported"); // 如果已经启用崩溃退出,输出错误信息 + exit_crash = 1; // 启用崩溃退出 break; - case 'm': { - - u8 suffix = 'M'; + case 'm': { // 内存限制选项 - if (mem_limit_given) FATAL("Multiple -m options not supported"); - mem_limit_given = 1; + u8 suffix = 'M'; // 默认单位是MB - if (!strcmp(optarg, "none")) { + if (mem_limit_given) FATAL("Multiple -m options not supported"); // 如果已经设置了内存限制,输出错误信息 + mem_limit_given = 1; // 设置内存限制标志 - mem_limit = 0; + if (!strcmp(optarg, "none")) { // 如果选项值为"none" + mem_limit = 0; // 不设置内存限制 break; - } - if (sscanf(optarg, "%llu%c", &mem_limit, &suffix) < 1 || - optarg[0] == '-') FATAL("Bad syntax used for -m"); + 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"); - + case 'T': mem_limit *= 1024 * 1024; break; // TB转为MB + case 'G': mem_limit *= 1024; break; // GB转为MB + case 'k': mem_limit /= 1024; break; // kB转为MB + case 'M': break; // 如果是MB,不需要更改 + default: FATAL("Unsupported suffix or bad syntax for -m"); // 如果单位不支持,输出错误信息 } - if (mem_limit < 5) FATAL("Dangerously low value of -m"); + // 检查内存限制值的合法范围 + if (mem_limit < 5) FATAL("Dangerously low value of -m"); // 如果小于5MB,输出错误信息 + // 检查32位系统的内存限制 if (sizeof(rlim_t) == 4 && mem_limit > 2000) - FATAL("Value of -m out of range on 32-bit systems"); + 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; + case 't': // 超时选项 + if (timeout_given) FATAL("Multiple -t options not supported"); // 如果已经设置了超时,输出错误信息 + timeout_given = 1; // 设置超时标志 - exec_tmout = atoi(optarg); + exec_tmout = atoi(optarg); // 将选项值转换为整数并设置超时 - if (exec_tmout < 10 || optarg[0] == '-') - FATAL("Dangerously low value of -t"); + if (exec_tmout < 10 || optarg[0] == '-') // 检查超时值是否合法 + FATAL("Dangerously low value of -t"); // 如果小于10,输出错误信息 break; - case 'Q': - - if (qemu_mode) FATAL("Multiple -Q options not supported"); - if (!mem_limit_given) mem_limit = MEM_LIMIT_QEMU; + case 'Q': // QEMU模式选项 + if (qemu_mode) FATAL("Multiple -Q options not supported"); // 如果已经启用QEMU模式,输出错误信息 + if (!mem_limit_given) mem_limit = MEM_LIMIT_QEMU; // 如果未设置内存限制,则默认使用QEMU内存限制 - qemu_mode = 1; + qemu_mode = 1; // 启用QEMU模式 break; - case 'B': /* load bitmap */ + case 'B': { /* load bitmap */ + /* 这是一个秘密的未公开选项!如果有一个基线“无聊”的输入文件和另一个“有趣”的文件你想最小化, + 这个选项可能是有用的。 - /* This is a secret undocumented option! It is speculated to be useful - if you have a baseline "boring" input file and another "interesting" - file you want to minimize. + 你可以使用afl-showmap -b来为无聊的文件转储二进制位图, + 然后通过-B加载到afl-tmin中。最小化器将只保留有趣输入文件中独特的边, + 但忽略来自原始映射的所有内容。 - You can dump a binary bitmap for the boring file using - afl-showmap -b, and then load it into afl-tmin via -B. The minimizer - will then minimize to preserve only the edges that are unique to - the interesting input file, but ignoring everything from the - original map. + 如果这个选项被证明是有用,可能会被扩展并正式记录。 */ - The option may be extended and made more official if it proves - to be useful. */ + if (mask_bitmap) FATAL("Multiple -B options not supported"); // 如果已经设置了掩码位图,输出错误信息 + mask_bitmap = ck_alloc(MAP_SIZE); // 分配掩码位图内存 + read_bitmap(optarg); // 读取位图文件 - if (mask_bitmap) FATAL("Multiple -B options not supported"); - mask_bitmap = ck_alloc(MAP_SIZE); - read_bitmap(optarg); break; + } case 'V': /* Show version number */ - - /* Version number has been printed already, just quit. */ - exit(0); + /* 版本号已经打印过了,直接退出 */ + exit(0); // 退出程序 default: - - usage(argv[0]); + usage(argv[0]); // 如果遇到未知选项,显示用法信息 } - if (optind == argc || !in_file || !out_file) usage(argv[0]); +} - setup_shm(); - setup_signal_handlers(); + if (optind == argc || !in_file || !out_file) usage(argv[0]); // 检查命令行参数,如果缺少输入文件或输出文件,则显示用法信息并退出 - set_up_environment(); + setup_shm(); // 设置共享内存,用于进程间通信 + setup_signal_handlers(); // 设置信号处理程序,以处理如Ctrl-C等中断信号 - find_binary(argv[optind]); - detect_file_args(argv + optind); + set_up_environment(); // 进行环境准备,设置临时文件描述符等 - if (qemu_mode) - use_argv = get_qemu_argv(argv[0], argv + optind, argc - optind); - else - use_argv = argv + optind; + find_binary(argv[optind]); // 查找并验证要执行的目标程序的路径 + detect_file_args(argv + optind); // 检测命令行参数中的“@@”并进行替换 - exact_mode = !!getenv("AFL_TMIN_EXACT"); + if (qemu_mode) // 如果处于QEMU模式 + use_argv = get_qemu_argv(argv[0], argv + optind, argc - optind); // 获取QEMU模式下的参数 + else + use_argv = argv + optind; // 在非QEMU模式下使用原始参数 - SAYF("\n"); + exact_mode = !!getenv("AFL_TMIN_EXACT"); // 检查环境变量以确定是否启用精确模式 - read_initial_file(); + SAYF("\n"); // 输出换行 + + read_initial_file(); // 读取初始输入文件内容 - ACTF("Performing dry run (mem limit = %llu MB, timeout = %u ms%s)...", + ACTF("Performing dry run (mem limit = %llu MB, timeout = %u ms%s)...", // 输出干运行信息 mem_limit, exec_tmout, edges_only ? ", edges only" : ""); - run_target(use_argv, in_data, in_len, 1); - - if (child_timed_out) - FATAL("Target binary times out (adjusting -t may help)."); + run_target(use_argv, in_data, in_len, 1); // 执行目标程序,将初始数据传递给它 - if (!crash_mode) { + if (child_timed_out) // 检查是否子进程超时 + FATAL("Target binary times out (adjusting -t may help)."); // 如果超时,输出错误信息并退出 + if (!crash_mode) { // 如果不是崩溃模式 OKF("Program terminates normally, minimizing in " - cCYA "instrumented" cRST " mode."); - - if (!anything_set()) FATAL("No instrumentation detected."); - - } else { - + cCYA "instrumented" cRST " mode."); // 输出程序正常结束信息,进入仪器模式 + if (!anything_set()) FATAL("No instrumentation detected."); // 检查是否有仪器信息,如果没有则退出 + } else { // 如果处于崩溃模式 OKF("Program exits with a signal, minimizing in " cMGN "%scrash" cRST - " mode.", exact_mode ? "EXACT " : ""); - + " mode.", exact_mode ? "EXACT " : ""); // 输出程序因信号退出的信息 } - minimize(use_argv); + minimize(use_argv); // 进入最小化阶段,执行最小化算法 - ACTF("Writing output to '%s'...", out_file); + ACTF("Writing output to '%s'...", out_file); // 输出正在写入输出文件的信息 - unlink(prog_in); - prog_in = NULL; + unlink(prog_in); // 删除临时输入文件 + prog_in = NULL; // 重置临时输入文件路径 - close(write_to_file(out_file, in_data, in_len)); + close(write_to_file(out_file, in_data, in_len)); // 将最小化后的数据写入输出文件并关闭文件描述符 - OKF("We're done here. Have a nice day!\n"); - - exit(0); + OKF("We're done here. Have a nice day!\n"); // 输出完成信息 + exit(0); // 正常退出程序 } - diff --git a/src/alloc-inl.h b/src/alloc-inl.h index 9a68126..58f52cc 100644 --- a/src/alloc-inl.h +++ b/src/alloc-inl.h @@ -1,346 +1,776 @@ /* - 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 - error-checking, memory-zeroing alloc routines + American Fuzzy Lop - 错误检查、内存清零分配例程 ------------------------------------------------------------------ - Written and maintained by Michal Zalewski + 作者和维护者:Michal Zalewski - This allocator is not designed to resist malicious attackers (the canaries - are small and predictable), but provides a robust and portable way to detect - use-after-free, off-by-one writes, stale pointers, and so on. + 此分配器并未设计为抵抗恶意攻击者(由于哨兵小且可预测), + 但提供了一种强健且可移植的方法来检测 + 使用后释放、越界写入、过时指针等问题。 +*/ + +#ifndef _HAVE_ALLOC_INL_H // 如果没有定义 _HAVE_ALLOC_INL_H +#define _HAVE_ALLOC_INL_H // 定义 _HAVE_ALLOC_INL_H + +#include // 包含标准输入输出库 +#include // 包含标准库 +#include // 包含字符串处理库 + +#include "config.h" // 包含自定义配置文件 +#include "types.h" // 包含自定义类型定义 +#include "debug.h" // 包含调试相关的定义 + +/* 用户接口宏,将 sprintf() 输出到动态分配的缓冲区。 */ + +#define alloc_printf(_str...) ({ // 定义宏 alloc_printf + u8* _tmp; // 定义一个临时指针变量 + s32 _len = snprintf(NULL, 0, _str); // 计算格式化字符串的长度 + if (_len < 0) FATAL("Whoa, snprintf() fails?!"); // 检查 snprintf 是否成功 + _tmp = ck_alloc(_len + 1); // 动态分配内存,包括结束符 + snprintf((char*)_tmp, _len + 1, _str); // 格式化字符串并存储到分配的内存中 + _tmp; // 返回分配的指针 + }) + +/* 宏来强制分配限制作为防止整数溢出的最后防线。 */ + +#define ALLOC_CHECK_SIZE(_s) do { // 定义宏检查分配大小 + if ((_s) > MAX_ALLOC) // 如果请求的大小超过最大分配限制 + ABORT("Bad alloc request: %u bytes", (_s)); // 抛出异常 + } while (0) + +/* 宏来检查 malloc() 失败等情况。 */ + +#define ALLOC_CHECK_RESULT(_r, _s) do { // 定义宏检查分配结果 + if (!(_r)) // 如果分配结果为空 + ABORT("Out of memory: can't allocate %u bytes", (_s)); // 抛出异常 + } while (0) + +/* 用于标记使用/释放块的魔法令牌。 */ + +#define ALLOC_MAGIC_C1 0xFF00FF00 /* 使用头部(双字) */ +#define ALLOC_MAGIC_F 0xFE00FE00 /* 释放头部(双字) */ +#define ALLOC_MAGIC_C2 0xF0 /* 使用尾部(字节) */ + +/* 哨兵标记相对于用户可见指针的位置。 */ + +#define ALLOC_C1(_ptr) (((u32*)(_ptr))[-2]) // 获取头部的魔法值 +#define ALLOC_S(_ptr) (((u32*)(_ptr))[-1]) // 获取当前块的大小 +#define ALLOC_C2(_ptr) (((u8*)(_ptr))[ALLOC_S(_ptr)]) // 获取尾部的魔法值 + +#define ALLOC_OFF_HEAD 8 // 头部偏移量 +#define ALLOC_OFF_TOTAL (ALLOC_OFF_HEAD + 1) // 总偏移量 + +/* 分配器增量,用于 ck_realloc_block()。 */ + +#define ALLOC_BLK_INC 256 // 分配块的增量大小 + +/* 指针的完整性检查宏。 */ + +#define CHECK_PTR(_p) do { // 定义指针检查宏 + if (_p) { // 如果指针不为空 + if (ALLOC_C1(_p) ^ ALLOC_MAGIC_C1) { // 检查头部的魔法值是否正确 + if (ALLOC_C1(_p) == ALLOC_MAGIC_F) // 如果是释放过的指针 + ABORT("Use after free."); // 抛出异常 + else ABORT("Corrupted head alloc canary."); // 抛出异常 + } + if (ALLOC_C2(_p) ^ ALLOC_MAGIC_C2) // 检查尾部的魔法值是否正确 + ABORT("Corrupted tail alloc canary."); // 抛出异常 + } + } while (0) + +#define CHECK_PTR_EXPR(_p) ({ // 定义检查指针的表达式宏 + typeof (_p) _tmp = (_p); // 保存指针值 + CHECK_PTR(_tmp); // 检查指针 + _tmp; // 返回指针值 + }) + +/* 分配一个缓冲区,显式不清零。对零大小请求返回 NULL。 */ + +static inline void* DFL_ck_alloc_nozero(u32 size) { // 定义内联函数 + + void* ret; // 定义返回指针 + + if (!size) return NULL; // 如果请求大小为零,则返回 NULL + + ALLOC_CHECK_SIZE(size); // 检查请求的大小 + ret = malloc(size + ALLOC_OFF_TOTAL); // 分配内存 + ALLOC_CHECK_RESULT(ret, size); // 检查分配结果 + + ret += ALLOC_OFF_HEAD; // 调整指针,跳过头部 + + ALLOC_C1(ret) = ALLOC_MAGIC_C1; // 设置头部魔法值 + ALLOC_S(ret) = size; // 设置当前块的大小 + ALLOC_C2(ret) = ALLOC_MAGIC_C2; // 设置尾部魔法值 + + return ret; // 返回分配的指针 +} + + +/* 分配一个缓冲区,返回清零的内存。 */ + +static inline void* DFL_ck_alloc(u32 size) { // 定义内联函数 + + void* mem; // 定义返回指针 + + if (!size) return NULL; // 如果请求大小为零,则返回 NULL + mem = DFL_ck_alloc_nozero(size); // 调用不清零的分配函数 + + return memset(mem, 0, size); // 将分配的内存清零并返回指针 +} + + +/* 释放内存,检查重复释放和堆损坏。当 DEBUG_BUILD 被定义时, + 旧内存也会用 0xFF 覆盖。 */ + +static inline void DFL_ck_free(void* mem) { // 定义内联释放函数 + + if (!mem) return; // 如果指针为空,则返回 + + CHECK_PTR(mem); // 检查指针的完整性 + +#ifdef DEBUG_BUILD // 如果定义了 DEBUG_BUILD + + /* 及时捕捉指针问题。 */ + memset(mem, 0xFF, ALLOC_S(mem)); // 将内存清零以检测问题 + +#endif /* DEBUG_BUILD */ + + ALLOC_C1(mem) = ALLOC_MAGIC_F; // 设置魔法值以标记为释放 + + free(mem - ALLOC_OFF_HEAD); // 实际释放内存 +} + + +/* 重新分配缓冲区,检查问题并清零任何新增尾数据。 + 在 DEBUG_BUILD 下,缓冲区总是重新分配到新地址,并且 + 旧内存用 0xFF 覆盖。 */ + +static inline void* DFL_ck_realloc(void* orig, u32 size) { // 定义内联重新分配函数 + + void* ret; // 定义返回指针 + u32 old_size = 0; // 定义旧大小变量 + + if (!size) { // 如果请求大小为零 + DFL_ck_free(orig); // 释放原内存 + return NULL; // 返回 NULL + } + + if (orig) { // 如果原指针不为空 + CHECK_PTR(orig); // 检查指针的完整性 + +#ifndef DEBUG_BUILD + ALLOC_C1(orig) = ALLOC_MAGIC_F; // 标记原指针为释放 +#endif /* !DEBUG_BUILD */ + + old_size = ALLOC_S(orig); // 获取原指针的大小 + orig -= ALLOC_OFF_HEAD; // 调整指针以获取实际内存 + + ALLOC_CHECK_SIZE(old_size); // 检查旧大小 + } + + ALLOC_CHECK_SIZE(size); // 检查新请求的大小 + +#ifndef DEBUG_BUILD + + ret = realloc(orig, size + ALLOC_OFF_TOTAL); // 进行重新分配 + ALLOC_CHECK_RESULT(ret, size); // 检查分配结果 + +#else + + /* 提早捕捉指针问题:强制重定位并确保原始缓冲区被清除。 */ + + ret = malloc(size + ALLOC_OFF_TOTAL); // 分配新的内存 + ALLOC_CHECK_RESULT(ret, size); // 检查分配结果 + + if (orig) { // 如果原指针不为空 + memcpy(ret + ALLOC_OFF_HEAD, orig + ALLOC_OFF_HEAD, MIN(size, old_size)); // 拷贝旧数据 + memset(orig + ALLOC_OFF_HEAD, 0xFF, old_size); // 清零旧数据 + + ALLOC_C1(orig + ALLOC_OFF_HEAD) = ALLOC_MAGIC_F; // 标记为释放 + + free(orig); // 释放原指针 + } + +#endif /* ^!DEBUG_BUILD */ + + ret += ALLOC_OFF_HEAD; // 调整返回指针 + + ALLOC_C1(ret) = ALLOC_MAGIC_C1; // 设置头部魔法值 + ALLOC_S(ret) = size; // 设置当前块的大小 + ALLOC_C2(ret) = ALLOC_MAGIC_C2; // 设置尾部魔法值 + + if (size > old_size) // 如果新大小大于旧大小 + memset(ret + old_size, 0, size - old_size); // 清零新增的内存 + + return ret; // 返回分配的指针 +} + + +/* 以 ALLOC_BLK_INC 增量重新分配缓冲区(用于加速 + 重复的小块重新分配而不复杂化用户代码)。 */ + +static inline void* DFL_ck_realloc_block(void* orig, u32 size) { // 定义块重新分配函数 + +#ifndef DEBUG_BUILD + + if (orig) { // 如果原指针不为空 + + CHECK_PTR(orig); // 检查指针的完整性 + + if (ALLOC_S(orig) >= size) return orig; // 如果当前大小足够,返回原指针 + + size += ALLOC_BLK_INC; // 否则增加大小 + } + +#endif /* !DEBUG_BUILD */ + + return DFL_ck_realloc(orig, size); // 调用重新分配函数 +} + + +/* 创建一个包含字符串副本的缓冲区。对 NULL 输入返回 NULL。 */ + +static inline u8* DFL_ck_strdup(u8* str) { // 定义字符串复制函数 + + void* ret; // 定义返回指针 + u32 size; // 定义大小变量 + + if (!str) return NULL; // 如果输入字符串为空,则返回 NULL + + size = strlen((char*)str) + 1; // 计算字符串大小,包括结束符 + + ALLOC_CHECK_SIZE(size); // 检查请求的大小 + ret = malloc(size + ALLOC_OFF_TOTAL); // 分配内存 + ALLOC_CHECK_RESULT(ret, size); // 检查分配结果 + + ret += ALLOC_OFF_HEAD; // 调整指针以跳过头部 + + ALLOC_C1(ret) = ALLOC_MAGIC_C1; // 设置头部魔法值 + ALLOC_S(ret) = size; // 设置当前块的大小 + ALLOC_C2(ret) = ALLOC_MAGIC_C2; // 设置尾部魔法值 + + return memcpy(ret, str, size); // 返回字符串副本 +} + + +/* 创建一个包含内存块副本的缓冲区。对零大小或 NULL 输入返回 NULL。 */ + +static inline void* DFL_ck_memdup(void* mem, u32 size) { // 定义内存复制函数 + + void* ret; // 定义返回指针 + + if (!mem || !size) return NULL; // 如果输入为空或大小为零,则返回 NULL + + ALLOC_CHECK_SIZE(size); // 检查请求的大小 + ret = malloc(size + ALLOC_OFF_TOTAL); // 分配内存 + ALLOC_CHECK_RESULT(ret, size); // 检查分配结果 + + ret += ALLOC_OFF_HEAD; // 调整指针以跳过头部 + + ALLOC_C1(ret) = ALLOC_MAGIC_C1; // 设置头部魔法值 + ALLOC_S(ret) = size; // 设置当前块的大小 + ALLOC_C2(ret) = ALLOC_MAGIC_C2; // 设置尾部魔法值 + + return memcpy(ret, mem, size); // 返回内存副本 +} + + +/* 创建一个包含文本块的缓冲区,并在末尾附加 NUL 终止符。 + 对零大小或 NULL 输入返回 NULL。 */ + +static inline u8* DFL_ck_memdup_str(u8* mem, u32 size) { // 定义文本内存复制函数 + + u8* ret; // 定义返回指针 + + if (!mem || !size) return NULL; // 如果输入为空或大小为零,则返回 NULL + + ALLOC_CHECK_SIZE(size); // 检查请求的大小 + ret = malloc(size + ALLOC_OFF_TOTAL + 1); // 分配内存,包括结束符 + ALLOC_CHECK_RESULT(ret, size); // 检查分配结果 + + ret += ALLOC_OFF_HEAD; // 调整指针以跳过头部 + + ALLOC_C1(ret) = ALLOC_MAGIC_C1; // 设置头部魔法值 + ALLOC_S(ret) = size; // 设置当前块的大小 + ALLOC_C2(ret) = ALLOC_MAGIC_C2; // 设置尾部魔法值 + + memcpy(ret, mem, size); // 拷贝内存块 + ret[size] = 0; // 添加结束符 + + return ret; // 返回文本副本 +} + + +#ifndef DEBUG_BUILD // 如果没有定义 DEBUG_BUILD + +/* 在非调试模式下,我们只需将上述函数简单别名化为用户可见名称,如 ck_alloc()。 */ + +#define ck_alloc DFL_ck_alloc // 分配内存 +#define ck_alloc_nozero DFL_ck_alloc_nozero // 分配不清零内存 +#define ck_realloc DFL_ck_realloc // 重新分配内存 +#define ck_realloc_block DFL_ck_realloc_block // 块级重新分配内存 +#define ck_strdup DFL_ck_strdup // 字符串复制 +#define ck_memdup DFL_ck_memdup // 内存块复制 +#define ck_memdup_str DFL_ck_memdup_str // 字符串内存块复制 +#define ck_free DFL_ck_free // 释放内存 + +#define alloc_report() // 释放报告宏 + +#else + +/* 在调试模式下,我们还跟踪分配以检测内存泄漏, + 处理过程多了一层间接性。 */ + +/* 分配跟踪数据结构: */ + +#define ALLOC_BUCKETS 4096 // 定义分配桶的数量 + +struct TRK_obj { // 定义跟踪对象结构 + void *ptr; // 指针 + char *file, *func; // 文件名和函数名 + u32 line; // 行号 +}; + +#ifdef AFL_MAIN // 如果是主程序 + +struct TRK_obj* TRK[ALLOC_BUCKETS]; // 指针数组,用于跟踪对象 +u32 TRK_cnt[ALLOC_BUCKETS]; // 计数数组 + +# define alloc_report() TRK_report() // 定义分配报告宏 + +#else + +extern struct TRK_obj* TRK[ALLOC_BUCKETS]; // 声明外部跟踪对象 +extern u32 TRK_cnt[ALLOC_BUCKETS]; // 声明外部计数数组 + +# define alloc_report() // 定义外部报告宏 + +#endif /* ^AFL_MAIN */ + +/* 指定位于给定指针的桶位置: */ + +#define TRKH(_ptr) (((((u32)(_ptr)) >> 16) ^ ((u32)(_ptr))) % ALLOC_BUCKETS) // 计算桶索引 + +/* 向已分配对象列表添加新条目。 */ + +static inline void TRK_alloc_buf(void* ptr, const char* file, const char* func,u32 line) { // 定义分配跟踪函数 + + u32 i, bucket; // 定义桶索引和循环变量 + + if (!ptr) return; // 如果指针为空则返回 + + bucket = TRKH(ptr); // 获取桶索引 + + /* 在该桶的条目列表中查找空闲插槽。 */ + + for (i = 0; i < TRK_cnt[bucket]; i++) // 遍历桶中的计数 + if (!TRK[bucket][i].ptr) { // 如果找到空闲插槽 + TRK[bucket][i].ptr = ptr; // 保存指针 + TRK[bucket][i].file = (char*)file; // 保存文件名 + TRK[bucket][i].func = (char*)func; // 保存函数名 + TRK[bucket][i].line = line; // 保存行号 + return; // 返回 + } + + /* 没有可用空间 - 分配更多。 */ + + TRK[bucket] = DFL_ck_realloc_block(TRK[bucket], // 重新分配桶 + (TRK_cnt[bucket] + 1) * sizeof(struct TRK_obj)); // 增加桶的大小 + + TRK[bucket][i].ptr = ptr; // 保存指针 + TRK[bucket][i].file = (char*)file; // 保存文件名 + TRK[bucket][i].func = (char*)func; // 保存函数名 + TRK[bucket][i].line = line; // 保存行号 + + TRK_cnt[bucket]++; // 增加桶的计数 +} + + +/* 从已分配对象列表中删除条目。 */ + +static inline void TRK_free_buf(void* ptr, const char* file, const char* func, + u32 line) { // 定义释放跟踪函数 + + u32 i, bucket; // 定义桶索引和循环变量 + + if (!ptr) return; // 如果指针为空则返回 + + bucket = TRKH(ptr); // 获取桶索引 + + /* 在列表中查找元素... */ + + for (i = 0; i < TRK_cnt[bucket]; i++) // 遍历桶中的计数 + if (TRK[bucket][i].ptr == ptr) { // 如果找到对应指针 + TRK[bucket][i].ptr = 0; // 将指针标记为已释放 + return; + } + + WARNF("ALLOC: Attempt to free non-allocated memory in %s (%s:%u)", // 否则输出警告 + func, file, line); +} + + +/* 对所有未释放的对象进行最终报告。 */ + +static inline void TRK_report(void) { // 定义报告函数 + + u32 i, bucket; // 定义桶索引和循环变量 + + fflush(0); // 刷新输出缓冲区 + + for (bucket = 0; bucket < ALLOC_BUCKETS; bucket++) // 遍历每个桶 + for (i = 0; i < TRK_cnt[bucket]; i++) // 遍历桶中的计数 + if (TRK[bucket][i].ptr) // 如果指针不为空 + WARNF("ALLOC: Memory never freed, created in %s (%s:%u)", // 输出未释放内存警告 + TRK[bucket][i].func, TRK[bucket][i].file, TRK[bucket][i].line); +} + + +/* 简单的非调试函数的包装: */ + +static inline void* TRK_ck_alloc(u32 size, const char* file, const char* func, + u32 line) { // 定义跟踪分配函数 + + void* ret = DFL_ck_alloc(size);�������� + +```c +/* + 版权所有 2013 Google LLC 保留所有权利。 + + 根据 Apache 许可证 2.0 版("许可证")授权; + 除非符合许可证的规定,否则您不得使用此文件。 + 您可以从以下网址获取许可证的副本: + + http://www.apache.org/licenses/LICENSE-2.0 + + 除非适用法律要求或书面同意,软件 + 根据许可证分发是一种"按现状"的基础, + 不提供任何形式的保证或条件,无论是明确的还是隐含的。 + 请参阅许可证以了解管理权限和 + 限制的具体语言。 +*/ + +/* + American Fuzzy Lop - 错误检查、内存清零的分配例程 + ------------------------------------------------------------------ + + 作者和维护者:Michal Zalewski + + 此分配器并非设计用于抵抗恶意攻击(金丝雀值较小且可预测), + 但提供了一种健壮且可移植的方式来检测 + 释放后使用、越界写入、悬空指针等问题。 */ #ifndef _HAVE_ALLOC_INL_H #define _HAVE_ALLOC_INL_H -#include -#include -#include +#include // 标准输入输出库 +#include // 标准库函数 +#include // 字符串处理函数 -#include "config.h" -#include "types.h" -#include "debug.h" +#include "config.h" // 配置文件 +#include "types.h" // 类型定义 +#include "debug.h" // 调试工具 -/* User-facing macro to sprintf() to a dynamically allocated buffer. */ +/* 用户可见的宏,用于将格式化字符串输出到动态分配的缓冲区。 */ #define alloc_printf(_str...) ({ \ u8* _tmp; \ - s32 _len = snprintf(NULL, 0, _str); \ - if (_len < 0) FATAL("Whoa, snprintf() fails?!"); \ - _tmp = ck_alloc(_len + 1); \ - snprintf((char*)_tmp, _len + 1, _str); \ - _tmp; \ + s32 _len = snprintf(NULL, 0, _str); /* 计算格式化字符串的长度 */ \ + if (_len < 0) FATAL("Whoa, snprintf() fails?!"); /* 如果失败则报错 */ \ + _tmp = ck_alloc(_len + 1); /* 分配足够的内存 */ \ + snprintf((char*)_tmp, _len + 1, _str); /* 将格式化字符串写入缓冲区 */ \ + _tmp; /* 返回分配的缓冲区 */ \ }) -/* Macro to enforce allocation limits as a last-resort defense against - integer overflows. */ +/* 宏用于强制执行分配限制,作为防止整数溢出的最后手段。 */ #define ALLOC_CHECK_SIZE(_s) do { \ - if ((_s) > MAX_ALLOC) \ - ABORT("Bad alloc request: %u bytes", (_s)); \ + if ((_s) > MAX_ALLOC) /* 检查分配大小是否超过最大限制 */ \ + ABORT("Bad alloc request: %u bytes", (_s)); /* 如果超过则报错 */ \ } while (0) -/* Macro to check malloc() failures and the like. */ +/* 宏用于检查 malloc() 失败等情况。 */ #define ALLOC_CHECK_RESULT(_r, _s) do { \ - if (!(_r)) \ - ABORT("Out of memory: can't allocate %u bytes", (_s)); \ + if (!(_r)) /* 检查分配是否成功 */ \ + ABORT("Out of memory: can't allocate %u bytes", (_s)); /* 失败则报错 */ \ } while (0) -/* Magic tokens used to mark used / freed chunks. */ +/* 用于标记已使用/已释放块的神奇令牌。 */ -#define ALLOC_MAGIC_C1 0xFF00FF00 /* Used head (dword) */ -#define ALLOC_MAGIC_F 0xFE00FE00 /* Freed head (dword) */ -#define ALLOC_MAGIC_C2 0xF0 /* Used tail (byte) */ +#define ALLOC_MAGIC_C1 0xFF00FF00 /* 已使用的头部标记(双字) */ +#define ALLOC_MAGIC_F 0xFE00FE00 /* 已释放的头部标记(双字) */ +#define ALLOC_MAGIC_C2 0xF0 /* 已使用的尾部标记(字节) */ -/* Positions of guard tokens in relation to the user-visible pointer. */ +/* 相对于用户可见指针的守卫令牌位置。 */ -#define ALLOC_C1(_ptr) (((u32*)(_ptr))[-2]) -#define ALLOC_S(_ptr) (((u32*)(_ptr))[-1]) -#define ALLOC_C2(_ptr) (((u8*)(_ptr))[ALLOC_S(_ptr)]) +#define ALLOC_C1(_ptr) (((u32*)(_ptr))[-2]) /* 获取头部标记 */ +#define ALLOC_S(_ptr) (((u32*)(_ptr))[-1]) /* 获取分配大小 */ +#define ALLOC_C2(_ptr) (((u8*)(_ptr))[ALLOC_S(_ptr)]) /* 获取尾部标记 */ -#define ALLOC_OFF_HEAD 8 -#define ALLOC_OFF_TOTAL (ALLOC_OFF_HEAD + 1) +#define ALLOC_OFF_HEAD 8 /* 头部偏移量 */ +#define ALLOC_OFF_TOTAL (ALLOC_OFF_HEAD + 1) /* 总偏移量 */ -/* Allocator increments for ck_realloc_block(). */ +/* ck_realloc_block() 的分配增量。 */ -#define ALLOC_BLK_INC 256 +#define ALLOC_BLK_INC 256 /* 块增量 */ -/* Sanity-checking macros for pointers. */ +/* 用于指针的健全性检查宏。 */ #define CHECK_PTR(_p) do { \ - if (_p) { \ - if (ALLOC_C1(_p) ^ ALLOC_MAGIC_C1) {\ - if (ALLOC_C1(_p) == ALLOC_MAGIC_F) \ - ABORT("Use after free."); \ - else ABORT("Corrupted head alloc canary."); \ + if (_p) { /* 检查指针是否为空 */ \ + if (ALLOC_C1(_p) ^ ALLOC_MAGIC_C1) { /* 检查头部标记是否损坏 */ \ + if (ALLOC_C1(_p) == ALLOC_MAGIC_F) /* 如果是已释放的标记 */ \ + ABORT("Use after free."); /* 报错:释放后使用 */ \ + else ABORT("Corrupted head alloc canary."); /* 否则报错:头部标记损坏 */ \ } \ - if (ALLOC_C2(_p) ^ ALLOC_MAGIC_C2) \ - ABORT("Corrupted tail alloc canary."); \ + if (ALLOC_C2(_p) ^ ALLOC_MAGIC_C2) /* 检查尾部标记是否损坏 */ \ + ABORT("Corrupted tail alloc canary."); /* 报错:尾部标记损坏 */ \ } \ } while (0) #define CHECK_PTR_EXPR(_p) ({ \ - typeof (_p) _tmp = (_p); \ - CHECK_PTR(_tmp); \ - _tmp; \ + typeof (_p) _tmp = (_p); /* 临时存储指针 */ \ + CHECK_PTR(_tmp); /* 检查指针 */ \ + _tmp; /* 返回指针 */ \ }) - -/* Allocate a buffer, explicitly not zeroing it. Returns NULL for zero-sized - requests. */ +/* 分配一个缓冲区,明确不清零。对于零大小的请求返回 NULL。 */ static inline void* DFL_ck_alloc_nozero(u32 size) { void* ret; - if (!size) return NULL; + if (!size) return NULL; /* 如果大小为 0,返回 NULL */ - ALLOC_CHECK_SIZE(size); - ret = malloc(size + ALLOC_OFF_TOTAL); - ALLOC_CHECK_RESULT(ret, size); + ALLOC_CHECK_SIZE(size); /* 检查分配大小是否合法 */ + ret = malloc(size + ALLOC_OFF_TOTAL); /* 分配内存 */ + ALLOC_CHECK_RESULT(ret, size); /* 检查分配是否成功 */ - ret += ALLOC_OFF_HEAD; + ret += ALLOC_OFF_HEAD; /* 调整指针以跳过头部标记 */ - ALLOC_C1(ret) = ALLOC_MAGIC_C1; - ALLOC_S(ret) = size; - ALLOC_C2(ret) = ALLOC_MAGIC_C2; - - return ret; + ALLOC_C1(ret) = ALLOC_MAGIC_C1; /* 设置头部标记 */ + ALLOC_S(ret) = size; /* 设置分配大小 */ + ALLOC_C2(ret) = ALLOC_MAGIC_C2; /* 设置尾部标记 */ + return ret; /* 返回分配的缓冲区 */ } - -/* Allocate a buffer, returning zeroed memory. */ +/* 分配一个缓冲区,返回清零的内存。 */ static inline void* DFL_ck_alloc(u32 size) { void* mem; - if (!size) return NULL; - mem = DFL_ck_alloc_nozero(size); - - return memset(mem, 0, size); + if (!size) return NULL; /* 如果大小为 0,返回 NULL */ + mem = DFL_ck_alloc_nozero(size); /* 分配内存 */ + return memset(mem, 0, size); /* 清零内存并返回 */ } - -/* Free memory, checking for double free and corrupted heap. When DEBUG_BUILD - is set, the old memory will be also clobbered with 0xFF. */ +/* 释放内存,检查双重释放和堆损坏。当 DEBUG_BUILD 启用时, + 旧内存将被填充为 0xFF。 */ static inline void DFL_ck_free(void* mem) { - if (!mem) return; + if (!mem) return; /* 如果指针为空,直接返回 */ - CHECK_PTR(mem); + CHECK_PTR(mem); /* 检查指针是否合法 */ #ifdef DEBUG_BUILD - /* Catch pointer issues sooner. */ - memset(mem, 0xFF, ALLOC_S(mem)); + /* 尽早捕获指针问题。 */ + memset(mem, 0xFF, ALLOC_S(mem)); /* 用 0xFF 填充内存 */ #endif /* DEBUG_BUILD */ - ALLOC_C1(mem) = ALLOC_MAGIC_F; - - free(mem - ALLOC_OFF_HEAD); + ALLOC_C1(mem) = ALLOC_MAGIC_F; /* 标记为已释放 */ + free(mem - ALLOC_OFF_HEAD); /* 释放内存 */ } - -/* Re-allocate a buffer, checking for issues and zeroing any newly-added tail. - With DEBUG_BUILD, the buffer is always reallocated to a new addresses and the - old memory is clobbered with 0xFF. */ +/* 重新分配缓冲区,检查问题并清零任何新添加的尾部。 + 在 DEBUG_BUILD 下,缓冲区总是重新分配到新地址, + 旧内存被填充为 0xFF。 */ static inline void* DFL_ck_realloc(void* orig, u32 size) { void* ret; u32 old_size = 0; - if (!size) { + if (!size) { /* 如果新大小为 0,释放原内存并返回 NULL */ DFL_ck_free(orig); return NULL; } - if (orig) { + if (orig) { /* 如果原指针不为空 */ - CHECK_PTR(orig); + CHECK_PTR(orig); /* 检查原指针是否合法 */ #ifndef DEBUG_BUILD - ALLOC_C1(orig) = ALLOC_MAGIC_F; + ALLOC_C1(orig) = ALLOC_MAGIC_F; /* 标记为已释放 */ #endif /* !DEBUG_BUILD */ - old_size = ALLOC_S(orig); - orig -= ALLOC_OFF_HEAD; + old_size = ALLOC_S(orig); /* 获取原分配大小 */ + orig -= ALLOC_OFF_HEAD; /* 调整指针以跳过头部标记 */ - ALLOC_CHECK_SIZE(old_size); + ALLOC_CHECK_SIZE(old_size); /* 检查原分配大小是否合法 */ } - ALLOC_CHECK_SIZE(size); + ALLOC_CHECK_SIZE(size); /* 检查新分配大小是否合法 */ #ifndef DEBUG_BUILD - ret = realloc(orig, size + ALLOC_OFF_TOTAL); - ALLOC_CHECK_RESULT(ret, size); + ret = realloc(orig, size + ALLOC_OFF_TOTAL); /* 重新分配内存 */ + ALLOC_CHECK_RESULT(ret, size); /* 检查分配是否成功 */ #else - /* Catch pointer issues sooner: force relocation and make sure that the - original buffer is wiped. */ + /* 尽早捕获指针问题:强制重新分配并确保原缓冲区被清除。 */ - ret = malloc(size + ALLOC_OFF_TOTAL); - ALLOC_CHECK_RESULT(ret, size); + ret = malloc(size + ALLOC_OFF_TOTAL); /* 分配新内存 */ + ALLOC_CHECK_RESULT(ret, size); /* 检查分配是否成功 */ - if (orig) { + if (orig) { /* 如果原指针不为空 */ - memcpy(ret + ALLOC_OFF_HEAD, orig + ALLOC_OFF_HEAD, MIN(size, old_size)); - memset(orig + ALLOC_OFF_HEAD, 0xFF, old_size); + memcpy(ret + ALLOC_OFF_HEAD, orig + ALLOC_OFF_HEAD, MIN(size, old_size)); /* 复制数据 */ + memset(orig + ALLOC_OFF_HEAD, 0xFF, old_size); /* 用 0xFF 填充原内存 */ - ALLOC_C1(orig + ALLOC_OFF_HEAD) = ALLOC_MAGIC_F; + ALLOC_C1(orig + ALLOC_OFF_HEAD) = ALLOC_MAGIC_F; /* 标记为已释放 */ - free(orig); + free(orig); /* 释放原内存 */ } #endif /* ^!DEBUG_BUILD */ - ret += ALLOC_OFF_HEAD; + ret += ALLOC_OFF_HEAD; /* 调整指针以跳过头部标记 */ - ALLOC_C1(ret) = ALLOC_MAGIC_C1; - ALLOC_S(ret) = size; - ALLOC_C2(ret) = ALLOC_MAGIC_C2; + ALLOC_C1(ret) = ALLOC_MAGIC_C1; /* 设置头部标记 */ + ALLOC_S(ret) = size; /* 设置分配大小 */ + ALLOC_C2(ret) = ALLOC_MAGIC_C2; /* 设置尾部标记 */ - if (size > old_size) - memset(ret + old_size, 0, size - old_size); - - return ret; + if (size > old_size) /* 如果新大小大于原大小 */ + memset(ret + old_size, 0, size - old_size); /* 清零新添加的部分 */ + return ret; /* 返回重新分配的缓冲区 */ } - -/* Re-allocate a buffer with ALLOC_BLK_INC increments (used to speed up - repeated small reallocs without complicating the user code). */ +/* 使用 ALLOC_BLK_INC 增量重新分配缓冲区(用于加速重复的小型重新分配, + 而不会使用户代码复杂化)。 */ static inline void* DFL_ck_realloc_block(void* orig, u32 size) { #ifndef DEBUG_BUILD - if (orig) { + if (orig) { /* 如果原指针不为空 */ - CHECK_PTR(orig); + CHECK_PTR(orig); /* 检查原指针是否合法 */ - if (ALLOC_S(orig) >= size) return orig; + if (ALLOC_S(orig) >= size) return orig; /* 如果原大小足够,直接返回 */ - size += ALLOC_BLK_INC; + size += ALLOC_BLK_INC; /* 增加分配大小 */ } #endif /* !DEBUG_BUILD */ - return DFL_ck_realloc(orig, size); - + return DFL_ck_realloc(orig, size); /* 重新分配内存 */ } - -/* Create a buffer with a copy of a string. Returns NULL for NULL inputs. */ +/* 创建一个包含字符串副本的缓冲区。对于 NULL 输入返回 NULL。 */ static inline u8* DFL_ck_strdup(u8* str) { void* ret; u32 size; - if (!str) return NULL; + if (!str) return NULL; /* 如果字符串为空,返回 NULL */ - size = strlen((char*)str) + 1; + size = strlen((char*)str) + 1; /* 计算字符串长度 */ - ALLOC_CHECK_SIZE(size); - ret = malloc(size + ALLOC_OFF_TOTAL); - ALLOC_CHECK_RESULT(ret, size); + ALLOC_CHECK_SIZE(size); /* 检查分配大小是否合法 */ + ret = malloc(size + ALLOC_OFF_TOTAL); /* 分配内存 */ + ALLOC_CHECK_RESULT(ret, size); /* 检查分配是否成功 */ - ret += ALLOC_OFF_HEAD; + ret += ALLOC_OFF_HEAD; /* 调整指针以跳过头部标记 */ - ALLOC_C1(ret) = ALLOC_MAGIC_C1; - ALLOC_S(ret) = size; - ALLOC_C2(ret) = ALLOC_MAGIC_C2; - - return memcpy(ret, str, size); + ALLOC_C1(ret) = ALLOC_MAGIC_C1; /* 设置头部标记 */ + ALLOC_S(ret) = size; /* 设置分配大小 */ + ALLOC_C2(ret) = ALLOC_MAGIC_C2; /* 设置尾部标记 */ + return memcpy(ret, str, size); /* 复制字符串并返回 */ } - -/* Create a buffer with a copy of a memory block. Returns NULL for zero-sized - or NULL inputs. */ +/* 创建一个包含内存块副本的缓冲区。对于零大小或 NULL 输入返回 NULL。 */ static inline void* DFL_ck_memdup(void* mem, u32 size) { void* ret; - if (!mem || !size) return NULL; + if (!mem || !size) return NULL; /* 如果内存块为空或大小为 0,返回 NULL */ - ALLOC_CHECK_SIZE(size); - ret = malloc(size + ALLOC_OFF_TOTAL); - ALLOC_CHECK_RESULT(ret, size); + ALLOC_CHECK_SIZE(size); /* 检查分配大小是否合法 */ + ret = malloc(size + ALLOC_OFF_TOTAL); /* 分配内存 */ + ALLOC_CHECK_RESULT(ret, size); /* 检查分配是否成功 */ - ret += ALLOC_OFF_HEAD; + ret += ALLOC_OFF_HEAD; /* 调整指针以跳过头部标记 */ - ALLOC_C1(ret) = ALLOC_MAGIC_C1; - ALLOC_S(ret) = size; - ALLOC_C2(ret) = ALLOC_MAGIC_C2; - - return memcpy(ret, mem, size); + ALLOC_C1(ret) = ALLOC_MAGIC_C1; /* 设置头部标记 */ + ALLOC_S(ret) = size; /* 设置分配大小 */ + ALLOC_C2(ret) = ALLOC_MAGIC_C2; /* 设置尾部标记 */ + return memcpy(ret, mem, size); /* 复制内存块并返回 */ } - -/* Create a buffer with a block of text, appending a NUL terminator at the end. - Returns NULL for zero-sized or NULL inputs. */ +/* 创建一个包含文本块的缓冲区,并在末尾附加一个 NUL 终止符。 + 对于零大小或 NULL 输入返回 NULL。 */ static inline u8* DFL_ck_memdup_str(u8* mem, u32 size) { u8* ret; - if (!mem || !size) return NULL; + if (!mem || !size) return NULL; /* 如果内存块为空或大小为 0,返回 NULL */ - ALLOC_CHECK_SIZE(size); - ret = malloc(size + ALLOC_OFF_TOTAL + 1); - ALLOC_CHECK_RESULT(ret, size); + ALLOC_CHECK_SIZE(size); /* 检查分配大小是否合法 */ + ret = malloc(size + ALLOC_OFF_TOTAL + 1); /* 分配内存 */ + ALLOC_CHECK_RESULT(ret, size); /* 检查分配是否成功 */ - ret += ALLOC_OFF_HEAD; - - ALLOC_C1(ret) = ALLOC_MAGIC_C1; - ALLOC_S(ret) = size; - ALLOC_C2(ret) = ALLOC_MAGIC_C2; + ret += ALLOC_OFF_HEAD; /* 调整指针以跳过头部标记 */ - memcpy(ret, mem, size); - ret[size] = 0; + ALLOC_C1(ret) = ALLOC_MAGIC_C1; /* 设置头部标记 */ + ALLOC_S(ret) = size; /* 设置分配大小 */ + ALLOC_C2(ret) = ALLOC_MAGIC_C2; /* 设置尾部标记 */ - return ret; + memcpy(ret, mem, size); /* 复制内存块 */ + ret[size] = 0; /* 添加 NUL 终止符 */ + return ret; /* 返回缓冲区 */ } - #ifndef DEBUG_BUILD -/* In non-debug mode, we just do straightforward aliasing of the above functions - to user-visible names such as ck_alloc(). */ +/* 在非调试模式下,我们直接将上述函数别名为用户可见的名称,如 ck_alloc()。 */ #define ck_alloc DFL_ck_alloc #define ck_alloc_nozero DFL_ck_alloc_nozero @@ -351,123 +781,121 @@ static inline u8* DFL_ck_memdup_str(u8* mem, u32 size) { #define ck_memdup_str DFL_ck_memdup_str #define ck_free DFL_ck_free -#define alloc_report() +#define alloc_report() /* 无操作 */ #else -/* In debugging mode, we also track allocations to detect memory leaks, and the - flow goes through one more layer of indirection. */ +/* 在调试模式下,我们还跟踪分配以检测内存泄漏, + 并且流程通过另一层间接性。 */ -/* Alloc tracking data structures: */ +/* 分配跟踪数据结构: */ -#define ALLOC_BUCKETS 4096 +#define ALLOC_BUCKETS 4096 /* 分配桶的数量 */ struct TRK_obj { - void *ptr; - char *file, *func; - u32 line; + void *ptr; /* 分配的指针 */ + char *file, *func; /* 分配位置的文件名和函数名 */ + u32 line; /* 分配位置的行号 */ }; #ifdef AFL_MAIN -struct TRK_obj* TRK[ALLOC_BUCKETS]; -u32 TRK_cnt[ALLOC_BUCKETS]; +struct TRK_obj* TRK[ALLOC_BUCKETS]; /* 分配跟踪数组 */ +u32 TRK_cnt[ALLOC_BUCKETS]; /* 每个桶的计数 */ -# define alloc_report() TRK_report() +# define alloc_report() TRK_report() /* 定义分配报告宏 */ #else -extern struct TRK_obj* TRK[ALLOC_BUCKETS]; -extern u32 TRK_cnt[ALLOC_BUCKETS]; +extern struct TRK_obj* TRK[ALLOC_BUCKETS]; /* 外部声明分配跟踪数组 */ +extern u32 TRK_cnt[ALLOC_BUCKETS]; /* 外部声明每个桶的计数 */ -# define alloc_report() +# define alloc_report() /* 无操作 */ #endif /* ^AFL_MAIN */ -/* Bucket-assigning function for a given pointer: */ +/* 为给定指针分配桶的函数: */ #define TRKH(_ptr) (((((u32)(_ptr)) >> 16) ^ ((u32)(_ptr))) % ALLOC_BUCKETS) - -/* Add a new entry to the list of allocated objects. */ +/* 将新条目添加到已分配对象的列表中。 */ static inline void TRK_alloc_buf(void* ptr, const char* file, const char* func, u32 line) { u32 i, bucket; - if (!ptr) return; + if (!ptr) return; /* 如果指针为空,直接返回 */ - bucket = TRKH(ptr); + bucket = TRKH(ptr); /* 计算桶索引 */ - /* Find a free slot in the list of entries for that bucket. */ + /* 在该桶的条目列表中查找空闲槽。 */ for (i = 0; i < TRK_cnt[bucket]; i++) - if (!TRK[bucket][i].ptr) { + if (!TRK[bucket][i].ptr) { /* 如果找到空闲槽 */ - TRK[bucket][i].ptr = ptr; - TRK[bucket][i].file = (char*)file; - TRK[bucket][i].func = (char*)func; - TRK[bucket][i].line = line; + TRK[bucket][i].ptr = ptr; /* 存储指针 */ + TRK[bucket][i].file = (char*)file; /* 存储文件名 */ + TRK[bucket][i].func = (char*)func; /* 存储函数名 */ + TRK[bucket][i].line = line; /* 存储行号 */ return; } - /* No space available - allocate more. */ + /* 没有可用空间 - 分配更多。 */ TRK[bucket] = DFL_ck_realloc_block(TRK[bucket], - (TRK_cnt[bucket] + 1) * sizeof(struct TRK_obj)); + (TRK_cnt[bucket] + 1) * sizeof(struct TRK_obj)); /* 重新分配内存 */ - TRK[bucket][i].ptr = ptr; - TRK[bucket][i].file = (char*)file; - TRK[bucket][i].func = (char*)func; - TRK[bucket][i].line = line; + TRK[bucket][i].ptr = ptr; /* 存储指针 */ + TRK[bucket][i].file = (char*)file; /* 存储文件名 */ + TRK[bucket][i].func = (char*)func; /* 存储函数名 */ + TRK[bucket][i].line = line; /* 存储行号 */ - TRK_cnt[bucket]++; + TRK_cnt[bucket]++; /* 增加计数 */ } - -/* Remove entry from the list of allocated objects. */ +/* 从已分配对象的列表中删除条目。 */ static inline void TRK_free_buf(void* ptr, const char* file, const char* func, u32 line) { u32 i, bucket; - if (!ptr) return; + if (!ptr) return; /* 如果指针为空,直接返回 */ - bucket = TRKH(ptr); + bucket = TRKH(ptr); /* 计算桶索引 */ - /* Find the element on the list... */ + /* 在列表中查找元素... */ for (i = 0; i < TRK_cnt[bucket]; i++) - if (TRK[bucket][i].ptr == ptr) { + if (TRK[bucket][i].ptr == ptr) { /* 如果找到匹配的指针 */ - TRK[bucket][i].ptr = 0; + TRK[bucket][i].ptr = 0; /* 标记为已释放 */ return; } WARNF("ALLOC: Attempt to free non-allocated memory in %s (%s:%u)", - func, file, line); + func, file, line); /* 报错:尝试释放未分配的内存 */ } - -/* Do a final report on all non-deallocated objects. */ +/* 对所有未释放的对象进行最终报告。 */ static inline void TRK_report(void) { u32 i, bucket; - fflush(0); + fflush(0); /* 刷新所有输出流 */ + + for (bucket = 0; bucket < ALLOC_BUCKETS; bucket++) /* 遍历所有桶 */ + for (i = 0; i < TRK_cnt[bucket]; i++) /* 遍历每个桶的条目 */ + if (TRK[bucket][i].ptr) /* 如果 - for (bucket = 0; bucket < ALLOC_BUCKETS; bucket++) - for (i = 0; i < TRK_cnt[bucket]; i++) - if (TRK[bucket][i].ptr) WARNF("ALLOC: Memory never freed, created in %s (%s:%u)", TRK[bucket][i].func, TRK[bucket][i].file, TRK[bucket][i].line); diff --git a/src/debug.h b/src/debug.h index 5f75974..d01621a 100644 --- a/src/debug.h +++ b/src/debug.h @@ -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 + 作者和维护者:Michal Zalewski */ #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 */ diff --git a/src/experimental/asan_cgroups/limit_memory.sh b/src/experimental/asan_cgroups/limit_memory.sh index 9ee378c..4dec01e 100644 --- a/src/experimental/asan_cgroups/limit_memory.sh +++ b/src/experimental/asan_cgroups/limit_memory.sh @@ -33,24 +33,31 @@ # task. # +# 输出脚本的作者信息 echo "cgroup tool for afl-fuzz by and " 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" diff --git a/src/libdislocator/libdislocator.so.c b/src/libdislocator/libdislocator.so.c index 8f415d5..0d41c29 100644 --- a/src/libdislocator/libdislocator.so.c +++ b/src/libdislocator/libdislocator.so.c @@ -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. 释放原始内存(通过调用free函数来实现,free函数中会调用mprotect来保护原始内存) +*/ 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"); } diff --git a/src/libtokencap/libtokencap.so.c b/src/libtokencap/libtokencap.so.c index 6abdf23..635ba94 100644 --- a/src/libtokencap/libtokencap.so.c +++ b/src/libtokencap/libtokencap.so.c @@ -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; } diff --git a/src/llvm_mode/afl-clang-fast.c b/src/llvm_mode/afl-clang-fast.c index 2104a12..84eef01 100644 --- a/src/llvm_mode/afl-clang-fast.c +++ b/src/llvm_mode/afl-clang-fast.c @@ -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 \n"); #else + // 否则,仅打印版本信息 SAYF(cCYA "afl-clang-fast " cBRI VERSION cRST " by \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; // 正常结束程序 } diff --git a/src/qemu_mode/patches/afl-qemu-cpu-inl.h b/src/qemu_mode/patches/afl-qemu-cpu-inl.h index c05bd77..8ef62e3 100644 --- a/src/qemu_mode/patches/afl-qemu-cpu-inl.h +++ b/src/qemu_mode/patches/afl-qemu-cpu-inl.h @@ -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 and - Michal Zalewski + Written by Andrew Griffiths and // 由Andrew Griffiths和 + Michal Zalewski // 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 -#include "../../config.h" +#include // 包含共享内存的头文件 +#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); // 关闭文件描述符 }