百科.dev
全部条目AI 编程趋势榜开源项目技术资讯提交条目
登录
< 返回工具列表
R

rustFrida

> 编程语言
开源

xxxxxx

384 stars0 点赞0 次浏览
访问官网GitHub

工具介绍

xxxxxx

rustFrida

ARM64 Android 动态插桩框架。

环境要求

  • Android NDK 25+(默认路径 ~/Android/Sdk/ndk/)
  • Rust toolchain + aarch64-linux-android target
  • Python 3(构建 loader shellcode)
  • .cargo/config.toml 已配置交叉编译(仓库自带)

首次 clone 后先拉取子仓库:

bash
git submodule update --init --recursive

quickjs-hook/third_party/tinycc 是 RF 的 CModule 编译器子仓库,默认跟随 https://github.com/kkkbbb/tinycc.git 的 rf/cmodule-runtime 分支。

构建

最终产物 rustfrida 通过 include_bytes! 内嵌了 loader shellcode 和 agent SO,有严格的构建顺序:

loader shellcode  ──┐
                    ├──→  rustfrida (主程序)
agent (libagent.so) ┘

1. 构建 loader shellcode(bootstrapper + rustfrida-loader)

bash
python3 build_helpers.py
# 输出:
#   loader/build/bootstrapper.bin
#   loader/build/rustfrida-loader.bin

loader 是 bare-metal ARM64 shellcode,被 rustfrida 通过 include_bytes! 嵌入。修改 loader C 代码后需重新运行此步。

2. 构建 agent(libagent.so)

bash
cargo build -p agent --release
# 输出: target/aarch64-linux-android/release/libagent.so

agent 是注入到目标进程的动态库,包含 hook 引擎、QuickJS、Java hook 等。必须先于 rustfrida 构建,因为 rustfrida 通过 include_bytes! 嵌入 agent SO。

3. 构建 rustfrida(主程序)

bash
cargo build -p rust_frida --release
# 输出: target/aarch64-linux-android/release/rustfrida

rustfrida 内嵌了 bootstrapper.bin + rustfrida-loader.bin + libagent.so,是一个自包含的单文件。

可选组件(单独构建)

这些不在 default-members 里,按需构建:

QBDI Trace 支持: 需要先构建 qbdi-helper SO,再用 --features qbdi 编译 agent 和 rustfrida:

bash
cargo build -p qbdi-helper --release           # → libqbdi_helper.so
cargo build -p agent --release --features qbdi  # agent 启用 qbdi feature
cargo build -p rust_frida --release --features qbdi  # rustfrida 嵌入 qbdi-helper SO

eBPF SO 加载监控(--watch-so): ldmonitor 是 rustfrida 的编译依赖,默认构建已包含,--watch-so 无需额外步骤。如需独立使用 ldmonitor 命令行工具:

bash
cargo build -p ldmonitor --release    # → ldmonitor 独立二进制

TinyCC 子仓库维护

RF 的 CModule 功能依赖 quickjs-hook/third_party/tinycc。该目录是 git submodule,RF 定制修改维护在 rf/cmodule-runtime 分支:

bash
git -C quickjs-hook/third_party/tinycc remote -v
# origin   https://github.com/kkkbbb/tinycc.git
# upstream https://github.com/frida/tinycc.git

git -C quickjs-hook/third_party/tinycc status --short --branch

同步上游时,在子仓库 rebase 后更新父仓库的 gitlink:

bash
git -C quickjs-hook/third_party/tinycc fetch upstream
git -C quickjs-hook/third_party/tinycc rebase upstream/main
git -C quickjs-hook/third_party/tinycc push origin rf/cmodule-runtime

git add quickjs-hook/third_party/tinycc
git commit -m "Update tinycc submodule"

如果修改了 TinyCC 本身,先在子仓库提交并 push,再回到父仓库提交 submodule 指针。

部署 & 运行

bash
adb push target/aarch64-linux-android/release/rustfrida /data/local/tmp/

# PID 注入
./rustfrida --pid 
./rustfrida --pid  -l script.js

# Spawn 模式(启动时注入)
./rustfrida --spawn com.example.app
./rustfrida --spawn com.example.app -l script.js

# 等待 SO 加载后注入(eBPF)
./rustfrida --watch-so libnative.so

# 详细日志
./rustfrida --pid  --verbose

# 同步输出日志到文件(终端仍正常输出,文件为纯文本)
./rustfrida --pid  -l script.js -o /data/local/tmp/rustfrida.log

REPL 命令

jsinit              # 初始化 JS 引擎
jseval        # 求值表达式
loadjs      # 执行脚本
jsrepl              # 交互式 REPL(Tab 补全)
exit                # 退出

快速上手

最常见的工作流是:写一个 script.js,用 -l 加载到目标进程,然后通过日志、RPC 或文件把结果带出来。

bash
# 已运行的进程
./rustfrida --pid  -l script.js

# 从启动阶段注入,适合抓 Application / ClassLoader 初始化
./rustfrida --spawn com.example.app -l script.js

# 先进入交互,再手动 loadjs / jseval
./rustfrida --pid 

最小脚本:

javascript
console.log("agent loaded");

Java.ready(function() {
    console.log("Java is ready");
});

能力地图

你想做什么 优先使用 典型入口
Hook Java 方法、改参数/返回值 Java.use() Class.method.impl = function (...) { ... }
高频 Java 方法 Hook Managed DSL 动态编译器 method.dslImpl = script
Hook native 函数并继续跑原函数 Interceptor.attach onEnter(args) / onLeave(retval)
完全替换 native 函数 hook() 或 Interceptor.replace() return value / 条件性 this.$orig()
高频 native Hook CModule + attachNative / hookNative void cb(HookContext *ctx, void *data)
查找 so、符号、导入导出 Module findExportByName() / enumerateSymbols()
读写目标进程内存 Memory / ptr() p.readU32() / p.writeBytes()
监控 JNI 注册 Jni + native hook Jni.addr("RegisterNatives")
远程触发脚本能力 HTTP RPC rpc.exports = { ... }
采集指令 trace 用于回放分析 qbdi registerTraceCallbacks()

常见场景

Hook Java 方法

适合看业务参数、绕过判断、替换返回值。Spawn 模式下务必放在 Java.ready() 里。

javascript
Java.ready(function() {
    var Login = Java.use("com.example.LoginManager");

    Login.checkPassword.impl = function(user, pass) {
        console.log("checkPassword", user, pass);
        return true;              // 直接改返回值,不调原方法
    };
});

需要保留原逻辑时调用 $orig():

javascript
Java.ready(function() {
    var Log = Java.use("android.util.Log");

    Log.i.overload("java.lang.String", "java.lang.String").impl = function(tag, msg) {
        console.log("[Log.i]", tag, msg);
        return this.$orig(tag, msg);
    };
});

Hook Native 函数并修改参数

只改参数然后继续执行原函数,优先用 Interceptor.attach({ onEnter })。

javascript
var open = Module.findExportByName("libc.so", "open");

Interceptor.attach(open, {
    onEnter(args) {
        var path = args[0].readCString();
        console.log("open", path);

        if (path.indexOf("/proc/self/maps") >= 0) {
            args[0] = Memory.allocUtf8String("/data/local/tmp/fake_maps");
        }
    }
});

Hook Native 函数并修改返回值

需要返回值时加 onLeave。

javascript
var getuid = Module.findExportByName("libc.so", "getuid");

Interceptor.attach(getuid, {
    onLeave(retval) {
        console.log("getuid =>", retval.toUInt32());
        retval.replace(0);
    }
});

条件性调用原 native 函数

如果你需要“有时调原函数、有时直接返回”,用 hook() 更直接。

javascript
var getpid = Module.findExportByName("libc.so", "getpid");

hook(getpid, function() {
    if (Date.now() & 1) {
        return this.$orig();    // 调原函数,参数默认来自当前寄存器
    }
    return 12345;               // 跳过原函数
});

监控 RegisterNatives

适合定位 Java native 方法和 so 内真实函数地址。

javascript
Interceptor.attach(Jni.addr("RegisterNatives"), {
    onEnter(args) {
        var cls = Jni.env.getClassName(args[1]);
        var methods = Jni.structs.JNINativeMethod.readArray(args[2], Number(args[3]));

        console.log("RegisterNatives:", cls);
        methods.forEach(function(m) {
            var mod = Module.findByAddress(m.fnPtr);
            var where = mod ? mod.name + "+" + m.fnPtr.sub(mod.base) : m.fnPtr.toString();
            console.log("  " + m.name + " " + m.sig + " -> " + where);
        });
    }
});

远程调用脚本能力

当你希望工具常驻,然后由 host 脚本、UI 或自动化流程触发功能时,用 rpc.exports。

javascript
rpc.exports = {
    ping: function() { return "pong"; },
    app: function() {
        var ActivityThread = Java.use("android.app.ActivityThread");
        var app = ActivityThread.currentApplication();
        return String(app.getPackageName());
    }
};

启动时加 --rpc-port,host 侧通过 curl 调用:

bash
adb forward tcp:9191 tcp:9191
./rustfrida --pid  -l script.js --rpc-port 9191
curl -X POST http://127.0.0.1:9191/rpc/0/ping

选择建议

  • 普通 Java 逻辑先用 Java.use().impl,稳定后再考虑 DSL。
  • 高频 Java Hook 用 DSL 动态编译器,避免每次命中都进 JS runtime。
  • Native 只改参数并继续执行,用 Interceptor.attach({ onEnter })。
  • Native 需要决定是否调用原函数,用 hook() / Interceptor.replace()。
  • 高频 Native 热路径用 CModule 写 C callback,再用 attachNative / hookNative 安装。

HTTP RPC 远程调用

脚本里用 Frida 风格的 rpc.exports 注册方法,host 端通过 HTTP POST 调用,返回值会 JSON.stringify 后透传回来。适合把 agent 当成一个常驻服务用——UI、自动化脚本、测试框架都可以直接 curl 触发。

启动

在 legacy 单会话或 --server 多会话模式下,加上 --rpc-port 即可启动 HTTP 服务器。参数可以是纯端口号(默认绑 0.0.0.0),也可以是完整地址:

bash
# legacy 模式:attach + 加载脚本 + 开 RPC 端口
./rustfrida --pid 1234 -l rpc_test.js --rpc-port 9191

# server 模式:多 session 共享同一个 RPC 端口,按 session id 路由
./rustfrida --server --rpc-port 127.0.0.1:9191

# 本机访问通过 adb forward 最简单
adb forward tcp:9191 tcp:9191

JS 侧注册

…

rpc.exports 就是个普通 JS 对象,现场 lookup,不需要向 host 注册方法列表——你可以任意时刻增删改,下一次 HTTP 请求立刻生效。

HTTP 路由

方法 路径 Body 说明
GET / / /health — 健康检查
GET /sessions — 列出所有 session(id/pid/label/status)
POST /rpc// JSON 数组 调用 rpc.exports[method].apply(null, args);空 body 等价 []

`` 在 legacy 模式下固定为 0,在 --server 模式下对应 list 命令显示的 id。

调用示例

…

成功响应统一是 {"ok":true,"result":};失败是 {"ok":false,"error":""},HTTP 状态码 400(参数错)/404(session/method 不存在)/503(session 未连接)/500(JS 异常或超时)。

行为约束

  • 返回值必须 JSON-safe:JSON.stringify 在 JS 侧执行,函数/循环引用/undefined 会被跳过。直接 return 一个 Java wrapper 只会得到指针字面量——请手动 String(obj.method()) 或构造 plain object。
  • 并发串行化:同一 session 内 HTTP 请求排队执行;跨 session 完全并行。
  • 超时 30 秒:超时返回 {"ok":false,"error":"rpc call timed out"}。长耗时任务请改用轮询接口。
  • 仅同步:不支持 async / Promise——Promise 会被 JSON.stringify 成 {}。

JS API 参考

全局对象一览

console, ptr(), Memory, File, Module, Interceptor, CModule, hook(), hookNative(), attachNative(), unhook(), callNative(), qbdi, Java, Jni

常用类型别名

类型名 实际含义
AddressLike NativePointer | number | bigint | "0x..."
NativePointer ptr() 创建的指针对象
JavaObjectProxy Java.use() / Java hook 中返回的 Java 对象代理

结构体 / 上下文对象

…

Native Hook

Frida 风格:arguments = x0..x7(前 8 个整型参数,BigInt),this = register 上下文(含 x0-x30 / sp / pc / $orig)。

…

NativeFunction(任意签名调用)

Frida 兼容 API,任意参数数量(寄存器用完自动栈溢出,上限 256 个栈参数)。

javascript
var open = new NativeFunction(
    Module.findExportByName("libc.so", "open"),
    "int",                            // 返回类型
    ["pointer", "int"]                // 参数类型
);
var fd = open(Memory.allocUtf8String("/tmp/foo"), 0);

var atan2 = new NativeFunction(
    Module.findExportByName("libm.so", "atan2"),
    "double",
    ["double", "double"]
);
atan2(1.0, 2.0);

支持的类型:void, bool, char/uchar, int8/uint8, short/ushort, int16/uint16, int/uint, int32/uint32, long/ulong (64-bit), int64/uint64, size_t/ssize_t, pointer, float, double。

AAPCS64 调用约定:整数/指针先填 x0-x7,浮点先填 d0-d7(两队列独立),超出部分自动压栈。不支持 struct-by-value。

CModule 和 native C callback

CModule 用内置 TinyCC 在目标进程里动态编译 C 代码,适合把高频 native hook 的热路径从 JS callback 下沉到 C callback。CModule 对象持有编译后的代码内存;只要 hook 还在使用其中的函数指针,就必须保留 JS 引用,避免 GC 释放代码。

javascript
var cm = new CModule(`
    #include 

    void on_getuid(HookContext *ctx, void *user_data) {
        uint64_t real = hook_invoke_trampoline(ctx, ctx->trampoline);
        ctx->x[0] = (real == 0 ? 0 : 20000);
    }
`);

globalThis.keep_getuid_cmodule = cm;        // hook 存活期间必须保留引用

var getuid = Module.findExportByName("libc.so", "getuid");
var trampoline = hookNative(getuid, cm.on_getuid);

hookNative(target, callbackPtr, userData?, mode?) 是 replace 语义:原函数不会自动执行。需要原函数时,在 C callback 里调用 hook_invoke_trampoline(ctx, ctx->trampoline);不需要原函数时直接改 ctx->x[0]。

…

attachNative(target, { onEnter?, onLeave?, data?, mode? }) 是 attach 语义:hook engine 会自动执行原函数。只提供 onEnter 时走 tail-jump 快路径,不保留 leave 状态;提供 onLeave 时才会在原函数返回后进入 leave callback。onEnter 和 onLeave 的 C 函数签名相同:

c
void callback(HookContext *ctx, void *user_data);

两者区别只在时机和 ctx 内容:

阶段 ctx->x[0..] 含义 原函数
onEnter 入参寄存器,可改参数 返回后自动执行
onLeave x0 是返回值,可改返回值 已经执行完

如果安装了 onLeave,onEnter 可用 ctx->intercept_leave = 0 跳过本次 leave;没有安装 onLeave 时设置这个字段没有意义。

CModule 默认注入这些头:stdint.h, stddef.h, stdbool.h, string.h, rfhook.h。rfhook.h 暴露 HookContext、RfHookCallback 和 hook_invoke_trampoline():

c
typedef struct {
    uint64_t x[31];
    uint64_t sp;
    uint64_t pc;
    uint64_t nzcv;
    void *trampoline;
    uint64_t d[8];
    uint64_t intercept_leave;
} HookContext;

typedef void (*RfHookCallback)(HookContext *ctx, void *user_data);
uint64_t hook_invoke_trampoline(HookContext *ctx, void *trampoline);

也可以把 JS 侧找到的 native s

Issues· 0 开放

查看全部 Issues在 GitHub 打开

暂无开放 Issues,或尚未同步最近议题。

> 标签

C

暂无评论,来聊聊你的看法吧

> 工具信息

发布日期2026年8月1日
最后更新2026年9月17日
分类编程语言
定价开源

> 相关工具

T
TypeScript
JavaScript 的超集,为前端与全栈提供静态类型
P
Python
通用编程语言,广泛用于 Web、数据与 AI
G
Go
Google 推出的简洁高效系统语言