-
-
[原创]frida-gum 源码解读
-
发表于: 2025-11-5 20:01 559
-
学习 frida 源码,先从 test 案例入口开始分析,以 https://bbs.kanxue.com/thread-278423.htm 作为指导进行学习,有理解不对的地方欢迎指正,谢谢。
测试文件 frida\subprojects\frida-gum\tests\core\arch-arm64\interceptor-arm64.c
你看到的这段代码是一个 单元测试(Unit Test)。
目标模块: Interceptor (拦截器),这是 Frida 用来实现 Inline Hook 的核心组件。
目标架构: interceptor_arm64,这特指是为 ARM64 架构(目前绝大多数 64 位智能手机的 CPU 架构)编写的测试。
核心概念:lr 寄存器
测试的真正目的:一个棘手的边界情况

这部分很简单,只是一个目录,告诉测试框架:“嘿,我们要跑这两个测试。”
这个测试用例测试的是:Hook 一个“会读取 lr 寄存器”的*普通函数*。
这个测试用例测试的是:Hook 一个“会读取 lr 寄存器”的*Thunk 函数*。
这两个测试非常精彩,它们共同验证了 Interceptor 的一个核心能力:
这就是 Frida 强大的 稳定性(Robustness) 的体现。它不仅能 Hook 函数,还能在 Hook 的同时,保证原函数的(尤其是依赖 lr 这种易变状态的)逻辑不被破坏。
下面从“它做了什么” (What it does) 转向了“它是怎么做的” (How it does it)。
看上面两个 TESTCASE 中的公共函数 interceptor_fixture_attach 是 Frida 测试框架中用于“挂钩” (Hook) 的辅助函数 (Helper Functions)。让我们来解剖它们。
interceptor_fixture_attach 是一个 “必须成功”的包装器。
它存在的意义就是为了让 TESTCASE 里的代码更简洁。在测试中,我们 期望 Hook 总是成功的。所以我们用这个函数:它去尝试 Hook,如果失败了,就直接让测试失败。如果成功了,它就安静地返回。
这是真正的主菜。这个函数负责 创建并注册一个监听器 (Listener),然后把它附加 (attach) 到目标函数上。
我们把测试需要的数据存放到“工具箱” ctx 中。
ctx->fixture = h;:把测试“固定装置”h 存进去。
ctx->enter_char = enter_char;:把 '>' 存进去。
ctx->leave_char = leave_char;:把 '<' 存进去。
回顾一下 Hooked 函数调用 的工作流:
目标函数被调用,Hook 触发。
Frida 调用 on_enter(即 arm64_listener_context_on_enter)。
Frida 把 user_data(即 ctx)传给 on_enter。
on_enter 函数内部:
(1). 通过 ctx 拿到 ctx->fixture(即 h)。
(2). 通过 ctx 拿到 ctx->enter_char(即 '>')。
(3). 把 '>' 追加到 h->result->str 字符串的末尾。
Frida 调用原始的目标函数(ctx.run() 内部的原始逻辑)。
原始函数执行完毕,准备返回。Frida 在它真正返回 之前,调用 on_leave (即 arm64_listener_context_on_leave)。
on_leave 函数内部:
(1). 通过 ctx 拿到 ctx->fixture(即 h)。
(2). 通过 ctx 拿到 ctx->leave_char(即 '<')。
(3) 把 '<' 追加到 h->result->str 字符串的末尾。
(此时,h->result->str 的内容变成了 "><")
on_leave 返回,Frida 将原始函数的返回值(如果有的话)交还给调用者。
这就是 fixture->result->str == "><" 这个断言的由来!
通过分析这两个函数,你实际上已经学通了 Frida Interceptor (C API 层面) 的标准工作流程:
分配上下文 (Context):创建一个结构体(如 Arm64ListenerContext)来存放你需要的所有信息(回调函数、用户数据等)。
创建监听器 (Listener):创建一个 GumInvocationListener 对象。
配置回调 (Callbacks):设置 listener->on_enter 和 listener->on_leave 指向你自己的 C 函数。
设置 user_data:把你的 上下文 (Context) 地址赋值给 listener->user_data。这是连接回调函数和你的数据的桥梁。
调用 gum_interceptor_attach:传入目标函数和配置好的监听器,执行 Hook。
处理结果:
可以注意到,其中 on_enter 和 on_leave 是可以由用户自行重载的。然后再从 gum_interceptor_attach 进入,该函数包括了布置 hook 并启动 hook 的任务:
interceptor_fixture_try_attach 只是一个测试辅助函数,而你现在看到的 gum_interceptor_attach 则是 Frida Interceptor 模块的公开 C API 核心。所有语言(JavaScript, Python, Swift...)的 Interceptor.attach() 最终都会调用到这个 C 函数。
让我们来一步步解剖这个“引擎”。
这个函数的 真正工作 可以分为两个阶段:
这是在执行“高危操作”(修改正在运行的程序的代码)之前的标准 安全措施。
精妙的技巧! 这行代码告诉 Interceptor:“嘿,接下来我(指当前这个线程)要执行一些内部操作了。如果我不小心调用了 其他 已经被 Hook 的函数,请不要 触发它们的 onEnter/onLeave 回调。”
为什么这么做?
在 Frida Hook 框架中,当我们对一个函数(如 open, recv, malloc)安装拦截(intercept)后,所有线程执行该函数时都会跳转到我们注入的 trampoline / replacement 函数。但有时,我们自己当前线程的代码 也会调用被 Hook 的函数(比如 Hook malloc 时,我们自己的日志模块也会用到 malloc),
这就可能导致:
当前线程陷入递归 hook → 死循环 → 崩溃。
为了避免这种情况,Frida 提供了:
最后,我们来看看那些 goto 语句。在 C 语言中,goto 经常被(正确地)用于 集中的错误处理和资源释放。这是一种非常标准的模式。

gum_interceptor_attach 是一个 非常健壮 的函数:
Inline Hook(也称内联钩子)是一种低级的函数钩子技术,它通过直接修改目标函数的机器码(通常是函数开头的几条指令)来实现钩子。具体来说,它会用跳转指令(如 jmp)替换函数的起始代码,将执行流重定向到自定义的钩子函数中,执行完钩子后再跳回原函数继续运行。这种方式效率高,但实现复杂,需要处理架构差异(如 ARM、x86)和指令对齐问题。
Inline Hook 可以看作是 Frida 等工具可能采用的一种底层技术,但 Frida 提供了更全面的生态和易用性。如果你是在开发钩子工具,Frida 更推荐入门;如果追求低级控制,则需掌握 Inline Hook。
当一个被 Hook 的函数(TargetFunction)被调用时,它首先会跳转(JUMP)到 Frida 的“总调度中心”(Dispatcher)。这个Dispatcher会先保存现场,然后循环调用所有已注册的 onEnter 监听器(Listener)。接着,它会通过“跳板机”(Trampoline)来执行被覆盖的几条原始指令,并跳回到原函数的“身体”部分继续执行。当原函数执行完毕返回时,Dispatcher会再次捕获返回,循环调用所有 onLeave 监听器,最后才恢复现场,将控制权和(可能被修改过的)返回值交还给最初的调用者。

关于 lr 寄存器、Thunk、Trampoline 的 所有 汇编层面的“脏活累活”,几乎都发生在这个函数调用的深处。
gum_interceptor_attach (我们上一步看的) 只是一个“前台接待”,它负责处理锁、事务和 Listener 列表。
gum_interceptor_instrument 则是“后台的建筑工头”。它的工作是:“去,给我把 function_address 这个地方的‘舞台’(function_ctx)建好!”
让我们逐段解剖这个“工头”的施工流程。
这是 整个 Interceptor 中最重要的优化。
self->function_by_address 是一个 哈希表(HashTable),你可以把它想象成一个“全局登记册”。
g_hash_table_lookup(..., function_address) 的意思是:“去登记册里查一下,我们是不是已经为 function_address 这个地址建过‘舞台’(function_ctx)了?”
if (ctx != NULL):
查到了! 这意味着这是对该函数的 第二次(或第 N 次)attach。
“工头”一看:“哦,舞台(ctx)已经在了。”
它什么也不用做,直接返回之前建好的那个 ctx。
这就是为什么我们说“插桩 (Instrument) 只会发生一次”!
if (ctx->type != type)
type (类型) 这个参数,代表你 这次 想用 哪种方式 来 Hook 这个函数。
这个 if (ctx->type != type) 检查就是在说:
“OK,我发现这个函数(function_address)已经被 Hook 过了(ctx != NULL)。
“我检查一下当初 Hook 它的类型(ctx->type),是不是和你 这次 想用的类型(type)一样?
“如果不一样(比如你之前用 attach Hook 了它,现在又想用 replace 去 Hook 它),那对不起,这两种操作是 互斥 的!我不能让你这么做。”
这是一个 安全锁,它确保你 不能对同一个函数混用不同类型的 Hook(比如在 attach 之后又去 replace 它)。如果 Frida 发现你试图这么做,它会返回 GUM_INSTRUMENTATION_ERROR_WRONG_TYPE(类型错误)来拒绝这次操作。
if (ctx == NULL):
_gum_interceptor_backend_create 关键函数
这是 最核心的施工步骤。
gum_interceptor_instrument 是 Interceptor 的“建築工頭”,它是一個 效率與安全並重 的函數,其核心職責是 “建造”(插樁)一個函數的“舞台”(ctx),並確保這個建造過程只發生一次。
先看【Interceptor的核心——gum_interceptor_instrument 】代码中的第 2 阶段:【准备施工队】—— “我的汇编工具在哪?”的_gum_interceptor_backend_create 函数
因此我们跟到 _gum_interceptor_backend_create 里看看它是如何实现的。该函数是平台相关的具体函数,由于打算分析 arm64 下的实现,因此这里的源代码应为 frida-gum/gum/backend-arm64/guminterceptor-arm64.c
你現在看到的 _gum_interceptor_backend_create 函數,就是 gum_interceptor_instrument (建築工頭) 在找不到“施工隊”(self->backend == NULL)時,調用的“HR/工具房管理員”。
它的職責是:創建並初始化一個特定於 CPU 架構(在這裡是 ARM64)的 backend(“施工隊”),並為其配備所有必需的工具。
_gum_interceptor_backend_create 是 Interceptor 的“军械库/工具房”。
gum_interceptor_backend_create_thunks 及其调用的所有函数,其唯一目的就是预先 JIT(即时编译) (创建)两个可重用的汇编代码块:enter_thunk 和 leave_thunk。
这正是我们之前所说的“性能优化”。Frida 无需在 每次 attach 时都重新生成这些通用的“桥接”代码,而是在启动时就将它们一次性造好。
让我们像搭乐高一样,从最小的零件(enter/leave thunk)开始,逐步拼装出完整的逻辑。
从底层向上层看代码
这是“进入 Thunk”(enter_thunk)的汇编代码 生成器。
enter_thunk 是一个标准化的汇编“零件”,它是在 Dispatcher(总调度中心)内部被调用的。它的工作是 从纯汇编的世界“桥接”到 C 语言的世界,去 调用那个真正执行 onEnter 循环的 C 函数。
这是“离开 Thunk”(leave_thunk)的汇编代码 生成器。
它几乎和 enter_thunk 一模一样,但它调用的是 C 函数 _gum_function_context_end_invocation。
这是“Thunk 布局引擎”。它调用上面的两个“生成器”,把 enter_thunk 和 leave_thunk 一前一后 地写(JIT, 即时编译)到一块内存里。
我们来详细分解 gum_emit_thunks 这个“布局引擎”的工作流程:
总结一下:
“一前一后”的实现非常简单:
这就好像 用笔在纸上写字,enter_thunk 写完了不换行,紧接着写 leave_thunk。
这是“Thunk 生产线经理”。它负责申请内存、“隐藏”内存,并启动“布局引擎”(gum_emit_thunks)。
gum_interceptor_backend_create_thunks 是一个 性能优化。它在 Interceptor 启动时,预先 JIT 编译 了两个高度可重用的汇编“零件”:
Dispatcher(总调度中心)在 JIT 编译时,就不再需要自己生成这些复杂的“调用 C”的代码了,它只需要 JIT 编译两条简单的 BL(跳转)指令,分别跳转到这两个 已经预制好 的 thunk 地址即可。这就是“搭积木”的原理。
这个函数是真正执行 onEnter 逻辑的地方。它在纯 C 语言环境中运行,负责处理所有复杂的逻辑:比如递归保护、线程过滤、调用 onEnter 循环,以及决定下一步该做什么(是去执行原函数?还是去执行替换函数?)。
让我们来详细拆解这个“大脑”的思维过程。
_gum_function_context_begin_invocation:
_gum_function_context_end_invocation:
在深入代码之前,我们必须先理解它的参数,尤其是最后两个,它们是 “出参”(out-parameters),这个函数通过 修改它们 来 告诉 外层(即 enter_thunk 汇编代码)接下来该跳到哪里去。
问题:之前的【第 4 部分(最上层):gum_interceptor_backend_create_thunks】中的gum_cloak_add_range 已经“隐藏”了内存,避免无限递归,为什么还要做下面的保护措施呢?
gum_cloak_add_range 确实是“隐藏”了内存,但让我们搞清楚它们各自的 保护对象 是谁。
_gum_function_context_begin_invocation 里面的两个“保镖”(gum_tls_key_get_value 和 calling_replacement)不是 用来防 Stalker 的。
你设想的场景(onEnter(A) 调用 function_B)还不是最直接的递归。
让我们用一个更常见、更危险的真实场景来解释:onEnter 内部调用了 console.log。
想象一下,console.log 最终会调用底层的 write 函数来向屏幕输出。
你,作为用户,Hook 了底层的 write 函数:
JavaScript
现在,你的程序调用了 write(比如,printf("Hello"))。
下面是“无限递归”的开始(假设没有“守卫”):
现在,我们看看 有了“守卫”,Frida 是如何优雅地处理这个灾难的:
“守卫”(gum_tls_key_get_value)的用途是:防止一个 onEnter 回调(或 replace 实现)不小心调用了 另一个(甚至是 同一个 )被 Hook 的函数,从而导致 Interceptor 逻辑“重入”(Re-entrancy)而引发无限递归。
它就像一个“请勿打扰”的门牌:Frida 在进入 C 语言总控室时挂上门牌,如果它在挂着门牌时又有人来敲门(递归调用),它就知道这是“自己人”在办事,于是直接让他们“走旁门”(goto bypass)去执行原始函数,而不是再走一遍“总控室”逻辑。
我们来详细解释一下这个“干扰”是怎么发生的。
errno (在 Windows 上是 GetLastError())并不是一个普通的变量,它是一个 保存在线程局部存储(TLS)中的“最后错误码”。
C 语言中很多函数的错误处理都依赖它。一个非常经典的“错误”流程是这样的:
现在,假设我们 Hook 了另一个函数,比如 write,而程序是这样执行的:
结果就是: 你的 onEnter 回调(通过 console.log)意外地““治愈”了”原始程序的错误状态,导致原始程序的错误处理逻辑(if (errno == 2) { ... })失效 了,从而引发了 Bug。
逻辑: 这一长串 if 语句在做一个决定:gboolean invoke_listeners = ?(我们到底要不要运行 onEnter?)
检查 1: selected_thread_id。你是否 attach 时只指定了某个特定线程?如果是,我们现在是那个线程吗?
检查 2: ignore_level。gum_interceptor_ignore_current_thread()(attach 里的那个)就是增加了这个 ignore_level。如果 > 0,就 不 调用。
invoke_listeners 目前是 true
interceptor_ctx->ignore_level 是什么?
(interceptor_ctx->ignore_level <= 0) 的判断
这行代码是 ignoreCurrentThread() 功能的“执行者”。它确保了:
这和我们之前讨论的 “递归保镖”(gum_tls_key_get_value)是两个 不同 的机制:
“递归保镖”(gum_tls_key_get_value):是 自动的,防止 onEnter 调用 console.log 这种 意外 的无限递归。
“忽略计数器”(ignore_level):是 手动的,让你(用户)可以 主动地 在一段时间内关闭当前线程的所有 Hook。
检查 3: unignorable_listener(不可忽略的 Listener)。这是一个例外:即使上面两项检查都说“不要调用”,但如果存在一个“不可忽略”的 Listener,我们 必须 调用它。
逻辑: 这是在为 onLeave 做准备。
will_trap_on_leave = ?(我们需要 onLeave 吗?)
如果这是一个 replace 操作(replacement_function != NULL),是。
如果我们要调用 Listener(invoke_listeners)并且 它们中有任何一个注册了 onLeave,是。
否则,否。
解释说明:
这行代码是在决定:“Frida 是否需要在目标函数执行完毕、即将返回时,再次‘劫持’控制权?”
will_trap_on_leave 是一个布尔值(true 或 false),它的意思是“是否要在离开时设置陷阱?”
所以,Frida 必须在 onEnter 阶段就决定是否需要 onLeave。它只在以下两种情况下才 必须(||,或)去劫持返回:
if (will_trap_on_leave)(如果需要 onLeave):
else if (invoke_listeners)(如果只需要 onEnter,不需要 onLeave):
gum_function_context_fixup_cpu_context(...):
if (invoke_listeners):
invocation_ctx->cpu_context = cpu_context;
关键! 这行代码把“存包小票”(invocation_ctx)和“CPU 状态”(cpu_context)关联 起来。
invocation_ctx 的真正意思是“调用上下文”(Invo cation C on t e x t),它是一个 “句柄” (Handle),代表了 “这一次” 函数调用。
想象一下你去一个需要“存包”和“取包”的地方(比如游乐园):
现在,当 JS 回调中执行 this.context.x0 = 0x123 时,它修改的就是 cpu_context->x0,也就是 堆栈上真实的 X0!
for (i = 0; ...):
这就是 循环,遍历所有 listener。
listener_entry->listener_interface->on_enter(...):
执行! 这就是调用 C 回调(arm64_listener_context_on_enter)的地方,C 回调再去调用 JS 的 onEnter。
我们最开始分析的测试代码 interceptor_fixture_try_attach ,在那里面做了这件事:
现在,for 循环里的 listener_entry->listener_interface->on_enter(...) 调用的,正是我们当初注册的回调函数 arm64_listener_context_on_enter!
从而达成 完整的调用链:
_gum_function_context_begin_invocation 是 Interceptor 的 “C 语言总控室”。
它在 enter_thunk(汇编桥梁)之后、onEnter(JS 回调)之前运行。它负责:
通过 gum_tls_key_get_value 防止无限递归。
通过 selected_thread_id 和 ignore_level 过滤 掉不应触发的调用。
通过 gum_invocation_stack_push 保存 onLeave 所需的状态(“存包”)。
通过 invocation_ctx->cpu_context = cpu_context 将 JS 的 this.context 链接 到真实的 CPU 寄存器,从而可以修改读取寄存器值。
循环调用 所有 onEnter 回调。
决定 enter_thunk 的 “下一跳”(*next_hop):是去原函数还是替换函数?
劫持 CPU 的 “返回地址”(*caller_ret_addr):是返回给原调用者,还是返回给 on_leave_trampoline?
在以下两种情况下,will_trap_on_leave 会为 true,Frida 会 覆写返回地址:
在这两种情况下,Frida 都 必须 在函数返回时重新获得控制权(去执行 onLeave 循环或清理堆栈),因此它 必须 劫持返回地址。
返回给“原调用者”
在以下情况下,will_trap_on_leave 会为 false,Frida 不会 触碰返回地址:
在这种情况下,Frida 知道 onLeave 阶段无事可做,它会选择性能最高的路径:在 onEnter 执行完毕后,让原函数直接 RET(返回)给它的“原调用者”,Frida 不再介入。
分析完onEnter,我们再看onLeave: 第2部分gum_emit_leave_thunk(我们之前分析的), 它调用的是这个 _gum_function_context_end_invocation C 函数。
gum_emit_leave_thunk(汇编桥梁)的工作就是调用这个 C 函数。
这个函数是 “C 语言总控室”的下半场。onEnter 阶段(begin_invocation)负责了 “存包”、决策、和“劫持”返回地址。而这个 end_invocation 函数(onLeave 阶段)则负责:“取包”、执行 onLeave 循环、清理、并最终“放行”。
_gum_function_context_end_invocation 是 Interceptor 的“C 语言收尾室”。
它在 on_leave_trampoline 劫持了函数返回后被调用。它负责:
现在我在返回【Interceptor的核心——gum_interceptor_instrument 】的代码中去分析 第 3 阶段:【施工!】—— “开工!建 Trampoline!”中的 _gum_interceptor_backend_create_trampoline 函数

我们终于抵达了 Interceptor 的 “施工现场”!
gum_interceptor_instrument(“工头”)在检查了“缓存”后,如果发现是第一次 Hook,就会调用这个 _gum_interceptor_backend_create_trampoline 函数。
这就是 “施工队” (backend) 实际在“建造” Trampoline(跳板机)和 Dispatcher(总调度中心)的地方。
我们最初关于 lr 寄存器的所有问题,都将在这个函数里找到最终的答案。
让我们开始“施工”!
gum_interceptor_backend_prepare_trampoline(...):
gum_arm64_writer_reset (aw, ctx->trampoline_slice->data);:
ctx->on_enter_trampoline = gum_sign_code_pointer (gum_arm64_writer_cur (aw));:
这行代码的 核心目的 是:JIT 编译(即时编译)Dispatcher(总调度中心)的入口点,对其进行“安全签名”,然后把这个入口地址“登记”到“施工蓝图” (ctx) 中。
让我们从右到左,一步一步把它分解开:
gum_arm64_writer_cur (aw)
gum_sign_code_pointer ( ... )
ctx->on_enter_trampoline = ...
这就是 Dispatcher 的“JIT 编译”过程!
onEnter 部分:
LDR X17, [ctx_addr]:JIT 一条指令,把“舞台” (ctx) 的地址加载到 X17 寄存器。(然后回顾之前分析的 第 1 部分(最底层):gum_emit_enter_thunk代码中 X17 在 enter_thunk 中被用作第一个参数)。
LDR X16, [enter_thunk_addr]:JIT 一条指令,把我们 预先造好 的 enter_thunk(还记得吗?那个 C 语言桥梁)的地址加载到 X16。
BR X16:JIT 一条指令,JUMP 到 enter_thunk。

实际调试演示:
特征检测相关文章:
onLeave 部分:
ctx->on_leave_trampoline = ...:“标记 onLeave 的入口!”
下面 JIT 了 一模一样 的代码,只是它加载的是 leave_thunk 的地址。(然后回顾之前分析的 第 2 部分(最底层):gum_emit_leave_thunk代码中 X17 在 leave_thunk 中被用作第一个参数)。
至此,“总调度中心” (Dispatcher) 建造完毕。 它包含两个部分,分别用于 onEnter 和 onLeave。
这就是我们所有分析的“风暴中心”! 这就是对你最初 lr 测试用例的 生产环境 解决方案。
is_eligible_for_lr_rewriting = ...:
if (is_eligible_for_lr_rewriting)(如果启用):
关于 Interceptor 如何(以及何时)决定修复 LR(链接寄存器)读取的核心逻辑。
问题 1:“矛盾”:为什么比较的长度不同?(8 字节 vs 16 字节)
答案: 因为 Frida “偷”的字节数(data->redirect_code_size)是 动态的,而不是固定的 8 字节。
在第 1 阶段:准备“施工地”和“入口”代码中的 gum_interceptor_backend_prepare_trampoline(“预分析”函数)会智能地分析目标函数,来决定“安全偷取”的最小字节数(决定是“偷”4 字节 or 8 字节 or 16 字节 等等):
结论: 8 字节和 16 字节并不矛盾。它们是 Frida 为了 两种不同情况 而设置的 两种不同长度 的“高风险”签名。
问题 2:为什么是这两个“签名”?(目的)
答案: 这是一个 “启发式检查”(Heuristic Check),是 Frida 最重要的 性能优化 之一。
Frida 不会 在 每个 函数上都执行昂贵的 LR 修复(即逐条检查指令的 while 循环),因为太慢了。相反,它使用这两个“签名”作为 “高风险特征码”(Red Flags) 来 “快速筛选”:
总结: 这行代码的 唯一目的,就是 “解锁” 那个昂贵的 LR 修复逻辑(while 循环),只对“高风险分子”进行逐条扫描。
问题 3:假如“被偷”的指令中没有读取 LR,但是后续(函数“身体”里)有读取 LR,还会修复吗?
答案:会!但是通过*另一种*机制。
Frida 有 两套 LR 修复方案,互为补充:
结论:
问题 4:开头‘长得’像 "stp;mov;mov;bl" 的函数,第二个 mov 是不是就是 MOV Xn, LR?
答案:不是的。
这是一个非常常见的、也是非常合理的推测,但答案是否定的。
“签名” (signature->str) 是一个“傻瓜式”检查。
这个“签名” ("stp;mov;mov;bl") 只是一个“高风险特征码”。
打个比方:
while 循环(peek_next_write_insn)才是真正的“搜身”(Detailed Search)。它会逐个检查那 4 条“被偷”的指令的**“操作数”,去真正地寻找**那把“武器”(MOV Xn, LR)。
总结: "stp;mov;mov;bl" 里的 mov 不是 MOV Xn, LR。它只是一个“外貌特征”,这个“外貌”足以**“解锁”**那个真正会去检查 LR 的 while 循环。
问题 5:“解锁”那个昂贵的 while 循环,去逐条扫描,扫描多少字节?
答案:它扫描的不是“字节”,而是“被偷走的指令列表”。
while 循环(while ((insn = gum_arm64_relocator_peek_next_write_insn (ar)) != NULL))不会再去内存中读取新的字节。
它扫描(遍历)的是已经被 do...while 循环“偷走”并暂存在 ar(“搬运工”)对象中的那个指令列表。
所以,这个 while 循环的迭代次数是固定的,它等于“被偷走”的指令数量:
结论: 这个昂贵的 while 循环,它的扫描范围仅限于被 do...while 循环(为了腾出空间而)“偷走”的那几条指令。它不会扫描函数“身体”的其余部分(这就是为什么我们还需要“运行时修复” gum_function_context_fixup_cpu_context)。
_gum_interceptor_backend_create_trampoline 是 Interceptor 中 最复杂 的 JIT 编译函数(“施工现场”)。
Stalker 的主要目标是跟踪一个线程的执行流(Code Execution Tracing),而不是监控内存访问。
它的大致原理是这样的:
Stalker 是一种非常精细的执行跟踪技术,它让您几乎可以在每条指令(或每个基本块)执行前后“挂钩”(hook),从而实现代码覆盖率分析、路径跟踪等高级功能。
TESTLIST_BEGIN (interceptor_arm64) TESTENTRY (attach_to_thunk_reading_lr)
TESTENTRY (attach_to_function_reading_lr)
TESTLIST_END ()TESTLIST_BEGIN (interceptor_arm64) TESTENTRY (attach_to_thunk_reading_lr)
TESTENTRY (attach_to_function_reading_lr)
TESTLIST_END ()TESTCASE (attach_to_function_reading_lr){ // 1. 准备工作:分配内存
const gsize code_size_in_pages = 1;
gsize code_size;
GumEmitLrFuncContext ctx; // 'ctx' 是一个结构体,像个工具箱,存放这次测试需要的所有变量
code_size = code_size_in_pages * gum_query_page_size (); // 计算1个内存页到底是多少字节
ctx.code = gum_alloc_n_pages (code_size_in_pages, GUM_PAGE_RW); // 分配1页可读(R)可写(W)的内存
// 2. 初始化工具箱
ctx.run = NULL; // C语言的函数指针,指向我们要测试的函数
ctx.func = NULL; // 原始函数指针
ctx.caller_lr = 0; // 用来存放“期望的”lr值
// 3. 动态生成测试函数(最关键的步骤之一)
gum_memory_patch_code (ctx.code, code_size, gum_emit_lr_func, &ctx);
TESTCASE (attach_to_function_reading_lr){ // 1. 准备工作:分配内存
const gsize code_size_in_pages = 1;
gsize code_size;
GumEmitLrFuncContext ctx; // 'ctx' 是一个结构体,像个工具箱,存放这次测试需要的所有变量
code_size = code_size_in_pages * gum_query_page_size (); // 计算1个内存页到底是多少字节
ctx.code = gum_alloc_n_pages (code_size_in_pages, GUM_PAGE_RW); // 分配1页可读(R)可写(W)的内存
// 2. 初始化工具箱
ctx.run = NULL; // C语言的函数指针,指向我们要测试的函数
ctx.func = NULL; // 原始函数指针
ctx.caller_lr = 0; // 用来存放“期望的”lr值
// 3. 动态生成测试函数(最关键的步骤之一)
gum_memory_patch_code (ctx.code, code_size, gum_emit_lr_func, &ctx);
// 4. 【测试点A】: 验证函数在 Hook 前是否正常g_assert_cmphex (ctx.run (), ==, ctx.caller_lr);// 4. 【测试点A】: 验证函数在 Hook 前是否正常g_assert_cmphex (ctx.run (), ==, ctx.caller_lr);// 5. 【核心操作】: Attach!开始 Hookinterceptor_fixture_attach (fixture, 0, ctx.func, '>', '<');
// 5. 【核心操作】: Attach!开始 Hookinterceptor_fixture_attach (fixture, 0, ctx.func, '>', '<');
// 6. 【测试点B】: 验证 Hook 后的行为g_assert_cmphex (ctx.run (), !=, ctx.caller_lr);g_assert_cmpstr (fixture->result->str, ==, "><");
// 6. 【测试点B】: 验证 Hook 后的行为g_assert_cmphex (ctx.run (), !=, ctx.caller_lr);g_assert_cmpstr (fixture->result->str, ==, "><");
// 7. 清理工作
interceptor_fixture_detach (fixture, 0); // 卸载 Hook
gum_free_pages (ctx.code); // 释放内存
} // 7. 清理工作
interceptor_fixture_detach (fixture, 0); // 卸载 Hook
gum_free_pages (ctx.code); // 释放内存
}TESTCASE (attach_to_thunk_reading_lr){ // 1. 准备工作 (和上一个测试完全一样)
const gsize code_size_in_pages = 1;
gsize code_size;
GumEmitLrThunkContext ctx; // 这次用的是 Thunk 的“工具箱”
code_size = code_size_in_pages * gum_query_page_size ();
ctx.code = gum_alloc_n_pages (code_size_in_pages, GUM_PAGE_RW);
ctx.run = NULL;
ctx.thunk = NULL;
ctx.expected_lr = 0;
// 2. 动态生成 Thunk 函数
gum_memory_patch_code (ctx.code, code_size, gum_emit_lr_thunk, &ctx);
TESTCASE (attach_to_thunk_reading_lr){ // 1. 准备工作 (和上一个测试完全一样)
const gsize code_size_in_pages = 1;
gsize code_size;
GumEmitLrThunkContext ctx; // 这次用的是 Thunk 的“工具箱”
code_size = code_size_in_pages * gum_query_page_size ();
ctx.code = gum_alloc_n_pages (code_size_in_pages, GUM_PAGE_RW);
ctx.run = NULL;
ctx.thunk = NULL;
ctx.expected_lr = 0;
// 2. 动态生成 Thunk 函数
gum_memory_patch_code (ctx.code, code_size, gum_emit_lr_thunk, &ctx);
// 3. 【测试点A】: 验证 Thunk 在 Hook 前是否正常g_assert_cmphex (ctx.run (), ==, ctx.expected_lr);// 3. 【测试点A】: 验证 Thunk 在 Hook 前是否正常g_assert_cmphex (ctx.run (), ==, ctx.expected_lr);// 4. 【核心操作】: Attach!Hook 这个 Thunkinterceptor_fixture_attach (fixture, 0, ctx.thunk, '>', '<');
// 4. 【核心操作】: Attach!Hook 这个 Thunkinterceptor_fixture_attach (fixture, 0, ctx.thunk, '>', '<');
// 5. 【测试点B】: 验证 Hook 后的行为(这是最关键的区别!)g_assert_cmphex (ctx.run (), ==, ctx.expected_lr);g_assert_cmpstr (fixture->result->str, ==, "><");
// 5. 【测试点B】: 验证 Hook 后的行为(这是最关键的区别!)g_assert_cmphex (ctx.run (), ==, ctx.expected_lr);g_assert_cmpstr (fixture->result->str, ==, "><");
// 6. 清理工作
interceptor_fixture_detach (fixture, 0);
gum_free_pages (ctx.code);
} // 6. 清理工作
interceptor_fixture_detach (fixture, 0);
gum_free_pages (ctx.code);
}static void
interceptor_fixture_attach (InterceptorFixture * h, guint listener_index,
gpointer test_func,
gchar enter_char,
gchar leave_char)
{ g_assert_cmpint (interceptor_fixture_try_attach (h, listener_index, test_func,
enter_char, leave_char), ==, GUM_ATTACH_OK);
}static void
interceptor_fixture_attach (InterceptorFixture * h, guint listener_index,
gpointer test_func,
gchar enter_char,
gchar leave_char)
{ g_assert_cmpint (interceptor_fixture_try_attach (h, listener_index, test_func,
enter_char, leave_char), ==, GUM_ATTACH_OK);
}static GumAttachReturn
interceptor_fixture_try_attach (InterceptorFixture * h, guint listener_index,
gpointer test_func,
gchar enter_char,
gchar leave_char)
{ GumAttachReturn result;
Arm64ListenerContext * ctx;
static GumAttachReturn
interceptor_fixture_try_attach (InterceptorFixture * h, guint listener_index,
gpointer test_func,
gchar enter_char,
gchar leave_char)
{ GumAttachReturn result;
Arm64ListenerContext * ctx;
// --- 步骤 1: 清理可能存在的旧监听器 ---ctx = h->listener_context[listener_index];if (ctx != NULL)
{ arm64_listener_context_free (ctx);
h->listener_context[listener_index] = NULL;
}// --- 步骤 1: 清理可能存在的旧监听器 ---ctx = h->listener_context[listener_index];if (ctx != NULL)
{ arm64_listener_context_free (ctx);
h->listener_context[listener_index] = NULL;
}// --- 步骤 2: 创建新的监听器上下文 (Context) ---ctx = g_slice_new0 (Arm64ListenerContext);// --- 步骤 2: 创建新的监听器上下文 (Context) ---ctx = g_slice_new0 (Arm64ListenerContext);// --- 步骤 3: 配置监听器 (Listener) ---ctx->listener = test_callback_listener_new ();ctx->listener->on_enter =(TestCallbackListenerFunc) arm64_listener_context_on_enter;ctx->listener->on_leave =(TestCallbackListenerFunc) arm64_listener_context_on_leave;ctx->listener->user_data = ctx;// --- 步骤 3: 配置监听器 (Listener) ---ctx->listener = test_callback_listener_new ();ctx->listener->on_enter =(TestCallbackListenerFunc) arm64_listener_context_on_enter;ctx->listener->on_leave =(TestCallbackListenerFunc) arm64_listener_context_on_leave;ctx->listener->user_data = ctx;// --- 步骤 4: 填充我们自己的上下文信息 ---ctx->fixture = h;ctx->enter_char = enter_char;ctx->leave_char = leave_char;// --- 步骤 4: 填充我们自己的上下文信息 ---ctx->fixture = h;ctx->enter_char = enter_char;ctx->leave_char = leave_char;// --- 步骤 5: 执行真正的附加 (Attach) 操作 ---result = gum_interceptor_attach (h->interceptor, test_func, GUM_INVOCATION_LISTENER (ctx->listener), NULL,
GUM_ATTACH_FLAGS_NONE);
// --- 步骤 5: 执行真正的附加 (Attach) 操作 ---result = gum_interceptor_attach (h->interceptor, test_func, GUM_INVOCATION_LISTENER (ctx->listener), NULL,
GUM_ATTACH_FLAGS_NONE);
// --- 步骤 6: 根据结果进行处理 ---
if (result == GUM_ATTACH_OK)
{
h->listener_context[listener_index] = ctx;
}
else
{
arm64_listener_context_free (ctx);
}
return result;
} // --- 步骤 6: 根据结果进行处理 ---
if (result == GUM_ATTACH_OK)
{
h->listener_context[listener_index] = ctx;
}
else
{
arm64_listener_context_free (ctx);
}
return result;
}//frida\subprojects\frida-gum\gum\guminterceptor.cGumAttachReturngum_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, &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;
instrumentation_error: {
switch (error)
{
case GUM_INSTRUMENTATION_ERROR_WRONG_SIGNATURE:
result = GUM_ATTACH_WRONG_SIGNATURE;
break;
case GUM_INSTRUMENTATION_ERROR_POLICY_VIOLATION:
result = GUM_ATTACH_POLICY_VIOLATION;
break;
case GUM_INSTRUMENTATION_ERROR_WRONG_TYPE:
result = GUM_ATTACH_WRONG_TYPE;
break;
default:
g_assert_not_reached ();
}
goto beach;
}
already_attached: {
result = GUM_ATTACH_ALREADY_ATTACHED;
goto beach;
}
beach: {
gum_interceptor_transaction_end (&self->current_transaction);
GUM_INTERCEPTOR_UNLOCK (self);
gum_interceptor_unignore_current_thread (self);
return result;
}
}//frida\subprojects\frida-gum\gum\guminterceptor.cGumAttachReturngum_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, &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;
instrumentation_error: {
switch (error)
{
case GUM_INSTRUMENTATION_ERROR_WRONG_SIGNATURE:
result = GUM_ATTACH_WRONG_SIGNATURE;
break;
case GUM_INSTRUMENTATION_ERROR_POLICY_VIOLATION:
result = GUM_ATTACH_POLICY_VIOLATION;
break;
case GUM_INSTRUMENTATION_ERROR_WRONG_TYPE:
result = GUM_ATTACH_WRONG_TYPE;
break;
default:
g_assert_not_reached ();
}
goto beach;
}
already_attached: {
result = GUM_ATTACH_ALREADY_ATTACHED;
goto beach;
}
beach: {
gum_interceptor_transaction_end (&self->current_transaction);
GUM_INTERCEPTOR_UNLOCK (self);
gum_interceptor_unignore_current_thread (self);
return result;
}
}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;
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;gum_interceptor_ignore_current_thread (self);GUM_INTERCEPTOR_LOCK (self);gum_interceptor_transaction_begin (&self->current_transaction);self->current_transaction.is_dirty = TRUE;gum_interceptor_ignore_current_thread(self);
gum_interceptor_ignore_current_thread(self);
它的作用是: **让当前线程暂时被标记为“忽略拦截”**,
当被 hook 的函数在该线程中执行时,Frida 会自动跳过 hook 路径,直接执行原始函数。它的作用是: **让当前线程暂时被标记为“忽略拦截”**,
当被 hook 的函数在该线程中执行时,Frida 会自动跳过 hook 路径,直接执行原始函数。function_address = gum_interceptor_resolve (self, function_address);function_ctx = gum_interceptor_instrument (self, GUM_INTERCEPTOR_TYPE_DEFAULT, function_address, &error);
if (function_ctx == NULL)
goto instrumentation_error;
function_address = gum_interceptor_resolve (self, function_address);function_ctx = gum_interceptor_instrument (self, GUM_INTERCEPTOR_TYPE_DEFAULT, function_address, &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;
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;
instrumentation_error: {
// ... 根据 'error' 类型设置 'result' 状态码 ...
goto beach;
}
already_attached: {
result = GUM_ATTACH_ALREADY_ATTACHED;
goto beach;
}
beach: {
gum_interceptor_transaction_end (&self->current_transaction);
GUM_INTERCEPTOR_UNLOCK (self);
gum_interceptor_unignore_current_thread (self);
return result;
}
instrumentation_error: {
// ... 根据 'error' 类型设置 'result' 状态码 ...
goto beach;
}
already_attached: {
result = GUM_ATTACH_ALREADY_ATTACHED;
goto beach;
}
beach: {
gum_interceptor_transaction_end (&self->current_transaction);
GUM_INTERCEPTOR_UNLOCK (self);
gum_interceptor_unignore_current_thread (self);
return result;
}
static GumFunctionContext *
gum_interceptor_instrument (/* ... */)
{ GumFunctionContext * ctx;
*error = GUM_INSTRUMENTATION_ERROR_NONE;
ctx = (GumFunctionContext *) g_hash_table_lookup (self->function_by_address,function_address); //function_by_address“全局登记册”
if (ctx != NULL)
{
if (ctx->type != type)
{
*error = GUM_INSTRUMENTATION_ERROR_WRONG_TYPE;
return NULL;
}
return ctx;
}
static GumFunctionContext *
gum_interceptor_instrument (/* ... */)
{ GumFunctionContext * ctx;
*error = GUM_INSTRUMENTATION_ERROR_NONE;
ctx = (GumFunctionContext *) g_hash_table_lookup (self->function_by_address,function_address); //function_by_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); //平台相关(当前 CPU 架构)的具体函数
}if (self->backend == NULL)
{ self->backend = _gum_interceptor_backend_create(&self->mutex, &self->allocator); //平台相关(当前 CPU 架构)的具体函数
}ctx = gum_function_context_new (self, function_address, type);if (gum_process_get_code_signing_policy () == GUM_CODE_SIGNING_REQUIRED)
{ //分支 A
if (!_gum_interceptor_backend_claim_grafted_trampoline (self->backend, ctx))
goto policy_violation; //违反政策 杀死APP
}else{ //分支 B
if (!_gum_interceptor_backend_create_trampoline (self->backend, ctx)) //就是我们一直在找的那个函数
goto wrong_signature;
}ctx = gum_function_context_new (self, function_address, type);if (gum_process_get_code_signing_policy () == GUM_CODE_SIGNING_REQUIRED)
{ //分支 A
if (!_gum_interceptor_backend_claim_grafted_trampoline (self->backend, ctx))
goto policy_violation; //违反政策 杀死APP
}else{ //分支 B
if (!_gum_interceptor_backend_create_trampoline (self->backend, ctx)) //就是我们一直在找的那个函数
goto wrong_signature;
}//登记到function_by_address“全局登记册”,实现“只插桩一次”的优化 g_hash_table_insert (self->function_by_address, function_address, ctx);//添加任务,设置回调函数 gum_interceptor_activate 用于激活跳板//gum_interceptor_activate真正是去内存中写入那条 `JUMP` 指令gum_interceptor_transaction_schedule_update (&self->current_transaction, ctx,gum_interceptor_activate);return ctx;
//登记到function_by_address“全局登记册”,实现“只插桩一次”的优化 g_hash_table_insert (self->function_by_address, function_address, ctx);//添加任务,设置回调函数 gum_interceptor_activate 用于激活跳板//gum_interceptor_activate真正是去内存中写入那条 `JUMP` 指令gum_interceptor_transaction_schedule_update (&self->current_transaction, ctx,gum_interceptor_activate);return ctx;
policy_violation: /* ... */
wrong_signature: /* ... */
propagate_error: {
gum_function_context_finalize (ctx);
return NULL;
}
policy_violation: /* ... */
wrong_signature: /* ... */
propagate_error: {
gum_function_context_finalize (ctx);
return NULL;
}
GumInterceptorBackend *_gum_interceptor_backend_create (GRecMutex * mutex, GumCodeAllocator * allocator)
{ GumInterceptorBackend * backend;
backend = g_slice_new0 (GumInterceptorBackend); //為“施工隊”(`backend`)本身分配內存
backend->mutex = mutex;
backend->allocator = allocator;
GumInterceptorBackend *_gum_interceptor_backend_create (GRecMutex * mutex, GumCodeAllocator * allocator)
{ GumInterceptorBackend * backend;
backend = g_slice_new0 (GumInterceptorBackend); //為“施工隊”(`backend`)本身分配內存
backend->mutex = mutex;
backend->allocator = allocator;
if (gum_process_get_code_signing_policy () == GUM_CODE_SIGNING_OPTIONAL)
{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_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 (backend);
}
return backend;
}static void gum_interceptor_backend_create_thunks (GumInterceptorBackend * self) //第4部分:最上层
{ gsize page_size, code_size;
GumMemoryRange range;
page_size = gum_query_page_size ();
code_size = page_size;
self->thunks = gum_memory_allocate (NULL, code_size, page_size, GUM_PAGE_RW);
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);
}static void gum_interceptor_backend_destroy_thunks (GumInterceptorBackend * self)
{ gum_memory_free (self->thunks, gum_query_page_size ());
}static void gum_emit_thunks (gpointer mem,GumInterceptorBackend * self)//第3部分:中间层
{ 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);
}static void
gum_emit_enter_thunk (GumArm64Writer * aw) //第1部分:最底层
{ 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);
}static void
gum_emit_leave_thunk (GumArm64Writer * aw) //第2部分:最底层
{ 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);
}static void gum_interceptor_backend_create_thunks (GumInterceptorBackend * self) //第4部分:最上层
{ gsize page_size, code_size;
GumMemoryRange range;
page_size = gum_query_page_size ();
code_size = page_size;
self->thunks = gum_memory_allocate (NULL, code_size, page_size, GUM_PAGE_RW);
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);
}static void gum_interceptor_backend_destroy_thunks (GumInterceptorBackend * self)
{ gum_memory_free (self->thunks, gum_query_page_size ());
}static void gum_emit_thunks (gpointer mem,GumInterceptorBackend * self)//第3部分:中间层
{ 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);
}static void
gum_emit_enter_thunk (GumArm64Writer * aw) //第1部分:最底层
{ 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);
}static void
gum_emit_leave_thunk (GumArm64Writer * aw) //第2部分:最底层
{ 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);
}static void
gum_emit_enter_thunk (GumArm64Writer * aw){ // 1. 发出“函数序言”(Prolog)
// 这会 JIT 编译出保存所有 CPU 寄存器(X0-X28, FP, LR)到堆栈上的代码。
// 这是“保护现场”。
gum_emit_prolog (aw);
// 2. 准备 C 函数的参数 (X1, X2, X3)
// 在 ARM64 C 语言调用约定中,参数通过 X0, X1, X2, X3... 传递。
// X0 稍后会设置(它来自 X17)。
// ADD X1, SP, ...CPU_CONTEXT
// 将“指向堆栈上 GumCpuContext(包含了所有寄存器值) 结构的指针”放入 X1(第二个参数)。
gum_arm64_writer_put_add_reg_reg_imm (aw, ARM64_REG_X1, ARM64_REG_SP, GUM_FRAME_OFFSET_CPU_CONTEXT);
// ADD X2, SP, ...CpuContext.lr
// 将“指向堆栈上 lr 寄存器保存位置的指针”放入 X2(第三个参数)。
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));
// ADD X3, SP, ...NEXT_HOP
// 将“指向堆栈上 next_hop 字段的指针”放入 X3(第四个参数)。
gum_arm64_writer_put_add_reg_reg_imm (aw, ARM64_REG_X3, ARM64_REG_SP,
GUM_FRAME_OFFSET_NEXT_HOP);
// 3. JIT 编译一个 C 函数调用
gum_arm64_writer_put_call_address_with_arguments (aw,
GUM_ADDRESS (_gum_function_context_begin_invocation), 4, // 目标C函数
// 这个 C 函数将接收 4 个参数:
// - Arg 1 (X0) = X17 (这是由 Dispatcher 提前设置好的 GumFunctionContext*,(包含了 `listener` 列表) )
GUM_ARG_REGISTER, ARM64_REG_X17,
// - Arg 2 (X1) = X1 (我们刚设置的 GumCpuContext*,(包含了所有寄存器值))
GUM_ARG_REGISTER, ARM64_REG_X1,
// - Arg 3 (X2) = X2 (我们刚设置的 saved_lr*)
GUM_ARG_REGISTER, ARM64_REG_X2,
// - Arg 4 (X3) = X3 (我们刚设置的 next_hop*)
GUM_ARG_REGISTER, ARM64_REG_X3);
// 4. 发出“函数终言”(Epilog)
// 这会 JIT 编译出从堆栈上恢复所有寄存器的代码,并执行 RET(返回)。
gum_emit_epilog (aw);
}static void
gum_emit_enter_thunk (GumArm64Writer * aw){ // 1. 发出“函数序言”(Prolog)
// 这会 JIT 编译出保存所有 CPU 寄存器(X0-X28, FP, LR)到堆栈上的代码。
// 这是“保护现场”。
gum_emit_prolog (aw);
// 2. 准备 C 函数的参数 (X1, X2, X3)
// 在 ARM64 C 语言调用约定中,参数通过 X0, X1, X2, X3... 传递。
// X0 稍后会设置(它来自 X17)。
// ADD X1, SP, ...CPU_CONTEXT
// 将“指向堆栈上 GumCpuContext(包含了所有寄存器值) 结构的指针”放入 X1(第二个参数)。
gum_arm64_writer_put_add_reg_reg_imm (aw, ARM64_REG_X1, ARM64_REG_SP, GUM_FRAME_OFFSET_CPU_CONTEXT);
// ADD X2, SP, ...CpuContext.lr
// 将“指向堆栈上 lr 寄存器保存位置的指针”放入 X2(第三个参数)。
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));
// ADD X3, SP, ...NEXT_HOP
// 将“指向堆栈上 next_hop 字段的指针”放入 X3(第四个参数)。
gum_arm64_writer_put_add_reg_reg_imm (aw, ARM64_REG_X3, ARM64_REG_SP,
GUM_FRAME_OFFSET_NEXT_HOP);
// 3. JIT 编译一个 C 函数调用
gum_arm64_writer_put_call_address_with_arguments (aw,
GUM_ADDRESS (_gum_function_context_begin_invocation), 4, // 目标C函数
// 这个 C 函数将接收 4 个参数:
// - Arg 1 (X0) = X17 (这是由 Dispatcher 提前设置好的 GumFunctionContext*,(包含了 `listener` 列表) )
GUM_ARG_REGISTER, ARM64_REG_X17,
// - Arg 2 (X1) = X1 (我们刚设置的 GumCpuContext*,(包含了所有寄存器值))
GUM_ARG_REGISTER, ARM64_REG_X1,
// - Arg 3 (X2) = X2 (我们刚设置的 saved_lr*)
GUM_ARG_REGISTER, ARM64_REG_X2,
// - Arg 4 (X3) = X3 (我们刚设置的 next_hop*)
GUM_ARG_REGISTER, ARM64_REG_X3);
// 4. 发出“函数终言”(Epilog)
// 这会 JIT 编译出从堆栈上恢复所有寄存器的代码,并执行 RET(返回)。
gum_emit_epilog (aw);
}; 步骤 C: 保存所有寄存器(包括 X17)到堆栈; (gum_emit_prolog); 步骤 D: 准备 C 函数的第 2、3、4 个参数 (X1, X2, X3)
ADD X1, SP, ... ; (GumCpuContext*)
ADD X2, SP, ... ; (saved_lr*)
ADD X3, SP, ... ; (next_hop*)
; 步骤 E: 准备 C 函数的第 1 个参数 (X0)
; 关键!把“中转”寄存器 X17 里的值,; “正式”移动到第一个参数寄存器 X0 中!MOV X0, X17; 步骤 F: 调用 C 语言大脑BL _gum_function_context_begin_invocation; 步骤 C: 保存所有寄存器(包括 X17)到堆栈; (gum_emit_prolog); 步骤 D: 准备 C 函数的第 2、3、4 个参数 (X1, X2, X3)
ADD X1, SP, ... ; (GumCpuContext*)
ADD X2, SP, ... ; (saved_lr*)
ADD X3, SP, ... ; (next_hop*)
; 步骤 E: 准备 C 函数的第 1 个参数 (X0)
; 关键!把“中转”寄存器 X17 里的值,; “正式”移动到第一个参数寄存器 X0 中!MOV X0, X17; 步骤 F: 调用 C 语言大脑BL _gum_function_context_begin_invocationstatic void
gum_emit_leave_thunk (GumArm64Writer * aw){ // 1. 发出“函数序言”(Prolog) - 保存所有寄存器
gum_emit_prolog (aw);
// 2. 准备 C 函数的参数 (X1, X2)
// ADD X1, SP, ...CPU_CONTEXT
// 将 GumCpuContext* 放入 X1(第二个参数)。
gum_arm64_writer_put_add_reg_reg_imm (aw, ARM64_REG_X1, ARM64_REG_SP,
GUM_FRAME_OFFSET_CPU_CONTEXT);
// ADD X2, SP, ...NEXT_HOP
// 将 next_hop* 放入 X2(第三个参数)。
gum_arm64_writer_put_add_reg_reg_imm (aw, ARM64_REG_X2, ARM64_REG_SP,
GUM_FRAME_OFFSET_NEXT_HOP);
// 3. JIT 编译一个 C 函数调用
gum_arm64_writer_put_call_address_with_arguments (aw,
GUM_ADDRESS (_gum_function_context_end_invocation), 3, // 目标C函数
// 这个 C 函数将接收 3 个参数:
// - Arg 1 (X0) = X17 (GumFunctionContext*)
GUM_ARG_REGISTER, ARM64_REG_X17,
// - Arg 2 (X1) = X1 (GumCpuContext*)
GUM_ARG_REGISTER, ARM64_REG_X1,
// - Arg 2 (X2) = X2 (next_hop*)
GUM_ARG_REGISTER, ARM64_REG_X2);
// 4. 发出“函数终言”(Epilog) - 恢复所有寄存器并 RET
gum_emit_epilog (aw);
}static void
gum_emit_leave_thunk (GumArm64Writer * aw){ // 1. 发出“函数序言”(Prolog) - 保存所有寄存器
gum_emit_prolog (aw);
// 2. 准备 C 函数的参数 (X1, X2)
// ADD X1, SP, ...CPU_CONTEXT
// 将 GumCpuContext* 放入 X1(第二个参数)。
gum_arm64_writer_put_add_reg_reg_imm (aw, ARM64_REG_X1, ARM64_REG_SP,
GUM_FRAME_OFFSET_CPU_CONTEXT);
// ADD X2, SP, ...NEXT_HOP
// 将 next_hop* 放入 X2(第三个参数)。
gum_arm64_writer_put_add_reg_reg_imm (aw, ARM64_REG_X2, ARM64_REG_SP,
GUM_FRAME_OFFSET_NEXT_HOP);
// 3. JIT 编译一个 C 函数调用
gum_arm64_writer_put_call_address_with_arguments (aw,
GUM_ADDRESS (_gum_function_context_end_invocation), 3, // 目标C函数
// 这个 C 函数将接收 3 个参数:
// - Arg 1 (X0) = X17 (GumFunctionContext*)
GUM_ARG_REGISTER, ARM64_REG_X17,
// - Arg 2 (X1) = X1 (GumCpuContext*)
GUM_ARG_REGISTER, ARM64_REG_X1,
// - Arg 2 (X2) = X2 (next_hop*)
GUM_ARG_REGISTER, ARM64_REG_X2);
// 4. 发出“函数终言”(Epilog) - 恢复所有寄存器并 RET
gum_emit_epilog (aw);
}static void
gum_emit_thunks (gpointer mem, // 'mem' 是那块新分配的 RW 内存页
GumInterceptorBackend * self)
{ GumArm64Writer * aw = &self->writer; // 拿起“汇编笔”
// 1. 记录 enter_thunk 的起始地址
self->enter_thunk = self->thunks;
// 2. 将“汇编笔”重置到这块内存的开头
gum_arm64_writer_reset (aw, mem);
aw->pc = GUM_ADDRESS (self->enter_thunk); // 告知“笔”当前的绝对地址
// 3. 在内存中 JIT 编译出 enter_thunk 的所有汇编代码
gum_emit_enter_thunk (aw);
gum_arm64_writer_flush (aw); // 提交写入
// 4. 记录 leave_thunk 的起始地址
// 它紧跟在 enter_thunk 的末尾
self->leave_thunk = (guint8 *) self->enter_thunk + gum_arm64_writer_offset (aw);
// 5. 在内存中 JIT 编译出 leave_thunk 的所有汇编代码
gum_emit_leave_thunk (aw);
gum_arm64_writer_flush (aw); // 提交写入
}static void
gum_emit_thunks (gpointer mem, // 'mem' 是那块新分配的 RW 内存页
GumInterceptorBackend * self)
{ GumArm64Writer * aw = &self->writer; // 拿起“汇编笔”
// 1. 记录 enter_thunk 的起始地址
self->enter_thunk = self->thunks;
// 2. 将“汇编笔”重置到这块内存的开头
gum_arm64_writer_reset (aw, mem);
aw->pc = GUM_ADDRESS (self->enter_thunk); // 告知“笔”当前的绝对地址
// 3. 在内存中 JIT 编译出 enter_thunk 的所有汇编代码
gum_emit_enter_thunk (aw);
gum_arm64_writer_flush (aw); // 提交写入
// 4. 记录 leave_thunk 的起始地址
// 它紧跟在 enter_thunk 的末尾
self->leave_thunk = (guint8 *) self->enter_thunk + gum_arm64_writer_offset (aw);
// 5. 在内存中 JIT 编译出 leave_thunk 的所有汇编代码
gum_emit_leave_thunk (aw);
gum_arm64_writer_flush (aw); // 提交写入
}static void
gum_emit_thunks (gpointer mem, // mem 是一块刚申请的“空白内存页”
GumInterceptorBackend * self)
{ GumArm64Writer * aw = &self->writer; // 拿到“汇编笔”
/* --- 步骤 1: 写入“前面”的 enter_thunk --- */
// 1a. 把“笔尖”复位 (reset) 到“空白内存页”(mem) 的最开头
gum_arm64_writer_reset (aw, mem);
// 1b. 告诉“笔”它现在的绝对地址(用于计算跳转)
aw->pc = GUM_ADDRESS (self->enter_thunk);
// 1c. 开始 JIT 编译!
// “笔”开始在内存上写入 enter_thunk 的所有汇编指令
// (比如写了 100 字节)
gum_emit_enter_thunk (aw);
// 1d. “笔”停在了 enter_thunk 的末尾(即 100 字节处)
gum_arm64_writer_flush (aw);
/* --- 步骤 2: 写入“后面”的 leave_thunk --- */
// 2a. 记录 leave_thunk 的起始地址:
// gum_arm64_writer_offset(aw) 会返回“笔”现在的位置(即 100)
// 所以 leave_thunk 的地址 = 内存页起始地址 + 100
self->leave_thunk =
(guint8 *) self->enter_thunk + gum_arm64_writer_offset (aw);
// 2b. 开始 JIT 编译!
// “笔”从它当前的位置(100 字节处)继续写入 leave_thunk 的指令
gum_emit_leave_thunk (aw);
// 2c. “笔”停在了 leave_thunk 的末尾
gum_arm64_writer_flush (aw);
}static void
gum_emit_thunks (gpointer mem, // mem 是一块刚申请的“空白内存页”
GumInterceptorBackend * self)
{ GumArm64Writer * aw = &self->writer; // 拿到“汇编笔”
/* --- 步骤 1: 写入“前面”的 enter_thunk --- */
// 1a. 把“笔尖”复位 (reset) 到“空白内存页”(mem) 的最开头
gum_arm64_writer_reset (aw, mem);
// 1b. 告诉“笔”它现在的绝对地址(用于计算跳转)
aw->pc = GUM_ADDRESS (self->enter_thunk);
// 1c. 开始 JIT 编译!
// “笔”开始在内存上写入 enter_thunk 的所有汇编指令
// (比如写了 100 字节)
gum_emit_enter_thunk (aw);
// 1d. “笔”停在了 enter_thunk 的末尾(即 100 字节处)
gum_arm64_writer_flush (aw);
/* --- 步骤 2: 写入“后面”的 leave_thunk --- */
// 2a. 记录 leave_thunk 的起始地址:
// gum_arm64_writer_offset(aw) 会返回“笔”现在的位置(即 100)
// 所以 leave_thunk 的地址 = 内存页起始地址 + 100
self->leave_thunk =
(guint8 *) self->enter_thunk + gum_arm64_writer_offset (aw);
// 2b. 开始 JIT 编译!
// “笔”从它当前的位置(100 字节处)继续写入 leave_thunk 的指令
gum_emit_leave_thunk (aw);
// 2c. “笔”停在了 leave_thunk 的末尾
gum_arm64_writer_flush (aw);
}static void
gum_interceptor_backend_create_thunks (GumInterceptorBackend * self){ gsize page_size, code_size;
GumMemoryRange range;
page_size = gum_query_page_size ();
code_size = page_size;
// 1. 申请一页 RW(读写)内存
self->thunks = gum_memory_allocate (NULL, code_size, page_size, GUM_PAGE_RW);
// 2. “隐藏” (Cloak) 这块内存
// 这是为了防止 Frida 自己的 Stalker 等工具来跟踪 Frida 内部的
// thunk 代码,避免无限递归。
range.base_address = GUM_ADDRESS (self->thunks);
range.size = code_size;
gum_cloak_add_range (&range); //隐藏这块内存
// 3. 调用 gum_emit_thunks(作为回调),
// 让它在这块内存里 JIT 编译出 enter 和 leave thunks。
// (gum_memory_patch_code 稍后会负责将这块内存变为 RX(读+执行))
gum_memory_patch_code (self->thunks, 1024, (GumMemoryPatchApplyFunc) gum_emit_thunks, self);
}static void
gum_interceptor_backend_create_thunks (GumInterceptorBackend * self){ gsize page_size, code_size;
GumMemoryRange range;
page_size = gum_query_page_size ();
code_size = page_size;
// 1. 申请一页 RW(读写)内存
self->thunks = gum_memory_allocate (NULL, code_size, page_size, GUM_PAGE_RW);
// 2. “隐藏” (Cloak) 这块内存
// 这是为了防止 Frida 自己的 Stalker 等工具来跟踪 Frida 内部的
// thunk 代码,避免无限递归。
range.base_address = GUM_ADDRESS (self->thunks);
range.size = code_size;
gum_cloak_add_range (&range); //隐藏这块内存
// 3. 调用 gum_emit_thunks(作为回调),
// 让它在这块内存里 JIT 编译出 enter 和 leave thunks。
// (gum_memory_patch_code 稍后会负责将这块内存变为 RX(读+执行))
gum_memory_patch_code (self->thunks, 1024, (GumMemoryPatchApplyFunc) gum_emit_thunks, self);
}<span style="color:red; font-size:26px;">我们先看</span> <span style="color:blue; font-size:26px;">第1部分`gum_emit_enter_thunk`(我们上一步分析的)是一个汇编“桥梁”, </span><span style="color:red; font-size:26px;">它**调用**的就是这个 `_gum_function_context_begin_invocation` C 函数。</span>
<span style="color:red; font-size:26px;">我们先看</span> <span style="color:blue; font-size:26px;">第1部分`gum_emit_enter_thunk`(我们上一步分析的)是一个汇编“桥梁”, </span><span style="color:red; font-size:26px;">它**调用**的就是这个 `_gum_function_context_begin_invocation` C 函数。</span>
gboolean _gum_function_context_begin_invocation (GumFunctionContext * function_ctx, GumCpuContext * cpu_context,
gpointer * caller_ret_addr,
gpointer * next_hop)
gboolean _gum_function_context_begin_invocation (GumFunctionContext * function_ctx, GumCpuContext * cpu_context,
gpointer * caller_ret_addr,
gpointer * next_hop)
Interceptor.attach(ptr("write"), {
onEnter: function(args) {
// 你的目的是:打印所有被写入的内容
console.log("WRITING: " + args[1].readCString());
}
});Interceptor.attach(ptr("write"), {
onEnter: function(args) {
// 你的目的是:打印所有被写入的内容
console.log("WRITING: " + args[1].readCString());
}
});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);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);stack_entry = gum_invocation_stack_peek_top (stack);if (stack_entry != NULL &&
stack_entry->calling_replacement &&
/* ... 检查函数地址是否匹配 ... */)
{ gum_tls_key_set_value (gum_interceptor_guard_key, NULL); // 清除守卫
*next_hop = function_ctx->on_invoke_trampoline;
goto bypass;
}stack_entry = gum_invocation_stack_peek_top (stack);if (stack_entry != NULL &&
stack_entry->calling_replacement &&
/* ... 检查函数地址是否匹配 ... */)
{ gum_tls_key_set_value (gum_interceptor_guard_key, NULL); // 清除守卫
*next_hop = function_ctx->on_invoke_trampoline;
goto bypass;
}#ifndef HAVE_WINDOWS system_error = gum_thread_get_system_error ();
#endif#ifndef HAVE_WINDOWS system_error = gum_thread_get_system_error ();
#endifif (interceptor->selected_thread_id != 0)
{ invoke_listeners =
gum_process_get_current_thread_id () == interceptor->selected_thread_id;
}if (invoke_listeners)
{ invoke_listeners = (interceptor_ctx->ignore_level <= 0);
}if (!invoke_listeners && function_ctx->has_unignorable_listener)
{ invoke_listeners = TRUE;
only_invoke_unignorable_listeners = TRUE;
}if (interceptor->selected_thread_id != 0)
{ invoke_listeners =
gum_process_get_current_thread_id () == interceptor->selected_thread_id;
}if (invoke_listeners)
{ invoke_listeners = (interceptor_ctx->ignore_level <= 0);
}if (!invoke_listeners && function_ctx->has_unignorable_listener)
{ invoke_listeners = TRUE;
only_invoke_unignorable_listeners = TRUE;
}will_trap_on_leave = function_ctx->replacement_function != NULL || (invoke_listeners && function_ctx->has_on_leave_listener);//Frida 是否需要在目标函数执行完毕、即将返回时,再次‘劫持’控制权?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)
{ /* ... 还是 push,但存入的是假返回地址 ... */
invocation_ctx = &stack_entry->invocation_context;
}will_trap_on_leave = function_ctx->replacement_function != NULL || (invoke_listeners && function_ctx->has_on_leave_listener);//Frida 是否需要在目标函数执行完毕、即将返回时,再次‘劫持’控制权?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)
{ /* ... 还是 push,但存入的是假返回地址 ... */
invocation_ctx = &stack_entry->invocation_context;
}gum_function_context_fixup_cpu_context (function_ctx, cpu_context);if (invoke_listeners)
{ /* ... */
invocation_ctx->cpu_context = cpu_context;
/* ... */
listener_entries =
(GPtrArray *) g_atomic_pointer_get (&function_ctx->listener_entries);
for (i = 0; i != listener_entries->len; i++)
{
/* ... 过滤 unignorable ... */
if (listener_entry->listener_interface->on_enter != NULL)
{
listener_entry->listener_interface->on_enter (
listener_entry->listener_instance, invocation_ctx);
}
}
}gum_function_context_fixup_cpu_context (function_ctx, cpu_context);if (invoke_listeners)
{ /* ... */
invocation_ctx->cpu_context = cpu_context;
/* ... */
listener_entries =
(GPtrArray *) g_atomic_pointer_get (&function_ctx->listener_entries);
for (i = 0; i != listener_entries->len; i++)
{
/* ... 过滤 unignorable ... */
if (listener_entry->listener_interface->on_enter != NULL)
{
listener_entry->listener_interface->on_enter (
listener_entry->listener_instance, invocation_ctx);
}
}
}// 我们在“测试代码”里,把 arm64_listener_context_on_enter 这个 C 函数注册为了 on_enter 的回调。ctx->listener->on_enter = (TestCallbackListenerFunc) arm64_listener_context_on_enter;// 我们在“测试代码”里,把 arm64_listener_context_on_enter 这个 C 函数注册为了 on_enter 的回调。ctx->listener->on_enter = (TestCallbackListenerFunc) arm64_listener_context_on_enter;if (!will_trap_on_leave && invoke_listeners)
{ gum_invocation_stack_pop (interceptor_ctx->stack);
}gum_tls_key_set_value (gum_interceptor_guard_key, NULL);if (will_trap_on_leave)
{ *caller_ret_addr = function_ctx->on_leave_trampoline;
}if (function_ctx->replacement_function != NULL) //使用了frida的 `Interceptor.replace`了
{ /* ... */
*next_hop = function_ctx->replacement_function;//设置“下一跳”为 `Interceptor.replace` 的**替换函数**
}else{ *next_hop = function_ctx->on_invoke_trampoline;//设置“下一跳”为**原始函数**
}if (!will_trap_on_leave && invoke_listeners)
{ gum_invocation_stack_pop (interceptor_ctx->stack);
}gum_tls_key_set_value (gum_interceptor_guard_key, NULL);if (will_trap_on_leave)
{ *caller_ret_addr = function_ctx->on_leave_trampoline;
}if (function_ctx->replacement_function != NULL) //使用了frida的 `Interceptor.replace`了
{ /* ... */
*next_hop = function_ctx->replacement_function;//设置“下一跳”为 `Interceptor.replace` 的**替换函数**
}else{ *next_hop = function_ctx->on_invoke_trampoline;//设置“下一跳”为**原始函数**
}void _gum_function_context_end_invocation (GumFunctionContext * function_ctx,
GumCpuContext * cpu_context,
gpointer * next_hop)
void _gum_function_context_end_invocation (GumFunctionContext * function_ctx,
GumCpuContext * cpu_context,
gpointer * next_hop)
#ifdef HAVE_WINDOWS system_error = gum_thread_get_system_error ();
#endif gum_tls_key_set_value (gum_interceptor_guard_key, function_ctx->interceptor);
#ifndef HAVE_WINDOWS system_error = gum_thread_get_system_error ();
#endif#ifdef HAVE_WINDOWS system_error = gum_thread_get_system_error ();
#endif gum_tls_key_set_value (gum_interceptor_guard_key, function_ctx->interceptor);
#ifndef HAVE_WINDOWS system_error = gum_thread_get_system_error ();
#endifinterceptor_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);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);invocation_ctx = &stack_entry->invocation_context;invocation_ctx->cpu_context = cpu_context;/* ... errno 相关的复杂处理 ... */invocation_ctx->backend = &interceptor_ctx->listener_backend;invocation_ctx = &stack_entry->invocation_context;invocation_ctx->cpu_context = cpu_context;/* ... errno 相关的复杂处理 ... */invocation_ctx->backend = &interceptor_ctx->listener_backend;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++)
{ /* ... 过滤 unignorable(不可忽视的) ... */
if (listener_entry->listener_interface->on_leave != NULL)
{
listener_entry->listener_interface->on_leave (
listener_entry->listener_instance, invocation_ctx);
}
}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++)
{ /* ... 过滤 unignorable(不可忽视的) ... */
if (listener_entry->listener_interface->on_leave != NULL)
{
listener_entry->listener_interface->on_leave (
listener_entry->listener_instance, invocation_ctx);
}
} gum_thread_set_system_error (invocation_ctx->system_error);
gum_invocation_stack_pop (interceptor_ctx->stack);
gum_tls_key_set_value (gum_interceptor_guard_key, NULL);
g_atomic_int_dec_and_test (&function_ctx->trampoline_usage_counter);
} gum_thread_set_system_error (invocation_ctx->system_error);
gum_invocation_stack_pop (interceptor_ctx->stack);
gum_tls_key_set_value (gum_interceptor_guard_key, NULL);
g_atomic_int_dec_and_test (&function_ctx->trampoline_usage_counter);
}gboolean _gum_interceptor_backend_create_trampoline (GumInterceptorBackend * self, GumFunctionContext * ctx)
{ GumArm64Writer * aw = &self->writer;
GumArm64Relocator * ar = &self->relocator;
gpointer function_address = ctx->function_address;
// ...
gboolean _gum_interceptor_backend_create_trampoline (GumInterceptorBackend * self, GumFunctionContext * ctx)
{ GumArm64Writer * aw = &self->writer;
GumArm64Relocator * ar = &self->relocator;
gpointer function_address = ctx->function_address;
// ...
if (!gum_interceptor_backend_prepare_trampoline (self, ctx, &need_deflector)) //预分析步骤
return FALSE;
//把“汇编笔” (`aw`) 放在“施工地”的开头,准备 JIT(即时编译)。gum_arm64_writer_reset (aw, ctx->trampoline_slice->data);if (ctx->type == GUM_INTERCEPTOR_TYPE_FAST)
{ deflector_target = ctx->replacement_function;
}else{ //JIT 编译出的 `Dispatcher`(总调度中心)的入口地址
//gum_arm64_writer_cur (aw) 在这里返回的就是这块新内存(“施工地”)的“起始地址”
ctx->on_enter_trampoline = gum_sign_code_pointer (gum_arm64_writer_cur (aw));
deflector_target = ctx->on_enter_trampoline;
}if (need_deflector)
{ // ... (处理 B 指令跳转距离太远的情况) ...
}if (!gum_interceptor_backend_prepare_trampoline (self, ctx, &need_deflector)) //预分析步骤
return FALSE;
//把“汇编笔” (`aw`) 放在“施工地”的开头,准备 JIT(即时编译)。gum_arm64_writer_reset (aw, ctx->trampoline_slice->data);if (ctx->type == GUM_INTERCEPTOR_TYPE_FAST)
{ deflector_target = ctx->replacement_function;
}else{ //JIT 编译出的 `Dispatcher`(总调度中心)的入口地址
//gum_arm64_writer_cur (aw) 在这里返回的就是这块新内存(“施工地”)的“起始地址”
ctx->on_enter_trampoline = gum_sign_code_pointer (gum_arm64_writer_cur (aw));
deflector_target = ctx->on_enter_trampoline;
}if (need_deflector)
{ // ... (处理 B 指令跳转距离太远的情况) ...
}if (ctx->type != GUM_INTERCEPTOR_TYPE_FAST)
{ // JIT 编译“进入 onEnter 逻辑”的部分
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);
// JIT 编译“进入 onLeave 逻辑”的部分
ctx->on_leave_trampoline = gum_arm64_writer_cur (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);
}if (ctx->type != GUM_INTERCEPTOR_TYPE_FAST)
{ // JIT 编译“进入 onEnter 逻辑”的部分
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);
// JIT 编译“进入 onLeave 逻辑”的部分
ctx->on_leave_trampoline = gum_arm64_writer_cur (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);
}//回顾之前分析的代码——第 1 部分(最底层):`gum_emit_enter_thunk` 如下:static void
gum_emit_enter_thunk (GumArm64Writer * aw){ // 1. 发出“函数序言”(Prolog)
// 这会 JIT 编译出保存所有 CPU 寄存器(X0-X28, FP, LR)到堆栈上的代码。
// 这是“保护现场”。
gum_emit_prolog (aw);
// 2. 准备 C 函数的参数 (X1, X2, X3)
// 在 ARM64 C 语言调用约定中,参数通过 X0, X1, X2, X3... 传递。
// X0 稍后会设置(它来自 X17)。
// ADD X1, SP, ...CPU_CONTEXT
// 将“指向堆栈上 GumCpuContext(包含了所有寄存器值) 结构的指针”放入 X1(第二个参数)。
gum_arm64_writer_put_add_reg_reg_imm (aw, ARM64_REG_X1, ARM64_REG_SP, GUM_FRAME_OFFSET_CPU_CONTEXT);
// ADD X2, SP, ...CpuContext.lr
// 将“指向堆栈上 lr 寄存器保存位置的指针”放入 X2(第三个参数)。
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));
// ADD X3, SP, ...NEXT_HOP
// 将“指向堆栈上 next_hop 字段的指针”放入 X3(第四个参数)。
gum_arm64_writer_put_add_reg_reg_imm (aw, ARM64_REG_X3, ARM64_REG_SP,
GUM_FRAME_OFFSET_NEXT_HOP);
// 3. JIT 编译一个 C 函数调用
gum_arm64_writer_put_call_address_with_arguments (aw,
GUM_ADDRESS (_gum_function_context_begin_invocation), 4, // 目标C函数
// 这个 C 函数将接收 4 个参数:
// - Arg 1 (X0) = X17 (这是由 Dispatcher 提前设置好的 GumFunctionContext*,(包含了 `listener` 列表) )
GUM_ARG_REGISTER, ARM64_REG_X17,
// - Arg 2 (X1) = X1 (我们刚设置的 GumCpuContext*,(包含了所有寄存器值))
GUM_ARG_REGISTER, ARM64_REG_X1,
// - Arg 3 (X2) = X2 (我们刚设置的 saved_lr*)
GUM_ARG_REGISTER, ARM64_REG_X2,
// - Arg 4 (X3) = X3 (我们刚设置的 next_hop*)
GUM_ARG_REGISTER, ARM64_REG_X3);
// 4. 发出“函数终言”(Epilog)
// 这会 JIT 编译出从堆栈上恢复所有寄存器的代码,并执行 RET(返回)。
gum_emit_epilog (aw);
}//回顾之前分析的代码——第 1 部分(最底层):`gum_emit_enter_thunk` 如下:static void
gum_emit_enter_thunk (GumArm64Writer * aw){ // 1. 发出“函数序言”(Prolog)
// 这会 JIT 编译出保存所有 CPU 寄存器(X0-X28, FP, LR)到堆栈上的代码。
// 这是“保护现场”。
gum_emit_prolog (aw);
// 2. 准备 C 函数的参数 (X1, X2, X3)
// 在 ARM64 C 语言调用约定中,参数通过 X0, X1, X2, X3... 传递。
// X0 稍后会设置(它来自 X17)。
// ADD X1, SP, ...CPU_CONTEXT
// 将“指向堆栈上 GumCpuContext(包含了所有寄存器值) 结构的指针”放入 X1(第二个参数)。
gum_arm64_writer_put_add_reg_reg_imm (aw, ARM64_REG_X1, ARM64_REG_SP, GUM_FRAME_OFFSET_CPU_CONTEXT);
// ADD X2, SP, ...CpuContext.lr
// 将“指向堆栈上 lr 寄存器保存位置的指针”放入 X2(第三个参数)。
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));
// ADD X3, SP, ...NEXT_HOP
// 将“指向堆栈上 next_hop 字段的指针”放入 X3(第四个参数)。
gum_arm64_writer_put_add_reg_reg_imm (aw, ARM64_REG_X3, ARM64_REG_SP,
GUM_FRAME_OFFSET_NEXT_HOP);
// 3. JIT 编译一个 C 函数调用
gum_arm64_writer_put_call_address_with_arguments (aw,
GUM_ADDRESS (_gum_function_context_begin_invocation), 4, // 目标C函数
// 这个 C 函数将接收 4 个参数:
// - Arg 1 (X0) = X17 (这是由 Dispatcher 提前设置好的 GumFunctionContext*,(包含了 `listener` 列表) )
GUM_ARG_REGISTER, ARM64_REG_X17,
// - Arg 2 (X1) = X1 (我们刚设置的 GumCpuContext*,(包含了所有寄存器值))
GUM_ARG_REGISTER, ARM64_REG_X1,
// - Arg 3 (X2) = X2 (我们刚设置的 saved_lr*)
GUM_ARG_REGISTER, ARM64_REG_X2,
// - Arg 4 (X3) = X3 (我们刚设置的 next_hop*)
GUM_ARG_REGISTER, ARM64_REG_X3);
// 4. 发出“函数终言”(Epilog)
// 这会 JIT 编译出从堆栈上恢复所有寄存器的代码,并执行 RET(返回)。
gum_emit_epilog (aw);
}//回顾之前分析的代码——第 2 部分(最底层):`gum_emit_leave_thunk`如下:static void
gum_emit_leave_thunk (GumArm64Writer * aw){ // 1. 发出“函数序言”(Prolog) - 保存所有寄存器
gum_emit_prolog (aw);
// 2. 准备 C 函数的参数 (X1, X2)
// ADD X1, SP, ...CPU_CONTEXT
// 将 GumCpuContext* 放入 X1(第二个参数)。
gum_arm64_writer_put_add_reg_reg_imm (aw, ARM64_REG_X1, ARM64_REG_SP,
GUM_FRAME_OFFSET_CPU_CONTEXT);
// ADD X2, SP, ...NEXT_HOP
// 将 next_hop* 放入 X2(第三个参数)。
gum_arm64_writer_put_add_reg_reg_imm (aw, ARM64_REG_X2, ARM64_REG_SP,
GUM_FRAME_OFFSET_NEXT_HOP);
// 3. JIT 编译一个 C 函数调用
gum_arm64_writer_put_call_address_with_arguments (aw,
GUM_ADDRESS (_gum_function_context_end_invocation), 3, // 目标C函数
// 这个 C 函数将接收 3 个参数:
// - Arg 1 (X0) = X17 (GumFunctionContext*)
GUM_ARG_REGISTER, ARM64_REG_X17,
// - Arg 2 (X1) = X1 (GumCpuContext*)
GUM_ARG_REGISTER, ARM64_REG_X1,
// - Arg 2 (X2) = X2 (next_hop*)
GUM_ARG_REGISTER, ARM64_REG_X2);
// 4. 发出“函数终言”(Epilog) - 恢复所有寄存器并 RET
gum_emit_epilog (aw);
}//回顾之前分析的代码——第 2 部分(最底层):`gum_emit_leave_thunk`如下:static void
gum_emit_leave_thunk (GumArm64Writer * aw){ // 1. 发出“函数序言”(Prolog) - 保存所有寄存器
gum_emit_prolog (aw);
// 2. 准备 C 函数的参数 (X1, X2)
// ADD X1, SP, ...CPU_CONTEXT
// 将 GumCpuContext* 放入 X1(第二个参数)。
gum_arm64_writer_put_add_reg_reg_imm (aw, ARM64_REG_X1, ARM64_REG_SP,
GUM_FRAME_OFFSET_CPU_CONTEXT);
// ADD X2, SP, ...NEXT_HOP
// 将 next_hop* 放入 X2(第三个参数)。
gum_arm64_writer_put_add_reg_reg_imm (aw, ARM64_REG_X2, ARM64_REG_SP,
GUM_FRAME_OFFSET_NEXT_HOP);
// 3. JIT 编译一个 C 函数调用
gum_arm64_writer_put_call_address_with_arguments (aw,
GUM_ADDRESS (_gum_function_context_end_invocation), 3, // 目标C函数
// 这个 C 函数将接收 3 个参数:
// - Arg 1 (X0) = X17 (GumFunctionContext*)
GUM_ARG_REGISTER, ARM64_REG_X17,
// - Arg 2 (X1) = X1 (GumCpuContext*)
GUM_ARG_REGISTER, ARM64_REG_X1,
// - Arg 2 (X2) = X2 (next_hop*)
GUM_ARG_REGISTER, ARM64_REG_X2);
// 4. 发出“函数终言”(Epilog) - 恢复所有寄存器并 RET
gum_emit_epilog (aw);
}ctx->on_invoke_trampoline = gum_sign_code_pointer (gum_arm64_writer_cur (aw));//开始 **读取** `function_address`(原函数)的指令,并把 **搬运/修复** 后的指令,用“汇编笔” (`aw`) JIT 到我们 **当前的位置**。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);//读取一条指令
// ...
g_string_append (signature, insn->mnemonic);
}while (reloc_bytes < data->redirect_code_size);
ctx->on_invoke_trampoline = gum_sign_code_pointer (gum_arm64_writer_cur (aw));//开始 **读取** `function_address`(原函数)的指令,并把 **搬运/修复** 后的指令,用“汇编笔” (`aw`) JIT 到我们 **当前的位置**。gum_arm64_relocator_reset (ar, function_address, aw);signature = g_string_sized_new (16);[培训]科锐软件逆向54期预科班、正式班开始火爆招生报名啦!!!