文章来源于网络收集而来,版权归原创者所有,如有侵权请及时联系!
JavaScript API 2
Int64
- new Int64(v): 以 v 为参数,创建一个 Int64 对象,v 可以是一个数值,也可以是一个字符串形式的数值表示,也可以使用 Int64(v) 这种简单的方式。
- add(rhs), sub(rhs), and(rhs), or(rhs), xor(rhs): Int64 相关的加减乘除。
- shr(n), shl(n): Int64 相关的左移、右移操作
- compare(rhs): Int64 的比较操作,有点类似 String.localCompare()
- toNumber(): 把 Int64 转换成一个实数
- toString([radix = 10]): 按照一定的数值进制把 Int64 转成字符串,默认是十进制
UInt64
- 可以直接参考 Int64
NativePointer
- 可以直接参考 Int64
NativeFunction
- new NativeFunction(address, returnType, argTypes[, abi]): 在 address(使用 NativePointer 的格式) 地址上创建一个 NativeFunction 对象来进行函数调用, returnType 指定函数返回类型, argTypes 指定函数的参数类型,如果不是系统默认类型,也可以选择性的指定 abi 参数,对于可变类型的函数,在固定参数之后使用 "..." 来表示。
- 类和结构体
- 在函数调用的过程中,类和结构体是按值传递的,传递的方式是使用一个数组来分别指定类和结构体的各个字段,理论上为了和需要的数组对应起来,这个数组是可以支持无限嵌套的,结构体和类构造完成之后,使用 NativePointer 的形式返回的,因此也可以传递给 Interceptor.attach() 调用。
- 需要注意的点是, 传递的数组一定要和需要的参数结构体严格吻合,比如一个函数的参数是一个 3 个整形的结构体,那参数传递的时候一定要是 ['int', 'int', 'int'] ,对于一个拥有虚函数的类来说,调用的时候,第一个参数一定是虚表指针。
- Supported Types
- void
- pointer
- int
- uint
- long
- ulong
- char
- uchar
- float
- double
- int8
- uint8
- int16
- uint16
- int32
- uint32
- int64
- uint64
- Supported ABIs
- default
- Windows 32-bit:
- sysv
- stdcall
- thiscall
- fastcall
- mscdecl
- Windows 64-bit:
- win64
- UNIX x86:
- sysv
- unix64
- UNIX ARM:
- sysv
- vfp
NativeCallback
- new NativeCallback(func, returnType, argTypes[, abi]): 使用 JavaScript 函数 func 来创建一个 Native 函数,其中 returnType 和 argTypes 分别指定函数的返回类型和参数类型数组。如果不想使用系统默认的 abi 类型,则可以指定 abi 这个参数。关于 argTypes 和 abi 类型,可以查看 NativeFunction 来了解详细信息,这个对象的返回类型也是 NativePointer 类型,因此可以作为 Interceptor.replace 的参数使用。
SystemFunction
- new SystemFunction(address, returnType, argTypes[, abi]): 功能基本和 NativeFunction 一致,但是使用这个对象可以获取到调用线程的 last error 状态,返回值是对平台相关的数值的一层封装,为 value 对象,比如是对这两个值的封装, errno (UNIX) 或者 lastError (Windows)。
Socket
SocketListener
SocketConnection
IOStream
InputStream
OutputStream
UnixInputStream
UnixOutputStream
Win32InputStream
Win32OutputStream
File
SqliteDatabase
SqliteStatement
Interceptor
- Interceptor.attach(target, callbacks): 在 target 指定的位置进行函数调用拦截,target 是一个 NativePointer 参数,用来指定你想要拦截的函数的地址,有一点需要注意,在 32 位 ARM 机型上,ARM 函数地址末位一定是 0(2 字节对齐),Thumb 函数地址末位一定 1(单字节对齐),如果使用的函数地址是用 Frida API 获取的话, 那么 API 内部会自动处理这个细节(比如:Module.findExportByName())。其中 callbacks 参数是一个对象,大致结构如下:
- onEnter: function(args): 被拦截函数调用之前回调,其中原始函数的参数使用 args 数组(NativePointer 对象数组)来表示,可以在这里修改函数的调用参数。
- onLeave: function(retval): 被拦截函数调用之后回调,其中 retval 表示原始函数的返回值,retval 是从 NativePointer 继承来的,是对原始返回值的一个封装,你可以使用 retval.replace(1337) 调用来修改返回值的内容。需要注意的一点是,retval 对象只在 onLeave 函数作用域范围内有效,因此如果你要保存这个对象以备后续使用的话,一定要使用深拷贝来保存对象,比如:ptr(retval.toString())。
- 事实上 Frida 可以在代码的任意位置进行拦截,但是这样一来 callbacks 回调的时候,因为回调位置有可能不在函数的开头,这样 onEnter 这样的回调参数 Frida 只能尽量的保证(比如拦截的位置前面的代码没有修改过传入的参数),不能像在函数头那样可以确保正确。
- 拦截器的 attach 调用返回一个监听对象,后续取消拦截的时候,可以作为 Interceptor.detach() 的参数使用。
- 还有一个比较方便的地方,那就是在回调函数里面,包含了一个隐藏的 this 的线程 tls 对象,方便在回调函数中存储变量,比如可以在 onEnter 中保存值,然后在 onLeave 中使用,看一个例子:
- 另外, this 对象还包含了一些额外的比较有用的属性:
- returnAddress: 返回 NativePointer 类型的 address 对象
- context: 包含 pc,sp,以及相关寄存器比如 eax, ebx 等,可以在回调函数中直接修改
- errno: (UNIX)当前线程的错误值
- lastError: (Windows) 当前线程的错误值
- threadId: 操作系统线程 Id
- depth: 函数调用层次深度
- 看个例子:
- Interceptor.detachAll(): 取消之前所有的拦截调用
- Interceptor.replace(target, replacement): 函数实现代码替换,这种情况主要是你想要完全替换掉一个原有函数的实现的时候来使用,注意 replacement 参数使用 JavaScript 形式的一个 NativeCallback 来实现,后续如果想要取消这个替换效果,可以使用 Interceptor.revert 调用来实现,如果你还想在你自己的替换函数里面继续调用原始的函数,可以使用以 target 为参数的 NativeFunction 对象来调用,来看一个例子:
- Interceptor.revert(target): 还原函数的原始实现逻辑,即取消前面的 Interceptor.replace 调用
- Interceptor.flush(): 确保之前的内存修改操作都执行完毕,并切已经在内存中发生作用,只要少数几种情况需要这个调用,比如你刚执行了 attach() 或者 replace() 调用,然后接着想要使用 NativeFunction 对象对函数进行调用,这种情况就需要调用 flush。正常情况下,缓存的调用操作会在当前线程即将离开 JavaScript 运行时环境或者调用 send() 的时候自动进行 flush 操作,也包括那些底层会调用 send() 操作的函数,比如 RPC 函数,或者任何的 console API
Stalker
- Stalker.follow([threadId, options]): 开始监视线程 ID 为 threadId (如果是本线程,可以省略)的线程事件,举个例子:
- Stalker.unfollow([threadId]): 停止监控线程事件,如果是当前线程,则可以省略 threadId 参数
- Stalker.parse(events[, options]): 按照指定格式介些 GumEvent 二进制数据块,按照 options 的要求格式化输出,举个例子:
- Stalker.garbageCollect(): 在调用 Stalker.unfollow() 之后,在一个合适的时候,释放对应的内存,可以避免多线程竞态条件下的内存释放问题。
- Stalker.addCallProbe(address, callback): 当 address 地址处的函数被调用的时候,调用 callback 对象(对象类型和 Interceptor.attach.onEnter 一致),返回一个 Id,可以给后面的 Stalker.removeCallProbe 使用
- Stalker.removeCallProbe(): 移除前面的 addCallProbe 调用效果。
- Stalker.trustThreshold: 指定一个整型 x,表示可以确保一段代码在执行 x 次之后,代码才可以认为是可靠的稳定的, -1 表示不信任,0 表示持续信任,N 表示执行 N 次之后才是可靠的,稳定的,默认值是 1。
- Stalker.queueCapacity: 指定事件队列的长度,默认长度是 16384
- Stalker.queueDrainInterval: 事件队列查询派发时间间隔,默认是 250ms,也就是说 1 秒钟事件队列会轮询 4 次
ApiResolver
- new ApiResolver(type): 创建指定类型 type 的 API 查找器,可以根据函数名称快速定位到函数地址,根据当前进程环境不同,可用的 ApiResolver 类型也不同,到目前为止,可用的类型有:
- Module: 枚举当前进程中已经加载的动态链接库的导入导出函数名称。
- objc: 定位已经加载进来的 Object-C 类方法,在 macOS 和 iOS 进程中可用,可以使用 Objc.available 来进行运行时判断,或者在 try-catch 块中使用 new ApiResolver('objc') 来尝试创建。
- 解析器在创建的时候,会加载最小的数据,后续使用懒加载的方式来持续加载剩余的数据,因此最好是一次相关的批量调用使用同一个 resolver 对象,然后下次的相关操作,重新创建一个 resolver 对象,避免使用上个 resolver 的老数据。
- enumerateMatches(query, callbacks): 执行函数查找过程,按照参数 query 来查找,查找结果调用 callbacks 来回调通知:
- onMatch: function(match): 每次枚举到一个函数,调用一次,回调参数 match 包含 name 和 address 两个属性。
- onComplete: function(): 整个枚举过程完成之后调用。
- 举个例子:
- enumerateMatchesSync(query): enumerateMatches() 的同步版本,直接返回所有结果的数组形式
DebugSymbol
- DebugSymbol.fromAddress(address), DebugSymbol.fromName(name): 在指定地址或者指定名称查找符号信息,返回的符号信息对象包含下面的属性:
- address: 当前符号的地址,NativePointer
- name: 当前符号的名称,字符串形式
- moduleName: 符号所在的模块名称
- fileName: 符号所在的文件名
- lineNumber: 符号所在的文件内的行号
- 为了方便使用,也可以在这个对象上直接使用 toString() ,输出信息的时候比较有用,比如和 Thread.backtrace 配合使用,举个例子来看:
- DebugSymbol.getFunctionByName(name), DebugSymbol.findFunctionsNamed(name), DebugSymbol.findFunctionsMatching(glob): 这三个函数,都是根据符号信息来查找函数,结果返回 NativePointer 对象。
Instruction
- Instruction.parse(target): 在 target 指定的地址处解析指令,其中 target 是一个 NativePointer。注意,在 32 位 ARM 上,ARM 函数地址需要是 2 字节对齐的,Thumb 函数地址是 1 字节对齐的,如果你是使用 Frida 本身的函数来获取的 target 地址,Frida 会自动处理掉这个细节,parse 函数返回的对象包含如下属性:
- address: 当前指令的 EIP,NativePointer 类型
- next: 下条指令的地址,可以继续使用 parse 函数
- size: 当前指令大小
- mnemonic: 指令助记符
- opStr: 字符串格式显示操作数
- operands: 操作数数组,每个操作数对象包含 type 和 value 两个属性,根据平台不同,有可能还包含一些额外属性
- regsRead: 这条指令显式进行读取的寄存器数组
- regsWritten: 这条指令显式的写入的寄存器数组
- groups: 该条指令所属的指令分组
- toString(): 把指令格式化成一条人比较容易读懂的字符串形式
- 关于 operands 和 groups 的细节,请参考 CapStone 文档
ObjC
Java
- Java.available: 布尔型取值,表示当前进程中是否存在完整可用的 Java 虚拟机环境,Dalvik 或者 Art,建议在使用 Java 方法之前,使用这个变量来确保环境正常。
- Java.enumerateLoadedClasses(callbacks): 枚举当前进程中已经加载的类,每次枚举到加载的类回调 callbacks :
- onMatch: function(className): 枚举到一个类,以类名称进行回调,这个类名称后续可以作为 Java.use() 的参数来获取该类的一个引用对象。
- onComplete: function(): 所有的类枚举完毕之后调用
- Java.enumerateLoadedClassesSync(): 同步枚举所有已经加载的类
- Java.use(fn): 把当前线程附加到 Java VM 环境中去,并且执行 Java 函数 fn(如果已经在 Java 函数的回调中,则不需要再附加到 VM),举个例子:
- Java.use(className): 对指定的类名动态的获取这个类的 JavaScript 引用,后续可以使用 \$new() 来调用类的构造函数进行类对象的创建,后续可以主动调用 \$dispose() 来调用类的析构函数来进行对象清理(或者等待 Java 的垃圾回收,再或者是 JavaScript 脚本卸载的时候),静态和非静态成员函数在 JavaScript 脚本里面也都是可见的, 你可以替换 Java 类中的方法,甚至可以在里面抛出异常,比如:
- Java.scheduleOnMainThread(fn): 在虚拟机主线程上执行函数 fn
- Java.choose(className, callbacks): 在 Java 的内存堆上扫描指定类名称的 Java 对象,每次扫描到一个对象,则回调 callbacks :
- onMatch: function(instance): 每次扫描到一个实例对象,调用一次,函数返回 stop 结束扫描的过程
- onComplete: function(): 当所有的对象都扫描完毕之后进行回调
- Java.cast(handle, klass): 使用对象句柄 handle 按照 klass (Java.use 方法返回)的类型创建一个对象的 JavaScript 引用,这个对象引用包含一个 class 属性来获取当前对象的类,也包含一个 \$className 属性来获取类名称字符串,比如:
单篇儿太长,下篇继续
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论