afl-untracer completed

This commit is contained in:
van Hauser 2020-04-30 17:59:59 +02:00
parent a37eca9df5
commit efa9df24c2
10 changed files with 106 additions and 75 deletions

View File

@ -31,6 +31,8 @@ sending a mail to <afl-users+subscribe@googlegroups.com>.
- added examples/afl_network_proxy which allows to fuzz a target over the - added examples/afl_network_proxy which allows to fuzz a target over the
network (not fuzzing tcp/ip services but running afl-fuzz on one system network (not fuzzing tcp/ip services but running afl-fuzz on one system
and the target being on an embedded device) and the target being on an embedded device)
- added examples/afl_untracer which does a binary-only fuzzing with the
modifications done in memory
- added examples/afl_proxy which can be easily used to fuzz and instrument - added examples/afl_proxy which can be easily used to fuzz and instrument
non-standard things non-standard things
- all: - all:

View File

@ -20,6 +20,7 @@ e.g.:
``` ```
$ afl-network-server -i 1111 -m 25M -t 1000 -- /bin/target -f @@ $ afl-network-server -i 1111 -m 25M -t 1000 -- /bin/target -f @@
``` ```
### on the fuzzing master ### on the fuzzing master
Just run afl-fuzz with your normal options, however the target should be Just run afl-fuzz with your normal options, however the target should be
@ -42,3 +43,13 @@ either. Note that also the outgoing interface can be specified with a '%' for
## how to compile and install ## how to compile and install
`make && sudo make install` `make && sudo make install`
## Future
It would be much faster and more effective if `afl-network-server` does not
send the map data back (64kb or more) but the checksum that `afl-fuzz` would
generate. This change however would make it incompatible with existing
afl spinoffs.
But in the future this will be implemented and supported as a compile option.

View File

@ -175,7 +175,7 @@ static void __afl_start_forkserver(void) {
static u32 __afl_next_testcase(u8 *buf, u32 max_len) { static u32 __afl_next_testcase(u8 *buf, u32 max_len) {
s32 status, res = 0xffffff; s32 status, res = 0x0fffffff; // res is a dummy pid
/* Wait for parent by reading from the pipe. Abort if read fails. */ /* Wait for parent by reading from the pipe. Abort if read fails. */
if (read(FORKSRV_FD, &status, 4) != 4) return 0; if (read(FORKSRV_FD, &status, 4) != 4) return 0;
@ -193,9 +193,7 @@ static u32 __afl_next_testcase(u8 *buf, u32 max_len) {
} }
static void __afl_end_testcase(void) { static void __afl_end_testcase(int status) {
int status = 0xffffff;
if (write(FORKSRV_FD + 1, &status, 4) != 4) exit(1); if (write(FORKSRV_FD + 1, &status, 4) != 4) exit(1);
@ -273,7 +271,7 @@ int main(int argc, char *argv[]) {
__afl_map_shm(); __afl_map_shm();
__afl_start_forkserver(); __afl_start_forkserver();
int i = 1, j; int i = 1, j, status, ret;
// fprintf(stderr, "Waiting for first testcase\n"); // fprintf(stderr, "Waiting for first testcase\n");
while ((len = __afl_next_testcase(buf, max_len)) > 0) { while ((len = __afl_next_testcase(buf, max_len)) > 0) {
@ -281,17 +279,25 @@ int main(int argc, char *argv[]) {
if (send(s, &len, 4, 0) != 4) PFATAL("sending size data %d failed", len); if (send(s, &len, 4, 0) != 4) PFATAL("sending size data %d failed", len);
if (send(s, buf, len, 0) != len) PFATAL("sending test data failed"); if (send(s, buf, len, 0) != len) PFATAL("sending test data failed");
int received = 0, ret; int received = 0;
while (received < 4 &&
(ret = recv(s, &status + received, 4 - received, 0)) > 0)
received += ret;
if (received != 4)
FATAL("did not receive waitpid data (%d, %d)", received, ret);
// fprintf(stderr, "Received status\n");
int received = 0;
while (received < __afl_map_size && while (received < __afl_map_size &&
(ret = recv(s, __afl_area_ptr + received, __afl_map_size - received, (ret = recv(s, __afl_area_ptr + received, __afl_map_size - received,
0)) > 0) 0)) > 0)
received += ret; received += ret;
if (received != __afl_map_size) if (received != __afl_map_size)
FATAL("did not receive valid data (%d, %d)", received, ret); FATAL("did not receive coverage data (%d, %d)", received, ret);
// fprintf(stderr, "Received coverage\n"); // fprintf(stderr, "Received coverage\n");
/* report the test case is done and wait for the next */ /* report the test case is done and wait for the next */
__afl_end_testcase(); __afl_end_testcase(status);
// fprintf(stderr, "Waiting for next testcase %d\n", ++i); // fprintf(stderr, "Waiting for next testcase %d\n", ++i);
} }
@ -299,4 +305,3 @@ int main(int argc, char *argv[]) {
return 0; return 0;
} }

View File

@ -579,6 +579,8 @@ int main(int argc, char **argv_orig, char **envp) {
// fprintf(stderr, "received %u\n", in_len); // fprintf(stderr, "received %u\n", in_len);
run_target(fsrv, use_argv, in_data, in_len, 1); run_target(fsrv, use_argv, in_data, in_len, 1);
if (send(s, fsrv->child_status, 4, 0) != 4)
FATAL("could not send waitpid data");
if (send(s, fsrv->trace_bits, fsrv->map_size, 0) != fsrv->map_size) if (send(s, fsrv->trace_bits, fsrv->map_size, 0) != fsrv->map_size)
FATAL("could not send coverage data"); FATAL("could not send coverage data");
// fprintf(stderr, "sent result\n"); // fprintf(stderr, "sent result\n");

View File

@ -7,4 +7,4 @@ libtestinstr.so: libtestinstr.c
$(CC) -fPIC -o libtestinstr.so -shared libtestinstr.c $(CC) -fPIC -o libtestinstr.so -shared libtestinstr.c
clean: clean:
rm -f afl-untracer *~ core rm -f afl-untracer libtestinstr.so *~ core

View File

@ -3,9 +3,14 @@
afl-untracer is an example skeleton file which can easily be used to fuzz afl-untracer is an example skeleton file which can easily be used to fuzz
a closed source library. a closed source library.
It is faster and requires less memory than qemu_mode however it is way It requires less memory than qemu_mode however it is way
more course grained and does not provide interesting features like compcov more course grained and does not provide interesting features like compcov
or cmplog. or cmplog.
Read and modify afl-untracer.c then `make` and use it as the afl-fuzz target Read and modify afl-untracer.c then `make` and use it as the afl-fuzz target
(or even remote via afl-network-proxy). (or even remote via afl-network-proxy).
This idea is based on [UnTracer](https://github.com/FoRTE-Research/UnTracer-AFL)
and modified by [Trapfuzz](https://github.com/googleprojectzero/p0tools/tree/master/TrapFuzz).
This implementation is slower because the traps are not patched out with each
run, but on the other hand gives much better coverage information.

View File

@ -76,16 +76,9 @@ static u32 use_stdin = 1;
/* This is were the testcase data is written into */ /* This is were the testcase data is written into */
static u8 buf[10000]; // this is the maximum size for a test case! set it! static u8 buf[10000]; // this is the maximum size for a test case! set it!
/* if you want to use fork() then uncomment the following line. Otherwise /* If you want to have debug output set this to 1, can also be set with
threads are used. The differences: AFL_DEBUG */
fork() pthread() static u32 debug = 0;
speed slow fast
coverage detailed minimal
memory leaks no problem an issue */
//#define USE_FORK
/* If you want to have debug output set this to 1 */
static u32 debug = 1;
// END STEP 1 // END STEP 1
@ -130,7 +123,7 @@ void read_library_information() {
if (debug) fprintf(stderr, "Library list:\n"); if (debug) fprintf(stderr, "Library list:\n");
while (fgets(buf, sizeof(buf), f)) { while (fgets(buf, sizeof(buf), f)) {
if (strstr(buf, " r-xp ")) { if (strstr(buf, " r-x")) {
if (liblist_cnt >= MAX_LIB_COUNT) { if (liblist_cnt >= MAX_LIB_COUNT) {
@ -159,9 +152,9 @@ void read_library_information() {
liblist[liblist_cnt].addr_start = strtoull(b, NULL, 16); liblist[liblist_cnt].addr_start = strtoull(b, NULL, 16);
liblist[liblist_cnt].addr_end = strtoull(m, NULL, 16); liblist[liblist_cnt].addr_end = strtoull(m, NULL, 16);
if (debug) if (debug)
fprintf(stderr, "%s:%x (%x-%x)\n", liblist[liblist_cnt].name, fprintf(stderr, "%s:%x (%lx-%lx)\n", liblist[liblist_cnt].name,
liblist[liblist_cnt].addr_end - liblist[liblist_cnt].addr_start, liblist[liblist_cnt].addr_end - liblist[liblist_cnt].addr_start,
liblist[liblist_cnt].addr_start, liblist[liblist_cnt].addr_end); liblist[liblist_cnt].addr_start, liblist[liblist_cnt].addr_end - 1);
liblist_cnt++; liblist_cnt++;
} }
@ -367,17 +360,13 @@ static u32 __afl_next_testcase(u8 *buf, u32 max_len) {
if (write(FORKSRV_FD + 1, &pid, 4) != 4) do_exit = 1; if (write(FORKSRV_FD + 1, &pid, 4) != 4) do_exit = 1;
// fprintf(stderr, "write1 %d\n", do_exit); // fprintf(stderr, "write1 %d\n", do_exit);
if (!do_exit)
__afl_area_ptr[0] = 1; // otherwise afl-fuzz will exit with NOINST
return status; return status;
} }
static void __afl_end_testcase(void) { static void __afl_end_testcase(int status) {
s32 res = 0xffffff; if (write(FORKSRV_FD + 1, &status, 4) != 4) do_exit = 1;
if (write(FORKSRV_FD + 1, &res, 4) != 4) do_exit = 1;
// if (write(FORKSRV_FD + 1, &pid, 4) != 4) do_exit = 1;
// fprintf(stderr, "write2 %d\n", do_exit); // fprintf(stderr, "write2 %d\n", do_exit);
if (do_exit) exit(0); if (do_exit) exit(0);
@ -455,6 +444,19 @@ void setup_trap_instrumentation() {
// It's an offset, parse it and do the patching. // It's an offset, parse it and do the patching.
unsigned long offset = strtoul(line, NULL, 16); unsigned long offset = strtoul(line, NULL, 16);
// I dont know what it is. /proc/<pid>/maps shows the right start address
// and the offsets generated by the python scripts are fine as well.
// And loading the library into gdb also shows the offsets generated
// by the script are correct. However when loaded via dlopen the first
// 0x1000 are skipped ...
#if defined(__linux__)
if (offset >= 0x1000)
offset -= 0x1000;
else
fprintf(stderr, "Warning: offset is < 0x1000: %x\n", offset);
#endif
if (offset > lib_size) if (offset > lib_size)
FATAL("Invalid offset: 0x%lx. Current library is 0x%zx bytes large", FATAL("Invalid offset: 0x%lx. Current library is 0x%zx bytes large",
offset, lib_size); offset, lib_size);
@ -482,11 +484,10 @@ void setup_trap_instrumentation() {
// this will be ARM and AARCH64 // this will be ARM and AARCH64
// for ARM we will need to identify if the code is in thumb or ARM // for ARM we will need to identify if the code is in thumb or ARM
#error "non x86_64 not supported yet" #error "non x86_64 not supported yet"
//__arm__ //__arm__:
// linux thumb: 0xde01 // linux thumb: 0xde01
// linux thumb2: 0xf7f0a000
// linux arm: 0xe7f001f0 // linux arm: 0xe7f001f0
//__aarch64__ //__aarch64__:
// linux aarch64: 0xd4200000 // linux aarch64: 0xd4200000
#endif #endif
@ -510,6 +511,8 @@ void setup_trap_instrumentation() {
} }
/* the signal handler for the traps / debugging interrupts
No debug output here because this would cost speed */
static void sigtrap_handler(int signum, siginfo_t *si, void *context) { static void sigtrap_handler(int signum, siginfo_t *si, void *context) {
uint64_t addr; uint64_t addr;
@ -525,31 +528,25 @@ static void sigtrap_handler(int signum, siginfo_t *si, void *context) {
#error "Unsupported platform" #error "Unsupported platform"
#endif #endif
if (debug) //fprintf(stderr, "TRAP at context addr = %lx, fault addr = %lx\n", addr, si->si_addr);
fprintf(stderr, "TRAP at context addr = %lx, fault addr = %lx\n", addr,
si->si_addr);
// If the trap didn't come from our instrumentation, then we probably will // If the trap didn't come from our instrumentation, then we probably will
// just segfault here // just segfault here
uint8_t *faultaddr; uint8_t *faultaddr;
if (si->si_addr) if (unlikely(si->si_addr))
faultaddr = (u8 *)si->si_addr - 1; faultaddr = (u8 *)si->si_addr - 1;
else else
faultaddr = (u8 *)addr; faultaddr = (u8 *)addr;
// u8 *loc = SHADOW(faultaddr); //if (debug) fprintf(stderr, "Shadow location: %p\n", SHADOW(faultaddr));
if (debug)
fprintf(stderr, "Shadow location: %p\n", SHADOW(faultaddr));
uint32_t shadow = *SHADOW(faultaddr); uint32_t shadow = *SHADOW(faultaddr);
uint8_t orig_byte = shadow & 0xff; uint8_t orig_byte = shadow & 0xff;
uint32_t index = shadow >> 8; uint32_t index = shadow >> 8;
if (debug) //if (debug) fprintf(stderr, "shadow data: %x, orig_byte %02x, index %d\n", shadow, orig_byte, index);
fprintf(stderr, "shadow data: %x, orig_byte %02x, index %d\n", shadow,
orig_byte, index);
// Index zero is invalid so that it is still possible to catch actual trap // Index zero is invalid so that it is still possible to catch actual trap
// instructions in instrumented libraries. // instructions in instrumented libraries.
if (index == 0) abort(); if (unlikely(index == 0)) abort();
// Restore original instruction // Restore original instruction
*faultaddr = orig_byte; *faultaddr = orig_byte;
@ -561,6 +558,7 @@ static void sigtrap_handler(int signum, siginfo_t *si, void *context) {
/* here you need to specify the parameter for the target function */ /* here you need to specify the parameter for the target function */
static void *(*o_function)(u8 *buf, int len); static void *(*o_function)(u8 *buf, int len);
/* the MAIN function */
int main(int argc, char *argv[]) { int main(int argc, char *argv[]) {
pid = getpid(); pid = getpid();
@ -579,11 +577,11 @@ int main(int argc, char *argv[]) {
// inclusive of the cleanup functions // inclusive of the cleanup functions
// NOTE: above the main() you have to define the functions! // NOTE: above the main() you have to define the functions!
/* setup the target */
void *dl = dlopen("./libtestinstr.so", RTLD_LAZY); void *dl = dlopen("./libtestinstr.so", RTLD_LAZY);
if (!dl) FATAL("could not find target library"); if (!dl) FATAL("could not find target library");
o_function = dlsym(dl, "testinstr"); o_function = dlsym(dl, "testinstr");
if (!o_function) FATAL("could not resolve target function from library"); if (!o_function) FATAL("could not resolve target function from library");
if (debug) fprintf(stderr, "Function address: %p\n", o_function);
// END STEP 2 // END STEP 2
@ -595,42 +593,33 @@ int main(int argc, char *argv[]) {
while (1) { while (1) {
#ifdef USE_FORK
if ((pid = fork()) == -1) PFATAL("fork failed"); if ((pid = fork()) == -1) PFATAL("fork failed");
if (pid) { if (pid) {
u32 status; u32 status;
if (waitpid(pid, &status, 0) < 0) exit(1); if (waitpid(pid, &status, 0) < 0) exit(1);
/* report the test case is done and wait for the next */
__afl_end_testcase(status);
} else { } else {
#endif
pid = getpid(); pid = getpid();
while ((len = __afl_next_testcase(buf, sizeof(buf))) > 0) { while ((len = __afl_next_testcase(buf, sizeof(buf))) > 0) {
#ifdef USE_FORK // in this function the fuzz magic happens, this is STEP 3
fuzz(); fuzz();
#else
if (pthread_create(&__afl_thread, NULL, (void *)fuzz, NULL) != 0)
PFATAL("cannot create thread");
pthread_join(__afl_thread, NULL);
#endif
/* report the test case is done and wait for the next */ // we can use _exit which is faster because our target library
__afl_end_testcase(); // was loaded via dlopen and there cannot have deconstructors
#ifdef USE_FORK // registered.
exit(0); _exit(0);
#endif
} }
#ifdef USE_FORK
} }
#endif
} }
return 0; return 0;
@ -651,9 +640,4 @@ static void fuzz() {
// END STEP 3 // END STEP 3
#ifndef USE_FORK
pthread_exit(NULL);
#endif
} }

View File

@ -0,0 +1,23 @@
libtestinstr.so:0x2000L
0x1050L
0x1063L
0x106fL
0x1078L
0x1080L
0x10a4L
0x10b0L
0x10b8L
0x10c0L
0x10c9L
0x10d7L
0x10e3L
0x10f8L
0x1100L
0x1105L
0x111aL
0x1135L
0x1143L
0x114eL
0x115cL
0x116aL
0x116bL

View File

@ -43,6 +43,7 @@ typedef struct afl_forkserver {
s32 fsrv_pid, /* PID of the fork server */ s32 fsrv_pid, /* PID of the fork server */
child_pid, /* PID of the fuzzed program */ child_pid, /* PID of the fuzzed program */
child_status, /* waitpid result for the child */
out_dir_fd; /* FD of the lock file */ out_dir_fd; /* FD of the lock file */
s32 out_fd, /* Persistent fd for fsrv->out_file */ s32 out_fd, /* Persistent fd for fsrv->out_file */

View File

@ -790,8 +790,6 @@ fsrv_run_result_t afl_fsrv_run_target(afl_forkserver_t *fsrv, u32 timeout,
s32 res; s32 res;
u32 exec_ms; u32 exec_ms;
int status = 0;
/* After this memset, fsrv->trace_bits[] are effectively volatile, so we /* After this memset, fsrv->trace_bits[] are effectively volatile, so we
must prevent any earlier operations from venturing into that must prevent any earlier operations from venturing into that
territory. */ territory. */
@ -821,7 +819,7 @@ fsrv_run_result_t afl_fsrv_run_target(afl_forkserver_t *fsrv, u32 timeout,
if (fsrv->child_pid <= 0) { FATAL("Fork server is misbehaving (OOM?)"); } if (fsrv->child_pid <= 0) { FATAL("Fork server is misbehaving (OOM?)"); }
exec_ms = read_timed(fsrv->fsrv_st_fd, &status, 4, timeout, stop_soon_p); exec_ms = read_timed(fsrv->fsrv_st_fd, &fsrv->child_status, 4, timeout, stop_soon_p);
if (exec_ms > timeout) { if (exec_ms > timeout) {
@ -830,7 +828,7 @@ fsrv_run_result_t afl_fsrv_run_target(afl_forkserver_t *fsrv, u32 timeout,
kill(fsrv->child_pid, SIGKILL); kill(fsrv->child_pid, SIGKILL);
fsrv->last_run_timed_out = 1; fsrv->last_run_timed_out = 1;
if (read(fsrv->fsrv_st_fd, &status, 4) < 4) { exec_ms = 0; } if (read(fsrv->fsrv_st_fd, &fsrv->child_status, 4) < 4) { exec_ms = 0; }
} }
@ -862,7 +860,7 @@ fsrv_run_result_t afl_fsrv_run_target(afl_forkserver_t *fsrv, u32 timeout,
} }
if (!WIFSTOPPED(status)) { fsrv->child_pid = 0; } if (!WIFSTOPPED(fsrv->child_status)) { fsrv->child_pid = 0; }
fsrv->total_execs++; fsrv->total_execs++;
@ -874,9 +872,9 @@ fsrv_run_result_t afl_fsrv_run_target(afl_forkserver_t *fsrv, u32 timeout,
/* Report outcome to caller. */ /* Report outcome to caller. */
if (WIFSIGNALED(status) && !*stop_soon_p) { if (WIFSIGNALED(fsrv->child_status) && !*stop_soon_p) {
fsrv->last_kill_signal = WTERMSIG(status); fsrv->last_kill_signal = WTERMSIG(fsrv->child_status);
if (fsrv->last_run_timed_out && fsrv->last_kill_signal == SIGKILL) { if (fsrv->last_run_timed_out && fsrv->last_kill_signal == SIGKILL) {
@ -891,7 +889,7 @@ fsrv_run_result_t afl_fsrv_run_target(afl_forkserver_t *fsrv, u32 timeout,
/* A somewhat nasty hack for MSAN, which doesn't support abort_on_error and /* A somewhat nasty hack for MSAN, which doesn't support abort_on_error and
must use a special exit code. */ must use a special exit code. */
if (fsrv->uses_asan && WEXITSTATUS(status) == MSAN_ERROR) { if (fsrv->uses_asan && WEXITSTATUS(fsrv->child_status) == MSAN_ERROR) {
fsrv->last_kill_signal = 0; fsrv->last_kill_signal = 0;
return FSRV_RUN_CRASH; return FSRV_RUN_CRASH;