From 3635815bec71a35fef3a7514335ab263756d479c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ole=20Andr=C3=A9=20Vadla=20Ravn=C3=A5s?= Date: Tue, 15 Apr 2014 18:03:59 +0200 Subject: [PATCH] Improve ARM64 relocator to support `adr` and `adrp` --- gum/arch-arm64/gumarm64.h | 6 +- gum/arch-arm64/gumarm64relocator.c | 106 ++++++++++++++---- gum/arch-arm64/gumarm64relocator.h | 1 + .../core/arch-arm64/arm64relocator-fixture.c | 4 +- tests/core/arch-arm64/arm64relocator.c | 60 ++++++++++ 5 files changed, 154 insertions(+), 23 deletions(-) diff --git a/gum/arch-arm64/gumarm64.h b/gum/arch-arm64/gumarm64.h index 7b3553a0e..5dd84d242 100644 --- a/gum/arch-arm64/gumarm64.h +++ b/gum/arch-arm64/gumarm64.h @@ -30,7 +30,10 @@ typedef struct _GumArm64Instruction GumArm64Instruction; enum _GumArm64Mnemonic { - GUM_ARM64_UNKNOWN + GUM_ARM64_UNKNOWN, + + GUM_ARM64_ADR, + GUM_ARM64_ADRP }; enum _GumArm64Reg @@ -115,6 +118,7 @@ struct _GumArm64Instruction gconstpointer address; guint length; + GumAddress pc; }; G_END_DECLS diff --git a/gum/arch-arm64/gumarm64relocator.c b/gum/arch-arm64/gumarm64relocator.c index d13d34490..cae83ba43 100644 --- a/gum/arch-arm64/gumarm64relocator.c +++ b/gum/arch-arm64/gumarm64relocator.c @@ -30,7 +30,7 @@ typedef struct _GumCodeGenCtx GumCodeGenCtx; struct _GumCodeGenCtx { const GumArm64Instruction * insn; - const guint32 * raw_insn; + guint32 raw_insn; const guint8 * start; const guint8 * end; guint len; @@ -38,8 +38,8 @@ struct _GumCodeGenCtx GumArm64Writer * output; }; -static gboolean gum_arm64_relocator_write_one_instruction ( - GumArm64Relocator * self); +static gboolean gum_arm64_relocator_rewrite_adr (GumArm64Relocator * self, + GumCodeGenCtx * ctx); void gum_arm64_relocator_init (GumArm64Relocator * relocator, @@ -57,10 +57,13 @@ gum_arm64_relocator_reset (GumArm64Relocator * relocator, gconstpointer input_code, GumArm64Writer * output) { - relocator->input_start = relocator->input_cur = input_code; + relocator->input_start = input_code; + relocator->input_cur = input_code; + relocator->input_pc = GUM_ADDRESS (input_code); relocator->output = output; - relocator->inpos = relocator->outpos = 0; + relocator->inpos = 0; + relocator->outpos = 0; relocator->eob = FALSE; relocator->eoi = FALSE; @@ -100,20 +103,28 @@ gum_arm64_relocator_increment_outpos (GumArm64Relocator * self) guint gum_arm64_relocator_read_one (GumArm64Relocator * self, - const GumArm64Instruction ** instruction) + const GumArm64Instruction ** instruction) { guint32 raw_insn; GumArm64Instruction * insn; + guint32 adr_bits; if (self->eoi) return 0; - raw_insn = *((guint32 *) self->input_cur); + raw_insn = GUINT32_FROM_LE (*((guint32 *) self->input_cur)); insn = &self->input_insns[gum_arm64_relocator_inpos (self)]; insn->mnemonic = GUM_ARM64_UNKNOWN; - insn->length = 4; insn->address = self->input_cur; + insn->length = 4; + insn->pc = self->input_pc; + + adr_bits = raw_insn & 0x9f000000; + if (adr_bits == 0x10000000) + insn->mnemonic = GUM_ARM64_ADR; + else if (adr_bits == 0x90000000) + insn->mnemonic = GUM_ARM64_ADRP; gum_arm64_relocator_increment_inpos (self); @@ -121,6 +132,7 @@ gum_arm64_relocator_read_one (GumArm64Relocator * self, *instruction = insn; self->input_cur += insn->length; + self->input_pc += insn->length; return self->input_cur - self->input_start; } @@ -161,30 +173,33 @@ gboolean gum_arm64_relocator_write_one (GumArm64Relocator * self) { GumArm64Instruction * cur; + GumCodeGenCtx ctx; + gboolean rewritten = FALSE; if ((cur = gum_arm64_relocator_peek_next_write_insn (self)) == NULL) return FALSE; - return gum_arm64_relocator_write_one_instruction (self); -} - -static gboolean -gum_arm64_relocator_write_one_instruction (GumArm64Relocator * self) -{ - GumCodeGenCtx ctx; - gboolean rewritten = FALSE; - if ((ctx.insn = gum_arm64_relocator_peek_next_write_insn (self)) == NULL) return FALSE; gum_arm64_relocator_increment_outpos (self); ctx.len = ctx.insn->length; - ctx.raw_insn = ctx.insn->address; + ctx.raw_insn = GUINT32_FROM_LE (*((guint32 *) ctx.insn->address)); ctx.start = ctx.insn->address; ctx.end = ctx.start + ctx.len; ctx.output = self->output; + switch (ctx.insn->mnemonic) + { + case GUM_ARM64_ADR: + case GUM_ARM64_ADRP: + rewritten = gum_arm64_relocator_rewrite_adr (self, &ctx); + break; + default: + break; + } + if (!rewritten) gum_arm64_writer_put_bytes (ctx.output, ctx.start, ctx.len); @@ -216,7 +231,7 @@ gum_arm64_relocator_eoi (GumArm64Relocator * self) gboolean gum_arm64_relocator_can_relocate (gpointer address, - guint min_bytes) + guint min_bytes) { guint8 * buf; GumArm64Writer cw; @@ -245,8 +260,8 @@ gum_arm64_relocator_can_relocate (gpointer address, guint gum_arm64_relocator_relocate (gpointer from, - guint min_bytes, - gpointer to) + guint min_bytes, + gpointer to) { GumArm64Writer cw; GumArm64Relocator rl; @@ -270,3 +285,52 @@ gum_arm64_relocator_relocate (gpointer from, return reloc_bytes; } + +static gboolean +gum_arm64_relocator_rewrite_adr (GumArm64Relocator * self, + GumCodeGenCtx * ctx) +{ + GumArm64Reg reg; + guint64 imm_hi, imm_lo; + guint64 negative_mask; + union + { + gint64 i; + guint64 u; + } distance; + GumAddress absolute_target; + + reg = ctx->raw_insn & 0x1f; + imm_hi = (ctx->raw_insn >> 5) & 0x7ffff; + imm_lo = (ctx->raw_insn >> 29) & 3; + + if (ctx->insn->mnemonic == GUM_ARM64_ADR) + { + negative_mask = G_GUINT64_CONSTANT (0xffffffffffe00000); + + if ((imm_hi & 0x40000) != 0) + distance.u = negative_mask | (imm_hi << 2) | imm_lo; + else + distance.u = (imm_hi << 2) | imm_lo; + } + else if (ctx->insn->mnemonic == GUM_ARM64_ADRP) + { + negative_mask = G_GUINT64_CONSTANT (0xfffffffe00000000); + + if ((imm_hi & 0x40000) != 0) + distance.u = negative_mask | (imm_hi << 14) | (imm_lo << 12); + else + distance.u = (imm_hi << 14) | (imm_lo << 12); + } + else + { + g_assert_not_reached (); + } + + absolute_target = ctx->insn->pc + distance.i; + + gum_arm64_writer_put_ldr_reg_address (ctx->output, reg, + absolute_target); + + return TRUE; +} diff --git a/gum/arch-arm64/gumarm64relocator.h b/gum/arch-arm64/gumarm64relocator.h index 4225e9610..916eb4cda 100644 --- a/gum/arch-arm64/gumarm64relocator.h +++ b/gum/arch-arm64/gumarm64relocator.h @@ -32,6 +32,7 @@ struct _GumArm64Relocator { const guint8 * input_start; const guint8 * input_cur; + GumAddress input_pc; GumArm64Instruction * input_insns; GumArm64Writer * output; diff --git a/tests/core/arch-arm64/arm64relocator-fixture.c b/tests/core/arch-arm64/arm64relocator-fixture.c index 93141f7a1..18a8666d1 100644 --- a/tests/core/arch-arm64/arm64relocator-fixture.c +++ b/tests/core/arch-arm64/arm64relocator-fixture.c @@ -47,6 +47,7 @@ test_arm64_relocator_fixture_setup (TestArm64RelocatorFixture * fixture, fixture->output = (guint8 *) gum_alloc_n_pages (1, GUM_PAGE_RW); gum_arm64_writer_init (&fixture->aw, fixture->output); + fixture->aw.pc = 1024; } static void @@ -61,7 +62,8 @@ test_arm64_relocator_fixture_teardown (TestArm64RelocatorFixture * fixture, static const guint8 cleared_outbuf[TEST_OUTBUF_SIZE] = { 0, }; #define SETUP_RELOCATOR_WITH(CODE) \ - gum_arm64_relocator_init (&fixture->rl, CODE, &fixture->aw) + gum_arm64_relocator_init (&fixture->rl, CODE, &fixture->aw); \ + fixture->rl.input_pc = 2048 #define assert_outbuf_still_zeroed_from_offset(OFF) \ g_assert_cmpint (memcmp (fixture->output + OFF, cleared_outbuf + OFF, \ diff --git a/tests/core/arch-arm64/arm64relocator.c b/tests/core/arch-arm64/arm64relocator.c index 28d6c5a79..d24888ada 100644 --- a/tests/core/arch-arm64/arm64relocator.c +++ b/tests/core/arch-arm64/arm64relocator.c @@ -21,6 +21,8 @@ TEST_LIST_BEGIN (arm64relocator) TESTENTRY (one_to_one) + TESTENTRY (adr_should_be_rewritten) + TESTENTRY (adrp_should_be_rewritten) TEST_LIST_END () TESTCASE (one_to_one) @@ -52,3 +54,61 @@ TESTCASE (one_to_one) g_assert (!gum_arm64_relocator_write_one (&fixture->rl)); } +TESTCASE (adr_should_be_rewritten) +{ + const guint32 input[] = { + GUINT32_TO_LE (0x5000a721) /* adr x1, 0x14e6 */ + }; + const guint32 expected_output_instructions[] = { + GUINT32_TO_LE (0x58000021), /* ldr x1, [pc, #4] */ + 0xffffffff, /* */ + }; + gchar expected_output[3 * sizeof (guint32)]; + guint64 calculated_pc; + const GumArm64Instruction * insn; + + SETUP_RELOCATOR_WITH (input); + + memcpy (expected_output, expected_output_instructions, + sizeof (expected_output_instructions)); + calculated_pc = fixture->rl.input_pc + 0x14e6; + *((guint64 *) (expected_output + 4)) = GUINT64_TO_LE (calculated_pc); + + g_assert_cmpuint (gum_arm64_relocator_read_one (&fixture->rl, &insn), ==, 4); + g_assert_cmpint (insn->mnemonic, ==, GUM_ARM64_ADR); + g_assert (gum_arm64_relocator_write_one (&fixture->rl)); + gum_arm64_writer_flush (&fixture->aw); + g_assert_cmpint (memcmp (fixture->output, expected_output, + sizeof (expected_output)), ==, 0); +} + +TESTCASE (adrp_should_be_rewritten) +{ + const guint32 input[] = { + GUINT32_TO_LE (0xd000a723) /* adrp x3, 0x14e6000 */ + }; + const guint32 expected_output_instructions[] = { + GUINT32_TO_LE (0x58000023), /* ldr x3, [pc, #4] */ + 0xffffffff, /* */ + }; + gchar expected_output[3 * sizeof (guint32)]; + guint64 calculated_pc; + const GumArm64Instruction * insn; + + SETUP_RELOCATOR_WITH (input); + + memcpy (expected_output, expected_output_instructions, + sizeof (expected_output_instructions)); + calculated_pc = fixture->rl.input_pc + 0x14e6000; + *((guint64 *) (expected_output + 4)) = GUINT64_TO_LE (calculated_pc); + + g_assert_cmpuint (gum_arm64_relocator_read_one (&fixture->rl, &insn), ==, 4); + g_assert_cmpint (insn->mnemonic, ==, GUM_ARM64_ADRP); + g_assert (gum_arm64_relocator_write_one (&fixture->rl)); + gum_arm64_writer_flush (&fixture->aw); + g_assert_cmpint (memcmp (fixture->output, expected_output, + sizeof (expected_output)), ==, 0); +} +