about summary refs log tree commit diff
path: root/custom_mutators/examples
diff options
context:
space:
mode:
authorvan Hauser <vh@thc.org>2021-05-10 13:57:47 +0200
committerGitHub <noreply@github.com>2021-05-10 13:57:47 +0200
commit8b7a7b29c60f11cdf6226b3e418e87a5c3f5caac (patch)
tree6ce9d90644f161d21d802e9cbe48eb38467684e9 /custom_mutators/examples
parentd0225c2c4d465968660a08c93857fed354e539b1 (diff)
downloadafl++-8b7a7b29c60f11cdf6226b3e418e87a5c3f5caac.tar.gz
Push to stable (#895)
* sync (#886)

* Create FUNDING.yml

* Update FUNDING.yml

* moved custom_mutator examples

* unicorn speedtest makefile cleanup

* fixed example location

* fix qdbi

* update util readme

* Frida persistent (#880)

* Added x64 support for persistent mode (function call only), in-memory teest cases and complog

* Review changes, fix NeverZero and code to parse the .text section of the main executable. Excluded ranges TBC

* Various minor fixes and finished support for AFL_INST_LIBS

* Review changes

Co-authored-by: Your Name <you@example.com>

* nits

* fix frida mode

* Integer overflow/underflow fixes in libdislocator (#889)

* libdislocator: fixing integer overflow in 'max_mem' variable and setting 'max_mem' type to 'size_t'

* libdislocator: fixing potential integer underflow in 'total_mem' variable due to its different values in different threads

* Bumped warnings up to the max and fixed remaining issues (#890)

Co-authored-by: Your Name <you@example.com>

* nits

* frida mode - support non-pie

* nits

* nit

* update grammar mutator

* Fixes for aarch64, OSX and other minor issues (#891)

Co-authored-by: Your Name <you@example.com>

* nits

* nits

* fix PCGUARD, build aflpp_driver with fPIC

* Added representative fuzzbench test and test for libxml (#893)

* Added representative fuzzbench test and test for libxml

* Added support for building FRIDA from source with FRIDA_SOURCE=1

Co-authored-by: Your Name <you@example.com>

* nits

* update changelog

* typos

* fixed potential double free in custom trim (#881)

* error handling, freeing mem

* frida: complog -> cmplog

* fix statsd writing

* let aflpp_qemu_driver_hook.so build fail gracefully

* fix stdin trimming

* Support for AFL_ENTRYPOINT (#898)

Co-authored-by: Your Name <you@example.com>

* remove the input file .cur_input at the end of the fuzzing, if AFL_TMPDIR is used

* reverse push (#901)

* Create FUNDING.yml

* Update FUNDING.yml

* disable QEMU static pie

Co-authored-by: Andrea Fioraldi <andreafioraldi@gmail.com>

* clarify that no modifications are required.

* add new test for frida_mode (please review)

* typos

* fix persistent mode (64-bit)

* set ARCH for linux intel 32-bit for frida-gum-devkit

* prepare for 32-bit support (later)

* not on qemu 3 anymore

* unicorn mips fixes

* instrumentation further move to C++11 (#900)

* unicorn fixes

* more unicorn fixes

* Fix memory errors when trim causes testcase growth (#881) (#903)

* Revert "fixed potential double free in custom trim (#881)"

This reverts commit e9d2f72382cab75832721d859c3e731da071435d.

* Revert "fix custom trim for increasing data"

This reverts commit 86a8ef168dda766d2f25f15c15c4d3ecf21d0667.

* Fix memory errors when trim causes testcase growth

Modify trim_case_custom to avoid writing into in_buf because
some custom mutators can cause the testcase to grow rather than
shrink.

Instead of modifying in_buf directly, we write the update out
to the disk when trimming is complete, and then the caller is
responsible for refreshing the in-memory buffer from the file.

This is still a bit sketchy because it does need to modify q->len in
order to notify the upper layers that something changed, and it could
end up telling upper layer code that the q->len is *bigger* than
the buffer (q->testcase_buf) that contains it, which is asking
for trouble down the line somewhere...

* Fix an unlikely situation

Put back some `unlikely()` calls that were in
the e9d2f72382cab75832721d859c3e731da071435d commit that was
reverted.

* typo

* Exit on time (#904)

* Variable AFL_EXIT_ON_TIME description has been added.
Variables AFL_EXIT_ON_TIME and afl_exit_on_time has been added.
afl->exit_on_time variable initialization has been added.
The asignment of a value to the afl->afl_env.afl_exit_on_time variable from
environment variables has been added.
Code to exit on timeout if new path not found has been added.

* Type of afl_exit_on_time variable has been changed.
Variable exit_on_time has been added to the afl_state_t structure.

* Command `export AFL_EXIT_WHEN_DONE=1` has been added.

* Millisecond to second conversion has been added.
Call get_cur_time() has been added.

* Revert to using the saved current time value.

* Useless check has been removed.

* fix new path to custom-mutators

* ensure crashes/README.txt exists

* fix

* Changes to bump FRIDA version and to clone FRIDA repo in to build directory rather than use a submodule as the FRIDA build scripts don't like it (#906)

Co-authored-by: Your Name <you@example.com>

* Fix numeric overflow in cmplog implementation (#907)

Co-authored-by: Your Name <you@example.com>

* testcase fixes for unicorn

* remove merge conflict artifacts

* fix afl-plot

* Changes to remove binaries from frida_mode (#913)

Co-authored-by: Your Name <you@example.com>

* Frida cmplog fail fast (#914)

* Changes to remove binaries from frida_mode

* Changes to make cmplog fail fast

Co-authored-by: Your Name <you@example.com>

* afl-plot: relative time

* arch linux and mac os support for afl-system-config

* typo

* code-format

* update documentation

Co-authored-by: Dominik Maier <domenukk@gmail.com>
Co-authored-by: WorksButNotTested <62701594+WorksButNotTested@users.noreply.github.com>
Co-authored-by: Your Name <you@example.com>
Co-authored-by: Dmitry Zheregelya <zheregelya.d@gmail.com>
Co-authored-by: hexcoder <hexcoder-@users.noreply.github.com>
Co-authored-by: hexcoder- <heiko@hexco.de>
Co-authored-by: Andrea Fioraldi <andreafioraldi@gmail.com>
Co-authored-by: David CARLIER <devnexen@gmail.com>
Co-authored-by: realmadsci <71108352+realmadsci@users.noreply.github.com>
Co-authored-by: Roman M. Iudichev <SecNotice@ya.ru>
Diffstat (limited to 'custom_mutators/examples')
-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
12 files changed, 1926 insertions, 0 deletions
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)