Add support for generating coverage information
MODIFY
frida_mode/README.md
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.
```

MODIFY
frida_mode/frida.map
frida_mode/frida.map
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;

MODIFY
frida_mode/include/instrument.h
frida_mode/include/instrument.h
#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);

MODIFY
frida_mode/src/instrument/instrument.c
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);
}
@@ -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();

ADD
frida_mode/src/instrument/instrument_coverage.c
+#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");
+
+ }
+
+}
+

MODIFY
frida_mode/src/js/api.js
frida_mode/src/js/api.js
@@ -85,6 +85,14 @@ class Afl {
static setInMemoryFuzzing() {
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", []);

MODIFY
frida_mode/src/js/js_api.c
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) {

MODIFY
frida_mode/ts/lib/afl.ts
frida_mode/ts/lib/afl.ts
@@ -103,6 +103,15 @@ class Afl {
Afl.jsApiAflSharedMemFuzzing.writeInt(1);
}
+ /**
+ * 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",

MODIFY
include/envs.h
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",

Add support for generating coverage information