about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--frida_mode/.gitignore5
-rw-r--r--frida_mode/Makefile310
-rw-r--r--frida_mode/README.md48
-rw-r--r--frida_mode/inc/instrument.h7
-rw-r--r--frida_mode/inc/interceptor.h4
-rw-r--r--frida_mode/inc/prefetch.h5
-rw-r--r--frida_mode/inc/ranges.h6
-rw-r--r--frida_mode/src/instrument.c265
-rw-r--r--frida_mode/src/interceptor.c16
-rw-r--r--frida_mode/src/main.c149
-rw-r--r--frida_mode/src/prefetch.c121
-rw-r--r--frida_mode/src/ranges.c395
-rw-r--r--frida_mode/test/testinstr.c105
-rwxr-xr-xfrida_mode/test/testinstr.py32
-rw-r--r--include/envs.h7
-rw-r--r--include/forkserver.h2
-rw-r--r--src/afl-analyze.c50
-rw-r--r--src/afl-fuzz-init.c7
-rw-r--r--src/afl-fuzz.c54
-rw-r--r--src/afl-showmap.c47
-rw-r--r--src/afl-tmin.c49
22 files changed, 1668 insertions, 17 deletions
diff --git a/.gitignore b/.gitignore
index 3f440730..2aaaf9ef 100644
--- a/.gitignore
+++ b/.gitignore
@@ -82,3 +82,4 @@ libAFLDriver.a
 libAFLQemuDriver.a
 test/.afl_performance
 gmon.out
+afl-frida-trace.so
diff --git a/frida_mode/.gitignore b/frida_mode/.gitignore
new file mode 100644
index 00000000..956b9911
--- /dev/null
+++ b/frida_mode/.gitignore
@@ -0,0 +1,5 @@
+build/
+frida_test.dat
+qemu_test.dat
+frida_out/**
+qemu_out/**
diff --git a/frida_mode/Makefile b/frida_mode/Makefile
new file mode 100644
index 00000000..73a4142c
--- /dev/null
+++ b/frida_mode/Makefile
@@ -0,0 +1,310 @@
+PWD:=$(shell pwd)/
+INC_DIR:=$(PWD)inc/
+SRC_DIR:=$(PWD)src/
+INCLUDES:=$(wildcard $(INC_DIR)*.h)
+SOURCES:=$(wildcard $(SRC_DIR)*.c)
+BUILD_DIR:=$(PWD)build/
+CFLAGS:= $(CFLAGS) \
+		-fPIC \
+		-D_GNU_SOURCE
+
+FRIDA_BUILD_DIR:=$(BUILD_DIR)frida/
+FRIDA_TRACE:=$(FRIDA_BUILD_DIR)afl-frida-trace.so
+
+ARCH=$(shell uname -m)
+ifeq "$(ARCH)" "aarch64"
+ARCH:=arm64
+TESTINSTR_BASE:=0x0000aaaaaaaaa000
+endif
+
+ifeq "$(ARCH)" "x86_64"
+TESTINSTR_BASE:=0x0000555555554000
+endif
+
+ifeq "$(shell uname)" "Darwin"
+OS:=macos
+AFL_FRIDA_INST_RANGES=0x0000000000001000-0xFFFFFFFFFFFFFFFF
+CFLAGS:=$(CFLAGS) -Wno-deprecated-declarations
+TEST_LDFLAGS:=-undefined dynamic_lookup
+endif
+ifeq "$(shell uname)" "Linux"
+OS:=linux
+AFL_FRIDA_INST_RANGES=$(shell $(PWD)test/testinstr.py -f $(BUILD_DIR)testinstr -s .testinstr -b $(TESTINSTR_BASE))
+CFLAGS:=$(CFLAGS) -Wno-prio-ctor-dtor
+TEST_LDFLAGS:=
+endif
+
+ifndef OS
+$(error "Operating system unsupported")
+endif
+
+VERSION=14.2.13
+GUM_DEVKIT_FILENAME=frida-gum-devkit-$(VERSION)-$(OS)-$(ARCH).tar.xz
+GUM_DEVKIT_URL="https://github.com/frida/frida/releases/download/$(VERSION)/$(GUM_DEVKIT_FILENAME)"
+GUM_DEVKIT_TARBALL:=$(FRIDA_BUILD_DIR)$(GUM_DEVKIT_FILENAME)
+GUM_DEVIT_LIBRARY=$(FRIDA_BUILD_DIR)libfrida-gum.a
+GUM_DEVIT_HEADER=$(FRIDA_BUILD_DIR)frida-gum.h
+
+TEST_BUILD_DIR:=$(BUILD_DIR)test/
+
+LIBPNG_FILE:=$(TEST_BUILD_DIR)libpng-1.2.56.tar.gz
+LIBPNG_URL:=https://downloads.sourceforge.net/project/libpng/libpng12/older-releases/1.2.56/libpng-1.2.56.tar.gz
+LIBPNG_DIR:=$(TEST_BUILD_DIR)libpng-1.2.56/
+LIBPNG_MAKEFILE:=$(LIBPNG_DIR)Makefile
+LIBPNG_LIB:=$(LIBPNG_DIR).libs/libpng12.a
+
+HARNESS_FILE:=$(TEST_BUILD_DIR)StandaloneFuzzTargetMain.c
+HARNESS_OBJ:=$(TEST_BUILD_DIR)StandaloneFuzzTargetMain.o
+HARNESS_URL:="https://raw.githubusercontent.com/llvm/llvm-project/main/compiler-rt/lib/fuzzer/standalone/StandaloneFuzzTargetMain.c"
+
+PNGTEST_FILE:=$(TEST_BUILD_DIR)target.cc
+PNGTEST_OBJ:=$(TEST_BUILD_DIR)target.o
+PNGTEST_URL:="https://raw.githubusercontent.com/google/fuzzbench/master/benchmarks/libpng-1.2.56/target.cc"
+
+TEST_BIN:=$(TEST_BUILD_DIR)pngtest
+
+TESTINSTBIN:=$(BUILD_DIR)testinstr
+TESTINSTSRC:=$(PWD)test/testinstr.c
+
+TEST_DATA_DIR:=$(PWD)build/test/libpng-1.2.56/contrib/pngsuite/
+
+TESTINSTR_DATA_DIR:=$(BUILD_DIR)testinstr_in/
+TESTINSTR_DATA_FILE:=$(TESTINSTR_DATA_DIR)test.dat
+FRIDA_OUT:=$(PWD)frida_out
+QEMU_OUT:=$(PWD)qemu_out
+
+.PHONY: all frida test clean format test_frida test_qemu compare testinstr test_testinstr standalone
+
+all: $(FRIDA_TRACE)
+
+frida: $(FRIDA_TRACE)
+
+$(BUILD_DIR):
+	mkdir -p $(BUILD_DIR)
+
+############################# FRIDA ############################################
+$(FRIDA_BUILD_DIR): | $(BUILD_DIR)
+	mkdir -p $@
+
+$(GUM_DEVKIT_TARBALL): | $(FRIDA_BUILD_DIR)
+	wget -O $@ $(GUM_DEVKIT_URL)
+
+$(GUM_DEVIT_LIBRARY): | $(GUM_DEVKIT_TARBALL)
+	tar Jxvf $(GUM_DEVKIT_TARBALL) -C $(FRIDA_BUILD_DIR)
+
+$(GUM_DEVIT_HEADER): | $(GUM_DEVKIT_TARBALL)
+	tar Jxvf $(GUM_DEVKIT_TARBALL) -C $(FRIDA_BUILD_DIR)
+
+$(FRIDA_TRACE): $(GUM_DEVIT_LIBRARY) $(GUM_DEVIT_HEADER) $(SOURCES) Makefile | $(FRIDA_BUILD_DIR)
+	$(CC) -shared \
+		$(CFLAGS) \
+		-o $@ $(SOURCES) \
+		$(GUM_DEVIT_LIBRARY) \
+		-I $(FRIDA_BUILD_DIR) \
+		-I .. \
+		-I ../include \
+		-I $(INC_DIR) \
+		../instrumentation/afl-compiler-rt.o.c \
+		-lpthread -ldl -lresolv
+
+	cp -v $(FRIDA_TRACE) ../
+
+############################# TEST #############################################
+
+test: $(TEST_BIN)
+
+$(TEST_BUILD_DIR): $(BUILD_DIR)
+	mkdir -p $@
+
+$(HARNESS_FILE): | $(TEST_BUILD_DIR)
+	wget -O $@ $(HARNESS_URL)
+
+$(HARNESS_OBJ): $(HARNESS_FILE)
+	$(CC) -o $@ -c $<
+
+$(PNGTEST_FILE): | $(TEST_BUILD_DIR)
+	wget -O $@ $(PNGTEST_URL)
+
+$(PNGTEST_OBJ): $(PNGTEST_FILE) | $(LIBPNG_DIR)
+	$(CXX) -std=c++11 -I $(LIBPNG_DIR) -o $@ -c $<
+
+$(LIBPNG_FILE): | $(TEST_BUILD_DIR)
+	wget -O $@ $(LIBPNG_URL)
+
+$(LIBPNG_DIR): $(LIBPNG_FILE)
+	tar zxvf $(LIBPNG_FILE) -C $(TEST_BUILD_DIR)
+
+$(LIBPNG_MAKEFILE): | $(LIBPNG_DIR)
+	cd $(LIBPNG_DIR) && ./configure
+
+$(LIBPNG_LIB): $(LIBPNG_MAKEFILE)
+	make -C $(LIBPNG_DIR)
+
+$(TEST_BIN): $(HARNESS_OBJ) $(PNGTEST_OBJ) $(LIBPNG_LIB)
+	$(CXX) \
+		-o $@ \
+		$(HARNESS_OBJ) $(PNGTEST_OBJ) $(LIBPNG_LIB) \
+		-lz \
+		$(TEST_LDFLAGS)
+
+############################# TESTINSR #########################################
+$(TESTINSTR_DATA_DIR): | $(BUILD_DIR)
+	mkdir -p $@
+
+$(TESTINSTR_DATA_FILE): | $(TESTINSTR_DATA_DIR)
+	echo -n "000" > $@
+
+$(TESTINSTBIN): $(TESTINSTSRC) | $(BUILD_DIR)
+	$(CC) -o $@ $<
+
+testinstr: $(TESTINSTBIN)
+
+############################# CLEAN ############################################
+clean:
+	rm -rf $(BUILD_DIR)
+
+############################# FORMAT ###########################################
+format:
+	cd .. && echo $(SOURCES) | xargs -L1 ./.custom-format.py -i
+	cd .. && echo $(INCLUDES) | xargs -L1 ./.custom-format.py -i
+	cd .. && ./.custom-format.py -i $(TESTINSTSRC)
+
+############################# RUN #############################################
+
+# Add the environment variable AFL_DEBUG_CHILD=1 to show printf's from the target
+
+png_frida: $(FRIDA_TRACE) $(TEST_BIN)
+	make -C ..
+	cd .. && \
+		./afl-fuzz \
+			-O \
+			-i $(TEST_DATA_DIR) \
+			-o $(FRIDA_OUT) \
+			-- \
+				$(TEST_BIN) @@
+
+png_qemu: $(TEST_BIN)
+	make -C ..
+	cd .. && \
+		./afl-fuzz \
+			-Q \
+			-i $(TEST_DATA_DIR) \
+			-o $(QEMU_OUT) \
+			-- \
+				$(TEST_BIN) @@
+
+compare: $(FRIDA_TRACE) $(TEST_BIN)
+	cd .. && \
+		./afl-fuzz \
+			-V30 \
+			-O \
+			-i $(TEST_DATA_DIR) \
+			-o $(FRIDA_OUT) \
+			-- \
+				$(TEST_BIN) @@
+	cd .. && \
+		./afl-fuzz \
+			-V30 \
+			-Q \
+			-i $(TEST_DATA_DIR) \
+			-o $(QEMU_OUT) \
+				-- \
+					$(TEST_BIN) @@
+	cat frida_out/default/fuzzer_stats
+	cat qemu_out/default/fuzzer_stats
+
+testinstr_qemu: $(TESTINSTBIN) $(TESTINSTR_DATA_FILE)
+	make -C ..
+	cd .. && \
+			AFL_QEMU_INST_RANGES=$(AFL_FRIDA_INST_RANGES) \
+			./afl-fuzz \
+				-Q \
+				-i $(TESTINSTR_DATA_DIR) \
+				-o $(QEMU_OUT) \
+				-- \
+					$(TESTINSTBIN) @@
+
+testinstr_frida: $(FRIDA_TRACE) $(TESTINSTBIN) $(TESTINSTR_DATA_FILE)
+	make -C ..
+	cd .. && \
+			AFL_FRIDA_INST_RANGES=$(AFL_FRIDA_INST_RANGES) \
+			AFL_FRIDA_INST_NO_OPTIMIZE=1 \
+			AFL_FRIDA_INST_NO_PREFETCH=1 \
+			AFL_FRIDA_INST_STRICT=1 \
+			./afl-fuzz \
+				-O \
+				-i $(TESTINSTR_DATA_DIR) \
+				-o $(FRIDA_OUT) \
+				-- \
+					$(TESTINSTBIN) @@
+
+standalone: $(FRIDA_TRACE) $(TESTINSTBIN) $(TESTINSTR_DATA_FILE)
+	cd .. && \
+			AFL_FRIDA_INST_RANGES=$(AFL_FRIDA_INST_RANGES) \
+			AFL_DEBUG_CHILD=1 \
+			AFL_FRIDA_DEBUG_MAPS=1 \
+			AFL_FRIDA_INST_NO_OPTIMIZE=1 \
+			AFL_FRIDA_INST_NO_PREFETCH=1 \
+			AFL_FRIDA_INST_TRACE=1 \
+			AFL_FRIDA_INST_STRICT=1 \
+			LD_PRELOAD=$(FRIDA_TRACE) \
+			DYLD_INSERT_LIBRARIES=$(FRIDA_TRACE) \
+			$(TESTINSTBIN) $(TESTINSTR_DATA_FILE)
+
+tmin_qemu: $(TEST_BIN)
+	make -C ..
+	cd .. && \
+		./afl-tmin \
+			-Q \
+			-i $(TEST_DATA_DIR)basn0g01.png \
+			-o $(QEMU_OUT)/qemu-min-basn0g01.png \
+			-- \
+				$(TEST_BIN) @@
+
+tmin_frida: $(TEST_BIN)
+	make -C ..
+	cd .. && \
+		./afl-tmin \
+			-O \
+			-i $(TEST_DATA_DIR)basn0g01.png \
+			-o $(FRIDA_OUT)/qemu-min-basn0g01.png \
+			-- \
+				$(TEST_BIN)
+
+showmap_qemu: $(TEST_BIN)
+	make -C ..
+	cd .. && \
+		./afl-showmap \
+			-Q \
+			-i $(TEST_DATA_DIR) \
+			-o $(QEMU_OUT) \
+			-- \
+				$(TEST_BIN) @@
+
+showmap_frida: $(TEST_BIN)
+	make -C ..
+	cd .. && \
+		./afl-showmap \
+			-O \
+			-i $(TEST_DATA_DIR) \
+			-o $(FRIDA_OUT) \
+			-- \
+				$(TEST_BIN) @@
+
+analyze_qemu: $(TEST_BIN)
+	make -C ..
+	cd .. && \
+		./afl-analyze \
+			-Q \
+			-i $(TEST_DATA_DIR)basn0g01.png \
+			-- \
+				$(TEST_BIN) @@
+
+analyze_frida: $(TEST_BIN)
+	make -C ..
+	cd .. && \
+		./afl-analyze \
+			-O \
+			-i $(TEST_DATA_DIR)basn0g01.png \
+			-- \
+				$(TEST_BIN) @@
\ No newline at end of file
diff --git a/frida_mode/README.md b/frida_mode/README.md
new file mode 100644
index 00000000..c5436e8b
--- /dev/null
+++ b/frida_mode/README.md
@@ -0,0 +1,48 @@
+# FRIDA MODE
+The purpose of FRIDA mode is to provide an alternative binary only fuzzer for AFL
+just like that provided by QEMU mode. The intention is to provide a very similar
+user experience, right down to the options provided through environment variables.
+
+Additionally, the intention is to be able to make a direct performance comparison
+between the two approaches. Hopefully, we should also be able to leverage the same
+approaches for adding features which QEMU uses, possibly even sharing code.
+
+## Limitations
+The current focus is on x64 support for Intel. Although parts may be architecturally
+dependent, the approach itself should remain architecture agnostic.
+
+## Usage
+FRIDA mode requires some small modifications to the afl-fuzz and similar tools in
+AFLplusplus. The intention is that it behaves identically to QEMU, but uses the 'O'
+switch rather than 'Q'.
+
+## Design
+AFL Frida works by means of a shared library injected into a binary program using
+LD_PRELOAD, similar to the way which other fuzzing features are injected into targets.
+
+## Testing
+Alongside the FRIDA mode, we also include a test program for fuzzing. This test
+program is built using the libpng benchmark from fuzz-bench and integrating the
+StandaloneFuzzTargetMain from the llvm project. This is built and linked without
+any special modifications to suit FRIDA or QEMU. However, at present we don't have
+a representative corpus.
+
+## Getting Started
+To build everything run `make`.
+
+To run the benchmark sample with qemu run `make test_qemu`.
+To run the benchmark sample with frida run `make test_frida`.
+
+# Configuration options
+* `AFL_FRIDA_DEBUG_MAPS` - See `AFL_QEMU_DEBUG_MAPS`
+* `AFL_FRIDA_EXCLUDE_RANGES` - See `AFL_QEMU_EXCLUDE_RANGES`
+* `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_NO_PREFETCH` - Disable prefetching. By default the child will report instrumented blocks back to the parent so that it can also instrument them and they be inherited by the next child on fork.
+* `AFL_FRIDA_INST_RANGES` - See `AFL_QEMU_INST_RANGES`
+* `AFL_FRIDA_INST_STRICT` - Under certain conditions, Stalker may encroach into excluded regions and generate both instrumented blocks and coverage data (e.g. indirect calls on x86). The excluded block is generally honoured as soon as another function is called within the excluded region. The overhead of generating, running and instrumenting these few additional blocks is likely to be fairly small, but it may hinder you when checking that the correct number of paths are found for testing purposes or similar. There is a performance penatly for this option during block compilation where we check the block isn't in a list of excluded ranges.
+* `AFL_FRIDA_INST_TRACE` - Generate some logging when running instrumented code. Requires `AFL_FRIDA_INST_NO_OPTIMIZE`.
+
+# TODO
+* Add AARCH64 inline assembly optimization from libFuzz
+* Fix issues running on OSX
+* Identify cause of erroneous additional paths
diff --git a/frida_mode/inc/instrument.h b/frida_mode/inc/instrument.h
new file mode 100644
index 00000000..ff71bed4
--- /dev/null
+++ b/frida_mode/inc/instrument.h
@@ -0,0 +1,7 @@
+#include "frida-gum.h"
+
+void instr_basic_block(GumStalkerIterator *iterator, GumStalkerOutput *output,
+                       gpointer user_data);
+
+void instrument_init();
+
diff --git a/frida_mode/inc/interceptor.h b/frida_mode/inc/interceptor.h
new file mode 100644
index 00000000..5ed3cf49
--- /dev/null
+++ b/frida_mode/inc/interceptor.h
@@ -0,0 +1,4 @@
+#include "frida-gum.h"
+
+void intercept(void *address, gpointer replacement, gpointer user_data);
+
diff --git a/frida_mode/inc/prefetch.h b/frida_mode/inc/prefetch.h
new file mode 100644
index 00000000..b7f25a97
--- /dev/null
+++ b/frida_mode/inc/prefetch.h
@@ -0,0 +1,5 @@
+void prefetch_init();
+void prefetch_start(GumStalker *stalker);
+void prefetch_write(void *addr);
+void prefetch_read(GumStalker *stalker);
+
diff --git a/frida_mode/inc/ranges.h b/frida_mode/inc/ranges.h
new file mode 100644
index 00000000..b9394dbc
--- /dev/null
+++ b/frida_mode/inc/ranges.h
@@ -0,0 +1,6 @@
+#include "frida-gum.h"
+
+void ranges_init(GumStalker *stalker);
+
+gboolean range_is_excluded(gpointer address);
+
diff --git a/frida_mode/src/instrument.c b/frida_mode/src/instrument.c
new file mode 100644
index 00000000..042fdab8
--- /dev/null
+++ b/frida_mode/src/instrument.c
@@ -0,0 +1,265 @@
+#include "frida-gum.h"
+#include "config.h"
+#include "debug.h"
+#include "prefetch.h"
+#include "ranges.h"
+#include "unistd.h"
+
+extern uint8_t *__afl_area_ptr;
+extern u32      __afl_map_size;
+
+uint64_t __thread previous_pc = 0;
+GumAddress current_log_impl = GUM_ADDRESS(0);
+
+static gboolean tracing = false;
+static gboolean optimize = false;
+static gboolean strict = false;
+
+#if defined(__x86_64__)
+static const guint8 afl_log_code[] = {
+
+    0x9c,                                                         /* pushfq */
+    0x50,                                                       /* push rax */
+    0x51,                                                       /* push rcx */
+    0x52,                                                       /* push rdx */
+
+    0x48, 0x8d, 0x05, 0x27,
+    0x00, 0x00, 0x00,                     /* lea rax, sym._afl_area_ptr_ptr */
+    0x48, 0x8b, 0x00,                               /* mov rax, qword [rax] */
+    0x48, 0x8b, 0x00,                               /* mov rax, qword [rax] */
+    0x48, 0x8d, 0x0d, 0x22,
+    0x00, 0x00, 0x00,                       /* lea rcx, sym.previous_pc     */
+    0x48, 0x8b, 0x11,                               /* mov rdx, qword [rcx] */
+    0x48, 0x8b, 0x12,                               /* mov rdx, qword [rdx] */
+    0x48, 0x31, 0xfa,                                       /* xor rdx, rdi */
+    0xfe, 0x04, 0x10,                               /* inc byte [rax + rdx] */
+    0x48, 0xd1, 0xef,                                         /* shr rdi, 1 */
+    0x48, 0x8b, 0x01,                               /* mov rax, qword [rcx] */
+    0x48, 0x89, 0x38,                               /* mov qword [rax], rdi */
+
+    0x5a,                                                        /* pop rdx */
+    0x59,                                                        /* pop rcx */
+    0x58,                                                        /* pop rax */
+    0x9d,                                                          /* popfq */
+
+    0xc3,                                                            /* ret */
+
+    /* Read-only data goes here: */
+    /* uint8_t** afl_area_ptr_ptr */
+    /* uint64_t* afl_prev_loc_ptr */
+
+};
+
+void instrument_coverage_optimize(const cs_insn *   instr,
+                                  GumStalkerOutput *output) {
+
+  guint64 current_pc = instr->address;
+  guint64 area_offset = (current_pc >> 4) ^ (current_pc << 8);
+  area_offset &= MAP_SIZE - 1;
+  GumX86Writer *cw = output->writer.x86;
+
+  if (current_log_impl == 0 ||
+      !gum_x86_writer_can_branch_directly_between(cw->pc, current_log_impl) ||
+      !gum_x86_writer_can_branch_directly_between(cw->pc + 128,
+                                                  current_log_impl)) {
+
+    gconstpointer after_log_impl = cw->code + 1;
+
+    gum_x86_writer_put_jmp_near_label(cw, after_log_impl);
+
+    current_log_impl = cw->pc;
+    gum_x86_writer_put_bytes(cw, afl_log_code, sizeof(afl_log_code));
+
+    uint8_t **afl_area_ptr_ptr = &__afl_area_ptr;
+    uint64_t *afl_prev_loc_ptr = &previous_pc;
+    gum_x86_writer_put_bytes(cw, (const guint8 *)&afl_area_ptr_ptr,
+                             sizeof(afl_area_ptr_ptr));
+    gum_x86_writer_put_bytes(cw, (const guint8 *)&afl_prev_loc_ptr,
+                             sizeof(afl_prev_loc_ptr));
+
+    gum_x86_writer_put_label(cw, after_log_impl);
+
+  }
+
+  gum_x86_writer_put_lea_reg_reg_offset(cw, GUM_REG_RSP, GUM_REG_RSP,
+                                        -GUM_RED_ZONE_SIZE);
+  gum_x86_writer_put_push_reg(cw, GUM_REG_RDI);
+  gum_x86_writer_put_mov_reg_address(cw, GUM_REG_RDI, area_offset);
+  gum_x86_writer_put_call_address(cw, current_log_impl);
+  gum_x86_writer_put_pop_reg(cw, GUM_REG_RDI);
+  gum_x86_writer_put_lea_reg_reg_offset(cw, GUM_REG_RSP, GUM_REG_RSP,
+                                        GUM_RED_ZONE_SIZE);
+
+}
+
+#elif defined(__aarch64__)
+static const guint8 afl_log_code[] = {
+
+    // __afl_area_ptr[current_pc ^ previous_pc]++;
+    // previous_pc = current_pc >> 1;
+    0xE1, 0x0B, 0xBF, 0xA9,  // stp x1, x2, [sp, -0x10]!
+    0xE3, 0x13, 0xBF, 0xA9,  // stp x3, x4, [sp, -0x10]!
+
+    // x0 = current_pc
+    0xc1, 0x01, 0x00, 0x58,  // ldr x1, #0x38, =&__afl_area_ptr
+    0x21, 0x00, 0x40, 0xf9,  // ldr x1, [x1] (=__afl_area_ptr)
+
+    0xc2, 0x01, 0x00, 0x58,  // ldr x2, #0x38, =&previous_pc
+    0x42, 0x00, 0x40, 0xf9,  // ldr x2, [x2] (=previous_pc)
+
+    // __afl_area_ptr[current_pc ^ previous_pc]++;
+    0x42, 0x00, 0x00, 0xca,  // eor x2, x2, x0
+    0x23, 0x68, 0x62, 0xf8,  // ldr x3, [x1, x2]
+    0x63, 0x04, 0x00, 0x91,  // add x3, x3, #1
+    0x23, 0x68, 0x22, 0xf8,  // str x3, [x1, x2]
+
+    // previous_pc = current_pc >> 1;
+    0xe0, 0x07, 0x40, 0x8b,  // add x0, xzr, x0, LSR #1
+    0xe2, 0x00, 0x00, 0x58,  // ldr x2, #0x1c, =&previous_pc
+    0x40, 0x00, 0x00, 0xf9,  // str x0, [x2]
+
+    0xE3, 0x13, 0xc1, 0xA8,  // ldp x3, x4, [sp], #0x10
+    0xE1, 0x0B, 0xc1, 0xA8,  // ldp x1, x2, [sp], #0x10
+    0xC0, 0x03, 0x5F, 0xD6,  // ret
+
+    // &afl_area_ptr_ptr
+    // &afl_prev_loc_ptr
+
+};
+
+void instrument_coverage_optimize(const cs_insn *   instr,
+                                  GumStalkerOutput *output) {
+
+  guint64 current_pc = instr->address;
+  guint64 area_offset = (current_pc >> 4) ^ (current_pc << 8);
+  area_offset &= MAP_SIZE - 1;
+  GumArm64Writer *cw = output->writer.arm64;
+
+  if (current_log_impl == 0 ||
+      !gum_arm64_writer_can_branch_directly_between(cw, cw->pc,
+                                                    current_log_impl) ||
+      !gum_arm64_writer_can_branch_directly_between(cw, cw->pc + 128,
+                                                    current_log_impl)) {
+
+    gconstpointer after_log_impl = cw->code + 1;
+
+    gum_arm64_writer_put_b_label(cw, after_log_impl);
+
+    current_log_impl = cw->pc;
+    gum_arm64_writer_put_bytes(cw, afl_log_code, sizeof(afl_log_code));
+
+    uint8_t **afl_area_ptr_ptr = &__afl_area_ptr;
+    uint64_t *afl_prev_loc_ptr = &previous_pc;
+    gum_arm64_writer_put_bytes(cw, (const guint8 *)&afl_area_ptr_ptr,
+                               sizeof(afl_area_ptr_ptr));
+    gum_arm64_writer_put_bytes(cw, (const guint8 *)&afl_prev_loc_ptr,
+                               sizeof(afl_prev_loc_ptr));
+
+    gum_arm64_writer_put_label(cw, after_log_impl);
+
+  }
+
+  gum_arm64_writer_put_stp_reg_reg_reg_offset(
+      cw, ARM64_REG_LR, ARM64_REG_X0, ARM64_REG_SP, -(16 + GUM_RED_ZONE_SIZE),
+      GUM_INDEX_PRE_ADJUST);
+  gum_arm64_writer_put_ldr_reg_u64(cw, ARM64_REG_X0, area_offset);
+  gum_arm64_writer_put_bl_imm(cw, current_log_impl);
+  gum_arm64_writer_put_ldp_reg_reg_reg_offset(
+      cw, ARM64_REG_LR, ARM64_REG_X0, ARM64_REG_SP, 16 + GUM_RED_ZONE_SIZE,
+      GUM_INDEX_POST_ADJUST);
+
+}
+
+#endif
+
+static void on_basic_block(GumCpuContext *context, gpointer user_data) {
+
+  /* Avoid stack operations in potentially performance critical code */
+  static char buffer[200];
+  int         len;
+  guint64     current_pc = (guint64)user_data;
+  if (tracing) {
+
+    /* Avoid any functions which may cause an allocation since the target app
+     * may already be running inside malloc and it isn't designed to be
+     * re-entrant on a single thread */
+    len = snprintf(buffer, sizeof(buffer),
+                   "current_pc: 0x%016" G_GINT64_MODIFIER
+                   "x, previous_pc: 0x%016" G_GINT64_MODIFIER "x\n",
+                   current_pc, previous_pc);
+
+    write(STDOUT_FILENO, buffer, len + 1);
+
+  }
+
+  current_pc = (current_pc >> 4) ^ (current_pc << 8);
+  current_pc &= MAP_SIZE - 1;
+
+  __afl_area_ptr[current_pc ^ previous_pc]++;
+  previous_pc = current_pc >> 1;
+
+}
+
+void instr_basic_block(GumStalkerIterator *iterator, GumStalkerOutput *output,
+                       gpointer user_data) {
+
+  const cs_insn *instr;
+  gboolean       begin = TRUE;
+  while (gum_stalker_iterator_next(iterator, &instr)) {
+
+    if (begin) {
+
+      prefetch_write((void *)instr->address);
+      if (!strict || !range_is_excluded((void *)instr->address)) {
+
+        if (optimize) {
+
+          instrument_coverage_optimize(instr, output);
+
+        } else {
+
+          gum_stalker_iterator_put_callout(iterator, on_basic_block,
+                                           (gpointer)instr->address, NULL);
+
+        }
+
+      }
+
+      begin = FALSE;
+
+    }
+
+    gum_stalker_iterator_keep(iterator);
+
+  }
+
+}
+
+void instrument_init() {
+
+  optimize = (getenv("AFL_FRIDA_INST_NO_OPTIMIZE") == NULL);
+  tracing = (getenv("AFL_FRIDA_INST_TRACE") != NULL);
+  strict = (getenv("AFL_FRIDA_INST_STRICT") != NULL);
+
+#if !defined(__x86_64__) && !defined(__aarch64__)
+  optimize = false;
+#endif
+
+  OKF("Instrumentation - optimize [%c]", optimize ? 'X' : ' ');
+  OKF("Instrumentation - tracing [%c]", tracing ? 'X' : ' ');
+  OKF("Instrumentation - strict [%c]", strict ? 'X' : ' ');
+
+  if (tracing && optimize) {
+
+    FATAL("AFL_FRIDA_INST_OPTIMIZE and AFL_FRIDA_INST_TRACE are incompatible");
+
+  }
+
+  if (__afl_map_size != 0x10000) {
+
+    FATAL("Bad map size: 0x%08x", __afl_map_size);
+
+  }
+
+}
+
diff --git a/frida_mode/src/interceptor.c b/frida_mode/src/interceptor.c
new file mode 100644
index 00000000..ba05a80a
--- /dev/null
+++ b/frida_mode/src/interceptor.c
@@ -0,0 +1,16 @@
+#include "frida-gum.h"
+#include "debug.h"
+
+#include "interceptor.h"
+
+void intercept(void *address, gpointer replacement, gpointer user_data) {
+
+  GumInterceptor *interceptor = gum_interceptor_obtain();
+  gum_interceptor_begin_transaction(interceptor);
+  GumReplaceReturn ret =
+      gum_interceptor_replace(interceptor, address, replacement, user_data);
+  if (ret != GUM_ATTACH_OK) { FATAL("gum_interceptor_attach: %d", ret); }
+  gum_interceptor_end_transaction(interceptor);
+
+}
+
diff --git a/frida_mode/src/main.c b/frida_mode/src/main.c
new file mode 100644
index 00000000..444c9583
--- /dev/null
+++ b/frida_mode/src/main.c
@@ -0,0 +1,149 @@
+#include <unistd.h>
+#include <sys/types.h>
+
+#ifdef __APPLE__
+  #include <mach/mach.h>
+  #include <mach-o/dyld_images.h>
+#else
+  #include <sys/wait.h>
+  #include <sys/personality.h>
+#endif
+
+#include "frida-gum.h"
+#include "config.h"
+#include "debug.h"
+
+#include "interceptor.h"
+#include "instrument.h"
+#include "prefetch.h"
+#include "ranges.h"
+
+#ifdef __APPLE__
+extern mach_port_t mach_task_self();
+extern GumAddress  gum_darwin_find_entrypoint(mach_port_t task);
+#else
+extern int __libc_start_main(int *(main)(int, char **, char **), int argc,
+                             char **ubp_av, void (*init)(void),
+                             void (*fini)(void), void (*rtld_fini)(void),
+                             void(*stack_end));
+#endif
+
+typedef int *(*main_fn_t)(int argc, char **argv, char **envp);
+
+static main_fn_t      main_fn = NULL;
+static GumStalker *   stalker = NULL;
+static GumMemoryRange code_range = {0};
+
+extern void              __afl_manual_init();
+extern __thread uint64_t previous_pc;
+
+static int on_fork() {
+
+  prefetch_read(stalker);
+  return fork();
+
+}
+
+#ifdef __APPLE__
+static void on_main_os(int argc, char **argv, char **envp) {
+
+}
+
+#else
+static void on_main_os(int argc, char **argv, char **envp) {
+
+  /* Personality doesn't affect the current process, it only takes effect on
+   * evec */
+  int persona = personality(ADDR_NO_RANDOMIZE);
+  if ((persona & ADDR_NO_RANDOMIZE) == 0) { execvpe(argv[0], argv, envp); }
+
+  GumInterceptor *interceptor = gum_interceptor_obtain();
+
+  gum_interceptor_begin_transaction(interceptor);
+  gum_interceptor_revert(interceptor, __libc_start_main);
+  gum_interceptor_end_transaction(interceptor);
+  gum_interceptor_flush(interceptor);
+
+}
+
+#endif
+
+static int *on_main(int argc, char **argv, char **envp) {
+
+  on_main_os(argc, argv, envp);
+
+  stalker = gum_stalker_new();
+  if (stalker == NULL) { FATAL("Failed to initialize stalker"); }
+
+  gum_stalker_set_trust_threshold(stalker, 0);
+
+  GumStalkerTransformer *transformer =
+      gum_stalker_transformer_make_from_callback(instr_basic_block, NULL, NULL);
+
+  instrument_init();
+  prefetch_init();
+  ranges_init(stalker);
+
+  intercept(fork, on_fork, stalker);
+
+  gum_stalker_follow_me(stalker, transformer, NULL);
+  gum_stalker_deactivate(stalker);
+
+  __afl_manual_init();
+
+  /* Child here */
+  previous_pc = 0;
+  prefetch_start(stalker);
+  main_fn(argc, argv, envp);
+  _exit(0);
+
+}
+
+#ifdef __APPLE__
+static void intercept_main() {
+
+  mach_port_t task = mach_task_self();
+  OKF("Task Id: %u", task);
+  GumAddress entry = gum_darwin_find_entrypoint(task);
+  OKF("Entry Point: 0x%016" G_GINT64_MODIFIER "x", entry);
+  void *main = GSIZE_TO_POINTER(entry);
+  main_fn = main;
+  intercept(main, on_main, NULL);
+
+}
+
+#else
+static int on_libc_start_main(int *(main)(int, char **, char **), int argc,
+                              char **ubp_av, void (*init)(void),
+                              void (*fini)(void), void (*rtld_fini)(void),
+                              void(*stack_end)) {
+
+  main_fn = main;
+  intercept(main, on_main, NULL);
+  return __libc_start_main(main, argc, ubp_av, init, fini, rtld_fini,
+                           stack_end);
+
+}
+
+static void intercept_main() {
+
+  intercept(__libc_start_main, on_libc_start_main, NULL);
+
+}
+
+#endif
+
+__attribute__((constructor)) static void init() {
+
+  gum_init_embedded();
+  if (!gum_stalker_is_supported()) {
+
+    gum_deinit_embedded();
+    FATAL("Failed to initialize embedded");
+
+  }
+
+  intercept_main();
+
+}
+
diff --git a/frida_mode/src/prefetch.c b/frida_mode/src/prefetch.c
new file mode 100644
index 00000000..64633c1c
--- /dev/null
+++ b/frida_mode/src/prefetch.c
@@ -0,0 +1,121 @@
+#include <errno.h>
+#include <sys/shm.h>
+#include <sys/mman.h>
+
+#include "frida-gum.h"
+#include "prefetch.h"
+#include "debug.h"
+
+#define TRUST 0
+#define PREFETCH_SIZE 65536
+#define PREFETCH_ENTRIES ((PREFETCH_SIZE - sizeof(size_t)) / sizeof(void *))
+
+typedef struct {
+
+  size_t count;
+  void * entry[PREFETCH_ENTRIES];
+
+} prefetch_data_t;
+
+static prefetch_data_t *prefetch_data = NULL;
+
+static int prefetch_shm_id = -1;
+
+/*
+ * We do this from the transformer since we need one anyway for coverage, this
+ * saves the need to use an event sink.
+ */
+void prefetch_write(void *addr) {
+
+  /* Bail if we aren't initialized */
+  if (prefetch_data == NULL) return;
+
+  /*
+   * Our shared memory IPC is large enough for about 1000 entries, we can fine
+   * tune this if we need to. But if we have more new blocks that this in a
+   * single run then we ignore them and we'll pick them up next time.
+   */
+  if (prefetch_data->count >= PREFETCH_ENTRIES) return;
+
+  /*
+   * Write the block address to the SHM IPC and increment the number of entries.
+   */
+
+  prefetch_data->entry[prefetch_data->count] = addr;
+  prefetch_data->count++;
+
+}
+
+/*
+ * Read the IPC region one block at the time and prefetch it
+ */
+void prefetch_read(GumStalker *stalker) {
+
+  if (prefetch_data == NULL) return;
+
+  for (size_t i = 0; i < prefetch_data->count; i++) {
+
+    void *addr = prefetch_data->entry[i];
+    gum_stalker_prefetch(stalker, addr, 1);
+
+  }
+
+  /*
+   * Reset the entry count to indicate we have finished with it and it can be
+   * refilled by the child.
+   */
+  prefetch_data->count = 0;
+
+}
+
+void prefetch_init() {
+
+  g_assert_cmpint(sizeof(prefetch_data_t), ==, PREFETCH_SIZE);
+  gboolean prefetch = (getenv("AFL_FRIDA_INST_NO_PREFETCH") == NULL);
+
+  OKF("Instrumentation - prefetch [%c]", prefetch ? 'X' : ' ');
+
+  if (!prefetch) { return; }
+  /*
+   * Make our shared memory, we can attach before we fork, just like AFL does
+   * with the coverage bitmap region and fork will take care of ensuring both
+   * the parent and child see the same consistent memory region.
+   */
+  prefetch_shm_id =
+      shmget(IPC_PRIVATE, sizeof(prefetch_data_t), IPC_CREAT | IPC_EXCL | 0600);
+  if (prefetch_shm_id < 0) {
+
+    FATAL("prefetch_shm_id < 0 - errno: %d\n", errno);
+
+  }
+
+  prefetch_data = shmat(prefetch_shm_id, NULL, 0);
+  g_assert(prefetch_data != MAP_FAILED);
+
+  /*
+   * Configure the shared memory region to be removed once the process dies.
+   */
+  if (shmctl(prefetch_shm_id, IPC_RMID, NULL) < 0) {
+
+    FATAL("shmctl (IPC_RMID) < 0 - errno: %d\n", errno);
+
+  }
+
+  /* Clear it, not sure it's necessary, just seems like good practice */
+  memset(prefetch_data, '\0', sizeof(prefetch_data_t));
+
+}
+
+__attribute__((noinline)) static void prefetch_activation() {
+
+  asm volatile("");
+
+}
+
+void prefetch_start(GumStalker *stalker) {
+
+  gum_stalker_activate(stalker, prefetch_activation);
+  prefetch_activation();
+
+}
+
diff --git a/frida_mode/src/ranges.c b/frida_mode/src/ranges.c
new file mode 100644
index 00000000..fc14710f
--- /dev/null
+++ b/frida_mode/src/ranges.c
@@ -0,0 +1,395 @@
+// 0x123-0x321
+// module.so
+
+#include "ranges.h"
+#include "debug.h"
+
+#define MAX_RANGES 20
+
+typedef struct {
+
+  gchar *         suffix;
+  GumMemoryRange *range;
+  gboolean        done;
+
+} convert_name_ctx_t;
+
+typedef struct {
+
+  GumStalker *stalker;
+  GArray *    array;
+
+} include_range_ctx_t;
+
+GArray * ranges = NULL;
+gboolean exclude_ranges = false;
+
+static void convert_address_token(gchar *token, GumMemoryRange *range) {
+
+  gchar **tokens;
+  int     token_count;
+  tokens = g_strsplit(token, "-", 2);
+  for (token_count = 0; tokens[token_count] != NULL; token_count++)
+    ;
+
+  if (token_count != 2) {
+
+    FATAL("Invalid range (should have two addresses seperated by a '-'): %s\n",
+          token);
+
+  }
+
+  gchar *from_str = tokens[0];
+  gchar *to_str = tokens[1];
+
+  if (!g_str_has_prefix(from_str, "0x")) {
+
+    FATAL("Invalid range: %s - Start address should have 0x prefix: %s\n",
+          token, from_str);
+
+  }
+
+  if (!g_str_has_prefix(to_str, "0x")) {
+
+    FATAL("Invalid range: %s - End address should have 0x prefix: %s\n", token,
+          to_str);
+
+  }
+
+  from_str = &from_str[2];
+  to_str = &to_str[2];
+
+  for (char *c = from_str; *c != '\0'; c++) {
+
+    if (!g_ascii_isxdigit(*c)) {
+
+      FATAL("Invalid range: %s - Start address not formed of hex digits: %s\n",
+            token, from_str);
+
+    }
+
+  }
+
+  for (char *c = to_str; *c != '\0'; c++) {
+
+    if (!g_ascii_isxdigit(*c)) {
+
+      FATAL("Invalid range: %s - End address not formed of hex digits: %s\n",
+            token, to_str);
+
+    }
+
+  }
+
+  guint64 from = g_ascii_strtoull(from_str, NULL, 16);
+  if (from == 0) {
+
+    FATAL("Invalid range: %s - Start failed hex conversion: %s\n", token,
+          from_str);
+
+  }
+
+  guint64 to = g_ascii_strtoull(to_str, NULL, 16);
+  if (to == 0) {
+
+    FATAL("Invalid range: %s - End failed hex conversion: %s\n", token, to_str);
+
+  }
+
+  if (from >= to) {
+
+    FATAL("Invalid range: %s - Start (0x%016" G_GINT64_MODIFIER
+          "x) must be less than end "
+          "(0x%016" G_GINT64_MODIFIER "x)\n",
+          token, from, to);
+
+  }
+
+  range->base_address = from;
+  range->size = to - from;
+
+  g_strfreev(tokens);
+
+}
+
+static gboolean convert_name_token_for_module(const GumModuleDetails *details,
+                                              gpointer user_data) {
+
+  convert_name_ctx_t *ctx = (convert_name_ctx_t *)user_data;
+  if (details->path == NULL) { return true; };
+
+  if (!g_str_has_suffix(details->path, ctx->suffix)) { return true; };
+
+  OKF("Found module - prefix: %s, 0x%016" G_GINT64_MODIFIER
+      "x-0x%016" G_GINT64_MODIFIER "x %s",
+      ctx->suffix, details->range->base_address,
+      details->range->base_address + details->range->size, details->path);
+
+  *ctx->range = *details->range;
+  ctx->done = true;
+  return false;
+
+}
+
+static void convert_name_token(gchar *token, GumMemoryRange *range) {
+
+  gchar *            suffix = g_strconcat("/", token, NULL);
+  convert_name_ctx_t ctx = {.suffix = suffix, .range = range, .done = false};
+
+  gum_process_enumerate_modules(convert_name_token_for_module, &ctx);
+  if (!ctx.done) { FATAL("Failed to resolve module: %s\n", token); }
+  g_free(suffix);
+
+}
+
+static void convert_token(gchar *token, GumMemoryRange *range) {
+
+  if (g_strrstr(token, "-")) {
+
+    convert_address_token(token, range);
+
+  } else {
+
+    convert_name_token(token, range);
+
+  }
+
+  OKF("Converted token: %s -> 0x%016" G_GINT64_MODIFIER
+      "x-0x%016" G_GINT64_MODIFIER "x\n",
+      token, range->base_address, range->base_address + range->size);
+
+}
+
+static gboolean include_ranges(const GumRangeDetails *details,
+                               gpointer               user_data) {
+
+  include_range_ctx_t *ctx = (include_range_ctx_t *)user_data;
+  GArray *             array = (GArray *)ctx->array;
+  GumAddress           base = details->range->base_address;
+  GumAddress limit = details->range->base_address + details->range->size;
+
+  OKF("Range for inclusion 0x%016" G_GINT64_MODIFIER
+      "x-0x%016" G_GINT64_MODIFIER "x",
+      base, limit);
+
+  for (int i = 0; i < array->len; i++) {
+
+    GumMemoryRange *range = &g_array_index(array, GumMemoryRange, i);
+    GumAddress      range_base = range->base_address;
+    GumAddress      range_limit = range->base_address + range->size;
+
+    /* Before the region */
+    if (range_limit < base) { continue; }
+
+    /* After the region */
+    if (range_base > limit) {
+
+      GumMemoryRange exclude = {.base_address = base, .size = limit - base};
+      OKF("\t Excluding 0x%016" G_GINT64_MODIFIER "x-0x%016" G_GINT64_MODIFIER
+          "x",
+          base, limit);
+      gum_stalker_exclude(ctx->stalker, &exclude);
+      return true;
+
+    }
+
+    /* Overlap the start of the region */
+    if (range_base < base) {
+
+      /* Range contains the region */
+      if (range_limit > limit) {
+
+        return true;
+
+      } else {
+
+        base = range_limit;
+        continue;
+
+      }
+
+      /* Overlap the end of the region */
+
+    } else {
+
+      GumMemoryRange exclude = {.base_address = base,
+                                .size = range_base - base};
+      OKF("\t Excluding 0x%016" G_GINT64_MODIFIER "x-0x%016" G_GINT64_MODIFIER
+          "x",
+          base, range_base);
+      gum_stalker_exclude(ctx->stalker, &exclude);
+      /* Extend past the end of the region */
+      if (range_limit >= limit) {
+
+        return true;
+
+        /* Contained within the region */
+
+      } else {
+
+        base = range_limit;
+        continue;
+
+      }
+
+    }
+
+  }
+
+  GumMemoryRange exclude = {.base_address = base, .size = limit - base};
+  OKF("\t Excluding 0x%016" G_GINT64_MODIFIER "x-0x%016" G_GINT64_MODIFIER "x",
+      base, limit);
+  gum_stalker_exclude(ctx->stalker, &exclude);
+  return true;
+
+}
+
+gint range_sort(gconstpointer a, gconstpointer b) {
+
+  return ((GumMemoryRange *)a)->base_address -
+         ((GumMemoryRange *)b)->base_address;
+
+}
+
+static gboolean print_ranges(const GumRangeDetails *details,
+                             gpointer               user_data) {
+
+  if (details->file == NULL) {
+
+    OKF("MAP - 0x%016" G_GINT64_MODIFIER "x - 0x%016" G_GINT64_MODIFIER "X",
+        details->range->base_address,
+        details->range->base_address + details->range->size);
+
+  } else {
+
+    OKF("MAP - 0x%016" G_GINT64_MODIFIER "x - 0x%016" G_GINT64_MODIFIER
+        "X %s(0x%016" G_GINT64_MODIFIER "x)",
+        details->range->base_address,
+        details->range->base_address + details->range->size,
+        details->file->path, details->file->offset);
+
+  }
+
+  return true;
+
+}
+
+void ranges_init(GumStalker *stalker) {
+
+  char *         showmaps;
+  char *         include;
+  char *         exclude;
+  char *         list;
+  gchar **       tokens;
+  int            token_count;
+  GumMemoryRange range;
+
+  int i;
+
+  showmaps = getenv("AFL_FRIDA_DEBUG_MAPS");
+  include = getenv("AFL_FRIDA_INST_RANGES");
+  exclude = getenv("AFL_FRIDA_EXCLUDE_RANGES");
+
+  if (showmaps) {
+
+    gum_process_enumerate_ranges(GUM_PAGE_NO_ACCESS, print_ranges, NULL);
+
+  }
+
+  if (include != NULL && exclude != NULL) {
+
+    FATAL(
+        "Cannot specifify both AFL_FRIDA_INST_RANGES and "
+        "AFL_FRIDA_EXCLUDE_RANGES");
+
+  }
+
+  if (include == NULL && exclude == NULL) { return; }
+
+  list = include == NULL ? exclude : include;
+  exclude_ranges = include == NULL ? true : false;
+
+  tokens = g_strsplit(list, ",", MAX_RANGES);
+
+  for (token_count = 0; tokens[token_count] != NULL; token_count++)
+    ;
+
+  ranges = g_array_sized_new(false, false, sizeof(GumMemoryRange), token_count);
+
+  for (i = 0; i < token_count; i++) {
+
+    convert_token(tokens[i], &range);
+    g_array_append_val(ranges, range);
+
+  }
+
+  g_array_sort(ranges, range_sort);
+
+  /* Check for overlaps */
+  for (i = 1; i < token_count; i++) {
+
+    GumMemoryRange *prev = &g_array_index(ranges, GumMemoryRange, i - 1);
+    GumMemoryRange *curr = &g_array_index(ranges, GumMemoryRange, i);
+    GumAddress      prev_limit = prev->base_address + prev->size;
+    GumAddress      curr_limit = curr->base_address + curr->size;
+    if (prev_limit > curr->base_address) {
+
+      FATAL("OVerlapping ranges 0x%016" G_GINT64_MODIFIER
+            "x-0x%016" G_GINT64_MODIFIER "x 0x%016" G_GINT64_MODIFIER
+            "x-0x%016" G_GINT64_MODIFIER "x",
+            prev->base_address, prev_limit, curr->base_address, curr_limit);
+
+    }
+
+  }
+
+  for (i = 0; i < token_count; i++) {
+
+    GumMemoryRange *curr = &g_array_index(ranges, GumMemoryRange, i);
+    GumAddress      curr_limit = curr->base_address + curr->size;
+    OKF("Range %3d - 0x%016" G_GINT64_MODIFIER "x-0x%016" G_GINT64_MODIFIER "x",
+        i, curr->base_address, curr_limit);
+
+  }
+
+  if (include == NULL) {
+
+    for (i = 0; i < token_count; i++) {
+
+      gum_stalker_exclude(stalker, &g_array_index(ranges, GumMemoryRange, i));
+
+    }
+
+  } else {
+
+    include_range_ctx_t ctx = {.stalker = stalker, .array = ranges};
+    gum_process_enumerate_ranges(GUM_PAGE_NO_ACCESS, include_ranges, &ctx);
+
+  }
+
+  g_strfreev(tokens);
+
+}
+
+gboolean range_is_excluded(gpointer address) {
+
+  int        i;
+  GumAddress test = GUM_ADDRESS(address);
+
+  if (ranges == NULL) { return false; }
+
+  for (i = 0; i < ranges->len; i++) {
+
+    GumMemoryRange *curr = &g_array_index(ranges, GumMemoryRange, i);
+    GumAddress      curr_limit = curr->base_address + curr->size;
+
+    if (test < curr->base_address) { return !exclude_ranges; }
+
+    if (test < curr_limit) { return exclude_ranges; }
+
+  }
+
+  return !exclude_ranges;
+
+}
+
diff --git a/frida_mode/test/testinstr.c b/frida_mode/test/testinstr.c
new file mode 100644
index 00000000..2c3d5144
--- /dev/null
+++ b/frida_mode/test/testinstr.c
@@ -0,0 +1,105 @@
+/*
+   american fuzzy lop++ - a trivial program to test the build
+   --------------------------------------------------------
+   Originally written by Michal Zalewski
+   Copyright 2014 Google Inc. All rights reserved.
+   Copyright 2019-2020 AFLplusplus Project. All rights reserved.
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at:
+     http://www.apache.org/licenses/LICENSE-2.0
+ */
+
+#include <fcntl.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#ifdef __APPLE__
+  #define TESTINSTR_SECTION
+#else
+  #define TESTINSTR_SECTION __attribute__((section(".testinstr")))
+#endif
+
+TESTINSTR_SECTION void testinstr(char *buf, int len) {
+
+  if (len < 1) return;
+  buf[len] = 0;
+
+  // we support three input cases
+  if (buf[0] == '0')
+    printf("Looks like a zero to me!\n");
+  else if (buf[0] == '1')
+    printf("Pretty sure that is a one!\n");
+  else
+    printf("Neither one or zero? How quaint!\n");
+
+}
+
+int main(int argc, char **argv) {
+
+  char * file;
+  int    fd = -1;
+  off_t  len;
+  char * buf = NULL;
+  size_t n_read;
+  int    result = -1;
+
+  if (argc != 2) { return 1; }
+
+  do {
+
+    file = argv[1];
+
+    dprintf(STDERR_FILENO, "Running: %s\n", file);
+
+    fd = open(file, O_RDONLY);
+    if (fd < 0) {
+
+      perror("open");
+      break;
+
+    }
+
+    len = lseek(fd, 0, SEEK_END);
+    if (len < 0) {
+
+      perror("lseek (SEEK_END)");
+      break;
+
+    }
+
+    if (lseek(fd, 0, SEEK_SET) != 0) {
+
+      perror("lseek (SEEK_SET)");
+      break;
+
+    }
+
+    buf = malloc(len);
+    n_read = read(fd, buf, len);
+    if (n_read != len) {
+
+      perror("read");
+      break;
+
+    }
+
+    dprintf(STDERR_FILENO, "Running:    %s: (%zd bytes)\n", file, n_read);
+
+    testinstr(buf, len);
+    dprintf(STDERR_FILENO, "Done:    %s: (%zd bytes)\n", file, n_read);
+
+    result = 0;
+
+  } while (false);
+
+  if (buf != NULL) { free(buf); }
+
+  if (fd != -1) { close(fd); }
+
+  return result;
+
+}
+
diff --git a/frida_mode/test/testinstr.py b/frida_mode/test/testinstr.py
new file mode 100755
index 00000000..8f5fe886
--- /dev/null
+++ b/frida_mode/test/testinstr.py
@@ -0,0 +1,32 @@
+#!/usr/bin/python3
+import argparse
+from elftools.elf.elffile import ELFFile
+
+def process_file(file, section, base):
+    with open(file, 'rb') as f:
+        for sect in ELFFile(f).iter_sections():
+            if (sect.name == section):
+                start = base + sect.header['sh_offset']
+                end = start + sect.header['sh_size']
+                print ("0x%016x-0x%016x" % (start, end))
+                return
+
+    print ("Section '%s' not found in '%s'" % (section, file))
+
+def hex_value(x):
+    return int(x, 16)
+
+def main():
+    parser = argparse.ArgumentParser(description='Process some integers.')
+    parser.add_argument('-f', '--file', dest='file', type=str,
+                    help='elf file name', required=True)
+    parser.add_argument('-s', '--section', dest='section', type=str,
+                    help='elf section name', required=True)
+    parser.add_argument('-b', '--base', dest='base', type=hex_value,
+                    help='elf base address', required=True)
+
+    args = parser.parse_args()
+    process_file (args.file, args.section, args.base)
+
+if __name__ == "__main__":
+    main()
\ No newline at end of file
diff --git a/include/envs.h b/include/envs.h
index d7578045..1e383b0c 100644
--- a/include/envs.h
+++ b/include/envs.h
@@ -50,6 +50,13 @@ static char *afl_environment_variables[] = {
     "AFL_EXIT_WHEN_DONE",
     "AFL_FAST_CAL",
     "AFL_FORCE_UI",
+    "AFL_FRIDA_DEBUG_MAPS",
+    "AFL_FRIDA_EXCLUDE_RANGES",
+    "AFL_FRIDA_INST_NO_OPTIMIZE",
+    "AFL_FRIDA_INST_NO_PREFETCH",
+    "AFL_FRIDA_INST_RANGES",
+    "AFL_FRIDA_INST_STRICT",
+    "AFL_FRIDA_INST_TRACE",
     "AFL_FUZZER_ARGS",  // oss-fuzz
     "AFL_GDB",
     "AFL_GCC_ALLOWLIST",
diff --git a/include/forkserver.h b/include/forkserver.h
index ac027f81..4709f6a5 100644
--- a/include/forkserver.h
+++ b/include/forkserver.h
@@ -77,6 +77,8 @@ typedef struct afl_forkserver {
 
   bool qemu_mode;                       /* if running in qemu mode or not   */
 
+  bool frida_mode;                       /* if running in frida mode or not   */
+
   bool use_stdin;                       /* use stdin for sending data       */
 
   bool no_unlink;                       /* do not unlink cur_input          */
diff --git a/src/afl-analyze.c b/src/afl-analyze.c
index e106cd31..6ff119ac 100644
--- a/src/afl-analyze.c
+++ b/src/afl-analyze.c
@@ -83,6 +83,7 @@ static volatile u8 stop_soon,          /* Ctrl-C pressed?                   */
     child_timed_out;                   /* Child timed out?                  */
 
 static u8 *target_path;
+static u8  frida_mode;
 static u8  qemu_mode;
 static u32 map_size = MAP_SIZE;
 
@@ -717,9 +718,11 @@ static void handle_stop_sig(int sig) {
 
 /* Do basic preparations - persistent fds, filenames, etc. */
 
-static void set_up_environment(void) {
+static void set_up_environment(char **argv) {
 
-  u8 *x;
+  u8 *  x;
+  char *afl_preload;
+  char *frida_afl_preload = NULL;
 
   dev_null_fd = open("/dev/null", O_RDWR);
   if (dev_null_fd < 0) { PFATAL("Unable to open /dev/null"); }
@@ -824,6 +827,26 @@ static void set_up_environment(void) {
 
       /* afl-qemu-trace takes care of converting AFL_PRELOAD. */
 
+    } else if (frida_mode) {
+
+      afl_preload = getenv("AFL_PRELOAD");
+      u8 *frida_binary = find_afl_binary(argv[0], "afl-frida-trace.so");
+      if (afl_preload) {
+
+        frida_afl_preload = alloc_printf("%s:%s", afl_preload, frida_binary);
+
+      } else {
+
+        frida_afl_preload = alloc_printf("%s", frida_binary);
+
+      }
+
+      ck_free(frida_binary);
+      OKF("Frida Mode setting LD_PRELOAD %s", frida_afl_preload);
+
+      setenv("LD_PRELOAD", frida_afl_preload, 1);
+      setenv("DYLD_INSERT_LIBRARIES", frida_afl_preload, 1);
+
     } else {
 
       setenv("LD_PRELOAD", getenv("AFL_PRELOAD"), 1);
@@ -831,8 +854,18 @@ static void set_up_environment(void) {
 
     }
 
+  } else if (frida_mode) {
+
+    u8 *frida_binary = find_afl_binary(argv[0], "afl-frida-trace.so");
+    setenv("LD_PRELOAD", frida_binary, 1);
+    setenv("DYLD_INSERT_LIBRARIES", frida_binary, 1);
+    OKF("Frida Mode setting LD_PRELOAD %s", frida_binary);
+    ck_free(frida_binary);
+
   }
 
+  if (frida_afl_preload) { ck_free(frida_afl_preload); }
+
 }
 
 /* Setup signal handlers, duh. */
@@ -872,6 +905,7 @@ static void usage(u8 *argv0) {
       "  -f file       - input file read by the tested program (stdin)\n"
       "  -t msec       - timeout for each run (%u ms)\n"
       "  -m megs       - memory limit for child process (%u MB)\n"
+      "  -O            - use binary-only instrumentation (FRIDA mode)\n"
       "  -Q            - use binary-only instrumentation (QEMU mode)\n"
       "  -U            - use unicorn-based instrumentation (Unicorn mode)\n"
       "  -W            - use qemu-based instrumentation with Wine (Wine "
@@ -914,7 +948,7 @@ int main(int argc, char **argv_orig, char **envp) {
 
   SAYF(cCYA "afl-analyze" VERSION cRST " by Michal Zalewski\n");
 
-  while ((opt = getopt(argc, argv, "+i:f:m:t:eQUWh")) > 0) {
+  while ((opt = getopt(argc, argv, "+i:f:m:t:eOQUWh")) > 0) {
 
     switch (opt) {
 
@@ -1008,6 +1042,14 @@ int main(int argc, char **argv_orig, char **envp) {
 
         break;
 
+      case 'O':                                               /* FRIDA mode */
+
+        if (frida_mode) { FATAL("Multiple -O options not supported"); }
+
+        frida_mode = 1;
+
+        break;
+
       case 'Q':
 
         if (qemu_mode) { FATAL("Multiple -Q options not supported"); }
@@ -1062,7 +1104,7 @@ int main(int argc, char **argv_orig, char **envp) {
   atexit(at_exit_handler);
   setup_signal_handlers();
 
-  set_up_environment();
+  set_up_environment(argv);
 
   target_path = find_binary(argv[optind]);
   detect_file_args(argv + optind, prog_in, &use_stdin);
diff --git a/src/afl-fuzz-init.c b/src/afl-fuzz-init.c
index 70a49a6b..cb0190a0 100644
--- a/src/afl-fuzz-init.c
+++ b/src/afl-fuzz-init.c
@@ -2692,7 +2692,7 @@ void check_binary(afl_state_t *afl, u8 *fname) {
 
 #endif                                                       /* ^!__APPLE__ */
 
-  if (!afl->fsrv.qemu_mode && !afl->unicorn_mode &&
+  if (!afl->fsrv.qemu_mode && !afl->fsrv.frida_mode && !afl->unicorn_mode &&
       !afl->non_instrumented_mode &&
       !memmem(f_data, f_len, SHM_ENV_VAR, strlen(SHM_ENV_VAR) + 1)) {
 
@@ -2720,7 +2720,7 @@ void check_binary(afl_state_t *afl, u8 *fname) {
 
   }
 
-  if ((afl->fsrv.qemu_mode) &&
+  if ((afl->fsrv.qemu_mode || afl->fsrv.frida_mode) &&
       memmem(f_data, f_len, SHM_ENV_VAR, strlen(SHM_ENV_VAR) + 1)) {
 
     SAYF("\n" cLRD "[-] " cRST
@@ -2757,7 +2757,8 @@ void check_binary(afl_state_t *afl, u8 *fname) {
 
   }
 
-  if (memmem(f_data, f_len, DEFER_SIG, strlen(DEFER_SIG) + 1)) {
+  if (afl->fsrv.frida_mode ||
+      memmem(f_data, f_len, DEFER_SIG, strlen(DEFER_SIG) + 1)) {
 
     OKF(cPIN "Deferred forkserver binary detected.");
     setenv(DEFER_ENV_VAR, "1", 1);
diff --git a/src/afl-fuzz.c b/src/afl-fuzz.c
index d70ffd31..34e9d420 100644
--- a/src/afl-fuzz.c
+++ b/src/afl-fuzz.c
@@ -109,6 +109,7 @@ static void usage(u8 *argv0, int more_help) {
       "maximum.\n"
       "  -m megs       - memory limit for child process (%u MB, 0 = no limit "
       "[default])\n"
+      "  -O            - use binary-only instrumentation (FRIDA mode)\n"
       "  -Q            - use binary-only instrumentation (QEMU mode)\n"
       "  -U            - use unicorn-based instrumentation (Unicorn mode)\n"
       "  -W            - use qemu-based instrumentation with Wine (Wine "
@@ -320,6 +321,8 @@ int main(int argc, char **argv_orig, char **envp) {
   u8 *extras_dir[4];
   u8  mem_limit_given = 0, exit_1 = 0, debug = 0,
      extras_dir_cnt = 0 /*, have_p = 0*/;
+  char * afl_preload;
+  char * frida_afl_preload = NULL;
   char **use_argv;
 
   struct timeval  tv;
@@ -363,7 +366,7 @@ int main(int argc, char **argv_orig, char **envp) {
 
   while ((opt = getopt(
               argc, argv,
-              "+b:B:c:CdDe:E:hi:I:f:F:l:L:m:M:nNo:p:RQs:S:t:T:UV:Wx:Z")) > 0) {
+              "+b:B:c:CdDe:E:hi:I:f:F:l:L:m:M:nNOo:p:RQs:S:t:T:UV:Wx:Z")) > 0) {
 
     switch (opt) {
 
@@ -755,6 +758,18 @@ int main(int argc, char **argv_orig, char **envp) {
         afl->use_banner = optarg;
         break;
 
+      case 'O':                                               /* FRIDA mode */
+
+        if (afl->fsrv.frida_mode) {
+
+          FATAL("Multiple -O options not supported");
+
+        }
+
+        afl->fsrv.frida_mode = 1;
+
+        break;
+
       case 'Q':                                                /* QEMU mode */
 
         if (afl->fsrv.qemu_mode) { FATAL("Multiple -Q options not supported"); }
@@ -1085,6 +1100,7 @@ int main(int argc, char **argv_orig, char **envp) {
   if (afl->non_instrumented_mode) {
 
     if (afl->crash_mode) { FATAL("-C and -n are mutually exclusive"); }
+    if (afl->fsrv.frida_mode) { FATAL("-O and -n are mutually exclusive"); }
     if (afl->fsrv.qemu_mode) { FATAL("-Q and -n are mutually exclusive"); }
     if (afl->unicorn_mode) { FATAL("-U and -n are mutually exclusive"); }
 
@@ -1289,6 +1305,26 @@ int main(int argc, char **argv_orig, char **envp) {
 
       /* afl-qemu-trace takes care of converting AFL_PRELOAD. */
 
+    } else if (afl->fsrv.frida_mode) {
+
+      afl_preload = getenv("AFL_PRELOAD");
+      u8 *frida_binary = find_afl_binary(argv[0], "afl-frida-trace.so");
+      if (afl_preload) {
+
+        frida_afl_preload = alloc_printf("%s:%s", afl_preload, frida_binary);
+
+      } else {
+
+        frida_afl_preload = alloc_printf("%s", frida_binary);
+
+      }
+
+      ck_free(frida_binary);
+      OKF("Frida Mode setting LD_PRELOAD %s", frida_afl_preload);
+
+      setenv("LD_PRELOAD", frida_afl_preload, 1);
+      setenv("DYLD_INSERT_LIBRARIES", frida_afl_preload, 1);
+
     } else {
 
       setenv("LD_PRELOAD", getenv("AFL_PRELOAD"), 1);
@@ -1296,6 +1332,14 @@ int main(int argc, char **argv_orig, char **envp) {
 
     }
 
+  } else if (afl->fsrv.frida_mode) {
+
+    u8 *frida_binary = find_afl_binary(argv[0], "afl-frida-trace.so");
+    setenv("LD_PRELOAD", frida_binary, 1);
+    setenv("DYLD_INSERT_LIBRARIES", frida_binary, 1);
+    OKF("Frida Mode setting LD_PRELOAD %s", frida_binary);
+    ck_free(frida_binary);
+
   }
 
   if (getenv("AFL_LD_PRELOAD")) {
@@ -1479,7 +1523,8 @@ int main(int argc, char **argv_orig, char **envp) {
 
     }
 
-    if (!afl->fsrv.qemu_mode && !afl->non_instrumented_mode) {
+    if (!afl->fsrv.qemu_mode && !afl->fsrv.frida_mode &&
+        !afl->non_instrumented_mode) {
 
       check_binary(afl, afl->cmplog_binary);
 
@@ -1513,7 +1558,8 @@ int main(int argc, char **argv_orig, char **envp) {
 
   }
 
-  if (afl->non_instrumented_mode || afl->fsrv.qemu_mode || afl->unicorn_mode) {
+  if (afl->non_instrumented_mode || afl->fsrv.qemu_mode ||
+      afl->fsrv.frida_mode || afl->unicorn_mode) {
 
     map_size = afl->fsrv.map_size = MAP_SIZE;
     afl->virgin_bits = ck_realloc(afl->virgin_bits, map_size);
@@ -2074,6 +2120,8 @@ stop_fuzzing:
 
   }
 
+  if (frida_afl_preload) { ck_free(frida_afl_preload); }
+
   fclose(afl->fsrv.plot_file);
   destroy_queue(afl);
   destroy_extras(afl);
diff --git a/src/afl-showmap.c b/src/afl-showmap.c
index 558665a2..aea90b3b 100644
--- a/src/afl-showmap.c
+++ b/src/afl-showmap.c
@@ -555,8 +555,10 @@ static void handle_stop_sig(int sig) {
 
 /* Do basic preparations - persistent fds, filenames, etc. */
 
-static void set_up_environment(afl_forkserver_t *fsrv) {
+static void set_up_environment(afl_forkserver_t *fsrv, char **argv) {
 
+  char *afl_preload;
+  char *frida_afl_preload = NULL;
   setenv("ASAN_OPTIONS",
          "abort_on_error=1:"
          "detect_leaks=0:"
@@ -600,6 +602,26 @@ static void set_up_environment(afl_forkserver_t *fsrv) {
 
       /* afl-qemu-trace takes care of converting AFL_PRELOAD. */
 
+    } else if (fsrv->frida_mode) {
+
+      afl_preload = getenv("AFL_PRELOAD");
+      u8 *frida_binary = find_afl_binary(argv[0], "afl-frida-trace.so");
+      if (afl_preload) {
+
+        frida_afl_preload = alloc_printf("%s:%s", afl_preload, frida_binary);
+
+      } else {
+
+        frida_afl_preload = alloc_printf("%s", frida_binary);
+
+      }
+
+      ck_free(frida_binary);
+      OKF("Frida Mode setting LD_PRELOAD %s", frida_afl_preload);
+
+      setenv("LD_PRELOAD", frida_afl_preload, 1);
+      setenv("DYLD_INSERT_LIBRARIES", frida_afl_preload, 1);
+
     } else {
 
       setenv("LD_PRELOAD", getenv("AFL_PRELOAD"), 1);
@@ -607,8 +629,18 @@ static void set_up_environment(afl_forkserver_t *fsrv) {
 
     }
 
+  } else if (fsrv->frida_mode) {
+
+    u8 *frida_binary = find_afl_binary(argv[0], "afl-frida-trace.so");
+    setenv("LD_PRELOAD", frida_binary, 1);
+    setenv("DYLD_INSERT_LIBRARIES", frida_binary, 1);
+    OKF("Frida Mode setting LD_PRELOAD %s", frida_binary);
+    ck_free(frida_binary);
+
   }
 
+  if (frida_afl_preload) { ck_free(frida_afl_preload); }
+
 }
 
 /* Setup signal handlers, duh. */
@@ -655,6 +687,7 @@ static void usage(u8 *argv0) {
       "Execution control settings:\n"
       "  -t msec       - timeout for each run (none)\n"
       "  -m megs       - memory limit for child process (%u MB)\n"
+      "  -O            - use binary-only instrumentation (FRIDA mode)\n"
       "  -Q            - use binary-only instrumentation (QEMU mode)\n"
       "  -U            - use Unicorn-based instrumentation (Unicorn mode)\n"
       "  -W            - use qemu-based instrumentation with Wine (Wine mode)\n"
@@ -723,7 +756,7 @@ int main(int argc, char **argv_orig, char **envp) {
 
   if (getenv("AFL_QUIET") != NULL) { be_quiet = 1; }
 
-  while ((opt = getopt(argc, argv, "+i:o:f:m:t:A:eqCZQUWbcrsh")) > 0) {
+  while ((opt = getopt(argc, argv, "+i:o:f:m:t:A:eqCZOQUWbcrsh")) > 0) {
 
     switch (opt) {
 
@@ -857,6 +890,14 @@ int main(int argc, char **argv_orig, char **envp) {
         at_file = optarg;
         break;
 
+      case 'O':                                               /* FRIDA mode */
+
+        if (fsrv->frida_mode) { FATAL("Multiple -O options not supported"); }
+
+        fsrv->frida_mode = 1;
+
+        break;
+
       case 'Q':
 
         if (fsrv->qemu_mode) { FATAL("Multiple -Q options not supported"); }
@@ -943,7 +984,7 @@ int main(int argc, char **argv_orig, char **envp) {
   shm.cmplog_mode = 0;
   setup_signal_handlers();
 
-  set_up_environment(fsrv);
+  set_up_environment(fsrv, argv);
 
   fsrv->target_path = find_binary(argv[optind]);
   fsrv->trace_bits = afl_shm_init(&shm, map_size, 0);
diff --git a/src/afl-tmin.c b/src/afl-tmin.c
index fc974262..68e61109 100644
--- a/src/afl-tmin.c
+++ b/src/afl-tmin.c
@@ -640,9 +640,11 @@ static void handle_stop_sig(int sig) {
 
 /* Do basic preparations - persistent fds, filenames, etc. */
 
-static void set_up_environment(afl_forkserver_t *fsrv) {
+static void set_up_environment(afl_forkserver_t *fsrv, char **argv) {
 
-  u8 *x;
+  u8 *  x;
+  char *afl_preload;
+  char *frida_afl_preload = NULL;
 
   fsrv->dev_null_fd = open("/dev/null", O_RDWR);
   if (fsrv->dev_null_fd < 0) { PFATAL("Unable to open /dev/null"); }
@@ -755,6 +757,26 @@ static void set_up_environment(afl_forkserver_t *fsrv) {
 
       /* afl-qemu-trace takes care of converting AFL_PRELOAD. */
 
+    } else if (fsrv->frida_mode) {
+
+      afl_preload = getenv("AFL_PRELOAD");
+      u8 *frida_binary = find_afl_binary(argv[0], "afl-frida-trace.so");
+      if (afl_preload) {
+
+        frida_afl_preload = alloc_printf("%s:%s", afl_preload, frida_binary);
+
+      } else {
+
+        frida_afl_preload = alloc_printf("%s", frida_binary);
+
+      }
+
+      ck_free(frida_binary);
+      OKF("Frida Mode setting LD_PRELOAD %s", frida_afl_preload);
+
+      setenv("LD_PRELOAD", frida_afl_preload, 1);
+      setenv("DYLD_INSERT_LIBRARIES", frida_afl_preload, 1);
+
     } else {
 
       setenv("LD_PRELOAD", getenv("AFL_PRELOAD"), 1);
@@ -762,8 +784,18 @@ static void set_up_environment(afl_forkserver_t *fsrv) {
 
     }
 
+  } else if (fsrv->frida_mode) {
+
+    u8 *frida_binary = find_afl_binary(argv[0], "afl-frida-trace.so");
+    setenv("LD_PRELOAD", frida_binary, 1);
+    setenv("DYLD_INSERT_LIBRARIES", frida_binary, 1);
+    OKF("Frida Mode setting LD_PRELOAD %s", frida_binary);
+    ck_free(frida_binary);
+
   }
 
+  if (frida_afl_preload) { ck_free(frida_afl_preload); }
+
 }
 
 /* Setup signal handlers, duh. */
@@ -804,6 +836,7 @@ static void usage(u8 *argv0) {
       "  -f file       - input file read by the tested program (stdin)\n"
       "  -t msec       - timeout for each run (%u ms)\n"
       "  -m megs       - memory limit for child process (%u MB)\n"
+      "  -O            - use binary-only instrumentation (FRIDA mode)\n"
       "  -Q            - use binary-only instrumentation (QEMU mode)\n"
       "  -U            - use unicorn-based instrumentation (Unicorn mode)\n"
       "  -W            - use qemu-based instrumentation with Wine (Wine "
@@ -859,7 +892,7 @@ int main(int argc, char **argv_orig, char **envp) {
 
   SAYF(cCYA "afl-tmin" VERSION cRST " by Michal Zalewski\n");
 
-  while ((opt = getopt(argc, argv, "+i:o:f:m:t:B:xeQUWHh")) > 0) {
+  while ((opt = getopt(argc, argv, "+i:o:f:m:t:B:xeOQUWHh")) > 0) {
 
     switch (opt) {
 
@@ -971,6 +1004,14 @@ int main(int argc, char **argv_orig, char **envp) {
 
         break;
 
+      case 'O':                                               /* FRIDA mode */
+
+        if (fsrv->frida_mode) { FATAL("Multiple -O options not supported"); }
+
+        fsrv->frida_mode = 1;
+
+        break;
+
       case 'Q':
 
         if (fsrv->qemu_mode) { FATAL("Multiple -Q options not supported"); }
@@ -1054,7 +1095,7 @@ int main(int argc, char **argv_orig, char **envp) {
   atexit(at_exit_handler);
   setup_signal_handlers();
 
-  set_up_environment(fsrv);
+  set_up_environment(fsrv, argv);
 
   fsrv->target_path = find_binary(argv[optind]);
   fsrv->trace_bits = afl_shm_init(&shm, map_size, 0);