diff options
author | Your Name <you@example.com> | 2022-02-01 08:13:28 +0000 |
---|---|---|
committer | Your Name <you@example.com> | 2022-02-01 08:13:28 +0000 |
commit | e2f76dd41e908d37caa6ab41bfe0f1c62f9b501f (patch) | |
tree | 46b29c88afabdb8f6940466782c98a650c32457c | |
parent | 1c79b82ab85da06686bd00f65099d7553d05a802 (diff) | |
download | afl++-e2f76dd41e908d37caa6ab41bfe0f1c62f9b501f.tar.gz |
AARCH64 branch suppression
-rw-r--r-- | frida_mode/src/instrument/instrument_arm64.c | 123 |
1 files changed, 123 insertions, 0 deletions
diff --git a/frida_mode/src/instrument/instrument_arm64.c b/frida_mode/src/instrument/instrument_arm64.c index 57b60317..e1dc2441 100644 --- a/frida_mode/src/instrument/instrument_arm64.c +++ b/frida_mode/src/instrument/instrument_arm64.c @@ -14,9 +14,12 @@ #define PAGE_MASK (~(GUM_ADDRESS(0xfff))) #define PAGE_ALIGNED(x) ((GUM_ADDRESS(x) & PAGE_MASK) == GUM_ADDRESS(x)) +#define GUM_RESTORATION_PROLOG_SIZE 4 #if defined(__aarch64__) +static GHashTable *coverage_blocks = NULL; + __attribute__((aligned(0x1000))) static guint8 area_ptr_dummy[MAP_SIZE]; #pragma pack(push, 1) @@ -120,6 +123,101 @@ gboolean instrument_is_coverage_optimize_supported(void) { } +static void instrument_coverage_switch(GumStalkerObserver *self, + gpointer start_address, + const cs_insn * from_insn, + gpointer * target) { + + UNUSED_PARAMETER(self); + UNUSED_PARAMETER(start_address); + + cs_arm64 * arm64; + arm64_cc cc; + gsize fixup_offset; + + if (from_insn == NULL) { return; } + + arm64 = &from_insn->detail->arm64; + cc = arm64->cc; + + if (!g_hash_table_contains(coverage_blocks, GSIZE_TO_POINTER(*target))) { + + return; + + } + + switch (from_insn->id) { + + case ARM64_INS_B: + case ARM64_INS_BL: + if (cc != ARM64_CC_INVALID) { return; } + break; + + case ARM64_INS_RET: + case ARM64_INS_RETAA: + case ARM64_INS_RETAB: + if (arm64->op_count != 0) { return; } + break; + default: + return; + + } + + /* + * Since each block is prefixed with a restoration prologue, we need to be + * able to begin execution at an offset into the block and execute both this + * restoration prologue and the instrumented block without the coverage code. + * We therefore layout our block as follows: + * + * +-----+--------------------------+------------------+-----+-------------+ + * | LDP | COVERAGE INSTRUMENTATION | BR <TARGET CODE> | LDP | TARGET CODE | + * +-----+--------------------------+------------------+-----+-------------+ + * + * ^ ^ ^ ^ + * | | | | + * A B C D + * + * Without instrumentation suppression, the block is either executed at point + * (A) if it is reached by an indirect branch (and registers need to be + * restored) or point (B) if it is reached by an direct branch (and hence the + * registers don't need restoration). Similarly, we can start execution of the + * block at points (C) or (D) to achieve the same functionality, but without + * executing the coverage instrumentation. + * + * In either case, Stalker will call us back with the address of the target + * block to be executed as the destination. It is not until later that Stalker + * will determine which branch type is required given the location of its + * instrumented code and add the `GUM_RESTORATION_PROLOG_SIZE` to the target + * address. Therefore, we need to map the address of point (A) to that of + * point (C) and also ensure that the offset between (A)->(B) and (C)->(D) is + * identical so that if Stalker can use an immediate call, it still branches + * to a valid offset. + */ + fixup_offset = GUM_RESTORATION_PROLOG_SIZE + + G_STRUCT_OFFSET(afl_log_code_asm_t, restoration_prolog); + *target += fixup_offset; + +} + +static void instrument_coverage_suppress_init(void) { + + static gboolean initialized = false; + if (initialized) { return; } + initialized = true; + + GumStalkerObserver * observer = stalker_get_observer(); + GumStalkerObserverInterface *iface = GUM_STALKER_OBSERVER_GET_IFACE(observer); + iface->switch_callback = instrument_coverage_switch; + + coverage_blocks = g_hash_table_new(g_direct_hash, g_direct_equal); + if (coverage_blocks == NULL) { + + FATAL("Failed to g_hash_table_new, errno: %d", errno); + + } + +} + static gboolean instrument_coverage_in_range(gssize offset) { return (offset >= G_MININT33 && offset <= G_MAXINT33); @@ -151,6 +249,7 @@ void instrument_coverage_optimize(const cs_insn * instr, afl_log_code code = {0}; GumArm64Writer *cw = output->writer.arm64; + gpointer block_start; guint64 area_offset = instrument_get_offset_hash(GUM_ADDRESS(instr->address)); gsize map_size_pow2; gsize area_offset_ror; @@ -171,8 +270,32 @@ void instrument_coverage_optimize(const cs_insn * instr, // gum_arm64_writer_put_brk_imm(cw, 0x0); + instrument_coverage_suppress_init(); + code_addr = cw->pc; + /* + * On AARCH64, immediate branches can only be encoded with a 28-bit offset. To + * make a longer branch, it is necessary to load a register with the target + * address, this register must be saved beyond the red-zone before the branch + * is taken. To restore this register each block is prefixed by Stalker with + * an instruction to load x16,x17 from beyond the red-zone on the stack. A + * pair of registers are saved/restored because on AARCH64, the stack pointer + * must be 16 byte aligned. This instruction is emitted into the block before + * the tranformer (from which we are called) is executed. If is is possible + * for Stalker to make a direct branch (the target block is close enough), it + * can forego pushing the registers and instead branch at an offset into the + * block to skip this restoration prolog. + */ + block_start = + GSIZE_TO_POINTER(GUM_ADDRESS(cw->code) - GUM_RESTORATION_PROLOG_SIZE); + + if (!g_hash_table_add(coverage_blocks, block_start)) { + + FATAL("Failed - g_hash_table_add"); + + } + code.code = template; /* |