Frida17.12.0的功能与变化

admin 2026-06-15 04:57:50 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: Frida17.12.0主要更新包括新增函数范围定位接口Process.findFunctionRange()和控制流图分析模块ControlFlowGraph,提供基本块支配关系查询与代码形状分析能力;Linux注入器优化触发点采样策略并改进agent装载路径,提升attach稳定性;Interceptor增加细粒度刷新接口guminterceptorflush_function()。 综合评分: 87 文章分类: 安全工具,二进制安全,逆向分析,安全开发


cover_image

Frida17.12.0的功能与变化

原创

非虫 非虫

软件安全与逆向分析

2026年6月12日 16:14 湖北

在小说阅读器读本章

去阅读

Frida17.12.0发布于2026年6月10日。官方发布说明列出的更新集中在四个方向:Gum/GumJS新增代码形状分析接口,Linux注入器调整触发点选择和agent装载路径,Interceptor增加细粒度刷新接口,Windows arm64相关的Stalker和Exceptor修复。

更新范围

17.12.0对应的主要提交如下。

| 子仓库 | 提交 | 更新内容 | | — | — | — | | frida-gum | 67a1b9c | 新增gum_process_find_function_range() | | frida-gum | 5e2fd31 | 新增GumControlFlowGraph模块 | | frida-gum | b3273ea | 在GumJS暴露Process.findFunctionRange()ControlFlowGraph | | frida-gum | f04c3e3 | 调整控制流图公开命名,例如findBlockContaining()capacity | | frida-gum | 779f9b0 | 新增gum_interceptor_flush_function()gum_interceptor_flush_listener() | | frida-core | a0a8838 | eBPF采样不可用时回退到/proc/<pid>/task/<tid>/syscall | | frida-core | 33e5879 | 按实际bootstrap stub大小筛选触发函数 | | frida-core | 880f7ac | 采样线程栈,寻找可容纳注入stub的libc调用点 | | frida-core | f0cc3b4 | 安装和恢复触发点时使用对称写回流程 | | frida-core | f51327f | agent通过/proc/<pid>/fd/<fd>路径装载 | | frida-core | 21dc508 | 捕获libbpf诊断日志并写入错误信息 |

这些提交覆盖了两个层面。frida-gum侧增加公开API和GumJS绑定,主要供脚本和嵌入式使用;frida-core侧调整Linux注入实现,主要影响frida-server、attach和注入稳定性。

函数范围定位

frida-gum提交67a1b9c新增gum_process_find_function_range(),用于根据函数内部地址查找该地址所在函数的连续代码范围。

头文件中的声明如下:

GUM_API gboolean&nbsp;gum_process_find_function_range(gconstpointer address,
&nbsp; &nbsp; GumMemoryRange * range);

该接口不要求目标二进制保留符号。实现侧会优先读取平台的unwind信息:POSIX平台通过_Unwind_Find_FDE并解析FDE编码,Windows平台通过RtlLookupFunctionEntry查询运行时函数表。若unwind信息不可用,则退回到符号边界查询。

GumJS侧对应接口是Process.findFunctionRange()。它接收一个NativePointer,返回{ base, size }形式的内存范围;如果无法定位函数范围,则返回null

官方测试中的调用方式如下:

const&nbsp;f = ptr.strip();
const&nbsp;range =&nbsp;Process.findFunctionRange(f);
send(range !==&nbsp;null);
send(range.base.compare(f) <=&nbsp;0&nbsp;&&
&nbsp; &nbsp; f.compare(range.base.add(range.size)) <&nbsp;0);

在实际脚本中,ptr可以替换为某个导出函数地址、符号地址,或者任意落在目标函数内部的指令地址。例如:

const&nbsp;openPtr =&nbsp;Module.getGlobalExportByName('open');
const&nbsp;range =&nbsp;Process.findFunctionRange(openPtr);

if&nbsp;(range !==&nbsp;null) {
console.log('base='&nbsp;+ range.base&nbsp;+&nbsp;', size='&nbsp;+ range.size);
}

该接口可用于确定函数边界、检查某个Hook点是否位于同一函数内,或者在指令重定位前获取可分析范围。

控制流图接口

frida-gum提交5e2fd31新增GumControlFlowGraph。该模块基于Capstone反汇编函数代码,构建基本块、前驱、后继和支配关系。提交说明中提到,它使用resolver定位函数范围,并通过直接分支发现多段函数,例如热路径和冷路径分离的函数片段。

公开头文件中的主要接口如下:

typedefgboolean(* GumControlFlowGraphFindRangeFunc)(gconstpointer address,
&nbsp; &nbsp; GumMemoryRange * range, gpointer user_data);

typedefgboolean(* GumFoundDominatingSiteFunc)(gconstpointer site,
&nbsp; &nbsp; gsize capacity, gpointer user_data);

GUM_API GumControlFlowGraph *&nbsp;gum_control_flow_graph_new(gconstpointer entry,
&nbsp; &nbsp; cs_arch arch, cs_mode mode, GumControlFlowGraphFindRangeFunc find_range,
&nbsp; &nbsp; gpointer user_data);
GUM_API GumControlFlowGraph *&nbsp;gum_control_flow_graph_new_for_function(
&nbsp; &nbsp; gconstpointer entry_point);
GUM_API gboolean&nbsp;gum_control_flow_graph_dominates(GumControlFlowGraph * self,
&nbsp; &nbsp; gconstpointer a, gconstpointer b);
GUM_API&nbsp;voidgum_control_flow_graph_enumerate_dominating_sites(
&nbsp; &nbsp; GumControlFlowGraph * self, gconstpointer target,
&nbsp; &nbsp; GumFoundDominatingSiteFunc func, gpointer user_data);

其中gum_control_flow_graph_dominates()用于判断地址a是否支配地址bgum_control_flow_graph_enumerate_dominating_sites()会枚举支配目标地址的可写入位置,并给出capacity,表示从该位置开始、不会被其它控制流边落入的连续字节数。

测试用例中构造了一个类似start_thread的短函数,用来验证支配关系和可写入位置。

TESTCASE (single_range_dominators_and_sites)
{
staticconst&nbsp;guint8 code[] = {
0x48,&nbsp;0x85,&nbsp;0xc0,&nbsp;0x74,&nbsp;0x04,&nbsp;0xff,&nbsp;0xd0,&nbsp;0xeb,&nbsp;0x03,&nbsp;0xff,&nbsp;0xd0,&nbsp;0x90,
0xc3,
&nbsp; };
&nbsp; GumTestCode layout = { code,&nbsp;sizeof&nbsp;(code),&nbsp;NULL,&nbsp;0&nbsp;};
&nbsp; GumControlFlowGraph * cfg;
&nbsp; GumCollectedSites sites = { {&nbsp;NULL, }, };

&nbsp; cs_arch_register_x86 ();

&nbsp; cfg = gum_control_flow_graph_new (code, CS_ARCH_X86, CS_MODE_64,
&nbsp; &nbsp; &nbsp; gum_test_find_range, &layout);

&nbsp; g_assert_true (
&nbsp; &nbsp; &nbsp; gum_control_flow_graph_dominates (cfg, code +&nbsp;0x00, code +&nbsp;0x09));
&nbsp; g_assert_true (
&nbsp; &nbsp; &nbsp; gum_control_flow_graph_dominates (cfg, code +&nbsp;0x03, code +&nbsp;0x09));
&nbsp; g_assert_false (
&nbsp; &nbsp; &nbsp; gum_control_flow_graph_dominates (cfg, code +&nbsp;0x05, code +&nbsp;0x09));

&nbsp; gum_control_flow_graph_enumerate_dominating_sites (cfg, code +&nbsp;0x09,
&nbsp; &nbsp; &nbsp; gum_test_collect_site, &sites);
}

这段测试说明了接口的判定语义:只有位于所有到达目标路径上的指令才算支配目标;被条件分支绕过的指令不会被当作支配点。

GumJS控制流图用法

frida-gum提交b3273ea把控制流图接口暴露到GumJS。17.12.0最终公开的JS对象包括ControlFlowGraphBasicBlock

官方测试中的调用方式如下:

const&nbsp;cfg =&nbsp;newControlFlowGraph(ptr);
const&nbsp;entry = cfg.entryBlock;

send(entry !==&nbsp;null);
send(cfg.blocks.length&nbsp;>&nbsp;0);
send(cfg.findBlockContaining(entry.start).start.equals(entry.start));
send(cfg.dominates(entry.start, entry.start));

const&nbsp;insns = entry.instructions;
send(insns.length&nbsp;>&nbsp;0);
send(insns[0].address.equals(entry.start));
send(Array.isArray(entry.successors));
send(Array.isArray(entry.predecessors));

const&nbsp;sites = cfg.enumerateDominatingSites(entry.start);
send(sites.length&nbsp;>&nbsp;0);
send(sites[0].address.equals(entry.start));
send(typeof&nbsp;sites[0].capacity&nbsp;===&nbsp;'number');

实际使用时,可以先取一个函数地址,再构建控制流图:

const&nbsp;target =&nbsp;Module.getGlobalExportByName('pthread_create');
const&nbsp;cfg =&nbsp;newControlFlowGraph(target);

console.log('block count='&nbsp;+ cfg.blocks.length);

for&nbsp;(const&nbsp;site&nbsp;of&nbsp;cfg.enumerateDominatingSites(target)) {
console.log('site='&nbsp;+ site.address&nbsp;+&nbsp;', capacity='&nbsp;+ site.capacity);
}

如果脚本需要检查某个地址属于哪个基本块,可以使用findBlockContaining()

const&nbsp;block = cfg.findBlockContaining(target);

if&nbsp;(block !==&nbsp;null) {
console.log('block start='&nbsp;+ block.start);
console.log('block end='&nbsp;+ block.end);
console.log('instruction count='&nbsp;+ block.instructions.length);
}

这组接口可用于Hook点筛选、函数内控制流查看、基本块粒度分析等场景。

C层控制流图用法

嵌入Gum或开发原生组件时,可直接使用C接口。

static&nbsp;gboolean
on_site(gconstpointer site, gsize capacity, gpointer user_data)
{
&nbsp; g_print ("site=%p capacity=%zu\n", site, capacity);
return&nbsp;TRUE;
}

void
inspect_target(gconstpointer target)
{
&nbsp; GumControlFlowGraph * cfg;

&nbsp; cfg = gum_control_flow_graph_new_for_function (target);
&nbsp; gum_control_flow_graph_enumerate_dominating_sites (cfg, target, on_site,&nbsp;NULL);
&nbsp; gum_control_flow_graph_free (cfg);
}

如果只查询函数范围,可直接使用gum_process_find_function_range()

GumMemoryRange range;

if&nbsp;(gum_process_find_function_range (target, &range))
{
&nbsp; g_print ("function range: %p - %p\n",
&nbsp; &nbsp; &nbsp; (void&nbsp;*) range.base_address,
&nbsp; &nbsp; &nbsp; (void&nbsp;*) (range.base_address + range.size));
}

Linux注入器触发点采样

17.12.0中,frida-core对Linux的proc-mem注入链路做了多处修改。相关代码位于src/linux/proc-mem-injector.vala

提交a0a8838增加了采样回退逻辑。优先使用ActivitySampler采样;如果eBPF或perf因为权限限制不可用,则读取/proc/<pid>/task/<tid>/syscall

privateasyncuint64&nbsp;discover_trigger (ProcMapsSnapshot maps, RegionLayout region, StackRendezvous rendezvous,
uint64&nbsp;mmap_impl,&nbsp;uint64&nbsp;exclude, Cancellable? cancellable) throws Error, IOError {
Gee.List<SampledStack> stacks;

var&nbsp;sampler =&nbsp;new&nbsp;ActivitySampler (pid);
try&nbsp;{
&nbsp; &nbsp; sampler.start ();
&nbsp; &nbsp; yield sleep_ms (SAMPLE_WINDOW_MS);
&nbsp; &nbsp; sampler.stop ();
&nbsp; &nbsp; stacks = sampler.stacks;
&nbsp; }&nbsp;catch&nbsp;(Error e) {
&nbsp; &nbsp; stacks = yield sample_threads_via_proc (maps, cancellable);
&nbsp; }

return&nbsp;hottest_libc_function (maps, region, rendezvous, mmap_impl, stacks, exclude);
}

提交880f7ac扩展/proc路径的采样内容。修改前只读取线程当前PC;修改后还会读取栈指针附近的若干栈字,形成候选调用帧。

privateuint64[] read_thread_frames (ProcMapsSnapshot maps,&nbsp;uint&nbsp;tid) throws Error {
string&nbsp;contents;
try&nbsp;{
&nbsp; &nbsp; FileUtils.get_contents ("/proc/%u/task/%u/syscall".printf (pid, tid), out contents);
&nbsp; }&nbsp;catch&nbsp;(FileError e) {
return&nbsp;{};
&nbsp; }

string[] fields = contents.strip ().split (" ");
if&nbsp;(fields.length <&nbsp;2)
return&nbsp;{};

uint64&nbsp;pc;
if&nbsp;(!uint64.try_parse (fields[fields.length -&nbsp;1], out pc,&nbsp;null,&nbsp;0) || pc ==&nbsp;0)
return&nbsp;{};

uint64&nbsp;sp;
var&nbsp;stack = (uint64.try_parse (fields[fields.length -&nbsp;2], out sp,&nbsp;null,&nbsp;0) && sp !=&nbsp;0)
&nbsp; &nbsp; ? maps.find_mapping (sp)
&nbsp; &nbsp; :&nbsp;null;
if&nbsp;(stack ==&nbsp;null)
returnnewuint64[] { pc };

uint&nbsp;depth =&nbsp;uint.min (STACK_SCAN_DEPTH, (uint) ((stack.end - sp) /&nbsp;8));
var&nbsp;frames =&nbsp;newuint64[1&nbsp;+ depth];
&nbsp; frames[0] = pc;
for&nbsp;(uint&nbsp;i =&nbsp;0; i != depth; i++)
&nbsp; &nbsp; frames[1&nbsp;+ i] = mem.read_pointer (sp + (uint64) i *&nbsp;8);
return&nbsp;frames;
}

这段逻辑用于处理多线程目标中的一种情况:线程可能停在很短的syscall wrapper里,当前PC所在函数空间不足以写入bootstrap stub。读取栈上的返回地址后,注入器可在调用链中查找可容纳stub的libc函数作为触发点。

提交33e5879调整了触发函数大小判定方式。修改前使用固定最小长度;修改后先构造实际要写入的stub,再用实际长度筛选候选函数。

privateuint64&nbsp;trigger_stub_footprint (uint64&nbsp;sample_target, RegionLayout region, StackRendezvous rendezvous,
uint64&nbsp;mmap_impl) throws Error {
uint8[] stub = build_trigger_stub (sample_target, rendezvous.cas, mmap_impl,&nbsp;0, region.total,
&nbsp; &nbsp; region.entry_offset);
return&nbsp;BOOTSTRAP_OFFSET + stub.length;
}

触发点写入与恢复

提交f0cc3b4调整了触发函数恢复过程。恢复时先让调用者停在触发点入口,再写回stub覆盖区域,最后恢复函数前导指令。

privateasyncvoid&nbsp;restore_trigger (uint64&nbsp;target,&nbsp;uint8[] original) throws Error, IOError {
&nbsp; block_callers (target);
&nbsp; yield sleep_ms (DRAIN_MS);
&nbsp; mem.write_memory (target + BOOTSTRAP_OFFSET, original[BOOTSTRAP_OFFSET:original.length]);
&nbsp; restore_prologue (target, original);
}

对应的安装流程也先调用block_callers(),等待短时间后再写入bootstrap。

privateasyncvoid&nbsp;install_bootstrap (uint64&nbsp;target,&nbsp;uint8[] stub) throws Error, IOError {
&nbsp; block_callers (target);
&nbsp; yield sleep_ms (DRAIN_MS);
&nbsp; mem.write_memory (target + BOOTSTRAP_OFFSET, stub);
&nbsp; release_callers (target);
}

该修改针对的是多线程进程中触发函数正在被其它线程执行的情况,避免恢复过程中出现半写入的函数前导区域。

Agent装载路径

提交f51327f修改了Linux loader中agent的dlopen()路径。旧路径使用/proc/self/fd/<fd>,新路径改为/proc/<pid>/fd/<fd>

补丁中的核心代码如下:

staticpid_t
frida_getpid(void)
{
return&nbsp;frida_syscall_0 (SYS_getpid);
}
libc->sprintf&nbsp;(agent_path,&nbsp;"/proc/%d/fd/%d", frida_getpid (), agent_codefd);

ctx->agent_handle = libc->dlopen (agent_path, libc->dlopen_flags, pretend_caller_addr);
if&nbsp;(ctx->agent_handle ==&nbsp;NULL)
goto&nbsp;dlopen_failed;

提交说明指出,附加调试器会在动态链接器的库加载事件中读取dlopen()传入的路径。若路径是/proc/self/fd/<fd>,调试器侧解析到的是调试器自己的fd表,而不是目标进程的fd表,可能导致读取失败并让加载线程停在动态链接器断点处。改成/proc/<pid>/fd/<fd>后,目标进程内部和外部观察者解析到同一个对象路径。

libbpf诊断信息

提交21dc508修改了libbpf错误日志处理方式。此前libbpf诊断信息可能直接输出到stderr,这次改为捕获日志并写入抛出的错误信息中。

该修改主要影响失败场景的可观测性。eBPF加载失败时,调用方可从异常消息中看到verifier或权限相关信息,而不是只能依赖外部标准错误输出。

Interceptor刷新接口

提交779f9b0GumInterceptor新增两个刷新接口。

GUM_API gboolean&nbsp;gum_interceptor_flush_function(GumInterceptor * self,
&nbsp; &nbsp; gconstpointer function_address);
GUM_API gboolean&nbsp;gum_interceptor_flush_listener(GumInterceptor * self,
&nbsp; &nbsp; GumInvocationListener * listener);

gum_interceptor_flush()是全局刷新;新增的两个接口分别按函数地址和监听器刷新。源码中的实现会检查当前transaction状态和待销毁任务,只在指定函数或指定listener相关任务仍未完成时返回FALSE

最小调用方式如下:

if&nbsp;(!gum_interceptor_flush_function (interceptor, target))
&nbsp; g_print ("target still busy\n");

if&nbsp;(!gum_interceptor_flush_listener (interceptor, listener))
&nbsp; g_print ("listener still busy\n");

该接口可供原生Gum使用者在卸载单个Hook或释放某个listener前确认相关调用已结束。

Windows arm64修复

17.12.0还包含多项Windows arm64相关修复。发布说明中列出的主要内容包括:

  1. stalker-arm64

    修复远距离backpatch epilog丢失BL的问题。

  2. stalker

    在Windows上检测线程退出,避免继续跟踪进入ntdll线程清理路径。

  3. exceptor

    在Windows arm64上改用RtlRestoreContext()恢复上下文,避免longjmp()处理合成栈帧时终止进程。

  4. CI增加Windows arm64原生任务。

这些修改集中在稳定性修复,不涉及新的脚本API。

公共API文档

17.12.0还补充了Gum和Core的公开API文档。发布说明中列出的Gum文档对象包括InterceptorStalkerExceptorModuleModuleMapModuleRegistryMemoryMapMemoryAccessMonitorDarwinGrafterfrida-core侧也新增了Vala文档注释注入GIR的相关提交。

这部分更新主要影响文档生成、GObject introspection数据和下游绑定维护。

总结

Frida17.12.0的更新包括以下几类:

  1. Gum新增函数范围定位和控制流图接口,GumJS增加对应的Process.findFunctionRange()ControlFlowGraph
  2. Linux注入器调整触发点采样、触发函数大小判断、触发点恢复和agent装载路径。
  3. Interceptor增加按函数和按listener刷新的接口。
  4. Windows arm64相关的Stalker、Exceptor和CI配置得到修复和补充。
  5. Gum和Core补充公开API文档和GObject introspection发布流程。

对脚本使用者来说,直接可用的新接口是Process.findFunctionRange()ControlFlowGraph。对嵌入Gum或维护Frida集成的人来说,相关接口包括gum_process_find_function_range()gum_control_flow_graph_*()和新的Interceptor刷新接口。Linux注入器相关修改属于内部实现调整,主要体现在attach和注入失败场景的行为变化。

参考

  1. Frida新闻页:https://frida.re/news/

  2. Frida17.12.0发布页:https://frida.re/news/2026/06/10/frida-17-12-0-released/

  3. frida-gum

    提交67a1b9c5e2fd31b3273eaf04c3e3779f9b0

  4. frida-core

    提交a0a883833e5879880f7acf0cc3b4f51327f21dc508


免责声明:

本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。

任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。

本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我

本文转载自:软件安全与逆向分析 非虫 非虫《Frida17.12.0的功能与变化》

评论:0   参与:  0