AFL
AFL¶
AFL works like this
- Take Good input
- Mutate that input
- Trace the program.
- If it covers new code then add it to the list of mutation to test
- Go to 1.
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(¤t_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