about summary refs log tree commit diff
diff options
context:
space:
mode:
authorvan Hauser <vh@thc.org>2023-04-22 11:40:50 +0200
committerGitHub <noreply@github.com>2023-04-22 11:40:50 +0200
commitdbb317162415a28e3fd2ff4c574292c924493a00 (patch)
tree190be094155fbdc8bf642f3b84942c4ad1808060
parenta326c23210dc2ace37bf1cadcc4521cf5d0b58cb (diff)
parent6bd48a48cbed1f923ff0999ea24af1f548c2e2bc (diff)
downloadafl++-dbb317162415a28e3fd2ff4c574292c924493a00.tar.gz
Merge pull request #1712 from AFLplusplus/dev
push to stable
-rw-r--r--.github/workflows/ci.yml2
-rw-r--r--GNUmakefile6
-rw-r--r--TODO.md4
-rwxr-xr-xafl-cmin2
-rwxr-xr-xafl-cmin.bash2
-rw-r--r--custom_mutators/README.md33
-rw-r--r--custom_mutators/atnwalk/README.md45
-rw-r--r--custom_mutators/atnwalk/atnwalk.c539
-rw-r--r--docs/Changelog.md9
-rw-r--r--docs/env_variables.md2
-rw-r--r--frida_mode/README.md4
-rw-r--r--frida_mode/Scripting.md6
-rw-r--r--frida_mode/frida.map1
-rw-r--r--frida_mode/include/ranges.h1
-rw-r--r--frida_mode/src/js/api.js7
-rw-r--r--frida_mode/src/js/js_api.c7
-rw-r--r--frida_mode/src/ranges.c39
-rw-r--r--frida_mode/ts/lib/afl.ts12
-rw-r--r--include/afl-fuzz.h11
-rw-r--r--include/config.h2
-rw-r--r--include/envs.h2
-rw-r--r--src/afl-fuzz-run.c26
-rw-r--r--src/afl-fuzz-state.c7
-rw-r--r--src/afl-fuzz.c4
-rw-r--r--src/afl-showmap.c127
25 files changed, 871 insertions, 29 deletions
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index ed1f3228..03e4151d 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -14,7 +14,7 @@ jobs:
     runs-on: "${{ matrix.os }}"
     strategy:
       matrix:
-        os: [ubuntu-22.04, ubuntu-20.04, ubuntu-18.04]
+        os: [ubuntu-22.04, ubuntu-20.04]
     env:
       AFL_SKIP_CPUFREQ: 1
       AFL_I_DONT_CARE_ABOUT_MISSING_CRASHES: 1
diff --git a/GNUmakefile b/GNUmakefile
index 5bc3f9d5..23cae65d 100644
--- a/GNUmakefile
+++ b/GNUmakefile
@@ -286,6 +286,8 @@ ifeq "$(shell command -v svn >/dev/null && svn proplist . 2>/dev/null && echo 1
   IN_REPO=1
 endif
 
+CCVER=$(shell cc -v 2>&1|tail -n 1)
+
 ifeq "$(shell echo 'int main() { return 0;}' | $(CC) $(CFLAGS) -fsanitize=address -x c - -o .test2 2>/dev/null && echo 1 || echo 0 ; rm -f .test2 )" "1"
 	ASAN_CFLAGS=-fsanitize=address -fstack-protector-all -fno-omit-frame-pointer -DASAN_BUILD
 	ASAN_LDFLAGS=-fsanitize=address -fstack-protector-all -fno-omit-frame-pointer
@@ -431,7 +433,7 @@ endif
 
 .PHONY: ready
 ready:
-	@echo "[+] Everything seems to be working, ready to compile."
+	@echo "[+] Everything seems to be working, ready to compile. ($(CCVER))"
 
 afl-as: src/afl-as.c include/afl-as.h $(COMM_HDR) | test_x86
 	$(CC) $(CFLAGS) src/$@.c -o $@ $(LDFLAGS)
@@ -453,7 +455,7 @@ afl-fuzz: $(COMM_HDR) include/afl-fuzz.h $(AFL_FUZZ_FILES) src/afl-common.o src/
 	$(CC) $(CFLAGS) $(COMPILE_STATIC) $(CFLAGS_FLTO) $(AFL_FUZZ_FILES) src/afl-common.o src/afl-sharedmem.o src/afl-forkserver.o src/afl-performance.o -o $@ $(PYFLAGS) $(LDFLAGS) -lm
 
 afl-showmap: src/afl-showmap.c src/afl-common.o src/afl-sharedmem.o src/afl-forkserver.o src/afl-performance.o $(COMM_HDR) | test_x86
-	$(CC) $(CFLAGS) $(COMPILE_STATIC) $(CFLAGS_FLTO) src/$@.c src/afl-common.o src/afl-sharedmem.o src/afl-forkserver.o src/afl-performance.o -o $@ $(LDFLAGS)
+	$(CC) $(CFLAGS) $(COMPILE_STATIC) $(CFLAGS_FLTO) src/$@.c src/afl-fuzz-mutators.c src/afl-fuzz-python.c src/afl-common.o src/afl-sharedmem.o src/afl-forkserver.o src/afl-performance.o -o $@ $(PYFLAGS) $(LDFLAGS)
 
 afl-tmin: src/afl-tmin.c src/afl-common.o src/afl-sharedmem.o src/afl-forkserver.o src/afl-performance.o $(COMM_HDR) | test_x86
 	$(CC) $(CFLAGS) $(COMPILE_STATIC) $(CFLAGS_FLTO) src/$@.c src/afl-common.o src/afl-sharedmem.o src/afl-forkserver.o src/afl-performance.o -o $@ $(LDFLAGS)
diff --git a/TODO.md b/TODO.md
index e7789cf6..dba75070 100644
--- a/TODO.md
+++ b/TODO.md
@@ -3,14 +3,14 @@
 ## Should
 
  - splicing selection weighted?
- - support afl_custom_{send,post_process}, persistent and deferred fork
-   server in afl-showmap
+ - support persistent and deferred fork server in afl-showmap?
  - better autodetection of shifting runtime timeout values
  - Update afl->pending_not_fuzzed for MOpt
  - afl-plot to support multiple plot_data
  - parallel builds for source-only targets
  - get rid of check_binary, replace with more forkserver communication
  - first fuzzer should be a main automatically? not sure.
+ - reload fuzz binary on signal
 
 ## Maybe
 
diff --git a/afl-cmin b/afl-cmin
index c5e64410..e2c26d91 100755
--- a/afl-cmin
+++ b/afl-cmin
@@ -133,6 +133,8 @@ function usage() {
 "AFL_PRINT_FILENAMES: If set, the filename currently processed will be " \
       "printed to stdout\n" \
 "AFL_SKIP_BIN_CHECK: skip afl instrumentation checks for target binary\n"
+"AFL_CUSTOM_MUTATOR_LIBRARY: custom mutator library (post_process and send)\n"
+"AFL_PYTHON_MODULE: custom mutator library (post_process and send)\n"
    exit 1
 }
 
diff --git a/afl-cmin.bash b/afl-cmin.bash
index bcf62eba..5258758e 100755
--- a/afl-cmin.bash
+++ b/afl-cmin.bash
@@ -151,6 +151,8 @@ AFL_KEEP_TRACES: leave the temporary <out_dir>\.traces directory
 AFL_NO_FORKSRV: run target via execve instead of using the forkserver
 AFL_PATH: last resort location to find the afl-showmap binary
 AFL_SKIP_BIN_CHECK: skip check for target binary
+AFL_CUSTOM_MUTATOR_LIBRARY: custom mutator library (post_process and send)
+AFL_PYTHON_MODULE: custom mutator library (post_process and send)
 _EOF_
   exit 1
 fi
diff --git a/custom_mutators/README.md b/custom_mutators/README.md
index 8d01856f..a5a572c0 100644
--- a/custom_mutators/README.md
+++ b/custom_mutators/README.md
@@ -11,7 +11,20 @@ The `./examples` folder contains examples for custom mutators in python and C.
 
 In `./rust`, you will find rust bindings, including a simple example in `./rust/example` and an example for structured fuzzing, based on lain, in`./rust/example_lain`.
 
-## The AFL++ grammar agnostic grammar mutator
+## Production-Ready Custom Mutators
+
+This directory holds ready to use custom mutators.
+Just type "make" in the individual subdirectories.
+
+Use with e.g.
+
+`AFL_CUSTOM_MUTATOR_LIBRARY=custom_mutators/radamsa/radamsa-mutator.so afl-fuzz ....`
+
+and add `AFL_CUSTOM_MUTATOR_ONLY=1` if you only want to use the custom mutator.
+
+Multiple custom mutators can be used by separating their paths with `:` in the environment variable.
+
+### The AFL++ grammar agnostic grammar mutator
 
 In `./autotokens` you find a token-level fuzzer that does not need to know
 anything about the grammar of an input as long as it is in ascii and allows
@@ -21,7 +34,7 @@ It is very fast and effective.
 If you are looking for an example of how to effectively create a custom
 mutator take a look at this one.
 
-## The AFL++ Grammar Mutator
+### The AFL++ Grammar Mutator
 
 If you use git to clone AFL++, then the following will incorporate our
 excellent grammar custom mutator:
@@ -34,18 +47,18 @@ Read the README in the [Grammar-Mutator] repository on how to use it.
 
 [Grammar-Mutator]: https://github.com/AFLplusplus/Grammar-Mutator
 
-## Production-Ready Custom Mutators
-
-This directory holds ready to use custom mutators.
-Just type "make" in the individual subdirectories.
+Note that this custom mutator is not very good though!
 
-Use with e.g.
+### Other Mutators
 
-`AFL_CUSTOM_MUTATOR_LIBRARY=custom_mutators/radamsa/radamsa-mutator.so afl-fuzz ....`
+atnwalk and gramatron are grammar custom mutators. Example grammars are
+provided.
 
-and add `AFL_CUSTOM_MUTATOR_ONLY=1` if you only want to use the custom mutator.
+honggfuzz, libfuzzer and  libafl are partial implementations based on the
+mutator implementations of the respective fuzzers. 
+More for playing than serious usage.
 
-Multiple custom mutators can be used by separating their paths with `:` in the environment variable.
+radamsa is slow and not very good.
 
 ## 3rd Party Custom Mutators
 
diff --git a/custom_mutators/atnwalk/README.md b/custom_mutators/atnwalk/README.md
new file mode 100644
index 00000000..badb856f
--- /dev/null
+++ b/custom_mutators/atnwalk/README.md
@@ -0,0 +1,45 @@
+# ATNwalk: Grammar-Based Fuzzing using Only Bit-Mutations
+
+This is a custom mutator integration of ATNwalk that works by communicating via UNIX domain sockets.
+
+Refer to [https://github.com/atnwalk/testbed](https://github.com/atnwalk/testbed) for detailed instructions on how to get ATNwalk running.
+
+## Build
+
+```bash
+gcc -I ../../include/ -shared -fPIC -Wall -O3 atnwalk.c -o atnwalk.so
+```
+
+## Run
+
+**NOTE:** The commands below just demonstrate an example how running ATNwalk looks like and require a working [testbed](https://github.com/atnwalk/testbed)
+
+```bash
+# create the required a random seed first
+mkdir -p ~/campaign/example/seeds
+cd ~/campaign/example/seeds
+head -c1 /dev/urandom | ~/atnwalk/build/javascript/bin/decode -wb > seed.decoded 2> seed.encoded
+
+# create the required atnwalk directory and copy the seed
+cd ../
+mkdir -p atnwalk/in
+cp ./seeds/seed.encoded atnwalk/in/seed
+cd atnwalk
+
+# assign to a single core when benchmarking it, change the CPU number as required
+CPU_ID=0
+
+# start the ATNwalk server
+nohup taskset -c ${CPU_ID} ${HOME}/atnwalk/build/javascript/bin/server 100 > server.log 2>&1 &
+
+# start AFL++ with ATNwalk
+AFL_SKIP_CPUFREQ=1 \
+  AFL_DISABLE_TRIM=1 \
+  AFL_CUSTOM_MUTATOR_ONLY=1 \
+  AFL_CUSTOM_MUTATOR_LIBRARY=${HOME}/AFLplusplus/custom_mutators/atnwalk/atnwalk.so \
+  AFL_POST_PROCESS_KEEP_ORIGINAL=1 \
+  ~/AFLplusplus/afl-fuzz -t 100 -i in/ -o out -b ${CPU_ID} -- ~/jerryscript/build/bin/jerry
+
+# make sure to kill the ATNwalk server process after you're done
+kill "$(cat atnwalk.pid)"
+```
diff --git a/custom_mutators/atnwalk/atnwalk.c b/custom_mutators/atnwalk/atnwalk.c
new file mode 100644
index 00000000..c3a2cd95
--- /dev/null
+++ b/custom_mutators/atnwalk/atnwalk.c
@@ -0,0 +1,539 @@
+#include "afl-fuzz.h"
+
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <unistd.h>
+
+#define BUF_SIZE_INIT 4096
+#define SOCKET_NAME "./atnwalk.socket"
+
+// how many errors (e.g. timeouts) to tolerate until moving on to the next queue
+// entry
+#define ATNWALK_ERRORS_MAX 1
+
+// how many execution timeouts to tolerate until moving on to the next queue
+// entry
+#define EXEC_TIMEOUT_MAX 2
+
+// handshake constants
+const uint8_t SERVER_ARE_YOU_ALIVE = 213;
+const uint8_t SERVER_YES_I_AM_ALIVE = 42;
+
+// control bits
+const uint8_t SERVER_CROSSOVER_BIT = 0b00000001;
+const uint8_t SERVER_MUTATE_BIT = 0b00000010;
+const uint8_t SERVER_DECODE_BIT = 0b00000100;
+const uint8_t SERVER_ENCODE_BIT = 0b00001000;
+
+typedef struct atnwalk_mutator {
+
+  afl_state_t *afl;
+  uint8_t      atnwalk_error_count;
+  uint64_t     prev_timeouts;
+  uint32_t     prev_hits;
+  uint32_t     stage_havoc_cur;
+  uint32_t     stage_havoc_max;
+  uint32_t     stage_splice_cur;
+  uint32_t     stage_splice_max;
+  uint8_t     *fuzz_buf;
+  size_t       fuzz_size;
+  uint8_t     *post_process_buf;
+  size_t       post_process_size;
+
+} atnwalk_mutator_t;
+
+int read_all(int fd, uint8_t *buf, size_t buf_size) {
+
+  int    n;
+  size_t offset = 0;
+  while (offset < buf_size) {
+
+    n = read(fd, buf + offset, buf_size - offset);
+    if (n == -1) { return 0; }
+    offset += n;
+
+  }
+
+  return 1;
+
+}
+
+int write_all(int fd, uint8_t *buf, size_t buf_size) {
+
+  int    n;
+  size_t offset = 0;
+  while (offset < buf_size) {
+
+    n = write(fd, buf + offset, buf_size - offset);
+    if (n == -1) { return 0; }
+    offset += n;
+
+  }
+
+  return 1;
+
+}
+
+void put_uint32(uint8_t *buf, uint32_t val) {
+
+  buf[0] = (uint8_t)(val >> 24);
+  buf[1] = (uint8_t)((val & 0x00ff0000) >> 16);
+  buf[2] = (uint8_t)((val & 0x0000ff00) >> 8);
+  buf[3] = (uint8_t)(val & 0x000000ff);
+
+}
+
+uint32_t to_uint32(uint8_t *buf) {
+
+  uint32_t val = 0;
+  val |= (((uint32_t)buf[0]) << 24);
+  val |= (((uint32_t)buf[1]) << 16);
+  val |= (((uint32_t)buf[2]) << 8);
+  val |= ((uint32_t)buf[3]);
+  return val;
+
+}
+
+void put_uint64(uint8_t *buf, uint64_t val) {
+
+  buf[0] = (uint8_t)(val >> 56);
+  buf[1] = (uint8_t)((val & 0x00ff000000000000) >> 48);
+  buf[2] = (uint8_t)((val & 0x0000ff0000000000) >> 40);
+  buf[3] = (uint8_t)((val & 0x000000ff00000000) >> 32);
+  buf[4] = (uint8_t)((val & 0x00000000ff000000) >> 24);
+  buf[5] = (uint8_t)((val & 0x0000000000ff0000) >> 16);
+  buf[6] = (uint8_t)((val & 0x000000000000ff00) >> 8);
+  buf[7] = (uint8_t)(val & 0x00000000000000ff);
+
+}
+
+/**
+ * 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.
+ */
+atnwalk_mutator_t *afl_custom_init(afl_state_t *afl, unsigned int seed) {
+
+  srand(seed);
+  atnwalk_mutator_t *data =
+      (atnwalk_mutator_t *)malloc(sizeof(atnwalk_mutator_t));
+  if (!data) {
+
+    perror("afl_custom_init alloc");
+    return NULL;
+
+  }
+
+  data->afl = afl;
+  data->prev_hits = 0;
+  data->fuzz_buf = (uint8_t *)malloc(BUF_SIZE_INIT);
+  data->fuzz_size = BUF_SIZE_INIT;
+  data->post_process_buf = (uint8_t *)malloc(BUF_SIZE_INIT);
+  data->post_process_size = BUF_SIZE_INIT;
+  return data;
+
+}
+
+unsigned int afl_custom_fuzz_count(atnwalk_mutator_t   *data,
+                                   const unsigned char *buf, size_t buf_size) {
+
+  // afl_custom_fuzz_count is called exactly once before entering the
+  // 'stage-loop' for the current queue entry thus, we use it to reset the error
+  // count and to initialize stage variables (somewhat not intended by the API,
+  // but still better than rewriting the whole thing to have a custom mutator
+  // stage)
+  data->atnwalk_error_count = 0;
+  data->prev_timeouts = data->afl->total_tmouts;
+
+  // it might happen that on the last execution of the splice stage a new path
+  // is found we need to fix that here and count it
+  if (data->prev_hits) {
+
+    data->afl->stage_finds[STAGE_SPLICE] +=
+        data->afl->queued_items + data->afl->saved_crashes - data->prev_hits;
+
+  }
+
+  data->prev_hits = data->afl->queued_items + data->afl->saved_crashes;
+  data->stage_havoc_cur = 0;
+  data->stage_splice_cur = 0;
+
+  // 50% havoc, 50% splice
+  data->stage_havoc_max = data->afl->stage_max >> 1;
+  if (data->stage_havoc_max < HAVOC_MIN) { data->stage_havoc_max = HAVOC_MIN; }
+  data->stage_splice_max = data->stage_havoc_max;
+  return data->stage_havoc_max + data->stage_splice_max;
+
+}
+
+size_t fail_fatal(int fd_socket, uint8_t **out_buf) {
+
+  if (fd_socket != -1) { close(fd_socket); }
+  *out_buf = NULL;
+  return 0;
+
+}
+
+size_t fail_gracefully(int fd_socket, atnwalk_mutator_t *data, uint8_t *buf,
+                       size_t buf_size, uint8_t **out_buf) {
+
+  if (fd_socket != -1) { close(fd_socket); }
+  data->atnwalk_error_count++;
+  if (data->atnwalk_error_count > ATNWALK_ERRORS_MAX) {
+
+    data->afl->stage_max = data->afl->stage_cur;
+
+  }
+
+  *out_buf = buf;
+  return buf_size;
+
+}
+
+/**
+ * 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(atnwalk_mutator_t *data, uint8_t *buf, size_t buf_size,
+                       uint8_t **out_buf, uint8_t *add_buf, size_t add_buf_size,
+                       size_t max_size) {
+
+  struct sockaddr_un addr;
+  int                fd_socket;
+  uint8_t            ctrl_buf[8];
+  uint8_t            wanted;
+
+  // let's display what's going on in a nice way
+  if (data->stage_havoc_cur == 0) {
+
+    data->afl->stage_name = (uint8_t *)"atnwalk - havoc";
+
+  }
+
+  if (data->stage_havoc_cur == data->stage_havoc_max) {
+
+    data->afl->stage_name = (uint8_t *)"atnwalk - splice";
+
+  }
+
+  // increase the respective havoc or splice counters
+  if (data->stage_havoc_cur < data->stage_havoc_max) {
+
+    data->stage_havoc_cur++;
+    data->afl->stage_cycles[STAGE_HAVOC]++;
+
+  } else {
+
+    // if there is nothing to splice, continue with havoc and skip splicing this
+    // time
+    if (data->afl->ready_for_splicing_count < 1) {
+
+      data->stage_havoc_max = data->afl->stage_max;
+      data->stage_havoc_cur++;
+      data->afl->stage_cycles[STAGE_HAVOC]++;
+
+    } else {
+
+      data->stage_splice_cur++;
+      data->afl->stage_cycles[STAGE_SPLICE]++;
+
+    }
+
+  }
+
+  // keep track of found new corpus seeds per stage
+  if (data->afl->queued_items + data->afl->saved_crashes > data->prev_hits) {
+
+    if (data->stage_splice_cur <= 1) {
+
+      data->afl->stage_finds[STAGE_HAVOC] +=
+          data->afl->queued_items + data->afl->saved_crashes - data->prev_hits;
+
+    } else {
+
+      data->afl->stage_finds[STAGE_SPLICE] +=
+          data->afl->queued_items + data->afl->saved_crashes - data->prev_hits;
+
+    }
+
+  }
+
+  data->prev_hits = data->afl->queued_items + data->afl->saved_crashes;
+
+  // check whether this input produces a lot of timeouts, if it does then
+  // abandon this queue entry
+  if (data->afl->total_tmouts - data->prev_timeouts >= EXEC_TIMEOUT_MAX) {
+
+    data->afl->stage_max = data->afl->stage_cur;
+    return fail_gracefully(-1, data, buf, buf_size, out_buf);
+
+  }
+
+  // initialize the socket
+  fd_socket = socket(AF_UNIX, SOCK_STREAM, 0);
+  if (fd_socket == -1) { return fail_fatal(fd_socket, out_buf); }
+  memset(&addr, 0, sizeof(addr));
+  addr.sun_family = AF_UNIX;
+  strncpy(addr.sun_path, SOCKET_NAME, sizeof(addr.sun_path) - 1);
+  if (connect(fd_socket, (const struct sockaddr *)&addr, sizeof(addr)) == -1) {
+
+    return fail_fatal(fd_socket, out_buf);
+
+  }
+
+  // ask whether the server is alive
+  ctrl_buf[0] = SERVER_ARE_YOU_ALIVE;
+  if (!write_all(fd_socket, ctrl_buf, 1)) {
+
+    return fail_fatal(fd_socket, out_buf);
+
+  }
+
+  // see whether the server replies as expected
+  if (!read_all(fd_socket, ctrl_buf, 1) ||
+      ctrl_buf[0] != SERVER_YES_I_AM_ALIVE) {
+
+    return fail_fatal(fd_socket, out_buf);
+
+  }
+
+  // tell the server what we want to do
+  wanted = SERVER_MUTATE_BIT | SERVER_ENCODE_BIT;
+
+  // perform a crossover if we are splicing
+  if (data->stage_splice_cur > 0) { wanted |= SERVER_CROSSOVER_BIT; }
+
+  // tell the server what we want and how much data will be sent
+  ctrl_buf[0] = wanted;
+  put_uint32(ctrl_buf + 1, (uint32_t)buf_size);
+  if (!write_all(fd_socket, ctrl_buf, 5)) {
+
+    return fail_fatal(fd_socket, out_buf);
+
+  }
+
+  // send the data to mutate and encode
+  if (!write_all(fd_socket, buf, buf_size)) {
+
+    return fail_gracefully(fd_socket, data, buf, buf_size, out_buf);
+
+  }
+
+  if (wanted & SERVER_CROSSOVER_BIT) {
+
+    // since we requested crossover, we will first tell how much additional data
+    // is to be expected
+    put_uint32(ctrl_buf, (uint32_t)add_buf_size);
+    if (!write_all(fd_socket, ctrl_buf, 4)) {
+
+      return fail_gracefully(fd_socket, data, buf, buf_size, out_buf);
+
+    }
+
+    // send the additional data for crossover
+    if (!write_all(fd_socket, add_buf, add_buf_size)) {
+
+      return fail_gracefully(fd_socket, data, buf, buf_size, out_buf);
+
+    }
+
+    // lastly, a seed is required for crossover so send one
+    put_uint64(ctrl_buf, (uint64_t)rand());
+    if (!write_all(fd_socket, ctrl_buf, 8)) {
+
+      return fail_gracefully(fd_socket, data, buf, buf_size, out_buf);
+
+    }
+
+  }
+
+  // since we requested mutation, we need to provide a seed for that
+  put_uint64(ctrl_buf, (uint64_t)rand());
+  if (!write_all(fd_socket, ctrl_buf, 8)) {
+
+    return fail_gracefully(fd_socket, data, buf, buf_size, out_buf);
+
+  }
+
+  // obtain the required buffer size for the data that will be returned
+  if (!read_all(fd_socket, ctrl_buf, 4)) {
+
+    return fail_gracefully(fd_socket, data, buf, buf_size, out_buf);
+
+  }
+
+  size_t new_size = (size_t)to_uint32(ctrl_buf);
+
+  // if the data is too large then we ignore this round
+  if (new_size > max_size) {
+
+    return fail_gracefully(fd_socket, data, buf, buf_size, out_buf);
+
+  }
+
+  if (new_size > buf_size) {
+
+    // buf is too small, need to use data->fuzz_buf, let's see whether we need
+    // to reallocate
+    if (new_size > data->fuzz_size) {
+
+      data->fuzz_size = new_size << 1;
+      data->fuzz_buf = (uint8_t *)realloc(data->fuzz_buf, data->fuzz_size);
+
+    }
+
+    *out_buf = data->fuzz_buf;
+
+  } else {
+
+    // new_size fits into buf, so re-use it
+    *out_buf = buf;
+
+  }
+
+  // obtain the encoded data
+  if (!read_all(fd_socket, *out_buf, new_size)) {
+
+    return fail_gracefully(fd_socket, data, buf, buf_size, out_buf);
+
+  }
+
+  close(fd_socket);
+  return new_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(atnwalk_mutator_t *data, uint8_t *buf,
+                               size_t buf_size, uint8_t **out_buf) {
+
+  struct sockaddr_un addr;
+  int                fd_socket;
+  uint8_t            ctrl_buf[8];
+
+  // initialize the socket
+  fd_socket = socket(AF_UNIX, SOCK_STREAM, 0);
+  if (fd_socket == -1) { return fail_fatal(fd_socket, out_buf); }
+  memset(&addr, 0, sizeof(addr));
+  addr.sun_family = AF_UNIX;
+  strncpy(addr.sun_path, SOCKET_NAME, sizeof(addr.sun_path) - 1);
+  if (connect(fd_socket, (const struct sockaddr *)&addr, sizeof(addr)) == -1) {
+
+    return fail_fatal(fd_socket, out_buf);
+
+  }
+
+  // ask whether the server is alive
+  ctrl_buf[0] = SERVER_ARE_YOU_ALIVE;
+  if (!write_all(fd_socket, ctrl_buf, 1)) {
+
+    return fail_fatal(fd_socket, out_buf);
+
+  }
+
+  // see whether the server replies as expected
+  if (!read_all(fd_socket, ctrl_buf, 1) ||
+      ctrl_buf[0] != SERVER_YES_I_AM_ALIVE) {
+
+    return fail_fatal(fd_socket, out_buf);
+
+  }
+
+  // tell the server what we want and how much data will be sent
+  ctrl_buf[0] = SERVER_DECODE_BIT;
+  put_uint32(ctrl_buf + 1, (uint32_t)buf_size);
+  if (!write_all(fd_socket, ctrl_buf, 5)) {
+
+    return fail_gracefully(fd_socket, data, buf, buf_size, out_buf);
+
+  }
+
+  // send the data to decode
+  if (!write_all(fd_socket, buf, buf_size)) {
+
+    return fail_gracefully(fd_socket, data, buf, buf_size, out_buf);
+
+  }
+
+  // obtain the required buffer size for the data that will be returned
+  if (!read_all(fd_socket, ctrl_buf, 4)) {
+
+    return fail_gracefully(fd_socket, data, buf, buf_size, out_buf);
+
+  }
+
+  size_t new_size = (size_t)to_uint32(ctrl_buf);
+
+  // need to use data->post_process_buf, let's see whether we need to reallocate
+  if (new_size > data->post_process_size) {
+
+    data->post_process_size = new_size << 1;
+    data->post_process_buf =
+        (uint8_t *)realloc(data->post_process_buf, data->post_process_size);
+
+  }
+
+  *out_buf = data->post_process_buf;
+
+  // obtain the decoded data
+  if (!read_all(fd_socket, *out_buf, new_size)) {
+
+    return fail_gracefully(fd_socket, data, buf, buf_size, out_buf);
+
+  }
+
+  close(fd_socket);
+  return new_size;
+
+}
+
+/**
+ * Deinitialize everything
+ *
+ * @param data The data ptr from afl_custom_init
+ */
+void afl_custom_deinit(atnwalk_mutator_t *data) {
+
+  free(data->fuzz_buf);
+  free(data->post_process_buf);
+  free(data);
+
+}
+
diff --git a/docs/Changelog.md b/docs/Changelog.md
index 587fb64c..f33acff9 100644
--- a/docs/Changelog.md
+++ b/docs/Changelog.md
@@ -3,6 +3,15 @@
   This is the list of all noteworthy changes made in every public
   release of the tool. See README.md for the general instruction manual.
 
+### Version ++4.07a (dev)
+  - afl-fuzz:
+    - new env `AFL_POST_PROCESS_KEEP_ORIGINAL` to keep the orignal
+      data before post process on finds
+  - afl-showmap:
+    - added custom mutator post_process and send support
+  - a new grammar custom mutator atnwalk was submitted by @voidptr127 !
+
+
 ### Version ++4.06c (release)
   - afl-fuzz:
     - ensure temporary file descriptor is closed when not used
diff --git a/docs/env_variables.md b/docs/env_variables.md
index a6a0ae44..c5995d13 100644
--- a/docs/env_variables.md
+++ b/docs/env_variables.md
@@ -677,6 +677,8 @@ support.
 * `AFL_FRIDA_INST_JIT` - Enable the instrumentation of Just-In-Time compiled
   code. Code is considered to be JIT if the executable segment is not backed by
   a file.
+* `AFL_FRIDA_INST_NO_DYNAMIC_LOAD` - Don't instrument the code loaded late at
+  runtime. Strictly limits instrumentation to what has been included.
 * `AFL_FRIDA_INST_NO_OPTIMIZE` - Don't use optimized inline assembly coverage
   instrumentation (the default where available). Required to use
   `AFL_FRIDA_INST_TRACE`.
diff --git a/frida_mode/README.md b/frida_mode/README.md
index aac13153..49a1fe38 100644
--- a/frida_mode/README.md
+++ b/frida_mode/README.md
@@ -178,11 +178,13 @@ Default is 256Mb.
 * `AFL_FRIDA_INST_JIT` - Enable the instrumentation of Just-In-Time compiled
   code. Code is considered to be JIT if the executable segment is not backed by
   a file.
+* `AFL_FRIDA_INST_NO_DYNAMIC_LOAD` - Don't instrument the code loaded late at
+  runtime. Strictly limits instrumentation to what has been included.
 * `AFL_FRIDA_INST_NO_OPTIMIZE` - Don't use optimized inline assembly coverage
   instrumentation (the default where available). Required to use
+  `AFL_FRIDA_INST_TRACE`.
 * `AFL_FRIDA_INST_REGS_FILE` - File to write raw register contents at the start
   of each block.
-  `AFL_FRIDA_INST_TRACE`.
 * `AFL_FRIDA_INST_NO_CACHE` - Don't use a look-up table to cache real to
 instrumented address block translations.
 * `AFL_FRIDA_INST_NO_PREFETCH` - Disable prefetching. By default, the child will
diff --git a/frida_mode/Scripting.md b/frida_mode/Scripting.md
index 023e4a19..dfd09e7b 100644
--- a/frida_mode/Scripting.md
+++ b/frida_mode/Scripting.md
@@ -845,6 +845,12 @@ class Afl {
       Afl.jsApiSetInstrumentLibraries();
   }
   /**
+   * See `AFL_FRIDA_INST_NO_DYNAMIC_LOAD`
+   */
+  static setInstrumentNoDynamicLoad() {
+    Afl.jsApiSetInstrumentNoDynamicLoad();
+  }
+  /**
    * See `AFL_FRIDA_INST_NO_OPTIMIZE`
    */
   static setInstrumentNoOptimize() {
diff --git a/frida_mode/frida.map b/frida_mode/frida.map
index baf067ab..a98c2096 100644
--- a/frida_mode/frida.map
+++ b/frida_mode/frida.map
@@ -19,6 +19,7 @@
     js_api_set_instrument_jit;
     js_api_set_instrument_libraries;
     js_api_set_instrument_instructions;
+    js_api_set_instrument_no_dynamic_load;
     js_api_set_instrument_no_optimize;
     js_api_set_instrument_regs_file;
     js_api_set_instrument_seed;
diff --git a/frida_mode/include/ranges.h b/frida_mode/include/ranges.h
index 3bd9eaa6..ca28acd9 100644
--- a/frida_mode/include/ranges.h
+++ b/frida_mode/include/ranges.h
@@ -6,6 +6,7 @@
 extern gboolean ranges_debug_maps;
 extern gboolean ranges_inst_libs;
 extern gboolean ranges_inst_jit;
+extern gboolean ranges_inst_dynamic_load;
 
 void ranges_config(void);
 void ranges_init(void);
diff --git a/frida_mode/src/js/api.js b/frida_mode/src/js/api.js
index f9ea1ffb..a65d32df 100644
--- a/frida_mode/src/js/api.js
+++ b/frida_mode/src/js/api.js
@@ -151,6 +151,12 @@ class Afl {
         Afl.jsApiSetInstrumentLibraries();
     }
     /**
+     * See `AFL_FRIDA_INST_NO_DYNAMIC_LOAD`
+     */
+    static setInstrumentNoDynamicLoad() {
+      Afl.jsApiSetInstrumentNoDynamicLoad();
+    }
+    /**
      * See `AFL_FRIDA_INST_NO_OPTIMIZE`
      */
     static setInstrumentNoOptimize() {
@@ -342,6 +348,7 @@ Afl.jsApiSetInstrumentDebugFile = Afl.jsApiGetFunction("js_api_set_instrument_de
 Afl.jsApiSetInstrumentInstructions = Afl.jsApiGetFunction("js_api_set_instrument_instructions", "void", []);
 Afl.jsApiSetInstrumentJit = Afl.jsApiGetFunction("js_api_set_instrument_jit", "void", []);
 Afl.jsApiSetInstrumentLibraries = Afl.jsApiGetFunction("js_api_set_instrument_libraries", "void", []);
+Afl.jsApiSetInstrumentNoDynamicLoad = Afl.jsApiGetFunction("js_api_set_instrument_no_dynamic_load", "void", []);
 Afl.jsApiSetInstrumentNoOptimize = Afl.jsApiGetFunction("js_api_set_instrument_no_optimize", "void", []);
 Afl.jsApiSetInstrumentRegsFile = Afl.jsApiGetFunction("js_api_set_instrument_regs_file", "void", ["pointer"]);
 Afl.jsApiSetInstrumentSeed = Afl.jsApiGetFunction("js_api_set_instrument_seed", "void", ["uint64"]);
diff --git a/frida_mode/src/js/js_api.c b/frida_mode/src/js/js_api.c
index 2e996c1c..288aec95 100644
--- a/frida_mode/src/js/js_api.c
+++ b/frida_mode/src/js/js_api.c
@@ -156,6 +156,13 @@ __attribute__((visibility("default"))) void js_api_set_instrument_instructions(
 
 }
 
+__attribute__((visibility("default"))) void
+js_api_set_instrument_no_dynamic_load(void) {
+
+  ranges_inst_dynamic_load = FALSE;
+
+}
+
 __attribute__((visibility("default"))) void js_api_set_instrument_no_optimize(
     void) {
 
diff --git a/frida_mode/src/ranges.c b/frida_mode/src/ranges.c
index 72cb9730..e9fc3b4e 100644
--- a/frida_mode/src/ranges.c
+++ b/frida_mode/src/ranges.c
@@ -18,6 +18,7 @@ typedef struct {
 gboolean ranges_debug_maps = FALSE;
 gboolean ranges_inst_libs = FALSE;
 gboolean ranges_inst_jit = FALSE;
+gboolean ranges_inst_dynamic_load = TRUE;
 
 static GArray *module_ranges = NULL;
 static GArray *libs_ranges = NULL;
@@ -25,6 +26,7 @@ static GArray *jit_ranges = NULL;
 static GArray *include_ranges = NULL;
 static GArray *exclude_ranges = NULL;
 static GArray *ranges = NULL;
+static GArray *whole_memory_ranges = NULL;
 
 static void convert_address_token(gchar *token, GumMemoryRange *range) {
 
@@ -387,6 +389,21 @@ static GArray *collect_jit_ranges(void) {
 
 }
 
+static GArray *collect_whole_mem_ranges(void) {
+
+  GArray        *result;
+  GumMemoryRange range;
+  result = g_array_new(false, false, sizeof(GumMemoryRange));
+
+  range.base_address = 0;
+  range.size = G_MAXULONG;
+
+  g_array_append_val(result, range);
+
+  return result;
+
+}
+
 static gboolean intersect_range(GumMemoryRange *rr, GumMemoryRange *ra,
                                 GumMemoryRange *rb) {
 
@@ -574,11 +591,17 @@ void ranges_config(void) {
   if (getenv("AFL_FRIDA_DEBUG_MAPS") != NULL) { ranges_debug_maps = TRUE; }
   if (getenv("AFL_INST_LIBS") != NULL) { ranges_inst_libs = TRUE; }
   if (getenv("AFL_FRIDA_INST_JIT") != NULL) { ranges_inst_jit = TRUE; }
+  if (getenv("AFL_FRIDA_INST_NO_DYNAMIC_LOAD") != NULL) {
+
+    ranges_inst_dynamic_load = FALSE;
+
+  }
 
   if (ranges_debug_maps) { ranges_print_debug_maps(); }
 
   include_ranges = collect_ranges("AFL_FRIDA_INST_RANGES");
   exclude_ranges = collect_ranges("AFL_FRIDA_EXCLUDE_RANGES");
+  whole_memory_ranges = collect_whole_mem_ranges();
 
 }
 
@@ -628,10 +651,20 @@ void ranges_init(void) {
   print_ranges("step4", step4);
 
   /*
-   * After step4, we have the total ranges to be instrumented, we now subtract
-   * that from the original ranges of the modules to configure stalker.
+   * After step 4 we have the total ranges to be instrumented, we now subtract
+   * that either from the original ranges of the modules or from the whole
+   * memory if AFL_INST_NO_DYNAMIC_LOAD to configure the stalker.
    */
-  step5 = subtract_ranges(module_ranges, step4);
+  if (ranges_inst_dynamic_load) {
+
+    step5 = subtract_ranges(module_ranges, step4);
+
+  } else {
+
+    step5 = subtract_ranges(whole_memory_ranges, step4);
+
+  }
+
   print_ranges("step5", step5);
 
   ranges = merge_ranges(step5);
diff --git a/frida_mode/ts/lib/afl.ts b/frida_mode/ts/lib/afl.ts
index 6a2350e7..7d1fac6b 100644
--- a/frida_mode/ts/lib/afl.ts
+++ b/frida_mode/ts/lib/afl.ts
@@ -179,6 +179,13 @@ class Afl {
   }
 
   /**
+   * See `AFL_FRIDA_INST_NO_DYNAMIC_LOAD`
+   */
+  public static setInstrumentNoDynamicLoad(): void {
+    Afl.jsApiSetInstrumentNoDynamicLoad();
+  }
+
+  /**
    * See `AFL_FRIDA_INST_NO_OPTIMIZE`
    */
   public static setInstrumentNoOptimize(): void {
@@ -443,6 +450,11 @@ class Afl {
     "void",
     []);
 
+  private static readonly jsApiSetInstrumentNoDynamicLoad = Afl.jsApiGetFunction(
+    "js_api_set_instrument_no_dynamic_load",
+    "void",
+    []);
+
   private static readonly jsApiSetInstrumentNoOptimize = Afl.jsApiGetFunction(
     "js_api_set_instrument_no_optimize",
     "void",
diff --git a/include/afl-fuzz.h b/include/afl-fuzz.h
index 8b6502b4..831a0dbc 100644
--- a/include/afl-fuzz.h
+++ b/include/afl-fuzz.h
@@ -399,7 +399,8 @@ typedef struct afl_env_vars {
       afl_cycle_schedules, afl_expand_havoc, afl_statsd, afl_cmplog_only_new,
       afl_exit_on_seed_issues, afl_try_affinity, afl_ignore_problems,
       afl_keep_timeouts, afl_no_crash_readme, afl_ignore_timeouts,
-      afl_no_startup_calibration, afl_no_warn_instability;
+      afl_no_startup_calibration, afl_no_warn_instability,
+      afl_post_process_keep_original;
 
   u8 *afl_tmpdir, *afl_custom_mutator_library, *afl_python_module, *afl_path,
       *afl_hang_tmout, *afl_forksrv_init_tmout, *afl_preload,
@@ -1103,7 +1104,6 @@ u32  count_bits(afl_state_t *, u8 *);
 u32  count_bytes(afl_state_t *, u8 *);
 u32  count_non_255_bytes(afl_state_t *, u8 *);
 void simplify_trace(afl_state_t *, u8 *);
-void classify_counts(afl_forkserver_t *);
 #ifdef WORD_SIZE_64
 void discover_word(u8 *ret, u64 *current, u64 *virgin);
 #else
@@ -1117,6 +1117,9 @@ u8 *describe_op(afl_state_t *, u8, size_t);
 u8 save_if_interesting(afl_state_t *, void *, u32, u8);
 u8 has_new_bits(afl_state_t *, u8 *);
 u8 has_new_bits_unclassified(afl_state_t *, u8 *);
+#ifndef AFL_SHOWMAP
+void classify_counts(afl_forkserver_t *);
+#endif
 
 /* Extras */
 
@@ -1192,11 +1195,13 @@ void   fix_up_sync(afl_state_t *);
 void   check_asan_opts(afl_state_t *);
 void   check_binary(afl_state_t *, u8 *);
 void   check_if_tty(afl_state_t *);
-void   setup_signal_handlers(void);
 void   save_cmdline(afl_state_t *, u32, char **);
 void   read_foreign_testcases(afl_state_t *, int);
 void   write_crash_readme(afl_state_t *afl);
 u8     check_if_text_buf(u8 *buf, u32 len);
+#ifndef AFL_SHOWMAP
+void setup_signal_handlers(void);
+#endif
 
 /* CmpLog */
 
diff --git a/include/config.h b/include/config.h
index b6249a0f..764c29dc 100644
--- a/include/config.h
+++ b/include/config.h
@@ -26,7 +26,7 @@
 /* Version string: */
 
 // c = release, a = volatile github dev, e = experimental branch
-#define VERSION "++4.06c"
+#define VERSION "++4.07a"
 
 /******************************************************
  *                                                    *
diff --git a/include/envs.h b/include/envs.h
index 066921b9..23599b26 100644
--- a/include/envs.h
+++ b/include/envs.h
@@ -65,6 +65,7 @@ static char *afl_environment_variables[] = {
     "AFL_FRIDA_INST_INSN",
     "AFL_FRIDA_INST_JIT",
     "AFL_FRIDA_INST_NO_CACHE",
+    "AFL_FRIDA_INST_NO_DYNAMIC_LOAD",
     "AFL_FRIDA_INST_NO_OPTIMIZE",
     "AFL_FRIDA_INST_NO_PREFETCH",
     "AFL_FRIDA_INST_NO_PREFETCH_BACKPATCH",
@@ -186,6 +187,7 @@ static char *afl_environment_variables[] = {
     "AFL_PATH",
     "AFL_PERFORMANCE_FILE",
     "AFL_PERSISTENT_RECORD",
+    "AFL_POST_PROCESS_KEEP_ORIGINAL",
     "AFL_PRELOAD",
     "AFL_TARGET_ENV",
     "AFL_PYTHON_MODULE",
diff --git a/src/afl-fuzz-run.c b/src/afl-fuzz-run.c
index 4d56f3a7..ac4fb4a9 100644
--- a/src/afl-fuzz-run.c
+++ b/src/afl-fuzz-run.c
@@ -135,10 +135,19 @@ write_to_testcase(afl_state_t *afl, void **mem, u32 len, u32 fix) {
 
     if (new_mem != *mem && new_mem != NULL && new_size > 0) {
 
-      u8 *new_buf = afl_realloc(AFL_BUF_PARAM(out_scratch), new_size);
+      new_buf = afl_realloc(AFL_BUF_PARAM(out_scratch), new_size);
       if (unlikely(!new_buf)) { PFATAL("alloc"); }
+      memcpy(new_buf, new_mem, new_size);
+
+      /* if AFL_POST_PROCESS_KEEP_ORIGINAL is set then save the original memory
+         prior post-processing in new_mem to restore it later */
+      if (unlikely(afl->afl_env.afl_post_process_keep_original)) {
+
+        new_mem = *mem;
+
+      }
+
       *mem = new_buf;
-      memcpy(*mem, new_mem, new_size);
       afl_swap_bufs(AFL_BUF_PARAM(out), AFL_BUF_PARAM(out_scratch));
 
     }
@@ -162,7 +171,18 @@ write_to_testcase(afl_state_t *afl, void **mem, u32 len, u32 fix) {
 
       /* everything as planned. use the potentially new data. */
       afl_fsrv_write_to_testcase(&afl->fsrv, *mem, new_size);
-      len = new_size;
+
+      if (likely(!afl->afl_env.afl_post_process_keep_original)) {
+
+        len = new_size;
+
+      } else {
+
+        /* restore the original memory which was saved in new_mem */
+        *mem = new_mem;
+        afl_swap_bufs(AFL_BUF_PARAM(out), AFL_BUF_PARAM(out_scratch));
+
+      }
 
     }
 
diff --git a/src/afl-fuzz-state.c b/src/afl-fuzz-state.c
index 46b67def..5e736029 100644
--- a/src/afl-fuzz-state.c
+++ b/src/afl-fuzz-state.c
@@ -394,6 +394,13 @@ void read_afl_environment(afl_state_t *afl, char **envp) {
             afl->afl_env.afl_statsd =
                 get_afl_env(afl_environment_variables[i]) ? 1 : 0;
 
+          } else if (!strncmp(env, "AFL_POST_PROCESS_KEEP_ORIGINAL",
+
+                              afl_environment_variable_len)) {
+
+            afl->afl_env.afl_post_process_keep_original =
+                get_afl_env(afl_environment_variables[i]) ? 1 : 0;
+
           } else if (!strncmp(env, "AFL_TMPDIR",
 
                               afl_environment_variable_len)) {
diff --git a/src/afl-fuzz.c b/src/afl-fuzz.c
index f6628851..ebdbb3fa 100644
--- a/src/afl-fuzz.c
+++ b/src/afl-fuzz.c
@@ -292,6 +292,8 @@ static void usage(u8 *argv0, int more_help) {
 
       PERSISTENT_MSG
 
+      "AFL_POST_PROCESS_KEEP_ORIGINAL: save the file as it was prior post-processing to the queue,\n"
+      "                                but execute the post-processed one\n"
       "AFL_PRELOAD: LD_PRELOAD / DYLD_INSERT_LIBRARIES settings for target\n"
       "AFL_TARGET_ENV: pass extra environment variables to target\n"
       "AFL_SHUFFLE_QUEUE: reorder the input queue randomly on startup\n"
@@ -326,7 +328,7 @@ static void usage(u8 *argv0, int more_help) {
   }
 
 #ifdef USE_PYTHON
-  SAYF("Compiled with %s module support, see docs/custom_mutator.md\n",
+  SAYF("Compiled with %s module support, see docs/custom_mutators.md\n",
        (char *)PYTHON_VERSION);
 #else
   SAYF("Compiled without Python module support.\n");
diff --git a/src/afl-showmap.c b/src/afl-showmap.c
index df030672..0b9fc211 100644
--- a/src/afl-showmap.c
+++ b/src/afl-showmap.c
@@ -30,8 +30,10 @@
  */
 
 #define AFL_MAIN
+#define AFL_SHOWMAP
 
 #include "config.h"
+#include "afl-fuzz.h"
 #include "types.h"
 #include "debug.h"
 #include "alloc-inl.h"
@@ -62,6 +64,8 @@
 #include <sys/types.h>
 #include <sys/resource.h>
 
+static afl_state_t *afl;
+
 static char *stdin_file;               /* stdin file                        */
 
 static u8 *in_dir = NULL,              /* input folder                      */
@@ -136,7 +140,39 @@ static void kill_child() {
 
 }
 
-static void classify_counts(afl_forkserver_t *fsrv) {
+/* dummy functions */
+u32 write_to_testcase(afl_state_t *afl, void **mem, u32 a, u32 b) {
+
+  (void)afl;
+  (void)mem;
+  return a + b;
+
+}
+
+void show_stats(afl_state_t *afl) {
+
+  (void)afl;
+
+}
+
+void update_bitmap_score(afl_state_t *afl, struct queue_entry *q) {
+
+  (void)afl;
+  (void)q;
+
+}
+
+fsrv_run_result_t fuzz_run_target(afl_state_t *afl, afl_forkserver_t *fsrv,
+                                  u32 i) {
+
+  (void)afl;
+  (void)fsrv;
+  (void)i;
+  return 0;
+
+}
+
+void classify_counts(afl_forkserver_t *fsrv) {
 
   u8       *mem = fsrv->trace_bits;
   const u8 *map = binary_mode ? count_class_binary : count_class_human;
@@ -308,12 +344,73 @@ static u32 write_results_to_file(afl_forkserver_t *fsrv, u8 *outfile) {
 
 }
 
+void pre_afl_fsrv_write_to_testcase(afl_forkserver_t *fsrv, u8 *mem, u32 len) {
+
+  static u8 buf[MAX_FILE];
+  u32       sent = 0;
+
+  if (unlikely(afl->custom_mutators_count)) {
+
+    ssize_t new_size = len;
+    u8     *new_mem = mem;
+    u8     *new_buf = NULL;
+
+    LIST_FOREACH(&afl->custom_mutator_list, struct custom_mutator, {
+
+      if (el->afl_custom_post_process) {
+
+        new_size =
+            el->afl_custom_post_process(el->data, new_mem, new_size, &new_buf);
+
+        if (unlikely(!new_buf || new_size <= 0)) {
+
+          return;
+
+        } else {
+
+          new_mem = new_buf;
+          len = new_size;
+
+        }
+
+      }
+
+    });
+
+    if (new_mem != mem && new_mem != NULL) {
+
+      mem = buf;
+      memcpy(mem, new_mem, new_size);
+
+    }
+
+    if (unlikely(afl->custom_mutators_count)) {
+
+      LIST_FOREACH(&afl->custom_mutator_list, struct custom_mutator, {
+
+        if (el->afl_custom_fuzz_send) {
+
+          el->afl_custom_fuzz_send(el->data, mem, len);
+          sent = 1;
+
+        }
+
+      });
+
+    }
+
+  }
+
+  if (likely(!sent)) { afl_fsrv_write_to_testcase(fsrv, mem, len); }
+
+}
+
 /* Execute target application. */
 
 static void showmap_run_target_forkserver(afl_forkserver_t *fsrv, u8 *mem,
                                           u32 len) {
 
-  afl_fsrv_write_to_testcase(fsrv, mem, len);
+  pre_afl_fsrv_write_to_testcase(fsrv, mem, len);
 
   if (!quiet_mode) { SAYF("-- Program output begins --\n" cRST); }
 
@@ -835,6 +932,10 @@ static void usage(u8 *argv0) {
       "This tool displays raw tuple data captured by AFL instrumentation.\n"
       "For additional help, consult %s/README.md.\n\n"
 
+      "If you use -i mode, then custom mutator post_process send send "
+      "functionality\n"
+      "is supported.\n\n"
+
       "Environment variables used:\n"
       "LD_BIND_LAZY: do not set LD_BIND_NOW env var for target\n"
       "AFL_CMIN_CRASHES_ONLY: (cmin_mode) only write tuples for crashing "
@@ -1266,6 +1367,8 @@ int main(int argc, char **argv_orig, char **envp) {
 
   }
 
+  afl = calloc(1, sizeof(afl_state_t));
+
   if (getenv("AFL_FORKSRV_INIT_TMOUT")) {
 
     s32 forksrv_init_tmout = atoi(getenv("AFL_FORKSRV_INIT_TMOUT"));
@@ -1382,6 +1485,26 @@ int main(int argc, char **argv_orig, char **envp) {
 
   if (in_dir) {
 
+    afl->fsrv.dev_urandom_fd = open("/dev/urandom", O_RDONLY);
+    afl->afl_env.afl_custom_mutator_library =
+        getenv("AFL_CUSTOM_MUTATOR_LIBRARY");
+    afl->afl_env.afl_python_module = getenv("AFL_PYTHON_MODULE");
+    setup_custom_mutators(afl);
+
+  } else {
+
+    if (getenv("AFL_CUSTOM_MUTATOR_LIBRARY") || getenv("AFL_PYTHON_MODULE")) {
+
+      WARNF(
+          "Custom mutator environment detected, this is only supported in -i "
+          "mode!\n");
+
+    }
+
+  }
+
+  if (in_dir) {
+
     DIR *dir_in, *dir_out = NULL;
 
     if (getenv("AFL_DEBUG_GDB")) wait_for_gdb = true;