-T text - text banner to show on the screen -M / -S id - distributed mode(see parallel_fuzzing.txt) -C - crash exploration mode(the peruvian rabbit thing) -V - show version number and exit
-b cpu_id - bind the fuzzing process to the specified CPU core
For additional tips, please consult /usr/local/share/doc/afl/README.
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");
/* Build a list of processes bound to specific cores. Returns -1 if nothing can be found. Assumes an upper bound of 4k CPUs. */
staticvoidbind_to_free_cpu(void) {
DIR* d; structdirent* de; cpu_set_t c;
u8 cpu_used[4096] = { 0 }; u32 i;
if (cpu_core_count < 2) return;
if (getenv("AFL_NO_AFFINITY")) {
WARNF("Not binding to a CPU core (AFL_NO_AFFINITY set)."); return;
}
d = opendir("/proc");
if (!d) {
WARNF("Unable to access /proc - can't scan for free CPU cores."); return;
}
ACTF("Checking CPU core loadout...");
/* Introduce some jitter, in case multiple AFL tasks are doing the same thing at the same time... */
usleep(R(1000) * 250);
/* Scan all /proc/<pid>/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. */
if (cpu_to_bind >= cpu_core_count) FATAL("The CPU core id to bind should be between 0 and %u", cpu_core_count - 1); if (cpu_used[cpu_to_bind]) FATAL("The CPU core #%u to bind is not free!", 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 "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", cpu_core_count);
FATAL("No more free CPU cores");
}
OKF("Found a free CPU core, binding to #%u.", i);
cpu_aff = i;
CPU_ZERO(&c); CPU_SET(i, &c);
if (sched_setaffinity(0, sizeof(c), &c)) PFATAL("sched_setaffinity failed");
/* Make sure that core dumps don't go to a program. */
staticvoidcheck_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"
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'");
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");
}
通过读取 /sys/devices/system/cpu/cpu0/cpufreq/ 下的一些内容检查 CPU 管理者
/* 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? */
/* 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. */
/* 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");
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");
/* Set next_100 pointer for every 100th element (index 0, 100, etc) to allow faster iteration. */ if ((queued_paths - 1) % 100 == 0 && queued_paths > 1) {
/* If the original file name conforms to the syntax and the recorded ID matches the one we'd assign, just use the original file name. This is valuable for resuming fuzzing runs. */
/* Make sure that the passed_det value carries over, too. */
if (q->passed_det) mark_as_det_done(q);
q = q->next; id++;
}
if (in_place_resume) nuke_resume_dir();
}
/* Mark deterministic checks as done for a particular queue entry. We use the .state file to avoid repeating deterministic fuzzing when resuming aborted scans. */
/* 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. */
staticvoidfind_timeout(void) {
static u8 tmp[4096]; /* Ought to be enough for anybody. */
/* 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 voidcheck_binary(u8* fname) {
u8* env_path = 0; structstatst;
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);
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);
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");
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");
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) {
/* The -t nn+ syntax in the command line sets timeout_given to '2' and instructs afl-fuzz to tolerate but skip queue entries that time out. */
if (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"
" Tip: you can use http://jwilk.net/software/recidivm to quickly\n" " estimate the required amount of virtual memory for the binary. Also,\n" " if you are using ASAN, see %s/notes_for_asan.txt.\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 <lcamtuf@coredump.cx> 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 <lcamtuf@coredump.cx> 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!");
/* 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;
}
/* Examine map coverage. Called once, for first test case. */
staticvoidcheck_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!");
/* 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. */
/* 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 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. */
/* 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 voidinit_forkserver(char** argv) {
staticstructitimervalit; 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) {
structrlimitr;
/* 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) {
/* 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");
/* 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);
} elseif (!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 <lcamtuf@coredump.cx> 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"
" 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 <lcamtuf@coredump.cx> 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);
} elseif (!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 <lcamtuf@coredump.cx> 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"
" 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 <lcamtuf@coredump.cx> 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);
/* 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. */
staticinline 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[]. */
/* 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. */
/* 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. */
/* 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; }
/* 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. */
/* Compact trace bytes into a smaller bitmap. We effectively just drop the count information here. This is called only sporadically, for some new paths. */
/* 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. */
/* 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. */
if (max_len > 50 * 1024) WARNF(cLRD "Some test cases are huge (%s) - see %s/perf_tips.txt!", DMS(max_len), doc_path); elseif (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."); elseif (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 =) */
/* 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. */
/* 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"); } elseif (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__ */ }
/* 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. */
/* 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 (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. */
/******************************************* * 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; }
/* 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. */
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. */
/* 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. */
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. */
/* 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. */
/* 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);
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);
/* 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);
该阶段会将 a_extras 里面的特殊 token 进行替换,主要有以下三类:
替换
user extras :stage_max 为 extras_cnt * len
auto extras :stage_max 为 MIN(a_extras_cnt, USE_AUTO_EXTRAS) * len
/**************** * 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. */
/* 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. */
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. */
/* 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(). */
/* 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) {
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. */
/* 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. */
/* 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. */
/* 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;
} elseif (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. */
/* Check if the result of an execve() during routine fuzzing is interesting, save or queue the input test case for further analysis if so. Returns 1 if entry is saved, 0 otherwise. */
/* Timeouts are not very interesting, but we're still obliged to keep a handful of samples. We use the presence of new bits in the hang-specific bitmap as a signal of uniqueness. In "dumb" mode, we just keep everything. */
total_tmouts++;
if (unique_hangs >= KEEP_UNIQUE_HANG) return keeping;
/* Before saving, we make sure that it's a genuine hang by re-running the target with a more generous timeout (unless the default timeout is already generous). */