aboutsummaryrefslogtreecommitdiff
path: root/custom_mutators
diff options
context:
space:
mode:
authorDominik Maier <domenukk@gmail.com>2021-04-26 16:03:08 +0200
committerDominik Maier <domenukk@gmail.com>2021-04-26 16:03:08 +0200
commita3f8fc5d1ccabc455e28157ee86211f0c11c81a3 (patch)
tree3ca0fd7889aa8291bd16226d50b95f8966bd8e82 /custom_mutators
parent3d830daa46da9412f475310afabf1b965156f3e1 (diff)
downloadAFLplusplus-a3f8fc5d1ccabc455e28157ee86211f0c11c81a3.tar.gz
moved custom_mutator examples
Diffstat (limited to 'custom_mutators')
-rw-r--r--custom_mutators/README.md8
-rw-r--r--custom_mutators/examples/Makefile7
-rw-r--r--custom_mutators/examples/README.md35
-rw-r--r--custom_mutators/examples/XmlMutatorMin.py348
-rw-r--r--custom_mutators/examples/common.py40
-rw-r--r--custom_mutators/examples/custom_mutator_helpers.h342
-rw-r--r--custom_mutators/examples/example.c376
-rw-r--r--custom_mutators/examples/example.py187
-rw-r--r--custom_mutators/examples/post_library_gif.so.c165
-rw-r--r--custom_mutators/examples/post_library_png.so.c163
-rw-r--r--custom_mutators/examples/simple-chunk-replace.py66
-rw-r--r--custom_mutators/examples/simple_example.c74
-rw-r--r--custom_mutators/examples/wrapper_afl_min.py123
13 files changed, 1934 insertions, 0 deletions
diff --git a/custom_mutators/README.md b/custom_mutators/README.md
index b0444c85..5e1d0fe6 100644
--- a/custom_mutators/README.md
+++ b/custom_mutators/README.md
@@ -3,6 +3,14 @@
Custom mutators enhance and alter the mutation strategies of afl++.
For further information and documentation on how to write your own, read [the docs](../docs/custom_mutators.md).
+## Examples
+
+The `./examples` folder contains examples for custom mutators in python and C.
+
+## Rust
+
+In `./rust`, you will find rust bindings, including a simple example in `./rust/example` and an example for structured fuzzing, based on lain, in`./rust/example_lain`.
+
## The afl++ Grammar Mutator
If you use git to clone afl++, then the following will incorporate our
diff --git a/custom_mutators/examples/Makefile b/custom_mutators/examples/Makefile
new file mode 100644
index 00000000..9849f3f4
--- /dev/null
+++ b/custom_mutators/examples/Makefile
@@ -0,0 +1,7 @@
+all: libexamplemutator.so
+
+libexamplemutator.so:
+ $(CC) $(CFLAGS) -D_FORTIFY_SOURCE=2 -O3 -fPIC -shared -g -I ../../include example.c -o libexamplemutator.so
+
+clean:
+ rm -rf libexamplemutator.so
diff --git a/custom_mutators/examples/README.md b/custom_mutators/examples/README.md
new file mode 100644
index 00000000..655f7a5e
--- /dev/null
+++ b/custom_mutators/examples/README.md
@@ -0,0 +1,35 @@
+# Examples for the custom mutator
+
+These are example and helper files for the custom mutator feature.
+See [docs/custom_mutators.md](../../docs/custom_mutators.md) for more information
+
+Note that if you compile with python3.7 you must use python3 scripts, and if
+you use python2.7 to compile python2 scripts!
+
+simple_example.c - most simplest example. generates a random sized buffer
+ filled with 'A'
+
+example.c - this is a simple example written in C and should be compiled to a
+ shared library. Use make to compile it and produce libexamplemutator.so
+
+example.py - this is the template you can use, the functions are there but they
+ are empty
+
+post_library_gif.so.c - fix a fuzz input to ensure it is valid for GIF
+
+post_library_png.so.c - fix a fuzz input to ensure it is valid for PNG
+
+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
+
+custom_mutator_helpers.h is an header that defines some helper routines
+like surgical_havoc_mutate() that allow to perform a randomly chosen
+mutation from a subset of the havoc mutations.
+If you do so, you have to specify -I /path/to/AFLplusplus/include when
+compiling.
diff --git a/custom_mutators/examples/XmlMutatorMin.py b/custom_mutators/examples/XmlMutatorMin.py
new file mode 100644
index 00000000..3e6cd0ff
--- /dev/null
+++ b/custom_mutators/examples/XmlMutatorMin.py
@@ -0,0 +1,348 @@
+#!/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/custom_mutators/examples/common.py b/custom_mutators/examples/common.py
new file mode 100644
index 00000000..44a5056a
--- /dev/null
+++ b/custom_mutators/examples/common.py
@@ -0,0 +1,40 @@
+#!/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/custom_mutators/examples/custom_mutator_helpers.h b/custom_mutators/examples/custom_mutator_helpers.h
new file mode 100644
index 00000000..62e6efba
--- /dev/null
+++ b/custom_mutators/examples/custom_mutator_helpers.h
@@ -0,0 +1,342 @@
+#ifndef CUSTOM_MUTATOR_HELPERS
+#define CUSTOM_MUTATOR_HELPERS
+
+#include "config.h"
+#include "types.h"
+#include <stdlib.h>
+
+#define INITIAL_GROWTH_SIZE (64)
+
+#define RAND_BELOW(limit) (rand() % (limit))
+
+/* Use in a struct: creates a name_buf and a name_size variable. */
+#define BUF_VAR(type, name) \
+ type * name##_buf; \
+ size_t name##_size;
+/* this fills in `&structptr->something_buf, &structptr->something_size`. */
+#define BUF_PARAMS(struct, name) \
+ (void **)&struct->name##_buf, &struct->name##_size
+
+typedef struct {
+
+} afl_t;
+
+static void surgical_havoc_mutate(u8 *out_buf, s32 begin, s32 end) {
+
+ static s8 interesting_8[] = {INTERESTING_8};
+ static s16 interesting_16[] = {INTERESTING_8, INTERESTING_16};
+ static s32 interesting_32[] = {INTERESTING_8, INTERESTING_16, INTERESTING_32};
+
+ switch (RAND_BELOW(12)) {
+
+ case 0: {
+
+ /* Flip a single bit somewhere. Spooky! */
+
+ s32 bit_idx = ((RAND_BELOW(end - begin) + begin) << 3) + RAND_BELOW(8);
+
+ out_buf[bit_idx >> 3] ^= 128 >> (bit_idx & 7);
+
+ break;
+
+ }
+
+ case 1: {
+
+ /* Set byte to interesting value. */
+
+ u8 val = interesting_8[RAND_BELOW(sizeof(interesting_8))];
+ out_buf[(RAND_BELOW(end - begin) + begin)] = val;
+
+ break;
+
+ }
+
+ case 2: {
+
+ /* Set word to interesting value, randomly choosing endian. */
+
+ if (end - begin < 2) break;
+
+ s32 byte_idx = (RAND_BELOW(end - begin) + begin);
+
+ if (byte_idx >= end - 1) break;
+
+ switch (RAND_BELOW(2)) {
+
+ case 0:
+ *(u16 *)(out_buf + byte_idx) =
+ interesting_16[RAND_BELOW(sizeof(interesting_16) >> 1)];
+ break;
+ case 1:
+ *(u16 *)(out_buf + byte_idx) =
+ SWAP16(interesting_16[RAND_BELOW(sizeof(interesting_16) >> 1)]);
+ break;
+
+ }
+
+ break;
+
+ }
+
+ case 3: {
+
+ /* Set dword to interesting value, randomly choosing endian. */
+
+ if (end - begin < 4) break;
+
+ s32 byte_idx = (RAND_BELOW(end - begin) + begin);
+
+ if (byte_idx >= end - 3) break;
+
+ switch (RAND_BELOW(2)) {
+
+ case 0:
+ *(u32 *)(out_buf + byte_idx) =
+ interesting_32[RAND_BELOW(sizeof(interesting_32) >> 2)];
+ break;
+ case 1:
+ *(u32 *)(out_buf + byte_idx) =
+ SWAP32(interesting_32[RAND_BELOW(sizeof(interesting_32) >> 2)]);
+ break;
+
+ }
+
+ break;
+
+ }
+
+ case 4: {
+
+ /* Set qword to interesting value, randomly choosing endian. */
+
+ if (end - begin < 8) break;
+
+ s32 byte_idx = (RAND_BELOW(end - begin) + begin);
+
+ if (byte_idx >= end - 7) break;
+
+ switch (RAND_BELOW(2)) {
+
+ case 0:
+ *(u64 *)(out_buf + byte_idx) =
+ (s64)interesting_32[RAND_BELOW(sizeof(interesting_32) >> 2)];
+ break;
+ case 1:
+ *(u64 *)(out_buf + byte_idx) = SWAP64(
+ (s64)interesting_32[RAND_BELOW(sizeof(interesting_32) >> 2)]);
+ break;
+
+ }
+
+ break;
+
+ }
+
+ case 5: {
+
+ /* Randomly subtract from byte. */
+
+ out_buf[(RAND_BELOW(end - begin) + begin)] -= 1 + RAND_BELOW(ARITH_MAX);
+
+ break;
+
+ }
+
+ case 6: {
+
+ /* Randomly add to byte. */
+
+ out_buf[(RAND_BELOW(end - begin) + begin)] += 1 + RAND_BELOW(ARITH_MAX);
+
+ break;
+
+ }
+
+ case 7: {
+
+ /* Randomly subtract from word, random endian. */
+
+ if (end - begin < 2) break;
+
+ s32 byte_idx = (RAND_BELOW(end - begin) + begin);
+
+ if (byte_idx >= end - 1) break;
+
+ if (RAND_BELOW(2)) {
+
+ *(u16 *)(out_buf + byte_idx) -= 1 + RAND_BELOW(ARITH_MAX);
+
+ } else {
+
+ u16 num = 1 + RAND_BELOW(ARITH_MAX);
+
+ *(u16 *)(out_buf + byte_idx) =
+ SWAP16(SWAP16(*(u16 *)(out_buf + byte_idx)) - num);
+
+ }
+
+ break;
+
+ }
+
+ case 8: {
+
+ /* Randomly add to word, random endian. */
+
+ if (end - begin < 2) break;
+
+ s32 byte_idx = (RAND_BELOW(end - begin) + begin);
+
+ if (byte_idx >= end - 1) break;
+
+ if (RAND_BELOW(2)) {
+
+ *(u16 *)(out_buf + byte_idx) += 1 + RAND_BELOW(ARITH_MAX);
+
+ } else {
+
+ u16 num = 1 + RAND_BELOW(ARITH_MAX);
+
+ *(u16 *)(out_buf + byte_idx) =
+ SWAP16(SWAP16(*(u16 *)(out_buf + byte_idx)) + num);
+
+ }
+
+ break;
+
+ }
+
+ case 9: {
+
+ /* Randomly subtract from dword, random endian. */
+
+ if (end - begin < 4) break;
+
+ s32 byte_idx = (RAND_BELOW(end - begin) + begin);
+
+ if (byte_idx >= end - 3) break;
+
+ if (RAND_BELOW(2)) {
+
+ *(u32 *)(out_buf + byte_idx) -= 1 + RAND_BELOW(ARITH_MAX);
+
+ } else {
+
+ u32 num = 1 + RAND_BELOW(ARITH_MAX);
+
+ *(u32 *)(out_buf + byte_idx) =
+ SWAP32(SWAP32(*(u32 *)(out_buf + byte_idx)) - num);
+
+ }
+
+ break;
+
+ }
+
+ case 10: {
+
+ /* Randomly add to dword, random endian. */
+
+ if (end - begin < 4) break;
+
+ s32 byte_idx = (RAND_BELOW(end - begin) + begin);
+
+ if (byte_idx >= end - 3) break;
+
+ if (RAND_BELOW(2)) {
+
+ *(u32 *)(out_buf + byte_idx) += 1 + RAND_BELOW(ARITH_MAX);
+
+ } else {
+
+ u32 num = 1 + RAND_BELOW(ARITH_MAX);
+
+ *(u32 *)(out_buf + byte_idx) =
+ SWAP32(SWAP32(*(u32 *)(out_buf + byte_idx)) + num);
+
+ }
+
+ break;
+
+ }
+
+ case 11: {
+
+ /* Just set a random byte to a random value. Because,
+ why not. We use XOR with 1-255 to eliminate the
+ possibility of a no-op. */
+
+ out_buf[(RAND_BELOW(end - begin) + begin)] ^= 1 + RAND_BELOW(255);
+
+ break;
+
+ }
+
+ }
+
+}
+
+/* This function calculates the next power of 2 greater or equal its argument.
+ @return The rounded up power of 2 (if no overflow) or 0 on overflow.
+*/
+static inline size_t next_pow2(size_t in) {
+
+ if (in == 0 || in > (size_t)-1)
+ return 0; /* avoid undefined behaviour under-/overflow */
+ size_t out = in - 1;
+ out |= out >> 1;
+ out |= out >> 2;
+ out |= out >> 4;
+ out |= out >> 8;
+ out |= out >> 16;
+ return out + 1;
+
+}
+
+/* This function makes sure *size is > size_needed after call.
+ It will realloc *buf otherwise.
+ *size will grow exponentially as per:
+ https://blog.mozilla.org/nnethercote/2014/11/04/please-grow-your-buffers-exponentially/
+ Will return NULL and free *buf if size_needed is <1 or realloc failed.
+ @return For convenience, this function returns *buf.
+ */
+static inline void *maybe_grow(void **buf, size_t *size, size_t size_needed) {
+
+ /* No need to realloc */
+ if (likely(size_needed && *size >= size_needed)) return *buf;
+
+ /* No initial size was set */
+ if (size_needed < INITIAL_GROWTH_SIZE) size_needed = INITIAL_GROWTH_SIZE;
+
+ /* grow exponentially */
+ size_t next_size = next_pow2(size_needed);
+
+ /* handle overflow */
+ if (!next_size) { next_size = size_needed; }
+
+ /* alloc */
+ *buf = realloc(*buf, next_size);
+ *size = *buf ? next_size : 0;
+
+ return *buf;
+
+}
+
+/* Swaps buf1 ptr and buf2 ptr, as well as their sizes */
+static inline void afl_swap_bufs(void **buf1, size_t *size1, void **buf2,
+ size_t *size2) {
+
+ void * scratch_buf = *buf1;
+ size_t scratch_size = *size1;
+ *buf1 = *buf2;
+ *size1 = *size2;
+ *buf2 = scratch_buf;
+ *size2 = scratch_size;
+
+}
+
+#undef INITIAL_GROWTH_SIZE
+
+#endif
+
diff --git a/custom_mutators/examples/example.c b/custom_mutators/examples/example.c
new file mode 100644
index 00000000..23add128
--- /dev/null
+++ b/custom_mutators/examples/example.c
@@ -0,0 +1,376 @@
+/*
+ New Custom Mutator for AFL++
+ Written by Khaled Yakdan <yakdan@code-intelligence.de>
+ Andrea Fioraldi <andreafioraldi@gmail.com>
+ Shengtuo Hu <h1994st@gmail.com>
+ Dominik Maier <mail@dmnk.co>
+*/
+
+// You need to use -I /path/to/AFLplusplus/include
+#include "custom_mutator_helpers.h"
+
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+
+#define DATA_SIZE (100)
+
+static const char *commands[] = {
+
+ "GET",
+ "PUT",
+ "DEL",
+
+};
+
+typedef struct my_mutator {
+
+ afl_t *afl;
+
+ // any additional data here!
+ size_t trim_size_current;
+ int trimmming_steps;
+ int cur_step;
+
+ // Reused buffers:
+ BUF_VAR(u8, fuzz);
+ BUF_VAR(u8, data);
+ BUF_VAR(u8, havoc);
+ BUF_VAR(u8, trim);
+ BUF_VAR(u8, post_process);
+
+} my_mutator_t;
+
+/**
+ * Initialize this custom mutator
+ *
+ * @param[in] afl a pointer to the internal state object. Can be ignored for
+ * now.
+ * @param[in] seed A seed for this mutator - the same seed should always mutate
+ * in the same way.
+ * @return Pointer to the data object this custom mutator instance should use.
+ * There may be multiple instances of this mutator in one afl-fuzz run!
+ * Return NULL on error.
+ */
+my_mutator_t *afl_custom_init(afl_t *afl, unsigned int seed) {
+
+ srand(seed); // needed also by surgical_havoc_mutate()
+
+ my_mutator_t *data = calloc(1, sizeof(my_mutator_t));
+ if (!data) {
+
+ perror("afl_custom_init alloc");
+ return NULL;
+
+ }
+
+ data->afl = afl;
+
+ return data;
+
+}
+
+/**
+ * Perform custom mutations on a given input
+ *
+ * (Optional for now. Required in the future)
+ *
+ * @param[in] data pointer returned in afl_custom_init for this fuzz case
+ * @param[in] buf Pointer to input data to be mutated
+ * @param[in] buf_size Size of input data
+ * @param[out] out_buf the buffer we will work on. we can reuse *buf. NULL on
+ * error.
+ * @param[in] add_buf Buffer containing the additional test case
+ * @param[in] add_buf_size Size of the additional test case
+ * @param[in] max_size Maximum size of the mutated output. The mutation must not
+ * produce data larger than max_size.
+ * @return Size of the mutated output.
+ */
+size_t afl_custom_fuzz(my_mutator_t *data, uint8_t *buf, size_t buf_size,
+ u8 **out_buf, uint8_t *add_buf,
+ size_t add_buf_size, // add_buf can be NULL
+ size_t max_size) {
+
+ // 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;
+
+ // maybe_grow is optimized to be quick for reused buffers.
+ u8 *mutated_out = maybe_grow(BUF_PARAMS(data, fuzz), mutated_size);
+ if (!mutated_out) {
+
+ *out_buf = NULL;
+ perror("custom mutator allocation (maybe_grow)");
+ return 0; /* afl-fuzz will very likely error out after this. */
+
+ }
+
+ // 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
+ int i;
+ for (i = 0; i < 8; ++i) {
+
+ // Randomly perform one of the (no len modification) havoc mutations
+ surgical_havoc_mutate(mutated_out, 3, mutated_size);
+
+ }
+
+ *out_buf = mutated_out;
+ return mutated_size;
+
+}
+
+/**
+ * A post-processing function to use right before AFL writes the test case to
+ * disk in order to execute the target.
+ *
+ * (Optional) If this functionality is not needed, simply don't define this
+ * function.
+ *
+ * @param[in] data pointer returned in afl_custom_init for this fuzz case
+ * @param[in] buf Buffer containing the test case to be executed
+ * @param[in] buf_size Size of the test case
+ * @param[out] out_buf Pointer to the buffer containing the test case after
+ * processing. External library should allocate memory for out_buf.
+ * The buf pointer may be reused (up to the given buf_size);
+ * @return Size of the output buffer after processing or the needed amount.
+ * A return of 0 indicates an error.
+ */
+size_t afl_custom_post_process(my_mutator_t *data, uint8_t *buf,
+ size_t buf_size, uint8_t **out_buf) {
+
+ uint8_t *post_process_buf =
+ maybe_grow(BUF_PARAMS(data, post_process), buf_size + 5);
+ if (!post_process_buf) {
+
+ perror("custom mutator realloc failed.");
+ *out_buf = NULL;
+ return 0;
+
+ }
+
+ memcpy(post_process_buf + 5, buf, buf_size);
+ post_process_buf[0] = 'A';
+ post_process_buf[1] = 'F';
+ post_process_buf[2] = 'L';
+ post_process_buf[3] = '+';
+ post_process_buf[4] = '+';
+
+ *out_buf = post_process_buf;
+
+ return buf_size + 5;
+
+}
+
+/**
+ * This method is called at the start of each trimming operation and receives
+ * the initial buffer. It should return the amount of iteration steps possible
+ * on this input (e.g. if your input has n elements and you want to remove
+ * them one by one, return n, if you do a binary search, return log(n),
+ * and so on...).
+ *
+ * If your trimming algorithm doesn't allow you to determine the amount of
+ * (remaining) steps easily (esp. while running), then you can alternatively
+ * return 1 here and always return 0 in post_trim until you are finished and
+ * no steps remain. In that case, returning 1 in post_trim will end the
+ * trimming routine. The whole current index/max iterations stuff is only used
+ * to show progress.
+ *
+ * (Optional)
+ *
+ * @param data pointer returned in afl_custom_init for this fuzz case
+ * @param buf Buffer containing the test case
+ * @param buf_size Size of the test case
+ * @return The amount of possible iteration steps to trim the input.
+ * negative on error.
+ */
+int32_t afl_custom_init_trim(my_mutator_t *data, uint8_t *buf,
+ size_t buf_size) {
+
+ // We simply trim once
+ data->trimmming_steps = 1;
+
+ data->cur_step = 0;
+
+ if (!maybe_grow(BUF_PARAMS(data, trim), buf_size)) {
+
+ perror("init_trim grow");
+ return -1;
+
+ }
+
+ memcpy(data->trim_buf, buf, buf_size);
+
+ data->trim_size_current = buf_size;
+
+ return data->trimmming_steps;
+
+}
+
+/**
+ * This method is called for each trimming operation. It doesn't have any
+ * arguments because we already have the initial buffer from init_trim and we
+ * can memorize the current state in *data. This can also save
+ * reparsing steps for each iteration. It should return the trimmed input
+ * buffer, where the returned data must not exceed the initial input data in
+ * length. Returning anything that is larger than the original data (passed
+ * to init_trim) will result in a fatal abort of AFLFuzz.
+ *
+ * (Optional)
+ *
+ * @param[in] data pointer returned in afl_custom_init for this fuzz case
+ * @param[out] out_buf Pointer to the buffer containing the trimmed test case.
+ * External library should allocate memory for out_buf.
+ * AFL++ will not release the memory after saving the test case.
+ * Keep a ref in *data.
+ * *out_buf = NULL is treated as error.
+ * @return Pointer to the size of the trimmed test case
+ */
+size_t afl_custom_trim(my_mutator_t *data, uint8_t **out_buf) {
+
+ *out_buf = data->trim_buf;
+
+ // Remove the last byte of the trimming input
+ return data->trim_size_current - 1;
+
+}
+
+/**
+ * This method is called after each trim operation to inform you if your
+ * trimming step was successful or not (in terms of coverage). If you receive
+ * a failure here, you should reset your input to the last known good state.
+ *
+ * (Optional)
+ *
+ * @param[in] data pointer returned in afl_custom_init for this fuzz case
+ * @param success Indicates if the last trim operation was successful.
+ * @return The next trim iteration index (from 0 to the maximum amount of
+ * steps returned in init_trim). negative ret on failure.
+ */
+int32_t afl_custom_post_trim(my_mutator_t *data, int success) {
+
+ if (success) {
+
+ ++data->cur_step;
+ return data->cur_step;
+
+ }
+
+ return data->trimmming_steps;
+
+}
+
+/**
+ * Perform a single custom mutation on a given input.
+ * This mutation is stacked with the other muatations in havoc.
+ *
+ * (Optional)
+ *
+ * @param[in] data pointer returned in afl_custom_init for this fuzz case
+ * @param[in] buf Pointer to the input data to be mutated and the mutated
+ * output
+ * @param[in] buf_size Size of input data
+ * @param[out] out_buf The output buffer. buf can be reused, if the content
+ * fits. *out_buf = NULL is treated as error.
+ * @param[in] max_size Maximum size of the mutated output. The mutation must
+ * not produce data larger than max_size.
+ * @return Size of the mutated output.
+ */
+size_t afl_custom_havoc_mutation(my_mutator_t *data, u8 *buf, size_t buf_size,
+ u8 **out_buf, size_t max_size) {
+
+ if (buf_size == 0) {
+
+ *out_buf = maybe_grow(BUF_PARAMS(data, havoc), 1);
+ if (!*out_buf) {
+
+ perror("custom havoc: maybe_grow");
+ return 0;
+
+ }
+
+ **out_buf = rand() % 256;
+ buf_size = 1;
+
+ } else {
+
+ // We reuse buf here. It's legal and faster.
+ *out_buf = buf;
+
+ }
+
+ size_t victim = rand() % buf_size;
+ (*out_buf)[victim] += rand() % 10;
+
+ return buf_size;
+
+}
+
+/**
+ * Return the probability (in percentage) that afl_custom_havoc_mutation
+ * is called in havoc. By default it is 6 %.
+ *
+ * (Optional)
+ *
+ * @param[in] data pointer returned in afl_custom_init for this fuzz case
+ * @return The probability (0-100).
+ */
+uint8_t afl_custom_havoc_mutation_probability(my_mutator_t *data) {
+
+ return 5; // 5 %
+
+}
+
+/**
+ * Determine whether the fuzzer should fuzz the queue entry or not.
+ *
+ * (Optional)
+ *
+ * @param[in] data pointer returned in afl_custom_init for this fuzz case
+ * @param filename File name of the test case in the queue entry
+ * @return Return True(1) if the fuzzer will fuzz the queue entry, and
+ * False(0) otherwise.
+ */
+uint8_t afl_custom_queue_get(my_mutator_t *data, const uint8_t *filename) {
+
+ return 1;
+
+}
+
+/**
+ * Allow for additional analysis (e.g. calling a different tool that does a
+ * different kind of coverage and saves this for the custom mutator).
+ *
+ * (Optional)
+ *
+ * @param data pointer returned in afl_custom_init for this fuzz case
+ * @param filename_new_queue File name of the new queue entry
+ * @param filename_orig_queue File name of the original queue entry
+ */
+void afl_custom_queue_new_entry(my_mutator_t * data,
+ const uint8_t *filename_new_queue,
+ const uint8_t *filename_orig_queue) {
+
+ /* Additional analysis on the original or new test case */
+
+}
+
+/**
+ * Deinitialize everything
+ *
+ * @param data The data ptr from afl_custom_init
+ */
+void afl_custom_deinit(my_mutator_t *data) {
+
+ free(data->post_process_buf);
+ free(data->havoc_buf);
+ free(data->data_buf);
+ free(data->fuzz_buf);
+ free(data->trim_buf);
+ free(data);
+
+}
+
diff --git a/custom_mutators/examples/example.py b/custom_mutators/examples/example.py
new file mode 100644
index 00000000..3a6d22e4
--- /dev/null
+++ b/custom_mutators/examples/example.py
@@ -0,0 +1,187 @@
+#!/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
+
+
+COMMANDS = [
+ b"GET",
+ b"PUT",
+ b"DEL",
+ b"AAAAAAAAAAAAAAAAA",
+]
+
+
+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)
+
+
+def deinit():
+ pass
+
+
+def fuzz(buf, add_buf, max_size):
+ """
+ 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.
+
+ @type max_size: int
+ @param max_size: Maximum size of the mutated output. The mutation must not
+ produce data larger than max_size.
+
+ @rtype: bytearray
+ @return: A new bytearray containing the mutated data
+ """
+ ret = bytearray(100)
+
+ ret[:3] = random.choice(COMMANDS)
+
+ 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
+#
+# def post_process(buf):
+# '''
+# Called just before the execution to write the test case in the format
+# expected by the target
+#
+# @type buf: bytearray
+# @param buf: The buffer containing the test case to be executed
+#
+# @rtype: bytearray
+# @return: The buffer containing the test case after
+# '''
+# return buf
+#
+# def havoc_mutation(buf, max_size):
+# '''
+# Perform a single custom mutation on a given input.
+#
+# @type buf: bytearray
+# @param buf: The buffer that should be mutated.
+#
+# @type max_size: int
+# @param max_size: Maximum size of the mutated output. The mutation must not
+# produce data larger than max_size.
+#
+# @rtype: bytearray
+# @return: A new bytearray containing the mutated data
+# '''
+# return mutated_buf
+#
+# def havoc_mutation_probability():
+# '''
+# Called for each `havoc_mutation`. Return the probability (in percentage)
+# that `havoc_mutation` is called in havoc. Be default it is 6%.
+#
+# @rtype: int
+# @return: The probability (0-100)
+# '''
+# return prob
+#
+# def queue_get(filename):
+# '''
+# Called at the beginning of each fuzz iteration to determine whether the
+# test case should be fuzzed
+#
+# @type filename: str
+# @param filename: File name of the test case in the current queue entry
+#
+# @rtype: bool
+# @return: Return True if the custom mutator decides to fuzz the test case,
+# and False otherwise
+# '''
+# return True
+#
+# def queue_new_entry(filename_new_queue, filename_orig_queue):
+# '''
+# Called after adding a new test case to the queue
+#
+# @type filename_new_queue: str
+# @param filename_new_queue: File name of the new queue entry
+#
+# @type filename_orig_queue: str
+# @param filename_orig_queue: File name of the original queue entry
+# '''
+# pass
diff --git a/custom_mutators/examples/post_library_gif.so.c b/custom_mutators/examples/post_library_gif.so.c
new file mode 100644
index 00000000..ac10f409
--- /dev/null
+++ b/custom_mutators/examples/post_library_gif.so.c
@@ -0,0 +1,165 @@
+/*
+ american fuzzy lop++ - postprocessor library example
+ --------------------------------------------------
+
+ Originally written by Michal Zalewski
+ Edited by Dominik Maier, 2020
+
+ 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_custom_post_process() function for every mutated output
+ buffer. From there, you have three choices:
+
+ 1) If you don't want to modify the test case, simply set `*out_buf = in_buf`
+ and return the original `len`.
+
+ 2) If you want to skip this test case altogether and have AFL generate a
+ new one, return 0 or set `*out_buf = 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 as out_buf. Return an appropriate len
+ afterwards.
+
+ 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).
+
+ *** Feel free to reuse the original 'in_buf' BUFFER and return it. ***
+
+ 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"
+
+typedef struct post_state {
+
+ unsigned char *buf;
+ size_t size;
+
+} post_state_t;
+
+void *afl_custom_init(void *afl) {
+
+ post_state_t *state = malloc(sizeof(post_state_t));
+ if (!state) {
+
+ perror("malloc");
+ return NULL;
+
+ }
+
+ state->buf = calloc(sizeof(unsigned char), 4096);
+ if (!state->buf) {
+
+ free(state);
+ perror("calloc");
+ return NULL;
+
+ }
+
+ return state;
+
+}
+
+/* The actual postprocessor routine called by afl-fuzz: */
+
+size_t afl_custom_post_process(post_state_t *data, unsigned char *in_buf,
+ unsigned int len, unsigned char **out_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 0;
+
+ /* Do nothing for buffers that already start with the expected header. */
+
+ if (!memcmp(in_buf, HEADER, strlen(HEADER))) {
+
+ *out_buf = in_buf;
+ return len;
+
+ }
+
+ /* Allocate memory for new buffer, reusing previous allocation if
+ possible. */
+
+ *out_buf = realloc(data->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 (!*out_buf) {
+
+ *out_buf = in_buf;
+ return len;
+
+ }
+
+ /* Copy the original data to the new location. */
+
+ memcpy(*out_buf, in_buf, len);
+
+ /* Insert the new header. */
+
+ memcpy(*out_buf, HEADER, strlen(HEADER));
+
+ /* Return the new len. It hasn't changed, so it's just len. */
+
+ return len;
+
+}
+
+/* Gets called afterwards */
+void afl_custom_deinit(post_state_t *data) {
+
+ free(data->buf);
+ free(data);
+
+}
+
diff --git a/custom_mutators/examples/post_library_png.so.c b/custom_mutators/examples/post_library_png.so.c
new file mode 100644
index 00000000..941f7e55
--- /dev/null
+++ b/custom_mutators/examples/post_library_png.so.c
@@ -0,0 +1,163 @@
+/*
+ american fuzzy lop++ - postprocessor for PNG
+ ------------------------------------------
+
+ Originally written by Michal Zalewski
+
+ Copyright 2015 Google Inc. All rights reserved.
+ Adapted to the new API, 2020 by Dominik Maier
+
+ 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)
+
+typedef struct post_state {
+
+ unsigned char *buf;
+ size_t size;
+
+} post_state_t;
+
+void *afl_custom_init(void *afl) {
+
+ post_state_t *state = malloc(sizeof(post_state_t));
+ if (!state) {
+
+ perror("malloc");
+ return NULL;
+
+ }
+
+ state->buf = calloc(sizeof(unsigned char), 4096);
+ if (!state->buf) {
+
+ free(state);
+ perror("calloc");
+ return NULL;
+
+ }
+
+ return state;
+
+}
+
+size_t afl_custom_post_process(post_state_t *data, const unsigned char *in_buf,
+ unsigned int len,
+ const unsigned char **out_buf) {
+
+ 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) {
+
+ *out_buf = in_buf;
+ return len;
+
+ }
+
+ /* 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 <= data->size) {
+
+ new_buf = data->buf;
+
+ } else {
+
+ new_buf = realloc(data->buf, UP4K(len));
+ if (!new_buf) {
+
+ *out_buf = in_buf;
+ return len;
+
+ }
+
+ data->buf = new_buf;
+ data->size = 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;
+
+ }
+
+ *out_buf = new_buf;
+ return len;
+
+}
+
+/* Gets called afterwards */
+void afl_custom_deinit(post_state_t *data) {
+
+ free(data->buf);
+ free(data);
+
+}
+
diff --git a/custom_mutators/examples/simple-chunk-replace.py b/custom_mutators/examples/simple-chunk-replace.py
new file mode 100644
index 00000000..c57218dd
--- /dev/null
+++ b/custom_mutators/examples/simple-chunk-replace.py
@@ -0,0 +1,66 @@
+#!/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)
+
+
+def fuzz(buf, add_buf, max_size):
+ """
+ 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.
+
+ @type max_size: int
+ @param max_size: Maximum size of the mutated output. The mutation must not
+ produce data larger than max_size.
+
+ @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/custom_mutators/examples/simple_example.c b/custom_mutators/examples/simple_example.c
new file mode 100644
index 00000000..d888ec1f
--- /dev/null
+++ b/custom_mutators/examples/simple_example.c
@@ -0,0 +1,74 @@
+// This simple example just creates random buffer <= 100 filled with 'A'
+// needs -I /path/to/AFLplusplus/include
+#include "custom_mutator_helpers.h"
+
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+
+#ifndef _FIXED_CHAR
+ #define _FIXED_CHAR 0x41
+#endif
+
+typedef struct my_mutator {
+
+ afl_t *afl;
+
+ // Reused buffers:
+ BUF_VAR(u8, fuzz);
+
+} my_mutator_t;
+
+my_mutator_t *afl_custom_init(afl_t *afl, unsigned int seed) {
+
+ srand(seed);
+ my_mutator_t *data = calloc(1, sizeof(my_mutator_t));
+ if (!data) {
+
+ perror("afl_custom_init alloc");
+ return NULL;
+
+ }
+
+ data->afl = afl;
+
+ return data;
+
+}
+
+size_t afl_custom_fuzz(my_mutator_t *data, uint8_t *buf, size_t buf_size,
+ u8 **out_buf, uint8_t *add_buf,
+ size_t add_buf_size, // add_buf can be NULL
+ size_t max_size) {
+
+ int size = (rand() % 100) + 1;
+ if (size > max_size) size = max_size;
+ u8 *mutated_out = maybe_grow(BUF_PARAMS(data, fuzz), size);
+ if (!mutated_out) {
+
+ *out_buf = NULL;
+ perror("custom mutator allocation (maybe_grow)");
+ return 0; /* afl-fuzz will very likely error out after this. */
+
+ }
+
+ memset(mutated_out, _FIXED_CHAR, size);
+
+ *out_buf = mutated_out;
+ return size;
+
+}
+
+/**
+ * Deinitialize everything
+ *
+ * @param data The data ptr from afl_custom_init
+ */
+void afl_custom_deinit(my_mutator_t *data) {
+
+ free(data->fuzz_buf);
+ free(data);
+
+}
+
diff --git a/custom_mutators/examples/wrapper_afl_min.py b/custom_mutators/examples/wrapper_afl_min.py
new file mode 100644
index 00000000..5cd60031
--- /dev/null
+++ b/custom_mutators/examples/wrapper_afl_min.py
@@ -0,0 +1,123 @@
+#!/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, max_size):
+ """
+ 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 Exception:
+ 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 Exception:
+ 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 Exception:
+ 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 Exception:
+ 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)