From 947d1bad52e40a95f4aba3ff558b832ca8d4f0bc Mon Sep 17 00:00:00 2001 From: s1341 Date: Fri, 10 May 2024 15:21:53 +0300 Subject: [PATCH] stalker: Add StalkerIterator.put_chaining_return() (#777) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit To be able to add early returns. Co-authored-by: Ole André Vadla Ravnås Co-authored-by: sharad --- bindings/gumjs/gumquickstalker.c | 35 +++++++++ bindings/gumjs/gumv8stalker.cpp | 28 +++++++ gum/backend-arm/gumstalker-arm.c | 60 ++++++++++++--- gum/backend-arm64/gumstalker-arm64.c | 22 ++++++ gum/backend-mips/gumstalker-mips.c | 5 ++ gum/backend-x86/gumstalker-x86.c | 35 +++++++-- gum/gumstalker.c | 8 ++ gum/gumstalker.h | 2 + tests/core/arch-arm/stalker-arm.c | 86 +++++++++++++++++++++ tests/core/arch-arm64/stalker-arm64.c | 104 ++++++++++++++++++++++++++ tests/core/arch-x86/stalker-x86.c | 103 +++++++++++++++++++++++++ 11 files changed, 473 insertions(+), 15 deletions(-) diff --git a/bindings/gumjs/gumquickstalker.c b/bindings/gumjs/gumquickstalker.c index 995c1aeb4..b08cfb504 100644 --- a/bindings/gumjs/gumquickstalker.c +++ b/bindings/gumjs/gumquickstalker.c @@ -115,6 +115,7 @@ GUMJS_DECLARE_GETTER (gumjs_default_iterator_get_memory_access) GUMJS_DECLARE_FUNCTION (gumjs_default_iterator_next) GUMJS_DECLARE_FUNCTION (gumjs_default_iterator_keep) GUMJS_DECLARE_FUNCTION (gumjs_default_iterator_put_callout) +GUMJS_DECLARE_FUNCTION (gumjs_default_iterator_put_chaining_return) static JSValue gum_quick_special_iterator_new (GumQuickStalker * parent, GumQuickSpecialIterator ** iterator); @@ -125,6 +126,7 @@ GUMJS_DECLARE_GETTER (gumjs_special_iterator_get_memory_access) GUMJS_DECLARE_FUNCTION (gumjs_special_iterator_next) GUMJS_DECLARE_FUNCTION (gumjs_special_iterator_keep) GUMJS_DECLARE_FUNCTION (gumjs_special_iterator_put_callout) +GUMJS_DECLARE_FUNCTION (gumjs_special_iterator_put_chaining_return) static void gum_quick_callout_free (GumQuickCallout * callout); static void gum_quick_callout_on_invoke (GumCpuContext * cpu_context, @@ -200,6 +202,8 @@ static const JSCFunctionListEntry gumjs_default_iterator_entries[] = JS_CFUNC_DEF ("next", 0, gumjs_default_iterator_next), JS_CFUNC_DEF ("keep", 0, gumjs_default_iterator_keep), JS_CFUNC_DEF ("putCallout", 0, gumjs_default_iterator_put_callout), + JS_CFUNC_DEF ("putChainingReturn", 0, + gumjs_default_iterator_put_chaining_return), }; static const JSClassDef gumjs_special_iterator_def = @@ -215,6 +219,8 @@ static const JSCFunctionListEntry gumjs_special_iterator_entries[] = JS_CFUNC_DEF ("next", 0, gumjs_special_iterator_next), JS_CFUNC_DEF ("keep", 0, gumjs_special_iterator_keep), JS_CFUNC_DEF ("putCallout", 0, gumjs_special_iterator_put_callout), + JS_CFUNC_DEF ("putChainingReturn", 0, + gumjs_special_iterator_put_chaining_return), }; static const JSClassExoticMethods gumjs_probe_args_exotic_methods = @@ -1016,6 +1022,15 @@ gum_quick_stalker_iterator_put_callout (GumQuickIterator * self, return JS_UNDEFINED; } +static JSValue +gum_quick_stalker_iterator_put_chaining_return (GumQuickIterator * self, + JSContext * ctx) +{ + gum_stalker_iterator_put_chaining_return (self->handle); + + return JS_UNDEFINED; +} + static JSValue gum_quick_default_iterator_new (GumQuickStalker * parent, GumQuickDefaultIterator ** iterator) @@ -1135,6 +1150,16 @@ GUMJS_DEFINE_FUNCTION (gumjs_default_iterator_put_callout) return gum_quick_stalker_iterator_put_callout (&self->iterator, ctx, args); } +GUMJS_DEFINE_FUNCTION (gumjs_default_iterator_put_chaining_return) +{ + GumQuickDefaultIterator * self; + + if (!gum_quick_default_iterator_get (ctx, this_val, core, &self)) + return JS_EXCEPTION; + + return gum_quick_stalker_iterator_put_chaining_return (&self->iterator, ctx); +} + static JSValue gum_quick_special_iterator_new (GumQuickStalker * parent, GumQuickSpecialIterator ** iterator) @@ -1254,6 +1279,16 @@ GUMJS_DEFINE_FUNCTION (gumjs_special_iterator_put_callout) return gum_quick_stalker_iterator_put_callout (&self->iterator, ctx, args); } +GUMJS_DEFINE_FUNCTION (gumjs_special_iterator_put_chaining_return) +{ + GumQuickSpecialIterator * self; + + if (!gum_quick_special_iterator_get (ctx, this_val, core, &self)) + return JS_EXCEPTION; + + return gum_quick_stalker_iterator_put_chaining_return (&self->iterator, ctx); +} + static void gum_quick_callout_free (GumQuickCallout * callout) { diff --git a/bindings/gumjs/gumv8stalker.cpp b/bindings/gumjs/gumv8stalker.cpp index 1f90f0eb0..0d5cee1b6 100644 --- a/bindings/gumjs/gumv8stalker.cpp +++ b/bindings/gumjs/gumv8stalker.cpp @@ -133,6 +133,7 @@ GUMJS_DECLARE_GETTER (gumjs_stalker_default_iterator_get_memory_access) GUMJS_DECLARE_FUNCTION (gumjs_stalker_default_iterator_next) GUMJS_DECLARE_FUNCTION (gumjs_stalker_default_iterator_keep) GUMJS_DECLARE_FUNCTION (gumjs_stalker_default_iterator_put_callout) +GUMJS_DECLARE_FUNCTION (gumjs_stalker_default_iterator_put_chaining_return) static GumV8StalkerSpecialIterator * gum_v8_stalker_special_iterator_new_persistent (GumV8Stalker * parent); @@ -149,6 +150,7 @@ GUMJS_DECLARE_GETTER (gumjs_stalker_special_iterator_get_memory_access) GUMJS_DECLARE_FUNCTION (gumjs_stalker_special_iterator_next) GUMJS_DECLARE_FUNCTION (gumjs_stalker_special_iterator_keep) GUMJS_DECLARE_FUNCTION (gumjs_stalker_special_iterator_put_callout) +GUMJS_DECLARE_FUNCTION (gumjs_stalker_special_iterator_put_chaining_return) static void gum_v8_callout_free (GumV8Callout * callout); static void gum_v8_callout_on_invoke (GumCpuContext * cpu_context, @@ -227,6 +229,7 @@ static const GumV8Function gumjs_stalker_default_iterator_functions[] = { "next", gumjs_stalker_default_iterator_next }, { "keep", gumjs_stalker_default_iterator_keep }, { "putCallout", gumjs_stalker_default_iterator_put_callout }, + { "putChainingReturn", gumjs_stalker_default_iterator_put_chaining_return }, { NULL, NULL } }; @@ -243,6 +246,7 @@ static const GumV8Function gumjs_stalker_special_iterator_functions[] = { "next", gumjs_stalker_special_iterator_next }, { "keep", gumjs_stalker_special_iterator_keep }, { "putCallout", gumjs_stalker_special_iterator_put_callout }, + { "putChainingReturn", gumjs_stalker_special_iterator_put_chaining_return }, { NULL, NULL } }; @@ -1083,6 +1087,16 @@ gum_v8_stalker_iterator_put_callout (GumV8StalkerIterator * self, } } +static void +gum_v8_stalker_iterator_put_chaining_return (GumV8StalkerIterator * self, + Isolate * isolate) +{ + if (!gum_v8_stalker_iterator_check_valid (self, isolate)) + return; + + gum_stalker_iterator_put_chaining_return (self->handle); +} + static GumV8StalkerDefaultIterator * gum_v8_stalker_default_iterator_new_persistent (GumV8Stalker * parent) { @@ -1170,6 +1184,13 @@ GUMJS_DEFINE_DIRECT_SUBCLASS_METHOD (gumjs_stalker_default_iterator_put_callout, gum_v8_stalker_iterator_put_callout (&self->iterator, args, isolate); } +GUMJS_DEFINE_DIRECT_SUBCLASS_METHOD ( + gumjs_stalker_default_iterator_put_chaining_return, + GumV8StalkerDefaultIterator) +{ + gum_v8_stalker_iterator_put_chaining_return (&self->iterator, isolate); +} + static GumV8StalkerSpecialIterator * gum_v8_stalker_special_iterator_new_persistent (GumV8Stalker * parent) { @@ -1257,6 +1278,13 @@ GUMJS_DEFINE_DIRECT_SUBCLASS_METHOD (gumjs_stalker_special_iterator_put_callout, gum_v8_stalker_iterator_put_callout (&self->iterator, args, isolate); } +GUMJS_DEFINE_DIRECT_SUBCLASS_METHOD ( + gumjs_stalker_special_iterator_put_chaining_return, + GumV8StalkerSpecialIterator) +{ + gum_v8_stalker_iterator_put_chaining_return (&self->iterator, isolate); +} + static void gum_v8_callout_free (GumV8Callout * callout) { diff --git a/gum/backend-arm/gumstalker-arm.c b/gum/backend-arm/gumstalker-arm.c index dd6875b15..175672d95 100644 --- a/gum/backend-arm/gumstalker-arm.c +++ b/gum/backend-arm/gumstalker-arm.c @@ -2512,7 +2512,7 @@ gum_stalker_iterator_arm_next (GumStalkerIterator * self, return FALSE; } - if (gum_arm_relocator_eob (rl)) + if (!skip_implicitly_requested && gum_arm_relocator_eob (rl)) return FALSE; } @@ -2571,7 +2571,7 @@ gum_stalker_iterator_thumb_next (GumStalkerIterator * self, return FALSE; } - if (gum_thumb_relocator_eob (rl)) + if (!skip_implicitly_requested && gum_thumb_relocator_eob (rl)) return FALSE; } @@ -2769,11 +2769,13 @@ gum_stalker_iterator_handle_thumb_branch_insn (GumStalkerIterator * self, case ARM_INS_MOV: gum_stalker_get_target_address (insn, TRUE, &target, &mask); gum_exec_block_virtualize_thumb_ret_insn (block, &target, FALSE, 0, gc); + gum_thumb_relocator_skip_one (gc->thumb_relocator); break; case ARM_INS_POP: case ARM_INS_LDM: gum_stalker_get_target_address (insn, TRUE, &target, &mask); gum_exec_block_virtualize_thumb_ret_insn (block, &target, TRUE, mask, gc); + gum_thumb_relocator_skip_one (gc->thumb_relocator); break; case ARM_INS_SMC: case ARM_INS_HVC: @@ -2833,14 +2835,6 @@ gum_stalker_iterator_handle_thumb_it_insn (GumStalkerIterator * self) */ insn->detail->arm.cc = ARM_CC_AL; gum_stalker_iterator_handle_thumb_branch_insn (self, insn); - - /* - * Put a breakpoint to trap and detect any errant continued execution (the - * branch should handle any possible continuation). Skip the original - * branch instruction. - */ - gum_thumb_writer_put_breakpoint (gc->thumb_writer); - gum_thumb_relocator_skip_one (gc->thumb_relocator); } else { @@ -3419,6 +3413,31 @@ gum_stalker_invoke_callout (GumCalloutEntry * entry, ec->pending_calls--; } +void +gum_stalker_iterator_put_chaining_return (GumStalkerIterator * self) +{ + GumExecBlock * block = self->exec_block; + GumGeneratorContext * gc = self->generator_context; + GumBranchTarget target; + GumBranchDirectRegOffset * value; + + target.type = GUM_TARGET_DIRECT_REG_OFFSET; + value = &target.value.direct_reg_offset; + value->reg = ARM_REG_LR; + value->offset = 0; + value->mode = GUM_ARM_MODE_CURRENT; + + if (gc->is_thumb) + { + gum_exec_block_virtualize_thumb_ret_insn (block, &target, FALSE, 0, gc); + } + else + { + gum_exec_block_virtualize_arm_ret_insn (block, &target, ARM_CC_AL, FALSE, 0, + gc); + } +} + csh gum_stalker_iterator_get_capstone (GumStalkerIterator * self) { @@ -4572,6 +4591,14 @@ gum_exec_block_virtualize_thumb_branch_insn (GumExecBlock * block, gum_exec_block_write_thumb_handle_writeback (block, writeback, gc); gum_exec_block_write_thumb_exec_generated_code (cw, block->ctx); + + /* + * We MUST do this last to account for IT blocks. + * gum_thumb_relocator_skip_one() will complete the IT branch, so if we do + * this early (like on ARM), then the end branch will be relocated into the + * middle of the relocated branch. + */ + gum_thumb_relocator_skip_one (gc->thumb_relocator); } static void @@ -4629,6 +4656,14 @@ gum_exec_block_virtualize_thumb_call_insn (GumExecBlock * block, gum_thumb_writer_put_ldr_reg_address (gc->thumb_writer, ARM_REG_LR, GUM_ADDRESS (ret_real_address)); gum_exec_block_write_thumb_exec_generated_code (gc->thumb_writer, block->ctx); + + /* + * We MUST do this last to account for IT blocks. + * gum_thumb_relocator_skip_one() will complete the IT branch, so if we do + * this early (like on ARM), then the end branch will be relocated into the + * middle of the relocated branch. + */ + gum_thumb_relocator_skip_one (gc->thumb_relocator); } static void @@ -4688,6 +4723,8 @@ gum_exec_block_virtualize_arm_ret_insn (GumExecBlock * block, } gum_exec_block_write_arm_exec_generated_code (gc->arm_writer, block->ctx); + + gum_arm_relocator_skip_one (gc->arm_relocator); } static void @@ -5124,7 +5161,10 @@ gum_exec_block_write_arm_handle_excluded (GumExecBlock * block, if (target->type == GUM_TARGET_DIRECT_ADDRESS) { if (!check (block->ctx, target->value.direct_address.address)) + { + gum_arm_relocator_skip_one (gc->arm_relocator); return; + } } if (target->type != GUM_TARGET_DIRECT_ADDRESS) diff --git a/gum/backend-arm64/gumstalker-arm64.c b/gum/backend-arm64/gumstalker-arm64.c index 12988a364..823ea4eea 100644 --- a/gum/backend-arm64/gumstalker-arm64.c +++ b/gum/backend-arm64/gumstalker-arm64.c @@ -693,6 +693,8 @@ static void gum_exec_block_write_jmp_transfer_code (GumExecBlock * block, GumGeneratorContext * gc); static void gum_exec_block_write_ret_transfer_code (GumExecBlock * block, GumGeneratorContext * gc, arm64_reg ret_reg); +static void gum_exec_block_write_chaining_return_code (GumExecBlock * block, + GumGeneratorContext * gc, arm64_reg ret_reg); static void gum_exec_block_write_slab_transfer_code (GumArm64Writer * from, GumArm64Writer * to); static void gum_exec_block_backpatch_slab (GumExecBlock * block, @@ -3146,6 +3148,18 @@ gum_stalker_invoke_callout (GumCalloutEntry * entry, ec->pending_calls--; } +void +gum_stalker_iterator_put_chaining_return (GumStalkerIterator * self) +{ + GumExecBlock * block = self->exec_block; + GumGeneratorContext * gc = self->generator_context; + + if ((block->ctx->sink_mask & GUM_RET) != 0) + gum_exec_block_write_ret_event_code (block, gc, GUM_CODE_INTERRUPTIBLE); + + gum_exec_block_write_chaining_return_code (block, gc, ARM64_REG_X30); +} + csh gum_stalker_iterator_get_capstone (GumStalkerIterator * self) { @@ -5093,6 +5107,14 @@ static void gum_exec_block_write_ret_transfer_code (GumExecBlock * block, GumGeneratorContext * gc, arm64_reg ret_reg) +{ + gum_exec_block_write_chaining_return_code (block, gc, ret_reg); +} + +static void +gum_exec_block_write_chaining_return_code (GumExecBlock * block, + GumGeneratorContext * gc, + arm64_reg ret_reg) { GumArm64Writer * cw = gc->code_writer; GumArm64Writer * cws = gc->slow_writer; diff --git a/gum/backend-mips/gumstalker-mips.c b/gum/backend-mips/gumstalker-mips.c index 7dc064c08..8e3517cdf 100644 --- a/gum/backend-mips/gumstalker-mips.c +++ b/gum/backend-mips/gumstalker-mips.c @@ -220,6 +220,11 @@ gum_stalker_iterator_put_callout (GumStalkerIterator * self, { } +void +gum_stalker_iterator_put_chaining_return (GumStalkerIterator * self) +{ +} + csh gum_stalker_iterator_get_capstone (GumStalkerIterator * self) { diff --git a/gum/backend-x86/gumstalker-x86.c b/gum/backend-x86/gumstalker-x86.c index 45a348ede..f8595d836 100644 --- a/gum/backend-x86/gumstalker-x86.c +++ b/gum/backend-x86/gumstalker-x86.c @@ -765,6 +765,8 @@ static void gum_exec_block_write_jmp_transfer_code (GumExecBlock * block, GumGeneratorContext * gc, guint id, GumAddress jcc_address); static void gum_exec_block_write_ret_transfer_code (GumExecBlock * block, GumGeneratorContext * gc); +static void gum_exec_block_write_chaining_return_code (GumExecBlock * block, + GumGeneratorContext * gc, guint16 npop); static gpointer * gum_exec_block_write_inline_cache_code (GumExecBlock * block, GumGeneratorContext * gc, GumX86Writer * cw, GumX86Writer * cws); static void gum_exec_block_backpatch_slab (GumExecBlock * block, @@ -3367,6 +3369,20 @@ gum_stalker_invoke_callout (GumCalloutEntry * entry, ec->pending_calls--; } +void +gum_stalker_iterator_put_chaining_return (GumStalkerIterator * self) +{ + GumExecBlock * block = self->exec_block; + GumGeneratorContext * gc = self->generator_context; + + if ((block->ctx->sink_mask & GUM_RET) != 0) + gum_exec_block_write_ret_event_code (block, gc, GUM_CODE_INTERRUPTIBLE); + + gum_exec_block_write_adjust_depth (block, gc->code_writer, -1); + + gum_exec_block_write_chaining_return_code (block, gc, 0); +} + csh gum_stalker_iterator_get_capstone (GumStalkerIterator * self) { @@ -5608,11 +5624,6 @@ gum_exec_block_write_ret_transfer_code (GumExecBlock * block, cs_x86 * x86 = &insn->ci->detail->x86; cs_x86_op * op = &x86->operands[0]; guint16 npop = 0; - const gint trust_threshold = block->ctx->stalker->trust_threshold; - GumX86Writer * cw = gc->code_writer; - GumX86Writer * cws = gc->slow_writer; - gpointer * ic_match; - GumExecCtx * ctx = block->ctx; if (x86->op_count != 0) { @@ -5622,6 +5633,20 @@ gum_exec_block_write_ret_transfer_code (GumExecBlock * block, npop = op->imm; } + gum_exec_block_write_chaining_return_code (block, gc, npop); +} + +static void +gum_exec_block_write_chaining_return_code (GumExecBlock * block, + GumGeneratorContext * gc, + guint16 npop) +{ + const gint trust_threshold = block->ctx->stalker->trust_threshold; + GumX86Writer * cw = gc->code_writer; + GumX86Writer * cws = gc->slow_writer; + gpointer * ic_match; + GumExecCtx * ctx = block->ctx; + if (trust_threshold >= 0) { gum_exec_block_close_prolog (block, gc, gc->code_writer); diff --git a/gum/gumstalker.c b/gum/gumstalker.c index c7083d112..b4e57433c 100644 --- a/gum/gumstalker.c +++ b/gum/gumstalker.c @@ -469,6 +469,14 @@ gum_callback_stalker_transformer_transform_block ( * Returns: %TRUE if there is a next instruction, else %FALSE */ +/** + * gum_stalker_iterator_put_chaining_return: + * @self: a #GumStalkerIterator + * + * Puts a chaining return at the current location in the output + * instruction stream. + */ + static void gum_stalker_observer_default_init (GumStalkerObserverInterface * iface) { diff --git a/gum/gumstalker.h b/gum/gumstalker.h index 17b796e8a..81cc18aed 100644 --- a/gum/gumstalker.h +++ b/gum/gumstalker.h @@ -235,6 +235,8 @@ GUM_API GumMemoryAccess gum_stalker_iterator_get_memory_access ( GumStalkerIterator * self); GUM_API void gum_stalker_iterator_put_callout (GumStalkerIterator * self, GumStalkerCallout callout, gpointer data, GDestroyNotify data_destroy); +GUM_API void gum_stalker_iterator_put_chaining_return ( + GumStalkerIterator * self); GUM_API csh gum_stalker_iterator_get_capstone (GumStalkerIterator * self); #define GUM_DECLARE_OBSERVER_INCREMENT(name) \ diff --git a/tests/core/arch-arm/stalker-arm.c b/tests/core/arch-arm/stalker-arm.c index 0a8343c80..f100bfb2d 100644 --- a/tests/core/arch-arm/stalker-arm.c +++ b/tests/core/arch-arm/stalker-arm.c @@ -100,6 +100,8 @@ TESTLIST_BEGIN (stalker) TESTENTRY (custom_transformer) TESTENTRY (arm_callout) TESTENTRY (thumb_callout) + TESTENTRY (arm_transformer_should_be_able_to_replace_call_with_callout) + TESTENTRY (arm_transformer_should_be_able_to_replace_jumpout_with_callout) TESTENTRY (unfollow_should_be_allowed_before_first_transform) TESTENTRY (unfollow_should_be_allowed_mid_first_transform) TESTENTRY (unfollow_should_be_allowed_after_first_transform) @@ -172,6 +174,11 @@ static void transform_thumb_return_value (GumStalkerIterator * iterator, static void on_thumb_ret (GumCpuContext * cpu_context, gpointer user_data); static gboolean is_thumb_pop_pc (const guint8 * bytes, gsize size); +static void replace_call_with_callout (GumStalkerIterator * iterator, + GumStalkerOutput * output, gpointer user_data); +static void replace_jumpout_with_callout (GumStalkerIterator * iterator, + GumStalkerOutput * output, gpointer user_data); +static void callout_set_cool (GumCpuContext * cpu_context, gpointer user_data); static void unfollow_during_transform (GumStalkerIterator * iterator, GumStalkerOutput * output, gpointer user_data); static void test_invalidation_for_current_thread_with_target (GumAddress target, @@ -2773,6 +2780,85 @@ is_thumb_pop_pc (const guint8 * bytes, return memcmp (bytes, pop_pc, size) == 0; } +TESTCODE (arm_simple_call, + 0x04, 0xe0, 0x2d, 0xe5, /* push {lr} */ + 0x0d, 0x00, 0x00, 0xe3, /* mov r0, 13 */ + 0x00, 0x00, 0x00, 0xeb, /* bl bump_number */ + 0x04, 0xf0, 0x9d, 0xe4, /* pop {pc} */ + /* bump_number: */ + 0x25, 0x00, 0x80, 0xe2, /* add r0, 37 */ + 0x1e, 0xff, 0x2f, 0xe1, /* bx lr */ +); + +TESTCASE (arm_transformer_should_be_able_to_replace_call_with_callout) +{ + fixture->transformer = gum_stalker_transformer_make_from_callback ( + replace_call_with_callout, NULL, NULL); + + INVOKE_ARM_EXPECTING (GUM_NOTHING, arm_simple_call, 0xc001); +} + +static void +replace_call_with_callout (GumStalkerIterator * iterator, + GumStalkerOutput * output, + gpointer user_data) +{ + const cs_insn * insn; + static int insn_num = 0; + + while (gum_stalker_iterator_next (iterator, &insn)) + { + if (insn_num == 4) + gum_stalker_iterator_put_callout (iterator, callout_set_cool, NULL, NULL); + else + gum_stalker_iterator_keep (iterator); + insn_num++; + } +} + +TESTCODE (arm_simple_jumpout, + 0x0d, 0x00, 0x00, 0xe3, /* mov r0, 13 */ + 0xff, 0xff, 0xff, 0xea, /* b bump_number */ + /* bump_number: */ + 0x25, 0x00, 0x80, 0xe2, /* add r0, 37 */ + 0x1e, 0xff, 0x2f, 0xe1, /* bx lr */ +); + +TESTCASE (arm_transformer_should_be_able_to_replace_jumpout_with_callout) +{ + fixture->transformer = gum_stalker_transformer_make_from_callback ( + replace_jumpout_with_callout, NULL, NULL); + + INVOKE_ARM_EXPECTING (GUM_EXEC, arm_simple_jumpout, 0xc001); +} + +static void +replace_jumpout_with_callout (GumStalkerIterator * iterator, + GumStalkerOutput * output, + gpointer user_data) +{ + const cs_insn * insn; + + while (gum_stalker_iterator_next (iterator, &insn)) + { + if (insn->id == ARM_INS_B) + { + gum_stalker_iterator_put_callout (iterator, callout_set_cool, NULL, NULL); + gum_stalker_iterator_put_chaining_return (iterator); + continue; + } + + gum_stalker_iterator_keep (iterator); + } +} + +static void +callout_set_cool (GumCpuContext * cpu_context, + gpointer user_data) +{ + cpu_context->r[0] = 0xc001; +} + TESTCASE (unfollow_should_be_allowed_before_first_transform) { UnfollowTransformContext ctx; diff --git a/tests/core/arch-arm64/stalker-arm64.c b/tests/core/arch-arm64/stalker-arm64.c index e0007557e..abfa20e41 100644 --- a/tests/core/arch-arm64/stalker-arm64.c +++ b/tests/core/arch-arm64/stalker-arm64.c @@ -32,6 +32,8 @@ TESTLIST_BEGIN (stalker) /* TRANSFORMERS */ TESTENTRY (custom_transformer) TESTENTRY (transformer_should_be_able_to_skip_call) + TESTENTRY (transformer_should_be_able_to_replace_call_with_callout) + TESTENTRY (transformer_should_be_able_to_replace_tailjump_with_callout) TESTENTRY (unfollow_should_be_allowed_before_first_transform) TESTENTRY (unfollow_should_be_allowed_mid_first_transform) TESTENTRY (unfollow_should_be_allowed_after_first_transform) @@ -123,6 +125,11 @@ static void insert_extra_add_after_sub (GumStalkerIterator * iterator, static void store_x0 (GumCpuContext * cpu_context, gpointer user_data); static void skip_call (GumStalkerIterator * iterator, GumStalkerOutput * output, gpointer user_data); +static void replace_call_with_callout (GumStalkerIterator * iterator, + GumStalkerOutput * output, gpointer user_data); +static void replace_jmp_with_callout (GumStalkerIterator * iterator, + GumStalkerOutput * output, gpointer user_data); +static void callout_set_cool (GumCpuContext * cpu_context, gpointer user_data); static void unfollow_during_transform (GumStalkerIterator * iterator, GumStalkerOutput * output, gpointer user_data); static gboolean test_is_finished (void); @@ -544,6 +551,103 @@ skip_call (GumStalkerIterator * iterator, } } +TESTCASE (transformer_should_be_able_to_replace_call_with_callout) +{ + guint32 code_template[] = + { + 0xa9bf7bfd, /* push {x29, x30} */ + 0xd280a280, /* mov x0, #1300 */ + 0x94000003, /* bl bump_number */ + 0xa8c17bfd, /* pop {x29, x30} */ + 0xd65f03c0, /* ret */ + /* bump_number: */ + 0x91009400, /* add x0, x0, #37 */ + 0xd65f03c0, /* ret */ + }; + StalkerTestFunc func; + gint ret; + + func = (StalkerTestFunc) test_arm64_stalker_fixture_dup_code (fixture, + code_template, sizeof (code_template)); + + fixture->transformer = gum_stalker_transformer_make_from_callback ( + replace_call_with_callout, func, NULL); + + ret = test_arm64_stalker_fixture_follow_and_invoke (fixture, func, 0); + g_assert_cmpuint (ret, ==, 0xc001); +} + +static void +replace_call_with_callout (GumStalkerIterator * iterator, + GumStalkerOutput * output, + gpointer user_data) +{ + const guint32 * func_start = user_data; + const cs_insn * insn; + + while (gum_stalker_iterator_next (iterator, &insn)) + { + if (insn->address == GPOINTER_TO_SIZE (func_start + 2)) + { + gum_stalker_iterator_put_callout (iterator, callout_set_cool, NULL, NULL); + continue; + } + + gum_stalker_iterator_keep (iterator); + } +} + +TESTCASE (transformer_should_be_able_to_replace_tailjump_with_callout) +{ + guint32 code_template[] = + { + 0xd280a280, /* mov x0, #1300 */ + 0x14000001, /* b bump_number */ + /* bump_number: */ + 0x91009400, /* add x0, x0, #37 */ + 0xd65f03c0, /* ret */ + }; + StalkerTestFunc func; + gint ret; + + func = (StalkerTestFunc) test_arm64_stalker_fixture_dup_code (fixture, + code_template, sizeof (code_template)); + + fixture->transformer = gum_stalker_transformer_make_from_callback ( + replace_jmp_with_callout, func, NULL); + + ret = test_arm64_stalker_fixture_follow_and_invoke (fixture, func, 0); + g_assert_cmpuint (ret, ==, 0xc001); +} + +static void +replace_jmp_with_callout (GumStalkerIterator * iterator, + GumStalkerOutput * output, + gpointer user_data) +{ + const guint32 * func_start = user_data; + const cs_insn * insn; + + while (gum_stalker_iterator_next (iterator, &insn)) + { + if (insn->address == GPOINTER_TO_SIZE (func_start + 1)) + { + gum_stalker_iterator_put_callout (iterator, callout_set_cool, NULL, NULL); + gum_stalker_iterator_put_chaining_return (iterator); + continue; + } + + gum_stalker_iterator_keep (iterator); + } +} + +static void +callout_set_cool (GumCpuContext * cpu_context, + gpointer user_data) +{ + cpu_context->x[0] = 0xc001; +} + TESTCASE (unfollow_should_be_allowed_before_first_transform) { UnfollowTransformContext ctx; diff --git a/tests/core/arch-x86/stalker-x86.c b/tests/core/arch-x86/stalker-x86.c index 683afaca6..82849e6b2 100644 --- a/tests/core/arch-x86/stalker-x86.c +++ b/tests/core/arch-x86/stalker-x86.c @@ -31,6 +31,8 @@ TESTLIST_BEGIN (stalker) TESTENTRY (call_probe) TESTENTRY (custom_transformer) TESTENTRY (transformer_should_be_able_to_skip_call) + TESTENTRY (transformer_should_be_able_to_replace_call_with_callout) + TESTENTRY (transformer_should_be_able_to_replace_tailjump_with_callout) TESTENTRY (unfollow_should_be_allowed_before_first_transform) TESTENTRY (unfollow_should_be_allowed_mid_first_transform) TESTENTRY (unfollow_should_be_allowed_after_first_transform) @@ -192,6 +194,11 @@ static void insert_extra_increment_after_xor (GumStalkerIterator * iterator, static void store_xax (GumCpuContext * cpu_context, gpointer user_data); static void skip_call (GumStalkerIterator * iterator, GumStalkerOutput * output, gpointer user_data); +static void replace_call_with_callout (GumStalkerIterator * iterator, + GumStalkerOutput * output, gpointer user_data); +static void replace_jmp_with_callout (GumStalkerIterator * iterator, + GumStalkerOutput * output, gpointer user_data); +static void callout_set_cool (GumCpuContext * cpu_context, gpointer user_data); static void unfollow_during_transform (GumStalkerIterator * iterator, GumStalkerOutput * output, gpointer user_data); static void modify_to_return_true_after_three_calls ( @@ -1005,6 +1012,102 @@ skip_call (GumStalkerIterator * iterator, } } +TESTCASE (transformer_should_be_able_to_replace_call_with_callout) +{ + guint8 code_template[] = + { + 0xb8, 0x14, 0x05, 0x00, 0x00, /* mov eax, 1300 */ + 0xe8, 0x01, 0x00, 0x00, 0x00, /* call bump_number */ + 0xc3, /* ret */ + /* bump_number: */ + 0x83, 0xc0, 0x25, /* add eax, 37 */ + 0xc3, /* ret */ + }; + StalkerTestFunc func; + gint ret; + + func = (StalkerTestFunc) test_stalker_fixture_dup_code (fixture, + code_template, sizeof (code_template)); + + fixture->transformer = gum_stalker_transformer_make_from_callback ( + replace_call_with_callout, func, NULL); + + ret = test_stalker_fixture_follow_and_invoke (fixture, func, 0); + g_assert_cmpuint (ret, ==, 0xc001); +} + +static void +replace_call_with_callout (GumStalkerIterator * iterator, + GumStalkerOutput * output, + gpointer user_data) +{ + const guint8 * func_start = user_data; + const cs_insn * insn; + + while (gum_stalker_iterator_next (iterator, &insn)) + { + if (insn->address == GPOINTER_TO_SIZE (func_start + 5)) + { + gum_stalker_iterator_put_callout (iterator, callout_set_cool, NULL, NULL); + continue; + } + + gum_stalker_iterator_keep (iterator); + } +} + +TESTCASE (transformer_should_be_able_to_replace_tailjump_with_callout) +{ + guint8 code_template[] = + { + 0xb8, 0x14, 0x05, 0x00, 0x00, /* mov eax, 1300 */ + 0xeb, 0x01, /* jmp bump_number */ + 0x90, /* nop */ + /* bump_number: */ + 0x83, 0xc0, 0x25, /* add eax, 37 */ + 0xc3, /* ret */ + }; + StalkerTestFunc func; + gint ret; + + func = (StalkerTestFunc) test_stalker_fixture_dup_code (fixture, + code_template, sizeof (code_template)); + + fixture->transformer = gum_stalker_transformer_make_from_callback ( + replace_jmp_with_callout, func, NULL); + + ret = test_stalker_fixture_follow_and_invoke (fixture, func, 0); + g_assert_cmpuint (ret, ==, 0xc001); +} + +static void +replace_jmp_with_callout (GumStalkerIterator * iterator, + GumStalkerOutput * output, + gpointer user_data) +{ + const guint8 * func_start = user_data; + const cs_insn * insn; + + while (gum_stalker_iterator_next (iterator, &insn)) + { + if (insn->address == GPOINTER_TO_SIZE (func_start + 5)) + { + gum_stalker_iterator_put_callout (iterator, callout_set_cool, NULL, NULL); + gum_stalker_iterator_put_chaining_return (iterator); + continue; + } + + gum_stalker_iterator_keep (iterator); + } +} + +static void +callout_set_cool (GumCpuContext * cpu_context, + gpointer user_data) +{ + GUM_CPU_CONTEXT_XAX (cpu_context) = 0xc001; +} + TESTCASE (unfollow_should_be_allowed_before_first_transform) { UnfollowTransformContext ctx;