frida-gum简介 Frida Gum 是一个底层代码插桩库,可在多个平台和架构上提供动态二进制插桩功能。它支持通过函数钩子(fun hooking GumInterceptor)、指令级跟踪(include-level tracing GumStalker)、内存访问监控(memory access monitoring GumMemoryAccessMonitor)和代码生成来运行时操作本地代码。该库支持 Darwin(macOS/iOS)、Linux、Windows、FreeBSD 和 QNX 平台上的 x86、x86_64、ARM、ARM64 和 MIPS 架构。
Hook函数的JS Bindings入口 以Native层的Hook为例
1 2 Interceptor . replace(addr, new NativeCallback() , retType, paramTypes)Interceptor . attach(addr, {onEnter(args ) {}, onLeave(retval ) {}})
这些JS API在bindings/gumjs/gumquickinterceptor.c绑定了Native层函数。
Interceptor.attach绑定gumjs_interceptor_attach函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 GUMJS_DEFINE_FUNCTION (gumjs_interceptor_attach) { JSValue target_val = args->elements[0 ]; JSValue cb_val = args->elements[1 ]; JSValue data_val = args->elements[2 ]; GumQuickInterceptor * self; gpointer target, cb_ptr; GumQuickInvocationListener * listener = NULL ; gpointer listener_function_data; GumAttachReturn attach_ret; self = gumjs_get_parent_module (core); else { JSValue on_enter_js, on_leave_js; GumQuickCHook on_enter_c, on_leave_c; if (!_gum_quick_args_parse (args, "pF*{onEnter?,onLeave?}" , &target, &on_enter_js, &on_enter_c, &on_leave_js, &on_leave_c)) goto propagate_exception; if (!JS_IsNull (on_enter_js) || !JS_IsNull (on_leave_js)) { GumQuickJSCallListener * l; l = g_object_new (GUM_QUICK_TYPE_JS_CALL_LISTENER, NULL ); l->on_enter = JS_DupValue (ctx, on_enter_js); l->on_leave = JS_DupValue (ctx, on_leave_js); listener = GUM_QUICK_INVOCATION_LISTENER (l); } else if (on_enter_c != NULL || on_leave_c != NULL ) { GumQuickCCallListener * l; l = g_object_new (GUM_QUICK_TYPE_C_CALL_LISTENER, NULL ); l->on_enter = on_enter_c; l->on_leave = on_leave_c; listener = GUM_QUICK_INVOCATION_LISTENER (l); } } listener->parent = self; attach_ret = gum_interceptor_attach (self->interceptor, target, GUM_INVOCATION_LISTENER (listener), listener_function_data, GUM_ATTACH_FLAGS_NONE); if (attach_ret != GUM_ATTACH_OK) goto unable_to_attach; listener->wrapper = JS_NewObjectClass (ctx, self->invocation_listener_class); JS_SetOpaque (listener->wrapper, listener); JS_DefinePropertyValue (ctx, listener->wrapper, GUM_QUICK_CORE_ATOM (core, resource), JS_DupValue (ctx, cb_val), 0 ); g_hash_table_add (self->invocation_listeners, listener); return JS_DupValue (ctx, listener->wrapper); }
调用gum_interceptor_attach函数,传入的第四个参数(即flags)为GUM_ATTACH_FLAGS_NONE = 0。
而对于Interceptor.replace,其绑定的是gumjs_interceptor_replace,额外有另一个函数是gumjs_interceptor_replace_fast。它们都调用相同的方法gum_interceptor_replace_with_type,不同之处在于replace模式下传入的第二个参数为GUM_INTERCEPTOR_TYPE_DEFAULT= 0。而replace fast模式下传入的第二个参数为GUM_INTERCEPTOR_TYPE_FAST=1。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 GumReplaceReturngum_interceptor_replace (GumInterceptor * self, gpointer function_address, gpointer replacement_function, gpointer replacement_data, gpointer * original_function) { return gum_interceptor_replace_with_type (self, GUM_INTERCEPTOR_TYPE_DEFAULT, function_address, replacement_function, replacement_data, original_function); } GumReplaceReturngum_interceptor_replace_fast (GumInterceptor * self, gpointer function_address, gpointer replacement_function, gpointer * original_function) { return gum_interceptor_replace_with_type (self, GUM_INTERCEPTOR_TYPE_FAST, function_address, replacement_function, NULL , original_function); }
尽管如此,gum_interceptor_replace_with_type跟gum_interceptor_attach的关键代码调用相同,因此以gum_interceptor_attach为例进行分析。
gum_interceptor_attach 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 GumAttachReturngum_interceptor_attach (GumInterceptor * self, gpointer function_address, GumInvocationListener * listener, gpointer listener_function_data, GumAttachFlags flags) { GumAttachReturn result = GUM_ATTACH_OK; GumFunctionContext * function_ctx; GumInstrumentationError error; gum_interceptor_ignore_current_thread (self); GUM_INTERCEPTOR_LOCK (self); gum_interceptor_transaction_begin (&self->current_transaction); self->current_transaction.is_dirty = TRUE; function_address = gum_interceptor_resolve (self, function_address); function_ctx = gum_interceptor_instrument (self, GUM_INTERCEPTOR_TYPE_DEFAULT, function_address, (flags & GUM_ATTACH_FLAGS_FORCE) != 0 , &error); if (function_ctx == NULL ) goto instrumentation_error; if (gum_function_context_has_listener (function_ctx, listener)) goto already_attached; gum_function_context_add_listener (function_ctx, listener, listener_function_data, (flags & GUM_ATTACH_FLAGS_UNIGNORABLE) != 0 ); goto beach; beach: { gum_interceptor_transaction_end (&self->current_transaction); GUM_INTERCEPTOR_UNLOCK (self); gum_interceptor_unignore_current_thread (self); return result; } }
gum_interceptor_attach整个代码逻辑是通过事务的方式进行处理的。它首先调用gum_interceptor_resolve()函数以获取真实的函数入口地址。然后调用gum_interceptor_instrument()函数生成底层的跳板代码(相较于一级跳板),最后调用gum_interceptor_transaction_end提交Hook事务,里面会生成一级跳板。
获取真实函数入口地址 gum_interceptor_resolve 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 static gpointergum_interceptor_resolve (GumInterceptor * self, gpointer address) { address = gum_strip_code_pointer (address); if (!gum_interceptor_has (self, address)) { const gsize max_redirect_size = 16 ; gpointer target; gum_ensure_code_readable (address, max_redirect_size); if (gum_process_get_code_signing_policy () == GUM_CODE_SIGNING_REQUIRED) return address; target = _gum_interceptor_backend_resolve_redirect (self->backend, address); if (target != NULL ) return gum_interceptor_resolve (self, target); } return address; }
该函数功能主要是递归以穿透跳板代码,获取到真实的hook地址。具体来说,它通过_gum_interceptor_backend_resolve_redirect函数获取目标地址处的前16字节中的首个相对跳转指令,解析出跳转地址,如果存在相对跳转,则进一步递归调用gum_interceptor_resolve函数以获取无相对跳转指令的空间用于inline hook。
gum_ensure_code_readable 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 void gum_ensure_code_readable (gconstpointer address, gsize size) { #ifdef HAVE_ANDROID gsize page_size; gconstpointer start_page, end_page, cur_page; if (gum_android_get_api_level () < 29 ) return ; page_size = gum_query_page_size (); start_page = GSIZE_TO_POINTER ( GPOINTER_TO_SIZE (address) & ~(page_size - 1 )); end_page = GSIZE_TO_POINTER ( GPOINTER_TO_SIZE (address + size - 1 ) & ~(page_size - 1 )) + page_size; G_LOCK (gum_softened_code_pages); if (gum_softened_code_pages == NULL ) gum_softened_code_pages = g_hash_table_new (NULL , NULL ); for (cur_page = start_page; cur_page != end_page; cur_page += page_size) { if (!g_hash_table_contains (gum_softened_code_pages, cur_page)) { if (gum_try_mprotect ((gpointer) cur_page, page_size, GUM_PAGE_RWX)) g_hash_table_add (gum_softened_code_pages, (gpointer) cur_page); } } G_UNLOCK (gum_softened_code_pages);#endif }
gum_ensure_code_readable函数会对目标平台是Android的进行额外处理。对于Android 10以下的版本则不做处理,对于Android 10及以上版本则调用gum_try_mprotect 函数修改页权限。
_gum_interceptor_backend_resolve_redirect 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 gpointer _gum_interceptor_backend_resolve_redirect (GumInterceptorBackend * self, gpointer address){ return gum_arm64_reader_try_get_relative_jump_target (address); } gpointer gum_arm64_reader_try_get_relative_jump_target (gconstpointer address) { gpointer result = NULL ; csh capstone; cs_insn * insn; const uint8_t * code; size_t size; uint64_t pc; const cs_arm64_op * ops; cs_arch_register_arm64 (); cs_open (CS_ARCH_ARM64, GUM_DEFAULT_CS_ENDIAN, &capstone); cs_option (capstone, CS_OPT_DETAIL, CS_OPT_ON); insn = cs_malloc (capstone); code = address; size = 16 ; pc = GPOINTER_TO_SIZE (address); #define GUM_DISASM_NEXT() \ if (!cs_disasm_iter (capstone, &code, &size, &pc, insn)) \ goto beach; \ ops = insn->detail->arm64.operands #define GUM_CHECK_ID(i) \ if (insn->id != G_PASTE (ARM64_INS_, i)) \ goto beach #define GUM_CHECK_OP_TYPE(n, t) \ if (ops[n].type != G_PASTE (ARM64_OP_, t)) \ goto beach #define GUM_CHECK_OP_REG(n, r) \ if (ops[n].reg != G_PASTE (ARM64_REG_, r)) \ goto beach #define GUM_CHECK_OP_MEM(n, b, i, d) \ if (ops[n].mem.base != G_PASTE (ARM64_REG_, b)) \ goto beach; \ if (ops[n].mem.index != G_PASTE (ARM64_REG_, i)) \ goto beach; \ if (ops[n].mem.disp != d) \ goto beach GUM_DISASM_NEXT (); switch (insn->id) { case ARM64_INS_B: result = GSIZE_TO_POINTER (ops[0 ].imm); break ;#ifdef HAVE_DARWIN #endif default : break ; } beach: cs_free (insn, 1 ); cs_close (&capstone); return result; }
这里寻找前16字节中的第一个相对跳转指令,解析出跳转的目标地址并返回。
二三级跳板代码生成 gum_interceptor_instrument 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 static GumFunctionContext *gum_interceptor_instrument (GumInterceptor * self, GumInterceptorType type, gpointer function_address, gboolean force, GumInstrumentationError * error) { GumFunctionContext * ctx; *error = GUM_INSTRUMENTATION_ERROR_NONE; ctx = (GumFunctionContext *) g_hash_table_lookup (self->function_by_address, function_address); if (ctx != NULL ) { if (ctx->type != type) { *error = GUM_INSTRUMENTATION_ERROR_WRONG_TYPE; return NULL ; } return ctx; } if (self->backend == NULL ) { self->backend = _gum_interceptor_backend_create (&self->mutex, &self->allocator); } ctx = gum_function_context_new (self, function_address, type); else { if (!_gum_interceptor_backend_create_trampoline (self->backend, ctx, force)) goto wrong_signature; } g_hash_table_insert (self->function_by_address, function_address, ctx); gum_interceptor_transaction_schedule_update (&self->current_transaction, ctx, gum_interceptor_activate); return ctx; }
初始化拦截器后端 _gum_interceptor_backend_create 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 GumInterceptorBackend * _gum_interceptor_backend_create (GRecMutex * mutex, GumCodeAllocator * allocator) { GumInterceptorBackend * backend; backend = g_slice_new0 (GumInterceptorBackend); backend->mutex = mutex; backend->allocator = allocator; if (gum_process_get_code_signing_policy () == GUM_CODE_SIGNING_OPTIONAL) { gum_arm64_writer_init (&backend->writer, NULL ); gum_arm64_relocator_init (&backend->relocator, NULL , &backend->writer); gum_interceptor_backend_create_thunks (backend); } return backend; }
调用gum_interceptor_backend_create_thunks函数预先生成跳板代码thunks,具体来说是enter_thunk和leave_thunk,这些小片段通常负责保存所有寄存器状态 、调用 C 层的拦截函数 、恢复寄存器状态 。
gum_interceptor_backend_create_thunks 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 static void gum_interceptor_backend_create_thunks (GumInterceptorBackend * self) { gsize page_size, code_size; GumPageProtection protection; GumMemoryRange range; page_size = gum_query_page_size (); code_size = page_size; protection = gum_memory_can_remap_writable () ? GUM_PAGE_RX : GUM_PAGE_RW; self->thunks = gum_memory_allocate (NULL , code_size, page_size, protection); range.base_address = GUM_ADDRESS (self->thunks); range.size = code_size; gum_cloak_add_range (&range); gum_memory_patch_code (self->thunks, 1024 , (GumMemoryPatchApplyFunc) gum_emit_thunks, self); }
给thunks分配内存后,之后调用gum_memory_patch_code函数(第三个参数传入的是gum_emit_thunks函数指针),该函数主要是对thunks内存的权限进行RWX修复,然后回调gum_emit_thunks函数。
thunks内存权限修复 这部分可以不用看,大概就是给thunks所占的页赋予RWX权限,最后回调gum_emit_thunks函数。
gum_memory_patch_code 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 gbooleangum_memory_patch_code (gpointer address, gsize size, GumMemoryPatchApplyFunc apply, gpointer apply_data) { gboolean result; gsize page_size; guint8 * start_page, * end_page; gsize page_offset; GPtrArray * page_addresses; GumPatchCodeContext context; address = gum_strip_code_pointer (address); page_size = gum_query_page_size (); start_page = GSIZE_TO_POINTER (GPOINTER_TO_SIZE (address) & ~(page_size - 1 )); end_page = GSIZE_TO_POINTER ( (GPOINTER_TO_SIZE (address) + size - 1 ) & ~(page_size - 1 )); page_offset = ((guint8 *) address) - start_page; page_addresses = g_ptr_array_sized_new (((end_page - start_page) / page_size) + 1 ); g_ptr_array_add (page_addresses, start_page); if (end_page != start_page) { guint8 * cur; for (cur = start_page + page_size; cur != end_page + page_size; cur += page_size) { g_ptr_array_add (page_addresses, cur); } } context.page_offset = page_offset; context.func = apply; context.user_data = apply_data; result = gum_memory_patch_code_pages (page_addresses, TRUE, gum_apply_patch_code, &context); g_ptr_array_unref (page_addresses); return result; }
创建数组存储thunks所占用的页,从代码逻辑上来看,thunks所占用的页是连续的。之后调用gum_memory_patch_code_pages函数,第三个参数为gum_apply_patch_code函数指针。
gum_memory_patch_code_pages 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 gbooleangum_memory_patch_code_pages (GPtrArray * sorted_addresses, gboolean coalesce, GumMemoryPatchPagesApplyFunc apply, gpointer apply_data) { gboolean result = TRUE; gsize page_size; guint i; guint8 * apply_start, * apply_target_start; guint apply_num_pages; gboolean rwx_supported; rwx_supported = gum_query_is_rwx_supported (); page_size = gum_query_page_size (); else if (rwx_supported || !gum_code_segment_is_supported ()) { GumPageProtection protection; GumSuspendOperation suspend_op = { 0 , }; protection = rwx_supported ? GUM_PAGE_RWX : GUM_PAGE_RW; ... for (i = 0 ; i != sorted_addresses->len; i++) { gpointer target_page = g_ptr_array_index (sorted_addresses, i); if (!gum_try_mprotect (target_page, page_size, protection)) { result = FALSE; goto resume_threads; } } apply_start = NULL ; apply_num_pages = 0 ; for (i = 0 ; i != sorted_addresses->len; i++) { gpointer target_page = g_ptr_array_index (sorted_addresses, i); if (coalesce) { if (apply_start != 0 ) { if (target_page == apply_start + (page_size * apply_num_pages)) { apply_num_pages++; } else { apply (apply_start, apply_target_start, apply_num_pages, apply_data); apply_start = 0 ; } } if (apply_start == 0 ) { apply_start = target_page; apply_target_start = target_page; apply_num_pages = 1 ; } } ... } if (apply_num_pages != 0 ) apply (apply_start, apply_target_start, apply_num_pages, apply_data); ... for (i = 0 ; i != sorted_addresses->len; i++) { gpointer target_page = g_ptr_array_index (sorted_addresses, i); gum_clear_cache (target_page, page_size); } resume_threads: ... return result; }
修改thunks块所占用的页的权限为RWX。由于thunks本身分配的页就是连续的,因此只回调一次gum_apply_patch_code函数。
gum_apply_patch_code 1 2 3 4 5 6 7 8 9 10 static void gum_apply_patch_code (gpointer mem, gpointer target_page, guint n_pages, gpointer user_data) { GumPatchCodeContext * context = user_data; context->func ((guint8 *) mem + context->page_offset, context->user_data); }
往上追溯func来源可以知道是gum_emit_thunks函数,第一个参数mem + page_offset就是thunks的起始地址,第二个参数user_data就是_gum_interceptor_backend_create函数中创建的GumInterceptorBackend结构体。
thunks跳板生成(三级跳板) gum_emit_thunks 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 static void gum_emit_thunks (gpointer mem, GumInterceptorBackend * self) { GumArm64Writer * aw = &self->writer; self->enter_thunk = self->thunks; gum_arm64_writer_reset (aw, mem); aw->pc = GUM_ADDRESS (self->enter_thunk); gum_emit_enter_thunk (aw); gum_arm64_writer_flush (aw); self->leave_thunk = (guint8 *) self->enter_thunk + gum_arm64_writer_offset (aw); gum_emit_leave_thunk (aw); gum_arm64_writer_flush (aw); }
主要调用gum_emit_enter_thunk函数和gum_emit_leave_thunk函数分别构建enter_thunk跳板以及leave_thunk跳板。
enter_thunk跳板生成 gum_emit_enter_thunk 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 static void gum_emit_enter_thunk (GumArm64Writer * aw) { gum_emit_prolog (aw); gum_arm64_writer_put_add_reg_reg_imm (aw, ARM64_REG_X1, ARM64_REG_SP, GUM_FRAME_OFFSET_CPU_CONTEXT); gum_arm64_writer_put_add_reg_reg_imm (aw, ARM64_REG_X2, ARM64_REG_SP, GUM_FRAME_OFFSET_CPU_CONTEXT + G_STRUCT_OFFSET (GumCpuContext, lr)); gum_arm64_writer_put_add_reg_reg_imm (aw, ARM64_REG_X3, ARM64_REG_SP, GUM_FRAME_OFFSET_NEXT_HOP); gum_arm64_writer_put_call_address_with_arguments (aw, GUM_ADDRESS (_gum_function_context_begin_invocation), 4 , GUM_ARG_REGISTER, ARM64_REG_X17, GUM_ARG_REGISTER, ARM64_REG_X1, GUM_ARG_REGISTER, ARM64_REG_X2, GUM_ARG_REGISTER, ARM64_REG_X3); gum_emit_epilog (aw); }
生成的跳板代码如下:
gum_emit_prolog 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 static void gum_emit_prolog (GumArm64Writer * aw) { gint i; gum_arm64_writer_put_sub_reg_reg_imm (aw, ARM64_REG_SP, ARM64_REG_SP, 16 ); for (i = 30 ; i != -2 ; i -= 2 ) gum_arm64_writer_put_push_reg_reg (aw, ARM64_REG_Q0 + i, ARM64_REG_Q1 + i); gum_arm64_writer_put_push_reg_reg (aw, ARM64_REG_FP, ARM64_REG_LR); for (i = 27 ; i != -1 ; i -= 2 ) gum_arm64_writer_put_push_reg_reg (aw, ARM64_REG_X0 + i, ARM64_REG_X1 + i); gum_arm64_writer_put_mov_reg_nzcv (aw, ARM64_REG_X1); gum_arm64_writer_put_push_reg_reg (aw, ARM64_REG_X1, ARM64_REG_X0); gum_arm64_writer_put_add_reg_reg_imm (aw, ARM64_REG_X0, ARM64_REG_SP, sizeof (GumCpuContext) - G_STRUCT_OFFSET (GumCpuContext, nzcv) + 16 ); gum_arm64_writer_put_push_reg_reg (aw, ARM64_REG_XZR, ARM64_REG_X0); gum_arm64_writer_put_str_reg_reg_offset (aw, ARM64_REG_LR, ARM64_REG_SP, sizeof (GumCpuContext) + 8 ); gum_arm64_writer_put_str_reg_reg_offset (aw, ARM64_REG_FP, ARM64_REG_SP, sizeof (GumCpuContext) + 0 ); gum_arm64_writer_put_add_reg_reg_imm (aw, ARM64_REG_FP, ARM64_REG_SP, sizeof (GumCpuContext)); }
该部分主要生成具有如下功能的代码:在函数进入钩时,保存所有 CPU 寄存器和设置帧指针链,以便后续恢复。构建的指令以及栈布局如下:
为什么会存储两次LR、FP寄存器?这是因为第一次是用于构建GumCpuContext,以便在hook执行后能够恢复原始CPU上下文。第二次是用于构造帧指针链,这是 ARM64 的标准调试/栈回溯机制。每个栈帧存储前一个栈帧的 FP 和 LR,允许调试器、性能分析器或异常处理器遍历调用栈。
回到gum_emit_enter_thunk中,接下来的指令如下:
1 2 3 4 add x 1 , sp, #0 add x 2 , sp, #offset_lr_in_ctxadd x 3 , sp, #ctx_sizecall _gum_function_context_begin_invocation(x 17 , x 1 , x 2 , x 3 )
_gum_function_context_begin_invocation 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 gboolean _gum_function_context_begin_invocation (GumFunctionContext * function_ctx, GumCpuContext * cpu_context, gpointer * caller_ret_addr, gpointer * next_hop) { GumInterceptor * interceptor; InterceptorThreadContext * interceptor_ctx; GumInvocationStack * stack ; GumInvocationStackEntry * stack_entry; GumInvocationContext * invocation_ctx = NULL ; gint system_error; gboolean invoke_listeners = TRUE; gboolean only_invoke_unignorable_listeners = FALSE; gboolean will_trap_on_leave = FALSE; g_atomic_int_inc (&function_ctx->trampoline_usage_counter); interceptor = function_ctx->interceptor; if (gum_tls_key_get_value (gum_interceptor_guard_key) == interceptor) { *next_hop = function_ctx->on_invoke_trampoline; goto bypass; } gum_tls_key_set_value (gum_interceptor_guard_key, interceptor); interceptor_ctx = get_interceptor_thread_context (); stack = interceptor_ctx->stack ; stack_entry = gum_invocation_stack_peek_top (stack ); if (stack_entry != NULL && stack_entry->calling_replacement && gum_strip_code_pointer (GUM_FUNCPTR_TO_POINTER ( stack_entry->invocation_context.function)) == function_ctx->function_address) { gum_tls_key_set_value (gum_interceptor_guard_key, NULL ); *next_hop = function_ctx->on_invoke_trampoline; goto bypass; } will_trap_on_leave = function_ctx->replacement_function != NULL || (invoke_listeners && function_ctx->has_on_leave_listener); if (will_trap_on_leave) { stack_entry = gum_invocation_stack_push (stack , function_ctx, *caller_ret_addr, only_invoke_unignorable_listeners); invocation_ctx = &stack_entry->invocation_context; } else if (invoke_listeners) { stack_entry = gum_invocation_stack_push (stack , function_ctx, function_ctx->function_address, only_invoke_unignorable_listeners); invocation_ctx = &stack_entry->invocation_context; } if (invocation_ctx != NULL ) invocation_ctx->system_error = system_error; gum_function_context_fixup_cpu_context (function_ctx, cpu_context); if (invoke_listeners) { for (i = 0 ; i != listener_entries->len; i++) { if (listener_entry->listener_interface->on_enter != NULL ) { listener_entry->listener_interface->on_enter ( listener_entry->listener_instance, invocation_ctx); } } system_error = invocation_ctx->system_error; } if (will_trap_on_leave) { *caller_ret_addr = function_ctx->on_leave_trampoline; } if (function_ctx->replacement_function != NULL ) { stack_entry->calling_replacement = TRUE; stack_entry->cpu_context = *cpu_context; stack_entry->original_system_error = system_error; invocation_ctx->cpu_context = &stack_entry->cpu_context; invocation_ctx->backend = &interceptor_ctx->replacement_backend; invocation_ctx->backend->data = function_ctx->replacement_data; *next_hop = function_ctx->replacement_function; } else { *next_hop = function_ctx->on_invoke_trampoline; } return will_trap_on_leave; }
如果存在onLeave hook,则将function_ctx和caller_ret_addr压入invocation_stack,之后会在_gum_function_context_end_invocation函数中取出并使用。
然后通过执行监听器的on_enter方法进入到自定义的onEnter逻辑。
如果后续存在onLeave hook,就修改栈上保存的GumCpuContext中的lr寄存器的值修改成on_leave_trampoline入口地址,这样后续原函数执行完后,通过ret指令就会回到on_leave_trampoline入口处。
之后根据不同hook类型对栈上的next_hop进行不同修改:如果是replace hook,则修改next_hop为replacement_function地址,否则修改成on_invoke_trampoline地址,这样一来,后续执行完enter_thunk最后一条指令br x16后,就会跳转到目标地址处。
gum_emit_epilog 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 static void gum_emit_epilog (GumArm64Writer * aw) { guint i; gum_arm64_writer_put_add_reg_reg_imm (aw, ARM64_REG_SP, ARM64_REG_SP, 16 ); gum_arm64_writer_put_pop_reg_reg (aw, ARM64_REG_X1, ARM64_REG_X0); gum_arm64_writer_put_mov_nzcv_reg (aw, ARM64_REG_X1); for (i = 1 ; i != 29 ; i += 2 ) gum_arm64_writer_put_pop_reg_reg (aw, ARM64_REG_X0 + i, ARM64_REG_X1 + i); gum_arm64_writer_put_pop_reg_reg (aw, ARM64_REG_FP, ARM64_REG_LR); for (i = 0 ; i != 32 ; i += 2 ) gum_arm64_writer_put_pop_reg_reg (aw, ARM64_REG_Q0 + i, ARM64_REG_Q1 + i); gum_arm64_writer_put_pop_reg_reg (aw, ARM64_REG_X16, ARM64_REG_X17); #ifndef HAVE_PTRAUTH gum_arm64_writer_put_ret_reg (aw, ARM64_REG_X16);#else gum_arm64_writer_put_br_reg (aw, ARM64_REG_X16);#endif }
恢复CPU上下文,最终x16寄存器指向next_hop前8字节(此值在_gum_function_context_begin_invocation根据不同情况赋予了不同的值),x17寄存器存储了原来的LR寄存器的值。
当执行最后一条指令时,对于进行了replace hook的情况,则是跳转到replacement_function处,对于非replace hook(即onEnter onLeave hook),则跳转到on_invoke_trampoline处。
leave_thunk跳板生成 gum_emit_leave_thunk 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 static void gum_emit_leave_thunk (GumArm64Writer * aw) { gum_emit_prolog (aw); gum_arm64_writer_put_add_reg_reg_imm (aw, ARM64_REG_X1, ARM64_REG_SP, GUM_FRAME_OFFSET_CPU_CONTEXT); gum_arm64_writer_put_add_reg_reg_imm (aw, ARM64_REG_X2, ARM64_REG_SP, GUM_FRAME_OFFSET_NEXT_HOP); gum_arm64_writer_put_call_address_with_arguments (aw, GUM_ADDRESS (_gum_function_context_end_invocation), 3 , GUM_ARG_REGISTER, ARM64_REG_X17, GUM_ARG_REGISTER, ARM64_REG_X1, GUM_ARG_REGISTER, ARM64_REG_X2); gum_emit_epilog (aw); }
这部分对应的伪汇编指令如下:
gum_emit_prolog函数和gum_emit_epilog函数前面已经分析过了,这里直接看_gum_function_context_end_invocation函数。
_gum_function_context_end_invocation 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 void _gum_function_context_end_invocation (GumFunctionContext * function_ctx, GumCpuContext * cpu_context, gpointer * next_hop) { gint system_error; InterceptorThreadContext * interceptor_ctx; GumInvocationStackEntry * stack_entry; GumInvocationContext * invocation_ctx; GPtrArray * listener_entries; gboolean only_invoke_unignorable_listeners; guint i; gum_tls_key_set_value (gum_interceptor_guard_key, function_ctx->interceptor); interceptor_ctx = get_interceptor_thread_context (); stack_entry = gum_invocation_stack_peek_top (interceptor_ctx->stack ); *next_hop = gum_sign_code_pointer (stack_entry->caller_ret_addr); gum_function_context_fixup_cpu_context (function_ctx, cpu_context); listener_entries = (GPtrArray *) g_atomic_pointer_get (&function_ctx->listener_entries); only_invoke_unignorable_listeners = stack_entry->only_invoke_unignorable_listeners; for (i = 0 ; i != listener_entries->len; i++) { if (listener_entry->listener_interface->on_leave != NULL ) { listener_entry->listener_interface->on_leave ( listener_entry->listener_instance, invocation_ctx); } } }
修改next_hop设置为之前在invocation_stack保存的返回地址,这样执行完leav_thunk最后一条指令br x16后,就会跳转到目标地址处(被hook函数的caller ret address)。之后on_leave执行自定义的onLeave代码。
至此,thunks的生成代码已经分析完毕,Hook生成的代码逻辑最终回到gum_interceptor_instrument中,接下来执行的是_gum_interceptor_backend_create_trampoline。
二级跳板生成 _gum_interceptor_backend_create_trampoline 由于该函数太大了,这里就根据功能划分成多个部分进行分析。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 gboolean _gum_interceptor_backend_create_trampoline (GumInterceptorBackend * self, GumFunctionContext * ctx, gboolean force) { GumArm64Writer * aw = &self->writer; GumArm64Relocator * ar = &self->relocator; gpointer function_address = ctx->function_address; GumArm64FunctionContextData * data = GUM_FCDATA (ctx); gboolean need_deflector; gpointer deflector_target; GString * signature; gboolean is_eligible_for_lr_rewriting; guint reloc_bytes;if (!gum_interceptor_backend_prepare_trampoline (self, ctx, force, &need_deflector)) return FALSE; gum_arm64_writer_reset (aw, ctx->trampoline_slice->data); aw->pc = GUM_ADDRESS (ctx->trampoline_slice->pc);
首先调用gum_interceptor_backend_prepare_trampoline来获取最大可用于重定向的空间(可用于inline hook空间),给ctx->trampoline_slice分配内存空间用于编写跳板,同时判断是否需要中继器(deflector),以及获取可用的临时寄存器x16或x17。然后初始化writer(其base、code初始化为相同的值)。
1 2 3 4 5 6 7 8 9 10 11 12 if (ctx->type == GUM_INTERCEPTOR_TYPE_FAST) { deflector_target = ctx->replacement_function; }else { ctx->on_enter_trampoline = gum_sign_code_pointer ( (guint8 *) ctx->trampoline_slice->pc + gum_arm64_writer_offset (aw)); deflector_target = ctx->on_enter_trampoline; }
之后根据hook类型给中继器跳转目标设置不同的地址:replace fast hook 需要设置中继器目标为replacement_function地址。非replace fast hook(replace与attach)设置中继器目标为on_enter_trampoline的地址,并初始化on_enter_trampoline的起始地址。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 if (need_deflector) { GumAddressSpec caller; gpointer return_address; gboolean dedicated; caller.near_address = (guint8 *) function_address + data->redirect_code_size - 4 ; caller.max_distance = GUM_ARM64_B_MAX_DISTANCE; return_address = (guint8 *) function_address + data->redirect_code_size; dedicated = data->redirect_code_size == 4 ; ctx->trampoline_deflector = gum_code_allocator_alloc_deflector ( self->allocator, &caller, return_address, deflector_target, dedicated); if (ctx->trampoline_deflector == NULL ) { gum_code_slice_unref (ctx->trampoline_slice); ctx->trampoline_slice = NULL ; return FALSE; } gum_arm64_writer_put_pop_reg_reg (aw, ARM64_REG_X0, ARM64_REG_LR); }
对于需要中继器的,则计算相关地址,并调用gum_code_allocator_alloc_deflector为中继器分配内存空间。然后向trampoline_slice写入ldp x0,lr, [sp, #-16]!指令。对于中继器这一块的分析,我将其放置文章末尾了,此处分析没有中继器的情况,这两种情况生成的trampoline_slice差不多,唯一的区别就是多了个刚刚生成的ldp x0,lr, [sp, #-16]!。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 if (ctx->type != GUM_INTERCEPTOR_TYPE_FAST){ gum_arm64_writer_put_ldr_reg_address (aw, ARM64_REG_X17, GUM_ADDRESS (ctx)); gum_arm64_writer_put_ldr_reg_address (aw, ARM64_REG_X16, GUM_ADDRESS (gum_sign_code_pointer (self->enter_thunk))); gum_arm64_writer_put_br_reg (aw, ARM64_REG_X16); ctx->on_leave_trampoline = (guint8 *) ctx->trampoline_slice->pc + gum_arm64_writer_offset (aw); gum_arm64_writer_put_ldr_reg_address (aw, ARM64_REG_X17, GUM_ADDRESS (ctx)); gum_arm64_writer_put_ldr_reg_address (aw, ARM64_REG_X16, GUM_ADDRESS (gum_sign_code_pointer (self->leave_thunk))); gum_arm64_writer_put_br_reg (aw, ARM64_REG_X16); gum_arm64_writer_flush (aw); g_assert (gum_arm64_writer_offset (aw) <= ctx->trampoline_slice->size); }
对于非replace fast hook,则编写跳板on_enter_trampoline和on_leave_trampoline分别用于跳转到enter_thunk和leave_thunk。
1 2 ctx->on_invoke_trampoline = gum_sign_code_pointer ( (guint8 *) ctx->trampoline_slice->pc + gum_arm64_writer_offset (aw));
此处开始,不区分hook模式,都设置on_invoke_trampoline的写入地址,它的地址紧挨着on_leave_trampoline,用于跳转到invoke_trampoline。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 gum_arm64_relocator_reset (ar, function_address, aw); signature = g_string_sized_new (16 );do { const cs_insn * insn; reloc_bytes = gum_arm64_relocator_read_one (ar, &insn); if (reloc_bytes == 0 ){ reloc_bytes = data->redirect_code_size; break ; } if (signature->len != 0 ) g_string_append_c (signature, ';' ); g_string_append (signature, insn->mnemonic); }while (reloc_bytes < data->redirect_code_size); is_eligible_for_lr_rewriting = strcmp (signature->str, "mov;b" ) == 0 || g_str_has_prefix (signature->str, "stp;mov;mov;bl" ); g_string_free (signature, TRUE);if (is_eligible_for_lr_rewriting){ const cs_insn * insn; while ((insn = gum_arm64_relocator_peek_next_write_insn (ar)) != NULL ){ const cs_arm64_op * source_op = &insn->detail->arm64.operands[1 ]; if (insn->id == ARM64_INS_MOV && source_op->type == ARM64_OP_REG && source_op->reg == ARM64_REG_LR){ arm64_reg dst_reg = insn->detail->arm64.operands[0 ].reg; const guint reg_size = sizeof (gpointer); const guint reg_pair_size = 2 * reg_size; guint dst_reg_index, dst_reg_slot_index, dst_reg_offset_in_frame; gum_arm64_writer_put_push_all_x_registers (aw); gum_arm64_writer_put_call_address_with_arguments (aw, GUM_ADDRESS (_gum_interceptor_translate_top_return_address), 1 , GUM_ARG_REGISTER, ARM64_REG_LR); if (dst_reg >= ARM64_REG_X0 && dst_reg <= ARM64_REG_X28){ dst_reg_index = dst_reg - ARM64_REG_X0; } else { g_assert (dst_reg >= ARM64_REG_X29 && dst_reg <= ARM64_REG_X30); dst_reg_index = dst_reg - ARM64_REG_X29; } dst_reg_slot_index = (dst_reg_index * reg_size) / reg_pair_size; dst_reg_offset_in_frame = (15 - dst_reg_slot_index) * reg_pair_size; if (dst_reg_index % 2 != 0 ) dst_reg_offset_in_frame += reg_size; gum_arm64_writer_put_str_reg_reg_offset (aw, ARM64_REG_X0, ARM64_REG_SP, dst_reg_offset_in_frame); gum_arm64_writer_put_pop_all_x_registers (aw); gum_arm64_relocator_skip_one (ar); } else { gum_arm64_relocator_write_one (ar); } } }else { gum_arm64_relocator_write_all (ar); }
这部分就是对重定向区域内的原指令进行LR修复并写入on_invoke_trampoline中。
1 2 3 4 5 6 7 8 9 10 11 if (!ar->eoi) { GumAddress resume_at; resume_at = gum_sign_code_address ( GUM_ADDRESS (function_address) + reloc_bytes); gum_arm64_writer_put_ldr_reg_address (aw, data->scratch_reg, resume_at); gum_arm64_writer_put_br_reg (aw, data->scratch_reg); }
这里继续构造on_invoke_trampoline,用于跳转到后续的原函数代码。以上代码最终生成的跳板指令如下图所示。
1 2 ctx->overwritten_prologue_len = reloc_bytes; gum_memcpy (ctx->overwritten_prologue, function_address, reloc_bytes);
备份原函数中inline hook所占用的指令。
gum_interceptor_backend_prepare_trampoline 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 static gbooleangum_interceptor_backend_prepare_trampoline (GumInterceptorBackend * self, GumFunctionContext * ctx, gboolean force, gboolean * need_deflector) { GumArm64FunctionContextData * data = GUM_FCDATA (ctx); gpointer function_address = ctx->function_address; guint redirect_limit; *need_deflector = FALSE; if (gum_arm64_relocator_can_relocate (function_address, GUM_INTERCEPTOR_FULL_REDIRECT_SIZE, GUM_SCENARIO_ONLINE, &redirect_limit, &data->scratch_reg)) { data->redirect_code_size = GUM_INTERCEPTOR_FULL_REDIRECT_SIZE; ctx->trampoline_slice = gum_code_allocator_alloc_slice (self->allocator); } else if (force) { data->redirect_code_size = GUM_INTERCEPTOR_FULL_REDIRECT_SIZE; ctx->trampoline_slice = gum_code_allocator_alloc_slice (self->allocator); if (data->scratch_reg == ARM64_REG_INVALID) data->scratch_reg = ARM64_REG_X16; return TRUE; } else if (ctx->type == GUM_INTERCEPTOR_TYPE_FAST) { return FALSE; } else { GumAddressSpec spec; gsize alignment; if (redirect_limit >= 8 ) { data->redirect_code_size = 8 ; spec.near_address = GSIZE_TO_POINTER ( GPOINTER_TO_SIZE (function_address) & ~((gsize) (GUM_ARM64_LOGICAL_PAGE_SIZE - 1 ))); spec.max_distance = GUM_ARM64_ADRP_MAX_DISTANCE; alignment = GUM_ARM64_LOGICAL_PAGE_SIZE; } else if (redirect_limit >= 4 ) { data->redirect_code_size = 4 ; spec.near_address = function_address; spec.max_distance = GUM_ARM64_B_MAX_DISTANCE; alignment = 0 ; } else { return FALSE; } ctx->trampoline_slice = gum_code_allocator_try_alloc_slice_near ( self->allocator, &spec, alignment); if (ctx->trampoline_slice == NULL ) { ctx->trampoline_slice = gum_code_allocator_alloc_slice (self->allocator); *need_deflector = TRUE; } } if (data->scratch_reg == ARM64_REG_INVALID) goto no_scratch_reg; return TRUE; no_scratch_reg: { gum_code_slice_unref (ctx->trampoline_slice); ctx->trampoline_slice = NULL ; return FALSE; } }
这个函数主要是判断最大可用于重定向的空间(可用于inline hook空间),对于16字节的inline hook,不需要中继器(deflector),而对于4字节或8字节的inline hook,先尝试通过gum_code_allocator_try_alloc_slice_near 函数探寻附近页中是否存在足够可用的空余空间,如果没有找到,则需要中继器(deflector)。之后就是给ctx->trampoline_slice分配内存空间用于编写跳板,同时获取可用的临时寄存器x16或x17。
对于可用空间为4/8字节的inline hook,其跳转范围受限,因此引入中继器(deflector),先让一级跳板跳转到中继器,然后由于中继器可用空间足够,可以像16字节的inline hook一样,跳转的地址不再受到限制。
当前hook任务加入hook任务队列 接下来的代码逻辑回到gum_interceptor_instrument函数中, 下一步调用的关键函数是
1 gum_interceptor_transaction_schedule_update (&self->current_transaction, ctx, gum_interceptor_activate);
传入的第三个参数为gum_interceptor_activate函数指针。
gum_interceptor_transaction_schedule_update 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 static void gum_interceptor_transaction_schedule_update (GumInterceptorTransaction * self, GumFunctionContext * ctx, GumUpdateTaskFunc func) { guint8 * function_address; gpointer start_page, end_page; GArray * pending; GumUpdateTask update; function_address = _gum_interceptor_backend_get_function_address (ctx); start_page = gum_page_address_from_pointer (function_address); end_page = gum_page_address_from_pointer (function_address + ctx->overwritten_prologue_len - 1 ); pending = g_hash_table_lookup (self->pending_update_tasks, start_page); if (pending == NULL ) { pending = g_array_new (FALSE, FALSE, sizeof (GumUpdateTask)); g_hash_table_insert (self->pending_update_tasks, start_page, pending); } update.ctx = ctx; update.func = func; g_array_append_val (pending, update); if (end_page != start_page) { pending = g_hash_table_lookup (self->pending_update_tasks, end_page); if (pending == NULL ) { pending = g_array_new (FALSE, FALSE, sizeof (GumUpdateTask)); g_hash_table_insert (self->pending_update_tasks, end_page, pending); } } }
这段代码主要功能是将一个 Hook 更新任务(Update Task)按“内存页”进行归类并排期。在 Frida 中,为了提高性能并确保线程安全,修改内存(Patching)通常不是立即执行的,而是先收集所有任务,最后统一修改。由于修改内存涉及到修改页属性,按页归类可以确保每一页只被执行一次权限切换操作。
Hook事务提交并执行 接下来的代码逻辑回到gum_interceptor_attach函数中, 下一步调用的关键函数是
1 2 gum_interceptor_transaction_end (&self->current_transaction);
gum_interceptor_transaction_end 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 static void gum_interceptor_transaction_end (GumInterceptorTransaction * self) { GumInterceptor * interceptor = self->interceptor; GumInterceptorTransaction transaction_copy; GPtrArray * addresses; GHashTableIter iter; gpointer address; self->level--; if (self->level > 0 ) return ; if (!self->is_dirty) return ; gum_interceptor_ignore_current_thread (interceptor); gum_code_allocator_commit (&interceptor->allocator); if (g_queue_is_empty (self->pending_destroy_tasks) && g_hash_table_size (self->pending_update_tasks) == 0 ) { interceptor->current_transaction.is_dirty = FALSE; goto no_changes; } transaction_copy = interceptor->current_transaction; self = &transaction_copy; gum_interceptor_transaction_init (&interceptor->current_transaction, interceptor); addresses = g_ptr_array_sized_new (g_hash_table_size (self->pending_update_tasks)); g_hash_table_iter_init (&iter, self->pending_update_tasks); while (g_hash_table_iter_next (&iter, &address, NULL )) g_ptr_array_add (addresses, address); g_ptr_array_sort (addresses, (GCompareFunc) gum_page_address_compare); else if (!gum_memory_patch_code_pages (addresses, FALSE, gum_apply_updates, self)) { g_abort (); } }
关键部分是调用gum_memory_patch_code_pages函数,这个函数在前面已经分析过了,但这里传入的第三个参数是gum_apply_updates函数指针。
gum_apply_updates 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 static void gum_apply_updates (gpointer source_page, gpointer target_page, guint n_pages, gpointer user_data) { GumInterceptorTransaction * self = user_data; GArray * pending; guint i; pending = g_hash_table_lookup (self->pending_update_tasks, target_page); g_assert (pending != NULL ); for (i = 0 ; i != pending->len; i++) { GumUpdateTask * update; gsize offset; update = &g_array_index (pending, GumUpdateTask, i); offset = (guint8 *) _gum_interceptor_backend_get_function_address (update->ctx) - (guint8 *) target_page; update->func (self->interceptor, update->ctx, (guint8 *) source_page + offset); } }
这里回调的是gum_interceptor_transaction_schedule_update函数中给update->func设置的gum_interceptor_activate函数。
gum_interceptor_activate 1 2 3 4 5 6 7 8 9 10 11 12 13 14 static void gum_interceptor_activate (GumInterceptor * self, GumFunctionContext * ctx, gpointer prologue) { if (ctx->destroyed) return ; g_assert (!ctx->activated); ctx->activated = TRUE; _gum_interceptor_backend_activate_trampoline (self->backend, ctx, prologue); }
一级跳板生成 _gum_interceptor_backend_activate_trampoline 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 void _gum_interceptor_backend_activate_trampoline (GumInterceptorBackend * self, GumFunctionContext * ctx, gpointer prologue) { GumArm64Writer * aw = &self->writer; GumArm64FunctionContextData * data = GUM_FCDATA (ctx); GumAddress on_enter; if (ctx->type == GUM_INTERCEPTOR_TYPE_FAST) on_enter = GUM_ADDRESS (ctx->replacement_function); else on_enter = GUM_ADDRESS (ctx->on_enter_trampoline); gum_arm64_writer_reset (aw, prologue); aw->pc = GUM_ADDRESS (ctx->function_address); if (ctx->trampoline_deflector != NULL ) { if (data->redirect_code_size == 8 ) { gum_arm64_writer_put_push_reg_reg (aw, ARM64_REG_X0, ARM64_REG_LR); gum_arm64_writer_put_bl_imm (aw, GUM_ADDRESS (ctx->trampoline_deflector->trampoline)); } else { g_assert (data->redirect_code_size == 4 ); gum_arm64_writer_put_b_imm (aw, GUM_ADDRESS (ctx->trampoline_deflector->trampoline)); } } else { switch (data->redirect_code_size) { case 4 : gum_arm64_writer_put_b_imm (aw, on_enter); break ; case 8 : gum_arm64_writer_put_adrp_reg_address (aw, data->scratch_reg, on_enter); gum_arm64_writer_put_br_reg_no_auth (aw, data->scratch_reg); break ; case GUM_INTERCEPTOR_FULL_REDIRECT_SIZE: gum_arm64_writer_put_ldr_reg_address (aw, data->scratch_reg, on_enter); gum_arm64_writer_put_br_reg (aw, data->scratch_reg); break ; default : g_assert_not_reached (); } } gum_arm64_writer_flush (aw); g_assert (gum_arm64_writer_offset (aw) <= data->redirect_code_size); }
这里就是一级跳板生成的地方了,该函数主要负责把被hook函数开头的几字节替换成跳转到下一跳板处(on_enter_trampoline或者deflector)的指令。根据可用空间和距离,它可能生成的指令如下图所示。
deflector trampoline生成 这里续接二级跳板生成时需要中继器(deflector)的情况,对应代码为
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 if (need_deflector) { GumAddressSpec caller; gpointer return_address; gboolean dedicated; caller.near_address = (guint8 *) function_address + data->redirect_code_size - 4 ; caller.max_distance = GUM_ARM64_B_MAX_DISTANCE; return_address = (guint8 *) function_address + data->redirect_code_size; dedicated = data->redirect_code_size == 4 ; ctx->trampoline_deflector = gum_code_allocator_alloc_deflector ( self->allocator, &caller, return_address, deflector_target, dedicated); if (ctx->trampoline_deflector == NULL ) { gum_code_slice_unref (ctx->trampoline_slice); ctx->trampoline_slice = NULL ; return FALSE; } gum_arm64_writer_put_pop_reg_reg (aw, ARM64_REG_X0, ARM64_REG_LR); }
这里通过gum_code_allocator_alloc_deflector函数为中继器分配内存空间。
gum_code_allocator_alloc_deflector 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 GumCodeDeflector *gum_code_allocator_alloc_deflector (GumCodeAllocator * self, const GumAddressSpec * caller, gpointer return_address, gpointer target, gboolean dedicated) { GumCodeDeflectorDispatcher * dispatcher = NULL ; GSList * cur; GumCodeDeflectorImpl * impl; GumCodeDeflector * deflector; if (!dedicated) { for (cur = self->dispatchers; cur != NULL ; cur = cur->next) { GumCodeDeflectorDispatcher * d = cur->data; gsize distance; distance = ABS ((gssize) GPOINTER_TO_SIZE (d->address) - (gssize) caller->near_address); if (distance <= caller->max_distance) { dispatcher = d; break ; } } } if (dispatcher == NULL ) { dispatcher = gum_code_deflector_dispatcher_new (caller, return_address, dedicated ? target : NULL ); if (dispatcher == NULL ) return NULL ; self->dispatchers = g_slist_prepend (self->dispatchers, dispatcher); } impl = g_slice_new (GumCodeDeflectorImpl); deflector = &impl->parent; deflector->return_address = return_address; deflector->target = target; deflector->trampoline = dispatcher->trampoline; deflector->ref_count = 1 ; impl->allocator = self; dispatcher->callers = g_slist_prepend (dispatcher->callers, deflector); return deflector; }
这里分为两种情况,对于8字节inline hook,先查找已有的deflector dispatcher是否可以复用(目标dispatcher在±128MB内),如果没有找到,则同4字节的inline hook一样,调用gum_code_deflector_dispatcher_new函数创建新的deflector dispatcher。
gum_code_deflector_dispatcher_new 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 static GumCodeDeflectorDispatcher *gum_code_deflector_dispatcher_new (const GumAddressSpec * caller, gpointer return_address, gpointer dedicated_target) {#if defined (HAVE_DARWIN) || (defined (HAVE_ELF) && GLIB_SIZEOF_VOID_P == 4) GumCodeDeflectorDispatcher * dispatcher; GumProbeRangeForCodeCaveContext probe_ctx; GumInsertDeflectorContext insert_ctx; gboolean remap_supported; remap_supported = gum_memory_can_remap_writable (); probe_ctx.caller = caller; probe_ctx.cave.base_address = 0 ; probe_ctx.cave.size = 0 ; gum_process_enumerate_modules (gum_probe_module_for_code_cave, &probe_ctx); if (probe_ctx.cave.base_address == 0 ) return NULL ; dispatcher = g_slice_new0 (GumCodeDeflectorDispatcher); dispatcher->address = GSIZE_TO_POINTER (probe_ctx.cave.base_address); dispatcher->original_data = g_memdup (dispatcher->address, probe_ctx.cave.size); dispatcher->original_size = probe_ctx.cave.size; if (dedicated_target == NULL ) { gsize thunk_size; GumMemoryRange range; GumPageProtection protection; thunk_size = gum_query_page_size (); protection = remap_supported ? GUM_PAGE_RX : GUM_PAGE_RW; dispatcher->thunk = gum_memory_allocate (NULL , thunk_size, thunk_size, protection); dispatcher->thunk_size = thunk_size; gum_memory_patch_code (dispatcher->thunk, GUM_MAX_CODE_DEFLECTOR_THUNK_SIZE, (GumMemoryPatchApplyFunc) gum_write_thunk, dispatcher); range.base_address = GUM_ADDRESS (dispatcher->thunk); range.size = thunk_size; gum_cloak_add_range (&range); } insert_ctx.pc = GUM_ADDRESS (dispatcher->address); insert_ctx.max_size = dispatcher->original_size; insert_ctx.return_address = return_address; insert_ctx.dedicated_target = dedicated_target; insert_ctx.dispatcher = dispatcher; gum_memory_patch_code (dispatcher->address, dispatcher->original_size, (GumMemoryPatchApplyFunc) gum_insert_deflector, &insert_ctx); return dispatcher;#else (void ) gum_insert_deflector; (void ) gum_write_thunk; (void ) gum_probe_module_for_code_cave; return NULL ;#endif }
对于Android ARM32,则会通过gum_process_enumerate_modules枚举现有模块,对每个模块利用 gum_probe_module_for_code_cave 函数在其中找一段没用的、被对齐填充出来的空白区域(Code Cave)。然后这部分空白区域就被当作delfector dispatcher,写入跳转指令(目标为on_enter_trampoline或者replacement_function)。
对于8字节空间的inline hook,额外开辟新的页给到thunk,然后回调gum_write_thunk函数写入跳板代码。
之后4/8字节的inline hook,回调gum_insert_deflector函数往dispatcher(附近页找到的空白区域cave)写入跳板代码。
对于Android ARM64,不创建deflector,而是直接返回NULL。
接下来就以Android ARM32的情况进行分析。
8字节inline hook额外开辟thunk空间 gum_write_thunk 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 static void gum_write_thunk (gpointer thunk, GumCodeDeflectorDispatcher * dispatcher) {# if defined (HAVE_ARM) GumThumbWriter tw; gum_thumb_writer_init (&tw, thunk); tw.pc = GUM_ADDRESS (dispatcher->thunk); gum_thumb_writer_put_push_regs (&tw, 2 , ARM_REG_R9, ARM_REG_R12); gum_thumb_writer_put_call_address_with_arguments (&tw, GUM_ADDRESS (gum_code_deflector_dispatcher_lookup), 2 , GUM_ARG_ADDRESS, GUM_ADDRESS (dispatcher), GUM_ARG_REGISTER, ARM_REG_LR); gum_thumb_writer_put_pop_regs (&tw, 2 , ARM_REG_R9, ARM_REG_R12); gum_thumb_writer_put_bx_reg (&tw, ARM_REG_R0); gum_thumb_writer_clear (&tw);# elif defined (HAVE_ARM64) }
对于Android ARM32,往thunk中填写的是thumb指令,具体如下:
BX (Branch and Exchange) 指令的主要作用是实现状态切换,即在 ARM 状态(32位指令)和 Thumb 状态(16/32位混合指令)之间来回切换。
gum_code_deflector_dispatcher_lookup 我们可以看看gum_code_deflector_dispatcher_lookup函数干了啥。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 static gpointergum_code_deflector_dispatcher_lookup (GumCodeDeflectorDispatcher * self, gpointer return_address) { GSList * cur; for (cur = self->callers; cur != NULL ; cur = cur->next) { GumCodeDeflector * caller = cur->data; if (caller->return_address == return_address) return caller->target; } return NULL ; }
遍历DeflectorDispatcher中的deflector,如果传入的返回地址与deflector中的原函数返回地址一致,那么就返回这个deflector的跳转目标,里面存储的是on_enter_trampoline或者 replacement_function的地址。
该函数返回后,借助bx r0指令跳转到目标地址。
4/8字节inline hook的dispatcher gum_insert_deflector 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 static void gum_insert_deflector (gpointer cave, GumInsertDeflectorContext * ctx) {# if defined (HAVE_ARM) GumCodeDeflectorDispatcher * dispatcher = ctx->dispatcher; GumThumbWriter tw; if (ctx->dedicated_target != NULL ) { gboolean owner_is_arm; owner_is_arm = (GPOINTER_TO_SIZE (ctx->return_address) & 1 ) == 0 ; if (owner_is_arm) { GumArmWriter aw; gum_arm_writer_init (&aw, cave); aw.cpu_features = gum_query_cpu_features (); aw.pc = ctx->pc; gum_arm_writer_put_ldr_reg_address (&aw, ARM_REG_PC, GUM_ADDRESS (ctx->dedicated_target)); gum_arm_writer_flush (&aw); g_assert (gum_arm_writer_offset (&aw) <= ctx->max_size); gum_arm_writer_clear (&aw); dispatcher->trampoline = GSIZE_TO_POINTER (ctx->pc); return ; } gum_thumb_writer_init (&tw, cave); tw.pc = ctx->pc; gum_thumb_writer_put_ldr_reg_address (&tw, ARM_REG_PC, GUM_ADDRESS (ctx->dedicated_target)); } else { gum_thumb_writer_init (&tw, cave); tw.pc = ctx->pc; gum_thumb_writer_put_ldr_reg_address (&tw, ARM_REG_PC, GUM_ADDRESS (dispatcher->thunk) + 1 ); } gum_thumb_writer_flush (&tw); g_assert (gum_thumb_writer_offset (&tw) <= ctx->max_size); gum_thumb_writer_clear (&tw); dispatcher->trampoline = GSIZE_TO_POINTER (ctx->pc + 1 ); }
这部分代码4/8字节的inline hook,都会执行。主要功能为往dispatcher(附近页找到的空白区域cave)写入跳板代码。根据重定向可用空间为4字节或8字节,deflector trampoline的构造情况具体如下。
总结 Frida 整体的inline hook实现原理图如下:
话说这需要deflector的流程中,4字节inline hook的一级跳板并没有将x0、lr存入栈中,但它同样也跳转到on_enter_trampoline,根据二级跳板的生成,需要中继器deflector的会在on_enter_trampoline写入第一条指令ldp x0,lr, [sp, #-0x10]!,这样对4字节的inline hook不会有什么影响嘛?希望大佬帮忙解答一下我的疑惑。
参考:
https://deepwiki.com/frida/frida-gum
[Frida Interceptor Hook实现原理图 | LLeaves Blog](https://blog.lleavesg.top/article/Frida Interceptor Hook实现原理图)