mirror of
https://github.com/AFLplusplus/AFLplusplus.git
synced 2025-06-12 01:58:17 +00:00
Add support for generating coverage information
This commit is contained in:
@ -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.
|
||||
```
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
|
||||
|
375
frida_mode/src/instrument/instrument_coverage.c
Normal file
375
frida_mode/src/instrument/instrument_coverage.c
Normal 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");
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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", []);
|
||||
|
@ -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) {
|
||||
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
Reference in New Issue
Block a user