blob: e3f09f9ea4b6d1d4b0af67a2988ccfc9af8e6117 [file] [log] [blame]
#include "frida-gum.h"
#include "debug.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;
GArray *module_ranges = NULL;
GArray *libs_ranges = NULL;
GArray *include_ranges = NULL;
GArray *exclude_ranges = NULL;
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) {
FATAL("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")) {
FATAL("Invalid range: %s - Start address should have 0x prefix: %s\n",
token, from_str);
}
if (!g_str_has_prefix(to_str, "0x")) {
FATAL("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)) {
FATAL("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)) {
FATAL("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) {
FATAL("Invalid range: %s - Start failed hex conversion: %s\n", token,
from_str);
}
guint64 to = g_ascii_strtoull(to_str, NULL, 16);
if (to == 0) {
FATAL("Invalid range: %s - End failed hex conversion: %s\n", token, to_str);
}
if (from >= to) {
FATAL("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; };
OKF("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) { FATAL("Failed to resolve module: %s\n", token); }
g_free(suffix);
}
static void convert_token(gchar *token, GumMemoryRange *range) {
if (g_strrstr(token, "-")) {
convert_address_token(token, range);
} else {
convert_name_token(token, range);
}
OKF("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) {
return ((GumMemoryRange *)a)->base_address -
((GumMemoryRange *)b)->base_address;
}
static gboolean print_ranges_callback(const GumRangeDetails *details,
gpointer user_data) {
UNUSED_PARAMETER(user_data);
if (details->file == NULL) {
OKF("MAP - 0x%016" G_GINT64_MODIFIER "x - 0x%016" G_GINT64_MODIFIER "X",
details->range->base_address,
details->range->base_address + details->range->size);
} else {
OKF("MAP - 0x%016" G_GINT64_MODIFIER "x - 0x%016" G_GINT64_MODIFIER
"X %s(0x%016" G_GINT64_MODIFIER "x)",
details->range->base_address,
details->range->base_address + details->range->size,
details->file->path, details->file->offset);
}
return true;
}
static void print_ranges(char *key, GArray *ranges) {
OKF("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;
OKF("Range: %s Idx: %3d - 0x%016" G_GINT64_MODIFIER
"x-0x%016" G_GINT64_MODIFIER "x",
key, 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 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 */
for (i = 1; i < token_count; i++) {
GumMemoryRange *prev = &g_array_index(result, GumMemoryRange, i - 1);
GumMemoryRange *curr = &g_array_index(result, GumMemoryRange, i);
GumAddress prev_limit = prev->base_address + prev->size;
GumAddress curr_limit = curr->base_address + curr->size;
if (prev_limit > curr->base_address) {
FATAL("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);
}
}
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 (getenv("AFL_INST_LIBS") == NULL) {
range.base_address = lib_get_text_base();
range.size = lib_get_text_limit() - lib_get_text_base();
} else {
range.base_address = 0;
range.size = G_MAXULONG;
}
g_array_append_val(result, range);
print_ranges("AFL_INST_LIBS", 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_init(void) {
GumMemoryRange ri;
GArray * step1;
GArray * step2;
GArray * step3;
GArray * step4;
GumMemoryRange *r;
GumStalker * stalker;
if (getenv("AFL_FRIDA_DEBUG_MAPS") != NULL) {
gum_process_enumerate_ranges(GUM_PAGE_NO_ACCESS, print_ranges_callback,
NULL);
}
module_ranges = collect_module_ranges();
libs_ranges = collect_libs_ranges();
include_ranges = collect_ranges("AFL_FRIDA_INST_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);
}
exclude_ranges = collect_ranges("AFL_FRIDA_EXCLUDE_RANGES");
/* 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);
/*
* After step3, we have the total ranges to be instrumented, we now subtract
* that from the original ranges of the modules to configure stalker.
*/
step4 = subtract_ranges(module_ranges, step3);
print_ranges("step4", step4);
ranges = merge_ranges(step4);
print_ranges("final", ranges);
stalker = stalker_get();
for (guint i = 0; i < ranges->len; i++) {
r = &g_array_index(ranges, GumMemoryRange, i);
gum_stalker_exclude(stalker, r);
}
g_array_free(step4, TRUE);
g_array_free(step3, TRUE);
g_array_free(step2, TRUE);
g_array_free(step1, TRUE);
}
gboolean range_is_excluded(gpointer address) {
GumAddress test = GUM_ADDRESS(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 (test < curr->base_address) { return false; }
if (test < curr_limit) { return true; }
}
return false;
}