about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--docs/Changelog.md3
-rw-r--r--unicorn_mode/UNICORNAFL_VERSION2
-rw-r--r--unicorn_mode/samples/speedtest/.gitignore6
-rw-r--r--unicorn_mode/samples/speedtest/Makefile17
-rw-r--r--unicorn_mode/samples/speedtest/README.md65
-rw-r--r--unicorn_mode/samples/speedtest/c/Makefile54
-rw-r--r--unicorn_mode/samples/speedtest/c/harness.c390
-rw-r--r--unicorn_mode/samples/speedtest/get_offsets.py77
-rw-r--r--unicorn_mode/samples/speedtest/python/Makefile8
-rw-r--r--unicorn_mode/samples/speedtest/python/harness.py277
-rw-r--r--unicorn_mode/samples/speedtest/rust/.gitignore1
-rw-r--r--unicorn_mode/samples/speedtest/rust/Cargo.lock80
-rw-r--r--unicorn_mode/samples/speedtest/rust/Cargo.toml15
-rw-r--r--unicorn_mode/samples/speedtest/rust/Makefile17
-rw-r--r--unicorn_mode/samples/speedtest/rust/src/main.rs231
-rw-r--r--unicorn_mode/samples/speedtest/sample_inputs/a1
-rw-r--r--unicorn_mode/samples/speedtest/target.c77
m---------unicorn_mode/unicornafl0
-rwxr-xr-xunicorn_mode/update_uc_ref.sh2
19 files changed, 1321 insertions, 2 deletions
diff --git a/docs/Changelog.md b/docs/Changelog.md
index 2b8b0e8d..062dd785 100644
--- a/docs/Changelog.md
+++ b/docs/Changelog.md
@@ -37,6 +37,9 @@ sending a mail to <afl-users+subscribe@googlegroups.com>.
     - added AFL_LLVM_INSTRUMENT option NATIVE for native clang pc-guard
       support (less performant than our own), GCC for old afl-gcc and
       CLANG for old afl-clang
+  - unicornafl
+    - Substential speed gains in python bindings for certain use cases
+    - Added a new example harness to compare python, c, and rust bindings
   - changed default: no memory limit for afl-cmin and afl-cmin.bash
   - warn on any _AFL and __AFL env vars
   - LLVM mode is now compiled with -j4, unicorn with all cores. qemu was
diff --git a/unicorn_mode/UNICORNAFL_VERSION b/unicorn_mode/UNICORNAFL_VERSION
index 2dbc30b8..f1fb7f18 100644
--- a/unicorn_mode/UNICORNAFL_VERSION
+++ b/unicorn_mode/UNICORNAFL_VERSION
@@ -1 +1 @@
-83d1b426
+0dd17c58
diff --git a/unicorn_mode/samples/speedtest/.gitignore b/unicorn_mode/samples/speedtest/.gitignore
new file mode 100644
index 00000000..78310c60
--- /dev/null
+++ b/unicorn_mode/samples/speedtest/.gitignore
@@ -0,0 +1,6 @@
+output
+harness
+harness-debug
+target
+target.o
+target.offsets.*
diff --git a/unicorn_mode/samples/speedtest/Makefile b/unicorn_mode/samples/speedtest/Makefile
new file mode 100644
index 00000000..23f5cb07
--- /dev/null
+++ b/unicorn_mode/samples/speedtest/Makefile
@@ -0,0 +1,17 @@
+CFLAGS += -Wall -Werror -Wextra -Wpedantic -Og -g -fPIE
+
+.PHONY: all clean
+
+all: target target.offsets.main
+
+clean:
+	rm -rf *.o target target.offsets.*
+
+target.o: target.c
+	${CC} ${CFLAGS} -c target.c -o $@
+
+target: target.o
+	${CC} ${CFLAGS} target.o -o $@
+
+target.offsets.main: target
+	./get_offsets.py
\ No newline at end of file
diff --git a/unicorn_mode/samples/speedtest/README.md b/unicorn_mode/samples/speedtest/README.md
new file mode 100644
index 00000000..3c1184a2
--- /dev/null
+++ b/unicorn_mode/samples/speedtest/README.md
@@ -0,0 +1,65 @@
+# Speedtest
+
+This is a simple sample harness for a non-crashing file,
+to show the raw speed of C, Rust, and Python harnesses.
+
+## Compiling...
+
+Make sure, you built unicornafl first (`../../build_unicorn_support.sh`).
+Then, follow these individual steps:
+
+### Rust
+
+```bash
+cd rust
+cargo build --release
+../../../afl-fuzz -i ../sample_inputs -o out -- ./target/release/harness @@
+```
+
+### C
+
+```bash
+cd c
+make
+../../../afl-fuzz -i ../sample_inputs -o out -- ./harness @@
+```
+
+### python
+
+```bash
+cd python
+../../../afl-fuzz -i ../sample_inputs -o out -U -- python3 ./harness.py @@
+```
+
+## Results
+
+TODO: add results here.
+
+
+## Compiling speedtest_target.c
+
+You shouldn't need to compile simple_target.c since a X86_64 binary version is
+pre-built and shipped in this sample folder. This file documents how the binary
+was built in case you want to rebuild it or recompile it for any reason.
+
+The pre-built binary (simple_target_x86_64.bin) was built using -g -O0 in gcc.
+
+We then load the binary and execute the main function directly.
+
+## Addresses for the harness:
+To find the address (in hex) of main, run:
+```bash
+objdump -M intel -D target | grep '<main>:' | cut -d" " -f1
+```
+To find all call sites to magicfn, run:
+```bash
+objdump -M intel -D target | grep '<magicfn>$' | cut -d":" -f1
+```
+For malloc callsites:
+```bash
+objdump -M intel -D target | grep '<malloc@plt>$' | cut -d":" -f1
+```
+And free callsites:
+```bash
+objdump -M intel -D target | grep '<free@plt>$' | cut -d":" -f1
+```
diff --git a/unicorn_mode/samples/speedtest/c/Makefile b/unicorn_mode/samples/speedtest/c/Makefile
new file mode 100644
index 00000000..ce784d4f
--- /dev/null
+++ b/unicorn_mode/samples/speedtest/c/Makefile
@@ -0,0 +1,54 @@
+# UnicornAFL Usage
+# Original Unicorn Example Makefile by Nguyen Anh Quynh <aquynh@gmail.com>, 2015
+# Adapted for AFL++ by domenukk <domenukk@gmail.com>, 2020
+.POSIX:
+UNAME_S =$(shell uname -s)# GNU make
+UNAME_S:sh=uname -s       # BSD make
+_UNIQ=_QINU_
+
+LIBDIR = ../../../unicornafl
+BIN_EXT =
+AR_EXT = a
+
+# Verbose output?
+V ?= 0
+
+CFLAGS += -Wall -Werror -Wextra -Wno-unused-parameter -I../../../unicornafl/include
+
+LDFLAGS += -L$(LIBDIR) -lpthread -lm
+
+_LRT = $(_UNIQ)$(UNAME_S:Linux=)
+__LRT = $(_LRT:$(_UNIQ)=-lrt)
+LRT = $(__LRT:$(_UNIQ)=)
+
+LDFLAGS += $(LRT)
+
+_CC = $(_UNIQ)$(CROSS)
+__CC = $(_CC:$(_UNIQ)=$(CC))
+MYCC = $(__CC:$(_UNIQ)$(CROSS)=$(CROSS)gcc)
+
+.PHONY: all clean
+
+all: fuzz
+
+clean:
+	rm -rf *.o harness harness-debug
+
+harness.o: harness.c ../../../unicornafl/include/unicorn/*.h
+	${MYCC} ${CFLAGS} -O3 -c harness.c -o $@
+
+harness-debug.o: harness.c ../../../unicornafl/include/unicorn/*.h
+	${MYCC} ${CFLAGS} -fsanitize=address -g -Og -c harness.c -o $@
+
+harness: harness.o
+	${MYCC} -L${LIBDIR} harness.o ../../../unicornafl/libunicornafl.a $(LDFLAGS) -o $@
+
+harness-debug: harness-debug.o
+	${MYCC} -fsanitize=address -g -Og -L${LIBDIR} harness-debug.o ../../../unicornafl/libunicornafl.a $(LDFLAGS) -o harness-debug
+
+../target:
+	$(MAKE) -C ..
+
+fuzz: ../target harness
+	rm -rf ./output
+	SKIP_BINCHECK=1 ../../../../afl-fuzz -s 1 -i ../sample_inputs -o ./output -- ./harness @@
diff --git a/unicorn_mode/samples/speedtest/c/harness.c b/unicorn_mode/samples/speedtest/c/harness.c
new file mode 100644
index 00000000..e8de3d80
--- /dev/null
+++ b/unicorn_mode/samples/speedtest/c/harness.c
@@ -0,0 +1,390 @@
+/*
+   Simple test harness for AFL++'s unicornafl c mode.
+
+   This loads the simple_target_x86_64 binary into
+   Unicorn's memory map for emulation, places the specified input into
+   argv[1], sets up argv, and argc and executes 'main()'.
+   If run inside AFL, afl_fuzz automatically does the "right thing"
+
+   Run under AFL as follows:
+
+   $ cd <afl_path>/unicorn_mode/samples/simple/
+   $ make
+   $ ../../../afl-fuzz -m none -i sample_inputs -o out -- ./harness @@
+*/
+
+// This is not your everyday Unicorn.
+#define UNICORN_AFL
+
+#include <string.h>
+#include <inttypes.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <unistd.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <sys/mman.h>
+
+#include <unicorn/unicorn.h>
+
+// Path to the file containing the binary to emulate
+#define BINARY_FILE ("../target")
+
+// Memory map for the code to be tested
+// Arbitrary address where code to test will be loaded
+static const int64_t BASE_ADDRESS = 0x0;
+// Max size for the code (64kb)
+static const int64_t CODE_SIZE_MAX = 0x00010000;
+// Location where the input will be placed (make sure the emulated program knows this somehow, too ;) )
+static const int64_t INPUT_ADDRESS = 0x00100000;
+// Maximum size for our input
+static const int64_t INPUT_MAX = 0x00100000;
+// Where our pseudo-heap is at
+static const int64_t HEAP_ADDRESS = 0x00200000;
+// Maximum allowable size for the heap
+static const int64_t HEAP_SIZE_MAX = 0x000F0000;
+// Address of the stack (Some random address again)
+static const int64_t STACK_ADDRESS = 0x00400000;
+// Size of the stack (arbitrarily chosen, just make it big enough)
+static const int64_t STACK_SIZE = 0x000F0000;
+
+// Alignment for unicorn mappings (seems to be needed)
+static const int64_t ALIGNMENT = 0x1000;
+
+static void hook_block(uc_engine *uc, uint64_t address, uint32_t size, void *user_data) {
+    printf(">>> Tracing basic block at 0x%"PRIx64 ", block size = 0x%x\n", address, size);
+}
+
+static void hook_code(uc_engine *uc, uint64_t address, uint32_t size, void *user_data) {
+    printf(">>> Tracing instruction at 0x%"PRIx64 ", instruction size = 0x%x\n", address, size);
+}
+
+/* Unicorn page needs to be 0x1000 aligned, apparently */
+static uint64_t pad(uint64_t size) {
+    if (size % ALIGNMENT == 0) { return size; }
+    return ((size / ALIGNMENT) + 1) * ALIGNMENT;
+} 
+
+/* returns the filesize in bytes, -1 or error. */
+static off_t afl_mmap_file(char *filename, char **buf_ptr) {
+
+    off_t ret = -1;
+
+    int fd = open(filename, O_RDONLY);
+
+    struct stat st = {0};
+    if (fstat(fd, &st)) goto exit;
+
+    off_t in_len = st.st_size;
+    if (in_len == -1) {
+        /* This can only ever happen on 32 bit if the file is exactly 4gb. */
+        fprintf(stderr, "Filesize of %s too large\n", filename);
+        goto exit;
+    }
+
+    *buf_ptr = mmap(0, in_len, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);
+
+    if (*buf_ptr != MAP_FAILED) ret = in_len;
+
+exit:
+    close(fd);
+    return ret;
+
+}
+
+/* Place the input at the right spot inside unicorn.
+   This code path is *HOT*, do as little work as possible! */
+static bool place_input_callback(
+    uc_engine *uc, 
+    char *input, 
+    size_t input_len, 
+    uint32_t persistent_round, 
+    void *data
+){
+    // printf("Placing input with len %ld to %x\n", input_len, DATA_ADDRESS);
+    if (input_len >= INPUT_MAX) {
+        // Test input too short or too long, ignore this testcase
+        return false;
+    }
+
+    // We need a valid c string, make sure it never goes out of bounds.
+    input[input_len-1] = '\0';
+
+    // Write the testcase to unicorn.
+    uc_mem_write(uc, INPUT_ADDRESS, input, input_len);
+
+    return true;
+}
+
+// exit in case the unicorn-internal mmap fails.
+static void mem_map_checked(uc_engine *uc, uint64_t addr, size_t size, uint32_t mode) {
+    size = pad(size);
+    //printf("SIZE %llx, align: %llx\n", size, ALIGNMENT);
+    uc_err err = uc_mem_map(uc, addr, size, mode);
+    if (err != UC_ERR_OK) {
+        printf("Error mapping %ld bytes at 0x%lx: %s (mode: %d)\n", size, addr, uc_strerror(err), mode);
+        exit(1);
+    }
+}
+
+// allocates an array, reads all addrs to the given array ptr, returns a size
+ssize_t read_all_addrs(char *path, uint64_t *addrs, size_t max_count) {
+
+    FILE *f = fopen(path, "r"); 
+    if (!f) {
+        perror("fopen");
+        fprintf(stderr, "Could not read %s, make sure you ran ./get_offsets.py\n", path);
+        exit(-1);
+    }
+    for (size_t i = 0; i < max_count; i++) {
+        bool end = false;
+        if(fscanf(f, "%lx", &addrs[i]) == EOF) {
+            end = true;
+            i--;
+        } else if (fgetc(f) == EOF) {
+            end = true;
+        }
+        if (end) {
+            printf("Set %ld addrs for %s\n", i + 1, path);
+            fclose(f);
+            return i + 1;
+        }
+    }
+    return max_count;
+}
+
+// Read all addresses from the given file, and set a hook for them.
+void set_all_hooks(uc_engine *uc, char *hook_file, void *hook_fn) {
+
+    FILE *f = fopen(hook_file, "r");
+    if (!f) {
+        fprintf(stderr, "Could not read %s, make sure you ran ./get_offsets.py\n", hook_file);
+        exit(-1);
+    }
+    uint64_t hook_addr;
+    for (int hook_count = 0; 1; hook_count++) {
+        if(fscanf(f, "%lx", &hook_addr) == EOF) {
+            printf("Set %d hooks for %s\n", hook_count, hook_file);
+            fclose(f);
+            return;
+        }
+        printf("got new hook addr %lx (count: %d) ohbytw: sizeof %lx\n", hook_addr, hook_count, sizeof(uc_hook));
+        hook_addr += BASE_ADDRESS;
+        // We'll leek these hooks like a good citizen.
+        uc_hook *hook = calloc(1, sizeof(uc_hook));
+        if (!hook) {
+            perror("calloc");
+            exit(-1);
+        }
+        uc_hook_add(uc, hook, UC_HOOK_CODE, hook_fn, NULL, hook_addr, hook_addr);
+        // guzzle up newline
+        if (fgetc(f) == EOF) {
+            printf("Set %d hooks for %s\n", hook_count, hook_file);
+            fclose(f);
+            return;
+        }
+    }
+
+}
+
+// This is a fancy print function that we're just going to skip for fuzzing.
+static void hook_magicfn(uc_engine *uc, uint64_t address, uint32_t size, void *user_data) {
+    address += size;
+    uc_reg_write(uc, UC_X86_REG_RIP, &address);
+} 
+
+static bool already_allocated = false;
+
+// We use a very simple malloc/free stub here, that only works for exactly one allocation at a time.
+static void hook_malloc(uc_engine *uc, uint64_t address, uint32_t size, void *user_data) {
+    if (already_allocated) {
+        printf("Double malloc, not supported right now!\n");
+        abort();
+    }
+    // read the first param.
+    uint64_t malloc_size;
+    uc_reg_read(uc, UC_X86_REG_RDI, &malloc_size);
+    if (malloc_size > HEAP_SIZE_MAX) {
+        printf("Tried to allocated %ld bytes, but we only support up to %ld\n", malloc_size, HEAP_SIZE_MAX);
+        abort();
+    }
+    uc_reg_write(uc, UC_X86_REG_RAX, &HEAP_ADDRESS);
+    address += size;
+    uc_reg_write(uc, UC_X86_REG_RIP, &address);
+    already_allocated = true;
+}
+
+// No real free, just set the "used"-flag to false.
+static void hook_free(uc_engine *uc, uint64_t address, uint32_t size, void *user_data) {
+    if (!already_allocated) {
+        printf("Double free detected. Real bug?\n");
+        abort();
+    }
+    // read the first param.
+    uint64_t free_ptr;
+    uc_reg_read(uc, UC_X86_REG_RDI, &free_ptr);
+    if (free_ptr != HEAP_ADDRESS) {
+        printf("Tried to free wrong mem region: 0x%lx at code loc 0x%lx\n", free_ptr, address);
+        abort();
+    }
+    address +=  size;
+    uc_reg_write(uc, UC_X86_REG_RIP, &address);
+    already_allocated = false;
+}
+
+int main(int argc, char **argv, char **envp) {
+    if (argc == 1) {
+        printf("Test harness to measure speed against Rust and python. Usage: harness [-t] <inputfile>\n");
+        exit(1);
+    }
+    bool tracing = false;
+    char *filename = argv[1];
+    if (argc > 2 && !strcmp(argv[1], "-t")) {
+        tracing = true;
+        filename = argv[2];
+    }
+
+    uc_engine *uc;
+    uc_err err;
+    uc_hook hooks[2];
+    char *file_contents;
+
+    // Initialize emulator in X86_64 mode
+    err = uc_open(UC_ARCH_X86, UC_MODE_64, &uc);
+    if (err) {
+        printf("Failed on uc_open() with error returned: %u (%s)\n",
+                err, uc_strerror(err));
+        return -1;
+    }
+
+    // If we want tracing output, set the callbacks here
+    if (tracing) {
+        // tracing all basic blocks with customized callback
+        uc_hook_add(uc, &hooks[0], UC_HOOK_BLOCK, hook_block, NULL, 1, 0);
+        uc_hook_add(uc, &hooks[1], UC_HOOK_CODE, hook_code, NULL, 1, 0);
+    }
+
+    printf("The input testcase is set to %s\n", filename);
+
+
+    printf("Loading target from %s\n", BINARY_FILE);
+    off_t len = afl_mmap_file(BINARY_FILE, &file_contents);
+    printf("Binary file size: %lx\n", len);
+    if (len < 0) {
+        perror("Could not read binary to emulate");
+        return -2;
+    }
+    if (len == 0) {
+        fprintf(stderr, "File at '%s' is empty\n", BINARY_FILE);
+        return -3;
+    }
+    if (len > CODE_SIZE_MAX) {
+        fprintf(stderr, "Binary too large, increase CODE_SIZE_MAX\n");
+        return -4;
+    }
+
+    // Map memory.
+    mem_map_checked(uc, BASE_ADDRESS, len, UC_PROT_ALL);
+    fflush(stdout);
+
+    // write machine code to be emulated to memory
+    if (uc_mem_write(uc, BASE_ADDRESS, file_contents, len) != UC_ERR_OK) {
+        puts("Error writing to CODE");
+        exit(-1);
+    }
+
+    // Release copied contents
+    munmap(file_contents, len);
+
+    // Set the program counter to the start of the code
+    FILE *f = fopen("../target.offsets.main", "r");
+    if (!f) {
+        perror("fopen");
+        puts("Could not read offset to main function, make sure you ran ./get_offsets.py");
+        exit(-1);
+    }
+    uint64_t start_address;
+    if(fscanf(f, "%lx", &start_address) == EOF) {
+        puts("Start address not found in target.offests.main");
+        exit(-1);
+    }
+    fclose(f);
+    start_address += BASE_ADDRESS;
+    printf("Execution will start at 0x%lx", start_address);
+    // Set the program counter to the start of the code
+    uc_reg_write(uc, UC_X86_REG_RIP, &start_address); // address of entry point of main()
+
+    // Setup the Stack
+    mem_map_checked(uc, STACK_ADDRESS, STACK_SIZE, UC_PROT_READ | UC_PROT_WRITE);
+    // Setup the stack pointer, but allocate two pointers for the pointers to input
+    uint64_t val = STACK_ADDRESS + STACK_SIZE - 16;
+    //printf("Stack at %lu\n", stack_val);
+    uc_reg_write(uc, UC_X86_REG_RSP, &val);
+
+    // reserve some space for our input data
+    mem_map_checked(uc, INPUT_ADDRESS, INPUT_MAX, UC_PROT_READ);
+
+    // argc = 2
+    val = 2;
+    uc_reg_write(uc, UC_X86_REG_RDI, &val);
+    //RSI points to our little 2 QWORD space at the beginning of the stack...
+    val = STACK_ADDRESS + STACK_SIZE - 16;
+    uc_reg_write(uc, UC_X86_REG_RSI, &val);
+
+    //... which points to the Input. Write the ptr to mem in little endian.
+    uint32_t addr_little = STACK_ADDRESS;
+#if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
+    // The chances you are on a big_endian system aren't too high, but still...
+    __builtin_bswap32(addr_little);
+#endif
+
+    uc_mem_write(uc, STACK_ADDRESS + STACK_SIZE - 16, (char *)&addr_little, 4);
+
+    set_all_hooks(uc, "../target.offsets.malloc", hook_malloc);
+    set_all_hooks(uc, "../target.offsets.magicfn", hook_magicfn);
+    set_all_hooks(uc, "../target.offsets.free", hook_free);
+
+    int exit_count_max = 100;
+    // we don't need more exits for now.
+    uint64_t exits[exit_count_max];
+
+    ssize_t exit_count = read_all_addrs("../target.offsets.main_ends", exits, exit_count_max);
+    if (exit_count < 1) {
+        printf("Could not find exits! aborting.\n");
+        abort();
+    }
+
+    printf("Starting to fuzz. Running from addr %ld to one of these %ld exits:\n", start_address, exit_count);
+    for (ssize_t i = 0; i < exit_count; i++) {
+        printf("    exit %ld: %ld\n", i, exits[i]);
+    }
+
+    fflush(stdout);
+
+    // let's gooo
+    uc_afl_ret afl_ret = uc_afl_fuzz(
+        uc, // The unicorn instance we prepared
+        filename, // Filename of the input to process. In AFL this is usually the '@@' placeholder, outside it's any input file.
+        place_input_callback, // Callback that places the input (automatically loaded from the file at filename) in the unicorninstance
+        exits, // Where to exit (this is an array)
+        exit_count,  // Count of end addresses
+        NULL, // Optional calback to run after each exec
+        false, // true, if the optional callback should be run also for non-crashes
+        1000, // For persistent mode: How many rounds to run
+        NULL // additional data pointer
+    );
+    switch(afl_ret) {
+        case UC_AFL_RET_ERROR:
+            printf("Error starting to fuzz");
+            return -3;
+            break;
+        case UC_AFL_RET_NO_AFL:
+            printf("No AFL attached - We are done with a single run.");
+            break;
+        default:
+            break;
+    } 
+    return 0;
+}
diff --git a/unicorn_mode/samples/speedtest/get_offsets.py b/unicorn_mode/samples/speedtest/get_offsets.py
new file mode 100644
index 00000000..c9dc76df
--- /dev/null
+++ b/unicorn_mode/samples/speedtest/get_offsets.py
@@ -0,0 +1,77 @@
+#!/usr/bin/env python3
+
+"""This simple script uses objdump to parse important addresses from the target"""
+import shlex
+import subprocess
+
+objdump_output = subprocess.check_output(
+    shlex.split("objdump -M intel -D target")
+).decode()
+main_loc = None
+main_ends = []
+main_ended = False
+magicfn_calls = []
+malloc_calls = []
+free_calls = []
+strlen_calls = []
+
+
+def line2addr(line):
+    return "0x" + line.split(":", 1)[0].strip()
+
+
+last_line = None
+for line in objdump_output.split("\n"):
+    line = line.strip()
+
+    def read_addr_if_endswith(findme, list_to):
+        """
+        Look, for example, for the addr like:
+        12a9:       e8 f2 fd ff ff          call   10a0 <free@plt>
+        """
+        if line.endswith(findme):
+            list_to.append(line2addr(line))
+
+    if main_loc is not None and main_ended is False:
+        # We want to know where main ends. An empty line in objdump.
+        if len(line) == 0:
+            main_ends.append(line2addr(last_line))
+            main_ended = True
+        elif "ret" in line:
+            main_ends.append(line2addr(line))
+
+    if "<main>:" in line:
+        if main_loc is not None:
+            raise Exception("Found multiple main functions, odd target!")
+        # main_loc is the label, so it's parsed differntly (i.e. `0000000000001220 <main>:`)
+        main_loc = "0x" + line.strip().split(" ", 1)[0].strip()
+    else:
+        [
+            read_addr_if_endswith(*x)
+            for x in [
+                ("<free@plt>", free_calls),
+                ("<malloc@plt>", malloc_calls),
+                ("<strlen@plt>", strlen_calls),
+                ("<magicfn>", magicfn_calls),
+            ]
+        ]
+
+    last_line = line
+
+if main_loc is None:
+    raise (
+        "Could not find main in ./target! Make sure objdump is installed and the target is compiled."
+    )
+
+with open("target.offsets.main", "w") as f:
+    f.write(main_loc)
+with open("target.offsets.main_ends", "w") as f:
+    f.write("\n".join(main_ends))
+with open("target.offsets.magicfn", "w") as f:
+    f.write("\n".join(magicfn_calls))
+with open("target.offsets.malloc", "w") as f:
+    f.write("\n".join(malloc_calls))
+with open("target.offsets.free", "w") as f:
+    f.write("\n".join(free_calls))
+with open("target.offsets.strlen", "w") as f:
+    f.write("\n".join(strlen_calls))
diff --git a/unicorn_mode/samples/speedtest/python/Makefile b/unicorn_mode/samples/speedtest/python/Makefile
new file mode 100644
index 00000000..4282c6cb
--- /dev/null
+++ b/unicorn_mode/samples/speedtest/python/Makefile
@@ -0,0 +1,8 @@
+all: fuzz
+
+../target:
+	$(MAKE) -C ..
+
+fuzz: ../target
+	rm -rf ./ouptput
+	../../../../afl-fuzz -s 1 -U -i ../sample_inputs -o ./output -- python3 harness.py @@
diff --git a/unicorn_mode/samples/speedtest/python/harness.py b/unicorn_mode/samples/speedtest/python/harness.py
new file mode 100644
index 00000000..f72eb32b
--- /dev/null
+++ b/unicorn_mode/samples/speedtest/python/harness.py
@@ -0,0 +1,277 @@
+#!/usr/bin/env python3
+""" 
+    Simple test harness for AFL's Unicorn Mode.
+
+    This loads the speedtest target binary (precompiled X64 code) into
+    Unicorn's memory map for emulation, places the specified input into
+    Argv, and executes main.
+    There should not be any crashes - it's a speedtest against Rust and c.
+
+    Before running this harness, call make in the parent folder.
+
+    Run under AFL as follows:
+
+    $ cd <afl_path>/unicorn_mode/samples/speedtest/python
+    $ ../../../../afl-fuzz -U -i ../sample_inputs -o ./output -- python3 harness.py @@
+"""
+
+import argparse
+import os
+import struct
+
+from unicornafl import *
+from unicornafl.unicorn_const import UC_ARCH_X86, UC_HOOK_CODE, UC_MODE_64
+from unicornafl.x86_const import (
+    UC_X86_REG_RAX,
+    UC_X86_REG_RDI,
+    UC_X86_REG_RIP,
+    UC_X86_REG_RSI,
+    UC_X86_REG_RSP,
+)
+
+# Memory map for the code to be tested
+BASE_ADDRESS = 0x0  # Arbitrary address where the (PIE) target binary will be loaded to
+CODE_SIZE_MAX = 0x00010000  # Max size for the code (64kb)
+INPUT_ADDRESS = 0x00100000  # where we put our stuff
+INPUT_MAX = 0x00100000  # max size for our input
+HEAP_ADDRESS = 0x00200000  # Heap addr
+HEAP_SIZE_MAX = 0x000F0000  # Maximum allowable size for the heap
+STACK_ADDRESS = 0x00400000  # Address of the stack (arbitrarily chosen)
+STACK_SIZE = 0x000F0000  # Size of the stack (arbitrarily chosen)
+
+target_path = os.path.abspath(
+    os.path.join(os.path.dirname(os.path.abspath(__file__)), "..")
+)
+target_bin = os.path.join(target_path, "target")
+
+
+def get_offsets_for(name):
+    full_path = os.path.join(target_path, f"target.offsets.{name}")
+    with open(full_path) as f:
+        return [int(x, 16) + BASE_ADDRESS for x in f.readlines()]
+
+
+# Read all offsets from our objdump file
+main_offset = get_offsets_for("main")[0]
+main_ends = get_offsets_for("main_ends")
+malloc_callsites = get_offsets_for("malloc")
+free_callsites = get_offsets_for("free")
+magicfn_callsites = get_offsets_for("magicfn")
+# Joke's on me: strlen got inlined by my compiler
+strlen_callsites = get_offsets_for("strlen")
+
+try:
+    # If Capstone is installed then we'll dump disassembly, otherwise just dump the binary.
+    from capstone import *
+
+    cs = Cs(CS_ARCH_MIPS, CS_MODE_MIPS32 + CS_MODE_BIG_ENDIAN)
+
+    def unicorn_debug_instruction(uc, address, size, user_data):
+        mem = uc.mem_read(address, size)
+        for (cs_address, cs_size, cs_mnemonic, cs_opstr) in cs.disasm_lite(
+            bytes(mem), size
+        ):
+            print("    Instr: {:#016x}:\t{}\t{}".format(address, cs_mnemonic, cs_opstr))
+
+
+except ImportError:
+
+    def unicorn_debug_instruction(uc, address, size, user_data):
+        print("    Instr: addr=0x{0:016x}, size=0x{1:016x}".format(address, size))
+
+
+def unicorn_debug_block(uc, address, size, user_data):
+    print("Basic Block: addr=0x{0:016x}, size=0x{1:016x}".format(address, size))
+
+
+def unicorn_debug_mem_access(uc, access, address, size, value, user_data):
+    if access == UC_MEM_WRITE:
+        print(
+            "        >>> Write: addr=0x{0:016x} size={1} data=0x{2:016x}".format(
+                address, size, value
+            )
+        )
+    else:
+        print("        >>> Read: addr=0x{0:016x} size={1}".format(address, size))
+
+
+def unicorn_debug_mem_invalid_access(uc, access, address, size, value, user_data):
+    if access == UC_MEM_WRITE_UNMAPPED:
+        print(
+            "        >>> INVALID Write: addr=0x{0:016x} size={1} data=0x{2:016x}".format(
+                address, size, value
+            )
+        )
+    else:
+        print(
+            "        >>> INVALID Read: addr=0x{0:016x} size={1}".format(address, size)
+        )
+
+
+already_allocated = False
+
+
+def malloc_hook(uc, address, size, user_data):
+    """
+    We use a very simple malloc/free stub here, that only works for exactly one allocation at a time.
+    """
+    global already_allocated
+    if already_allocated:
+        print("Double malloc, not supported right now!")
+        os.abort()
+    # read the first param
+    malloc_size = uc.reg_read(UC_X86_REG_RDI)
+    if malloc_size > HEAP_SIZE_MAX:
+        print(
+            f"Tried to allocate {malloc_size} bytes, aint't nobody got space for that! (We may only allocate up to {HEAP_SIZE_MAX})"
+        )
+        os.abort()
+    uc.reg_write(UC_X86_REG_RAX, HEAP_ADDRESS)
+    uc.reg_write(UC_X86_REG_RIP, address + size)
+    already_allocated = True
+
+
+def free_hook(uc, address, size, user_data):
+    """
+    No real free, just set the "used"-flag to false.
+    """
+    global already_allocated
+    if not already_allocated:
+        print("Double free detected. Real bug?")
+        os.abort()
+    # read the first param
+    free_ptr = uc.reg_read(UC_X86_REG_RDI)
+    if free_ptr != HEAP_ADDRESS:
+        print(
+            f"Tried to free wrong mem region: {hex(free_ptr)} at code loc {hex(address)}"
+        )
+        os.abort()
+    uc.reg_write(UC_X86_REG_RIP, address + size)
+    already_allocated = False
+
+
+# def strlen_hook(uc, address, size, user_data):
+#     """
+#     No real strlen, we know the len is == our input.
+#     This completely ignores '\0', but for this target, do we really care?
+#     """
+#     global input_len
+#     print(f"Returning len {input_len}")
+#     uc.reg_write(UC_X86_REG_RAX, input_len)
+#     uc.reg_write(UC_X86_REG_RIP, address + size)
+
+
+def magicfn_hook(uc, address, size, user_data):
+    """
+    This is a fancy print function that we're just going to skip for fuzzing.
+    """
+    uc.reg_write(UC_X86_REG_RIP, address + size)
+
+
+def main():
+
+    parser = argparse.ArgumentParser(description="Test harness for simple_target.bin")
+    parser.add_argument(
+        "input_file",
+        type=str,
+        help="Path to the file containing the mutated input to load",
+    )
+    parser.add_argument(
+        "-t",
+        "--trace",
+        default=False,
+        action="store_true",
+        help="Enables debug tracing",
+    )
+    args = parser.parse_args()
+
+    # Instantiate a MIPS32 big endian Unicorn Engine instance
+    uc = Uc(UC_ARCH_X86, UC_MODE_64)
+
+    if args.trace:
+        uc.hook_add(UC_HOOK_BLOCK, unicorn_debug_block)
+        uc.hook_add(UC_HOOK_CODE, unicorn_debug_instruction)
+        uc.hook_add(UC_HOOK_MEM_WRITE | UC_HOOK_MEM_READ, unicorn_debug_mem_access)
+        uc.hook_add(
+            UC_HOOK_MEM_WRITE_UNMAPPED | UC_HOOK_MEM_READ_INVALID,
+            unicorn_debug_mem_invalid_access,
+        )
+
+    print("The input testcase is set to {}".format(args.input_file))
+
+    # ---------------------------------------------------
+    # Load the binary to emulate and map it into memory
+    with open(target_bin, "rb") as f:
+        binary_code = f.read()
+
+    # Apply constraints to the mutated input
+    if len(binary_code) > CODE_SIZE_MAX:
+        print("Binary code is too large (> {} bytes)".format(CODE_SIZE_MAX))
+        return
+
+    # Write the binary to its place in mem
+    uc.mem_map(BASE_ADDRESS, CODE_SIZE_MAX)
+    uc.mem_write(BASE_ADDRESS, binary_code)
+
+    # Set the program counter to the start of the code
+    uc.reg_write(UC_X86_REG_RIP, main_offset)
+
+    # Setup the stack.
+    uc.mem_map(STACK_ADDRESS, STACK_SIZE)
+    # Setup the stack pointer, but allocate two pointers for the pointers to input.
+    uc.reg_write(UC_X86_REG_RSP, STACK_ADDRESS + STACK_SIZE - 16)
+
+    # Setup our input space, and push the pointer to it in the function params
+    uc.mem_map(INPUT_ADDRESS, INPUT_MAX)
+    # We have argc = 2
+    uc.reg_write(UC_X86_REG_RDI, 2)
+    # RSI points to our little 2 QWORD space at the beginning of the stack...
+    uc.reg_write(UC_X86_REG_RSI, STACK_ADDRESS + STACK_SIZE - 16)
+    # ... which points to the Input. Write the ptr to mem in little endian.
+    uc.mem_write(STACK_ADDRESS + STACK_SIZE - 16, struct.pack("<Q", INPUT_ADDRESS))
+
+    for addr in malloc_callsites:
+        uc.hook_add(UC_HOOK_CODE, malloc_hook, begin=addr, end=addr)
+
+    for addr in free_callsites:
+        uc.hook_add(UC_HOOK_CODE, free_hook, begin=addr, end=addr)
+
+    if len(strlen_callsites):
+        # strlen got inlined for my compiler.
+        print(
+            "Oops, your compiler emitted strlen as function. You may have to change the harness."
+        )
+    # for addr in strlen_callsites:
+    #     uc.hook_add(UC_HOOK_CODE, strlen_hook, begin=addr, end=addr)
+
+    for addr in magicfn_callsites:
+        uc.hook_add(UC_HOOK_CODE, magicfn_hook, begin=addr, end=addr + 1)
+
+    # -----------------------------------------------------
+    # Set up a callback to place input data (do little work here, it's called for every single iteration! This code is *HOT*)
+    # We did not pass in any data and don't use persistent mode, so we can ignore these params.
+    # Be sure to check out the docstrings for the uc.afl_* functions.
+    def place_input_callback(uc, input, persistent_round, data):
+        # Apply constraints to the mutated input
+        input_len = len(input)
+        # global input_len
+        if input_len > INPUT_MAX:
+            #print("Test input is too long (> {} bytes)")
+            return False
+
+        # print(f"Placing input: {input} in round {persistent_round}")
+
+        # Make sure the string is always 0-terminated (as it would be "in the wild")
+        input[-1] = b'\0'
+
+        # Write the mutated command into the data buffer
+        uc.mem_write(INPUT_ADDRESS, input)
+        #uc.reg_write(UC_X86_REG_RIP, main_offset)
+
+    print(f"Starting to fuzz. Running from addr {main_offset} to one of {main_ends}")
+    # Start the fuzzer.
+    uc.afl_fuzz(args.input_file, place_input_callback, main_ends, persistent_iters=1000)
+
+
+if __name__ == "__main__":
+    main()
diff --git a/unicorn_mode/samples/speedtest/rust/.gitignore b/unicorn_mode/samples/speedtest/rust/.gitignore
new file mode 100644
index 00000000..eb5a316c
--- /dev/null
+++ b/unicorn_mode/samples/speedtest/rust/.gitignore
@@ -0,0 +1 @@
+target
diff --git a/unicorn_mode/samples/speedtest/rust/Cargo.lock b/unicorn_mode/samples/speedtest/rust/Cargo.lock
new file mode 100644
index 00000000..5887facf
--- /dev/null
+++ b/unicorn_mode/samples/speedtest/rust/Cargo.lock
@@ -0,0 +1,80 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+[[package]]
+name = "bitflags"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
+
+[[package]]
+name = "build-helper"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bdce191bf3fa4995ce948c8c83b4640a1745457a149e73c6db75b4ffe36aad5f"
+dependencies = [
+ "semver",
+]
+
+[[package]]
+name = "capstone"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "031ba51c39151a1d6336ec859646153187204b0147c7b3f6fe2de636f1b8dbb3"
+dependencies = [
+ "capstone-sys",
+]
+
+[[package]]
+name = "capstone-sys"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fae25eddcb80e24f98c35952c37a91ff7f8d0f60dbbdafb9763e8d5cc566b8d7"
+dependencies = [
+ "cc",
+]
+
+[[package]]
+name = "cc"
+version = "1.0.66"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4c0496836a84f8d0495758516b8621a622beb77c0fed418570e50764093ced48"
+
+[[package]]
+name = "libc"
+version = "0.2.82"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "89203f3fba0a3795506acaad8ebce3c80c0af93f994d5a1d7a0b1eeb23271929"
+
+[[package]]
+name = "semver"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7a3186ec9e65071a2095434b1f5bb24838d4e8e130f584c790f6033c79943537"
+dependencies = [
+ "semver-parser",
+]
+
+[[package]]
+name = "semver-parser"
+version = "0.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
+
+[[package]]
+name = "unicornafl"
+version = "1.0.0"
+dependencies = [
+ "bitflags",
+ "build-helper",
+ "capstone",
+ "libc",
+]
+
+[[package]]
+name = "unicornafl_harness"
+version = "0.1.0"
+dependencies = [
+ "capstone",
+ "libc",
+ "unicornafl",
+]
diff --git a/unicorn_mode/samples/speedtest/rust/Cargo.toml b/unicorn_mode/samples/speedtest/rust/Cargo.toml
new file mode 100644
index 00000000..c19ee0a1
--- /dev/null
+++ b/unicorn_mode/samples/speedtest/rust/Cargo.toml
@@ -0,0 +1,15 @@
+[package]
+name = "unicornafl_harness"
+version = "0.1.0"
+authors = ["Dominik Maier <domenukk@gmail.com>"]
+edition = "2018"
+
+[profile.release]
+lto = true
+opt-level = 3
+panic = "abort"
+
+[dependencies]
+unicornafl = { path = "../../../unicornafl/bindings/rust/", version="1.0.0" }
+capstone="0.6.0"
+libc="0.2.66"
\ No newline at end of file
diff --git a/unicorn_mode/samples/speedtest/rust/Makefile b/unicorn_mode/samples/speedtest/rust/Makefile
new file mode 100644
index 00000000..fe18d6ee
--- /dev/null
+++ b/unicorn_mode/samples/speedtest/rust/Makefile
@@ -0,0 +1,17 @@
+all: fuzz
+
+clean:
+	cargo clean
+
+./target/release/unicornafl_harness: ./src/main.rs
+	cargo build --release
+
+./target/debug/unicornafl_harness: ./src/main.rs
+	cargo build
+
+../target:
+	$(MAKE) -c ..
+
+fuzz: ../target ./target/release/unicornafl_harness
+	rm -rf ./output
+	SKIP_BINCHECK=1 ../../../../afl-fuzz -s 1 -i ../sample_inputs -o ./output -- ./target/release/unicornafl_harness @@
diff --git a/unicorn_mode/samples/speedtest/rust/src/main.rs b/unicorn_mode/samples/speedtest/rust/src/main.rs
new file mode 100644
index 00000000..8e31d2e2
--- /dev/null
+++ b/unicorn_mode/samples/speedtest/rust/src/main.rs
@@ -0,0 +1,231 @@
+extern crate capstone;
+extern crate libc;
+
+use core::cell::Cell;
+use std::{
+    env,
+    fs::File,
+    io::{self, Read},
+    process::abort,
+    str,
+};
+
+use unicornafl::{
+    unicorn_const::{uc_error, Arch, Mode, Permission},
+    RegisterX86::{self, *},
+    Unicorn, UnicornHandle,
+};
+
+const BINARY: &str = &"../target";
+
+// Memory map for the code to be tested
+// Arbitrary address where code to test will be loaded
+const BASE_ADDRESS: u64 = 0x0;
+// Max size for the code (64kb)
+const CODE_SIZE_MAX: u64 = 0x00010000;
+// Location where the input will be placed (make sure the uclated program knows this somehow, too ;) )
+const INPUT_ADDRESS: u64 = 0x00100000;
+// Maximum size for our input
+const INPUT_MAX: u64 = 0x00100000;
+// Where our pseudo-heap is at
+const HEAP_ADDRESS: u64 = 0x00200000;
+// Maximum allowable size for the heap
+const HEAP_SIZE_MAX: u64 = 0x000F0000;
+// Address of the stack (Some random address again)
+const STACK_ADDRESS: u64 = 0x00400000;
+// Size of the stack (arbitrarily chosen, just make it big enough)
+const STACK_SIZE: u64 = 0x000F0000;
+
+fn read_file(filename: &str) -> Result<Vec<u8>, io::Error> {
+    let mut f = File::open(filename)?;
+    let mut buffer = Vec::new();
+    f.read_to_end(&mut buffer)?;
+    Ok(buffer)
+}
+
+/// Our location parser
+fn parse_locs(loc_name: &str) -> Result<Vec<u64>, io::Error> {
+    let contents = &read_file(&format!("../target.offsets.{}", loc_name))?;
+    //println!("Read: {:?}", contents);
+    Ok(str_from_u8_unchecked(&contents)
+        .split("\n")
+        .map(|x| {
+            //println!("Trying to convert {}", &x[2..]);
+            let result = u64::from_str_radix(&x[2..], 16);
+            result.unwrap()
+        })
+        .collect())
+}
+
+// find null terminated string in vec
+pub fn str_from_u8_unchecked(utf8_src: &[u8]) -> &str {
+    let nul_range_end = utf8_src
+        .iter()
+        .position(|&c| c == b'\0')
+        .unwrap_or(utf8_src.len());
+    unsafe { str::from_utf8_unchecked(&utf8_src[0..nul_range_end]) }
+}
+
+fn align(size: u64) -> u64 {
+    const ALIGNMENT: u64 = 0x1000;
+    if size % ALIGNMENT == 0 {
+        size
+    } else {
+        ((size / ALIGNMENT) + 1) * ALIGNMENT
+    }
+}
+
+fn main() {
+    let args: Vec<String> = env::args().collect();
+    if args.len() == 1 {
+        println!("Missing parameter <uclation_input> (@@ for AFL)");
+        return;
+    }
+    let input_file = &args[1];
+    println!("The input testcase is set to {}", input_file);
+    fuzz(input_file).unwrap();
+}
+
+fn fuzz(input_file: &str) -> Result<(), uc_error> {
+    let mut unicorn = Unicorn::new(Arch::X86, Mode::MODE_64, 0)?;
+    let mut uc: UnicornHandle<'_, _> = unicorn.borrow();
+
+    let binary = read_file(BINARY).expect(&format!("Could not read modem image: {}", BINARY));
+    let _aligned_binary_size = align(binary.len() as u64);
+    // Apply constraints to the mutated input
+    if binary.len() as u64 > CODE_SIZE_MAX {
+        println!("Binary code is too large (> {} bytes)", CODE_SIZE_MAX);
+    }
+
+    // Write the binary to its place in mem
+    uc.mem_map(BASE_ADDRESS, CODE_SIZE_MAX as usize, Permission::ALL)?;
+    uc.mem_write(BASE_ADDRESS, &binary)?;
+
+    // Set the program counter to the start of the code
+    let main_locs = parse_locs("main").unwrap();
+    //println!("Entry Point: {:x}", main_locs[0]);
+    uc.reg_write(RegisterX86::RIP as i32, main_locs[0])?;
+
+    // Setup the stack.
+    uc.mem_map(
+        STACK_ADDRESS,
+        STACK_SIZE as usize,
+        Permission::READ | Permission::WRITE,
+    )?;
+    // Setup the stack pointer, but allocate two pointers for the pointers to input.
+    uc.reg_write(RSP as i32, STACK_ADDRESS + STACK_SIZE - 16)?;
+
+    // Setup our input space, and push the pointer to it in the function params
+    uc.mem_map(INPUT_ADDRESS, INPUT_MAX as usize, Permission::READ)?;
+    // We have argc = 2
+    uc.reg_write(RDI as i32, 2)?;
+    // RSI points to our little 2 QWORD space at the beginning of the stack...
+    uc.reg_write(RSI as i32, STACK_ADDRESS + STACK_SIZE - 16)?;
+    // ... which points to the Input. Write the ptr to mem in little endian.
+    uc.mem_write(
+        STACK_ADDRESS + STACK_SIZE - 16,
+        &(INPUT_ADDRESS as u32).to_le_bytes(),
+    )?;
+
+    let already_allocated = Cell::new(false);
+
+    let already_allocated_malloc = already_allocated.clone();
+    // We use a very simple malloc/free stub here,
+    // that only works for exactly one allocation at a time.
+    let hook_malloc = move |mut uc: UnicornHandle<'_, _>, addr: u64, size: u32| {
+        if already_allocated_malloc.get() {
+            println!("Double malloc, not supported right now!");
+            abort();
+        }
+        // read the first param
+        let malloc_size = uc.reg_read(RDI as i32).unwrap();
+        if malloc_size > HEAP_SIZE_MAX {
+            println!(
+                "Tried to allocate {} bytes, but we may only allocate up to {}",
+                malloc_size, HEAP_SIZE_MAX
+            );
+            abort();
+        }
+        uc.reg_write(RAX as i32, HEAP_ADDRESS).unwrap();
+        uc.reg_write(RIP as i32, addr + size as u64).unwrap();
+        already_allocated_malloc.set(true);
+    };
+
+    let already_allocated_free = already_allocated.clone();
+    // No real free, just set the "used"-flag to false.
+    let hook_free = move |mut uc: UnicornHandle<'_, _>, addr, size| {
+        if already_allocated_free.get() {
+            println!("Double free detected. Real bug?");
+            abort();
+        }
+        // read the first param
+        let free_ptr = uc.reg_read(RDI as i32).unwrap();
+        if free_ptr != HEAP_ADDRESS {
+            println!(
+                "Tried to free wrong mem region {:x} at code loc {:x}",
+                free_ptr, addr
+            );
+            abort();
+        }
+        uc.reg_write(RIP as i32, addr + size as u64).unwrap();
+        already_allocated_free.set(false);
+    };
+
+    /*
+        BEGIN FUNCTION HOOKS
+    */
+
+    // This is a fancy print function that we're just going to skip for fuzzing.
+    let hook_magicfn = move |mut uc: UnicornHandle<'_, _>, addr, size| {
+        uc.reg_write(RIP as i32, addr + size as u64).unwrap();
+    };
+
+    for addr in parse_locs("malloc").unwrap() {
+        //hook!(addr, hook_malloc, "malloc");
+        uc.add_code_hook(addr, addr, Box::new(hook_malloc.clone()))?;
+    }
+
+    for addr in parse_locs("free").unwrap() {
+        uc.add_code_hook(addr, addr, Box::new(hook_free.clone()))?;
+    }
+
+    for addr in parse_locs("magicfn").unwrap() {
+        uc.add_code_hook(addr, addr, Box::new(hook_magicfn.clone()))?;
+    }
+
+    let place_input_callback =
+        |mut uc: UnicornHandle<'_, _>, afl_input: &[u8], _persistent_round| {
+            // apply constraints to the mutated input
+            if afl_input.len() > INPUT_MAX as usize {
+                //println!("Skipping testcase with leng {}", afl_input.len());
+                return false;
+            }
+
+            // TODO: afl_input[-1] = b'\0'
+            uc.mem_write(INPUT_ADDRESS, afl_input).unwrap();
+            true
+        };
+
+    let crash_validation_callback =
+        |_uc: UnicornHandle<'_, _>, result, _input: &[u8], _persistent_round| {
+            result != uc_error::OK
+        };
+
+    let end_addrs = parse_locs("main_ends").unwrap();
+
+    let ret = uc.afl_fuzz(
+        input_file,
+        Box::new(place_input_callback),
+        &end_addrs,
+        Box::new(crash_validation_callback),
+        false,
+        1000,
+    );
+
+    match ret {
+        Ok(_) => {}
+        Err(e) => panic!(format!("found non-ok unicorn exit: {:?}", e)),
+    }
+
+    Ok(())
+}
diff --git a/unicorn_mode/samples/speedtest/sample_inputs/a b/unicorn_mode/samples/speedtest/sample_inputs/a
new file mode 100644
index 00000000..78981922
--- /dev/null
+++ b/unicorn_mode/samples/speedtest/sample_inputs/a
@@ -0,0 +1 @@
+a
diff --git a/unicorn_mode/samples/speedtest/target.c b/unicorn_mode/samples/speedtest/target.c
new file mode 100644
index 00000000..8359a110
--- /dev/null
+++ b/unicorn_mode/samples/speedtest/target.c
@@ -0,0 +1,77 @@
+/*
+ * Sample target file to test afl-unicorn fuzzing capabilities.
+ * This is a very trivial example that will, however, never crash.
+ * Crashing would change the execution speed.
+ *
+ */
+#include <stdint.h>
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+// Random print function we can hook in our harness to test hook speeds.
+char magicfn(char to_print) {
+  puts("Printing a char, just minding my own business: ");
+  putchar(to_print);
+  putchar('\n');
+  return to_print;
+}
+
+int main(int argc, char** argv) {
+  if (argc < 2) {
+    printf("Gimme input pl0x!\n");
+    return -1;
+  }
+ 
+  // Make sure the hooks work...
+  char *test = malloc(1024);
+  if (!test) {
+    printf("Uh-Oh, malloc doesn't work!");
+    abort();
+  }
+  free(test);
+
+  char *data_buf = argv[1];
+  // We can start the unicorn hooking here.
+  uint64_t data_len = strlen(data_buf);
+  if (data_len < 20) return -2;
+
+  for (; data_len --> 0 ;) {
+    char *buf_cpy = NULL;
+    if (data_len) {
+      buf_cpy = malloc(data_len);
+      if (!buf_cpy) {
+        puts("Oof, malloc failed! :/");
+        abort();
+      }
+      memcpy(buf_cpy, data_buf, data_len);
+    }
+    if (data_len >= 18) {
+      free(buf_cpy);
+      continue;
+    }
+    if (data_len > 2 && data_len < 18) {
+      buf_cpy[data_len - 1] = (char) 0x90;
+    } else if (data_buf[9] == (char) 0x90 && data_buf[10] != 0x00 && buf_cpy[11] == (char) 0x90) {
+        // Cause a crash if data[10] is not zero, but [9] and [11] are zero
+        unsigned char valid_read = buf_cpy[10];
+        if (magicfn(valid_read) != valid_read) {
+          puts("Oof, the hook for data_buf[10] is broken?");
+          abort();
+        }
+    }
+    free(buf_cpy);
+  }
+  if (data_buf[0] > 0x10 && data_buf[0] < 0x20 && data_buf[1] > data_buf[2]) {
+    // Cause an 'invalid read' crash if (0x10 < data[0] < 0x20) and data[1] > data[2]
+    unsigned char valid_read = data_buf[0];
+    if (magicfn(valid_read) != valid_read) {
+      puts("Oof, the hook for data_buf[0] is broken?");
+      abort();
+    }
+  } 
+
+  magicfn('q');
+
+  return 0;
+}
diff --git a/unicorn_mode/unicornafl b/unicorn_mode/unicornafl
-Subproject 83d1b426be5d373edcc81576f58a10f617df143
+Subproject 0dd17c58d51ed6dc69a367adbe8d2dca4d224c4
diff --git a/unicorn_mode/update_uc_ref.sh b/unicorn_mode/update_uc_ref.sh
index a2613942..7c1c7778 100755
--- a/unicorn_mode/update_uc_ref.sh
+++ b/unicorn_mode/update_uc_ref.sh
@@ -19,7 +19,7 @@ if [ "$NEW_VERSION" = "-h" ]; then
   exit 1
 fi
 
-git submodule init && git submodule update || exit 1
+git submodule init && git submodule update unicornafl || exit 1
 cd ./unicornafl || exit 1
 git fetch origin dev 1>/dev/null || exit 1
 git stash 1>/dev/null 2>/dev/null