文章来源于网络收集而来,版权归原创者所有,如有侵权请及时联系!
JavaScript API 1
目录
- Global
- console
- rpc
- Frida
- Process
- Module
- ModuleMap
- Memory
- MemoryAccessMonitor
- Thread
- Int64
- UInt64
- NativePointer
- NativeFunction
- NativeCallback
- SystemFunction
- Socket
- SocketListener
- SocketConnection
- IOStream
- InputStream
- OutputStream
- UnixInputStream
- UnixOutputStream
- Win32InputStream
- Win32OutputStream
- File
- SqliteDatabase
- SqliteStatement
- Interceptor
- Stalker
- ApiResolver
- DebugSymbol
- Instruction
- ObjC
- Java
- WeakRef
- X86Writer
- X86Relocator
- X86_enum_types
- ArmWriter
- ArmRelocation
- ThumbWriter
- ThumbRelocator
- ARM_enum_types
- Arm64Writer
- Arm64Relocator
- AArch64_enum_types
- MipsWriter
- MipsRelocator
- 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) 来监控任何来自目标进程的消息,消息监控可以来自 script 和 session 两个方面,比如,如果你想要监控目标进程的退出,可以使用下面这个语句 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 参数包含 :
- id,线程 ID
- state,线程状态,取之范围是 running, stopped, waiting, uninterruptible, halted
- context, 包含 pc, sp ,分别代表 EIP/RIP/PC 和 ESP/RSP/SP,分别对应于 ia32/x64/arm 平台,其他的寄存器也都有,比如 eax, rax, r0, x0 等。
- 函数可以直接返回 stop 来停止枚举。
- onComplete: function(): 当所有的线程枚举都完成的时候调用。
- onMatch: function(thread): 当枚举到一个线程的时候,就调用这个函数,其中 thread 参数包含 :
- 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 对象包含如下字段:
- name, 模块名
- base, 基地址
- size,模块大小
- path,模块路径
- 函数可以返回 stop 来停止枚举 。
- onComplete: function(): 当所有的模块枚举完成的时候调用。
- onMatch: function(module): 枚举到一个模块的时候调用,module 对象包含如下字段:
- Process.enumerateModulesSync(): enumerateModules() 函数的同步版本,返回模块对象数组
- Process.findRangeByAddress(address), Process.getRangeByAddress(address): 返回一个内存块对象, 如果在这个 address 找不到内存块对象,那么 findRangeByAddress() 返回 null 而 getRangeByAddress 则抛出异常。
- Process.numerateRanges(protection | specifier, callbacks): 枚举指定 protection 类型的内存块,以指定形式的字符串给出: rwx ,而 rw- 表示最少是可读可写,也可以用分类符,里面包含 protection 这个 Key,取值就是前面提到的 rwx,还有一个 coalesce 这个 Key,表示是否要把位置相邻并且属性相同的内存块合并给出结果,枚举过程中回调 callbacks 对象:
- onMatch: function(range): 每次枚举到一个内存块都回调回来,其中 Range 对象包含如下属性:
- base:基地址
- size:内存块大小
- protection:保护属性
- file:(如果有的话)内存映射文件: 4.1 path,文件路径 4.2 offset,文件内偏移
- 如果要停止枚举过程,直接返回 stop 即可
- onComplete: function(): 所有内存块枚举完成之后会回调
- onMatch: function(range): 每次枚举到一个内存块都回调回来,其中 Range 对象包含如下属性:
- Process.enumerateRangesSync(protection | specifier): enumerateRanges() 函数的同步版本,返回内存块数组
- Process.enumerateMallocRanges(callbacks): 用于枚举在系统堆上申请的内存块
- Process.enumerateMallocRangesSync(protection): Process.enumerateMallocRanges() 的同步版本
- Process.setExceptionHandler(callback): 在进程内安装一个异常处理函数(Native Exception),回调函数会在目标进程本身的异常处理函数之前调用 ,回调函数只有一个参数 details ,包含以下几个属性:
- type ,取值为下列之一:
- abort
- access-violation
- guard-page
- illegal-instruction
- stack-overflow
- arithmetic
- breakpoint
- single-step
- system
- address ,异常发生的地址,NativePointer
- memory ,如果这个对象不为空,则会包含下面这些属性:
- operation: 引发一场的操作类型,取值范围是 read, write 或者 execute
- address: 操作发生异常的地址,NativePointer
- context ,包含 pc 和 sp 的 NativePointer,分别代表指令指针和堆栈指针
- nativeContext ,基于操作系统定义的异常上下文信息的 NativePointer,在 context 里面的信息不够用的时候,可以考虑用这个指针,但是一般不建议使用( 译者注:估计是考虑到可移植性或者稳定性 )
- 捕获到异常之后,怎么使用就看你自己了,比如可以把异常信息写到日志里面,然后发送个信息给主控端,然后同步等待主控端的响应之后处理,或者直接修改异常信息里面包含的寄存器的值,尝试恢复掉异常,继续执行。如果你处理了异常信息,那么这个异常回调里面你要返回 true ,Frida 会把异常交给进程异常处理函数处理,如果到最后都没人去处理这个异常,就直接结束目标进程。
- type ,取值为下列之一:
Module
- Module.emuerateImports(name, callbacks): 枚举模块 name 的导入表,枚举到一个导入项的时候回调 callbacks, callbacks 包含下面 2 个回调:
- onMatch: function(imp): 枚举到一个导入项到时候会被调用, imp 包含如下的字段:
- type ,导入项的类型, 取值范围是 function 或者 variable
- name ,导入项的名称
- module ,模块名称
- address ,导入项的绝对地址
- 以上所有的属性字段,只有 name 字段是一定会有,剩余的其他字段不能保证都有,底层会尽量保证每个字段都能给出数据,但是不能保证一定能拿到数据,onMatch 函数可以返回字符串 stop 表示要停止枚举。
- onComplete: function(): 当所有的导入表项都枚举完成的时候会回调
- onMatch: function(imp): 枚举到一个导入项到时候会被调用, imp 包含如下的字段:
- Module.eumerateImportsSync(name): enumerateImports() 的同步版本
- Module.emuerateExports(name, callbacks): 枚举指定模块 name 的导出表项,结果用 callbacks 进行回调:
- onMatch: function(exp): 其中 exp 代表枚举到的一个导出项,包含如下几个字段:
- type ,导出项类型,取值范围是 function 或者 variable
- name ,导出项名称
- address ,导出项的绝对地址,NativePointer
- 函数返回 stop 的时候表示停止枚举过程
- onComplete: function(): 枚举完成回调
- onMatch: function(exp): 其中 exp 代表枚举到的一个导出项,包含如下几个字段:
- Module.enumerateExportsSync(): Module.enumerateExports() 的同步版本
- Module.enumerateSymbols(name, callbacks): 枚举指定模块中包含的符号,枚举结果通过回调进行通知:
- onMatch: function(sym): 其中 sym 包含下面几个字段:
- isGlobal ,布尔值,表示符号是否全局可见
- type ,符号的类型,取值是下面其中一种:
- unknown
- undefined
- absolute
- section
- prebound-undefined
- indirect
- section ,如果这个字段不为空的话,那这个字段包含下面几个属性:
- id ,小节序号,段名,节名
- protection ,保护属性类型, rwx 这样的属性
- name ,符号名称
- address ,符号的绝对地址,NativePointer
- 这个函数返回 stop 的时候,表示要结束枚举过程
- Module.enumerateSymbolsSync(name): Module.enumerateSymbols() 的同步版本
- onMatch: function(sym): 其中 sym 包含下面几个字段:
- 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 是扫描函数回调对象:
- onMatch: function(address, size): 扫描到一个内存块,起始地址是 address ,大小 size 的内存块,返回 stop 表示停止扫描
- onError: function(reason): 扫描内存的时候出现内存访问异常的时候回调
- 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 状态),比如:
- 下面是接着是一些数据类型读写:
- Memory.readPointer(address)
- Memory.writePointer(address, ptr)
- Memory.readS8, Memory.readU8
- ...
MemoryAccessMonitor
- MemoryAccessMonitor.enable(ranges, callbacks): 监控一个或多个内存块的访问,在触发到内存访问的时候发出通知。 ranges 要么是一个单独的内存块,要么是一个内存块数组,每个内存块包含如下属性:
- base: 触发内存访问的 NativePointer 地址
- size: 被触发访问的内存块的大小
- callbacks: 回调对象结构:
- onAccess: function(details): 发生访问的时候同步调用这个函数,details 对象包含如下属性:
- operation: 触发内存访问的操作类型,取值范围是 read, write 或者 execute
- from: 触发内存访问的指令地址,NativePointer
- address: 被访问的内存地址
- rangeIndex: 被访问的内存块的索引,就是调用 MemoryAccessMonitor.enable() 的时候指定的内存块序号
- pageIndex: 在被监控内存块范围内的页面序号
- pagesCompleted: 到目前为止已经发生过内存访问的页面的个数(已经发生过内存访问的页面将不再进行监控)
- pagesTotal: 初始指定的需要监控的内存页面总数
- MemoryAccessMonitor.disable(): 停止监控页面访问操作
Thread
- Thread.backtrace([context, backtracer]): 抓取当前线程的调用堆栈,并以 NativePointer 指针数组的形式返回。
- 如果你是在 Interceptor.onEnter 或者 Interceptor.onLeave 中调用这个函数的话,那就必须要把 this.context 作为参数传入,这样就能拿到更佳精准的堆栈调用信息,如果省略这个参数不传,那就意味着从当前堆栈的位置开始抓取,这样的抓取效果可能不会很好,因为有不少 V8 引擎的栈帧的干扰。
- 第二个可选参数 backtracer ,表示使用哪种类型的堆栈抓取算法,目前的取值范围是 Backtracer.FUZZY 和 Backtracer.ACCURATE ,目前后者是默认模式。精确抓取模式下,如果如果程序是调试器友好(比如是标准编译器编译的结果,没有什么反调试技巧)或者有符号表的支持,抓取效果是最好的,而模糊抓取模式下,抓取器会在堆栈上尝试抓取,并且会猜测里面包含的返回地址,也就是说中间可能包含一些错误的信息,但是这种模式基本能在任何二进制程序里面工作:
- Thread.sleep(delay): 线程暂停 delay 秒执行
下篇继续
- 一篇文章写太长发现浏览器容易崩溃,所以下篇继续
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论