Frida-JSAPI
Runtime information
Frida
- Frida.version
包含当前 Frida 版本的属性,类型为字符串。 - Frida.heapSize
动态属性,包含 Frida 私有堆的当前大小,由所有脚本和 Frida 自身的运行时共享。
Process
- Process.id
包含进程 PID 的属性,类型为数字。 - Process.arch
包含架构信息的属性,类型为字符串,可能的值为ia32、x64、arm或arm64。 - Process.platform
包含平台信息的属性,类型为字符串,可能的值为windows、darwin、linux或qnx。 - Process.pageSize
包含虚拟内存页大小的属性(以字节为单位),类型为数字。这个属性用于使你的脚本更具可移植性。 - Process.pointerSize
包含指针大小的属性(以字节为单位),类型为数字。这个属性用于使你的脚本更具可移植性。 - Process.codeSigningPolicy
包含代码签名策略的属性,类型为字符串,可能的值为optional或required。required意味着Frida将避免修改内存中的现有代码,并且不会尝试运行未签名的代码。
目前,除非使用Gadget并配置为假设需要代码签名,否则此属性将始终设置为optional。此属性允许你确定是否可以使用Interceptor API,以及是否可以安全地修改代码或运行未签名的代码。 - Process.mainModule
包含表示进程主可执行文件的模块的属性。 - Process.getCurrentDir()
返回一个字符串,指定当前工作目录的文件系统路径。 - Process.getHomeDir()
返回一个字符串,指定当前用户主目录的文件系统路径。 - Process.getTmpDir()
返回一个字符串,指定用于临时文件的目录的文件系统路径。 - Process.isDebuggerAttached()
返回一个布尔值,指示当前是否有调试器附加。 - Process.getCurrentThreadId()
获取该线程的操作系统特定 ID,类型为数字。 - Process.enumerateThreads()
枚举所有线程,返回一个包含以下属性的对象数组:id: 操作系统特定的 ID。state: 字符串,指定线程状态,可能的值为running(运行中)、stopped(已停止)、waiting(等待中)、uninterruptible(不可中断)或halted(已终止)。context: 包含 pc 和 sp 键的对象,这些键是NativePointer对象,分别指定 EIP/RIP/PC 和 ESP/RSP/SP(用于 ia32/x64/arm)。也可以使用其他处理器特定的键,例如 eax、rax、r0、x0 等。
- Process.findModuleByAddress(address), Process.getModuleByAddress(address), Process.findModuleByName(name), Process.getModuleByName(name)
返回一个与指定地址或名称匹配的模块(Module)。如果找不到这样的模块,以 find 开头的函数返回 null,而以 get 开头的函数则抛出异常。 - Process.enumerateModules()
枚举当前加载的模块,返回一个模块(Module)对象的数组。 - Process.findRangeByAddress(address), getRangeByAddress(address)
返回一个对象,包含指定地址所在内存范围的详细信息。如果找不到这样的范围,findRangeByAddress() 返回 null,而 getRangeByAddress() 则抛出异常。
Thread
- Thread.backtrace([context, backtracer])
为当前线程生成一个调用栈回溯,返回一个 NativePointer 对象的数组。
如果在Interceptor的onEnter或onLeave回调中调用此函数,应为可选的context参数提供this.context,这样可以生成更准确的调用栈回溯。
可选的 backtracer 参数指定要使用的回溯器类型,必须是 Backtracer.FUZZY 或 Backtracer.ACCURATE,如果未指定,默认使用后者。1
console.log(Thread.backtrace(this.context, Backtracer.ACCURATE).map(DebugSymbol.fromAddress).join('\n') + '\n');
- Thread.sleep(delay)
暂停当前线程的执行,暂停时间由 delay 指定,以秒为单位。
Module
属性
name:模块的规范名称,类型为字符串base: 基址,类型为NativePointersize: 模块大小,以字节为单位path: 文件系统中的完整路径,类型为字符串
方法
- enumerateImports()
枚举模块导入表,返回一个包含以下属性的对象数组:type: 字符串,指定为function或variablename: 导入名称,类型为字符串module: 模块名称,类型为字符串address: 绝对地址,类型为NativePointerslot: 存储导入的内存位置,类型为NativePointer
- enumerateExports()
枚举模块导出表,返回一个包含以下属性的对象数组:type: 字符串,指定为function或variablename: 导出名称,类型为字符串address: 绝对地址,类型为NativePointer
- enumerateSymbols()
枚举模块符号表,返回一个包含以下属性的对象数组:isGlobal: 布尔值,指定符号是否是全局可见的type: 字符串,指定以下类型之一:unknown(未知)section(节)undefined(未定义,Mach-O 格式)absolute(绝对地址,Mach-O 格式)prebound-undefined(预绑定未定义,Mach-O 格式)indirect(间接,Mach-O 格式)object(对象,ELF 格式)function(函数,ELF 格式)file(文件,ELF 格式)common(公共,ELF 格式)tls(线程本地存储,ELF 格式)
section: 如果存在,则是一个对象,包含以下属性:id: 字符串,包含节索引、段名称(如果适用)和节名称,格式与 r2 的节 ID 相同protection: 与 Process.enumerateRanges() 中的保护属性相同
name: 符号名称,类型为字符串address: 绝对地址,类型为 NativePointersize: 如果存在,为一个数字,指定符号的大小,以字节为单位
Memory
Memory.alloc(size[, options])
在堆上分配size字节的内存,或者,如果size是Process.pageSize的倍数,则分配由操作系统管理的一个或多个原始内存页。
当使用页面粒度分配时,如果需要将内存分配在接近某个地址的位置,可以通过指定{ near: address, maxDistance: distanceInBytes }作为可选的options对象。
返回值是一个 NativePointer 对象,并且当所有指向该内存的 JavaScript 句柄消失后,底层内存将被释放。
这意味着在代码中使用该指针时,需要保留对它的引用,以防止它在 JavaScript 运行时之外的代码使用时被垃圾回收。Memory.protect(address, size, protection)
更新一块内存区域的保护属性,其中protection是一个字符串。1
Memory.protect(address,size,'rwx');
返回一个布尔值,指示操作是否成功完成。
Memory.allocUtf8String(str), Memory.allocUtf16String(str), Memory.allocAnsiString(str)
在堆上分配、编码并写入 str 作为 UTF-8/UTF-16/ANSI 字符串。
返回的对象是一个 NativePointer。Memory.patchCode(address, size, apply)
安全地修改由 address 指定的内存位置(类型为 NativePointer)处的size大小的字节。提供的 JavaScript 函数 apply 会被调用,并传入一个可写指针,在返回之前,你必须在该指针处写入所需的修改。address:指定要修改的内存区域的起始地址。size:指定要修改的内存区域的大小(通常以字节为单位)。apply:指定要应用的修改内容。1
2
3
4
5
6
7
8Memory.patchCode(address,size,function(code){
var writer =new Arm64Writer(code);
writer.putNop();
console.log(Instruction.parse(add));
console.log(writer.base);
console.log(writer.code);
console.log(writer.pc);
})
DebugSymbol
- DebugSymbol.fromAddress(address), DebugSymbol.fromName(name)
查找地址/名称的调试信息,并将其作为包含以下属性的对象返回:address: 符号对应的地址,作为 NativePointer。name: 符号的名称,作为字符串,如果未知则为 null。moduleName: 拥有这个符号的模块名称,作为字符串,如果未知则为 null。fileName: 拥有这个符号的文件名称,作为字符串,如果未知则为 null。lineNumber: 在 fileName 中的行号,作为数字,如果未知则为 null。
Data Types, Function and Callback
NativePointer
new NativePointer(s)
从包含内存地址的字符串 s 创建一个新的 NativePointer 对象。
字符串可以是十进制的,也可以是以 0x 开头的十六进制表示。
可以使用 ptr(s) 的简写形式来创建。isNull()
返回一个布尔值,方便检查指针是否为 NULL。add(rhs), sub(rhs), and(rhs), or(rhs), xor(rhs)
创建一个新的 NativePointer,其值为当前NativePointer加上/减去/与/或/异或rhs,其中rhs可以是一个数字或另一个NativePointer。shr(n), shl(n)
创建一个新的 NativePointer,其值为当前 NativePointer 向右/向左移位 n 位。not()
创建一个新的 NativePointer,其值为当前 NativePointer 的按位取反。sign([key, data])
通过对当前 NativePointer 的位添加指针认证位,创建一个签名指针。
如果当前进程不支持指针认证,这个操作无效,返回当前 NativePointer 而不是新的值。
可选的 key 参数可以指定为字符串。支持的值包括:ia:IA 密钥,用于签署代码指针。这是默认值。ib:IB 密钥,用于签署代码指针。da:DA 密钥,用于签署数据指针。db:DB 密钥,用于签署数据指针。
data参数也可以指定为NativePointer/数字类值,用于为签名提供额外的数据,默认值为 0。strip([key])
通过移除指针认证位,从当前 NativePointer 创建一个原始指针。如果当前进程不支持指针认证,这个操作无效,返回当前 NativePointer 而不是新的值。可选的 key 参数可以指定用于签名指针的密钥,默认值为 ia。blend(smallInteger)
通过将当前 NativePointer 的位与常量混合,创建一个新的 NativePointer,该常量可作为数据传递给 sign()。equals(rhs)
返回一个布尔值,指示 rhs 是否与当前指针相等,即它们的指针值是否相同。compare(rhs)
返回一个整数比较结果,类似于 String#localeCompare()。toInt32()
将当前 NativePointer 转换为有符号的 32 位整数。toString([radix = 16])
将当前 NativePointer 转换为字符串,使用可选的 radix 进制(默认值为 16)。toMatchPattern()
返回一个字符串,包含当前指针的原始值的 Memory.scan() 兼容匹配模式。readPointer()
从当前内存位置读取一个 NativePointer。
如果地址不可读,将抛出一个 JavaScript 异常。writePointer(ptr)
将 ptr 写入当前内存位置。
如果地址不可写,将抛出一个 JavaScript 异常。readS8(), readU8(), readS16(), readU16(), readS32(), readU32(), readShort(), readUShort(), readInt(), readUInt(), readFloat(), readDouble()
从当前内存位置读取一个有符号或无符号的 8/16/32 位整数,或 float/double 值,并将其作为数字返回。
如果地址不可读,将抛出一个 JavaScript 异常。writeS8(value), writeU8(value), writeS16(value), writeU16(value), writeS32(value), writeU32(value), writeShort(value), writeUShort(value), writeInt(value), writeUInt(value), writeFloat(value), writeDouble(value)
将有符号或无符号的 8/16/32 位整数,或 float/double 值写入当前内存位置。
如果地址不可写,将抛出一个 JavaScript 异常。readS64(), readU64(), readLong(), readULong() 从当前内存位置读取一个有符号或无符号的 64 位或长整型值,并将其作为 Int64/UInt64 值返回。
如果地址不可读,将抛出一个 JavaScript 异常。writeS64(value), writeU64(value), writeLong(value), writeULong(value)
将 Int64/UInt64 值写入当前内存位置。
如果地址不可写,将抛出一个 JavaScript 异常。readByteArray(length)
从当前内存位置读取 length 个字节,并将其返回为 ArrayBuffer。此缓冲区可以通过 send() 的第二个参数有效地传输到基于 Frida 的应用程序中。
如果地址中读取的任何字节不可读,将抛出一个 JavaScript 异常。writeByteArray(bytes)
将 bytes 写入当前内存位置,其中 bytes 可以是 ArrayBuffer(通常由 readByteArray() 返回)或由 0 到 255 之间的整数组成的数组。例如:[ 0x13, 0x37, 0x42 ]。
如果地址中写入的任何字节不可写,将抛出一个 JavaScript 异常。readCString([size = -1]), readUtf8String([size = -1]), readUtf16String([length = -1]), readAnsiString([size = -1])
从当前内存位置读取 ASCII、UTF-8、UTF-16 或 ANSI 字符串的字节。如果知道字符串的字节数,可以提供可选的 size 参数;如果字符串以 NULL 结尾,可以省略该参数或指定为 -1。同样,如果知道字符串的字符数,可以提供可选的 length 参数。
如果地址中读取的任何 size / length 字节不可读,将抛出一个 JavaScript 异常。
注意:readAnsiString() 仅在 Windows 上可用(且相关)。writeUtf8String(str), writeUtf16String(str), writeAnsiString(str)
将 JavaScript 字符串编码并写入当前内存位置(带有 NULL 终止符)。
如果地址中写入的任何字节不可写,将抛出一个 JavaScript 异常。
注意:writeAnsiString() 仅在 Windows 上可用(且相关)。
NativeFunction
- new NativeFunction(address, returnType, argTypes[, abi])
创建一个新的 NativeFunction,以调用位于指定地址(由 NativePointer 指定)的函数。address:指定地址returnType:指定返回类型argTypes[, abi]:argTypes数组指定参数类型。
还可以选择性地指定abi,如果没有指定,则使用系统默认的abi。
对于可变参数函数,在固定参数和可变参数之间添加一个...条目到 argTypes 中。
- Structs & Classes by Value(按值传递的结构体和类)
对于按值传递的结构体或类,不是提供一个字符串,而是提供一个包含结构体字段类型的数组,这些字段类型是按顺序排列的。你可以按需要深度嵌套这些数组,以表示嵌套结构体。注意,返回的对象也是一个 NativePointer,因此可以传递给 Interceptor#attach。
这必须与结构体/类完全匹配,所以如果你有一个包含三个 int 的结构体,你必须传递 [‘int’, ‘int’, ‘int’]。
对于具有虚方法的类,第一个字段将是指向虚函数表(vtable)的指针。
对于 C++ 中涉及返回值大于 Process.pointerSize 的情况,典型的 ABI 可能会要求传入一个指向预分配空间的 NativePointer 作为第一个参数。(这种情况在 WebKit 中很常见。) - Supported Types
- void
- pointer
- int
- uint
- long
- ulong
- char
- uchar
- size_t
- ssize_t
- float
- double
- int8
- uint8
- int16
- uint16
- int32
- uint32
- int64
- uint64
- bool
- Supported ABIs
- default
- Windows 32-bit:
sysv
stdcall
thiscall
fastcall
mscdecl - Windows 64-bit:
win64 - UNIX x86:
sysv
unix64 - UNIX ARM:
sysv
vfp
- new NativeFunction(address, returnType, argTypes[, options])
类似于之前的构造函数,但是第四个参数options是一个对象,可以包含以下一个或多个键:abi:与上面相同的枚举类型。
scheduling:调度行为,作为字符串指定。支持的值有:
- cooperative:在调用本地函数时允许其他线程执行 JavaScript 代码,即在调用前释放锁,并在调用后重新获取锁。这是默认行为。
- exclusive:在调用本地函数时不允许其他线程执行 JavaScript 代码,即保持持有 JavaScript 锁。这种方式更快,但可能导致死锁。
exceptions:异常行为,作为字符串指定。支持的值有:
- steal:如果被调用的函数产生了本地异常(例如,通过解引用无效指针),Frida 将展开堆栈并捕获异常,将其转化为可以处理的 JavaScript 异常。这可能会导致应用程序处于未定义的状态,但在实验时避免进程崩溃非常有用。这是默认行为。
- propagate:让应用程序处理在函数调用期间发生的任何本地异常。(或者由 Process.setExceptionHandler() 安装的处理程序来处理。)
traps:需要启用的代码陷阱,作为字符串指定。支持的值有:
- default:如果函数调用触发了任何钩子,Interceptor.attach() 回调将会被调用。
- all:除了 Interceptor 回调外,在每个函数调用期间还可以临时重新激活 Stalker。这对例如测量代码覆盖率、指导模糊测试、在调试器中实现“步入”等功能非常有用。请注意,当使用 Java 和 ObjC API 时也可以实现此功能,因为方法包装器也提供 clone(options) API,用于创建具有自定义 NativeFunction 选项的新方法包装器。
NativeCallback
new NativeCallback(func, returnType, argTypes[, abi])
func: 创建一个新的由JavaScript function实现的NativeCallbackreturnType: 指定返回类型。argTypes: 数组,指定参数类型。abi: 指定abi
返回的对象是
NativeFunction,因此可以传递给Interceptor.replace。
Instrumentation
通过动态插入代码来监控和收集程序的运行信息。
Interceptor
- Interceptor.attach(target, callbacks[, data])
拦截对target函数的调用。target
target是一个NativePointer,用于指定你想要拦截的函数的地址。
在32位ARM架构中,该地址的最低有效位必须为0(对于ARM函数)或1(对于Thumb函数)。如果通过Frida API(例如Module.getExportByName())获取地址,Frida会自动处理这个细节。callbacks
callbacks
参数是一个包含一个或多个回调函数的对象。- onEnter(args)
回调函数,接收一个参数args,它是一个NativePointer对象数组,可以用它来读取或写入函数的参数。 - onLeave(retval)
回调函数,接收一个参数retval,它是一个基于NativePointer的对象,包含了函数的原始返回值。
你可以调用retval.replace(1337)将返回值替换为整数1337,或者调用retval.replace(ptr(“0x1234”))将返回值替换为一个指针。
- onEnter(args)
- 该对象包含一些属性
returnAddress:
返回地址,类型为NativePointer。context:
一个对象,包含键pc和sp,它们是NativePointer对象,分别指定ia32/x64/arm架构下的EIP/RIP/PC和ESP/RSP/SP。errno:
(UNIX系统中)当前的errno值(你可以替换它)。lastError:
(Windows系统中)当前的操作系统错误值(你可以替换它)。threadId:
操作系统的线程ID。depth:
相对于其他调用的调用深度。
- Interceptor.replace(target, replacement[, data])
使用 replacement 替换 target 处的函数。
可以使用 NativeCallback 在 JavaScript 中实现替换。
Java
Java.perform(fn)
确保当前线程已连接到虚拟机并调用函数fn。Java.use(className)
动态获取一个 JavaScript 包装器(wrapper),用于指定的类名(className)。
通过调用 $new() 可以从这个包装器中实例化对象,以调用构造函数。
通过调用 $dispose() 可以显式地清理一个实例(或者可以等待 JavaScript 对象被垃圾回收,或者等待脚本被卸载)。
可以使用静态和非静态方法,甚至可以替换方法的实现,并从中抛出异常。1
2
3
4
5
6
7
8
9
10
11
12// 被动调用
setImmediate(function (){
Java.perform(function (){
var Class = Java.use("className");
Class.method.implementation=function (args){
console.log("args:",args);
var result = this.method(args);
console.log("result:",result);
return result;
}
})
})1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18// 主动调用
setImmediate(function (){
Java.perform(function (){
var Class = Java.use("className");
var result = Class.method(args);
console.log("result:",result);
})
})
// 主动调用实例方法
setImmediate(function (){
Java.perform(function (){
var Class = Java.use("className");
var ClassObj = Class.$new();
var result = ClassObj.method(args);
console.log("result:",result);
})
})Java.choose(className, callbacks)
通过扫描 Java 堆枚举指定类名(className)的活动实例。callbacks是一个指定回调函数的对象:- onMatch(instance):
当找到一个活动实例时调用,该实例已经准备好使用,就像你通过 Java.cast() 将原始句柄转换为该特定实例一样。 - onComplete():
当所有实例都被枚举完成后调用。1
2
3
4
5
6
7
8
9
10
11
12
13setImmediate(function (){
Java.perform(function (){
Java.choose("ClassName",{
onMatch:function (obj){
console.log(obj.method());
},
onComplete:function (){
console.log("over!")
}
})
})
})
- onMatch(instance):
CPU Instruction
Instruction
- Instruction.parse(target)
解析内存中 target 地址处的指令。
返回的对象具有的字段:address: 此指令的地址(EIP),类型为NativePointernext: 指向下一条指令的指针,您可以使用parse()解析它size: 此指令的大小mnemonic: 指令助记符的字符串表示opStr: 指令操作数的字符串表示operands: 描述每个操作数的对象数组,每个对象至少指定类型和值,但可能还包含取决于架构的其他属性regsRead: 此指令隐式读取的寄存器名称数组regsWritten: 此指令隐式写入的寄存器名称数组groups: 此指令所属的组名称数组toString(): 转换为人类可读的字符串
Arm64Writer
创建一个新的代码写入器,用于生成直接写入内存的 AArch64 机器代码
构造函数
new Arm64Writer(codeAddress[, { pc: ptr(‘0x1234’) }]`
- codeAddress: 代码将被写入的内存地址,指定为 NativePointer 类型。
- pc (可选): 初始程序计数器,当在临时缓冲区中生成代码时非常有用。
方法
- reset(codeAddress[, { pc: ptr(‘0x1234’) }])
回收实例,重新初始化Arm64Writer实例。 - dispose()
立即清理内存。 - flush()
解析标签引用并将待处理的数据写入内存。在生成代码完成后,应始终调用此方法。在一次生成多个函数时,通常也需要在不相关的代码片段之间调用此方法。
属性
- base
输出的第一个字节的内存位置,作为 NativePointer 类型。 - code
下一个字节的输出内存位置,作为 NativePointer 类型。 - pc
下一个字节的输出程序计数器,作为 NativePointer 类型。 - offset
当前偏移量,作为 JavaScript 数字类型。
代码生成方法
- skip(nBytes)
跳过指定字节数。 - putRet()
生成一个 RET 指令。 - putNop()
生成一个NOP指令
Hexdump
- hexdump(target[, options])
从提供的 ArrayBuffer 或 NativePointer 目标生成十六进制转储,可选择自定义输出选项。target:
提供的NativePointer对象options:1
2
3
4
5
6
7const libc = Module.findBaseAddress('libc.so');
console.log(hexdump(libc, {
offset: 0, //偏移
length: 64, //输出长度,必须是十进制
header: true, //头
ansi: true //颜色
}));