Files
AFLplusplus/frida_mode/src/ranges.c
2021-12-20 18:14:57 +00:00

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