ARM64 Android 动态插桩框架。
- Android NDK 25+(默认路径
~/Android/Sdk/ndk/) - Rust toolchain +
aarch64-linux-androidtarget - Python 3(构建 loader shellcode)
.cargo/config.toml已配置交叉编译(仓库自带)
最终产物 rustfrida 通过 include_bytes! 内嵌了 loader shellcode 和 agent SO,有严格的构建顺序:
loader shellcode ──┐
├──→ rustfrida (主程序)
agent (libagent.so) ┘
python3 build_helpers.py
# 输出:
# loader/build/bootstrapper.bin
# loader/build/rustfrida-loader.binloader 是 bare-metal ARM64 shellcode,被 rustfrida 通过 include_bytes! 嵌入。修改 loader C 代码后需重新运行此步。
cargo build -p agent --release
# 输出: target/aarch64-linux-android/release/libagent.soagent 是注入到目标进程的动态库,包含 hook 引擎、QuickJS、Java hook 等。必须先于 rustfrida 构建,因为 rustfrida 通过 include_bytes! 嵌入 agent SO。
cargo build -p rust_frida --release
# 输出: target/aarch64-linux-android/release/rustfridarustfrida 内嵌了 bootstrapper.bin + rustfrida-loader.bin + libagent.so,是一个自包含的单文件。
这些不在 default-members 里,按需构建:
QBDI Trace 支持: 需要先构建 qbdi-helper SO,再用 --features qbdi 编译 agent 和 rustfrida:
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 SOeBPF SO 加载监控(--watch-so): ldmonitor 是 rustfrida 的编译依赖,默认构建已包含,--watch-so 无需额外步骤。如需独立使用 ldmonitor 命令行工具:
cargo build -p ldmonitor --release # → ldmonitor 独立二进制adb push target/aarch64-linux-android/release/rustfrida /data/local/tmp/
# PID 注入
./rustfrida --pid <pid>
./rustfrida --pid <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 <pid> --verbosejsinit # 初始化 JS 引擎
jseval <expr> # 求值表达式
loadjs <script> # 执行脚本
jsrepl # 交互式 REPL(Tab 补全)
exit # 退出
console, ptr(), Memory, Module, hook(), unhook(), callNative(), qbdi, Java, Jni
| 类型名 | 实际含义 |
|---|---|
AddressLike |
NativePointer | number | bigint | "0x..." |
NativePointer |
ptr() 创建的指针对象 |
JavaObjectProxy |
Java.use() / Java hook 中返回的 Java 对象代理 |
type ModuleInfo = {
name: string; base: NativePointer; size: number; path: string
}
type NativeHookContext = {
x0 ~ x30: number | bigint // ARM64 通用寄存器
sp: number | bigint
pc: number | bigint
trampoline: number | bigint
orig(): number | bigint // 调用原函数,返回值写入 x0
}
type JavaHookContext = {
thisObj?: JavaObjectProxy // 实例方法的 this(静态方法无)
// 字段: thisObj.field.value 读/写
// 方法: thisObj.method(args) 调用
args: any[] // 参数数组(Object 参数自动包装为 Proxy)
env: number | bigint // JNIEnv*
orig(...args: any[]): any // 调原方法,不传参用原始参数
}
type JniEntry = { name: string; index: number; address: NativePointer }
type JNINativeMethodInfo = {
address: NativePointer; namePtr: NativePointer; sigPtr: NativePointer
fnPtr: NativePointer; name: string | null; sig: string | null
}// 基本 hook — 透传
hook(Module.findExportByName("libc.so", "open"), function(ctx) {
console.log("open:", Memory.readCString(ptr(ctx.x0)));
return ctx.orig();
});
// 修改返回值
hook(Module.findExportByName("libc.so", "getpid"), function(ctx) {
ctx.orig();
return 12345; // 调用方拿到 12345
});
// 修改参数 — 通过 ctx 属性
hook(target, function(ctx) {
ctx.x0 = ptr("0x1234"); // 改第一个参数
ctx.x1 = 100; // 改第二个参数
return ctx.orig(); // 用修改后的参数调原函数
});
// 修改参数 — 通过 orig() 传参(按顺序覆盖 x0-xN)
hook(target, function(ctx) {
return ctx.orig(ptr("0x1234"), 100);
});
// 不 return 也行 — ctx.x0 赋值会同步回 C 层
hook(Module.findExportByName("libc.so", "getuid"), function(ctx) {
ctx.orig();
ctx.x0 = 77777; // 调用方拿到 77777
});
// 移除 hook
unhook(Module.findExportByName("libc.so", "open"));
// 直接调用 native 函数(最多 6 个参数,走 x0-x5)
var pid = callNative(Module.findExportByName("libc.so", "getpid"));hook(target, callback, Hook.NORMAL) // 0: mprotect 直写(默认)
hook(target, callback, Hook.WXSHADOW) // 1: 内核 shadow 页,/proc/mem 不可见
hook(target, callback, Hook.RECOMP) // 2: 代码页重编译,仅 4B patch
hook(target, callback, 1) // 数字也行
hook(target, callback, true) // true = WXSHADOW| API | 参数 | 返回 |
|---|---|---|
hook(target, callback, stealth?) |
AddressLike, Function, number? |
boolean |
unhook(target) |
AddressLike |
boolean |
callNative(func, ...args) |
AddressLike, ...AddressLike (最多6个) |
number | bigint |
diagAllocNear(addr) |
AddressLike |
undefined |
Java.ready(function() {
var Activity = Java.use("android.app.Activity");
// hook 实例方法(return 值就是方法返回值)
Activity.onResume.impl = function(ctx) {
console.log("onResume:", ctx.thisObj.$className);
return ctx.orig();
};
// hook 构造函数
var MyClass = Java.use("com.example.MyClass");
MyClass.$init.impl = function(ctx) {
console.log("new MyClass, arg0 =", ctx.args[0]);
return ctx.orig();
};
// 修改参数
MyClass.test.impl = function(ctx) {
return ctx.orig("patched_arg");
};
// 指定 overload(Java 类型名或 JNI 签名都行)
MyClass.foo.overload("int", "java.lang.String").impl = function(ctx) {
return ctx.orig();
};
// 移除 hook
Activity.onResume.impl = null;
});var JString = Java.use("java.lang.String");
var s = JString.$new("hello"); // 创建对象
console.log(s.length()); // 调实例方法
console.log(s.$className); // 类名
var Process = Java.use("android.os.Process");
console.log(Process.myPid()); // 调静态方法字段通过 .value 读写,每次直接走 JNI,无缓存锁:
// 静态字段
var Build = Java.use("android.os.Build");
console.log(Build.MODEL.value); // 读: "Pixel 6"
Build.MODEL.value = "FakeModel"; // 写
// 实例字段(hook 回调中 / $new 创建的对象)
var Point = Java.use("android.graphics.Point");
var p = Point.$new(10, 20);
console.log(p.x.value, p.y.value); // 读: 10, 20
p.x.value = 100; // 写: JVM 同步更新
console.log(p.toString()); // "Point(100, 20)"
// hook 中访问 this 字段
Activity.onResume.impl = function(ctx) {
var name = ctx.thisObj.mComponent.value; // 读实例字段
console.log("resuming:", name);
return ctx.orig();
};字段/方法同名:Java 允许同名字段和方法共存。此时返回 hybrid——既可调用(方法)又有 .value(字段):
var map = HashMap.$new();
map.size(); // 调用 size() 方法
map.size.value; // 读取 size 字段Spawn 模式下 app ClassLoader 未就绪,用 Java.ready 延迟执行。PID 注入模式下立即执行。
Java.setStealth(0); // Normal: mprotect 直写
Java.setStealth(1); // WxShadow: shadow 页,CRC 校验不可见
Java.setStealth(2); // Recomp: 代码页重编译
Java.getStealth(); // 查询当前模式 (0/1/2)须在 Java.use().impl 之前设置。
Java.deopt(); // 清空 JIT 缓存(InvalidateAllMethods)
Java.deoptimizeBootImage(); // boot image AOT 降级为 interpreter (API >= 26)
Java.deoptimizeEverything(); // 全局强制解释执行
Java.deoptimizeMethod("com.example.Test", "foo", "(I)V"); // 单方法降级手动调用的工具函数,hook 流程不自动使用。
| API | 参数 | 返回 |
|---|---|---|
Java.use(className) |
string |
JavaClassWrapper |
Class.$new(...args) |
任意 | JavaObjectProxy |
Class.method.impl = fn |
(ctx: JavaHookContext) => any |
setter |
Class.method.impl = null |
— | setter |
Class.method.overload(...types) |
string... |
MethodWrapper |
Java.ready(fn) |
() => void |
void |
Java.deopt() |
— | boolean |
Java.deoptimizeBootImage() |
— | boolean |
Java.deoptimizeEverything() |
— | boolean |
Java.deoptimizeMethod(cls, method, sig) |
string, string, string |
boolean |
Java.setStealth(mode) |
number (0/1/2) |
— |
Java.getStealth() |
— | number |
obj.field.value |
— | any (读字段) |
obj.field.value = x |
— | — (写字段) |
Java.getField(objPtr, cls, field, sig) |
AddressLike, string, string, string |
any (低层 API) |
Jni.addr("RegisterNatives") // → NativePointer
Jni.FindClass // 属性直接取地址
Jni.find("FindClass") // → { name, index, address }
Jni.table // 整张 JNI 函数表
Jni.addr(envPtr, "FindClass") // 指定 JNIEnvJni.helper.env.ptr // 当前线程 JNIEnv*
Jni.helper.env.getClassName(jclass) // → "android.app.Activity"
Jni.helper.env.getObjectClassName(jobject) // → 对象的类名
Jni.helper.env.readJString(jstring) // → JS string
Jni.helper.env.getObjectClass(obj) // → jclass
Jni.helper.env.getSuperclass(clazz) // → jclass
Jni.helper.env.isSameObject(a, b) // → boolean
Jni.helper.env.isInstanceOf(obj, clazz) // → boolean
Jni.helper.env.exceptionCheck() // → boolean
Jni.helper.env.exceptionClear()
Jni.helper.structs.JNINativeMethod.readArray(addr, count) // → JNINativeMethodInfo[]
Jni.helper.structs.jvalue.readArray(addr, typesOrSig) // → any[]| API | 参数 | 返回 |
|---|---|---|
Jni.addr(name) |
string |
NativePointer |
Jni.addr(env, name) |
AddressLike, string |
NativePointer |
Jni.find(name) |
string |
JniEntry |
Jni.entries() |
— | JniEntry[] |
Jni.table |
— | Record<string, JniEntry> |
Jni.helper.env.getClassName(clazz) |
AddressLike |
string | null |
Jni.helper.env.readJString(jstr) |
AddressLike |
string | null |
Jni.helper.structs.JNINativeMethod.readArray(addr, count) |
AddressLike, number |
JNINativeMethodInfo[] |
hook(Jni.addr("RegisterNatives"), function(ctx) {
var cls = Jni.helper.env.getClassName(ctx.x1);
var count = Number(ctx.x3);
console.log(cls + " (" + count + " methods)");
var methods = Jni.helper.structs.JNINativeMethod.readArray(ptr(ctx.x2), count);
for (var i = 0; i < methods.length; i++) {
var m = methods[i];
var mod = Module.findByAddress(m.fnPtr);
console.log(" " + m.name + " " + m.sig + " → " + mod.name + "+" + m.fnPtr.sub(mod.base));
}
return ctx.orig();
}, 1);| API | 参数 | 返回 |
|---|---|---|
Memory.readU8(addr) |
AddressLike |
number |
Memory.readU16(addr) |
AddressLike |
number |
Memory.readU32(addr) |
AddressLike |
bigint |
Memory.readU64(addr) |
AddressLike |
bigint |
Memory.readPointer(addr) |
AddressLike |
NativePointer |
Memory.readCString(addr) |
AddressLike |
string (最多 4096B) |
Memory.readUtf8String(addr) |
AddressLike |
string |
Memory.readByteArray(addr, len) |
AddressLike, number |
ArrayBuffer |
Memory.writeU8(addr, value) |
AddressLike, number |
undefined |
Memory.writeU16(addr, value) |
AddressLike, number |
undefined |
Memory.writeU32(addr, value) |
AddressLike, number |
undefined |
Memory.writeU64(addr, value) |
AddressLike, bigint |
undefined |
Memory.writePointer(addr, value) |
AddressLike, AddressLike |
undefined |
无效地址抛 RangeError,不会崩进程。
| API | 参数 | 返回 |
|---|---|---|
Module.findExportByName(module, symbol) |
string, string |
NativePointer | null |
Module.findBaseAddress(module) |
string |
NativePointer | null |
Module.findByAddress(addr) |
AddressLike |
ModuleInfo | null |
Module.enumerateModules() |
— | ModuleInfo[] |
var p = ptr("0x7f12345678"); // hex string / number / BigInt / NativePointer
p.add(0x100) // → NativePointer
p.sub(offset) // → NativePointer
p.toString() // → "0x7f12345678"| API | 参数 | 返回 |
|---|---|---|
ptr(value) |
number | bigint | string | NativePointer |
NativePointer |
p.add(offset) |
AddressLike |
NativePointer |
p.sub(offset) |
AddressLike |
NativePointer |
p.toString() |
— | string |
p.toNumber() |
— | bigint |
console.log(...) / console.info(...) / console.warn(...) / console.error(...) / console.debug(...)
| API | 参数 | 返回 |
|---|---|---|
qbdi.newVM() |
— | number |
qbdi.destroyVM(vm) |
number |
boolean |
qbdi.addInstrumentedModuleFromAddr(vm, addr) |
number, AddressLike |
boolean |
qbdi.addInstrumentedRange(vm, start, end) |
number, AddressLike, AddressLike |
boolean |
qbdi.removeInstrumentedRange(vm, start, end) |
number, AddressLike, AddressLike |
boolean |
qbdi.removeAllInstrumentedRanges(vm) |
number |
boolean |
qbdi.allocateVirtualStack(vm, size) |
number, number |
boolean |
qbdi.simulateCall(vm, retAddr, ...args) |
number, AddressLike, ...AddressLike |
boolean |
qbdi.call(vm, target, ...args) |
number, AddressLike, ...AddressLike |
NativePointer | null |
qbdi.run(vm, start, stop) |
number, AddressLike, AddressLike |
boolean |
qbdi.getGPR(vm, reg) |
number, number |
NativePointer |
qbdi.setGPR(vm, reg, value) |
number, number, AddressLike |
boolean |
qbdi.registerTraceCallbacks(vm, target, outDir?) |
number, AddressLike, string? |
boolean |
qbdi.unregisterTraceCallbacks(vm) |
number |
boolean |
qbdi.lastError() |
— | string |
常用寄存器常量:qbdi.REG_RETURN, qbdi.REG_SP, qbdi.REG_LR, qbdi.REG_PC
var vm = qbdi.newVM();
qbdi.addInstrumentedModuleFromAddr(vm, target);
qbdi.allocateVirtualStack(vm, 0x100000);
qbdi.simulateCall(vm, 0, arg0, arg1);
qbdi.registerTraceCallbacks(vm, target);
qbdi.run(vm, target, 0);
var ret = qbdi.getGPR(vm, qbdi.REG_RETURN);
qbdi.unregisterTraceCallbacks(vm);
qbdi.destroyVM(vm);Trace 文件默认输出到 /data/data/<package>/trace_bundle.pb,配合 qbdi-replay + IDA 插件回放。
- 两种 hook 都建议
return ctx.orig()透传返回值 - Native hook 改参数/返回值:
ctx.x0 = value或ctx.orig(newArg0, newArg1),return value覆盖返回值 - Java hook 改参数/返回值:
return ctx.orig(newArgs)改参数,return value改返回值 - Java 字段访问必须用
.value:obj.field返回 FieldWrapper,obj.field.value才是真实值 - Spawn 模式下 Java hook 必须放在
Java.ready(fn)里 Java.setStealth()必须在Java.use().impl之前调用callNative()仅支持整数/指针参数(最多 6 个)
本项目仅供安全研究、逆向工程学习和授权测试用途。使用者应确保在合法授权范围内使用本工具,遵守所在地区的法律法规。作者不对任何滥用、非法使用或由此造成的损失承担责任。使用本项目即表示您同意自行承担所有风险。