首页
社区
课程
招聘
[原创]Frida源码分析之frida-core篇
发表于: 2026-5-9 15:10 15210

[原创]Frida源码分析之frida-core篇

2026-5-9 15:10
15210

本文分析的是frida-core源码,其中很多关键代码使用vala语言编写,再借助编译器转换成C语言代码。frida-core 封装了跨平台通信、进程管理和注入逻辑,包含如下关键模块:

frida-server的启动入口在server/server.vala文件中。

解析参数,初始化运行环境,最终调用run_application()启动frida-server

设置临时目录,用于存放 Frida 运行时需要落地的临时二进制、资源等文件,默认目录名为re.frida.server。然后创建Application对象并调用其run()方法。

Application的定义在 server/server.vala 265行处。

先把“启动服务”的异步任务投递到 GLib 主循环,然后进入主循环,让主循环去调度并驱动这个任务。因此真正启动服务的逻辑不在 run() 里,而是在它调度的 start() 方法里:

这里如果frida-server启动时没有指定--device,就会调用new ControlService()创建ControlService实例,并调用它的start()方法。该类定义在 src/control-service.vala 中,ControlService的初始化函数如下:

创建临时目录,接着创建LinuxHostSession并调用assign_session()把它注册到ControlService中,以及连接各种信号。LinuxHostSession 是本地 Frida 功能的核心入口之一,提供了应用枚举、进程枚举、进程注入、进程启动等功能。

现在回到ControlService.start()方法中。

此处的serviceWebService,这里给 WebService.incoming 信号绑定 on_server_connection()函数。当有 Frida 客户端连接到 frida-server时,会调用该绑定函数。接下来通过service.start()启动WebService,该函数定义在lib\base\socket.vala 237行处。

handle_start_request()会继续调用 do_start(),里面会解析监听地址,默认情况下是127.0.0.1:27042,之后WebService 内部的 ConnectionHandler 会创建 Soup server,并注册 WebSocket 路径 /ws,客户端脸上 /ws后,触发incoming信号,回调之前注册的on_server_connection()

这里视情况选择不同的ConnectionHandler,然后调用handle_server_connection()启动该连接的 DBus/认证处理流程。

IOStream包装成DBusConnection。有鉴权的情况下(比如说配置了token参数)创建AuthenticationChannel,只有认证成功后才能升级为ControlChannel,无鉴权的情况下创建ControlChannel,能直接调用 HostSessionspawn/attach/kill/... 等方法。

frida-server启动之后,接下来就是spawn/attach模式附加到目标进程中。spawn模式下启动目标程序后,并没有进行agent的注入,反而是后续调用attach进行注入的。这部分可以参考frida-tools项目,在\frida_tools\application.py 607行处:

他们最终都是通过perform_attach_to()函数实现frida-agent注入的,代码路径在src\linux\linux-host-session.vala 387行处。

先解释一下HAVE_EMBEDDED_ASSETS宏定义,当它为真时,在编译时 Frida 时会将frida-agent.so嵌入到 frida-core中,然后在运行时将内置的 agent 释放出来并使用,这部分在src\linux\linux-host-session.vala 62行处。

回到perform_attach_to()函数中,它的代码逻辑是通过 Linjector 注入 frida-agent.so,注入成功后通过 request_control_channel() 获取与目标进程内 agent 通信的 IOStream。根据HAVE_EMBEDDED_ASSETS宏定义,调用了Linjector的不同方法,具体如下:

最终调用的都是inject_library_fd函数

调用的是helper的inject_library()方法,代码位于src\linux\frida-helper-backend.vala 300行处。

perform()函数最终调用的是InjectTask对象的run()方法

InjectSession.open() 会通过 ptrace attach 到目标进程并保存寄存器状态。然后 session.inject() 才是真正做注入工作的。

InjectSession.open()调用链:

InjectSession.inject()代码位于src\linux\frida-helper-backend.vala 880行处。

注入loader到目标进程中,然后通过launch_loader远程启动它,让它完成 frida-agent的加载和入口调用。

构造loader入口函数的远程调用,让目标进程自己执行 loader 代码。之后的代码逻辑就是在src\linux\helpers\loader.c中,入口是frida_load(),里面通过线程启动frida_main()方法,在后者中,通过dlopen() 加载 agent,通过 dlsym() 找到 frida_agent_main,最后调用 agent 入口函数。

在上节中, perform_attach_to()函数指定了注入动态库后执行的函数符号为 frida_agent_main,该函数的定义在 lib/agent/agent.vala 中:

首次启动时调用Runner.create_and_run()初始化agent并运行。

初始化 agent 环境,构建资源标识符urisocket:<fd><parameters> 格式,然后创建全局单例 Runner,并调用run()方法启动。

启动异步函数 start(),该函数负责真正初始化agent。

解析 agent 启动参数,启用/禁用关键监控组件,调用setup_connection_with_transport_uri()函数建立与 frida-server 的 DBus 通信连接,最后切换到 JS 线程完成一次调度同步,确保JS 线程已经初始化到可用状态。

Android 上主要走 socket:<fd> 分支,把注入阶段传入的 Unix socket fd 包装成 IOStream通信流,然后再交给 setup_connection_with_stream() 用于建立 DBus 通信。

基于 IOStream 建立 DBus 通道。然后把当前 Runner 注册成 AgentSessionProvider,这样frida-server就可以通过 DBus 通信调用 agent 的方法。之后通过 DBus 通信获取frida-serverAgentController代理,这样frida-agent也同样可以通过 DBus 通信反向调用frida-server的方法。

Frida的Gadget是一个共享库,用于免root注入hook脚本。frida持久化是借助frida-gadget来进行的,主要方式有App重打包、源码定制等,详细分析可见[原创]小菜花的frida-gadget持久化方案汇总

frida-gadget的入口函数在lib\gadget\gadget.vala 379行处。

参考官方文档,gadget 启动时会去指定路径搜索配置文件,该配置文件应与gadget二进制文件同名,但文件扩展名为.config。然后根据配置文件的interaction字段进入不同的交互模式,可用的交互方式有:

Listen模式

默认的交互方式。配置文件最低要求如下:

Connect模式

连接到指定ip:port处的frida-portal ,并成为其进程集群中的一个节点。配置文件最低要求如下:

Script模式

gadget启动后,在目标App执行前加载指定 JS 脚本。配置文件最低要求如下:

ScriptDirectory模式

gadget启动后,在目标App执行前从指定目录中加载 JS 脚本。与gadget二进制文件同名的配置文件最低要求如下:

该目录下的每个 JS 脚本都可以配置一个同名的.json文件,用于决定相应 JS 脚本是否加载(比如仅在特定程序启动时才加载)。例如:

当应用程序满足如上任意一个条件时,才会加载相应 JS 脚本。

frida-server 负责在目标设备上启动控制服务,监听来自 PC 端 Frida 客户端的连接,并通过 ControlServiceWebServiceDBusConnection 等组件把外部操作转换成对本地 HostSession 的调用。客户端执行 spawnattachkill 等操作时,最终都会进入对应平台的 HostSession 实现。

在 Linux/Android 场景下,真正的注入流程主要由 LinuxHostSessionLinjectorfrida-helper-backend 完成。attach 时 Frida 会选择合适的 frida-agent.so,通过文件路径或内嵌资源方式交给 injector,再由 helper 使用 ptrace 控制目标进程,写入 loader 代码并初始化运行环境,远程执行 loader,最终在目标进程中使用 dlopen() 加载 agent 并调用 frida_agent_main

frida-agent 被加载后会初始化自身运行环境,建立与 frida-server 的通信通道,并把自身注册为 AgentSessionProvider。之后 PC 端加载脚本、发送消息、调用 RPC 等操作,都会通过这条 DBus 通道转发到目标进程内的 agent,由 agent 调用 Gum/JS 引擎完成实际 Hook 逻辑。


传播安全知识、拓宽行业人脉——看雪讲师团队等你加入!

收藏
免费 6
打赏
分享
最新回复 (1)
雪    币: 104
活跃值: (8697)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
tql
2026-5-11 15:21
0
游客
登录 | 注册 方可回帖
返回