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.
## 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.
```

View File

@ -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;

View File

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

View File

@ -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();

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() {
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", []);

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(
char *path) {

View File

@ -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",

View File

@ -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",