about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rwxr-xr-xafl-cmin11
-rwxr-xr-xafl-cmin.bash9
-rw-r--r--frida_mode/.gitignore5
-rw-r--r--frida_mode/Makefile350
-rw-r--r--frida_mode/README.md135
-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.c48
-rw-r--r--src/afl-fuzz-init.c7
-rw-r--r--src/afl-fuzz.c52
-rw-r--r--src/afl-showmap.c45
-rw-r--r--src/afl-tmin.c47
24 files changed, 1803 insertions, 21 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/afl-cmin b/afl-cmin
index a1fc6f21..3f3a7517 100755
--- a/afl-cmin
+++ b/afl-cmin
@@ -106,6 +106,7 @@ function usage() {
 "  -f file       - location read by the fuzzed program (stdin)\n" \
 "  -m megs       - memory limit for child process ("mem_limit" MB)\n" \
 "  -t msec       - run time limit for child process (none)\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" \
 "\n" \
@@ -140,7 +141,7 @@ BEGIN {
   # process options
   Opterr = 1    # default is to diagnose
   Optind = 1    # skip ARGV[0]
-  while ((_go_c = getopt(ARGC, ARGV, "hi:o:f:m:t:eCQU?")) != -1) {
+  while ((_go_c = getopt(ARGC, ARGV, "hi:o:f:m:t:eCOQU?")) != -1) {
     if (_go_c == "i") {
       if (!Optarg) usage()
       if (in_dir) { print "Option "_go_c" is only allowed once" > "/dev/stderr"}
@@ -180,6 +181,12 @@ BEGIN {
       extra_par = extra_par " -e"
       continue
     } else 
+    if (_go_c == "O") {
+      if (frida_mode) { print "Option "_go_c" is only allowed once" > "/dev/stderr"}
+      extra_par = extra_par " -O"
+      frida_mode = 1
+      continue
+    } else 
     if (_go_c == "Q") {
       if (qemu_mode) { print "Option "_go_c" is only allowed once" > "/dev/stderr"}
       extra_par = extra_par " -Q"
@@ -275,7 +282,7 @@ BEGIN {
     target_bin = tnew
   }
 
-  if (!ENVIRON["AFL_SKIP_BIN_CHECK"] && !qemu_mode && !unicorn_mode) {
+  if (!ENVIRON["AFL_SKIP_BIN_CHECK"] && !qemu_mode && !frida_mode && !unicorn_mode) {
     if (0 != system( "grep -q __AFL_SHM_ID "target_bin )) {
       print "[-] Error: binary '"target_bin"' doesn't appear to be instrumented." > "/dev/stderr"
       exit 1
diff --git a/afl-cmin.bash b/afl-cmin.bash
index 5b2c3894..f4bd269d 100755
--- a/afl-cmin.bash
+++ b/afl-cmin.bash
@@ -53,7 +53,7 @@ unset IN_DIR OUT_DIR STDIN_FILE EXTRA_PAR MEM_LIMIT_GIVEN \
 
 export AFL_QUIET=1
 
-while getopts "+i:o:f:m:t:eQUCh" opt; do
+while getopts "+i:o:f:m:t:eOQUCh" opt; do
 
   case "$opt" in 
 
@@ -83,6 +83,10 @@ while getopts "+i:o:f:m:t:eQUCh" opt; do
     "C")
          export AFL_CMIN_CRASHES_ONLY=1
          ;;
+    "O")
+         EXTRA_PAR="$EXTRA_PAR -O"
+         FRIDA_MODE=1
+         ;;         
     "Q")
          EXTRA_PAR="$EXTRA_PAR -Q"
          QEMU_MODE=1
@@ -118,6 +122,7 @@ Execution control settings:
   -f file       - location read by the fuzzed program (stdin)
   -m megs       - memory limit for child process ($MEM_LIMIT MB)
   -t msec       - run time limit for child process (none)
+  -O            - use binary-only instrumentation (FRIDA mode)
   -Q            - use binary-only instrumentation (QEMU mode)
   -U            - use unicorn-based instrumentation (Unicorn mode)
   
@@ -209,7 +214,7 @@ if [ ! -f "$TARGET_BIN" -o ! -x "$TARGET_BIN" ]; then
 
 fi
 
-if [ "$AFL_SKIP_BIN_CHECK" = "" -a "$QEMU_MODE" = "" -a "$UNICORN_MODE" = "" ]; then
+if [ "$AFL_SKIP_BIN_CHECK" = "" -a "$QEMU_MODE" = "" -a "$FRIDA_MODE" = "" -a "$UNICORN_MODE" = "" ]; then
 
   if ! grep -qF "__AFL_SHM_ID" "$TARGET_BIN"; then
     echo "[-] Error: binary '$TARGET_BIN' doesn't appear to be instrumented." 1>&2
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..efae5ebf
--- /dev/null
+++ b/frida_mode/Makefile
@@ -0,0 +1,350 @@
+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) @@
+
+cmin_qemu: $(TEST_BIN)
+	make -C ..
+	cd .. && \
+		./afl-cmin \
+			-Q \
+			-i $(TEST_DATA_DIR) \
+			-o $(QEMU_OUT) \
+			-- \
+				$(TEST_BIN) @@
+
+cmin_frida: $(TEST_BIN)
+	make -C ..
+	cd .. && \
+		./afl-cmin \
+			-O \
+			-i $(TEST_DATA_DIR) \
+			-o $(FRIDA_OUT) \
+			-- \
+				$(TEST_BIN) @@
+
+cmin_bash_qemu: $(TEST_BIN)
+	make -C ..
+	cd .. && \
+		./afl-cmin.bash \
+			-Q \
+			-i $(TEST_DATA_DIR) \
+			-o $(QEMU_OUT) \
+			-- \
+				$(TEST_BIN) @@
+
+cmin_bash_frida: $(TEST_BIN)
+	make -C ..
+	cd .. && \
+		./afl-cmin.bash \
+			-O \
+			-i $(TEST_DATA_DIR) \
+			-o $(FRIDA_OUT) \
+			-- \
+				$(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..bc260e3e
--- /dev/null
+++ b/frida_mode/README.md
@@ -0,0 +1,135 @@
+# 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.
+
+Whilst AFLplusplus already has some support for running on FRIDA [here](https://github.com/AFLplusplus/AFLplusplus/tree/stable/utils/afl_frida)
+this requires the code to be fuzzed to be provided as a shared library, it
+cannot be used to fuzz executables. Additionally, it requires the user to write
+a small harness around their target code of interest, FRIDA mode instead takes a
+different approach to avoid these limitations.
+
+# Current Progress
+As FRIDA mode is new, it is missing a lot of features. Most importantly,
+persistent mode. The design is such that it should be possible to add these
+features in a similar manner to QEMU mode and perhaps leverage some of its
+design and implementation.
+
+  | Feature/Instrumentation  | frida-mode |
+  | -------------------------|:----------:|
+  | NeverZero                |            |
+  | Persistent Mode          |            |
+  | LAF-Intel / CompCov      |            |
+  | CmpLog                   |            |
+  | Selective Instrumentation|     x      |
+  | Non-Colliding Coverage   |            |
+  | Ngram prev_loc Coverage  |            |
+  | Context Coverage         |            |
+  | Auto Dictionary          |            |
+  | Snapshot LKM Support     |            |
+
+# Compatibility
+Currently FRIDA mode supports Linux and macOS targets on both x86/x64
+architecture and aarch64. Later releases may add support for aarch32 and Windows
+targets as well as embedded linux environments.
+
+FRIDA has been used on various embedded targets using both uClibc and musl C
+runtime libraries, so porting should be possible. However, the current build
+system does not support cross compilation.
+
+## Getting Started
+To build everything run `make`.
+
+To run the benchmark sample with qemu run `make png_qemu`.
+To run the benchmark sample with frida run `make png_frida`.
+
+## 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'. Whilst the options 'f', 'F', 's' or 'S' may have
+made more sense for a mode powered by FRIDA Stalker, they were all taken, so
+instead we use 'O' in homage to the [author](https://github.com/oleavr) of
+FRIDA.
+
+Similarly, the intention is to mimic the use of environment variables used by
+QEMU where possible (although replacing `s/QEMU/FRIDA/g`). Accodingly, the
+following options are currently supported.
+
+* `AFL_FRIDA_DEBUG_MAPS` - See `AFL_QEMU_DEBUG_MAPS`
+* `AFL_FRIDA_EXCLUDE_RANGES` - See `AFL_QEMU_EXCLUDE_RANGES`
+* `AFL_FRIDA_INST_RANGES` - See `AFL_QEMU_INST_RANGES`
+
+# Performance
+
+Additionally, the intention is to be able to make a direct performance
+comparison between the two approaches. Accordingly, FRIDA mode includes a test
+target based on the [libpng](https://libpng.sourceforge.io/) benchmark used by
+[fuzzbench](https://google.github.io/fuzzbench/) and integrated with the
+[StandaloneFuzzTargetMain](https://raw.githubusercontent.com/llvm/llvm-project/main/compiler-rt/lib/fuzzer/standalone/StandaloneFuzzTargetMain.c)
+from the llvm project. This is built and linked without any special
+modifications to suit FRIDA or QEMU. We use the test data provided with libpng
+as our corpus.
+
+Whilst not much performance tuning has been completed to date, performance is
+around 30-50% of that of QEMU mode, however, this gap may reduce with the 
+introduction of persistent mode. Performance can be tested by running 
+`make compare`, albeit a longer time measurement may be required for move 
+accurate results. 
+
+Whilst [afl_frida](https://github.com/AFLplusplus/AFLplusplus/tree/stable/utils/afl_frida)
+claims a 5-10x performance increase over QEMU, it has not been possible to
+reproduce these claims. However, the number of executions per second can vary
+dramatically as a result of the randomization of the fuzzer input. Some inputs
+may traverse relatively few paths before being rejected as invalid whilst others
+may be valid inputs or be subject to much more processing before rejection.
+Accordingly, it is recommended that testing be carried out over prolongued
+periods to gather timings which are more than indicative.
+
+# Design
+FRIDA mode is supported by using `LD_PRELOAD` (`DYLD_INSERT_LIBRARIES` on macOS)
+to inject a shared library (`afl-frida-trace.so`) into the target. This shared
+library is built using the [frida-gum](https://github.com/frida/frida-gum)
+devkit from the [FRIDA](https://github.com/frida/frida) project. One of the
+components of frida-gum is [Stalker](https://medium.com/@oleavr/anatomy-of-a-code-tracer-b081aadb0df8),
+this allows the dynamic instrumentation of running code for AARCH32, AARCH64,
+x86 and x64 architectutes. Implementation details can be found
+[here](https://frida.re/docs/stalker/).
+
+Dynamic instrumentation is used to augment the target application with similar
+coverage information to that inserted by `afl-gcc` or `afl-clang`. The shared
+library is also linked to the `compiler-rt` component of AFLplusplus to feedback
+this coverage information to AFL and also provide a fork server. It also makes
+use of the FRIDA [prefetch](https://github.com/frida/frida-gum/blob/56dd9ba3ee9a5511b4b0c629394bf122775f1ab7/gum/gumstalker.h#L115)
+support to feedback instrumented blocks from the child to the parent using a
+shared memory region to avoid the need to regenerate instrumented blocks on each
+fork. 
+
+Whilst FRIDA allows for a normal C function to be used to augment instrumented
+code, to minimize the costs of storing and restoring all of the registers, FRIDA
+mode instead makes use of optimized assembly instead on AARCH64 and x86/64
+targets.
+
+# Advanced configuration options
+* `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_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 and so such encroachment
+is usually of little consequence. This detail may however, 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
+As can be seen from the progress section above, there are a number of features
+which are missing in its currently form. Chief amongst which is persistent mode.
+The intention is to achieve feature parity with QEMU mode in due course.
+Contributions are welcome, but please get in touch to ensure that efforts are
+deconflicted.
\ No newline at end of file
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 37adeff2..f7c8b460 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 808f6bd2..cc759545 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..8e5a1772 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,25 @@ 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);
+
+      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 +853,17 @@ 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);
+    ck_free(frida_binary);
+
   }
 
+  if (frida_afl_preload) { ck_free(frida_afl_preload); }
+
 }
 
 /* Setup signal handlers, duh. */
@@ -872,6 +903,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 +946,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 +1040,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 +1102,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 75f97719..a7edb924 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 "
@@ -329,6 +330,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;
@@ -372,7 +375,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) {
 
@@ -764,6 +767,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"); }
@@ -1118,6 +1133,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"); }
 
@@ -1322,6 +1338,25 @@ 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);
+
+      setenv("LD_PRELOAD", frida_afl_preload, 1);
+      setenv("DYLD_INSERT_LIBRARIES", frida_afl_preload, 1);
+
     } else {
 
       setenv("LD_PRELOAD", getenv("AFL_PRELOAD"), 1);
@@ -1329,6 +1364,13 @@ 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);
+    ck_free(frida_binary);
+
   }
 
   if (getenv("AFL_LD_PRELOAD")) {
@@ -1512,7 +1554,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);
 
@@ -1563,7 +1606,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);
@@ -2124,6 +2168,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 bedf7806..38d03d80 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,25 @@ 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);
+
+      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 +628,17 @@ 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);
+    ck_free(frida_binary);
+
   }
 
+  if (frida_afl_preload) { ck_free(frida_afl_preload); }
+
 }
 
 /* Setup signal handlers, duh. */
@@ -655,6 +685,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 +754,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 +888,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 +982,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..bad5d71b 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,25 @@ 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);
+
+      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 +783,17 @@ 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);
+    ck_free(frida_binary);
+
   }
 
+  if (frida_afl_preload) { ck_free(frida_afl_preload); }
+
 }
 
 /* Setup signal handlers, duh. */
@@ -804,6 +834,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 +890,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 +1002,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 +1093,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);