diff options
author | Andrea Fioraldi <andreafioraldi@gmail.com> | 2020-02-03 13:11:10 +0100 |
---|---|---|
committer | Andrea Fioraldi <andreafioraldi@gmail.com> | 2020-02-03 13:11:10 +0100 |
commit | 2fe7889912c9bb340f302a037585b7b1836ac94f (patch) | |
tree | 5c3e4e5829f45dce46794ebc2681732738d689fe /examples | |
parent | e2eedefc65bec1a04605f117a11ca8bdf9d80323 (diff) | |
download | afl++-2fe7889912c9bb340f302a037585b7b1836ac94f.tar.gz |
move custom and pythoon mutators examples into examples/
Diffstat (limited to 'examples')
26 files changed, 2139 insertions, 0 deletions
diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 00000000..512b03f7 --- /dev/null +++ b/examples/README.md @@ -0,0 +1,47 @@ +# AFL++ Examples + +Here's a quick overview of the stuff you can find in this directory: + + - custom_mutstors - An example custom mutator + + - python_mutators - Python mutators examples + + - argv_fuzzing - a simple wrapper to allow cmdline to be fuzzed + (e.g., to test setuid programs). + + - asan_cgroups - a contributed script to simplify fuzzing ASAN + binaries with robust memory limits on Linux. + + - bash_shellshock - a simple hack used to find a bunch of + post-Shellshock bugs in bash. + + - canvas_harness - a test harness used to find browser bugs with a + corpus generated using simple image parsing + binaries & afl-fuzz. + + - clang_asm_normalize - a script that makes it easy to instrument + hand-written assembly, provided that you have clang. + + - crash_triage - a very rudimentary example of how to annotate crashes + with additional gdb metadata. + + - distributed_fuzzing - a sample script for synchronizing fuzzer instances + across multiple machines (see parallel_fuzzing.md). + + - libpng_no_checksum - a sample patch for removing CRC checks in libpng. + + - persistent_demo - an example of how to use the LLVM persistent process + mode to speed up certain fuzzing jobs. + + - post_library - an example of how to build postprocessors for AFL. + + - socket_fuzzing - a LD_PRELOAD library 'redirects' a socket to stdin + for fuzzing access with afl++ + +Note that the minimize_corpus.sh tool has graduated from the experimental/ +directory and is now available as ../afl-cmin. The LLVM mode has likewise +graduated to ../llvm_mode/*. + +Most of the tools in this directory are meant chiefly as examples that need to +be tweaked for your specific needs. They come with some basic documentation, +but are not necessarily production-grade. diff --git a/examples/argv_fuzzing/Makefile b/examples/argv_fuzzing/Makefile new file mode 100644 index 00000000..ab16be87 --- /dev/null +++ b/examples/argv_fuzzing/Makefile @@ -0,0 +1,41 @@ +# +# american fuzzy lop++ - argvfuzz +# -------------------------------- +# +# Copyright 2019-2020 Kjell Braden <afflux@pentabarf.de> +# +# 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 +# + +.PHONY: all install clean + +PREFIX ?= /usr/local +BIN_PATH = $(PREFIX)/bin +HELPER_PATH = $(PREFIX)/lib/afl + +CFLAGS = -fPIC -Wall -Wextra +LDFLAGS = -shared + +ifneq "$(filter Linux GNU%,$(shell uname))" "" + LDFLAGS += -ldl +endif + +all: argvfuzz32.so argvfuzz64.so + +argvfuzz32.so: argvfuzz.c + -$(CC) -m32 $(CFLAGS) $^ $(LDFLAGS) -o $@ || echo "argvfuzz32 build failure (that's fine)" + +argvfuzz64.so: argvfuzz.c + -$(CC) $(CFLAGS) $^ $(LDFLAGS) -o $@ + +install: argvfuzz32.so argvfuzz64.so + install -d -m 755 $(DESTDIR)$(HELPER_PATH)/ + if [ -f argvfuzz32.so ]; then set -e; install -m 755 argvfuzz32.so $(DESTDIR)$(HELPER_PATH)/; fi + install -m 755 argvfuzz64.so $(DESTDIR)$(HELPER_PATH)/ + +clean: + rm -f argvfuzz32.so argvfuzz64.so diff --git a/examples/argv_fuzzing/README.md b/examples/argv_fuzzing/README.md new file mode 100644 index 00000000..fa8cad80 --- /dev/null +++ b/examples/argv_fuzzing/README.md @@ -0,0 +1,16 @@ +# argvfuzz + +afl supports fuzzing file inputs or stdin. When source is available, +`argv-fuzz-inl.h` can be used to change `main()` to build argv from stdin. + +`argvfuzz` tries to provide the same functionality for binaries. When loaded +using `LD_PRELOAD`, it will hook the call to `__libc_start_main` and replace +argv using the same logic of `argv-fuzz-inl.h`. + +A few conditions need to be fulfilled for this mechanism to work correctly: + +1. As it relies on hooking the loader, it cannot work on static binaries. +2. If the target binary does not use the default libc's `_start` implementation + (crt1.o), the hook may not run. +3. The hook will replace argv with pointers to `.data` of `argvfuzz.so`. If the + target binary expects argv to be living on the stack, things may go wrong. diff --git a/examples/argv_fuzzing/argv-fuzz-inl.h b/examples/argv_fuzzing/argv-fuzz-inl.h new file mode 100644 index 00000000..4d880020 --- /dev/null +++ b/examples/argv_fuzzing/argv-fuzz-inl.h @@ -0,0 +1,90 @@ +/* + american fuzzy lop++ - sample argv fuzzing wrapper + ------------------------------------------------ + + Originally written by Michal Zalewski + + Copyright 2015 Google Inc. 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 + + This file shows a simple way to fuzz command-line parameters with stock + afl-fuzz. To use, add: + + #include "/path/to/argv-fuzz-inl.h" + + ...to the file containing main(), ideally placing it after all the + standard includes. Next, put AFL_INIT_ARGV(); near the very beginning of + main(). + + This will cause the program to read NUL-delimited input from stdin and + put it in argv[]. Two subsequent NULs terminate the array. Empty + params are encoded as a lone 0x02. Lone 0x02 can't be generated, but + that shouldn't matter in real life. + + If you would like to always preserve argv[0], use this instead: + AFL_INIT_SET0("prog_name"); + +*/ + +#ifndef _HAVE_ARGV_FUZZ_INL +#define _HAVE_ARGV_FUZZ_INL + +#include <unistd.h> + +#define AFL_INIT_ARGV() \ + do { \ + \ + argv = afl_init_argv(&argc); \ + \ + } while (0) + +#define AFL_INIT_SET0(_p) \ + do { \ + \ + argv = afl_init_argv(&argc); \ + argv[0] = (_p); \ + if (!argc) argc = 1; \ + \ + } while (0) + +#define MAX_CMDLINE_LEN 100000 +#define MAX_CMDLINE_PAR 1000 + +static char** afl_init_argv(int* argc) { + + static char in_buf[MAX_CMDLINE_LEN]; + static char* ret[MAX_CMDLINE_PAR]; + + char* ptr = in_buf; + int rc = 0; + + if (read(0, in_buf, MAX_CMDLINE_LEN - 2) < 0) {} + + while (*ptr) { + + ret[rc] = ptr; + if (ret[rc][0] == 0x02 && !ret[rc][1]) ret[rc]++; + rc++; + + while (*ptr) + ptr++; + ptr++; + + } + + *argc = rc; + + return ret; + +} + +#undef MAX_CMDLINE_LEN +#undef MAX_CMDLINE_PAR + +#endif /* !_HAVE_ARGV_FUZZ_INL */ + diff --git a/examples/argv_fuzzing/argvfuzz.c b/examples/argv_fuzzing/argvfuzz.c new file mode 100644 index 00000000..4251ca4c --- /dev/null +++ b/examples/argv_fuzzing/argvfuzz.c @@ -0,0 +1,49 @@ +/* + american fuzzy lop++ - LD_PRELOAD for fuzzing argv in binaries + ------------------------------------------------------------ + + Copyright 2019-2020 Kjell Braden <afflux@pentabarf.de> + + 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 + + */ + +#define _GNU_SOURCE /* for RTLD_NEXT */ +#include <dlfcn.h> +#include <stdlib.h> +#include <stdio.h> +#include <unistd.h> +#include "argv-fuzz-inl.h" + +int __libc_start_main(int (*main)(int, char **, char **), int argc, char **argv, + void (*init)(void), void (*fini)(void), + void (*rtld_fini)(void), void *stack_end) { + + int (*orig)(int (*main)(int, char **, char **), int argc, char **argv, + void (*init)(void), void (*fini)(void), void (*rtld_fini)(void), + void *stack_end); + int sub_argc; + char **sub_argv; + + (void)argc; + (void)argv; + + orig = dlsym(RTLD_NEXT, __func__); + + if (!orig) { + + fprintf(stderr, "hook did not find original %s: %s\n", __func__, dlerror()); + exit(EXIT_FAILURE); + + } + + sub_argv = afl_init_argv(&sub_argc); + + return orig(main, sub_argc, sub_argv, init, fini, rtld_fini, stack_end); + +} + diff --git a/examples/asan_cgroups/limit_memory.sh b/examples/asan_cgroups/limit_memory.sh new file mode 100755 index 00000000..ac3a90fe --- /dev/null +++ b/examples/asan_cgroups/limit_memory.sh @@ -0,0 +1,157 @@ +#!/usr/bin/env bash +# +# american fuzzy lop++ - limit memory using cgroups +# ----------------------------------------------- +# +# Written by Samir Khakimov <samir.hakim@nyu.edu> and +# David A. Wheeler <dwheeler@ida.org> +# +# Edits to bring the script in line with afl-cmin and other companion scripts +# by Michal Zalewski. All bugs are my fault. +# +# Copyright 2015 Institute for Defense Analyses. +# +# 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 +# +# This tool allows the amount of actual memory allocated to a program +# to be limited on Linux systems using cgroups, instead of the traditional +# setrlimit() API. This helps avoid the address space problems discussed in +# docs/notes_for_asan.txt. +# +# Important: the limit covers *both* afl-fuzz and the fuzzed binary. In some +# hopefully rare circumstances, afl-fuzz could be killed before the fuzzed +# task. +# + +echo "cgroup tool for afl-fuzz by <samir.hakim@nyu.edu> and <dwheeler@ida.org>" +echo + +unset NEW_USER +MEM_LIMIT="50" + +while getopts "+u:m:" opt; do + + case "$opt" in + + "u") + NEW_USER="$OPTARG" + ;; + + "m") + MEM_LIMIT="$[OPTARG]" + ;; + + "?") + exit 1 + ;; + + esac + +done + +if [ "$MEM_LIMIT" -lt "5" ]; then + echo "[-] Error: malformed or dangerously low value of -m." 1>&2 + exit 1 +fi + +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... ] + +Required parameters: + + -u user - run the fuzzer as a specific user after setting up limits + +Optional parameters: + + -m megs - set memory limit to a specified value ($MEM_LIMIT MB) + +This tool configures cgroups-based memory limits for a fuzzing job to simplify +the task of fuzzing ASAN or MSAN binaries. You would normally want to use it in +conjunction with '-m none' passed to the afl-fuzz binary itself, say: + + $0 -u joe ./afl-fuzz -i input -o output -m none /path/to/target + +_EOF_ + + exit 1 + +fi + +# Basic sanity checks + +if [ ! "`uname -s`" = "Linux" ]; then + echo "[-] Error: this tool does not support non-Linux systems." 1>&2 + exit 1 +fi + +if [ ! "`id -u`" = "0" ]; then + echo "[-] Error: you need to run this script as root (sorry!)." 1>&2 + exit 1 +fi + +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-$$" + +CPATH="/sys/fs/cgroup/memory/$CID" + +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. + +cgexec -g "memory:$CID" su -c "$*" "$NEW_USER" + +cgdelete -g "memory:$CID" diff --git a/examples/bash_shellshock/shellshock-fuzz.diff b/examples/bash_shellshock/shellshock-fuzz.diff new file mode 100644 index 00000000..3fa05bf8 --- /dev/null +++ b/examples/bash_shellshock/shellshock-fuzz.diff @@ -0,0 +1,59 @@ +This patch shows a very simple way to find post-Shellshock bugs in bash, as +discussed here: + + http://lcamtuf.blogspot.com/2014/10/bash-bug-how-we-finally-cracked.html + +In essence, it shows a way to fuzz environmental variables. Instructions: + +1) Download bash 4.3, apply this patch, compile with: + + CC=/path/to/afl-gcc ./configure + make clean all + + Note that the harness puts the fuzzed output in $TEST_VARIABLE. With + Florian's Shellshock patch (bash43-028), this is no longer passed down + to the parser. + +2) Create and cd to an empty directory, put the compiled bash binary in + there, and run these commands: + + mkdir in_dir + echo -n '() { a() { a; }; : >b; }' >in_dir/script.txt + +3) Run the fuzzer with: + + /path/to/afl-fuzz -d -i in_dir -o out_dir ./bash -c : + + The -d parameter is advisable only if the tested shell is fairly slow + or if you are in a hurry; will cover more ground faster, but + less systematically. + +4) Watch for crashes in out_dir/crashes/. Also watch for any new files + created in cwd if you're interested in non-crash RCEs (files will be + created whenever the shell executes "foo>bar" or something like + that). You can correlate their creation date with new entries in + out_dir/queue/. + + You can also modify the bash binary to directly check for more subtle + fault conditions, or use the synthesized entries in out_dir/queue/ + as a seed for other, possibly slower or more involved testing regimes. + + Expect several hours to get decent coverage. + +--- bash-4.3/shell.c.orig 2014-01-14 14:04:32.000000000 +0100 ++++ bash-4.3/shell.c 2015-04-30 05:56:46.000000000 +0200 +@@ -371,6 +371,14 @@ + env = environ; + #endif /* __OPENNT */ + ++ { ++ ++ static char val[1024 * 16]; ++ read(0, val, sizeof(val) - 1); ++ setenv("TEST_VARIABLE", val, 1); ++ ++ } ++ + USE_VAR(argc); + USE_VAR(argv); + USE_VAR(env); diff --git a/examples/canvas_harness/canvas_harness.html b/examples/canvas_harness/canvas_harness.html new file mode 100644 index 00000000..a37b6937 --- /dev/null +++ b/examples/canvas_harness/canvas_harness.html @@ -0,0 +1,170 @@ +<html> +<!-- + + american fuzzy lop++ - <canvas> harness + ------------------------------------- + + Originally written by Michal Zalewski + + Copyright 2013, 2014 Google Inc. 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 + + A simple harness for going through afl-generated test cases, rendering them in + the browser environment, and discovering the use of uninitialized memory and + similar bugs. This code led to the discovery of a fair number of library and + browser security bugs! + + The url_list[] array is a placeholder; for this to work properly, it needs to + be initialized with web-reachable paths to individual test cases. This can + be done manually or with a simple script. + +--> + +<body onload="set_images()"> + +<div id="status"></div> + +<div id="image_div"></div> + +<canvas height=64 width=64 id=cvs></canvas> + +<h2>Results</h2> + +<ul id="output"></ul> + +<script> + +var c = document.getElementById('cvs'); +var ctx = c.getContext('2d'); + +var url_list = [ + "images/id:000000,[...].jpg", + "images/id:000001,[...].jpg", + /* ... */ + null +]; + +var USE_IMAGES = 50; +var cur_image = 0; + +if (location.hash) cur_image = parseInt(location.hash.substr(1)); + +var loaded = 0; +var image_obj = []; + +var msie_cleanup; + +function check_results() { + + var uniques = []; + + clearTimeout(msie_cleanup); + + ctx.clearRect(0, 0, 64, 64); + + uniques.push(image_obj[0].imgdata); + + for (var i = 1; i < USE_IMAGES; i++) { + + if (!image_obj[i].imgdata) continue; + + if (image_obj[0].imgdata != image_obj[i].imgdata) { + + for (var j = 1; j < uniques.length; j++) + if (uniques[j] == image_obj[i].imgdata) break; + + if (j == uniques.length) uniques.push(image_obj[i].imgdata); + + + } + + } + + if (uniques.length > 1) { + + var str = '<li> Image ' + url_list[cur_image] + ' has ' + uniques.length + ' variants: '; + + for (var i = 0; i < uniques.length; i++) + str += '<img src="' + uniques[i] + '">'; + + document.getElementById('output').innerHTML += str; + + } + + cur_image++; + set_images(); +} + + +function count_image() { + + if (!this.complete || this.counted) return; + + this.counted = true; + + loaded++; + + ctx.clearRect(0, 0, 64, 64); + + try { + ctx.drawImage(this, 0, 0, 64, 64); + } catch (e) { } + + this.imgdata = c.toDataURL(); + + if (loaded == USE_IMAGES) check_results(); +} + + +function set_images() { + + loaded = 0; + + document.getElementById('status').innerHTML = 'Now processing ' + cur_image + '...'; + location.hash = '#' + cur_image; + + if (url_list[cur_image] == null) { + alert('Done!'); + return; + } + + restart_images(); + + msie_cleanup = setTimeout(check_results, 5000); + + for (var i = 0; i < USE_IMAGES; i++) + image_obj[i].src = url_list[cur_image] + '?' + Math.random(); + +} + + +function restart_images() { + + for (var i = 0; i < USE_IMAGES; i++) + if (image_obj[i]) image_obj[i].counted = true; + + document.getElementById('image_div').innerHTML = ''; + image_obj = []; + + for (var i = 0; i < USE_IMAGES; i++) { + + image_obj[i] = new Image(); + image_obj[i].height = 64; + image_obj[i].width = 64; + image_obj[i].onerror = count_image; + image_obj[i].onload = count_image; + + document.getElementById('image_div').appendChild(image_obj[i]); + + } + +} + +</script> + +<iframe src='http://www.cnn.com/'></iframe> diff --git a/examples/clang_asm_normalize/as b/examples/clang_asm_normalize/as new file mode 100755 index 00000000..45537cae --- /dev/null +++ b/examples/clang_asm_normalize/as @@ -0,0 +1,75 @@ +#!/bin/sh +# +# american fuzzy lop++ - clang assembly normalizer +# ---------------------------------------------- +# +# Originally written by Michal Zalewski +# The idea for this wrapper comes from Ryan Govostes. +# +# Copyright 2013, 2014 Google Inc. 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 +# +# This 'as' wrapper should allow you to instrument unruly, hand-written +# assembly with afl-as. +# +# Usage: +# +# export AFL_REAL_PATH=/path/to/directory/with/afl-as/ +# AFL_PATH=/path/to/this/directory/ make clean all + +if [ "$#" -lt "2" ]; then + echo "[-] Error: this utility can't be called directly." 1>&2 + exit 1 +fi + +if [ "$AFL_REAL_PATH" = "" ]; then + echo "[-] Error: AFL_REAL_PATH not set!" 1>&2 + exit 1 +fi + +if [ ! -x "$AFL_REAL_PATH/afl-as" ]; then + echo "[-] Error: AFL_REAL_PATH does not contain the 'afl-as' binary." 1>&2 + exit 1 +fi + +unset __AFL_AS_CMDLINE __AFL_FNAME + +while [ ! "$#" = "0" ]; do + + if [ "$#" = "1" ]; then + __AFL_FNAME="$1" + else + __AFL_AS_CMDLINE="${__AFL_AS_CMDLINE} $1" + fi + + shift + +done + +test "$TMPDIR" = "" && TMPDIR=/tmp + +TMPFILE=`mktemp $TMPDIR/.afl-XXXXXXXXXX.s` + +test "$TMPFILE" = "" && exit 1 + +clang -cc1as -filetype asm -output-asm-variant 0 "${__AFL_FNAME}" >"$TMPFILE" + +ERR="$?" + +if [ ! "$ERR" = "0" ]; then + rm -f "$TMPFILE" + exit $ERR +fi + +"$AFL_REAL_PATH/afl-as" ${__AFL_AS_CMDLINE} "$TMPFILE" + +ERR="$?" + +rm -f "$TMPFILE" + +exit "$ERR" diff --git a/examples/crash_triage/triage_crashes.sh b/examples/crash_triage/triage_crashes.sh new file mode 100755 index 00000000..6d026d61 --- /dev/null +++ b/examples/crash_triage/triage_crashes.sh @@ -0,0 +1,115 @@ +#!/bin/sh +# +# american fuzzy lop++ - crash triage utility +# ----------------------------------------- +# +# Originally written by Michal Zalewski +# +# Copyright 2013, 2014, 2017 Google Inc. 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 +# +# Note that this assumes that the targeted application reads from stdin +# and requires no other cmdline parameters. Modify as needed if this is +# not the case. +# +# Note that on OpenBSD, you may need to install a newer version of gdb +# (e.g., from ports). You can set GDB=/some/path to point to it if +# necessary. +# + +echo "crash triage utility for afl-fuzz by Michal Zalewski" +echo + +ulimit -v 100000 2>/dev/null +ulimit -d 100000 2>/dev/null + +if [ "$#" -lt "2" ]; then + echo "Usage: $0 /path/to/afl_output_dir /path/to/tested_binary [...target params...]" 1>&2 + echo 1>&2 + exit 1 +fi + +DIR="$1" +BIN="$2" +shift +shift + +if [ "$AFL_ALLOW_TMP" = "" ]; then + + echo "$DIR" | grep -qE '^(/var)?/tmp/' + T1="$?" + + echo "$BIN" | grep -qE '^(/var)?/tmp/' + T2="$?" + + if [ "$T1" = "0" -o "$T2" = "0" ]; then + echo "[-] Error: do not use shared /tmp or /var/tmp directories with this script." 1>&2 + exit 1 + fi + +fi + +if + [ "$GDB" = "" ]; then + GDB=gdb +fi + +if [ ! -f "$BIN" -o ! -x "$BIN" ]; then + echo "[-] Error: binary '$2' not found or is not executable." 1>&2 + exit 1 +fi + +if [ ! -d "$DIR/queue" ]; then + echo "[-] Error: directory '$1' not found or not created by afl-fuzz." 1>&2 + exit 1 +fi + +CCOUNT=$((`ls -- "$DIR/crashes" 2>/dev/null | wc -l`)) + +if [ "$CCOUNT" = "0" ]; then + echo "No crashes recorded in the target directory - nothing to be done." + exit 0 +fi + +echo + +for crash in $DIR/crashes/id:*; do + + id=`basename -- "$crash" | cut -d, -f1 | cut -d: -f2` + sig=`basename -- "$crash" | cut -d, -f2 | cut -d: -f2` + + # Grab the args, converting @@ to $crash + + use_args="" + use_stdio=1 + + for a in $@; do + + if [ "$a" = "@@" ] ; then + args="$use_args $crash" + unset use_stdio + else + args="$use_args $a" + fi + + done + + # Strip the trailing space + use_args="${use_args# }" + + echo "+++ ID $id, SIGNAL $sig +++" + echo + + if [ "$use_stdio" = "1" ]; then + $GDB --batch -q --ex "r $use_args <$crash" --ex 'back' --ex 'disass $pc, $pc+16' --ex 'info reg' --ex 'quit' "$BIN" 0</dev/null + else + $GDB --batch -q --ex "r $use_args" --ex 'back' --ex 'disass $pc, $pc+16' --ex 'info reg' --ex 'quit' "$BIN" 0</dev/null + fi + echo + +done diff --git a/examples/custom_mutators/README b/examples/custom_mutators/README new file mode 100644 index 00000000..e83baa67 --- /dev/null +++ b/examples/custom_mutators/README @@ -0,0 +1,2 @@ +This is a simple example for the AFL_CUSTOM_MUTATOR_LIBRARY feature. +For more information see docs/custom_mutator.txt diff --git a/examples/custom_mutators/simple_mutator.c b/examples/custom_mutators/simple_mutator.c new file mode 100644 index 00000000..bf655679 --- /dev/null +++ b/examples/custom_mutators/simple_mutator.c @@ -0,0 +1,49 @@ +/* + Simple Custom Mutator for AFL + + Written by Khaled Yakdan <yakdan@code-intelligence.de> + + This a simple mutator that assumes that the generates messages starting with + one of the three strings GET, PUT, or DEL followed by a payload. The mutator + randomly selects a commend and mutates the payload of the seed provided as + input. +*/ + +#include <stdint.h> +#include <stdlib.h> +#include <string.h> + +static const char *commands[] = { + + "GET", + "PUT", + "DEL", + +}; + +static size_t data_size = 100; + +size_t afl_custom_mutator(uint8_t *data, size_t size, uint8_t *mutated_out, + size_t max_size, unsigned int seed) { + + // Seed the PRNG + srand(seed); + + // Make sure that the packet size does not exceed the maximum size expected by + // the fuzzer + size_t mutated_size = data_size <= max_size ? data_size : max_size; + + // Randomly select a command string to add as a header to the packet + memcpy(mutated_out, commands[rand() % 3], 3); + + // Mutate the payload of the packet + for (int i = 3; i < mutated_size; i++) { + + mutated_out[i] = (data[i] + rand() % 10) & 0xff; + + } + + return mutated_size; + +} + diff --git a/examples/distributed_fuzzing/sync_script.sh b/examples/distributed_fuzzing/sync_script.sh new file mode 100755 index 00000000..c45ae69b --- /dev/null +++ b/examples/distributed_fuzzing/sync_script.sh @@ -0,0 +1,94 @@ +#!/bin/sh +# +# american fuzzy lop++ - fuzzer synchronization tool +# -------------------------------------------------- +# +# Originally written by Michal Zalewski +# +# Copyright 2014 Google Inc. All rights reserved. +# Copyright 2019-2020 AFLplusplus Project. 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 +# +# To make this script work: +# +# - Edit FUZZ_HOSTS, FUZZ_DOMAIN, FUZZ_USER, and SYNC_DIR to reflect your +# environment. +# +# - Make sure that the system you are running this on can log into FUZZ_HOSTS +# without a password (authorized_keys or otherwise). +# +# - Make sure that every fuzzer is running with -o pointing to SYNC_DIR and -S +# that consists of its local host name, followed by an underscore, and then +# by some host-local fuzzer ID. +# + +# Hosts to synchronize the data across. +FUZZ_HOSTS='host1 host2 host3 host4' + +# Domain for all hosts +FUZZ_DOMAIN='example.com' + +# Remote user for SSH +FUZZ_USER=bob + +# Directory to synchronize +SYNC_DIR='/home/bob/sync_dir' + +# Interval (seconds) between sync attempts +SYNC_INTERVAL=$((30 * 60)) + +if [ "$AFL_ALLOW_TMP" = "" ]; then + + if [ "$PWD" = "/tmp" -o "$PWD" = "/var/tmp" ]; then + echo "[-] Error: do not use shared /tmp or /var/tmp directories with this script." 1>&2 + exit 1 + fi + +fi + +rm -rf .sync_tmp 2>/dev/null +mkdir .sync_tmp || exit 1 + +while :; do + + # Pull data in... + + for host in $FUZZ_HOSTS; do + + echo "[*] Retrieving data from ${host}.${FUZZ_DOMAIN}..." + + ssh -o 'passwordauthentication no' ${FUZZ_USER}@${host}.$FUZZ_DOMAIN \ + "cd '$SYNC_DIR' && tar -czf - ${host}_*/[qf]*" >".sync_tmp/${host}.tgz" + + done + + # Distribute data. For large fleets, see tips in the docs/ directory. + + for dst_host in $FUZZ_HOSTS; do + + echo "[*] Distributing data to ${dst_host}.${FUZZ_DOMAIN}..." + + for src_host in $FUZZ_HOSTS; do + + test "$src_host" = "$dst_host" && continue + + echo " Sending fuzzer data from ${src_host}.${FUZZ_DOMAIN}..." + + ssh -o 'passwordauthentication no' ${FUZZ_USER}@$dst_host \ + "cd '$SYNC_DIR' && tar -xkzf -" <".sync_tmp/${src_host}.tgz" + + done + + done + + echo "[+] Done. Sleeping for $SYNC_INTERVAL seconds (Ctrl-C to quit)." + + sleep $SYNC_INTERVAL + +done + diff --git a/examples/libpng_no_checksum/libpng-nocrc.patch b/examples/libpng_no_checksum/libpng-nocrc.patch new file mode 100644 index 00000000..0a3793a0 --- /dev/null +++ b/examples/libpng_no_checksum/libpng-nocrc.patch @@ -0,0 +1,15 @@ +--- pngrutil.c.orig 2014-06-12 03:35:16.000000000 +0200 ++++ pngrutil.c 2014-07-01 05:08:31.000000000 +0200 +@@ -268,7 +268,11 @@ + if (need_crc != 0) + { + crc = png_get_uint_32(crc_bytes); +- return ((int)(crc != png_ptr->crc)); ++ ++ if (crc != png_ptr->crc) ++ fprintf(stderr, "NOTE: CRC in the file is 0x%08x, change to 0x%08x\n", crc, png_ptr->crc); ++ ++ return ((int)(1 != 1)); + } + + else diff --git a/examples/persistent_demo/persistent_demo.c b/examples/persistent_demo/persistent_demo.c new file mode 100644 index 00000000..7d8638fb --- /dev/null +++ b/examples/persistent_demo/persistent_demo.c @@ -0,0 +1,101 @@ +/* + american fuzzy lop++ - persistent mode example + -------------------------------------------- + + Originally written by Michal Zalewski + + Copyright 2015 Google Inc. 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 + + This file demonstrates the high-performance "persistent mode" that may be + suitable for fuzzing certain fast and well-behaved libraries, provided that + they are stateless or that their internal state can be easily reset + across runs. + + To make this work, the library and this shim need to be compiled in LLVM + mode using afl-clang-fast (other compiler wrappers will *not* work). + + */ + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <signal.h> +#include <string.h> + +/* Main entry point. */ + +int main(int argc, char** argv) { + + ssize_t len; /* how much input did we read? */ + char buf[100]; /* Example-only buffer, you'd replace it with other global or + local variables appropriate for your use case. */ + + /* The number passed to __AFL_LOOP() controls the maximum number of + iterations before the loop exits and the program is allowed to + terminate normally. This limits the impact of accidental memory leaks + and similar hiccups. */ + + while (__AFL_LOOP(1000)) { + + /*** PLACEHOLDER CODE ***/ + + /* STEP 1: Fully re-initialize all critical variables. In our example, this + involves zeroing buf[], our input buffer. */ + + memset(buf, 0, 100); + + /* STEP 2: Read input data. When reading from stdin, no special preparation + is required. When reading from a named file, you need to close + the old descriptor and reopen the file first! + + Beware of reading from buffered FILE* objects such as stdin. Use + raw file descriptors or call fopen() / fdopen() in every pass. */ + + len = read(0, buf, 100); + + /* STEP 3: This is where we'd call the tested library on the read data. + We just have some trivial inline code that faults on 'foo!'. */ + + /* do we have enough data? */ + if (len < 4) return 0; + + if (buf[0] == 'f') { + + printf("one\n"); + if (buf[1] == 'o') { + + printf("two\n"); + if (buf[2] == 'o') { + + printf("three\n"); + if (buf[3] == '!') { + + printf("four\n"); + abort(); + + } + + } + + } + + } + + /*** END PLACEHOLDER CODE ***/ + + } + + /* Once the loop is exited, terminate normally - AFL will restart the process + when this happens, with a clean slate when it comes to allocated memory, + leftover file descriptors, etc. */ + + return 0; + +} + diff --git a/examples/post_library/post_library.so.c b/examples/post_library/post_library.so.c new file mode 100644 index 00000000..487b9a6d --- /dev/null +++ b/examples/post_library/post_library.so.c @@ -0,0 +1,120 @@ +/* + american fuzzy lop++ - postprocessor library example + -------------------------------------------------- + + Originally written by Michal Zalewski + + Copyright 2015 Google Inc. 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 + + Postprocessor libraries can be passed to afl-fuzz to perform final cleanup + of any mutated test cases - for example, to fix up checksums in PNG files. + + Please heed the following warnings: + + 1) In almost all cases, it is more productive to comment out checksum logic + in the targeted binary (as shown in ../libpng_no_checksum/). One possible + exception is the process of fuzzing binary-only software in QEMU mode. + + 2) The use of postprocessors for anything other than checksums is + questionable and may cause more harm than good. AFL is normally pretty good + about dealing with length fields, magic values, etc. + + 3) Postprocessors that do anything non-trivial must be extremely robust to + gracefully handle malformed data and other error conditions - otherwise, + they will crash and take afl-fuzz down with them. Be wary of reading past + *len and of integer overflows when calculating file offsets. + + In other words, THIS IS PROBABLY NOT WHAT YOU WANT - unless you really, + honestly know what you're doing =) + + With that out of the way: the postprocessor library is passed to afl-fuzz + via AFL_POST_LIBRARY. The library must be compiled with: + + gcc -shared -Wall -O3 post_library.so.c -o post_library.so + + AFL will call the afl_postprocess() function for every mutated output buffer. + From there, you have three choices: + + 1) If you don't want to modify the test case, simply return the original + buffer pointer ('in_buf'). + + 2) If you want to skip this test case altogether and have AFL generate a + new one, return NULL. Use this sparingly - it's faster than running + the target program with patently useless inputs, but still wastes CPU + time. + + 3) If you want to modify the test case, allocate an appropriately-sized + buffer, move the data into that buffer, make the necessary changes, and + then return the new pointer. You can update *len if necessary, too. + + Note that the buffer will *not* be freed for you. To avoid memory leaks, + you need to free it or reuse it on subsequent calls (as shown below). + + *** DO NOT MODIFY THE ORIGINAL 'in_buf' BUFFER. *** + + Aight. The example below shows a simple postprocessor that tries to make + sure that all input files start with "GIF89a". + + PS. If you don't like C, you can try out the unix-based wrapper from + Ben Nagy instead: https://github.com/bnagy/aflfix + + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +/* Header that must be present at the beginning of every test case: */ + +#define HEADER "GIF89a" + +/* The actual postprocessor routine called by afl-fuzz: */ + +const unsigned char* afl_postprocess(const unsigned char* in_buf, + unsigned int* len) { + + static unsigned char* saved_buf; + unsigned char* new_buf; + + /* Skip execution altogether for buffers shorter than 6 bytes (just to + show how it's done). We can trust *len to be sane. */ + + if (*len < strlen(HEADER)) return NULL; + + /* Do nothing for buffers that already start with the expected header. */ + + if (!memcmp(in_buf, HEADER, strlen(HEADER))) return in_buf; + + /* Allocate memory for new buffer, reusing previous allocation if + possible. */ + + new_buf = realloc(saved_buf, *len); + + /* If we're out of memory, the most graceful thing to do is to return the + original buffer and give up on modifying it. Let AFL handle OOM on its + own later on. */ + + if (!new_buf) return in_buf; + saved_buf = new_buf; + + /* Copy the original data to the new location. */ + + memcpy(new_buf, in_buf, *len); + + /* Insert the new header. */ + + memcpy(new_buf, HEADER, strlen(HEADER)); + + /* Return modified buffer. No need to update *len in this particular case, + as we're not changing it. */ + + return new_buf; + +} + diff --git a/examples/post_library/post_library_png.so.c b/examples/post_library/post_library_png.so.c new file mode 100644 index 00000000..43cb1101 --- /dev/null +++ b/examples/post_library/post_library_png.so.c @@ -0,0 +1,114 @@ +/* + american fuzzy lop++ - postprocessor for PNG + ------------------------------------------ + + Originally written by Michal Zalewski + + Copyright 2015 Google Inc. 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 + + See post_library.so.c for a general discussion of how to implement + postprocessors. This specific postprocessor attempts to fix up PNG + checksums, providing a slightly more complicated example than found + in post_library.so.c. + + Compile with: + + gcc -shared -Wall -O3 post_library_png.so.c -o post_library_png.so -lz + + */ + +#include <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include <string.h> +#include <zlib.h> + +#include <arpa/inet.h> + +/* A macro to round an integer up to 4 kB. */ + +#define UP4K(_i) ((((_i) >> 12) + 1) << 12) + +const unsigned char* afl_postprocess(const unsigned char* in_buf, + unsigned int* len) { + + static unsigned char* saved_buf; + static unsigned int saved_len; + + unsigned char* new_buf = (unsigned char*)in_buf; + unsigned int pos = 8; + + /* Don't do anything if there's not enough room for the PNG header + (8 bytes). */ + + if (*len < 8) return in_buf; + + /* Minimum size of a zero-length PNG chunk is 12 bytes; if we + don't have that, we can bail out. */ + + while (pos + 12 <= *len) { + + unsigned int chunk_len, real_cksum, file_cksum; + + /* Chunk length is the first big-endian dword in the chunk. */ + + chunk_len = ntohl(*(uint32_t*)(in_buf + pos)); + + /* Bail out if chunk size is too big or goes past EOF. */ + + if (chunk_len > 1024 * 1024 || pos + 12 + chunk_len > *len) break; + + /* Chunk checksum is calculated for chunk ID (dword) and the actual + payload. */ + + real_cksum = htonl(crc32(0, in_buf + pos + 4, chunk_len + 4)); + + /* The in-file checksum is the last dword past the chunk data. */ + + file_cksum = *(uint32_t*)(in_buf + pos + 8 + chunk_len); + + /* If the checksums do not match, we need to fix the file. */ + + if (real_cksum != file_cksum) { + + /* First modification? Make a copy of the input buffer. Round size + up to 4 kB to minimize the number of reallocs needed. */ + + if (new_buf == in_buf) { + + if (*len <= saved_len) { + + new_buf = saved_buf; + + } else { + + new_buf = realloc(saved_buf, UP4K(*len)); + if (!new_buf) return in_buf; + saved_buf = new_buf; + saved_len = UP4K(*len); + memcpy(new_buf, in_buf, *len); + + } + + } + + *(uint32_t*)(new_buf + pos + 8 + chunk_len) = real_cksum; + + } + + /* Skip the entire chunk and move to the next one. */ + + pos += 12 + chunk_len; + + } + + return new_buf; + +} + diff --git a/examples/python_mutators/README b/examples/python_mutators/README new file mode 100644 index 00000000..4e7d62bc --- /dev/null +++ b/examples/python_mutators/README @@ -0,0 +1,18 @@ +These are example and helper files for the AFL_PYTHON_MODULE feature. +See docs/python_mutators.txt for more information + +Note that if you compile with python3.7 you must use python3 scripts, and if +you use pyton2.7 to compile python2 scripts! + + +example.py - this is the template you can use, the functions are there + but they are empty + +simple-chunk-replace.py - this is a simple example where chunks are replaced + +common.py - this can be used for common functions and helpers. + the examples do not use this though. But you can :) + +wrapper_afl_min.py - mutation of XML documents, loads XmlMutatorMin.py + +XmlMutatorMin.py - module for XML mutation diff --git a/examples/python_mutators/XmlMutatorMin.py b/examples/python_mutators/XmlMutatorMin.py new file mode 100644 index 00000000..058b7e61 --- /dev/null +++ b/examples/python_mutators/XmlMutatorMin.py @@ -0,0 +1,331 @@ +#!/usr/bin/python + +""" Mutation of XML documents, should be called from one of its wrappers (CLI, AFL, ...) """ + +from __future__ import print_function +from copy import deepcopy +from lxml import etree as ET +import random, re, io + +########################### +# The XmlMutatorMin class # +########################### + +class XmlMutatorMin: + + """ + Optionals parameters: + seed Seed used by the PRNG (default: "RANDOM") + verbose Verbosity (default: False) + """ + + def __init__(self, seed="RANDOM", verbose=False): + + """ Initialize seed, database and mutators """ + + # Verbosity + self.verbose = verbose + + # Initialize PRNG + self.seed = str(seed) + if self.seed == "RANDOM": + random.seed() + else: + if self.verbose: + print("Static seed '%s'" % self.seed) + random.seed(self.seed) + + # Initialize input and output documents + self.input_tree = None + self.tree = None + + # High-level mutators (no database needed) + hl_mutators_delete = [ "del_node_and_children", "del_node_but_children", "del_attribute", "del_content" ] # Delete items + hl_mutators_fuzz = ["fuzz_attribute"] # Randomly change attribute values + + # Exposed mutators + self.hl_mutators_all = hl_mutators_fuzz + hl_mutators_delete + + def __parse_xml (self, xml): + + """ Parse an XML string. Basic wrapper around lxml.parse() """ + + try: + # Function parse() takes care of comments / DTD / processing instructions / ... + tree = ET.parse(io.BytesIO(xml)) + except ET.ParseError: + raise RuntimeError("XML isn't well-formed!") + except LookupError as e: + raise RuntimeError(e) + + # Return a document wrapper + return tree + + def __exec_among (self, module, functions, min_times, max_times): + + """ Randomly execute $functions between $min and $max times """ + + for i in xrange (random.randint (min_times, max_times)): + # Function names are mangled because they are "private" + getattr (module, "_XmlMutatorMin__" + random.choice(functions)) () + + def __serialize_xml (self, tree): + + """ Serialize a XML document. Basic wrapper around lxml.tostring() """ + + return ET.tostring(tree, with_tail=False, xml_declaration=True, encoding=tree.docinfo.encoding) + + def __ver (self, version): + + """ Helper for displaying lxml version numbers """ + + return ".".join(map(str, version)) + + def reset (self): + + """ Reset the mutator """ + + self.tree = deepcopy(self.input_tree) + + def init_from_string (self, input_string): + + """ Initialize the mutator from a XML string """ + + # Get a pointer to the top-element + self.input_tree = self.__parse_xml(input_string) + + # Get a working copy + self.tree = deepcopy(self.input_tree) + + def save_to_string (self): + + """ Return the current XML document as UTF-8 string """ + + # Return a text version of the tree + return self.__serialize_xml(self.tree) + + def __pick_element (self, exclude_root_node = False): + + """ Pick a random element from the current document """ + + # Get a list of all elements, but nodes like PI and comments + elems = list(self.tree.getroot().iter(tag=ET.Element)) + + # Is the root node excluded? + if exclude_root_node: + start = 1 + else: + start = 0 + + # Pick a random element + try: + elem_id = random.randint (start, len(elems) - 1) + elem = elems[elem_id] + except ValueError: + # Should only occurs if "exclude_root_node = True" + return (None, None) + + return (elem_id, elem) + + def __fuzz_attribute (self): + + """ Fuzz (part of) an attribute value """ + + # Select a node to modify + (rand_elem_id, rand_elem) = self.__pick_element() + + # Get all the attributes + attribs = rand_elem.keys() + + # Is there attributes? + if len(attribs) < 1: + if self.verbose: + print("No attribute: can't replace!") + return + + # Pick a random attribute + rand_attrib_id = random.randint (0, len(attribs) - 1) + rand_attrib = attribs[rand_attrib_id] + + # We have the attribute to modify + # Get its value + attrib_value = rand_elem.get(rand_attrib); + # print("- Value: " + attrib_value) + + # Should we work on the whole value? + func_call = "(?P<func>[a-zA-Z:\-]+)\((?P<args>.*?)\)" + p = re.compile(func_call) + l = p.findall(attrib_value) + if random.choice((True,False)) and l: + # Randomly pick one the function calls + (func, args) = random.choice(l) + # Split by "," and randomly pick one of the arguments + value = random.choice(args.split(',')) + # Remove superfluous characters + unclean_value = value + value = value.strip(" ").strip("'") + # print("Selected argument: [%s]" % value) + else: + value = attrib_value + + # For each type, define some possible replacement values + choices_number = ( \ + "0", \ + "11111", \ + "-128", \ + "2", \ + "-1", \ + "1/3", \ + "42/0", \ + "1094861636 idiv 1.0", \ + "-1123329771506872 idiv 3.8", \ + "17=$numericRTF", \ + str(3 + random.randrange(0, 100)), \ + ) + + choices_letter = ( \ + "P" * (25 * random.randrange(1, 100)), \ + "%s%s%s%s%s%s", \ + "foobar", \ + ) + + choices_alnum = ( \ + "Abc123", \ + "020F0302020204030204", \ + "020F0302020204030204" * (random.randrange(5, 20)), \ + ) + + # Fuzz the value + if random.choice((True,False)) and value == "": + + # Empty + new_value = value + + elif random.choice((True,False)) and value.isdigit(): + + # Numbers + new_value = random.choice(choices_number) + + elif random.choice((True,False)) and value.isalpha(): + + # Letters + new_value = random.choice(choices_letter) + + elif random.choice((True,False)) and value.isalnum(): + + # Alphanumeric + new_value = random.choice(choices_alnum) + + else: + + # Default type + new_value = random.choice(choices_alnum + choices_letter + choices_number) + + # If we worked on a substring, apply changes to the whole string + if value != attrib_value: + # No ' around empty values + if new_value != "" and value != "": + new_value = "'" + new_value + "'" + # Apply changes + new_value = attrib_value.replace(unclean_value, new_value) + + # Log something + if self.verbose: + print("Fuzzing attribute #%i '%s' of tag #%i '%s'" % (rand_attrib_id, rand_attrib, rand_elem_id, rand_elem.tag)) + + # Modify the attribute + rand_elem.set(rand_attrib, new_value.decode("utf-8")) + + def __del_node_and_children (self): + + """ High-level minimizing mutator + Delete a random node and its children (i.e. delete a random tree) """ + + self.__del_node(True) + + def __del_node_but_children (self): + + """ High-level minimizing mutator + Delete a random node but its children (i.e. link them to the parent of the deleted node) """ + + self.__del_node(False) + + def __del_node (self, delete_children): + + """ Called by the __del_node_* mutators """ + + # Select a node to modify (but the root one) + (rand_elem_id, rand_elem) = self.__pick_element (exclude_root_node = True) + + # If the document includes only a top-level element + # Then we can't pick a element (given that "exclude_root_node = True") + + # Is the document deep enough? + if rand_elem is None: + if self.verbose: + print("Can't delete a node: document not deep enough!") + return + + # Log something + if self.verbose: + but_or_and = "and" if delete_children else "but" + print("Deleting tag #%i '%s' %s its children" % (rand_elem_id, rand_elem.tag, but_or_and)) + + if delete_children is False: + # Link children of the random (soon to be deleted) node to its parent + for child in rand_elem: + rand_elem.getparent().append(child) + + # Remove the node + rand_elem.getparent().remove(rand_elem) + + def __del_content (self): + + """ High-level minimizing mutator + Delete the attributes and children of a random node """ + + # Select a node to modify + (rand_elem_id, rand_elem) = self.__pick_element() + + # Log something + if self.verbose: + print("Reseting tag #%i '%s'" % (rand_elem_id, rand_elem.tag)) + + # Reset the node + rand_elem.clear() + + def __del_attribute (self): + + """ High-level minimizing mutator + Delete a random attribute from a random node """ + + # Select a node to modify + (rand_elem_id, rand_elem) = self.__pick_element() + + # Get all the attributes + attribs = rand_elem.keys() + + # Is there attributes? + if len(attribs) < 1: + if self.verbose: + print("No attribute: can't delete!") + return + + # Pick a random attribute + rand_attrib_id = random.randint (0, len(attribs) - 1) + rand_attrib = attribs[rand_attrib_id] + + # Log something + if self.verbose: + print("Deleting attribute #%i '%s' of tag #%i '%s'" % (rand_attrib_id, rand_attrib, rand_elem_id, rand_elem.tag)) + + # Delete the attribute + rand_elem.attrib.pop(rand_attrib) + + def mutate (self, min=1, max=5): + + """ Execute some high-level mutators between $min and $max times, then some medium-level ones """ + + # High-level mutation + self.__exec_among(self, self.hl_mutators_all, min, max) + diff --git a/examples/python_mutators/common.py b/examples/python_mutators/common.py new file mode 100644 index 00000000..28b8ee80 --- /dev/null +++ b/examples/python_mutators/common.py @@ -0,0 +1,37 @@ +#!/usr/bin/env python +# encoding: utf-8 +''' +Module containing functions shared between multiple AFL modules + +@author: Christian Holler (:decoder) + +@license: + +This Source Code Form is subject to the terms of the Mozilla Public +License, v. 2.0. If a copy of the MPL was not distributed with this +file, You can obtain one at http://mozilla.org/MPL/2.0/. + +@contact: choller@mozilla.com +''' + +from __future__ import print_function +import random +import os +import re + +def randel(l): + if not l: + return None + return l[random.randint(0,len(l)-1)] + +def randel_pop(l): + if not l: + return None + return l.pop(random.randint(0,len(l)-1)) + +def write_exc_example(data, exc): + exc_name = re.sub(r'[^a-zA-Z0-9]', '_', repr(exc)) + + if not os.path.exists(exc_name): + with open(exc_name, 'w') as f: + f.write(data) diff --git a/examples/python_mutators/example.py b/examples/python_mutators/example.py new file mode 100644 index 00000000..d32a7eb2 --- /dev/null +++ b/examples/python_mutators/example.py @@ -0,0 +1,103 @@ +#!/usr/bin/env python +# encoding: utf-8 +''' +Example Python Module for AFLFuzz + +@author: Christian Holler (:decoder) + +@license: + +This Source Code Form is subject to the terms of the Mozilla Public +License, v. 2.0. If a copy of the MPL was not distributed with this +file, You can obtain one at http://mozilla.org/MPL/2.0/. + +@contact: choller@mozilla.com +''' + +import random + +def init(seed): + ''' + Called once when AFLFuzz starts up. Used to seed our RNG. + + @type seed: int + @param seed: A 32-bit random value + ''' + random.seed(seed) + return 0 + +def fuzz(buf, add_buf): + ''' + Called per fuzzing iteration. + + @type buf: bytearray + @param buf: The buffer that should be mutated. + + @type add_buf: bytearray + @param add_buf: A second buffer that can be used as mutation source. + + @rtype: bytearray + @return: A new bytearray containing the mutated data + ''' + ret = bytearray(buf) + # Do something interesting with ret + + return ret + +# Uncomment and implement the following methods if you want to use a custom +# trimming algorithm. See also the documentation for a better API description. + +# def init_trim(buf): +# ''' +# Called per trimming iteration. +# +# @type buf: bytearray +# @param buf: The buffer that should be trimmed. +# +# @rtype: int +# @return: The maximum number of trimming steps. +# ''' +# global ... +# +# # Initialize global variables +# +# # Figure out how many trimming steps are possible. +# # If this is not possible for your trimming, you can +# # return 1 instead and always return 0 in post_trim +# # until you are done (then you return 1). +# +# return steps +# +# def trim(): +# ''' +# Called per trimming iteration. +# +# @rtype: bytearray +# @return: A new bytearray containing the trimmed data. +# ''' +# global ... +# +# # Implement the actual trimming here +# +# return bytearray(...) +# +# def post_trim(success): +# ''' +# Called after each trimming operation. +# +# @type success: bool +# @param success: Indicates if the last trim operation was successful. +# +# @rtype: int +# @return: The next trim index (0 to max number of steps) where max +# number of steps indicates the trimming is done. +# ''' +# global ... +# +# if not success: +# # Restore last known successful input, determine next index +# else: +# # Just determine the next index, based on what was successfully +# # removed in the last step +# +# return next_index diff --git a/examples/python_mutators/simple-chunk-replace.py b/examples/python_mutators/simple-chunk-replace.py new file mode 100644 index 00000000..218dd4f8 --- /dev/null +++ b/examples/python_mutators/simple-chunk-replace.py @@ -0,0 +1,59 @@ +#!/usr/bin/env python +# encoding: utf-8 +''' +Simple Chunk Cross-Over Replacement Module for AFLFuzz + +@author: Christian Holler (:decoder) + +@license: + +This Source Code Form is subject to the terms of the Mozilla Public +License, v. 2.0. If a copy of the MPL was not distributed with this +file, You can obtain one at http://mozilla.org/MPL/2.0/. + +@contact: choller@mozilla.com +''' + +import random + +def init(seed): + ''' + Called once when AFLFuzz starts up. Used to seed our RNG. + + @type seed: int + @param seed: A 32-bit random value + ''' + # Seed our RNG + random.seed(seed) + return 0 + +def fuzz(buf, add_buf): + ''' + Called per fuzzing iteration. + + @type buf: bytearray + @param buf: The buffer that should be mutated. + + @type add_buf: bytearray + @param add_buf: A second buffer that can be used as mutation source. + + @rtype: bytearray + @return: A new bytearray containing the mutated data + ''' + # Make a copy of our input buffer for returning + ret = bytearray(buf) + + # Take a random fragment length between 2 and 32 (or less if add_buf is shorter) + fragment_len = random.randint(1, min(len(add_buf), 32)) + + # Determine a random source index where to take the data chunk from + rand_src_idx = random.randint(0, len(add_buf) - fragment_len) + + # Determine a random destination index where to put the data chunk + rand_dst_idx = random.randint(0, len(buf)) + + # Make the chunk replacement + ret[rand_dst_idx:rand_dst_idx + fragment_len] = add_buf[rand_src_idx:rand_src_idx + fragment_len] + + # Return data + return ret diff --git a/examples/python_mutators/wrapper_afl_min.py b/examples/python_mutators/wrapper_afl_min.py new file mode 100644 index 00000000..df09b40a --- /dev/null +++ b/examples/python_mutators/wrapper_afl_min.py @@ -0,0 +1,117 @@ +#!/usr/bin/env python + +from XmlMutatorMin import XmlMutatorMin + +# Default settings (production mode) + +__mutator__ = None +__seed__ = "RANDOM" +__log__ = False +__log_file__ = "wrapper.log" + +# AFL functions + +def log(text): + """ + Logger + """ + + global __seed__ + global __log__ + global __log_file__ + + if __log__: + with open(__log_file__, "a") as logf: + logf.write("[%s] %s\n" % (__seed__, text)) + +def init(seed): + """ + Called once when AFL starts up. Seed is used to identify the AFL instance in log files + """ + + global __mutator__ + global __seed__ + + # Get the seed + __seed__ = seed + + # Create a global mutation class + try: + __mutator__ = XmlMutatorMin(__seed__, verbose=__log__) + log("init(): Mutator created") + except RuntimeError as e: + log("init(): Can't create mutator: %s" % e.message) + +def fuzz(buf, add_buf): + """ + Called for each fuzzing iteration. + """ + + global __mutator__ + + # Do we have a working mutator object? + if __mutator__ is None: + log("fuzz(): Can't fuzz, no mutator available") + return buf + + # Try to use the AFL buffer + via_buffer = True + + # Interpret the AFL buffer (an array of bytes) as a string + if via_buffer: + try: + buf_str = str(buf) + log("fuzz(): AFL buffer converted to a string") + except: + via_buffer = False + log("fuzz(): Can't convert AFL buffer to a string") + + # Load XML from the AFL string + if via_buffer: + try: + __mutator__.init_from_string(buf_str) + log("fuzz(): Mutator successfully initialized with AFL buffer (%d bytes)" % len(buf_str)) + except: + via_buffer = False + log("fuzz(): Can't initialize mutator with AFL buffer") + + # If init from AFL buffer wasn't succesful + if not via_buffer: + log("fuzz(): Returning unmodified AFL buffer") + return buf + + # Sucessful initialization -> mutate + try: + __mutator__.mutate(max=5) + log("fuzz(): Input mutated") + except: + log("fuzz(): Can't mutate input => returning buf") + return buf + + # Convert mutated data to a array of bytes + try: + data = bytearray(__mutator__.save_to_string()) + log("fuzz(): Mutated data converted as bytes") + except: + log("fuzz(): Can't convert mutated data to bytes => returning buf") + return buf + + # Everything went fine, returning mutated content + log("fuzz(): Returning %d bytes" % len(data)) + return data + +# Main (for debug) + +if __name__ == '__main__': + + __log__ = True + __log_file__ = "/dev/stdout" + __seed__ = "RANDOM" + + init(__seed__) + + in_1 = bytearray("<foo ddd='eeee'>ffff<a b='c' d='456' eee='ffffff'>zzzzzzzzzzzz</a><b yyy='YYY' zzz='ZZZ'></b></foo>") + in_2 = bytearray("<abc abc123='456' abcCBA='ppppppppppppppppppppppppppppp'/>") + out = fuzz(in_1, in_2) + print(out) + diff --git a/examples/socket_fuzzing/Makefile b/examples/socket_fuzzing/Makefile new file mode 100644 index 00000000..0191ba53 --- /dev/null +++ b/examples/socket_fuzzing/Makefile @@ -0,0 +1,39 @@ +# +# american fuzzy lop++ - socket_fuzz +# ---------------------------------- +# +# 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 +# + +.PHONY: all install clean + +PREFIX ?= /usr/local +BIN_PATH = $(PREFIX)/bin +HELPER_PATH = $(PREFIX)/lib/afl + +CFLAGS = -fPIC -Wall -Wextra +LDFLAGS = -shared + +ifneq "$(filter Linux GNU%,$(shell uname))" "" + LDFLAGS += -ldl +endif + +all: socketfuzz32.so socketfuzz64.so + +socketfuzz32.so: socketfuzz.c + -$(CC) -m32 $(CFLAGS) $^ $(LDFLAGS) -o $@ || echo "socketfuzz32 build failure (that's fine)" + +socketfuzz64.so: socketfuzz.c + -$(CC) $(CFLAGS) $^ $(LDFLAGS) -o $@ + +install: socketfuzz32.so socketfuzz64.so + install -d -m 755 $(DESTDIR)$(HELPER_PATH)/ + if [ -f socketfuzz32.so ]; then set -e; install -m 755 socketfuzz32.so $(DESTDIR)$(HELPER_PATH)/; fi + install -m 755 socketfuzz64.so $(DESTDIR)$(HELPER_PATH)/ + +clean: + rm -f socketfuzz32.so socketfuzz64.so diff --git a/examples/socket_fuzzing/README.md b/examples/socket_fuzzing/README.md new file mode 100644 index 00000000..79f28bea --- /dev/null +++ b/examples/socket_fuzzing/README.md @@ -0,0 +1,11 @@ +# socketfuzz + +when you want to fuzz a network service and you can not/do not want to modify +the source (or just have a binary), then this LD_PRELOAD library will allow +for sending input to stdin which the target binary will think is coming from +a network socket. + +This is desock_dup.c from the amazing preeny project +https://github.com/zardus/preeny + +It is packaged in afl++ to have it at hand if needed diff --git a/examples/socket_fuzzing/socketfuzz.c b/examples/socket_fuzzing/socketfuzz.c new file mode 100644 index 00000000..3ec8383b --- /dev/null +++ b/examples/socket_fuzzing/socketfuzz.c @@ -0,0 +1,110 @@ +/* + * This is desock_dup.c from the amazing preeny project + * https://github.com/zardus/preeny + * + * It is packaged in afl++ to have it at hand if needed + * + */ + +#define _GNU_SOURCE + +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <sys/types.h> // +#include <sys/socket.h> // +#include <sys/stat.h> // +#include <fcntl.h> // +#include <netinet/in.h> +#include <pthread.h> +#include <signal.h> +#include <dlfcn.h> +#include <errno.h> +#include <stdio.h> +#include <poll.h> +//#include "logging.h" // switche from preeny_info() to fprintf(stderr, "Info: " + +// +// originals +// +int (*original_close)(int); +int (*original_dup2)(int, int); +__attribute__((constructor)) void preeny_desock_dup_orig() { + + original_close = dlsym(RTLD_NEXT, "close"); + original_dup2 = dlsym(RTLD_NEXT, "dup2"); + +} + +int close(int sockfd) { + + if (sockfd <= 2) { + + fprintf(stderr, "Info: Disabling close on %d\n", sockfd); + return 0; + + } else { + + return original_close(sockfd); + + } + +} + +int dup2(int old, int new) { + + if (new <= 2) { + + fprintf(stderr, "Info: Disabling dup from %d to %d\n", old, new); + return 0; + + } else { + + return original_dup2(old, new); + + } + +} + +int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen) { + + (void)sockfd; + (void)addr; + (void)addrlen; + fprintf(stderr, "Info: Emulating accept on %d\n", sockfd); + return 0; + +} + +int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen) { + + (void)sockfd; + (void)addr; + (void)addrlen; + fprintf(stderr, "Info: Emulating bind on port %d\n", + ntohs(((struct sockaddr_in *)addr)->sin_port)); + return 0; + +} + +int listen(int sockfd, int backlog) { + + (void)sockfd; + (void)backlog; + return 0; + +} + +int setsockopt(int sockfd, int level, int optid, const void *optdata, + socklen_t optdatalen) { + + (void)sockfd; + (void)level; + (void)optid; + (void)optdata; + (void)optdatalen; + return 0; + +} + |