about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--frida_mode/GNUmakefile2
-rw-r--r--frida_mode/README.md3
-rw-r--r--frida_mode/frida.map1
-rw-r--r--frida_mode/include/instrument.h6
-rw-r--r--frida_mode/many-linux/Dockerfile10
-rw-r--r--frida_mode/many-linux/GNUmakefile8
-rw-r--r--frida_mode/many-linux/README.md3
-rw-r--r--frida_mode/src/instrument/instrument.c4
-rw-r--r--frida_mode/src/instrument/instrument_coverage.c375
-rw-r--r--frida_mode/src/js/api.js9
-rw-r--r--frida_mode/src/js/js_api.c7
-rw-r--r--frida_mode/ts/lib/afl.ts14
-rw-r--r--include/envs.h1
13 files changed, 426 insertions, 17 deletions
diff --git a/frida_mode/GNUmakefile b/frida_mode/GNUmakefile
index 6b17982d..58a53823 100644
--- a/frida_mode/GNUmakefile
+++ b/frida_mode/GNUmakefile
@@ -166,7 +166,7 @@ $(GUM_DEVKIT_TARBALL): $(FRIDA_GUM_DEVKIT_COMPRESSED_TARBALL)| $(FRIDA_BUILD_DIR
 	cp -v $< $@
 else
 $(GUM_DEVKIT_TARBALL): | $(FRIDA_BUILD_DIR)
-	wget -O $@ $(GUM_DEVKIT_URL)
+	wget -O $@ $(GUM_DEVKIT_URL) || curl -L -o $@ $(GUM_DEVKIT_URL)
 endif
 
 $(GUM_DEVIT_LIBRARY): $(GUM_DEVKIT_TARBALL)
diff --git a/frida_mode/README.md b/frida_mode/README.md
index 83612210..af79de07 100644
--- a/frida_mode/README.md
+++ b/frida_mode/README.md
@@ -131,7 +131,8 @@ instances run CMPLOG mode and instrumentation of the binary is less frequent
 (only on CMP, SUB and CALL instructions) performance is not quite so critical.
 
 ## Advanced configuration options
-
+* `AFL_FRIDA_INST_COVERAGE_FILE` - File to write DynamoRio format coverage
+information (e.g. to be loaded within IDA lighthouse).
 * `AFL_FRIDA_INST_DEBUG_FILE` - File to write raw assembly of original blocks
 and their instrumented counterparts during block compilation.
 ```
diff --git a/frida_mode/frida.map b/frida_mode/frida.map
index 7223d50e..cf304885 100644
--- a/frida_mode/frida.map
+++ b/frida_mode/frida.map
@@ -10,6 +10,7 @@
     js_api_error;
     js_api_set_debug_maps;
     js_api_set_entrypoint;
+    js_api_set_instrument_coverage_file;
     js_api_set_instrument_debug_file;
     js_api_set_instrument_jit;
     js_api_set_instrument_libraries;
diff --git a/frida_mode/include/instrument.h b/frida_mode/include/instrument.h
index 29f14da9..2e8d6b6d 100644
--- a/frida_mode/include/instrument.h
+++ b/frida_mode/include/instrument.h
@@ -6,6 +6,7 @@
 #include "config.h"
 
 extern char *           instrument_debug_filename;
+extern char *           instrument_coverage_filename;
 extern gboolean         instrument_tracing;
 extern gboolean         instrument_optimize;
 extern gboolean         instrument_unique;
@@ -38,6 +39,11 @@ void     instrument_debug_end(GumStalkerOutput *output);
 void     instrument_flush(GumStalkerOutput *output);
 gpointer instrument_cur(GumStalkerOutput *output);
 
+void instrument_coverage_config(void);
+void instrument_coverage_init(void);
+void instrument_coverage_start(uint64_t address);
+void instrument_coverage_end(uint64_t address);
+
 void instrument_on_fork();
 
 guint64 instrument_get_offset_hash(GumAddress current_rip);
diff --git a/frida_mode/many-linux/Dockerfile b/frida_mode/many-linux/Dockerfile
index 2cd56bc8..170f0757 100644
--- a/frida_mode/many-linux/Dockerfile
+++ b/frida_mode/many-linux/Dockerfile
@@ -6,19 +6,9 @@ RUN chmod +x /bin/realpath
 RUN yum -y install xz
 RUN yum -y install vim-common
 
-WORKDIR /
-RUN git clone https://github.com/AFLplusplus/AFLplusplus.git
-
-WORKDIR /AFLplusplus
-RUN mkdir -p /AFLplusplus/frida_mode/build/frida/
-RUN curl -L -o /AFLplusplus/frida_mode/build/frida/frida-gumjs-devkit-15.0.0-linux-x86_64.tar.xz "https://github.com/frida/frida/releases/download/15.0.0/frida-gumjs-devkit-15.0.0-linux-x86_64.tar.xz"
-
 WORKDIR /AFLplusplus
-RUN git checkout dev
-WORKDIR /AFLplusplus/frida_mode
 ENV CFLAGS="\
     -DADDR_NO_RANDOMIZE=0x0040000 \
     -Wno-implicit-function-declaration \
     "
 ENV CXX=$CC
-RUN make
diff --git a/frida_mode/many-linux/GNUmakefile b/frida_mode/many-linux/GNUmakefile
index 2860f20c..03b619f6 100644
--- a/frida_mode/many-linux/GNUmakefile
+++ b/frida_mode/many-linux/GNUmakefile
@@ -1,20 +1,20 @@
 PWD:=$(shell pwd)/
+ROOT:=$(PWD)../../
 BUILD_DIR:=$(PWD)build/
 
 .PHONY: all clean shell
 
-all: | $(BUILD_DIR)
+all:
 	docker build --tag many-afl-frida .
 	docker run --rm \
-		-v $(PWD)build/:/export \
+		-v $(ROOT):/AFLplusplus \
 		many-afl-frida \
-		cp /AFLplusplus/afl-frida-trace.so /export
+		make -C /AFLplusplus/frida_mode clean all
 
 $(BUILD_DIR):
 	mkdir -p $@
 
 clean:
-	rm -rf $(BUILD_DIR)
 	docker images --filter 'dangling=true' -q --no-trunc | xargs -L1 docker rmi --force
 
 shell:
diff --git a/frida_mode/many-linux/README.md b/frida_mode/many-linux/README.md
index 2c7b6823..4bd7a6c1 100644
--- a/frida_mode/many-linux/README.md
+++ b/frida_mode/many-linux/README.md
@@ -5,4 +5,5 @@ This folder contains a Docker image to allow the building of
 based on CentOS Linux 5. By building `afl-frida-trace.so` for such an old
 version of Linux, given the strong backward compatibility of Linux, this should
 work on the majority of Linux environments. This may be useful for targetting
-Linux distributions other than your development environment.
\ No newline at end of file
+Linux distributions other than your development environment. `many-local` builds
+`AFLplusplus` from the local working copy in the `many-linux` environment.
diff --git a/frida_mode/src/instrument/instrument.c b/frida_mode/src/instrument/instrument.c
index fddff19a..e37c1d29 100644
--- a/frida_mode/src/instrument/instrument.c
+++ b/frida_mode/src/instrument/instrument.c
@@ -171,6 +171,7 @@ static void instrument_basic_block(GumStalkerIterator *iterator,
     if (unlikely(begin)) {
 
       instrument_debug_start(instr->address, output);
+      instrument_coverage_start(instr->address);
 
       if (likely(entry_reached)) {
 
@@ -216,6 +217,7 @@ static void instrument_basic_block(GumStalkerIterator *iterator,
 
   instrument_flush(output);
   instrument_debug_end(output);
+  instrument_coverage_end(instr->address + instr->size);
 
 }
 
@@ -228,6 +230,7 @@ void instrument_config(void) {
   instrument_fixed_seed = util_read_num("AFL_FRIDA_INST_SEED");
 
   instrument_debug_config();
+  instrument_coverage_config();
   asan_config();
   cmplog_config();
 
@@ -317,6 +320,7 @@ void instrument_init(void) {
   instrument_hash_zero = instrument_get_offset_hash(0);
 
   instrument_debug_init();
+  instrument_coverage_init();
   asan_init();
   cmplog_init();
 
diff --git a/frida_mode/src/instrument/instrument_coverage.c b/frida_mode/src/instrument/instrument_coverage.c
new file mode 100644
index 00000000..68284e71
--- /dev/null
+++ b/frida_mode/src/instrument/instrument_coverage.c
@@ -0,0 +1,375 @@
+#include <fcntl.h>
+#include <limits.h>
+#include <unistd.h>
+
+#include "frida-gumjs.h"
+
+#include "debug.h"
+
+#include "instrument.h"
+#include "util.h"
+
+char *instrument_coverage_filename = NULL;
+
+static int         coverage_fd = -1;
+static int         coverage_pipes[2] = {0};
+static uint64_t    coverage_last_start = 0;
+static GHashTable *coverage_hash = NULL;
+static GArray *    coverage_modules = NULL;
+static guint       coverage_marked_modules = 0;
+static guint       coverage_marked_entries = 0;
+
+typedef struct {
+
+  GumAddress base_address;
+  GumAddress limit;
+  gsize      size;
+  char       name[PATH_MAX + 1];
+  char       path[PATH_MAX + 1];
+  bool       referenced;
+  guint16    id;
+
+} coverage_module_t;
+
+typedef struct {
+
+  uint64_t           start;
+  uint64_t           end;
+  coverage_module_t *module;
+
+} coverage_data_t;
+
+typedef struct {
+
+  guint32 offset;
+  guint16 length;
+  guint16 module;
+
+} coverage_event_t;
+
+static gboolean coverage_module(const GumModuleDetails *details,
+                                gpointer                user_data) {
+
+  UNUSED_PARAMETER(user_data);
+  coverage_module_t coverage = {0};
+
+  coverage.base_address = details->range->base_address;
+  coverage.size = details->range->size;
+  coverage.limit = coverage.base_address + coverage.size;
+
+  if (details->name != NULL) strncpy(coverage.name, details->name, PATH_MAX);
+
+  if (details->path != NULL) strncpy(coverage.path, details->path, PATH_MAX);
+
+  coverage.referenced = false;
+  coverage.id = 0;
+
+  g_array_append_val(coverage_modules, coverage);
+  return TRUE;
+
+}
+
+static gint coverage_sort(gconstpointer a, gconstpointer b) {
+
+  coverage_module_t *ma = (coverage_module_t *)a;
+  coverage_module_t *mb = (coverage_module_t *)b;
+
+  if (ma->base_address < mb->base_address) return -1;
+
+  if (ma->base_address > mb->base_address) return 1;
+
+  return 0;
+
+}
+
+static void coverage_get_ranges(void) {
+
+  OKF("Coverage - Collecting ranges");
+
+  coverage_modules =
+      g_array_sized_new(false, false, sizeof(coverage_module_t), 100);
+  gum_process_enumerate_modules(coverage_module, NULL);
+  g_array_sort(coverage_modules, coverage_sort);
+
+  for (guint i = 0; i < coverage_modules->len; i++) {
+
+    coverage_module_t *module =
+        &g_array_index(coverage_modules, coverage_module_t, i);
+    OKF("Coverage Module - %3u: 0x%016" G_GINT64_MODIFIER
+        "X - 0x%016" G_GINT64_MODIFIER "X",
+        i, module->base_address, module->limit);
+
+  }
+
+}
+
+static void instrument_coverage_mark(void *key, void *value, void *user_data) {
+
+  UNUSED_PARAMETER(key);
+  UNUSED_PARAMETER(user_data);
+  coverage_data_t *val = (coverage_data_t *)value;
+  guint            i;
+
+  for (i = 0; i < coverage_modules->len; i++) {
+
+    coverage_module_t *module =
+        &g_array_index(coverage_modules, coverage_module_t, i);
+    if (val->start > module->limit) continue;
+
+    if (val->end >= module->limit) break;
+
+    val->module = module;
+    coverage_marked_entries++;
+    module->referenced = true;
+    return;
+
+  }
+
+  OKF("Coverage cannot find module for: 0x%016" G_GINT64_MODIFIER
+      "X - 0x%016" G_GINT64_MODIFIER "X %u %u",
+      val->start, val->end, i, coverage_modules->len);
+
+}
+
+static void coverage_write(void *data, size_t size) {
+
+  ssize_t written;
+  size_t  remain = size;
+
+  for (char *cursor = (char *)data; remain > 0;
+       remain -= written, cursor += written) {
+
+    written = write(coverage_fd, cursor, remain);
+
+    if (written < 0) {
+
+      FATAL("Coverage - Failed to write: %s (%d)\n", (char *)data, errno);
+
+    }
+
+  }
+
+}
+
+static void coverage_format(char *format, ...) {
+
+  va_list ap;
+  char    buffer[4096] = {0};
+  int     ret;
+  int     len;
+
+  va_start(ap, format);
+  ret = vsnprintf(buffer, sizeof(buffer) - 1, format, ap);
+  va_end(ap);
+
+  if (ret < 0) { return; }
+
+  len = strnlen(buffer, sizeof(buffer));
+
+  coverage_write(buffer, len);
+
+}
+
+static void coverage_write_modules() {
+
+  guint emitted = 0;
+  for (guint i = 0; i < coverage_modules->len; i++) {
+
+    coverage_module_t *module =
+        &g_array_index(coverage_modules, coverage_module_t, i);
+    if (!module->referenced) continue;
+
+    coverage_format("%3u, ", emitted);
+    coverage_format("%016" G_GINT64_MODIFIER "X, ", module->base_address);
+    coverage_format("%016" G_GINT64_MODIFIER "X, ", module->limit);
+    /* entry */
+    coverage_format("%016" G_GINT64_MODIFIER "X, ", 0);
+    /* checksum */
+    coverage_format("%016" G_GINT64_MODIFIER "X, ", 0);
+    /* timestamp */
+    coverage_format("%08" G_GINT32_MODIFIER "X, ", 0);
+    coverage_format("%s\n", module->path);
+    emitted++;
+
+  }
+
+}
+
+static void coverage_write_events(void *key, void *value, void *user_data) {
+
+  UNUSED_PARAMETER(key);
+  UNUSED_PARAMETER(user_data);
+  coverage_data_t *val = (coverage_data_t *)value;
+  coverage_event_t evt = {
+
+      .offset = val->start - val->module->base_address,
+      .length = val->end - val->start,
+      .module = val->module->id,
+
+  };
+
+  coverage_write(&evt, sizeof(coverage_event_t));
+
+}
+
+static void coverage_write_header() {
+
+  char version[] = "DRCOV VERSION: 2\n";
+  char flavour[] = "DRCOV FLAVOR: frida\n";
+  char columns[] = "Columns: id, base, end, entry, checksum, timestamp, path\n";
+  coverage_write(version, sizeof(version) - 1);
+  coverage_write(flavour, sizeof(flavour) - 1);
+  coverage_format("Module Table: version 2, count %u\n",
+                  coverage_marked_modules);
+  coverage_write(columns, sizeof(columns) - 1);
+  coverage_write_modules();
+  coverage_format("BB Table: %u bbs\n", coverage_marked_entries);
+  g_hash_table_foreach(coverage_hash, coverage_write_events, NULL);
+
+}
+
+static void coverage_mark_modules() {
+
+  guint i;
+  for (i = 0; i < coverage_modules->len; i++) {
+
+    coverage_module_t *module =
+        &g_array_index(coverage_modules, coverage_module_t, i);
+
+    OKF("Coverage Module - %3u: [%c] 0x%016" G_GINT64_MODIFIER
+        "X - 0x%016" G_GINT64_MODIFIER "X (%u:%s)",
+        i, module->referenced ? 'X' : ' ', module->base_address, module->limit,
+        module->id, module->path);
+
+    if (!module->referenced) { continue; }
+
+    module->id = coverage_marked_modules;
+    coverage_marked_modules++;
+
+  }
+
+}
+
+static void instrument_coverage_run() {
+
+  int              bytes;
+  coverage_data_t  data;
+  coverage_data_t *value;
+  OKF("Coverage - Running");
+
+  if (close(coverage_pipes[STDOUT_FILENO]) != 0) {
+
+    FATAL("Failed to close parent read pipe");
+
+  }
+
+  for (bytes =
+           read(coverage_pipes[STDIN_FILENO], &data, sizeof(coverage_data_t));
+       bytes == sizeof(coverage_data_t);
+       bytes =
+           read(coverage_pipes[STDIN_FILENO], &data, sizeof(coverage_data_t))) {
+
+    value = (coverage_data_t *)gum_malloc0(sizeof(coverage_data_t));
+    memcpy(value, &data, sizeof(coverage_data_t));
+    g_hash_table_insert(coverage_hash, GSIZE_TO_POINTER(data.start), value);
+
+  }
+
+  if (bytes != 0) { FATAL("Coverage data truncated"); }
+
+  if (errno != ENOENT) { FATAL("Coverage I/O error"); }
+
+  OKF("Coverage - Preparing");
+
+  coverage_get_ranges();
+
+  guint size = g_hash_table_size(coverage_hash);
+  OKF("Coverage - Total Entries: %u", size);
+
+  g_hash_table_foreach(coverage_hash, instrument_coverage_mark, NULL);
+  OKF("Coverage - Marked Entries: %u", coverage_marked_entries);
+
+  coverage_mark_modules();
+  OKF("Coverage - Marked Modules: %u", coverage_marked_modules);
+
+  coverage_write_header();
+
+  OKF("Coverage - Completed");
+
+}
+
+void instrument_coverage_config(void) {
+
+  instrument_coverage_filename = getenv("AFL_FRIDA_INST_COVERAGE_FILE");
+
+}
+
+void instrument_coverage_init(void) {
+
+  OKF("Coverage - enabled [%c]",
+      instrument_coverage_filename == NULL ? ' ' : 'X');
+
+  if (instrument_coverage_filename == NULL) { return; }
+
+  OKF("Coverage - file [%s]", instrument_coverage_filename);
+
+  char *path = g_canonicalize_filename(instrument_coverage_filename,
+                                       g_get_current_dir());
+
+  OKF("Coverage - path [%s]", path);
+
+  coverage_fd = open(path, O_RDWR | O_CREAT | O_TRUNC,
+                     S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP);
+
+  if (coverage_fd < 0) { FATAL("Failed to open coverage file '%s'", path); }
+
+  g_free(path);
+
+  if (pipe2(coverage_pipes, O_DIRECT) != 0) { FATAL("Failed to create pipes"); }
+
+  coverage_hash = g_hash_table_new(g_direct_hash, g_direct_equal);
+  if (coverage_hash == NULL) {
+
+    FATAL("Failed to g_hash_table_new, errno: %d", errno);
+
+  }
+
+  pid_t pid = fork();
+  if (pid == -1) { FATAL("Failed to start coverage process"); }
+
+  if (pid == 0) {
+
+    instrument_coverage_run();
+    exit(0);
+
+  }
+
+  if (close(coverage_pipes[STDIN_FILENO]) != 0) {
+
+    FATAL("Failed to close parent read pipe");
+
+  }
+
+}
+
+void instrument_coverage_start(uint64_t address) {
+
+  coverage_last_start = address;
+
+}
+
+void instrument_coverage_end(uint64_t address) {
+
+  coverage_data_t data = {
+
+      .start = coverage_last_start, .end = address, .module = NULL};
+
+  if (write(coverage_pipes[STDOUT_FILENO], &data, sizeof(coverage_data_t)) !=
+      sizeof(coverage_data_t)) {
+
+    FATAL("Coverage I/O error");
+
+  }
+
+}
+
diff --git a/frida_mode/src/js/api.js b/frida_mode/src/js/api.js
index b8f2d39a..f0cf7311 100644
--- a/frida_mode/src/js/api.js
+++ b/frida_mode/src/js/api.js
@@ -86,6 +86,14 @@ class Afl {
         Afl.jsApiAflSharedMemFuzzing.writeInt(1);
     }
     /**
+     * See `AFL_FRIDA_INST_COVERAGE_FILE`. This function takes a single `string`
+     * as an argument.
+     */
+    static setInstrumentCoverageFile(file) {
+        const buf = Memory.allocUtf8String(file);
+        Afl.jsApiSetInstrumentCoverageFile(buf);
+    }
+    /**
      * See `AFL_FRIDA_INST_DEBUG_FILE`. This function takes a single `string` as
      * an argument.
      */
@@ -233,6 +241,7 @@ Afl.jsApiDone = Afl.jsApiGetFunction("js_api_done", "void", []);
 Afl.jsApiError = Afl.jsApiGetFunction("js_api_error", "void", ["pointer"]);
 Afl.jsApiSetDebugMaps = Afl.jsApiGetFunction("js_api_set_debug_maps", "void", []);
 Afl.jsApiSetEntryPoint = Afl.jsApiGetFunction("js_api_set_entrypoint", "void", ["pointer"]);
+Afl.jsApiSetInstrumentCoverageFile = Afl.jsApiGetFunction("js_api_set_instrument_coverage_file", "void", ["pointer"]);
 Afl.jsApiSetInstrumentDebugFile = Afl.jsApiGetFunction("js_api_set_instrument_debug_file", "void", ["pointer"]);
 Afl.jsApiSetInstrumentJit = Afl.jsApiGetFunction("js_api_set_instrument_jit", "void", []);
 Afl.jsApiSetInstrumentLibraries = Afl.jsApiGetFunction("js_api_set_instrument_libraries", "void", []);
diff --git a/frida_mode/src/js/js_api.c b/frida_mode/src/js/js_api.c
index 930a6dc0..e51f852a 100644
--- a/frida_mode/src/js/js_api.c
+++ b/frida_mode/src/js/js_api.c
@@ -107,6 +107,13 @@ __attribute__((visibility("default"))) void js_api_set_instrument_libraries() {
 
 }
 
+__attribute__((visibility("default"))) void js_api_set_instrument_coverage_file(
+    char *path) {
+
+  instrument_coverage_filename = g_strdup(path);
+
+}
+
 __attribute__((visibility("default"))) void js_api_set_instrument_debug_file(
     char *path) {
 
diff --git a/frida_mode/ts/lib/afl.ts b/frida_mode/ts/lib/afl.ts
index 6326c099..c1ed123e 100644
--- a/frida_mode/ts/lib/afl.ts
+++ b/frida_mode/ts/lib/afl.ts
@@ -104,6 +104,15 @@ class Afl {
   }
 
   /**
+   * See `AFL_FRIDA_INST_COVERAGE_FILE`. This function takes a single `string`
+   * as an argument.
+   */
+  public static setInstrumentCoverageFile(file: string): void {
+    const buf = Memory.allocUtf8String(file);
+    Afl.jsApiSetInstrumentCoverageFile(buf);
+  }
+
+  /**
    * See `AFL_FRIDA_INST_DEBUG_FILE`. This function takes a single `string` as
    * an argument.
    */
@@ -282,6 +291,11 @@ class Afl {
     "void",
     ["pointer"]);
 
+  private static readonly jsApiSetInstrumentCoverageFile = Afl.jsApiGetFunction(
+    "js_api_set_instrument_coverage_file",
+    "void",
+    ["pointer"]);
+
   private static readonly jsApiSetInstrumentDebugFile = Afl.jsApiGetFunction(
     "js_api_set_instrument_debug_file",
     "void",
diff --git a/include/envs.h b/include/envs.h
index 49605330..722fe1a5 100644
--- a/include/envs.h
+++ b/include/envs.h
@@ -55,6 +55,7 @@ static char *afl_environment_variables[] = {
     "AFL_FORCE_UI",
     "AFL_FRIDA_DEBUG_MAPS",
     "AFL_FRIDA_EXCLUDE_RANGES",
+    "AFL_FRIDA_INST_COVERAGE_FILE",
     "AFL_FRIDA_INST_DEBUG_FILE",
     "AFL_FRIDA_INST_JIT",
     "AFL_FRIDA_INST_NO_OPTIMIZE",