mirror of
https://github.com/AFLplusplus/AFLplusplus.git
synced 2025-06-23 14:34:25 +00:00
687 lines
16 KiB
C
687 lines
16 KiB
C
#include "frida-gumjs.h"
|
|
|
|
#include "lib.h"
|
|
#include "ranges.h"
|
|
#include "stalker.h"
|
|
#include "util.h"
|
|
|
|
#define MAX_RANGES 20
|
|
|
|
typedef struct {
|
|
|
|
gchar * suffix;
|
|
GumMemoryRange *range;
|
|
gboolean done;
|
|
|
|
} convert_name_ctx_t;
|
|
|
|
gboolean ranges_debug_maps = FALSE;
|
|
gboolean ranges_inst_libs = FALSE;
|
|
gboolean ranges_inst_jit = FALSE;
|
|
|
|
static GArray *module_ranges = NULL;
|
|
static GArray *libs_ranges = NULL;
|
|
static GArray *jit_ranges = NULL;
|
|
static GArray *include_ranges = NULL;
|
|
static GArray *exclude_ranges = NULL;
|
|
static GArray *ranges = NULL;
|
|
|
|
static void convert_address_token(gchar *token, GumMemoryRange *range) {
|
|
|
|
gchar **tokens;
|
|
int token_count;
|
|
tokens = g_strsplit(token, "-", 2);
|
|
for (token_count = 0; tokens[token_count] != NULL; token_count++) {}
|
|
|
|
if (token_count != 2) {
|
|
|
|
FFATAL("Invalid range (should have two addresses seperated by a '-'): %s\n",
|
|
token);
|
|
|
|
}
|
|
|
|
gchar *from_str = tokens[0];
|
|
gchar *to_str = tokens[1];
|
|
|
|
if (!g_str_has_prefix(from_str, "0x")) {
|
|
|
|
FFATAL("Invalid range: %s - Start address should have 0x prefix: %s\n",
|
|
token, from_str);
|
|
|
|
}
|
|
|
|
if (!g_str_has_prefix(to_str, "0x")) {
|
|
|
|
FFATAL("Invalid range: %s - End address should have 0x prefix: %s\n", token,
|
|
to_str);
|
|
|
|
}
|
|
|
|
from_str = &from_str[2];
|
|
to_str = &to_str[2];
|
|
|
|
for (char *c = from_str; *c != '\0'; c++) {
|
|
|
|
if (!g_ascii_isxdigit(*c)) {
|
|
|
|
FFATAL("Invalid range: %s - Start address not formed of hex digits: %s\n",
|
|
token, from_str);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for (char *c = to_str; *c != '\0'; c++) {
|
|
|
|
if (!g_ascii_isxdigit(*c)) {
|
|
|
|
FFATAL("Invalid range: %s - End address not formed of hex digits: %s\n",
|
|
token, to_str);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
guint64 from = g_ascii_strtoull(from_str, NULL, 16);
|
|
if (from == 0) {
|
|
|
|
FFATAL("Invalid range: %s - Start failed hex conversion: %s\n", token,
|
|
from_str);
|
|
|
|
}
|
|
|
|
guint64 to = g_ascii_strtoull(to_str, NULL, 16);
|
|
if (to == 0) {
|
|
|
|
FFATAL("Invalid range: %s - End failed hex conversion: %s\n", token,
|
|
to_str);
|
|
|
|
}
|
|
|
|
if (from >= to) {
|
|
|
|
FFATAL("Invalid range: %s - Start (0x%016" G_GINT64_MODIFIER
|
|
"x) must be less than end "
|
|
"(0x%016" G_GINT64_MODIFIER "x)\n",
|
|
token, from, to);
|
|
|
|
}
|
|
|
|
range->base_address = from;
|
|
range->size = to - from;
|
|
|
|
g_strfreev(tokens);
|
|
|
|
}
|
|
|
|
static gboolean convert_name_token_for_module(const GumModuleDetails *details,
|
|
gpointer user_data) {
|
|
|
|
convert_name_ctx_t *ctx = (convert_name_ctx_t *)user_data;
|
|
if (details->path == NULL) { return true; };
|
|
|
|
if (!g_str_has_suffix(details->path, ctx->suffix)) { return true; };
|
|
|
|
FVERBOSE("Found module - prefix: %s, 0x%016" G_GINT64_MODIFIER
|
|
"x-0x%016" G_GINT64_MODIFIER "x %s",
|
|
ctx->suffix, details->range->base_address,
|
|
details->range->base_address + details->range->size, details->path);
|
|
|
|
*ctx->range = *details->range;
|
|
ctx->done = true;
|
|
return false;
|
|
|
|
}
|
|
|
|
static void convert_name_token(gchar *token, GumMemoryRange *range) {
|
|
|
|
gchar * suffix = g_strconcat("/", token, NULL);
|
|
convert_name_ctx_t ctx = {.suffix = suffix, .range = range, .done = false};
|
|
|
|
gum_process_enumerate_modules(convert_name_token_for_module, &ctx);
|
|
if (!ctx.done) { FFATAL("Failed to resolve module: %s\n", token); }
|
|
g_free(suffix);
|
|
|
|
}
|
|
|
|
static void convert_token(gchar *token, GumMemoryRange *range) {
|
|
|
|
if (g_str_has_prefix(token, "0x")) {
|
|
|
|
convert_address_token(token, range);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
convert_name_token(token, range);
|
|
|
|
}
|
|
|
|
FVERBOSE("Converted token: %s -> 0x%016" G_GINT64_MODIFIER
|
|
"x-0x%016" G_GINT64_MODIFIER "x\n",
|
|
token, range->base_address, range->base_address + range->size);
|
|
|
|
}
|
|
|
|
gint range_sort(gconstpointer a, gconstpointer b) {
|
|
|
|
GumMemoryRange *ra = (GumMemoryRange *)a;
|
|
GumMemoryRange *rb = (GumMemoryRange *)b;
|
|
|
|
if (ra->base_address < rb->base_address) {
|
|
|
|
return -1;
|
|
|
|
} else if (ra->base_address > rb->base_address) {
|
|
|
|
return 1;
|
|
|
|
} else {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
static gboolean print_ranges_callback(const GumRangeDetails *details,
|
|
gpointer user_data) {
|
|
|
|
UNUSED_PARAMETER(user_data);
|
|
|
|
if (details->file == NULL) {
|
|
|
|
FVERBOSE("\t0x%016" G_GINT64_MODIFIER "x-0x%016" G_GINT64_MODIFIER
|
|
"X %c%c%c",
|
|
details->range->base_address,
|
|
details->range->base_address + details->range->size,
|
|
details->protection & GUM_PAGE_READ ? 'R' : '-',
|
|
details->protection & GUM_PAGE_WRITE ? 'W' : '-',
|
|
details->protection & GUM_PAGE_EXECUTE ? 'X' : '-');
|
|
|
|
} else {
|
|
|
|
FVERBOSE("\t0x%016" G_GINT64_MODIFIER "x-0x%016" G_GINT64_MODIFIER
|
|
"X %c%c%c %s(0x%016" G_GINT64_MODIFIER "x)",
|
|
details->range->base_address,
|
|
details->range->base_address + details->range->size,
|
|
details->protection & GUM_PAGE_READ ? 'R' : '-',
|
|
details->protection & GUM_PAGE_WRITE ? 'W' : '-',
|
|
details->protection & GUM_PAGE_EXECUTE ? 'X' : '-',
|
|
details->file->path, details->file->offset);
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
static void print_ranges(char *key, GArray *ranges) {
|
|
|
|
FVERBOSE("Range: [%s], Length: %d", key, ranges->len);
|
|
for (guint i = 0; i < ranges->len; i++) {
|
|
|
|
GumMemoryRange *curr = &g_array_index(ranges, GumMemoryRange, i);
|
|
GumAddress curr_limit = curr->base_address + curr->size;
|
|
FVERBOSE("\t%3d - 0x%016" G_GINT64_MODIFIER "x-0x%016" G_GINT64_MODIFIER
|
|
"x",
|
|
i, curr->base_address, curr_limit);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
static gboolean collect_module_ranges_callback(const GumRangeDetails *details,
|
|
gpointer user_data) {
|
|
|
|
GArray * ranges = (GArray *)user_data;
|
|
GumMemoryRange range = *details->range;
|
|
g_array_append_val(ranges, range);
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
static GArray *collect_module_ranges(void) {
|
|
|
|
GArray *result;
|
|
result = g_array_new(false, false, sizeof(GumMemoryRange));
|
|
gum_process_enumerate_ranges(GUM_PAGE_NO_ACCESS,
|
|
collect_module_ranges_callback, result);
|
|
print_ranges("modules", result);
|
|
return result;
|
|
|
|
}
|
|
|
|
static void check_for_overlaps(GArray *array) {
|
|
|
|
for (guint i = 1; i < array->len; i++) {
|
|
|
|
GumMemoryRange *prev = &g_array_index(array, GumMemoryRange, i - 1);
|
|
GumMemoryRange *curr = &g_array_index(array, GumMemoryRange, i);
|
|
GumAddress prev_limit = prev->base_address + prev->size;
|
|
GumAddress curr_limit = curr->base_address + curr->size;
|
|
if (prev_limit > curr->base_address) {
|
|
|
|
FFATAL("Overlapping ranges 0x%016" G_GINT64_MODIFIER
|
|
"x-0x%016" G_GINT64_MODIFIER "x 0x%016" G_GINT64_MODIFIER
|
|
"x-0x%016" G_GINT64_MODIFIER "x",
|
|
prev->base_address, prev_limit, curr->base_address, curr_limit);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
void ranges_add_include(GumMemoryRange *range) {
|
|
|
|
g_array_append_val(include_ranges, *range);
|
|
g_array_sort(include_ranges, range_sort);
|
|
check_for_overlaps(include_ranges);
|
|
|
|
}
|
|
|
|
void ranges_add_exclude(GumMemoryRange *range) {
|
|
|
|
g_array_append_val(exclude_ranges, *range);
|
|
g_array_sort(exclude_ranges, range_sort);
|
|
check_for_overlaps(exclude_ranges);
|
|
|
|
}
|
|
|
|
static GArray *collect_ranges(char *env_key) {
|
|
|
|
char * env_val;
|
|
gchar ** tokens;
|
|
int token_count;
|
|
GumMemoryRange range;
|
|
int i;
|
|
GArray * result;
|
|
|
|
result = g_array_new(false, false, sizeof(GumMemoryRange));
|
|
|
|
env_val = getenv(env_key);
|
|
if (env_val == NULL) return result;
|
|
|
|
tokens = g_strsplit(env_val, ",", MAX_RANGES);
|
|
|
|
for (token_count = 0; tokens[token_count] != NULL; token_count++)
|
|
;
|
|
|
|
for (i = 0; i < token_count; i++) {
|
|
|
|
convert_token(tokens[i], &range);
|
|
g_array_append_val(result, range);
|
|
|
|
}
|
|
|
|
g_array_sort(result, range_sort);
|
|
|
|
check_for_overlaps(result);
|
|
|
|
print_ranges(env_key, result);
|
|
|
|
g_strfreev(tokens);
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
static GArray *collect_libs_ranges(void) {
|
|
|
|
GArray * result;
|
|
GumMemoryRange range;
|
|
result = g_array_new(false, false, sizeof(GumMemoryRange));
|
|
|
|
if (ranges_inst_libs) {
|
|
|
|
range.base_address = 0;
|
|
range.size = G_MAXULONG;
|
|
|
|
} else {
|
|
|
|
range.base_address = lib_get_text_base();
|
|
range.size = lib_get_text_limit() - lib_get_text_base();
|
|
|
|
}
|
|
|
|
g_array_append_val(result, range);
|
|
|
|
print_ranges("libs", result);
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
static gboolean collect_jit_ranges_callback(const GumRangeDetails *details,
|
|
gpointer user_data) {
|
|
|
|
GArray *ranges = (GArray *)user_data;
|
|
|
|
/* If the executable code isn't backed by a file, it's probably JIT */
|
|
if (details->file == NULL) {
|
|
|
|
GumMemoryRange range = *details->range;
|
|
g_array_append_val(ranges, range);
|
|
|
|
}
|
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
static GArray *collect_jit_ranges(void) {
|
|
|
|
GArray *result;
|
|
result = g_array_new(false, false, sizeof(GumMemoryRange));
|
|
if (!ranges_inst_jit) {
|
|
|
|
gum_process_enumerate_ranges(GUM_PAGE_EXECUTE, collect_jit_ranges_callback,
|
|
result);
|
|
|
|
}
|
|
|
|
print_ranges("jit", result);
|
|
return result;
|
|
|
|
}
|
|
|
|
static gboolean intersect_range(GumMemoryRange *rr, GumMemoryRange *ra,
|
|
GumMemoryRange *rb) {
|
|
|
|
GumAddress rab = ra->base_address;
|
|
GumAddress ral = rab + ra->size;
|
|
|
|
GumAddress rbb = rb->base_address;
|
|
GumAddress rbl = rbb + rb->size;
|
|
|
|
GumAddress rrb = 0;
|
|
GumAddress rrl = 0;
|
|
|
|
rr->base_address = 0;
|
|
rr->size = 0;
|
|
|
|
/* ra is before rb */
|
|
if (ral < rbb) { return false; }
|
|
|
|
/* ra is after rb */
|
|
if (rab > rbl) { return true; }
|
|
|
|
/* The largest of the two base addresses */
|
|
rrb = rab > rbb ? rab : rbb;
|
|
|
|
/* The smallest of the two limits */
|
|
rrl = ral < rbl ? ral : rbl;
|
|
|
|
rr->base_address = rrb;
|
|
rr->size = rrl - rrb;
|
|
return true;
|
|
|
|
}
|
|
|
|
static GArray *intersect_ranges(GArray *a, GArray *b) {
|
|
|
|
GArray * result;
|
|
GumMemoryRange *ra;
|
|
GumMemoryRange *rb;
|
|
GumMemoryRange ri;
|
|
|
|
result = g_array_new(false, false, sizeof(GumMemoryRange));
|
|
|
|
for (guint i = 0; i < a->len; i++) {
|
|
|
|
ra = &g_array_index(a, GumMemoryRange, i);
|
|
for (guint j = 0; j < b->len; j++) {
|
|
|
|
rb = &g_array_index(b, GumMemoryRange, j);
|
|
|
|
if (!intersect_range(&ri, ra, rb)) { break; }
|
|
|
|
if (ri.size == 0) { continue; }
|
|
|
|
g_array_append_val(result, ri);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
static GArray *subtract_ranges(GArray *a, GArray *b) {
|
|
|
|
GArray * result;
|
|
GumMemoryRange *ra;
|
|
GumAddress ral;
|
|
GumMemoryRange *rb;
|
|
GumMemoryRange ri;
|
|
GumMemoryRange rs;
|
|
|
|
result = g_array_new(false, false, sizeof(GumMemoryRange));
|
|
|
|
for (guint i = 0; i < a->len; i++) {
|
|
|
|
ra = &g_array_index(a, GumMemoryRange, i);
|
|
ral = ra->base_address + ra->size;
|
|
for (guint j = 0; j < b->len; j++) {
|
|
|
|
rb = &g_array_index(b, GumMemoryRange, j);
|
|
|
|
/*
|
|
* If rb is after ra, we have no more possible intersections and we can
|
|
* simply keep the remaining range
|
|
*/
|
|
if (!intersect_range(&ri, ra, rb)) { break; }
|
|
|
|
/*
|
|
* If there is no intersection, then rb must be before ra, so we must
|
|
* continue
|
|
*/
|
|
if (ri.size == 0) { continue; }
|
|
|
|
/*
|
|
* If the intersection is part way through the range, then we keep the
|
|
* start of the range
|
|
*/
|
|
if (ra->base_address < ri.base_address) {
|
|
|
|
rs.base_address = ra->base_address;
|
|
rs.size = ri.base_address - ra->base_address;
|
|
g_array_append_val(result, rs);
|
|
|
|
}
|
|
|
|
/*
|
|
* If the intersection extends past the limit of the range, then we should
|
|
* continue with the next range
|
|
*/
|
|
if ((ri.base_address + ri.size) > ral) {
|
|
|
|
ra->base_address = ral;
|
|
ra->size = 0;
|
|
break;
|
|
|
|
}
|
|
|
|
/*
|
|
* Otherwise we advance the base of the range to the end of the
|
|
* intersection and continue with the remainder of the range
|
|
*/
|
|
ra->base_address = ri.base_address + ri.size;
|
|
ra->size = ral - ra->base_address;
|
|
|
|
}
|
|
|
|
/*
|
|
* When we have processed all the possible intersections, we add what is
|
|
* left
|
|
*/
|
|
if (ra->size != 0) g_array_append_val(result, *ra);
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
static GArray *merge_ranges(GArray *a) {
|
|
|
|
GArray * result;
|
|
GumMemoryRange rp;
|
|
GumMemoryRange *r;
|
|
|
|
result = g_array_new(false, false, sizeof(GumMemoryRange));
|
|
if (a->len == 0) return result;
|
|
|
|
rp = g_array_index(a, GumMemoryRange, 0);
|
|
|
|
for (guint i = 1; i < a->len; i++) {
|
|
|
|
r = &g_array_index(a, GumMemoryRange, i);
|
|
|
|
if (rp.base_address + rp.size == r->base_address) {
|
|
|
|
rp.size += r->size;
|
|
|
|
} else {
|
|
|
|
g_array_append_val(result, rp);
|
|
rp.base_address = r->base_address;
|
|
rp.size = r->size;
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
g_array_append_val(result, rp);
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
void ranges_print_debug_maps(void) {
|
|
|
|
FVERBOSE("Maps");
|
|
gum_process_enumerate_ranges(GUM_PAGE_NO_ACCESS, print_ranges_callback, NULL);
|
|
|
|
}
|
|
|
|
void ranges_config(void) {
|
|
|
|
if (getenv("AFL_FRIDA_DEBUG_MAPS") != NULL) { ranges_debug_maps = TRUE; }
|
|
if (getenv("AFL_INST_LIBS") != NULL) { ranges_inst_libs = TRUE; }
|
|
if (getenv("AFL_FRIDA_INST_JIT") != NULL) { ranges_inst_jit = TRUE; }
|
|
|
|
if (ranges_debug_maps) { ranges_print_debug_maps(); }
|
|
|
|
include_ranges = collect_ranges("AFL_FRIDA_INST_RANGES");
|
|
exclude_ranges = collect_ranges("AFL_FRIDA_EXCLUDE_RANGES");
|
|
|
|
}
|
|
|
|
void ranges_init(void) {
|
|
|
|
GumMemoryRange ri;
|
|
GArray * step1;
|
|
GArray * step2;
|
|
GArray * step3;
|
|
GArray * step4;
|
|
GArray * step5;
|
|
|
|
FOKF(cBLU "Ranges" cRST " - " cGRN "instrument jit:" cYEL " [%c]",
|
|
ranges_inst_jit ? 'X' : ' ');
|
|
FOKF(cBLU "Ranges" cRST " - " cGRN "instrument libraries:" cYEL " [%c]",
|
|
ranges_inst_libs ? 'X' : ' ');
|
|
FOKF(cBLU "Ranges" cRST " - " cGRN "instrument libraries:" cYEL " [%c]",
|
|
ranges_inst_libs ? 'X' : ' ');
|
|
|
|
print_ranges("include", include_ranges);
|
|
print_ranges("exclude", exclude_ranges);
|
|
|
|
module_ranges = collect_module_ranges();
|
|
libs_ranges = collect_libs_ranges();
|
|
jit_ranges = collect_jit_ranges();
|
|
|
|
/* If include ranges is empty, then assume everything is included */
|
|
if (include_ranges->len == 0) {
|
|
|
|
ri.base_address = 0;
|
|
ri.size = G_MAXULONG;
|
|
g_array_append_val(include_ranges, ri);
|
|
|
|
}
|
|
|
|
/* Intersect with .text section of main executable unless AFL_INST_LIBS */
|
|
step1 = intersect_ranges(module_ranges, libs_ranges);
|
|
print_ranges("step1", step1);
|
|
|
|
/* Intersect with AFL_FRIDA_INST_RANGES */
|
|
step2 = intersect_ranges(step1, include_ranges);
|
|
print_ranges("step2", step2);
|
|
|
|
/* Subtract AFL_FRIDA_EXCLUDE_RANGES */
|
|
step3 = subtract_ranges(step2, exclude_ranges);
|
|
print_ranges("step3", step3);
|
|
|
|
step4 = subtract_ranges(step3, jit_ranges);
|
|
print_ranges("step4", step4);
|
|
|
|
/*
|
|
* After step4, we have the total ranges to be instrumented, we now subtract
|
|
* that from the original ranges of the modules to configure stalker.
|
|
*/
|
|
step5 = subtract_ranges(module_ranges, step4);
|
|
print_ranges("step5", step5);
|
|
|
|
ranges = merge_ranges(step5);
|
|
print_ranges("final", ranges);
|
|
|
|
g_array_free(step5, TRUE);
|
|
g_array_free(step4, TRUE);
|
|
g_array_free(step3, TRUE);
|
|
g_array_free(step2, TRUE);
|
|
g_array_free(step1, TRUE);
|
|
|
|
ranges_exclude();
|
|
|
|
}
|
|
|
|
gboolean range_is_excluded(GumAddress address) {
|
|
|
|
if (ranges == NULL) { return false; }
|
|
|
|
for (guint i = 0; i < ranges->len; i++) {
|
|
|
|
GumMemoryRange *curr = &g_array_index(ranges, GumMemoryRange, i);
|
|
GumAddress curr_limit = curr->base_address + curr->size;
|
|
|
|
if (address < curr->base_address) { return false; }
|
|
|
|
if (address < curr_limit) { return true; }
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
void ranges_exclude() {
|
|
|
|
GumMemoryRange *r;
|
|
GumStalker * stalker = stalker_get();
|
|
|
|
FVERBOSE("Excluding ranges");
|
|
|
|
for (guint i = 0; i < ranges->len; i++) {
|
|
|
|
r = &g_array_index(ranges, GumMemoryRange, i);
|
|
gum_stalker_exclude(stalker, r);
|
|
|
|
}
|
|
|
|
}
|
|
|