Skip to content

AFL

AFL

AFL works like this

  1. Take Good input
  2. Mutate that input
  3. Trace the program.
  4. If it covers new code then add it to the list of mutation to test
  5. Go to 1.

Intro to AFL

AFL docker:

docker pull aflplusplus/aflplusplus
docker run -ti -v /opt/Hacking/CTF/Hacknight/05_2021/:/src aflplusplus/aflplusplus

Compile the binary with AFL:

CC=afl-cc CXX=afl-c++ make

Fuzzing the input with AFL:

afl-fuzz -i input -o output -m none -- ./getimg @@

Examples

Example on Damn Vulnerable C Program

Compiling with AFL:

>>> make
AFL_HARDEN=1 afl-gcc   -fsanitize=address -fno-omit-frame-pointer -fsanitize=undefined imgRead.c -o imgRead
afl-cc 2.57b by <[email protected]>
afl-as 2.57b by <[email protected]>
[+] Instrumented 39 locations (64-bit, hardened mode, ratio 33%).

Fuzzing the Program:

export AFL_SKIP_CPUFREQ=1 && afl-fuzz -i input -o output -m none -- ./imgRead @@

Output Information:

                       american fuzzy lop 2.57b (imgRead)

lq process timing qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqwq overall results qqqqqk
x        run time : 0 days, 0 hrs, 0 min, 24 sec       x  cycles done : 0      x
x   last new path : 0 days, 0 hrs, 0 min, 1 sec        x  total paths : 5      x
x last uniq crash : 0 days, 0 hrs, 0 min, 8 sec        x uniq crashes : 7      x
x  last uniq hang : none seen yet                      x   uniq hangs : 0      x
tq cycle progress qqqqqqqqqqqqqqqqqqqqwq map coverage qvqqqqqqqqqqqqqqqqqqqqqqqu
x  now processing : 2 (40.00%)        x    map density : 0.04% / 0.04%         x
x paths timed out : 0 (0.00%)         x count coverage : 1.48 bits/tuple       x
tq stage progress qqqqqqqqqqqqqqqqqqqqnq findings in depth qqqqqqqqqqqqqqqqqqqqu
x  now trying : havoc                 x favored paths : 2 (40.00%)             x
x stage execs : 2056/6144 (33.46%)    x  new edges on : 5 (100.00%)            x
x total execs : 10.6k                 x total crashes : 740 (7 unique)         x
x  exec speed : 227.6/sec             x  total tmouts : 685 (8 unique)         x
tq fuzzing strategy yields qqqqqqqqqqqvqqqqqqqqqqqqqqqwq path geometry qqqqqqqqu
x   bit flips : 0/224, 0/222, 0/218                   x    levels : 3          x
x  byte flips : 0/28, 0/26, 0/22                      x   pending : 4          x
x arithmetics : 0/1566, 0/230, 0/0                    x  pend fav : 1          x
x  known ints : 0/177, 0/705, 0/968                   x own finds : 4          x
x  dictionary : 0/0, 0/0, 0/0                         x  imported : n/a        x
x       havoc : 8/4096, 0/0                           x stability : 100.00%    x
x        trim : 27.27%/8, 0.00%                       tqqqqqqqqqqqqqqqqqqqqqqqqj
mqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqj          [cpu000: 26%]

Example on the Linux Kernel

Lets use the linux Kernel for an example of how to Fuzz with AFL. This information was taken from CloudFalir Documentation

Generate a Coverage Information for linux network:

#Clone the Linux Repo
git clone git://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
cd linux

#Copy the config for the current system
cp /boot/config-4.15.0-54-generic .config

#Then, enable KCOV, but disable KCOV_INSTRUMENT_ALL:
./scripts/config -e KCOV -d KCOV_INSTRUMENT_ALL

#Enable KCOV in all "net" subdirectory
find net -name Makefile | xargs -L1 -I {} bash -c 'echo "KCOV_INSTRUMENT := y" >> {}'

#More Conf parameters
./scripts/config -e DEBUG_FS -e DEBUG_INFO -e KALLSYMS -e KALLSYMS_ALL 
    -e NAMESPACES -e UTS_NS -e IPC_NS -e PID_NS -e NET_NS -e USER_NS    -e CGROUP_PIDS -e MEMCG -e CONFIGFS_FS -e SECURITYFS \
    -e KASAN -e KASAN_INLINE -e WARNING -e FAULT_INJECTION -e FAULT_INJECTION_DEBUG_FS \
    -e FAILSLAB -e FAIL_PAGE_ALLOC -e FAIL_MAKE_REQUEST -e FAIL_IO_TIMEOUT -e FAIL_FUTEX \
    -e LOCKDEP -e PROVE_LOCKING -e DEBUG_ATOMIC_SLEEP -e PROVE_RCU -e DEBUG_VM \
    -e REFCOUNT_FULL -e FORTIFY_SOURCE -e HARDENED_USERCOPY -e LOCKUP_DETECTOR \
    -e SOFTLOCKUP_DETECTOR -e HARDLOCKUP_DETECTOR -e BOOTPARAM_HARDLOCKUP_PANIC \
    -e DETECT_HUNG_TASK -e WQ_WATCHDOG --set-val DEFAULT_HUNG_TASK_TIMEOUT 140 \
    --set-val RCU_CPU_STALL_TIMEOUT 100 -e UBSAN -d RANDOMIZE_BASE

#More Conf parameters
./scripts/config \
    -e VIRTIO -e VIRTIO_PCI -e VIRTIO_MMIO -e NET -e NET_CORE -e NETDEVICES -e NETWORK_FILESYSTEMS \
    -e INET -e NET_9P -e NET_9P_VIRTIO -e 9P_FS -e VIRTIO_NET -e VIRTIO_CONSOLE \
    -e DEVTMPFS -e SCSI_VIRTIO -e BINFMT_SCRIPT -e TMPFS \
    -e UNIX -e TTY -e VT -e UNIX98_PTYS -e WATCHDOG -e WATCHDOG_CORE \
    -e I6300ESB_WDT -e BLOCK -e SCSI_gLOWLEVEL -e SCSI -e SCSI_VIRTIO \
    -e BLK_DEV_SD -e VIRTIO_BALLOON -d CMDLINE_OVERRIDE -d UEVENT_HELPER -d EMBEDDED -d EXPERT \
    -d MODULE_SIG_FORCE

#Specific network parameters
./scripts/config -e PACKET -e INET_UDP_DIAG -e INET_RAW_DIAG -e INET_DIAG_DESTROY \
    -e NETLINK_DIAG -e UNIX_DIAG -e BPF_SYSCALL -e VSOCKETS -e NLMON -e DUMMY -e TLS

Making Socket Fuzzing file for AFL:

#include <fcntl.h>
#include <getopt.h>
#include <linux/netlink.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>

#include "common.h"

struct state {
	int one_run;
	int verbose;
	int dump;
	int no_kcov;
	int netns;
	char *user_dmesg;
};

static void usage(const char *command)
{
	fprintf(stderr,
		"Usage:\n"
		"\n"
		"    %s [options]\n"
		"\n"
		"Options:\n"
		"\n"
		"  -v --verbose       Print stuff to stderr\n"
		"  -r --one-run       Exit after first data read\n"
		"  -d --dump          Dump KCOV offsets\n"
		"  -k --no-kcov       Don't attempt to run KCOV\n"
		"  -n --netns=N       Set up new a namespace every N tests\n"
		"  -m --dmesg=FILE    Copy /dev/kmsg into a file\n"
		"\n",
		command);
}

int main(int argc, char **argv)
{
	static struct option long_options[] = {
		{"verbose", no_argument, 0, 'v'},
		{"one-run", no_argument, 0, 'r'},
		{"dump", no_argument, 0, 'd'},
		{"help", no_argument, 0, 'h'},
		{"no-kcov", no_argument, 0, 'k'},
		{"netns", optional_argument, 0, 'n'},
		{"dmesg", required_argument, 0, 'm'},
		{NULL, 0, 0, 0}};

	const char *optstring = optstring_from_long_options(long_options);

	struct state *state = calloc(1, sizeof(struct state));

	optind = 1;
	while (1) {
		int option_index = 0;
		int arg = getopt_long(argc, argv, optstring, long_options,
				      &option_index);
		if (arg == -1) {
			break;
		}

		switch (arg) {
		case 0:
			fprintf(stderr, "Unknown option: %s", argv[optind]);
			exit(-1);
			break;
		case 'h':
			usage(argv[0]);
			exit(0);
			break;
		case '?':
			exit(-1);
			break;
		case 'v':
			state->verbose++;
			break;
		case 'r':
			state->one_run++;
			break;
		case 'd':
			state->dump++;
			break;
		case 'k':
			state->no_kcov++;
			break;
		case 'n':
			if (optarg) {
				state->netns = atoi(optarg);
			} else {
				state->netns++;
			}
			break;
		case 'm':
			state->user_dmesg = optarg;
			break;
		default:
			fprintf(stderr, "Unknown option %c: %s\n", arg,
				argv[optind]);
			exit(-1);
		}
	}

	uint32_t child_pid = getpid() + 1;

	struct forksrv *forksrv = forksrv_new();
	forksrv_welcome(forksrv);
	uint8_t *afl_area_ptr = forksrv_area_ptr(forksrv);

	struct kcov *kcov = NULL;
	uint64_t *kcov_cover_buf = NULL;
	if (state->no_kcov == 0) {
		kcov = kcov_new();
		kcov_cover_buf = kcov_cover(kcov);
	}

	if (state->verbose) {
		fprintf(stderr, "[.] Starting v=%d r=%d netns=%d\n",
			state->verbose, state->one_run, state->netns);
	}

	/* Read on dmesg /dev/kmsg for crashes. */
	int dmesg_fs = -1;
	dmesg_fs = open("/dev/kmsg", O_RDONLY | O_NONBLOCK);
	if (dmesg_fs < 0) {
		PFATAL("open(/dev/kmsg)");
	}
	lseek(dmesg_fs, 0, SEEK_END);

	/* Perhaps copy over dmesg data to user file */
	int user_dmesg_fs = -1;
	if (state->user_dmesg) {
		user_dmesg_fs = open(state->user_dmesg,
				     O_APPEND | O_WRONLY | O_CREAT, 0644);
		if (user_dmesg_fs < 0) {
			PFATAL("can't open %s for append", state->user_dmesg);
		}
		char hello[] = ",;: Restarting fuzzing\n";
		int x = write(user_dmesg_fs, hello, strlen(hello));
		(void)x;
	}

	/* MAIN LOOP */
	int run_no;
	int force_new_netns = 1;
	for (run_no = 0; 1; run_no += 1) {
		/* Convince AFL we started a child. */
		forksrv_cycle(forksrv, child_pid);

		/* Load input from AFL (stdin) */
		char buf[512 * 1024];
		memset(buf, 0, 32);
		int buf_len = read(0, buf, sizeof(buf));
		if (buf_len < 0) {
			PFATAL("read(stdin)");
		}
		if (buf_len < 5) {
			buf_len = 5;
		}
		if (state->verbose) {
			fprintf(stderr, "[.] %d bytes on input\n", buf_len);
		}

		int kcov_len = 0;

		/* Once every netns runs cycle network namespaces */
		if ((state->netns && (run_no % state->netns) == 0) ||
		    force_new_netns != 0) {
			netns_new();
			force_new_netns = 0;
		}

		/* START coverage collection on the current task. */
		if (kcov) {
			kcov_enable(kcov);
		}

		int netlink_fd =
			socket(AF_NETLINK, SOCK_RAW | SOCK_NONBLOCK, buf[0]);
		if (netlink_fd < 0) {
			goto error;
		}

		struct sockaddr_nl sa = {
			.nl_family = AF_NETLINK,
			.nl_groups = (buf[1] << 24) | (buf[2] << 16) |
				     (buf[3] << 8) | buf[4],
		};

		int r = bind(netlink_fd, (struct sockaddr *)&sa, sizeof(sa));
		if (r < 0) {
			goto error;
		}

		struct iovec iov = {&buf[5], buf_len - 5};
		struct sockaddr_nl sax = {
			.nl_family = AF_NETLINK,
		};
		struct msghdr msg = {&sax, sizeof(sax), &iov, 1, NULL, 0, 0};
		r = sendmsg(netlink_fd, &msg, 0);
		if (r != -1) {
			char buf[8192];
			struct iovec iov = {buf, sizeof(buf)};
			struct sockaddr_nl sa;
			struct msghdr msg = {&sa,  sizeof(sa), &iov, 1,
					     NULL, 0,	  0};
			recvmsg(netlink_fd, &msg, 0);
		}

	error:
		if (netlink_fd >= 0) {
			close(netlink_fd);
		}

		/* STOP coverage */
		if (kcov) {
			kcov_len = kcov_disable(kcov);
		}

		/* Read recorded %rip */
		int i;
		uint64_t afl_prev_loc = 0;
		for (i = 0; i < kcov_len; i++) {
			uint64_t current_loc = kcov_cover_buf[i + 1];
			uint64_t hash = hsiphash_static(&current_loc,
							sizeof(unsigned long));
			uint64_t mixed = (hash & 0xffff) ^ afl_prev_loc;
			afl_prev_loc = (hash & 0xffff) >> 1;

			uint8_t *s = &afl_area_ptr[mixed];
			int r = __builtin_add_overflow(*s, 1, s);
			if (r) {
				/* Boxing. AFL is fine with overflows,
				 * but we can be better. Drop down to
				 * 128 on overflow. */
				*s = 128;
			}

			if (state->dump) {
				printf("0x%016lx%s\n", current_loc, "");
			}
		}

		if (state->verbose) {
			fprintf(stderr, "[.] %d measurements\n", kcov_len);
		}

		/* Check dmesg if there was something interesting */
		int crashed = 0;
		while (1) {
			// /dev/kmsg gives us one line per read
			char buf[8192];
			int r = read(dmesg_fs, buf, sizeof(buf) - 1);
			if (r <= 0) {
				break;
			}
			if (state->user_dmesg) {
				int x = write(user_dmesg_fs, buf, r);
				(void)x;
			}

			buf[r] = '\x00';
			if (strstr(buf, "Call Trace") != NULL ||
			    strstr(buf, "RIP:") != NULL ||
			    strstr(buf, "Code:") != NULL) {
				crashed += 1;
			}
		}
		if (crashed) {
			fprintf(stderr, "[!] BUG detected\n");
			forksrv_status(forksrv, 139);
			force_new_netns = 1;
		} else {
			forksrv_status(forksrv, 0);
		}

		if (state->one_run) {
			break;
		}
	}

	forksrv_free(forksrv);
	if (kcov) {
		kcov_free(kcov);
	}
	return 0;
}

Running AFL in a KVM for the Linux:

mkdir inp
echo "hello world" > inp/01.txt
virtme-run --kimg bzImage --rw --pwd --memory 512M --script-sh "./afl-fuzz -i inp -o out -- fuzznetlink" 

Fuzzing to Exploitation

This is an example of the data from Drew's Hacknight box

Collect Output data for AFL crashes and testing for exploitabale input:

>>> afl-collect -d crashes.db -e testscript -r -rr ./output2/default ./test -- ./getimg @@
afl-collect 1.34a by rc0r <[email protected]> # @_rc0r
Crash sample collection and processing utility for afl-fuzz.

[*] Going to collect crash samples from '/opt/Hacking/CTF/Hacknight/05_2021/output2/default'.
[!] Using existing database to store results, 31 entries in this database so far.
[*] Found 1 fuzzers, collecting crash samples.
[*] Successfully indexed 4 crash samples.
[*] Saving invalid sample info to database.
[!] Removed 0 invalid crash samples from index.
[!] Removed 0 timed out samples from index.
[*] Generating intermediate gdb+exploitable script '/opt/Hacking/CTF/Hacknight/05_2021/test/testscript.0' for 4 samples...
[*] Executing gdb+exploitable script 'testscript.0'...
*** GDB+EXPLOITABLE SCRIPT OUTPUT ***
[00001] default:id:000011,sig:11,src:000268,time:158836,op:havoc,rep:2..: EXPLOITABLE [ReturnAv (1/22)]
[00002] default:id:000012,sig:11,src:000212+000019,time:164373,op:splice,rep:16: UNKNOWN [SourceAv (19/22)]
[00003] default:id:000013,sig:11,src:000258+000145,time:180867,op:splice,rep:16: EXPLOITABLE [ReturnAv (1/22)]
[00004] default:id:000014,sig:11,src:000198,time:190087,op:havoc,rep:4..: UNKNOWN [SourceAv (19/22)]
*** ***************************** ***
[*] Saving sample classification info to database.
[!] Removed 1 duplicate samples from index. Will continue with 3 remaining samples.
[!] Removed 0 uninteresting crash samples from index.
[*] Generating final gdb+exploitable script '/opt/Hacking/CTF/Hacknight/05_2021/test/testscript' for 3 samples...
[*] Copying 3 samples into output directory...

Finishing the Exploit

GDB info for the Crash:

Program received signal SIGSEGV, Segmentation fault.
0x00000000004034ba in upng_header (upng=0x4082a0) at upng.c:962
962	}
[ Legend: Modified register | Code | Heap | Stack | String ]
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── registers ────
$rax   : 0x0               
$rbx   : 0x0000000000403f60    <__libc_csu_init+0> endbr64 
$rcx   : 0x0               
$rdx   : 0x00007fffffffdc0d    0x0000000000000000
$rsp   : 0x00007fffffffdba8    0x2d10e50e50101401
$rbp   : 0xdc24e6cddd8e67cf
$rsi   : 0x000000000040950a    0xf5147c799ded9c78
$rdi   : 0x0               
$rip   : 0x00000000004034ba    <upng_header+936> ret 
$r8    : 0x0               
$r9    : 0x00007fffffffda10    0x0000000000000000
$r10   : 0xfffffffffffff65d
$r11   : 0x00007ffff7e1d660    <snprintf+0> endbr64 
$r12   : 0x0000000000401120    <_start+0> endbr64 
$r13   : 0x0               
$r14   : 0x0               
$r15   : 0x0               
$eflags: [zero carry PARITY adjust sign trap INTERRUPT direction overflow RESUME virtualx86 identification]
$cs: 0x0033 $ss: 0x002b $ds: 0x0000 $es: 0x0000 $fs: 0x0000 $gs: 0x0000 
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── stack ────
0x00007fffffffdba8│+0x0000: 0x2d10e50e50101401	  $rsp
0x00007fffffffdbb0│+0x0008: 0xa6cb5151155e091e
0x00007fffffffdbb8│+0x0010: 0xeb5aa8f16d52d628
0x00007fffffffdbc0│+0x0018: 0x83d69376dfadab57
0x00007fffffffdbc8│+0x0020: 0x3f8ad8bf5abd6ad6
0x00007fffffffdbd0│+0x0028: 0x62ab5b55b1355b6b
0x00007fffffffdbd8│+0x0030: 0x84ee672a960a21a1
0x00007fffffffdbe0│+0x0038: 0x673dce4204201370
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── code:x86:64 ────
     0x4034af <upng_header+925> call   0x4010a0 <snprintf@plt>
     0x4034b4 <upng_header+930> mov    eax, 0x0
     0x4034b9 <upng_header+935> leave  
    0x4034ba <upng_header+936> ret    
[!] Cannot disassemble from $PC
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── source:upng.c+962 ────
    957	 	}
    958	 
    959	 	upng->state = UPNG_HEADER;
    960	     snprintf(source_header, "%s", upng->source.buffer+26, len);
    961	 	return UPNG_EOK;
   962	 }
    963	 
    964	 /*read a PNG, the result will be in the same color type as the PNG (hence "generic")*/
    965	 upng_error upng_decode(upng_t* upng)
    966	 {
    967	 	const unsigned char *chunk;
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "getimg", stopped, reason: SIGSEGV

Checking for Bytes in the input image:

>>> xxd test/default:id:000008,sig:11,src:000243,time:97695,op:havoc,rep:8 | grep "102d"
00000030: 1050 0ee5 102d 1e09 5e15 5151 cba6 28d6  .P...-..^.QQ..(.

Get the address that it returns to and replace it with the win function

Testing the new image:

>>> ./getimg test/crash_edit.png 
cat: /root/flag.txt: No such file or directory