Frida官方手册 - JavaScript API(篇一)

发布者:freakish
发布于:2017-10-22 16:02

JavaScript API

目录

  1. Global
  2. console
  3. rpc
  4. Frida
  5. Process
  6. Module
  7. ModuleMap
  8. Memory
  9. MemoryAccessMonitor
  10. Thread
  11. Int64
  12. UInt64
  13. NativePointer
  14. NativeFunction
  15. NativeCallback
  16. SystemFunction
  17. Socket
  18. SocketListener
  19. SocketConnection
  20. IOStream
  21. InputStream
  22. OutputStream
  23. UnixInputStream
  24. UnixOutputStream
  25. Win32InputStream
  26. Win32OutputStream
  27. File
  28. SqliteDatabase
  29. SqliteStatement
  30. Interceptor
  31. Stalker
  32. ApiResolver
  33. DebugSymbol
  34. Instruction
  35. ObjC
  36. Java
  37. WeakRef
  38. X86Writer
  39. X86Relocator
  40. X86_enum_types
  41. ArmWriter
  42. ArmRelocation
  43. ThumbWriter
  44. ThumbRelocator
  45. ARM_enum_types
  46. Arm64Writer
  47. Arm64Relocator
  48. AArch64_enum_types
  49. MipsWriter
  50. MipsRelocator
  51. Mips_enum_types

Global

  • hexdump(target[, options]): 把一个 ArrayBuffer 或者 NativePointer 的target变量,附加一些 options 属性,按照指定格式进行输出,比如:
  • int64(v): new Int64(v) 的缩写格式
  • uint64(v): new UInt64(v) 的缩写格式
  • ptr(s): new NativePointer(s) 的缩写格式
  • NULL ptr(“0”) 的缩写格式
  • recv([type, ]callback): 注册一个回调,当下次有消息到来的时候会收到回调消息,可选参数 type 相当于一个过滤器,表示只接收这种类型的消息。需要注意的一点是, 这个消息回调是一次性的, 收到一个消息之后,如果需要继续接收消息,那就需要重新调用一个 recv
  • send(message[, data]): 从目标进程中往主控端发 message(必须是可以序列换成Json的),如果你还有二进制数据需要附带发送(比如使用 Memory.readByteArray 拷贝的内存数据),就把这个附加数据填入 data 参数,但是有个要求,就是 data 参数必须是一个 ArrayBuffer 或者是一个整形数组(数值是 0-255)
  • setTimeout(fn, delay): 在延迟 delay 毫秒之后,调用 fn,这个调用会返回一个ID,这个ID可以传递给 clearTimeout 用来进行调用取消。
  • clearTimeout(id): 取消通过 setTimeout 发起的延迟调用
  • setInterval(fn, delay): 每隔 delay 毫秒调用一次 fn,返回一个ID,这个ID可以传给 clearInterval 进行调用取消。
  • clearInterval(id): 取消通过 setInterval 发起的调用

console

  • console.log(line), console.warn(line), console.error(line): 向标准输入输出界面写入 line 字符串。 比如:使用 Frida-Python 的时候就输出到 stdout 或者 stderr,使用 frida-qml 的时候则输出到 qDebug,如果输出的是一个ArrayBuffer,会以默认参数自动调用 hexdump 进行格式化输出。

rpc

  • rpc.exports: 可以在你的程序中导出一些 RPC-Style API函数,Key指定导出的名称,Value指定导出的函数,函数可以直接返回一个值,也可以是异步方式以 Promise 的方式返回,举个例子:
  • 对于Python主控端可以使用下面这样的脚本使用导出的函数:
  • 在上面这个例子里面,我们使用 script.on(‘message’, on_message) 来监控任何来自目标进程的消息,消息监控可以来自 scriptsession 两个方面,比如,如果你想要监控目标进程的退出,可以使用下面这个语句 session.on(‘detached’, my_function)

Frida

  • Frida.version: 包含当前Frida的版本信息

Process

  • Process.arch: CPU架构信息,取值范围:ia32、x64、arm、arm64
  • Process.platform: 平台信息,取值范围:windows、darwin、linux、qnx
  • Process.pageSize: 虚拟内存页面大小,主要用来辅助增加脚本可移植性
  • Process.pointerSize: 指针占用的内存大小,主要用来辅助增加脚本可移植性
  • Process.codeSigningPolicy: 取值范围是 optional 或者 required,后者表示Frida会尽力避免修改内存中的代码,并且不会执行未签名的代码。默认值是 optional,除非是在 Gadget 模式下通过配置文件来使用 required,通过这个属性可以确定 Interceptor API 是否有限制,确定代码修改或者执行未签名代码是否安全。(译者注:这个目前没有实验清楚,可以参考原文)
  • Process.isDebuggerAttached(): 确定当前是否有调试器附加
  • Process.getCurrentThreadId(): 获取当前线程ID
  • Process.enumerateThreads(callbacks): 枚举所有线程,每次枚举到一个线程就执行回调类callbacks:
    • onMatch: function(thread): 当枚举到一个线程的时候,就调用这个函数,其中thread参数包含 :
      1. id,线程ID
      2. state,线程状态,取之范围是 running, stopped, waiting, uninterruptible, halted
      3. context, 包含 pc, sp,分别代表 EIP/RIP/PC 和 ESP/RSP/SP,分别对应于 ia32/x64/arm平台,其他的寄存器也都有,比如 eax, rax, r0, x0 等。
      4. 函数可以直接返回 stop 来停止枚举。
    • onComplete: function(): 当所有的线程枚举都完成的时候调用。
  • Process.enumerateThreadSync(): enumerateThreads()的同步版本,返回线程对象数组
  • Process.findModuleByAddress(address), Process.getModuleByAddress(address), Process.findModuleByName(name), Process.getModuleByName(name): 根据地址或者名称来查找模块,如果找不到这样的模块,find开头的函数返回 null,get开头的函数会抛出异常。
  • Process.enumerateModules(callbacks): 枚举已经加载的模块,枚举到模块之后调用回调对象:
    • onMatch: function(module): 枚举到一个模块的时候调用,module对象包含如下字段:
      1. name, 模块名
      2. base, 基地址
      3. size,模块大小
      4. path,模块路径
      5. 函数可以返回 stop 来停止枚举 。
    • onComplete: function(): 当所有的模块枚举完成的时候调用。
  • Process.enumerateModulesSync(): enumerateModules() 函数的同步版本,返回模块对象数组
  • Process.findRangeByAddress(address), Process.getRangeByAddress(address): 返回一个内存块对象, 如果在这个address找不到内存块对象,那么 findRangeByAddress() 返回 nullgetRangeByAddress 则抛出异常。
  • Process.numerateRanges(protection | specifier, callbacks): 枚举指定 protection 类型的内存块,以指定形式的字符串给出:rwx,而 rw- 表示最少是可读可写,也可以用分类符,里面包含 protection 这个Key,取值就是前面提到的rwx,还有一个 coalesce 这个Key,表示是否要把位置相邻并且属性相同的内存块合并给出结果,枚举过程中回调 callbacks 对象:
    • onMatch: function(range): 每次枚举到一个内存块都回调回来,其中Range对象包含如下属性:
      1. base:基地址
      2. size:内存块大小
      3. protection:保护属性
      4. file:(如果有的话)内存映射文件:
        4.1 path,文件路径
        4.2 offset,文件内偏移
      5. 如果要停止枚举过程,直接返回 stop 即可
    • onComplete: function(): 所有内存块枚举完成之后会回调
  • Process.enumerateRangesSync(protection | specifier): enumerateRanges()函数的同步版本,返回内存块数组
  • Process.enumerateMallocRanges(callbacks): 用于枚举在系统堆上申请的内存块
  • Process.enumerateMallocRangesSync(protection): Process.enumerateMallocRanges() 的同步版本
  • Process.setExceptionHandler(callback): 在进程内安装一个异常处理函数(Native Exception),回调函数会在目标进程本身的异常处理函数之前调用 ,回调函数只有一个参数 details,包含以下几个属性:
    • type,取值为下列之一:
      1. abort
      2. access-violation
      3. guard-page
      4. illegal-instruction
      5. stack-overflow
      6. arithmetic
      7. breakpoint
      8. single-step
      9. system
    • address,异常发生的地址,NativePointer
    • memory,如果这个对象不为空,则会包含下面这些属性:
      1. operation: 引发一场的操作类型,取值范围是 read, write 或者 execute
      2. address: 操作发生异常的地址,NativePointer
    • context,包含 pcsp 的NativePointer,分别代表指令指针和堆栈指针
    • nativeContext,基于操作系统定义的异常上下文信息的NativePointer,在 context 里面的信息不够用的时候,可以考虑用这个指针,但是一般不建议使用(译者注:估计是考虑到可移植性或者稳定性
    • 捕获到异常之后,怎么使用就看你自己了,比如可以把异常信息写到日志里面,然后发送个信息给主控端,然后同步等待主控端的响应之后处理,或者直接修改异常信息里面包含的寄存器的值,尝试恢复掉异常,继续执行。如果你处理了异常信息,那么这个异常回调里面你要返回 true,Frida会把异常交给进程异常处理函数处理,如果到最后都没人去处理这个异常,就直接结束目标进程。

Module

  • Module.emuerateImports(name, callbacks): 枚举模块 name 的导入表,枚举到一个导入项的时候回调callbacks, callbacks包含下面2个回调:
    • onMatch: function(imp): 枚举到一个导入项到时候会被调用,imp包含如下的字段:
      1. type,导入项的类型, 取值范围是 function或者variable
      2. name,导入项的名称
      3. module,模块名称
      4. address,导入项的绝对地址
      5. 以上所有的属性字段,只有 name 字段是一定会有,剩余的其他字段不能保证都有,底层会尽量保证每个字段都能给出数据,但是不能保证一定能拿到数据,onMatch函数可以返回字符串 stop 表示要停止枚举。
    • onComplete: function(): 当所有的导入表项都枚举完成的时候会回调
  • Module.eumerateImportsSync(name): enumerateImports()的同步版本
  • Module.emuerateExports(name, callbacks): 枚举指定模块 name 的导出表项,结果用 callbacks 进行回调:
    • onMatch: function(exp): 其中 exp 代表枚举到的一个导出项,包含如下几个字段:
      1. type,导出项类型,取值范围是 function或者variable
      2. name,导出项名称
      3. address,导出项的绝对地址,NativePointer
      4. 函数返回 stop 的时候表示停止枚举过程
    • onComplete: function(): 枚举完成回调
  • Module.enumerateExportsSync(): Module.enumerateExports()的同步版本
  • Module.enumerateSymbols(name, callbacks): 枚举指定模块中包含的符号,枚举结果通过回调进行通知:
    • onMatch: function(sym): 其中 sym 包含下面几个字段:
      • isGlobal,布尔值,表示符号是否全局可见
      • type,符号的类型,取值是下面其中一种:
        1. unknown
        2. undefined
        3. absolute
        4. section
        5. prebound-undefined
        6. indirect
      • section,如果这个字段不为空的话,那这个字段包含下面几个属性:
        1. id,小节序号,段名,节名
        2. protection,保护属性类型, rwx这样的属性
      • name,符号名称
      • address,符号的绝对地址,NativePointer
      • 这个函数返回 stop 的时候,表示要结束枚举过程
    • Module.enumerateSymbolsSync(name): Module.enumerateSymbols() 的同步版本
  • Module.enumerateRanges(name, protection, callbacks): 功能基本等同于 Process.enumerateRanges(),只不过多了一个模块名限定了枚举的范围
  • Module.enumerateRangesSync(name, protection): Module.enumerateRanges() 的同步版本
  • Module.findBaseAddress(name): 获取指定模块的基地址
  • Module.findExportByName(module | null, exp): 返回模块module 内的导出项的绝对地址,如果模块名不确定,第一个参数传入 null,这种情况下会增大查找开销,尽量不要使用。

ModuleMap

  • new ModuleMap([filter]): 可以理解为内存模块快照,主要目的是可以作为一个模块速查表,比如你可以用这个快照来快速定位一个具体的地址是属于哪个模块。创建ModuleMap的时候,就是对目标进程当前加载的模块的信息作一个快照,后续想要更新这个快照信息的时候,可以使用 update 进行更新。 这个 filter 参数是可选的,主要是用来过滤你关心的模块,可以用来缩小快照的范围(注意:filter是过滤函数,不是字符串参数),为了让模块进入这个快照里,过滤函数的返回值要设置为true,反之设为false,如果后续内存中的模块加载信息更新了, 还会继续调用这个filter函数。
  • has(address): 检查 address 这个地址是不是包含在ModuleMap里面,返回bool值
  • find(address), get(address): 返回 address 地址所指向的模块对象详细信息,如果不存在 find 返回null,get 直接会抛出异常,具体的返回的对象的详细信息,可以参考 Process.enumerateModules()
  • findName(address), getName(address), findPath(address), getPath(address): 功能跟 find(), get() 类似,但是只返回 name 或者 path 字段,可以省点开销
  • update(): 更新ModuleMap信息,如果有模块加载或者卸载,最好调用一次,免得使用旧数据。

Memory

  • Memory.scan(address, size, pattern, callbacks):address 开始的地址,size 大小的内存范围内以 pattern 这个模式进行匹配查找,查找到一个内存块就回调callbacks,各个参数详细如下:
    • pattern 比如使用13 37 ?? ff来匹配0x13开头,然后跟着0x37,然后是任意字节内容,接着是0xff这样的内存块
    • callbacks 是扫描函数回调对象:
      1. onMatch: function(address, size): 扫描到一个内存块,起始地址是address,大小size的内存块,返回 stop 表示停止扫描
      2. onError: function(reason): 扫描内存的时候出现内存访问异常的时候回调
      3. onComplete: function(): 内存扫描完毕的时候调用
  • Memory.scanSync(address, size, pattern): 内存扫描 scan() 的同步版本
  • Memory.alloc(size): 在目标进程中的堆上申请size大小的内存,并且会按照Process.pageSize对齐,返回一个NativePointer,并且申请的内存如果在JavaScript里面没有对这个内存的使用的时候会自动释放的。也就是说,如果你不想要这个内存被释放,你需要自己保存一份对这个内存块的引用。
  • Memory.copy(dust, src, n): 就像是memcpy
  • Memory.dup(address, size): 等价于 Memory.alloc()Memory.copy()的组合。
  • Memory.protect(address, size, protection): 更新address开始,size大小的内存块的保护属性,protection 的取值参考 Process.enumerateRanges(),比如:Memory.protect(ptr(“0x123”, 4096, ‘rw-‘));
  • Memory.patchCode(address, size, apply): apply是一个回调函数,这个函数是用来在 address 开始的地址和 size 大小的地方开始Patch的时候调用,回调参数是一个NativePointer的可写指针,需要在apply回调函数里面要完成patch代码的写入,注意,这个可写的指针地址不一定和上面的address是同一个地址,因为在有的系统上是不允许直接写入代码段的,需要先写入到一个临时的地方,然后在影射到响应代码段上,(比如 iOS上, 会引发进程丢失 CS_VALID 状态),比如:
  • 下面是接着是一些数据类型读写:
    1. Memory.readPointer(address)
    2. Memory.writePointer(address, ptr)
    3. Memory.readS8, Memory.readU8

MemoryAccessMonitor

  • MemoryAccessMonitor.enable(ranges, callbacks): 监控一个或多个内存块的访问,在触发到内存访问的时候发出通知。ranges 要么是一个单独的内存块,要么是一个内存块数组,每个内存块包含如下属性:

    • base: 触发内存访问的NativePointer地址
    • size: 被触发访问的内存块的大小
    • callbacks: 回调对象结构:
    • onAccess: function(details): 发生访问的时候同步调用这个函数,details对象包含如下属性:
      1. operation: 触发内存访问的操作类型,取值范围是 read, write 或者 execute
      2. from: 触发内存访问的指令地址,NativePointer
      3. address: 被访问的内存地址
      4. rangeIndex: 被访问的内存块的索引,就是调用MemoryAccessMonitor.enable()的时候指定的内存块序号
      5. pageIndex: 在被监控内存块范围内的页面序号
      6. pagesCompleted: 到目前为止已经发生过内存访问的页面的个数(已经发生过内存访问的页面将不再进行监控)
      7. pagesTotal: 初始指定的需要监控的内存页面总数
  • MemoryAccessMonitor.disable(): 停止监控页面访问操作

Thread

  • Thread.backtrace([context, backtracer]): 抓取当前线程的调用堆栈,并以 NativePointer 指针数组的形式返回。
    1. 如果你是在 Interceptor.onEnter或者Interceptor.onLeave 中调用这个函数的话,那就必须要把 this.context 作为参数传入,这样就能拿到更佳精准的堆栈调用信息,如果省略这个参数不传,那就意味着从当前堆栈的位置开始抓取,这样的抓取效果可能不会很好,因为有不少V8引擎的栈帧的干扰。
    2. 第二个可选参数 backtracer,表示使用哪种类型的堆栈抓取算法,目前的取值范围是 Backtracer.FUZZYBacktracer.ACCURATE,目前后者是默认模式。精确抓取模式下,如果如果程序是调试器友好(比如是标准编译器编译的结果,没有什么反调试技巧)或者有符号表的支持,抓取效果是最好的,而模糊抓取模式下,抓取器会在堆栈上尝试抓取,并且会猜测里面包含的返回地址,也就是说中间可能包含一些错误的信息,但是这种模式基本能在任何二进制程序里面工作:
  • Thread.sleep(delay): 线程暂停 delay 秒执行

下篇继续

  • 一篇文章写太长发现浏览器容易崩溃,所以下篇继续

声明:该文观点仅代表作者本人,转载请注明来自看雪