Improve tracing support to include real addresses and edge ids and also support logging edges only once
MODIFY
frida_mode/GNUmakefile
frida_mode/GNUmakefile
@@ -20,6 +20,7 @@ RT_CFLAGS:=-Wno-unused-parameter \
-Wno-unused-function \
-Wno-unused-result \
-Wno-int-to-pointer-cast \
+ -Wno-pointer-sign \
LDFLAGS+=-shared \
-lpthread \

MODIFY
frida_mode/README.md
frida_mode/README.md
@@ -155,8 +155,10 @@ instrumentation (the default where available). Required to use
* `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_TRACE` - Log to stdout the address of executed blocks
-`AFL_FRIDA_INST_NO_OPTIMIZE`.
+* `AFL_FRIDA_INST_TRACE` - Log to stdout the address of executed blocks,
+requires `AFL_FRIDA_INST_NO_OPTIMIZE`.
+* `AFL_FRIDA_INST_TRACE_UNIQUE` - As per `AFL_FRIDA_INST_TRACE`, but each edge
+is logged only once, requires `AFL_FRIDA_INST_NO_OPTIMIZE`.
* `AFL_FRIDA_OUTPUT_STDOUT` - Redirect the standard output of the target
application to the named file (supersedes the setting of `AFL_DEBUG_CHILD`)
* `AFL_FRIDA_OUTPUT_STDERR` - Redirect the standard error of the target

MODIFY
frida_mode/src/instrument/instrument.c
frida_mode/src/instrument/instrument.c
#include <unistd.h>
+#include <sys/shm.h>
+#include <sys/mman.h>
#include "frida-gum.h"
static gboolean tracing = false;
static gboolean optimize = false;
+static gboolean unique = false;
static GumStalkerTransformer *transformer = NULL;
__thread uint64_t previous_pc = 0;
-__attribute__((hot)) static void on_basic_block(GumCpuContext *context,
- gpointer user_data) {
+static GumAddress previous_rip = 0;
+static u8 * edges_notified = NULL;
- UNUSED_PARAMETER(context);
- /*
- * This function is performance critical as it is called to instrument every
- * basic block. By moving our print buffer to a global, we avoid it affecting
- * the critical path with additional stack adjustments if tracing is not
- * enabled. If tracing is enabled, then we're printing a load of diagnostic
- * information so this overhead is unlikely to be noticeable.
- */
- static char buffer[200];
+static void trace_debug(char *format, ...) {
+
+ va_list ap;
+ char buffer[4096] = {0};
+ int ret;
int len;
- GumAddress current_pc = GUM_ADDRESS(user_data);
- uint8_t * cursor;
- uint64_t value;
- if (unlikely(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);
+ va_start(ap, format);
+ ret = vsnprintf(buffer, sizeof(buffer) - 1, format, ap);
+ va_end(ap);
- IGNORED_RETURN(write(STDOUT_FILENO, buffer, len + 1));
+ if (ret < 0) { return; }
+
+ len = strnlen(buffer, sizeof(buffer));
+
+ IGNORED_RETURN(write(STDOUT_FILENO, buffer, len));
}
- current_pc = (current_pc >> 4) ^ (current_pc << 8);
+__attribute__((hot)) static void on_basic_block(GumCpuContext *context,
+ gpointer user_data) {
+
+ UNUSED_PARAMETER(context);
+
+ GumAddress current_rip = GUM_ADDRESS(user_data);
+ GumAddress current_pc;
+ GumAddress edge;
+ uint8_t * cursor;
+ uint64_t value;
+
+ current_pc = (current_rip >> 4) ^ (current_rip << 8);
current_pc &= MAP_SIZE - 1;
- cursor = &__afl_area_ptr[current_pc ^ previous_pc];
+ edge = current_pc ^ previous_pc;
+
+ cursor = &__afl_area_ptr[edge];
value = *cursor;
if (value == 0xff) {
@@ -71,6 +79,23 @@ __attribute__((hot)) static void on_basic_block(GumCpuContext *context,
*cursor = value;
previous_pc = current_pc >> 1;
+ if (unlikely(tracing)) {
+
+ if (!unique || edges_notified[edge] == 0) {
+
+ trace_debug("TRACE: edge: %10" G_GINT64_MODIFIER
+ "d, current_rip: 0x%016" G_GINT64_MODIFIER
+ "x, previous_rip: 0x%016" G_GINT64_MODIFIER "x\n",
+ edge, current_rip, previous_rip);
+
+ }
+
+ if (unique) { edges_notified[edge] = 1; }
+
+ previous_rip = current_rip;
+
+ }
+
}
static void instr_basic_block(GumStalkerIterator *iterator,
@@ -164,18 +189,28 @@ void instrument_init(void) {
optimize = (getenv("AFL_FRIDA_INST_NO_OPTIMIZE") == NULL);
tracing = (getenv("AFL_FRIDA_INST_TRACE") != NULL);
+ unique = (getenv("AFL_FRIDA_INST_TRACE_UNIQUE") != NULL);
if (!instrument_is_coverage_optimize_supported()) optimize = false;
OKF("Instrumentation - optimize [%c]", optimize ? 'X' : ' ');
OKF("Instrumentation - tracing [%c]", tracing ? 'X' : ' ');
+ OKF("Instrumentation - unique [%c]", unique ? 'X' : ' ');
if (tracing && optimize) {
- FATAL("AFL_FRIDA_INST_OPTIMIZE and AFL_FRIDA_INST_TRACE are incompatible");
+ FATAL("AFL_FRIDA_INST_TRACE requires AFL_FRIDA_INST_NO_OPTIMIZE");
}
+ if (unique && optimize) {
+
+ FATAL("AFL_FRIDA_INST_TRACE_UNIQUE requires AFL_FRIDA_INST_NO_OPTIMIZE");
+
+ }
+
+ if (unique) { tracing = TRUE; }
+
if (__afl_map_size != 0x10000) {
FATAL("Bad map size: 0x%08x", __afl_map_size);
@@ -185,6 +220,28 @@ void instrument_init(void) {
transformer =
gum_stalker_transformer_make_from_callback(instr_basic_block, NULL, NULL);
+ if (unique) {
+
+ int shm_id = shmget(IPC_PRIVATE, MAP_SIZE, IPC_CREAT | IPC_EXCL | 0600);
+ if (shm_id < 0) { FATAL("shm_id < 0 - errno: %d\n", errno); }
+
+ edges_notified = shmat(shm_id, NULL, 0);
+ g_assert(edges_notified != MAP_FAILED);
+
+ /*
+ * Configure the shared memory region to be removed once the process dies.
+ */
+ if (shmctl(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(edges_notified, '\0', MAP_SIZE);
+
+ }
+
instrument_debug_init();
asan_init();
cmplog_init();

ADD
frida_mode/test/unstable/GNUmakefile
+PWD:=$(shell pwd)/
+ROOT:=$(shell realpath $(PWD)../../..)/
+BUILD_DIR:=$(PWD)build/
+UNSTABLE_DATA_DIR:=$(BUILD_DIR)in/
+UNSTABLE_DATA_FILE:=$(UNSTABLE_DATA_DIR)in
+
+UNSTABLE_BIN:=$(BUILD_DIR)unstable
+UNSTABLE_SRC:=$(PWD)unstable.c
+
+QEMU_OUT:=$(BUILD_DIR)qemu-out
+FRIDA_OUT:=$(BUILD_DIR)frida-out
+
+ifndef ARCH
+
+ARCH=$(shell uname -m)
+ifeq "$(ARCH)" "aarch64"
+ ARCH:=arm64
+endif
+
+ifeq "$(ARCH)" "i686"
+ ARCH:=x86
+endif
+endif
+
+AFL_QEMU_PERSISTENT_ADDR=$(shell $(PWD)get_symbol_addr.py -f $(UNSTABLE_BIN) -s run_test -b 0x4000000000)
+
+ifeq "$(ARCH)" "aarch64"
+ AFL_FRIDA_PERSISTENT_ADDR=$(shell $(PWD)get_symbol_addr.py -f $(UNSTABLE_BIN) -s run_test -b 0x0000aaaaaaaaa000)
+endif
+
+ifeq "$(ARCH)" "x86_64"
+ AFL_FRIDA_PERSISTENT_ADDR=$(shell $(PWD)get_symbol_addr.py -f $(UNSTABLE_BIN) -s run_test -b 0x0000555555554000)
+endif
+
+ifeq "$(ARCH)" "x86"
+ AFL_FRIDA_PERSISTENT_ADDR=$(shell $(PWD)get_symbol_addr.py -f $(UNSTABLE_BIN) -s run_test -b 0x56555000)
+endif
+
+.PHONY: all 32 clean qemu frida
+
+all: $(UNSTABLE_BIN)
+ make -C $(ROOT)frida_mode/
+
+32:
+ CFLAGS="-m32" LDFLAGS="-m32" ARCH="x86" make all
+
+$(BUILD_DIR):
+ mkdir -p $@
+
+$(UNSTABLE_DATA_DIR): | $(BUILD_DIR)
+ mkdir -p $@
+
+$(UNSTABLE_DATA_FILE): | $(UNSTABLE_DATA_DIR)
+ echo -n "000" > $@
+
+$(UNSTABLE_BIN): $(UNSTABLE_SRC) | $(BUILD_DIR)
+ $(CC) $(CFLAGS) $(LDFLAGS) -o $@ $<
+
+clean:
+ rm -rf $(BUILD_DIR)
+
+
+qemu: $(UNSTABLE_BIN) $(UNSTABLE_DATA_FILE)
+ AFL_QEMU_PERSISTENT_ADDR=$(AFL_QEMU_PERSISTENT_ADDR) \
+ $(ROOT)afl-fuzz \
+ -D \
+ -Q \
+ -i $(UNSTABLE_DATA_DIR) \
+ -o $(QEMU_OUT) \
+ -- \
+ $(UNSTABLE_BIN) @@
+
+frida: $(UNSTABLE_BIN) $(UNSTABLE_DATA_FILE)
+ AFL_DEBUG=1 \
+ AFL_FRIDA_PERSISTENT_ADDR=$(AFL_FRIDA_PERSISTENT_ADDR) \
+ AFL_FRIDA_INST_TRACE_UNIQUE=1 \
+ AFL_FRIDA_INST_NO_OPTIMIZE=1 \
+ $(ROOT)afl-fuzz \
+ -D \
+ -O \
+ -i $(UNSTABLE_DATA_DIR) \
+ -o $(FRIDA_OUT) \
+ -- \
+ $(UNSTABLE_BIN) @@
+
+debug:
+ gdb \
+ --ex 'set environment LD_PRELOAD=$(ROOT)afl-frida-trace.so' \
+ --ex 'set disassembly-flavor intel' \
+ --args $(UNSTABLE_BIN) $(UNSTABLE_DATA_FILE)

ADD
frida_mode/test/unstable/Makefile
+all:
+ @echo trying to use GNU make...
+ @gmake all || echo please install GNUmake
+
+32:
+ @echo trying to use GNU make...
+ @gmake 32 || echo please install GNUmake
+
+clean:
+ @gmake clean
+
+qemu:
+ @gmake qemu
+
+frida:
+ @gmake frida
+
+debug:
+ @gmake debug

ADD
frida_mode/test/unstable/get_symbol_addr.py
+#!/usr/bin/python3
+import argparse
+from elftools.elf.elffile import ELFFile
+
+def process_file(file, symbol, base):
+ with open(file, 'rb') as f:
+ elf = ELFFile(f)
+ symtab = elf.get_section_by_name('.symtab')
+ mains = symtab.get_symbol_by_name(symbol)
+ if len(mains) != 1:
+ print ("Failed to find main")
+ return 1
+
+ main_addr = mains[0]['st_value']
+ main = base + main_addr
+ print ("0x%016x" % main)
+ return 0
+
+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', '--symbol', dest='symbol', type=str,
+ help='symbol name', required=True)
+ parser.add_argument('-b', '--base', dest='base', type=hex_value,
+ help='elf base address', required=True)
+
+ args = parser.parse_args()
+ return process_file (args.file, args.symbol, args.base)
+
+if __name__ == "__main__":
+ ret = main()
+ exit(ret)

ADD
frida_mode/test/unstable/unstable.c
+/*
+ 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 <assert.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#ifdef __APPLE__
+ #define TESTINSTR_SECTION
+#else
+ #define TESTINSTR_SECTION __attribute__((section(".testinstr")))
+#endif
+
+void LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+
+ if (size < 1) return;
+
+ int r = rand();
+ if ((r % 2) == 0) {
+ printf ("Hooray all even\n");
+ } else {
+ printf ("Hmm that's odd\n");
+ }
+
+ // we support three input cases
+ if (data[0] == '0')
+ printf("Looks like a zero to me!\n");
+ else if (data[0] == '1')
+ printf("Pretty sure that is a one!\n");
+ else
+ printf("Neither one or zero? How quaint!\n");
+
+}
+
+void run_test(char * file) {
+ fprintf(stderr, "Running: %s\n", file);
+ FILE *f = fopen(file, "r");
+ assert(f);
+ fseek(f, 0, SEEK_END);
+ size_t len = ftell(f);
+ fseek(f, 0, SEEK_SET);
+ unsigned char *buf = (unsigned char*)malloc(len);
+ size_t n_read = fread(buf, 1, len, f);
+ fclose(f);
+ assert(n_read == len);
+ LLVMFuzzerTestOneInput(buf, len);
+ free(buf);
+ fprintf(stderr, "Done: %s: (%zd bytes)\n", file, n_read);
+}
+
+int main(int argc, char **argv) {
+ srand(1);
+ fprintf(stderr, "StandaloneFuzzTargetMain: running %d inputs\n", argc - 1);
+ for (int i = 1; i < argc; i++) {
+ run_test(argv[i]);
+ }
+}

MODIFY
include/envs.h
include/envs.h
@@ -60,6 +60,7 @@ static char *afl_environment_variables[] = {
"AFL_FRIDA_INST_NO_PREFETCH",
"AFL_FRIDA_INST_RANGES",
"AFL_FRIDA_INST_TRACE",
+ "AFL_FRIDA_INST_UNSTABLE",
"AFL_FRIDA_OUTPUT_STDOUT",
"AFL_FRIDA_OUTPUT_STDERR",
"AFL_FRIDA_PERSISTENT_ADDR",

Improve tracing support to include real addresses and edge ids and also support logging edges only once (#972)

Co-authored-by: Your Name <you@example.com>