Add support for generating coverage information

This commit is contained in:
Your Name
2021-08-17 18:30:30 +01:00
parent 2a68d37b4f
commit 5f20137e9d
9 changed files with 419 additions and 1 deletions

View File

@ -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. (only on CMP, SUB and CALL instructions) performance is not quite so critical.
## Advanced configuration options ## 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 * `AFL_FRIDA_INST_DEBUG_FILE` - File to write raw assembly of original blocks
and their instrumented counterparts during block compilation. and their instrumented counterparts during block compilation.
``` ```

View File

@ -10,6 +10,7 @@
js_api_error; js_api_error;
js_api_set_debug_maps; js_api_set_debug_maps;
js_api_set_entrypoint; js_api_set_entrypoint;
js_api_set_instrument_coverage_file;
js_api_set_instrument_debug_file; js_api_set_instrument_debug_file;
js_api_set_instrument_jit; js_api_set_instrument_jit;
js_api_set_instrument_libraries; js_api_set_instrument_libraries;

View File

@ -6,6 +6,7 @@
#include "config.h" #include "config.h"
extern char * instrument_debug_filename; extern char * instrument_debug_filename;
extern char * instrument_coverage_filename;
extern gboolean instrument_tracing; extern gboolean instrument_tracing;
extern gboolean instrument_optimize; extern gboolean instrument_optimize;
extern gboolean instrument_unique; extern gboolean instrument_unique;
@ -38,6 +39,11 @@ void instrument_debug_end(GumStalkerOutput *output);
void instrument_flush(GumStalkerOutput *output); void instrument_flush(GumStalkerOutput *output);
gpointer instrument_cur(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(); void instrument_on_fork();
guint64 instrument_get_offset_hash(GumAddress current_rip); guint64 instrument_get_offset_hash(GumAddress current_rip);

View File

@ -171,6 +171,7 @@ static void instrument_basic_block(GumStalkerIterator *iterator,
if (unlikely(begin)) { if (unlikely(begin)) {
instrument_debug_start(instr->address, output); instrument_debug_start(instr->address, output);
instrument_coverage_start(instr->address);
if (likely(entry_reached)) { if (likely(entry_reached)) {
@ -216,6 +217,7 @@ static void instrument_basic_block(GumStalkerIterator *iterator,
instrument_flush(output); instrument_flush(output);
instrument_debug_end(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_fixed_seed = util_read_num("AFL_FRIDA_INST_SEED");
instrument_debug_config(); instrument_debug_config();
instrument_coverage_config();
asan_config(); asan_config();
cmplog_config(); cmplog_config();
@ -317,6 +320,7 @@ void instrument_init(void) {
instrument_hash_zero = instrument_get_offset_hash(0); instrument_hash_zero = instrument_get_offset_hash(0);
instrument_debug_init(); instrument_debug_init();
instrument_coverage_init();
asan_init(); asan_init();
cmplog_init(); cmplog_init();

View File

@ -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");
}
}

View File

@ -85,6 +85,14 @@ class Afl {
static setInMemoryFuzzing() { static setInMemoryFuzzing() {
Afl.jsApiAflSharedMemFuzzing.writeInt(1); 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 * See `AFL_FRIDA_INST_DEBUG_FILE`. This function takes a single `string` as
* an argument. * an argument.
@ -233,6 +241,7 @@ Afl.jsApiDone = Afl.jsApiGetFunction("js_api_done", "void", []);
Afl.jsApiError = Afl.jsApiGetFunction("js_api_error", "void", ["pointer"]); Afl.jsApiError = Afl.jsApiGetFunction("js_api_error", "void", ["pointer"]);
Afl.jsApiSetDebugMaps = Afl.jsApiGetFunction("js_api_set_debug_maps", "void", []); Afl.jsApiSetDebugMaps = Afl.jsApiGetFunction("js_api_set_debug_maps", "void", []);
Afl.jsApiSetEntryPoint = Afl.jsApiGetFunction("js_api_set_entrypoint", "void", ["pointer"]); 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.jsApiSetInstrumentDebugFile = Afl.jsApiGetFunction("js_api_set_instrument_debug_file", "void", ["pointer"]);
Afl.jsApiSetInstrumentJit = Afl.jsApiGetFunction("js_api_set_instrument_jit", "void", []); Afl.jsApiSetInstrumentJit = Afl.jsApiGetFunction("js_api_set_instrument_jit", "void", []);
Afl.jsApiSetInstrumentLibraries = Afl.jsApiGetFunction("js_api_set_instrument_libraries", "void", []); Afl.jsApiSetInstrumentLibraries = Afl.jsApiGetFunction("js_api_set_instrument_libraries", "void", []);

View File

@ -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( __attribute__((visibility("default"))) void js_api_set_instrument_debug_file(
char *path) { char *path) {

View File

@ -103,6 +103,15 @@ class Afl {
Afl.jsApiAflSharedMemFuzzing.writeInt(1); 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 * See `AFL_FRIDA_INST_DEBUG_FILE`. This function takes a single `string` as
* an argument. * an argument.
@ -282,6 +291,11 @@ class Afl {
"void", "void",
["pointer"]); ["pointer"]);
private static readonly jsApiSetInstrumentCoverageFile = Afl.jsApiGetFunction(
"js_api_set_instrument_coverage_file",
"void",
["pointer"]);
private static readonly jsApiSetInstrumentDebugFile = Afl.jsApiGetFunction( private static readonly jsApiSetInstrumentDebugFile = Afl.jsApiGetFunction(
"js_api_set_instrument_debug_file", "js_api_set_instrument_debug_file",
"void", "void",

View File

@ -55,6 +55,7 @@ static char *afl_environment_variables[] = {
"AFL_FORCE_UI", "AFL_FORCE_UI",
"AFL_FRIDA_DEBUG_MAPS", "AFL_FRIDA_DEBUG_MAPS",
"AFL_FRIDA_EXCLUDE_RANGES", "AFL_FRIDA_EXCLUDE_RANGES",
"AFL_FRIDA_INST_COVERAGE_FILE",
"AFL_FRIDA_INST_DEBUG_FILE", "AFL_FRIDA_INST_DEBUG_FILE",
"AFL_FRIDA_INST_JIT", "AFL_FRIDA_INST_JIT",
"AFL_FRIDA_INST_NO_OPTIMIZE", "AFL_FRIDA_INST_NO_OPTIMIZE",