本文档只说明 rust_frida 里可直接使用的 JavaScript API。当前实现面向 Android + ARM64,JS 运行时为内嵌 QuickJS。
拉取git大文件
git lfs install
git lfs pull- JS 全局对象新增
qbdi- 用于在设备侧直接创建 QBDI VM、配置插桩范围、模拟调用、执行
run()和读取寄存器返回值。 - qbdi.registerTraceCallbacks(vm,addr), 配合qbdi-replay项目 + IDA插件,实现IDA trace回放,默认trace打包位置:/data/data/com.xxx/trace_bundle.pb,支持外部匿名段
- 用于在设备侧直接创建 QBDI VM、配置插桩范围、模拟调用、执行
注入成功后,在 rust_frida 的交互界面里可用下面几个命令:
jsinit- 初始化 QuickJS 引擎。
loadjs <script>- 执行一段 JS 代码。
jseval <expr>- 求值一个 JS 表达式并打印结果。
jsrepl- 进入交互式 JS REPL,支持 Tab 补全。
jsclean- 清理 QuickJS 引擎和已安装的 JS hook。
常见用法:
jsinit
jseval Module.findBaseAddress("libart.so").toString()
loadjs console.log("hello from quickjs")
jsrepl
如果是 spawn 模式,涉及应用类的 Java hook 建议放在 Java.ready(() => { ... }) 里执行。
初始化后,JS 全局可直接使用这些对象/函数:
consoleptr()MemoryModulehook()unhook()callNative()qbdiJavaJni
| 类型名 | 实际含义 |
|---|---|
AddressLike |
NativePointer | number | bigint | "0x..." |
NativePointer |
ptr() 创建出来的指针对象 |
JsNumber |
JS number |
JsBigInt |
JS bigint |
JavaObjectProxy |
Java.use() / Java hook 中返回的 Java 对象代理 |
ArrayBuffer |
Memory.readByteArray() 返回的字节数组 |
补充说明:
- 地址参数基本都可以写成
AddressLike - 64 位值在 JS 里可能表现为
number或bigint,取决于实现是否需要避免精度丢失 NativePointer.toString()/JSON.stringify()时会输出十六进制字符串
| API | 参数类型 | 返回类型 |
|---|---|---|
console.log(...args) |
any[] |
undefined |
console.info(...args) |
any[] |
undefined |
console.warn(...args) |
any[] |
undefined |
console.error(...args) |
any[] |
undefined |
console.debug(...args) |
any[] |
undefined |
| API | 参数类型 | 返回类型 |
|---|---|---|
ptr(value) |
number | bigint | string | NativePointer |
NativePointer |
p.add(offset) |
number | bigint | "0x..." | NativePointer |
NativePointer |
p.sub(offset) |
number | bigint | "0x..." | NativePointer |
NativePointer |
p.toString() |
无 | string |
p.toJSON() |
无 | string |
p.toNumber() |
无 | bigint |
p.toInt() |
无 | bigint |
| 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 |
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 | number |
undefined |
Memory.writePointer(addr, value) |
AddressLike, AddressLike |
undefined |
| API | 参数类型 | 返回类型 |
|---|---|---|
Module.findExportByName(moduleName, symbolName) |
string | null, string |
NativePointer | null |
Module.findBaseAddress(moduleName) |
string |
NativePointer | null |
Module.findByAddress(addr) |
AddressLike |
ModuleInfo | null |
Module.enumerateModules() |
无 | ModuleInfo[] |
| API | 参数类型 | 返回类型 |
|---|---|---|
hook(target, callback, stealth?) |
AddressLike, (ctx: NativeHookContext) => any, boolean? |
boolean |
unhook(target) |
AddressLike |
boolean |
callNative(func, ...args) |
AddressLike, up to 6 x (number | bigint | NativePointer) |
number | bigint |
| 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.clearVirtualStacks(vm) |
number |
boolean |
qbdi.simulateCall(vm, returnAddr, ...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.getErrno(vm) |
number |
number |
qbdi.setErrno(vm, value) |
number, number |
boolean |
qbdi.recordMemoryAccess(vm, accessType) |
number, number |
boolean |
qbdi.registerTraceCallbacks(vm, target, outputDir?) |
number, AddressLike, string? |
boolean |
qbdi.unregisterTraceCallbacks(vm) |
number |
boolean |
qbdi.lastError() |
无 | string |
qbdi.shutdown() |
无 | undefined |
补充说明:
vm是qbdi.newVM()返回的句柄,不是 JS 对象。- 常用寄存器常量包括:
qbdi.REG_RETURNqbdi.REG_BPqbdi.REG_LRqbdi.REG_SPqbdi.REG_FLAGqbdi.REG_PC
- 常见最小流程:
var vm = qbdi.newVM();
qbdi.addInstrumentedModuleFromAddr(vm, target);
qbdi.allocateVirtualStack(vm, 0x100000);
qbdi.simulateCall(vm, 0, arg0, arg1);
qbdi.run(vm, target, 0);
var ret = qbdi.getGPR(vm, qbdi.REG_RETURN);
qbdi.destroyVM(vm);| API | 参数类型 | 返回类型 |
|---|---|---|
Java.use(className) |
string |
JavaClassWrapper |
Class.$new(...args) |
任意,运行时按重载解析 | JavaObjectProxy |
Class.method(...args) |
任意,运行时按重载解析 | any |
Class.method.overload(...types) |
string... 或 string[]... |
MethodWrapper |
Class.method.impl = fn |
(ctx: JavaHookContext) => any |
setter,无返回值 |
Class.method.impl = null |
null |
setter,无返回值 |
Java.ready(fn) |
() => void |
void |
Java.deopt() |
无 | boolean |
Java.setStealth(enabled) |
boolean |
boolean |
Java.getStealth() |
无 | boolean |
Java.getField(objPtr, className, fieldName, fieldSig) |
AddressLike, string, string, string |
any |
| API | 参数类型 | 返回类型 |
|---|---|---|
Jni.addr(name) |
string |
NativePointer |
Jni.addr(env, name) |
AddressLike, string |
NativePointer |
Jni.find(name) |
string |
JniEntry |
Jni.find(env, name) |
AddressLike, string |
JniEntry |
Jni.entries() |
无 | JniEntry[] |
Jni.entries(env) |
AddressLike |
JniEntry[] |
Jni.table |
无 | Record<string, JniEntry> |
Jni.FindClass |
无 | NativePointer |
Jni.helper.env.ptr |
无 | NativePointer |
Jni.helper.env.getObjectClass(obj) |
AddressLike |
NativePointer |
Jni.helper.env.getSuperclass(clazz) |
AddressLike |
NativePointer |
Jni.helper.env.isSameObject(a, b) |
AddressLike, AddressLike |
boolean |
Jni.helper.env.isInstanceOf(obj, clazz) |
AddressLike, AddressLike |
boolean |
Jni.helper.env.exceptionCheck() |
无 | boolean |
Jni.helper.env.exceptionOccurred() |
无 | NativePointer |
Jni.helper.env.exceptionClear() |
无 | boolean |
Jni.helper.env.readJString(jstr) |
AddressLike |
string | null |
Jni.helper.env.getClassName(clazz) |
AddressLike |
string | null |
Jni.helper.env.getObjectClassName(obj) |
AddressLike |
string | null |
Jni.helper.structs.JNINativeMethod.read(addr) |
AddressLike |
JNINativeMethodInfo |
Jni.helper.structs.JNINativeMethod.readArray(addr, count) |
AddressLike, number |
JNINativeMethodInfo[] |
Jni.helper.structs.jvalue.read(addr, jniType) |
AddressLike, string |
any |
Jni.helper.structs.jvalue.readArray(addr, typesOrSig) |
AddressLike, string | string[] |
any[] |
type ModuleInfo = {
name: string
base: NativePointer
size: number
path: string
}type NativeHookContext = {
x0: number | bigint
x1: number | bigint
x2: number | bigint
x3: number | bigint
x4: number | bigint
x5: number | bigint
x6: number | bigint
x7: number | bigint
x8: number | bigint
x9: number | bigint
x10: number | bigint
x11: number | bigint
x12: number | bigint
x13: number | bigint
x14: number | bigint
x15: number | bigint
x16: number | bigint
x17: number | bigint
x18: number | bigint
x19: number | bigint
x20: number | bigint
x21: number | bigint
x22: number | bigint
x23: number | bigint
x24: number | bigint
x25: number | bigint
x26: number | bigint
x27: number | bigint
x28: number | bigint
x29: number | bigint
x30: number | bigint
sp: number | bigint
pc: number | bigint
trampoline: number | bigint
orig(): number | bigint
}说明:
- native hook 改返回值时,修改的是
ctx.x0 ctx.orig()会调用原函数并同步写回ctx.x0
type JavaHookContext = {
thisObj?: JavaObjectProxy
args: any[]
env: number | bigint
orig(...args: any[]): any
}说明:
thisObj仅实例方法存在,静态方法没有- Java hook 的
return值就是方法返回值 ctx.orig()不传参时使用原始参数;传参时用新参数调用原方法
type JniEntry = {
name: string
index: number
address: NativePointer
}type JNINativeMethodInfo = {
address: NativePointer
namePtr: NativePointer
sigPtr: NativePointer
fnPtr: NativePointer
name: string | null
sig: string | null
}支持:
console.log(...)
console.info(...)
console.warn(...)
console.error(...)
console.debug(...)示例:
console.log("base =", Module.findBaseAddress("libart.so"))ptr(value) 支持:
- 数字
BigInt- 十六进制字符串,如
"0x7f12345678" - 已经是
NativePointer的对象
示例:
var p = ptr("0x7f12345678")
console.log(p.toString()) // 0x7f12345678p.add(offset)p.sub(offset)p.toString()p.toJSON()p.toNumber()p.toInt()
说明:
offset可以是数字、BigInt、0x...字符串或另一个NativePointertoNumber()/toInt()返回BigInt
示例:
var base = Module.findBaseAddress("libart.so")
var target = base.add(0x1234)
console.log(target.toString())Memory.readU8(ptr)Memory.readU16(ptr)Memory.readU32(ptr)Memory.readU64(ptr)Memory.readPointer(ptr)Memory.readCString(ptr)Memory.readUtf8String(ptr)Memory.readByteArray(ptr, length)
说明:
- 无效地址会抛
RangeError,不会直接崩进程 readCString()最多读取4096字节readByteArray()返回ArrayBufferreadPointer()返回NativePointerreadU32()/readU64()返回BigInt
示例:
var sym = Module.findExportByName("libc.so", "dlopen")
console.log(Memory.readPointer(sym).toString())Memory.writeU8(ptr, value)Memory.writeU16(ptr, value)Memory.writeU32(ptr, value)Memory.writeU64(ptr, value)Memory.writePointer(ptr, value)
说明:
- 写入前会检查地址可访问性
- 必要时内部会临时尝试修改页面权限
- 成功时返回
undefined
示例:
var p = ptr("0x12345678")
Memory.writeU32(p, 0x90909090)支持:
Module.findExportByName(moduleName, symbolName)Module.findBaseAddress(moduleName)Module.findByAddress(address)Module.enumerateModules()
返回值:
findExportByName()/findBaseAddress()找不到时返回nullfindByAddress()找不到时返回nullenumerateModules()返回:
[
{
name: "libart.so",
base: ptr("0x7f..."),
size: 123456,
path: "/apex/..."
}
]示例:
var art = Module.findBaseAddress("libart.so")
var info = Module.findByAddress(art)
console.log(JSON.stringify(info))hook(target, callback[, stealth])unhook(target)
参数说明:
target必须是地址,可传NativePointer、数字、BigInt或十六进制字符串callback必须是 JS 函数stealth是可选布尔值,true时优先使用 stealth 模式安装 inline hook
示例:
var openPtr = Module.findExportByName("libc.so", "open")
hook(openPtr, function(ctx) {
console.log("open called, x0 =", ptr(ctx.x0).toString())
ctx.orig()
})native hook 的 ctx 里可用:
ctx.x0~ctx.x30ctx.spctx.pcctx.trampolinectx.orig()
关键行为:
ctx.orig()会调用原函数,并把返回值写回ctx.x0- native hook 的 JS
return值本身不会作为返回值使用 - 如果你想改返回值,要显式修改
ctx.x0
改返回值示例:
hook(Module.findExportByName("libc.so", "getpid"), function(ctx) {
ctx.orig()
ctx.x0 = 12345
})签名:
callNative(funcPtr, arg0?, arg1?, arg2?, arg3?, arg4?, arg5?)说明:
- 最多传 6 个参数,按
ARM64 x0 ~ x5传入 - 适合整数/指针参数
- 返回值会自动转成
number或BigInt
示例:
var getpidPtr = Module.findExportByName("libc.so", "getpid")
console.log(callNative(getpidPtr))Java 提供的是偏 Frida 风格的接口,但实现细节以当前仓库代码为准。
var Activity = Java.use("android.app.Activity")类包装对象支持:
Class.$new(...args)创建对象Class.method(...args)调用静态方法Class.method.overload(...)指定重载Class.method.impl = function(ctx) { ... }安装 hookClass.method.impl = null卸载 hook
实例对象支持:
obj.fieldName直接读字段obj.method(...args)调实例方法obj.$call(name, sig, ...args)按显式签名调用obj.$className
示例:
var Process = Java.use("android.os.Process")
console.log(Process.myPid())
var JString = Java.use("java.lang.String")
var s = JString.$new("hello")
console.log(s.length())支持三种写法:
Activity.onResume.overload("int")
Activity.onResume.overload("(I)V")
SomeClass.foo.overload(["int", "java.lang.String"], ["long"])说明:
- 传 Java 类型名时,会自动转 JNI 签名
- 传 JNI 签名时必须是
"(...)..."形式 - 传多个数组时,可一次选择多个 overload 进行 hook
Java hook 的回调上下文 ctx 里常用字段:
ctx.thisObjctx.argsctx.envctx.orig()
关键行为:
- Java hook 的
return值会作为方法返回值 ctx.orig()可调用原方法ctx.thisObj和ctx.args里的 Java 对象会自动包装成可直接访问的代理对象
示例:
var Activity = Java.use("android.app.Activity")
Activity.onResume.impl = function(ctx) {
console.log("Activity.onResume:", ctx.thisObj.$className)
return ctx.orig()
}修改参数再调用原方法:
var JString = Java.use("java.lang.String")
var Demo = Java.use("com.example.demo.MainActivity")
Demo.test.overload("java.lang.String").impl = function(ctx) {
return ctx.orig(JString.$new("patched"))
}spawn 模式下注入过早时,应用 ClassLoader 还没准备好。此时建议:
Java.ready(function() {
var Main = Java.use("com.example.app.MainActivity")
Main.onResume.impl = function(ctx) {
console.log("ready hook hit")
return ctx.orig()
}
})如果已经是 attach 到运行中的进程,Java.ready(fn) 会立即执行。
Java.deopt()- 尝试清空 JIT 缓存。
Java.setStealth(true | false)- 设置 Java hook 使用 stealth 模式。
Java.getStealth()- 查看当前 stealth 开关。
Java.getField(objPtr, className, fieldName, fieldSig)- 通过原始对象指针读字段。
Java.getField() 示例:
var objPtr = ptr("0x12345678")
var value = Java.getField(objPtr, "com.example.Test", "mCount", "I")
console.log(value)Jni 主要用于定位 JNI 函数地址,以及辅助解析 JNIEnv、jvalue、JNINativeMethod。
下面几种写法都可以:
Jni.FindClass
Jni.addr("FindClass")
Jni.find("FindClass")
Jni.table.FindClass如果需要指定某个 JNIEnv*:
Jni.addr(envPtr, "FindClass")
Jni.find(envPtr, "FindClass")
Jni.entries(envPtr)说明:
Jni.FindClass/Jni.RegisterNatives这类属性值是函数地址Jni.find(...)返回{ name, index, address }Jni.entries(...)返回整个 JNI 表数组Jni.table返回按名字索引的整张表
常用辅助:
Jni.helper.pointerSizeJni.helper.env.ptrJni.helper.env.getObjectClass(obj)Jni.helper.env.getSuperclass(clazz)Jni.helper.env.isSameObject(a, b)Jni.helper.env.isInstanceOf(obj, clazz)Jni.helper.env.exceptionCheck()Jni.helper.env.exceptionOccurred()Jni.helper.env.exceptionClear()Jni.helper.env.readJString(jstr)Jni.helper.env.getClassName(clazz)Jni.helper.env.getObjectClassName(obj)
结构体辅助:
Jni.helper.structs.JNINativeMethod.read(ptr)Jni.helper.structs.JNINativeMethod.readArray(ptr, count)Jni.helper.structs.jvalue.read(ptr, jniType)Jni.helper.structs.jvalue.readArray(ptr, typesOrSig)
示例:读取 RegisterNatives 的方法表
hook(Jni.addr("RegisterNatives"), function(ctx) {
var methods = Jni.helper.structs.JNINativeMethod.readArray(
ptr(ctx.x2),
Number(ctx.x3)
)
console.log(JSON.stringify(methods))
ctx.orig()
})示例:观察 FindClass
hook(Jni.addr("FindClass"), function(ctx) {
console.log("FindClass:", Memory.readCString(ptr(ctx.x1)))
ctx.orig()
})var dlopenPtr = Module.findExportByName("libdl.so", "dlopen")
hook(dlopenPtr, function(ctx) {
console.log("dlopen:", Memory.readCString(ptr(ctx.x0)))
ctx.orig()
})Java.ready(function() {
var Activity = Java.use("android.app.Activity")
Activity.onResume.impl = function(ctx) {
console.log("onResume hit")
return ctx.orig()
}
})var getpidPtr = Module.findExportByName("libc.so", "getpid")
console.log("pid =", callNative(getpidPtr))- native hook 和 Java hook 的返回语义不同:
- native hook 需要改
ctx.x0 - Java hook 直接
return即可
- native hook 需要改
callNative()当前主要面向整数/指针参数,不适合复杂 ABI 封送Java.ready()主要解决 spawn 场景下 appClassLoader尚未就绪的问题- 需要稳定 hook Java 层时,优先先
Java.ready(...)再Java.use(...)