diff options
-rw-r--r-- | frida_mode/GNUmakefile | 2 | ||||
-rw-r--r-- | frida_mode/README.md | 3 | ||||
-rw-r--r-- | frida_mode/frida.map | 1 | ||||
-rw-r--r-- | frida_mode/include/instrument.h | 6 | ||||
-rw-r--r-- | frida_mode/many-linux/Dockerfile | 10 | ||||
-rw-r--r-- | frida_mode/many-linux/GNUmakefile | 8 | ||||
-rw-r--r-- | frida_mode/many-linux/README.md | 3 | ||||
-rw-r--r-- | frida_mode/src/instrument/instrument.c | 4 | ||||
-rw-r--r-- | frida_mode/src/instrument/instrument_coverage.c | 375 | ||||
-rw-r--r-- | frida_mode/src/js/api.js | 9 | ||||
-rw-r--r-- | frida_mode/src/js/js_api.c | 7 | ||||
-rw-r--r-- | frida_mode/ts/lib/afl.ts | 14 | ||||
-rw-r--r-- | include/envs.h | 1 |
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", |