diff options
-rw-r--r-- | .gitignore | 18 | ||||
-rwxr-xr-x | afl-system-config | 1 | ||||
-rw-r--r-- | afl-tmin.c | 189 | ||||
-rw-r--r-- | docs/ChangeLog | 8 | ||||
-rw-r--r-- | docs/PATCHES | 5 | ||||
-rw-r--r-- | docs/README | 4 | ||||
-rw-r--r-- | docs/env_variables.txt | 6 | ||||
-rw-r--r-- | llvm_mode/Makefile | 2 | ||||
-rw-r--r-- | llvm_mode/README.laf-intel | 8 | ||||
-rw-r--r-- | llvm_mode/afl-clang-fast.c | 6 | ||||
-rw-r--r-- | llvm_mode/split-compares-pass.so.cc | 2 | ||||
-rw-r--r-- | python_mutators/README | 4 | ||||
-rw-r--r-- | python_mutators/XmlMutatorMin.py | 331 | ||||
-rw-r--r-- | python_mutators/wrapper_afl_min.py | 117 |
14 files changed, 667 insertions, 34 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..cc0fd5c7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,18 @@ +.gitignore +TODO +afl-analyze +afl-as +afl-clang +afl-clang++ +afl-clang-fast +afl-clang-fast++ +afl-fuzz +afl-g++ +afl-gcc +afl-gotcpu +afl-qemu-trace +afl-showmap +afl-tmin +as +qemu_mode/qemu-3.1.0 +qemu_mode/qemu-3.1.0.tar.xz diff --git a/afl-system-config b/afl-system-config index 88564c20..7538bc29 100755 --- a/afl-system-config +++ b/afl-system-config @@ -8,6 +8,7 @@ sysctl -w kernel.sched_migration_cost_ns=50000000 sysctl -w kernel.sched_latency_ns=250000000 echo never > /sys/kernel/mm/transparent_hugepage/enabled echo performance | tee /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor > /dev/null +test -e /sys/devices/system/cpu/intel_pstate/no_turbo && echo 0 > /sys/devices/system/cpu/intel_pstate/no_turbo echo echo It is recommended to boot the kernel with lots of security off - if you are running a machine that is in a secured network - so set this: echo '/etc/default/grub:GRUB_CMDLINE_LINUX_DEFAULT="ibpb=off ibrs=off kpti=off l1tf=off mds=off mitigations=off no_stf_barrier noibpb noibrs nopcid nopti nospec_store_bypass_disable nospectre_v1 nospectre_v2 pcid=off pti=off spec_store_bypass_disable=off spectre_v2=off stf_barrier=off"' diff --git a/afl-tmin.c b/afl-tmin.c index 2d839041..a42be6e9 100644 --- a/afl-tmin.c +++ b/afl-tmin.c @@ -44,7 +44,11 @@ #include <sys/types.h> #include <sys/resource.h> -static s32 child_pid; /* PID of the tested program */ +static s32 forksrv_pid, /* PID of the fork server */ + child_pid; /* PID of the tested program */ + +static s32 fsrv_ctl_fd, /* Fork server control pipe (write) */ + fsrv_st_fd; /* Fork server status pipe (read) */ static u8 *trace_bits, /* SHM with instrumentation bitmap */ *mask_bitmap; /* Mask for trace bits (-B) */ @@ -55,6 +59,8 @@ static u8 *in_file, /* Minimizer input test case */ *target_path, /* Path to target binary */ *doc_path; /* Path to docs */ +static s32 prog_in_fd; /* Persistent fd for prog_in */ + static u8* in_data; /* Input data for trimming */ static u32 in_len, /* Input data length */ @@ -236,38 +242,70 @@ static s32 write_to_file(u8* path, u8* mem, u32 len) { } +/* Write modified data to file for testing. If use_stdin is clear, the old file + is unlinked and a new one is created. Otherwise, prog_in_fd is rewound and + truncated. */ + +static void write_to_testcase(void* mem, u32 len) { + + s32 fd = prog_in_fd; + + if (!use_stdin) { + + unlink(prog_in); /* Ignore errors. */ + + fd = open(prog_in, O_WRONLY | O_CREAT | O_EXCL, 0600); + + if (fd < 0) PFATAL("Unable to create '%s'", prog_in); + + } else lseek(fd, 0, SEEK_SET); + + ck_write(fd, mem, len, prog_in); + + if (use_stdin) { + + if (ftruncate(fd, len)) PFATAL("ftruncate() failed"); + lseek(fd, 0, SEEK_SET); + + } else close(fd); + +} + + /* Handle timeout signal. */ static void handle_timeout(int sig) { + if (child_pid > 0) { + child_timed_out = 1; - if (child_pid > 0) kill(child_pid, SIGKILL); + kill(child_pid, SIGKILL); -} + } else if (child_pid == -1 && forksrv_pid > 0) { + child_timed_out = 1; + kill(forksrv_pid, SIGKILL); -/* Execute target application. Returns 0 if the changes are a dud, or - 1 if they should be kept. */ + } -static u8 run_target(char** argv, u8* mem, u32 len, u8 first_run) { +} +/* start the app and it's forkserver */ +static void init_forkserver(char **argv) { static struct itimerval it; + int st_pipe[2], ctl_pipe[2]; int status = 0; + s32 rlen; - s32 prog_in_fd; - u32 cksum; - - memset(trace_bits, 0, MAP_SIZE); - MEM_BARRIER(); - - prog_in_fd = write_to_file(prog_in, mem, len); + ACTF("Spinning up the fork server..."); + if (pipe(st_pipe) || pipe(ctl_pipe)) PFATAL("pipe() failed"); - child_pid = fork(); + forksrv_pid = fork(); - if (child_pid < 0) PFATAL("fork() failed"); + if (forksrv_pid < 0) PFATAL("fork() failed"); - if (!child_pid) { + if (!forksrv_pid) { struct rlimit r; @@ -304,6 +342,16 @@ static u8 run_target(char** argv, u8* mem, u32 len, u8 first_run) { r.rlim_max = r.rlim_cur = 0; setrlimit(RLIMIT_CORE, &r); /* Ignore errors */ + /* Set up control and status pipes, close the unneeded original fds. */ + + if (dup2(ctl_pipe[0], FORKSRV_FD) < 0) PFATAL("dup2() failed"); + if (dup2(st_pipe[1], FORKSRV_FD + 1) < 0) PFATAL("dup2() failed"); + + close(ctl_pipe[0]); + close(ctl_pipe[1]); + close(st_pipe[0]); + close(st_pipe[1]); + execv(target_path, argv); *(u32*)trace_bits = EXEC_FAIL_SIG; @@ -311,17 +359,113 @@ static u8 run_target(char** argv, u8* mem, u32 len, u8 first_run) { } - close(prog_in_fd); + /* Close the unneeded endpoints. */ + + close(ctl_pipe[0]); + close(st_pipe[1]); + + fsrv_ctl_fd = ctl_pipe[1]; + fsrv_st_fd = st_pipe[0]; + + /* Configure timeout, wait for child, cancel timeout. */ + + if (exec_tmout) { + + child_timed_out = 0; + it.it_value.tv_sec = (exec_tmout * FORK_WAIT_MULT / 1000); + it.it_value.tv_usec = ((exec_tmout * FORK_WAIT_MULT) % 1000) * 1000; + + } + + setitimer(ITIMER_REAL, &it, NULL); + + rlen = read(fsrv_st_fd, &status, 4); + + it.it_value.tv_sec = 0; + it.it_value.tv_usec = 0; + setitimer(ITIMER_REAL, &it, NULL); + + /* If we have a four-byte "hello" message from the server, we're all set. + Otherwise, try to figure out what went wrong. */ + + if (rlen == 4) { + ACTF("All right - fork server is up."); + return; + } + + if (waitpid(forksrv_pid, &status, 0) <= 0) + PFATAL("waitpid() failed"); + + u8 child_crashed; + + if (WIFSIGNALED(status)) + child_crashed = 1; + + if (child_timed_out) + SAYF(cLRD "\n+++ Program timed off +++\n" cRST); + else if (stop_soon) + SAYF(cLRD "\n+++ Program aborted by user +++\n" cRST); + else if (child_crashed) + SAYF(cLRD "\n+++ Program killed by signal %u +++\n" cRST, WTERMSIG(status)); + +} + + +/* Execute target application. Returns 0 if the changes are a dud, or + 1 if they should be kept. */ + +static u8 run_target(char** argv, u8* mem, u32 len, u8 first_run) { + + static struct itimerval it; + static u32 prev_timed_out = 0; + int status = 0; + + u32 cksum; + + memset(trace_bits, 0, MAP_SIZE); + MEM_BARRIER(); + + write_to_testcase(mem, len); + + s32 res; + + /* we have the fork server up and running, so simply + tell it to have at it, and then read back PID. */ + + if ((res = write(fsrv_ctl_fd, &prev_timed_out, 4)) != 4) { + + if (stop_soon) return 0; + RPFATAL(res, "Unable to request new process from fork server (OOM?)"); + + } + + if ((res = read(fsrv_st_fd, &child_pid, 4)) != 4) { + + if (stop_soon) return 0; + RPFATAL(res, "Unable to request new process from fork server (OOM?)"); + + } + + if (child_pid <= 0) FATAL("Fork server is misbehaving (OOM?)"); /* Configure timeout, wait for child, cancel timeout. */ + if (exec_tmout) { + child_timed_out = 0; it.it_value.tv_sec = (exec_tmout / 1000); it.it_value.tv_usec = (exec_tmout % 1000) * 1000; + } + setitimer(ITIMER_REAL, &it, NULL); - if (waitpid(child_pid, &status, 0) <= 0) FATAL("waitpid() failed"); + if ((res = read(fsrv_st_fd, &status, 4)) != 4) { + + if (stop_soon) return 0; + RPFATAL(res, "Unable to communicate with fork server (OOM?)"); + + } child_pid = 0; it.it_value.tv_sec = 0; @@ -687,6 +831,13 @@ static void set_up_environment(void) { } + unlink(prog_in); + + prog_in_fd = open(prog_in, O_RDWR | O_CREAT | O_EXCL, 0600); + + if (prog_in_fd < 0) PFATAL("Unable to create '%s'", prog_in); + + /* Set sane defaults... */ x = getenv("ASAN_OPTIONS"); @@ -1113,6 +1264,8 @@ int main(int argc, char** argv) { read_initial_file(); + init_forkserver(use_argv); + ACTF("Performing dry run (mem limit = %llu MB, timeout = %u ms%s)...", mem_limit, exec_tmout, edges_only ? ", edges only" : ""); diff --git a/docs/ChangeLog b/docs/ChangeLog index a533de05..b8d0d7ac 100644 --- a/docs/ChangeLog +++ b/docs/ChangeLog @@ -19,15 +19,21 @@ Version ++2.52d (tbd): - added never zero counters for afl-gcc and optional (because of an optimization issue in llvm < 9) for llvm_mode (AFL_LLVM_NEVER_ZERO=1) + - more cpu power for afl-system-config + - added forkserver patch to afl-tmin, makes it much faster (originally from + github.com/nccgroup/TriforceAFL) - added whitelist support for llvm_mode via AFL_LLVM_WHITELIST to allow only to instrument what is actually interesting. Gives more speed and less map pollution (originally by choller@mozilla) - added Python Module mutator support, python2.7-dev is autodetected. see docs/python_mutators.txt (originally by choller@mozilla) - - added AFL_CAL_FAST for slow applications and AFL_DEBUG_CHILD_OUTPUT for debugging + - added AFL_CAL_FAST for slow applications and AFL_DEBUG_CHILD_OUTPUT for + debugging - added a -s seed switch to allow afl run with a fixed initial seed that is not updated. this is good for performance and path discovery tests as the random numbers are deterministic then + - llvm_mode LAF_... env variables can now be specified as AFL_LLVM_LAF_... + that is longer but in line with other llvm specific env vars - ... your idea or patch? diff --git a/docs/PATCHES b/docs/PATCHES index c933e031..cb050218 100644 --- a/docs/PATCHES +++ b/docs/PATCHES @@ -19,8 +19,9 @@ afl-qemu-optimize-map.diff by mh(at)mh-sec(dot)de + AFLfast additions (github.com/mboehme/aflfast) were incorporated. + Qemu 3.1 upgrade with enhancement patches (github.com/andreafioraldi/afl) -+ Python mutator modules support (github.com/choeller/afl) -+ Whitelisting in LLVM mode (github.com/choeller/afl) ++ Python mutator modules support (github.com/choller/afl) ++ Whitelisting in LLVM mode (github.com/choller/afl) ++ forkserver patch for afl-tmin (github.com/nccgroup/TriforceAFL) NOT INSTALLED diff --git a/docs/README b/docs/README index ca8533f7..f9734528 100644 --- a/docs/README +++ b/docs/README @@ -21,8 +21,8 @@ american fuzzy lop plus plus https://github.com/andreafioraldi/afl and got the community patches applied to it. - C. Hoellers afl-fuzz Python mutator module and llvm_mode whitelist support - was added too (https://github.com/choeller/afl) + C. Hollers afl-fuzz Python mutator module and llvm_mode whitelist support + was added too (https://github.com/choller/afl) So all in all this is the best-of AFL that is currently out there :-) diff --git a/docs/env_variables.txt b/docs/env_variables.txt index f8c6c86a..d854ea8d 100644 --- a/docs/env_variables.txt +++ b/docs/env_variables.txt @@ -89,11 +89,11 @@ Then there are a few specific features that are only available in llvm_mode: This great feature will split compares to series of single byte comparisons to allow afl-fuzz to find otherwise rather impossible paths. - - Setting LAF_SPLIT_SWITCHES will split switch()es + - Setting AFL_LLVM_LAF_SPLIT_SWITCHES will split switch()es - - Setting LAF_TRANSFORM_COMPARES will split string compare functions + - Setting AFL_LLVM_LAF_TRANSFORM_COMPARES will split string compare functions - - Setting LAF_SPLIT_COMPARES will split > 8 bit CMP instructions + - Setting AFL_LLVM_LAF_SPLIT_COMPARES will split > 8 bit CMP instructions See llvm_mode/README.laf-intel for more information. diff --git a/llvm_mode/Makefile b/llvm_mode/Makefile index 76de10c0..b6ab0c61 100644 --- a/llvm_mode/Makefile +++ b/llvm_mode/Makefile @@ -116,7 +116,7 @@ endif test_build: $(PROGS) @echo "[*] Testing the CC wrapper and instrumentation output..." - unset AFL_USE_ASAN AFL_USE_MSAN AFL_INST_RATIO; AFL_QUIET=1 AFL_PATH=. AFL_CC=$(CC) LAF_SPLIT_SWITCHES=1 LAF_TRANSFORM_COMPARES=1 LAF_SPLIT_COMPARES=1 ../afl-clang-fast $(CFLAGS) ../test-instr.c -o test-instr $(LDFLAGS) + unset AFL_USE_ASAN AFL_USE_MSAN AFL_INST_RATIO; AFL_QUIET=1 AFL_PATH=. AFL_CC=$(CC) AFL_LLVM_LAF_SPLIT_SWITCHES=1 AFL_LLVM_LAF_TRANSFORM_COMPARES=1 AFL_LLVM_LAF_SPLIT_COMPARES=1 ../afl-clang-fast $(CFLAGS) ../test-instr.c -o test-instr $(LDFLAGS) echo 0 | ../afl-showmap -m none -q -o .test-instr0 ./test-instr echo 1 | ../afl-showmap -m none -q -o .test-instr1 ./test-instr @rm -f test-instr diff --git a/llvm_mode/README.laf-intel b/llvm_mode/README.laf-intel index 891ab5fd..340216c3 100644 --- a/llvm_mode/README.laf-intel +++ b/llvm_mode/README.laf-intel @@ -8,13 +8,13 @@ compile the target project. The following options exist: -export LAF_SPLIT_SWITCHES=1 Enables the split-switches pass. +export AFL_LLVM_LAF_SPLIT_SWITCHES=1 Enables the split-switches pass. -export LAF_TRANSFORM_COMPARES=1 Enables the transform-compares pass +export AFL_LLVM_LAF_TRANSFORM_COMPARES=1 Enables the transform-compares pass (strcmp, memcmp, strncmp, strcasecmp, strncasecmp). -export LAF_SPLIT_COMPARES=1 Enables the split-compares pass. +export AFL_LLVM_LAF_SPLIT_COMPARES=1 Enables the split-compares pass. By default it will split all compares with a bit width <= 64 bits. You can change this behaviour by setting - export LAF_SPLIT_COMPARES_BITW=<bit_width>. + export AFL_LLVM_LAF_SPLIT_COMPARES_BITW=<bit_width>. diff --git a/llvm_mode/afl-clang-fast.c b/llvm_mode/afl-clang-fast.c index 1e2e04ea..5bc4ae8c 100644 --- a/llvm_mode/afl-clang-fast.c +++ b/llvm_mode/afl-clang-fast.c @@ -120,21 +120,21 @@ static void edit_params(u32 argc, char** argv) { http://clang.llvm.org/docs/SanitizerCoverage.html#tracing-pcs-with-guards */ // laf - if (getenv("LAF_SPLIT_SWITCHES")) { + if (getenv("LAF_SPLIT_SWITCHES")||getenv("AFL_LLVM_LAF_SPLIT_SWITCHES")) { cc_params[cc_par_cnt++] = "-Xclang"; cc_params[cc_par_cnt++] = "-load"; cc_params[cc_par_cnt++] = "-Xclang"; cc_params[cc_par_cnt++] = alloc_printf("%s/split-switches-pass.so", obj_path); } - if (getenv("LAF_TRANSFORM_COMPARES")) { + if (getenv("LAF_TRANSFORM_COMPARES")||getenv("AFL_LLVM_LAF_TRANSFORM_COMPARES")) { cc_params[cc_par_cnt++] = "-Xclang"; cc_params[cc_par_cnt++] = "-load"; cc_params[cc_par_cnt++] = "-Xclang"; cc_params[cc_par_cnt++] = alloc_printf("%s/compare-transform-pass.so", obj_path); } - if (getenv("LAF_SPLIT_COMPARES")) { + if (getenv("LAF_SPLIT_COMPARES")||getenv("AFL_LLVM_LAF_SPLIT_COMPARES")) { cc_params[cc_par_cnt++] = "-Xclang"; cc_params[cc_par_cnt++] = "-load"; cc_params[cc_par_cnt++] = "-Xclang"; diff --git a/llvm_mode/split-compares-pass.so.cc b/llvm_mode/split-compares-pass.so.cc index 25ccb3b4..2ea73aaa 100644 --- a/llvm_mode/split-compares-pass.so.cc +++ b/llvm_mode/split-compares-pass.so.cc @@ -477,6 +477,8 @@ bool SplitComparesTransform::runOnModule(Module &M) { int bitw = 64; char* bitw_env = getenv("LAF_SPLIT_COMPARES_BITW"); + if (!bitw_env) + bitw_env = getenv("AFL_LLVM_LAF_SPLIT_COMPARES_BITW"); if (bitw_env) { bitw = atoi(bitw_env); } diff --git a/python_mutators/README b/python_mutators/README index 174c8a64..21a16e52 100644 --- a/python_mutators/README +++ b/python_mutators/README @@ -9,3 +9,7 @@ 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/python_mutators/XmlMutatorMin.py b/python_mutators/XmlMutatorMin.py new file mode 100644 index 00000000..058b7e61 --- /dev/null +++ b/python_mutators/XmlMutatorMin.py @@ -0,0 +1,331 @@ +#!/usr/bin/python + +""" Mutation of XML documents, should be called from one of its wrappers (CLI, AFL, ...) """ + +from __future__ import print_function +from copy import deepcopy +from lxml import etree as ET +import random, re, io + +########################### +# The XmlMutatorMin class # +########################### + +class XmlMutatorMin: + + """ + Optionals parameters: + seed Seed used by the PRNG (default: "RANDOM") + verbose Verbosity (default: False) + """ + + def __init__(self, seed="RANDOM", verbose=False): + + """ Initialize seed, database and mutators """ + + # Verbosity + self.verbose = verbose + + # Initialize PRNG + self.seed = str(seed) + if self.seed == "RANDOM": + random.seed() + else: + if self.verbose: + print("Static seed '%s'" % self.seed) + random.seed(self.seed) + + # Initialize input and output documents + self.input_tree = None + self.tree = None + + # High-level mutators (no database needed) + hl_mutators_delete = [ "del_node_and_children", "del_node_but_children", "del_attribute", "del_content" ] # Delete items + hl_mutators_fuzz = ["fuzz_attribute"] # Randomly change attribute values + + # Exposed mutators + self.hl_mutators_all = hl_mutators_fuzz + hl_mutators_delete + + def __parse_xml (self, xml): + + """ Parse an XML string. Basic wrapper around lxml.parse() """ + + try: + # Function parse() takes care of comments / DTD / processing instructions / ... + tree = ET.parse(io.BytesIO(xml)) + except ET.ParseError: + raise RuntimeError("XML isn't well-formed!") + except LookupError as e: + raise RuntimeError(e) + + # Return a document wrapper + return tree + + def __exec_among (self, module, functions, min_times, max_times): + + """ Randomly execute $functions between $min and $max times """ + + for i in xrange (random.randint (min_times, max_times)): + # Function names are mangled because they are "private" + getattr (module, "_XmlMutatorMin__" + random.choice(functions)) () + + def __serialize_xml (self, tree): + + """ Serialize a XML document. Basic wrapper around lxml.tostring() """ + + return ET.tostring(tree, with_tail=False, xml_declaration=True, encoding=tree.docinfo.encoding) + + def __ver (self, version): + + """ Helper for displaying lxml version numbers """ + + return ".".join(map(str, version)) + + def reset (self): + + """ Reset the mutator """ + + self.tree = deepcopy(self.input_tree) + + def init_from_string (self, input_string): + + """ Initialize the mutator from a XML string """ + + # Get a pointer to the top-element + self.input_tree = self.__parse_xml(input_string) + + # Get a working copy + self.tree = deepcopy(self.input_tree) + + def save_to_string (self): + + """ Return the current XML document as UTF-8 string """ + + # Return a text version of the tree + return self.__serialize_xml(self.tree) + + def __pick_element (self, exclude_root_node = False): + + """ Pick a random element from the current document """ + + # Get a list of all elements, but nodes like PI and comments + elems = list(self.tree.getroot().iter(tag=ET.Element)) + + # Is the root node excluded? + if exclude_root_node: + start = 1 + else: + start = 0 + + # Pick a random element + try: + elem_id = random.randint (start, len(elems) - 1) + elem = elems[elem_id] + except ValueError: + # Should only occurs if "exclude_root_node = True" + return (None, None) + + return (elem_id, elem) + + def __fuzz_attribute (self): + + """ Fuzz (part of) an attribute value """ + + # Select a node to modify + (rand_elem_id, rand_elem) = self.__pick_element() + + # Get all the attributes + attribs = rand_elem.keys() + + # Is there attributes? + if len(attribs) < 1: + if self.verbose: + print("No attribute: can't replace!") + return + + # Pick a random attribute + rand_attrib_id = random.randint (0, len(attribs) - 1) + rand_attrib = attribs[rand_attrib_id] + + # We have the attribute to modify + # Get its value + attrib_value = rand_elem.get(rand_attrib); + # print("- Value: " + attrib_value) + + # Should we work on the whole value? + func_call = "(?P<func>[a-zA-Z:\-]+)\((?P<args>.*?)\)" + p = re.compile(func_call) + l = p.findall(attrib_value) + if random.choice((True,False)) and l: + # Randomly pick one the function calls + (func, args) = random.choice(l) + # Split by "," and randomly pick one of the arguments + value = random.choice(args.split(',')) + # Remove superfluous characters + unclean_value = value + value = value.strip(" ").strip("'") + # print("Selected argument: [%s]" % value) + else: + value = attrib_value + + # For each type, define some possible replacement values + choices_number = ( \ + "0", \ + "11111", \ + "-128", \ + "2", \ + "-1", \ + "1/3", \ + "42/0", \ + "1094861636 idiv 1.0", \ + "-1123329771506872 idiv 3.8", \ + "17=$numericRTF", \ + str(3 + random.randrange(0, 100)), \ + ) + + choices_letter = ( \ + "P" * (25 * random.randrange(1, 100)), \ + "%s%s%s%s%s%s", \ + "foobar", \ + ) + + choices_alnum = ( \ + "Abc123", \ + "020F0302020204030204", \ + "020F0302020204030204" * (random.randrange(5, 20)), \ + ) + + # Fuzz the value + if random.choice((True,False)) and value == "": + + # Empty + new_value = value + + elif random.choice((True,False)) and value.isdigit(): + + # Numbers + new_value = random.choice(choices_number) + + elif random.choice((True,False)) and value.isalpha(): + + # Letters + new_value = random.choice(choices_letter) + + elif random.choice((True,False)) and value.isalnum(): + + # Alphanumeric + new_value = random.choice(choices_alnum) + + else: + + # Default type + new_value = random.choice(choices_alnum + choices_letter + choices_number) + + # If we worked on a substring, apply changes to the whole string + if value != attrib_value: + # No ' around empty values + if new_value != "" and value != "": + new_value = "'" + new_value + "'" + # Apply changes + new_value = attrib_value.replace(unclean_value, new_value) + + # Log something + if self.verbose: + print("Fuzzing attribute #%i '%s' of tag #%i '%s'" % (rand_attrib_id, rand_attrib, rand_elem_id, rand_elem.tag)) + + # Modify the attribute + rand_elem.set(rand_attrib, new_value.decode("utf-8")) + + def __del_node_and_children (self): + + """ High-level minimizing mutator + Delete a random node and its children (i.e. delete a random tree) """ + + self.__del_node(True) + + def __del_node_but_children (self): + + """ High-level minimizing mutator + Delete a random node but its children (i.e. link them to the parent of the deleted node) """ + + self.__del_node(False) + + def __del_node (self, delete_children): + + """ Called by the __del_node_* mutators """ + + # Select a node to modify (but the root one) + (rand_elem_id, rand_elem) = self.__pick_element (exclude_root_node = True) + + # If the document includes only a top-level element + # Then we can't pick a element (given that "exclude_root_node = True") + + # Is the document deep enough? + if rand_elem is None: + if self.verbose: + print("Can't delete a node: document not deep enough!") + return + + # Log something + if self.verbose: + but_or_and = "and" if delete_children else "but" + print("Deleting tag #%i '%s' %s its children" % (rand_elem_id, rand_elem.tag, but_or_and)) + + if delete_children is False: + # Link children of the random (soon to be deleted) node to its parent + for child in rand_elem: + rand_elem.getparent().append(child) + + # Remove the node + rand_elem.getparent().remove(rand_elem) + + def __del_content (self): + + """ High-level minimizing mutator + Delete the attributes and children of a random node """ + + # Select a node to modify + (rand_elem_id, rand_elem) = self.__pick_element() + + # Log something + if self.verbose: + print("Reseting tag #%i '%s'" % (rand_elem_id, rand_elem.tag)) + + # Reset the node + rand_elem.clear() + + def __del_attribute (self): + + """ High-level minimizing mutator + Delete a random attribute from a random node """ + + # Select a node to modify + (rand_elem_id, rand_elem) = self.__pick_element() + + # Get all the attributes + attribs = rand_elem.keys() + + # Is there attributes? + if len(attribs) < 1: + if self.verbose: + print("No attribute: can't delete!") + return + + # Pick a random attribute + rand_attrib_id = random.randint (0, len(attribs) - 1) + rand_attrib = attribs[rand_attrib_id] + + # Log something + if self.verbose: + print("Deleting attribute #%i '%s' of tag #%i '%s'" % (rand_attrib_id, rand_attrib, rand_elem_id, rand_elem.tag)) + + # Delete the attribute + rand_elem.attrib.pop(rand_attrib) + + def mutate (self, min=1, max=5): + + """ Execute some high-level mutators between $min and $max times, then some medium-level ones """ + + # High-level mutation + self.__exec_among(self, self.hl_mutators_all, min, max) + diff --git a/python_mutators/wrapper_afl_min.py b/python_mutators/wrapper_afl_min.py new file mode 100644 index 00000000..df09b40a --- /dev/null +++ b/python_mutators/wrapper_afl_min.py @@ -0,0 +1,117 @@ +#!/usr/bin/env python + +from XmlMutatorMin import XmlMutatorMin + +# Default settings (production mode) + +__mutator__ = None +__seed__ = "RANDOM" +__log__ = False +__log_file__ = "wrapper.log" + +# AFL functions + +def log(text): + """ + Logger + """ + + global __seed__ + global __log__ + global __log_file__ + + if __log__: + with open(__log_file__, "a") as logf: + logf.write("[%s] %s\n" % (__seed__, text)) + +def init(seed): + """ + Called once when AFL starts up. Seed is used to identify the AFL instance in log files + """ + + global __mutator__ + global __seed__ + + # Get the seed + __seed__ = seed + + # Create a global mutation class + try: + __mutator__ = XmlMutatorMin(__seed__, verbose=__log__) + log("init(): Mutator created") + except RuntimeError as e: + log("init(): Can't create mutator: %s" % e.message) + +def fuzz(buf, add_buf): + """ + Called for each fuzzing iteration. + """ + + global __mutator__ + + # Do we have a working mutator object? + if __mutator__ is None: + log("fuzz(): Can't fuzz, no mutator available") + return buf + + # Try to use the AFL buffer + via_buffer = True + + # Interpret the AFL buffer (an array of bytes) as a string + if via_buffer: + try: + buf_str = str(buf) + log("fuzz(): AFL buffer converted to a string") + except: + via_buffer = False + log("fuzz(): Can't convert AFL buffer to a string") + + # Load XML from the AFL string + if via_buffer: + try: + __mutator__.init_from_string(buf_str) + log("fuzz(): Mutator successfully initialized with AFL buffer (%d bytes)" % len(buf_str)) + except: + via_buffer = False + log("fuzz(): Can't initialize mutator with AFL buffer") + + # If init from AFL buffer wasn't succesful + if not via_buffer: + log("fuzz(): Returning unmodified AFL buffer") + return buf + + # Sucessful initialization -> mutate + try: + __mutator__.mutate(max=5) + log("fuzz(): Input mutated") + except: + log("fuzz(): Can't mutate input => returning buf") + return buf + + # Convert mutated data to a array of bytes + try: + data = bytearray(__mutator__.save_to_string()) + log("fuzz(): Mutated data converted as bytes") + except: + log("fuzz(): Can't convert mutated data to bytes => returning buf") + return buf + + # Everything went fine, returning mutated content + log("fuzz(): Returning %d bytes" % len(data)) + return data + +# Main (for debug) + +if __name__ == '__main__': + + __log__ = True + __log_file__ = "/dev/stdout" + __seed__ = "RANDOM" + + init(__seed__) + + in_1 = bytearray("<foo ddd='eeee'>ffff<a b='c' d='456' eee='ffffff'>zzzzzzzzzzzz</a><b yyy='YYY' zzz='ZZZ'></b></foo>") + in_2 = bytearray("<abc abc123='456' abcCBA='ppppppppppppppppppppppppppppp'/>") + out = fuzz(in_1, in_2) + print(out) + |