aboutsummaryrefslogtreecommitdiff
path: root/examples/custom_mutators
diff options
context:
space:
mode:
authorh1994st <h1994st@gmail.com>2020-03-04 01:09:37 -0500
committerh1994st <h1994st@gmail.com>2020-03-04 01:09:37 -0500
commit38e7dd2b9efbd9c6cda47774630a82660d3156b3 (patch)
treeb889bbc0c5c07f5f84b50253d675390305953c0b /examples/custom_mutators
parent42ce48db39ba487caeb9477535364170fccf956a (diff)
downloadafl++-38e7dd2b9efbd9c6cda47774630a82660d3156b3.tar.gz
Update examples of the custom mutator
- Merge `examples/python_mutators` into `examples/custom_mutators` - Remove `examples/python_mutators` - Update existing examples to demonstrate new APIs
Diffstat (limited to 'examples/custom_mutators')
-rw-r--r--examples/custom_mutators/README.md24
-rw-r--r--examples/custom_mutators/XmlMutatorMin.py332
-rw-r--r--examples/custom_mutators/common.py40
-rw-r--r--examples/custom_mutators/example.c177
-rw-r--r--examples/custom_mutators/example.py122
-rw-r--r--examples/custom_mutators/simple-chunk-replace.py64
-rw-r--r--examples/custom_mutators/simple_mutator.c49
-rw-r--r--examples/custom_mutators/wrapper_afl_min.py118
8 files changed, 874 insertions, 52 deletions
diff --git a/examples/custom_mutators/README.md b/examples/custom_mutators/README.md
index 6da288ab..ce49436e 100644
--- a/examples/custom_mutators/README.md
+++ b/examples/custom_mutators/README.md
@@ -1,4 +1,22 @@
-# A simple example for AFL_CUSTOM_MUTATOR_LIBRARY
+# Examples for the custom mutator
-This is a simple example for the AFL_CUSTOM_MUTATOR_LIBRARY feature.
-For more information see [docs/custom_mutator.md](../docs/custom_mutator.md)
+These are example and helper files for the custom mutator feature.
+See [docs/python_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 pyton2.7 to compile python2 scripts!
+
+example.c - this is a simple example written in C and should be compiled to a
+ shared library
+
+example.py - this is the template you can use, the functions are there but they
+ are empty
+
+simple-chunk-replace.py - this is a simple example where chunks are replaced
+
+common.py - this can be used for common functions and helpers.
+ the examples do not use this though. But you can :)
+
+wrapper_afl_min.py - mutation of XML documents, loads XmlMutatorMin.py
+
+XmlMutatorMin.py - module for XML mutation
diff --git a/examples/custom_mutators/XmlMutatorMin.py b/examples/custom_mutators/XmlMutatorMin.py
new file mode 100644
index 00000000..4c80a2ba
--- /dev/null
+++ b/examples/custom_mutators/XmlMutatorMin.py
@@ -0,0 +1,332 @@
+#!/usr/bin/python
+
+""" Mutation of XML documents, should be called from one of its wrappers (CLI, AFL, ...) """
+
+from __future__ import print_function
+from copy import deepcopy
+from lxml import etree as ET
+import random, re, io
+
+
+###########################
+# The XmlMutatorMin class #
+###########################
+
+class XmlMutatorMin:
+
+ """
+ Optionals parameters:
+ seed Seed used by the PRNG (default: "RANDOM")
+ verbose Verbosity (default: False)
+ """
+
+ def __init__(self, seed="RANDOM", verbose=False):
+
+ """ Initialize seed, database and mutators """
+
+ # Verbosity
+ self.verbose = verbose
+
+ # Initialize PRNG
+ self.seed = str(seed)
+ if self.seed == "RANDOM":
+ random.seed()
+ else:
+ if self.verbose:
+ print("Static seed '%s'" % self.seed)
+ random.seed(self.seed)
+
+ # Initialize input and output documents
+ self.input_tree = None
+ self.tree = None
+
+ # High-level mutators (no database needed)
+ hl_mutators_delete = ["del_node_and_children", "del_node_but_children", "del_attribute", "del_content"] # Delete items
+ hl_mutators_fuzz = ["fuzz_attribute"] # Randomly change attribute values
+
+ # Exposed mutators
+ self.hl_mutators_all = hl_mutators_fuzz + hl_mutators_delete
+
+ def __parse_xml(self, xml):
+
+ """ Parse an XML string. Basic wrapper around lxml.parse() """
+
+ try:
+ # Function parse() takes care of comments / DTD / processing instructions / ...
+ tree = ET.parse(io.BytesIO(xml))
+ except ET.ParseError:
+ raise RuntimeError("XML isn't well-formed!")
+ except LookupError as e:
+ raise RuntimeError(e)
+
+ # Return a document wrapper
+ return tree
+
+ def __exec_among(self, module, functions, min_times, max_times):
+
+ """ Randomly execute $functions between $min and $max times """
+
+ for i in xrange(random.randint(min_times, max_times)):
+ # Function names are mangled because they are "private"
+ getattr(module, "_XmlMutatorMin__" + random.choice(functions))()
+
+ def __serialize_xml(self, tree):
+
+ """ Serialize a XML document. Basic wrapper around lxml.tostring() """
+
+ return ET.tostring(tree, with_tail=False, xml_declaration=True, encoding=tree.docinfo.encoding)
+
+ def __ver(self, version):
+
+ """ Helper for displaying lxml version numbers """
+
+ return ".".join(map(str, version))
+
+ def reset(self):
+
+ """ Reset the mutator """
+
+ self.tree = deepcopy(self.input_tree)
+
+ def init_from_string(self, input_string):
+
+ """ Initialize the mutator from a XML string """
+
+ # Get a pointer to the top-element
+ self.input_tree = self.__parse_xml(input_string)
+
+ # Get a working copy
+ self.tree = deepcopy(self.input_tree)
+
+ def save_to_string(self):
+
+ """ Return the current XML document as UTF-8 string """
+
+ # Return a text version of the tree
+ return self.__serialize_xml(self.tree)
+
+ def __pick_element(self, exclude_root_node=False):
+
+ """ Pick a random element from the current document """
+
+ # Get a list of all elements, but nodes like PI and comments
+ elems = list(self.tree.getroot().iter(tag=ET.Element))
+
+ # Is the root node excluded?
+ if exclude_root_node:
+ start = 1
+ else:
+ start = 0
+
+ # Pick a random element
+ try:
+ elem_id = random.randint(start, len(elems) - 1)
+ elem = elems[elem_id]
+ except ValueError:
+ # Should only occurs if "exclude_root_node = True"
+ return (None, None)
+
+ return (elem_id, elem)
+
+ def __fuzz_attribute(self):
+
+ """ Fuzz (part of) an attribute value """
+
+ # Select a node to modify
+ (rand_elem_id, rand_elem) = self.__pick_element()
+
+ # Get all the attributes
+ attribs = rand_elem.keys()
+
+ # Is there attributes?
+ if len(attribs) < 1:
+ if self.verbose:
+ print("No attribute: can't replace!")
+ return
+
+ # Pick a random attribute
+ rand_attrib_id = random.randint(0, len(attribs) - 1)
+ rand_attrib = attribs[rand_attrib_id]
+
+ # We have the attribute to modify
+ # Get its value
+ attrib_value = rand_elem.get(rand_attrib)
+ # print("- Value: " + attrib_value)
+
+ # Should we work on the whole value?
+ func_call = "(?P<func>[a-zA-Z:\-]+)\((?P<args>.*?)\)"
+ p = re.compile(func_call)
+ l = p.findall(attrib_value)
+ if random.choice((True, False)) and l:
+ # Randomly pick one the function calls
+ (func, args) = random.choice(l)
+ # Split by "," and randomly pick one of the arguments
+ value = random.choice(args.split(','))
+ # Remove superfluous characters
+ unclean_value = value
+ value = value.strip(" ").strip("'")
+ # print("Selected argument: [%s]" % value)
+ else:
+ value = attrib_value
+
+ # For each type, define some possible replacement values
+ choices_number = ( \
+ "0", \
+ "11111", \
+ "-128", \
+ "2", \
+ "-1", \
+ "1/3", \
+ "42/0", \
+ "1094861636 idiv 1.0", \
+ "-1123329771506872 idiv 3.8", \
+ "17=$numericRTF", \
+ str(3 + random.randrange(0, 100)), \
+ )
+
+ choices_letter = ( \
+ "P" * (25 * random.randrange(1, 100)), \
+ "%s%s%s%s%s%s", \
+ "foobar", \
+ )
+
+ choices_alnum = ( \
+ "Abc123", \
+ "020F0302020204030204", \
+ "020F0302020204030204" * (random.randrange(5, 20)), \
+ )
+
+ # Fuzz the value
+ if random.choice((True,False)) and value == "":
+
+ # Empty
+ new_value = value
+
+ elif random.choice((True,False)) and value.isdigit():
+
+ # Numbers
+ new_value = random.choice(choices_number)
+
+ elif random.choice((True,False)) and value.isalpha():
+
+ # Letters
+ new_value = random.choice(choices_letter)
+
+ elif random.choice((True,False)) and value.isalnum():
+
+ # Alphanumeric
+ new_value = random.choice(choices_alnum)
+
+ else:
+
+ # Default type
+ new_value = random.choice(choices_alnum + choices_letter + choices_number)
+
+ # If we worked on a substring, apply changes to the whole string
+ if value != attrib_value:
+ # No ' around empty values
+ if new_value != "" and value != "":
+ new_value = "'" + new_value + "'"
+ # Apply changes
+ new_value = attrib_value.replace(unclean_value, new_value)
+
+ # Log something
+ if self.verbose:
+ print("Fuzzing attribute #%i '%s' of tag #%i '%s'" % (rand_attrib_id, rand_attrib, rand_elem_id, rand_elem.tag))
+
+ # Modify the attribute
+ rand_elem.set(rand_attrib, new_value.decode("utf-8"))
+
+ def __del_node_and_children(self):
+
+ """ High-level minimizing mutator
+ Delete a random node and its children (i.e. delete a random tree) """
+
+ self.__del_node(True)
+
+ def __del_node_but_children(self):
+
+ """ High-level minimizing mutator
+ Delete a random node but its children (i.e. link them to the parent of the deleted node) """
+
+ self.__del_node(False)
+
+ def __del_node(self, delete_children):
+
+ """ Called by the __del_node_* mutators """
+
+ # Select a node to modify (but the root one)
+ (rand_elem_id, rand_elem) = self.__pick_element(exclude_root_node=True)
+
+ # If the document includes only a top-level element
+ # Then we can't pick a element (given that "exclude_root_node = True")
+
+ # Is the document deep enough?
+ if rand_elem is None:
+ if self.verbose:
+ print("Can't delete a node: document not deep enough!")
+ return
+
+ # Log something
+ if self.verbose:
+ but_or_and = "and" if delete_children else "but"
+ print("Deleting tag #%i '%s' %s its children" % (rand_elem_id, rand_elem.tag, but_or_and))
+
+ if delete_children is False:
+ # Link children of the random (soon to be deleted) node to its parent
+ for child in rand_elem:
+ rand_elem.getparent().append(child)
+
+ # Remove the node
+ rand_elem.getparent().remove(rand_elem)
+
+ def __del_content(self):
+
+ """ High-level minimizing mutator
+ Delete the attributes and children of a random node """
+
+ # Select a node to modify
+ (rand_elem_id, rand_elem) = self.__pick_element()
+
+ # Log something
+ if self.verbose:
+ print("Reseting tag #%i '%s'" % (rand_elem_id, rand_elem.tag))
+
+ # Reset the node
+ rand_elem.clear()
+
+ def __del_attribute(self):
+
+ """ High-level minimizing mutator
+ Delete a random attribute from a random node """
+
+ # Select a node to modify
+ (rand_elem_id, rand_elem) = self.__pick_element()
+
+ # Get all the attributes
+ attribs = rand_elem.keys()
+
+ # Is there attributes?
+ if len(attribs) < 1:
+ if self.verbose:
+ print("No attribute: can't delete!")
+ return
+
+ # Pick a random attribute
+ rand_attrib_id = random.randint(0, len(attribs) - 1)
+ rand_attrib = attribs[rand_attrib_id]
+
+ # Log something
+ if self.verbose:
+ print("Deleting attribute #%i '%s' of tag #%i '%s'" % (rand_attrib_id, rand_attrib, rand_elem_id, rand_elem.tag))
+
+ # Delete the attribute
+ rand_elem.attrib.pop(rand_attrib)
+
+ def mutate(self, min=1, max=5):
+
+ """ Execute some high-level mutators between $min and $max times, then some medium-level ones """
+
+ # High-level mutation
+ self.__exec_among(self, self.hl_mutators_all, min, max)
+
diff --git a/examples/custom_mutators/common.py b/examples/custom_mutators/common.py
new file mode 100644
index 00000000..9a1ef0a3
--- /dev/null
+++ b/examples/custom_mutators/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/examples/custom_mutators/example.c b/examples/custom_mutators/example.c
new file mode 100644
index 00000000..63e4d6da
--- /dev/null
+++ b/examples/custom_mutators/example.c
@@ -0,0 +1,177 @@
+/*
+ New Custom Mutator for AFL++
+ Written by Khaled Yakdan <yakdan@code-intelligence.de>
+ Andrea Fioraldi <andreafioraldi@gmail.com>
+ Shengtuo Hu <h1994st@gmail.com>
+*/
+
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+static const char *commands[] = {
+
+ "GET",
+ "PUT",
+ "DEL",
+
+};
+
+static size_t data_size = 100;
+
+void afl_custom_init(unsigned int seed) {
+
+ srand(seed);
+
+}
+
+/**
+ * Perform custom mutations on a given input
+ *
+ * (Optional for now. Required in the future)
+ *
+ * @param[in] buf Input data to be mutated
+ * @param[in] buf_size Size of input data
+ * @param[in] add_buf Buffer containing the additional test case
+ * @param[in] add_buf_size Size of the additional test case
+ * @param[out] mutated_out Buffer to store the mutated input
+ * @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(uint8_t *buf, size_t buf_size,
+ uint8_t *add_buf,size_t add_buf_size, // add_buf can be NULL
+ uint8_t *mutated_out, 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;
+
+ // Randomly select a command string to add as a header to the packet
+ memcpy(mutated_out, commands[rand() % 3], 3);
+
+ // Mutate the payload of the packet
+ for (int i = 3; i < mutated_size; i++) {
+
+ mutated_out[i] = (data[i] + rand() % 10) & 0xff;
+
+ }
+
+ return mutated_size;
+
+}
+
+/**
+ * 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] 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. AFL++
+ * will release the memory after saving the test case.
+ * @return Size of the output buffer after processing
+ */
+size_t afl_custom_pre_save(uint8_t *buf, size_t buf_size, uint8_t **out_buf) {
+
+ size_t out_buf_size;
+
+ out_buf_size = buf_size;
+
+ // External mutator should allocate memory for `out_buf`
+ *out_buf = malloc(out_buf_size);
+ memcpy(*out_buf, buf, out_buf_size);
+
+ return out_buf_size;
+
+}
+
+uint8_t *trim_buf;
+size_t trim_buf_size
+int trimmming_steps;
+int cur_step;
+
+/**
+ * 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 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
+ */
+int afl_custom_init_trim(uint8_t *buf, size_t buf_size) {
+
+ // We simply trim once
+ trimmming_steps = 1;
+
+ cur_step = 0;
+ trim_buf = buf;
+ trim_buf_size = buf_size;
+
+ return 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 global variables. 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[out] out_buf Pointer to the buffer containing the trimmed test case.
+ * External library should allocate memory for out_buf. AFL++ will release
+ * the memory after saving the test case.
+ * @param[out] out_buf_size Pointer to the size of the trimmed test case
+ */
+void afl_custom_trim(uint8_t **out_buf, size_t* out_buf_size) {
+
+ *out_buf_size = trim_buf_size - 1;
+
+ // External mutator should allocate memory for `out_buf`
+ *out_buf = malloc(*out_buf_size);
+ // Remove the last byte of the trimming input
+ memcpy(*out_buf, trim_buf, *out_buf_size);
+
+}
+
+/**
+ * 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 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)
+ */
+int afl_custom_post_trim(int success) {
+
+ if (success) {
+ ++cur_step;
+ return cur_step;
+ }
+
+ return trimmming_steps;
+
+}
diff --git a/examples/custom_mutators/example.py b/examples/custom_mutators/example.py
new file mode 100644
index 00000000..a68f2ee5
--- /dev/null
+++ b/examples/custom_mutators/example.py
@@ -0,0 +1,122 @@
+#!/usr/bin/env python
+# encoding: utf-8
+'''
+Example Python Module for AFLFuzz
+
+@author: Christian Holler (:decoder)
+
+@license:
+
+This Source Code Form is subject to the terms of the Mozilla Public
+License, v. 2.0. If a copy of the MPL was not distributed with this
+file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+@contact: choller@mozilla.com
+'''
+
+import random
+
+
+def init(seed):
+ '''
+ Called once when AFLFuzz starts up. Used to seed our RNG.
+
+ @type seed: int
+ @param seed: A 32-bit random value
+ '''
+ random.seed(seed)
+
+
+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(buf)
+ # Do something interesting with ret
+
+ return ret
+
+# Uncomment and implement the following methods if you want to use a custom
+# trimming algorithm. See also the documentation for a better API description.
+
+# def init_trim(buf):
+# '''
+# Called per trimming iteration.
+#
+# @type buf: bytearray
+# @param buf: The buffer that should be trimmed.
+#
+# @rtype: int
+# @return: The maximum number of trimming steps.
+# '''
+# global ...
+#
+# # Initialize global variables
+#
+# # Figure out how many trimming steps are possible.
+# # If this is not possible for your trimming, you can
+# # return 1 instead and always return 0 in post_trim
+# # until you are done (then you return 1).
+#
+# return steps
+#
+# def trim():
+# '''
+# Called per trimming iteration.
+#
+# @rtype: bytearray
+# @return: A new bytearray containing the trimmed data.
+# '''
+# global ...
+#
+# # Implement the actual trimming here
+#
+# return bytearray(...)
+#
+# def post_trim(success):
+# '''
+# Called after each trimming operation.
+#
+# @type success: bool
+# @param success: Indicates if the last trim operation was successful.
+#
+# @rtype: int
+# @return: The next trim index (0 to max number of steps) where max
+# number of steps indicates the trimming is done.
+# '''
+# global ...
+#
+# if not success:
+# # Restore last known successful input, determine next index
+# else:
+# # Just determine the next index, based on what was successfully
+# # removed in the last step
+#
+# return next_index
+#
+# def pre_save(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
+#
diff --git a/examples/custom_mutators/simple-chunk-replace.py b/examples/custom_mutators/simple-chunk-replace.py
new file mode 100644
index 00000000..df2f4ca7
--- /dev/null
+++ b/examples/custom_mutators/simple-chunk-replace.py
@@ -0,0 +1,64 @@
+#!/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/examples/custom_mutators/simple_mutator.c b/examples/custom_mutators/simple_mutator.c
deleted file mode 100644
index bf655679..00000000
--- a/examples/custom_mutators/simple_mutator.c
+++ /dev/null
@@ -1,49 +0,0 @@
-/*
- Simple Custom Mutator for AFL
-
- Written by Khaled Yakdan <yakdan@code-intelligence.de>
-
- This a simple mutator that assumes that the generates messages starting with
- one of the three strings GET, PUT, or DEL followed by a payload. The mutator
- randomly selects a commend and mutates the payload of the seed provided as
- input.
-*/
-
-#include <stdint.h>
-#include <stdlib.h>
-#include <string.h>
-
-static const char *commands[] = {
-
- "GET",
- "PUT",
- "DEL",
-
-};
-
-static size_t data_size = 100;
-
-size_t afl_custom_mutator(uint8_t *data, size_t size, uint8_t *mutated_out,
- size_t max_size, unsigned int seed) {
-
- // Seed the PRNG
- srand(seed);
-
- // Make sure that the packet size does not exceed the maximum size expected by
- // the fuzzer
- size_t mutated_size = data_size <= max_size ? data_size : max_size;
-
- // Randomly select a command string to add as a header to the packet
- memcpy(mutated_out, commands[rand() % 3], 3);
-
- // Mutate the payload of the packet
- for (int i = 3; i < mutated_size; i++) {
-
- mutated_out[i] = (data[i] + rand() % 10) & 0xff;
-
- }
-
- return mutated_size;
-
-}
-
diff --git a/examples/custom_mutators/wrapper_afl_min.py b/examples/custom_mutators/wrapper_afl_min.py
new file mode 100644
index 00000000..ecb03b55
--- /dev/null
+++ b/examples/custom_mutators/wrapper_afl_min.py
@@ -0,0 +1,118 @@
+#!/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)