文章目录

  • Runtime information
    • Frida
    • Script
  • Process, Thread, Module and Memory
    • Thread 线程
      • Thread.backtrace
      • Thread.sleep
    • Process 进程
      • property
      • method
    • Module
      • Module 对象常见获取方式
      • Module 对象属性
      • Module 对象方法
        • 枚举(imports, exports, symbols, ranges)
        • find/get ExportByName
      • Module 模块方法 (加载、寻找等)
    • ModuleMap
    • Memory
    • MemoryAccessMonitor
    • CModule
    • ApiResolver
    • DebugSymbol
    • Kernel
  • 数据类型, 函数和回调函数
    • Int64
    • UInt64
    • NativePointer
      • 基本操作
      • 类型转换
      • r/w Pointer()
      • 读写 number
      • 读写 大整数
      • 读写 字节数组
      • 读写 字符串
    • ArrayBuffer
    • NativeFunction
      • STRUCTS & CLASSES BY VALUE
      • Supported Types
      • Supported ABIs
    • NativeCallback
    • SystemFunction
  • 网络(*)
    • Socket
    • SocketListener
    • SocketConnection
  • 文件与流
    • File
    • IOStream
    • InputStream
    • OutputStream
    • UnixInputStream
    • UnixOutputStream
    • Win32InputStream
    • Win32OutputStream
  • 数据库
    • SqliteDatabase
    • SqliteStatement
  • 插桩/注入 Instrumentation
    • Interceptor 拦截器
    • Stalker
    • WeakRef
    • ObjC
    • Java
  • CPU指令
    • Instruction
  • 其他
    • Console
    • Hexdump
    • 简写
    • 主机与注入进程之间的通信
    • Timing events
    • Garbage collection

注:本篇博文主要翻译自 Frida JavaScript API 官方文档
https://frida.re/docs/javascript-api/
如需转载,请注明出处!!!

Runtime information

Frida

  • Frida.version
  • Frida.heapSize 包含Frida当前私有堆大小的动态属性,由所有脚本和Frida自己的运行时共享。这 对于监视主机进程占用的 instrumentation 中正在使用的内存量很有用
Frida.version // "12.10.4"
Frida.heapSize //

Script

  • Script.runtime
    DUK or V8

Process, Thread, Module and Memory

Thread 线程

Thread.backtrace

  • Thread.backtrace([context, backtracer]) : 生成当前线程(thread)的 backtrace ,以 NativePointer 对象数组的形式返回。

    • 如果您是从 Interceptor 的 onEnteronLeave 回调(callback)中调用的,则应为可选的 context 参数提供 this.context,它将为您提供更准确的回溯。
    • 省略 context 意味着回溯将从当前堆栈位置生成,由于V8的堆栈框架,这可能无法为您提供很好的回溯。
    • 可选的 backtracer 参数指定要使用的 backtracer 的类型,并且必须为 Backtracer.FUZZYBacktracer.ACCURATE,如果未指定,则后者为默认值。
      • Backtracer.ACCURATE 依赖于 调试器友好的二进制文件(debugger-friendly binaries) 或 调试信息的存在(presence of debug information) 来做好工作
      • Backtracer.FUZZY 在堆栈上执行取证(forensics)以猜测返回地址,这意味着您会得到误报,但是它可以在任何二进制文件上工作。
    var f = Module.getExportByName('libcommonCrypto.dylib','CCCryptorCreate');
    Interceptor.attach(f, {onEnter: function (args) {console.log('CCCryptorCreate called from:\n' +Thread.backtrace(this.context, Backtracer.ACCURATE).map(DebugSymbol.fromAddress).join('\n') + '\n');}
    });
    

Thread.sleep

  • Thread.sleep(delay): 将当前线程的执行挂起delay秒。例如,0.05 ==> 50ms

Process 进程

property

  • Process.id: PID @number

  • Process.arch: ia32, x64, arm, arm64 之一 @string

  • Process.platform: windows, darwin, linux, qnx 之一 @string

  • Process.pageSize: 虚拟内存页面的大小(以字节为单位)@number

    • 这用于使脚本更具可移植性
  • Process.pointerSize: 指针的大小(以字节为单位)@number

    • 这用于使脚本更具可移植性
  • Process.codeSigningPolicy @string

    • optional

      • 此属性允许你决定(determine)

        • Interceptor API是否超出限制 (whether the Interceptor API is off limits)
        • 修改代码 或 运行未签名代码 是否安全(safe)
    • required
      • 意味着Frida将避免修改内存中的现有代码,并且不会尝试运行未签名的代码。

    当前,此属性将始终设置为 optional,除非您正在使用 Gadget 并将其配置为假定需要代码签名。

method

  • Process.isDebuggerAttached(): @return boolean 指示当前是否已连接调试器

  • Process.getCurrentThreadId(): @return number 获取此线程的特定于操作系统的ID

  • Process.enumerateThreads(): @return object[] 枚举所有线程,对象包含如下属性:

    • id: 特定于操作系统的ID
    • state: @string [running, stopped, waiting, uninterruptible, halted]
    • context: @object with the keys pc and sp,它们是 NativePointer 对象
      • 分别为ia32 / x64 / arm指定EIP / RIP / PC和ESP / RSP / SP
      • 也可以使用其他处理器特定的key,例如eaxraxr0x0
    key ia32 x64 arm
    pc EIP RIP PC
    sp ESP RSP SP
    • 译者注: 每个 CpuContext 保存了平台各个寄存器的状态
  • Process.findModuleByAddress(address), Process.getModuleByAddress(address), Process.findModuleByName(name), Process.getModuleByName(name): @return Module 顾名思义,如果找不到,同

  • Process.enumerateModules(): @return Module[] 枚举当前加载的模块

  • Process.findRangeByAddress(address), getRangeByAddress(address): 顾名思义。

    • 如果找不到,同
    • 有关包含哪些字段的详细信息,请参见 Process.enumerateRanges()
  • Process.enumerateRanges(protection|specifier):

    • 枚举满足 protection 的内存范围,以 rwx 字符串形式给出:

      • 其中 rw-表示“必须至少可读写”
    • 或者,您可以提供带有 protection 键的值为如上所述的一个 specifier 对象
    • 如果希望合并具有相同保护的相邻范围(默认值为 false ;即保持范围分离),则可以将 coalesce 键设置为 true
      返回包含以下属性的对象数组:

      • base: @NativePointer 基地址
      • size: @number 字节大小
      • protection: @string (见上)
      • file: (如果可用)@object 文件映射详细信息,包含以下内容:
        • path: @string 完整的文件系统路径
        • offset: 磁盘上映射文件中的偏移量,以字节为单位
        • size: 磁盘上映射文件的大小,以字节为单位
  • Process.enumerateMallocRanges(): 跟 enumerateRanges() 类似,但对于系统堆已知的单个内存分配(but for individual memory allocations known to the system heap.)。

    • 注:not yet implemented for linux
  • Process.setExceptionHandler(callback):

    • 安装一个 进程范围 的异常处理程序 callback,该 callback 有机会在 托管(hosting) 进程本身之前处理 本机(native) 异常。

    • called 需要 details 参数,这是一个对象,包含如下内容:

      • type: @string 指定以下之一:

        • abort (中止)
        • access-violation (访问冲突)
        • guard-page (保护页)
        • illegal-instruction (非法指令)
        • stack-overflow (堆栈溢出)
        • arithmetic (算术)
        • breakpoint (断点)
        • single-step
        • system
      • address: @NativePointer 发生异常的地址
      • memory: 如果存在,则是包含以下内容的对象:
        • operation: @string [read, write, execute] 触发异常的操作类型
        • address: @NativePointer 发生异常时访问的地址
      • context: @Cpucontext 同上,寄存器状态
      • nativeContext: @NativePointer 操作系统的地址和特定于体系结构的CPU上下文结构。仅在 context 无法提供足够详细信息的情况下,这才是最后的选择。
    • callback 决定如何处理异常。它可以

      • 记录问题(log the issue),通过一个send() 后跟一个 阻塞recv() 通知您的应用程序,以确认接收到的发送数据
      • 修改寄存器和内存 用于从异常中恢复。
    • 如果确实处理了异常

      • 应该返回 true,在这种情况下,Frida将立即恢复线程
      • 如果没有返回 true,Frida将把异常转发给 托管(hosting) 进程的异常处理程序(如果有),或者让操作系统终止进程。

Module

Module 对象常见获取方式

  • Module.load()
  • Process.enumerateModules()

Module 对象属性

  • name: 规范化的模块名 @string
  • base: NativePointer类型的基(base)地址
  • size: 字节大小
  • path 在文件系统中的完整路径路径 @string


Module 对象方法

枚举(imports, exports, symbols, ranges)

  • enumerateImports() 枚举 import 的 Module,返回包含以下属性的 对象数组

    • type: function or variable
    • name: import 名称
    • module: module 名称
    • address: 绝对(absolute)地址 NativePointer
    • slot 导入的模块在内存中的地址 NativePointer

所有导入都保证只有name字段存在。特定于平台的后端将尽最大努力解析其他字段,甚至超出本机元数据提供的范围,但不能保证它会成功。

  • enumerateExports() 枚举 exports 的 Module,返回包含以下属性的 对象数组

    • type: 同上
    • name: export 的 名称
    • address: 同上

  • enumerateSymbols() 枚举模块的符号,返回包含以下属性的对象数组:

    • isGlobal 指定符号是否全局可见 @Boolean
    • 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 的 section ID 相同格式):

        • section index
        • segment name (if applicable)
        • section name
      • protection : protection like in Process.enumerateRanges()
    • name
    • address: 绝对地址
    • size: 如果存在,则为 符号的 字节大小 @number

注: enumerateSymbols() 仅在 i/macOS 和基于Linux的操作系统上可用。


我们也很乐意在其他平台上支持此功能,因此,如果您发现此功能有用并希望提供帮助,请与我们联系。您还可能会发现DebugSymbol API足够,这取决于您的用例。

  • enumerateRanges(protection) : 就像 Process.enumerateRanges 一样,只不过它的作用域是 这个 Module。

find/get ExportByName

  • findExportByName(exportName), getExportByName(exportName):
    返回名为exportName的 expert 的绝对地址。如果找不到此类 expert,则

    • find-prefixed 函数将返回null
    • get-prefixed 函数将抛出异常

Module 模块方法 (加载、寻找等)

  • Module.load(path): 从 文件系统路径 加载指定的模块并返回 Module对象。如果无法加载指定的模块,则会抛出异常。
  • Module.ensureInitialized(name): 确保已运行指定模块的初始化程序。这对于早期检测非常重要,即代码在流程生命周期的早期运行,以便能够与API安全交互。例如:与给定模块提供的ObjC类进行交互。
  • Module.findBaseAddress(name), Module.getBaseAddress(name): 返回 name 模块的基地址。
    如果找不到此 Module,则find-prefixed函数将返回null,而get-prefixed函数将引发异常。
  • Module.findExportByName(moduleName|null, exportName), Module.getExportByName(moduleName|null, exportName):
    返回 moduleName 中名为 exportName 的导出的绝对地址。如果不知道该模块,则可以传递 null 而不是其名称,但这可能是一项代价高昂的搜索,应避免使用。
    如果找不到此类 module 或 expert,则find-prefixed函数将返回null,而get-prefixed函数将引发异常。

ModuleMap

  • new ModuleMap([filter]):

    • 创建一个新的 module map,用于确定给定内存地址属于哪个模块(如果有的话)。
    • 创建时加载当前加载的模块的快照,可以通过调用 update() 刷新该快照。
    • filter 参数是可选的,它允许您传递用于过滤模块列表的函数。
      • 如果您只关心应用程序本身拥有的模块,并且允许您快速检查某个地址是否属于其中一个模块,则此功能非常有用
      • filter 函数被传递给一个 Module 对象,对于应该保存在映射中的每个模块,必须返回 true
      • 每次更新 map 时,都会为每个加载的模块调用它
  • has(address): @return boolean 检查 address 是否属于任何包含的模块
  • find(address), get(address): @return Module,其中包含 address 所属模块的详细信息。
  • findName(address), getName(address), findPath(address), getPath(address): 跟 find(), get() 类似,但是仅返回 name or path 字段。当您 不需要其他详细信息 时,这意味着 较少的开销
  • update(): 更新map
    • 在加载或卸载模块后,应调用此函数,以避免对过时的数据进行操作
  • values(): 返回一个数组,其中包含当前 map 中的 Module 对象。返回的数组是一个 深拷贝(deep copy),在调用 update() 之后不会改变

Memory

  • Memory.scan(address, size, pattern, callbacks): 在 addresssize 给定的内存范围内扫描内存中是否出现 pattern

    • pattern

      • 必须为以下形式 “13 37 ?? ff”

        • 匹配 0x13,后跟 0x37,后跟任意字节,后跟 0xff
      • 对于更高级的匹配,还可以指定 r2 样式的掩码
        • 掩码针对 needle 和 haystack 的 按位与
        • 要指定掩码,请在 needle 后附加一个 : 字符,然后使用相同的语法加上掩码。 例如:“13 37 13 37 : 1f ff ff f1”
      • 为了方便起见,也可以指定半字节级别 (nibble-level) 的通配符,例如 “?3 37 13 ?7”,在后台将其转换为掩码
    • callbacks: 是具有以下内容的对象
      • onMatch: function (address, size): 一个调用(called)

        • address: @NativePointer 包含发生地点的地址
        • size: @number 指定大小
        • 可能返回字符串 stop 提前结束内存扫描
      • onError: function (reason): 扫描时出现内存访问错误时调用,携带 reason 参数
      • onComplete: function (): 当内存范围已被完全扫描时调用
  • Memory.scanSync(address, size, pattern): 同步版本的 scan(),它返回包含以下属性的对象数组:
    • address: @NativePointer 绝对地址
    • size: 字节大小

示例代码:

// 查找程序本身的模块, 取索引为 0 的结果
var m = Process.enumerateModules()[0];// 或按名称加载模块
//var m = Module.load('win32u.dll');// 打印其属性:
console.log(JSON.stringify(m));// 从基址 dump:
console.log(hexdump(m.base));// 您感兴趣的 pattern:
var pattern = '00 00 00 00 ?? 13 37 ?? 42';Memory.scan(m.base, m.size, pattern, {onMatch: function (address, size) {console.log'Memory.scan() found match at', address,'with size', size);// (可选)提前停止扫描return 'stop';},onComplete: function () {console.log('Memory.scan() complete');}
});var results = Memory.scanSync(m.base, m.size, pattern);
console.log('Memory.scanSync() result:\n' +JSON.stringify(results));
  • Memory.alloc(size):

    • 在堆上分配 size 字节的内存,或者,如果 sizeProcess.pageSize 的倍数,则由操作系统管理一个或多个原始 memory pages
    • @return NativePointer
    • 当所有的 JavaScript 句柄都消失时,底层内存将被释放。这意味着您需要 在JavaScript运行时之外的代码使用指针时 保留对它的引用。
  • Memory.copy(dst, src, n): 与 memcpy() 类似,不返回任何内容。

    • dst, src: @NativePointer 源/目的 基地址
    • n: 要复制的字节大小
  • Memory.dup(address, size)

    • Memory.alloc()后接 Memory.copy() 的简写
    • @return NativePointer 新分配的内存的基地址
    • 有关内存分配生命周期的详细信息,请参见 Memory.copy()
  • Memory.protect(address, size, protection)

    • 更新内存区域上的保护
    • 其中 protection 是与 Process.enumerateRanges() 格式相同的字符串
    • @return boolean 指示操作是否成功完成
    • 例如:
    Memory.protect(ptr('0x1234'), 4096, 'rw-');
    
  • Memory.patchCode(address, size, apply): 安全地(safely)修改 addresssize 字节

    • address: @NativePointer
    • 提供的JavaScript函数 apply 通过一个 可写指针 来调用
      • 在返回之前,您必须在其中编写所需的修改。
      • 不要假设这个位置与 address 是同一个位置,因为某些系统需要先将修改内容写入临时位置,然后再映射到原始内存页面顶部的内存中(例如,在iOS上,在其中直接修改- 内存代码可能会导致进程丢失其CS_VALID状态)
    • 例如:
    var getLivesLeft = Module.getExportByName('game-engine.so', 'get_lives_left');
    var maxPatchSize = 64; // 不要越界,可能是一个临时缓冲区!
    Memory.patchCode(getLivesLeft, maxPatchSize, function (code) {var cw = new X86Writer(code, { pc: getLivesLeft });cw.putMovRegU32('eax', 9000);cw.putRet();cw.flush();
    });
    
  • Memory.allocUtf8String(str), Memory.allocUtf16String(str), Memory.allocAnsiString(str)

    • 在堆上将 str 作为 UTF-8/UTF-16/ANSI 字符串进行分配,编码和写出
    • @return NativePointer
    • 有关其生存期的详细信息,请参见 Memory.alloc()

MemoryAccessMonitor

  • MemoryAccessMonitor.enable(ranges, callbacks): 监视一个或多个内存范围进行访问,并在首次访问时通知每个包含的 memory page

    • @param ranges: 单个范围对象或此类对象的数组,每个对象包含:

      • base: @NativePointer 基地址
      • size: 字节大小
    • @param callbacks 一个对象,它指定:
      • onAccess: function (details): 同步调用,details 对象包含:

        • operation: @string [read, write, execute] 触发访问的操作
        • from: @NativePointer 执行访问的指令的地址
        • address: @NativePointer 被访问的地址
        • rangeIndex: 提供给 MemoryAccessMonitor.enable() 的范围内的访问范围的索引
        • pageIndex: 指定范围内访问的 memory page 的索引
        • pagesCompleted: 到目前为止已访问(并且不再受监视)的 pages 总数
        • pagesTotal: 最初监视的 pages 总数
  • MemoryAccessMonitor.disable(): 停止监视传递给 MemoryAccessMonitor.enable() 的剩余内存范围

CModule

  • new CModule(source[, symbols]): 将C源代码 source 字符串编译为机器码,放入内存中

    • 可用于

      • 实现调用非常频繁的回调,例如:Interceptor, Stalker
      • 当需要启动新线程以在 紧密(tight) 循环中调用函数,例如用于模糊测试。
    • 全局函数将自动导出为 NativePointer 属性,其名称与C源代码中的名称完全相同。这意味着您可以
      • 将它们传递给 Interceptorstacker
      • 或者使用 NativePointer调用
    • @param 可选的 symbols: 是一个对象,指明了符号名称与它的 NativePointer 值, 它们将在对象创建时被插入进来. 例如, 一个或多个通过 Memory.alloc() 分配的内存块, 以及/或者用于从 C 模块接收回调的 NativeCallback
    • 要执行初始化和清除,可能需要定义 如下 名称和签名 的函数:
      • void init (void)
      • void finalize (void)
    • 请注意,所有数据都是只读的,因此 可写 全局变量应声明为 extern,使用例如 Memory.alloc()来分配,并通过构造函数的第二个参数作为符号传递
  • dispose(): 急切地从内存中取消映射模块。当不需要将来的垃圾收集时,对于短命的模块很有用。

EXAMPLES

var source = ['#include <stdio.h>','','void hello(void) {','  printf("Hello World from CModule\\n");','}',
].join('\n');var cm = new CModule(source);console.log(JSON.stringify(cm));var hello = new NativeFunction(cm.hello, 'void', []);
hello();

您可以使用Frida的REPL(交互式解释器)加载:

$ frida -p 0 -l example.js

(REPL监视磁盘上的文件,并在更改时重新加载脚本)

然后,您可以在REPL中键入 hello() 来调用C函数。

可以使用现代JavaScript语法简化同一示例:

const source = `
#include <stdio.h>void hello(void) {printf("Hello World from CModule\\n");
}
`;const cm = new CModule(source);const hello = new NativeFunction(cm.hello, 'void', []);
hello();

我们基于V8的运行时支持以下功能:

$ frida -p 0 --runtime=v8 -l example.js

另一个选择是使用 frida-compile 将JavaScript代码编译为ES5,这样可以在 Duktape-based runtime上运行它。

对于原型,我们建议使用Frida的REPL:

$ frida -p 0 -C example.c

你也可以加上 -l example 来加载它旁边的一些JavaScript。只有在调用了rpc.export.init()之后,JavaScript代码才可以使用名为 cm 的全局变量来访问CModule对象,所以要根据CModule执行任何初始化操作。还可以通过给名为 cs 的全局对象赋值来注入符号,但是必须在调用 rpc.export.init() 之前完成。

这是一个例子:

更多关于CModule的细节可以在 Frida 12.7 release notes 中找到

ApiResolver

  • new ApiResolver(type): 创建一个新的给定 type 解析器(resolver),允许使用globs通过名称快速查找函数。确切地说,可用的解析器取决于当前平台和当前进程中加载的 runtimes。在撰写本文时,可用的解析器有:

    • module: 解析当前加载的共享库的导出和导入函数。始终可用。
    • objc: 解析当前加载的类的Objective-C方法。在加载了Objective-C运行时的进程中的macOS和iOS上可用。使用 ObjC.available 在运行时进行检查,或将 new ApiResolver('objc') 调用包装在 try-catch

    解析器将加载创建时所需的最小数据量,并根据接收到的查询延迟加载其余数据。因此,建议对一批查询使用相同的实例,但在以后的批处理中重新创建它,以避免查看过时的数据。

  • enumerateMatches(query): 执行特定于解析程序的 query 字符串(可选地以/i作为后缀)以执行不区分大小写的匹配,返回包含以下属性的对象数组:

    • name: 找到的API的名称
    • address: @NativePointer
var resolver = new ApiResolver('module');
var matches = resolver.enumerateMatches('exports:*!open*');
var first = matches[0];
/** 其中 `first` 是类似于以下内容的对象:** {*   name: '/usr/lib/libSystem.B.dylib!opendir$INODE64',*   address: ptr('0x7fff870135c9')* }*/
var resolver = new ApiResolver('objc');
var matches = resolver.enumerateMatches('-[NSURL* *HTTP*]');
var first = matches[0];
/** 其中 `first` 包含这样一个对象:** {*   name: '-[NSURLRequest valueForHTTPHeaderField:]',*   address: ptr('0x7fff94183e22')* }*/

DebugSymbol

  • DebugSymbol.fromAddress(address), DebugSymbol.fromName(name): 查找 address/name 的调试信息,并将其作为包含以下内容的对象返回:

    • address: @NativePointer 此符号的地址
    • name: @string | null 符号名称。如果未知,则返回null
    • moduleName: @string | null 拥有此符号的模块名称
    • fileName: @string | null 拥有此符号的文件名
    • lineNumber: @number | null fileName中的行号

    您还可以在其上调用 toString(),当与 Thread.backtrace() 结合使用时,这非常有用:

    var f = Module.getExportByName('libcommonCrypto.dylib','CCCryptorCreate');
    Interceptor.attach(f, {onEnter: function (args) {console.log('CCCryptorCreate called from:\n' +Thread.backtrace(this.context, Backtracer.ACCURATE).map(DebugSymbol.fromAddress).join('\n') + '\n');}
    });
    
  • DebugSymbol.getFunctionByName(name): @return NativePointer

    • 解析函数名称并返回其地址
    • 如果找到多个函数,则返回第一个
    • 如果名称无法解析,则抛出异常
  • DebugSymbol.findFunctionsNamed(name): @return NativePointer[]

    • 解析函数名称并返回他们的地址
  • DebugSymbol.load(path): 加载特定模块的调试符号

Kernel

注:不适用于 Android 环境,暂不翻译

数据类型, 函数和回调函数

Int64

  • new Int64(v): 从 v 创建一个新的Int64,它可以是数字或包含十进制值的字符串或十六进制(如果前缀为“ 0x”)

    为了简洁起见,可以使用 int64(v) 的简写形式

  • add(rhs), sub(rhs), and(rhs), or(rhs), xor(rhs): @return number|Int64 ——使用此Int64 plus/minus/and/or/xor rhs 创建新的Int64
  • shr(n), shl(n): 右移、左移 n bits
  • compare(rhs): @return int 返回整数比较结果,就像String#localeCompare()
    // The letter "a" is before "c" yielding a negative value
    'a'.localeCompare('c');
    // -2 or -1 (or some other negative value)// Alphabetically the word "check" comes after "against" yielding a positive value
    'check'.localeCompare('against');
    // 2 or 1 (or some other positive value)// "a" and "a" are equivalent yielding a neutral value of zero
    'a'.localeCompare('a');
    // 0
    
  • toNumber(): 将此Int64强制转换为 number
  • toString([radix = 10]): 转换为可选基数的字符串(默认为10)

UInt64

  • new UInt64(v) <=> int64(v)

用法和其他API 同 Int64

NativePointer

基本操作

  • new NativePointer(s): 从字符串 s 创建一个新的NativePointer,该字符串包含以十进制或十六进制(如果以’0x’为前缀)的内存地址

    • 为了简洁起见,您可以使用 ptr(s) 的简写形式
  • isNull(): @return boolean 方便地检查指针是否为NULL
  • add(rhs), sub(rhs), and(rhs), or(rhs), xor(rhs): 思想同 Int64
  • shr(n), shl(n): 右移、左移 n bits
  • not(): 用此NativePointer的 位反转 来生成新的NativePointer
  • sign([key, data])
    • 通过获取此NativePointer的位并添加 pointer authentication 位,创建一个有符号指针,从而生成一个新的NativePointer
    • 如果当前进程不支持指针 pointer authentication,则此操作为空操作(no-op),返回此NativePointer而不是新值
    • 可选参数:
      • @params key: 可以指定为字符串。支持的值为:

        • ia: The IA key, for signing code pointers. This is the default.
        • ib: The IB key, for signing code pointers.
        • da: The DA key, for signing data pointers.
        • db: The DB key, for signing data pointers.
      • @params data: 指定为NativePointer /类似数字的值,以提供用于 signing 的额外数据,默认值为0
  • strip([key]): 通过获取此NativePointer的位并删除其指针 pointer authentication 位,从而创建原始指针,可以创建一个新的NativePointer。其他同 sign()
  • blend(smallInteger): 通过获取此NativePointer的位并将它们与常量混合(blending),从而生成一个新的NativePointer,可以作为 data 传递给 sign()
  • equals(rhs): @return boolean 指示与 rhs 是否等于,即 rhs 具有相同的指针值(pointer value)
  • compare(rhs): @return int, 与 String#localeCompare() 类似

类型转换

  • toInt32(): 将此NativePointer强制转换为带符号的32位整数
  • toString([radix = 16]): 转换为可选基数的字符串(默认为16)
  • toMatchPattern(): 返回一个字符串,该字符串包含此指针的原始值的 Memory.scan() 兼容匹配 pattern

r/w Pointer()

  • readPointer(): 从此内存位置读取一个NativePointer
    如果地址不可读,则会抛出JavaScript异常(下同)
  • writePointer(ptr): 将 ptr写入此 memory 位置
    如果地址不可写,则会抛出JavaScript异常(下同)

读写 number

  • readS8(), readU8(), readS16(), readU16(), readS32(), readU32(), readShort(), readUShort(), readInt(), readUInt(), readFloat(), readDouble(): 从此 memory 位置读取xxx值,并将其作为 number 返回
  • writeS8(value), writeU8(value), writeS16(value), writeU16(value), writeS32(value), writeU32(value), writeShort(value), writeUShort(value), writeInt(value), writeUInt(value), writeFloat(value), writeDouble(value): 将xxx值写入此 memory 位置

读写 大整数

  • readS64(), readU64(), readLong(), readULong(): @return Int64/UInt64
  • writeS64(value), writeU64(value), writeLong(value), writeULong(value)

读写 字节数组

  • readByteArray(length): 从此 memory 位置读取 length 字节,@return ArrayBuffer

    • 通过将此缓冲区(buffer)作为 send() 的第二个参数传递,可以有效地将此缓冲区 传输到(transferred to) 基于Frida的应用程序。
    • 如果从该地址读取的任何 length 字节都不可读,则会抛出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, or ANSI 字符串读取此内存位置的字节

    • 如果您知道字符串的大小(以字节为单位),则提供可选的 size参数
    • 如果字符串是NUL终止的 (NUL-terminated),则忽略它或指定 -1
    • 同样,如果您知道字符串的长度(以字符为单位),则可以提供可选的 length 参数
    • A JavaScript exception will be thrown if any of the size / length bytes read from the address isn’t readable.
    • 请注意,readAnsiString()仅在Windows上可用(并且相关)
  • writeUtf8String(str), writeUtf16String(str), writeAnsiString(str): 将JavaScript字符串编码并写入该存储位置(使用NUL终止符)
    • A JavaScript exception will be thrown if any of the bytes written to the address isn’t writable.
    • Note that writeAnsiString() is only available (and relevant) on Windows.

ArrayBuffer

  • wrap(address, size): 创建由 现有内存区域(existing memory region) 支持的ArrayBuffer

    • address: 指定区域基址的 NativePointer
    • size: 指定其大小
    • NativePointer 读/写 API不同,访问(access)时不执行任何验证(validation),这意味着 错误的指针将使进程崩溃
  • unwrap(): 返回一个 NativePointer,它指定 ArrayBuffer 的 备份存储(backing store) 的基址。
    • 调用方(caller) 有责任在 backing store 仍在使用时保持缓冲区的活动状态。

NativeFunction

  • new NativeFunction(address, returnType, argTypes[, abi]): 创建一个新的NativeFunction以在 address 处调用该函数(用NativePointer指定)

    • @params returnType: 指定返回类型
    • @params argTypes: 数组 指定参数类型
      • 如果不是系统默认值,您也可以选择指定 abi

      • 对于可变参数,请在固定参数和可变参数之间的 argTypes 中添加 '...' 条目。

      • STRUCTS & CLASSES BY VALUE

        • 对于按值传递的结构或类,请提供一个包含结构的字段类型(彼此跟随)的数组,而不是字符串。您可以根据需要将它们嵌套得足够深,以表示结构内部的结构。

        • 请注意,返回的对象也是 NativePointer,因此可以传递给 Interceptor#attach

        • 必须与 struct/class 完全匹配,因此,如果您的结构具有三个int,则必须传递 ['int', 'int', 'int']

        • 对于具有虚方法的类,第一个参数将是指向 [vtable]

        • 对于返回值大于 Process.pointerSize 的C++场景,必须传递NativePointer (预分配的空间)作为第一个参数。 (例如,这种情况在WebKit中很常见。)

        • 例子

          // LargeObject HandyClass::friendlyFunctionName();
          var friendlyFunctionName = new NativeFunction(friendlyFunctionPtr,'void', ['pointer', 'pointer']);
          var returnValue = Memory.alloc(sizeOfLargeObject);
          friendlyFunctionName(returnValue, thisPtr);
          
        • Supported Types

          • void
          • pointer
          • int
          • uint
          • long
          • ulong
          • char
          • uchar
          • 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]): 就像之前的构造函数一样,但是第四个参数
    • @param options是可能包含以下一个或多个 key 的对象:

      • abi: 与上面相同的 enum
      • scheduling: @string 调度行为。支持的值为:
        • cooperative: 允许其他线程在调用 native 函数时执行JavaScript代码,即在调用之前释放锁,然后重新获取它。这是默认行为
        • exclusive: 调用 native 函数时,不允许其他线程执行JavaScript代码,即保持JavaScript锁。这样速度更快,但可能导致死锁
      • exceptions: @string 异常行为。支持的值为:
        • steal: 如果被调用的函数产生 native 异常,例如通过取消对无效指针的引用,Frida将展开(unwind)堆栈 并 窃取(steal) 该异常,将其转换为可以处理的JavaScript异常。这可能会使应用程序处于未定义状态,但有助于避免实验过程崩溃。这是默认行为
        • propagate: 让应用程序处理函数调用期间发生的所有 native 异常。 (或者,通过 Process.setExceptionHandler()安装的处理程序。)
      • traps: @string 要启用的代码陷阱(code traps)。支持的值为:
        • default: 如果函数调用触发了任何钩子(hook),则将调用 Interceptor.attach() 回调
        • all: 除了 Interceptor 回调外,Stalker还可在每个函数调用期间被暂时重新激活。
          • 可用于:

            • 在引导模糊器 (fuzzer) 时测量代码覆盖率
            • 在 debugger 中实现 “step into”
            • etc
          • 请注意,当使用 JavaObjC API时,这也是可能的,因为方法包装器还提供了一个 clone(options) API来新建带有自定义NativeFunction选项的方法包装器

NativeCallback

  • new NativeCallback(func, returnType, argTypes[, abi]): 创建由JavaScript函数 func 实现的新 NativeCallback

    • @params returnType 同上
    • @params argTypes 同上
    • 请注意,返回的对象也是 NativePointer,因此可以传递给 Interceptor#replace
    • 当使用带有 Interceptor#replace 的回调时,将调用func,并将 this 绑定到具有一些有用属性的对象上,就像 Interceptor.attach() 中的那个一样。
    • When using the resulting callback with Interceptor.replace(), func will be invoked with this bound to an object with some useful properties, just like the one in Interceptor.attach().

SystemFunction

  • new SystemFunction(address, returnType, argTypes[, abi])

    • 就像 NativeFunction 一样,还提供了线程最后错误状态的快照
    • 返回值是一个将实际返回值包装为 value 的对象,另外还有一个名为errno(UNIX)或 lastError(Windows)的特定于平台的字段
  • new SystemFunction(address, returnType, argTypes[, options])
    • 与上述相同,但接受 像 NativeFunction 的相应构造函数的 options 对象

网络(*)

Socket

  • Socket.listen([options]): 打开一个TCP或UNIX侦听套接字。返回一个接收 SocketListenerPromise

    • 如果支持,则默认为同时侦听IPv4和IPv6,并在随机选择的TCP端口上的所有接口上进行绑定
    • 可选 options 参数是一个对象,可能包含以下某些key:
      • family: @string 地址族。支持的值为:

        • unix
        • ipv4
        • ipv6 如果支持,则默认为同时在 ipv4ipv6 上侦听
      • host: @string (IP family) IP地址。默认为所有接口
      • port: @number (IP family) IP端口。 默认为任何可用的
      • type: @string (UNIX family)UNIX套接字类型。支持的类型为:
        • anonymous | 匿名
        • path
        • abstract
        • abstract-padded | 抽象填充 默认为 path
      • path: @string (UNIX family)UNIX套接字路径
      • backlog: @number TCP参数——已完成TCP连接最大长度,默认为 10
        • 译者注:可参考 TCP Listen backlog
  • Socket.connect(options): 连接到TCP或UNIX服务器。返回一个接收 SocketConnectionPromise
    • @param options : 一个对象,应包含以下某些 key(解释同上, 区别会另外标注):

      • famaly: 其中 ipv6 默认为IP系列,具体取决于指定的host
      • host: 默认为 localhost
      • port
      • type
      • path
  • Socket.type(handle): 检查OS套接字 handle 并返回其类型
    • @ return string 以下之一

      • tcp, udp, tcp6, udp6, unix:stream, unix:dgram
      • null, 如果无效或未知
  • Socket.localAddress(handle), Socket.peerAddress(handle): 检查OS套接字 handle 并返回它的本地 (local) 或 对等(peer) 地址 或 null(如果无效或未知)。返回的对象具有以下字段:
    • ip: @string(IP套接字)IP地址
    • port: @number (IP套接字)IP端口
    • path: @string(UNIX套接字)UNIX路径

SocketListener

所有方法都是完全异步的,并返回Promise对象。

  • path: (UNIX系列)正在监听的路径
  • port: (IP系列)正在监听的IP端口
  • close(): 关闭 listener,释放与其相关的资源
    • 一旦关闭侦听器,所有其他操作将失败
    • 允许多次关闭侦听器,并且不会产生错误
  • accept(): 等待下一个客户端连接。返回的 Promise 接收SocketConnection

SocketConnection

继承自 IOStream。所有方法都是完全异步的,并返回Promise对象

  • setNoDelay(noDelay): 如果noDelay为true,则禁用Nagle算法,否则将其启用。

    • Nagle算法默认情况下处于启用状态,因此仅当您希望优化低延迟而不是高吞吐量时才需要调用此方法。

文件与流

File

  • new File(filePath, mode): 在 filePath 中打开或创建文件,并使用 mode 字符串指定应如何打开文件

    • 例如,"wb" 打开文件以二进制模式写入
    • 这与C标准库中的 fopen() 格式相同
  • write(data): 同步data 写入文件,其中 data 是 (二选一)
    • @string
    • NativePointer#readByteArray 返回的缓冲区
  • flush(): 将所有缓冲的数据刷新到 底层的(underlying)文件
  • close(): 关闭文件
    • 处理完文件后,应调用此函数
    • 除非 你对此事很好 对象被垃圾回收或脚本被卸载

IOStream

所有方法都是完全异步的,并返回Promise对象。

  • input: 要读取的 InputStream
  • output: 要写入的 OutputStream
  • close(): 关闭流,释放与其相关的资源
    • 这也将关闭各个输入和输出流
    • 一旦流关闭,所有其他操作都将失败
    • 允许多次关闭流,并且不会导致错误

InputStream

所有方法都是完全异步的,并返回Promise对象。

  • close(): 关闭流,释放与其相关的资源

    • 一旦关闭流,所有其他操作将失败
    • 允许多次关闭流,并且不会导致错误
  • read(size): 从流中读取最多 size 个字节
    • 返回的Promise接收一个 ArrayBuffer,长度最大为 size 个字节
    • 流的结尾通过一个空缓冲区发出信号
  • readAll(size): 一直从流中读取数据,直到正好消耗了 size 个字节
    • 返回的Promise接收一个 ArrayBuffer,长度最大为 size 个字节
    • 过早(Premature) 错误或流结束将导致 Promise 被拒绝并返回错误,
      • 其中 error 对象具有包含不完整数据的 partialData 属性

OutputStream

所有方法都是完全异步的,并返回Promise对象。

  • close(): 同 InputStream
  • write(data): 尝试将 data 写入流
    • @param data:

      • @ArrayBuffer
      • @ 一个介于0到255之间的整数数组
    • 返回的 Promise 接收一个 Number,指定将多少字节的 data 写入流
  • writeAll(data): 一直向流中写入,直到所有 data 都已写入
    • data 参数 同 write()
    • 过早的错误或流结束会导致错误
      • 其中 Error 对象具有 partialSize 属性,该属性指定在发生错误之前将多少字节的 data 写入流

UnixInputStream

(仅在类似UNIX的操作系统上可用。)

  • new UnixInputStream(fd[, options]): 从指定的文件描述符 fd 创建一个新的 InputStream

    • 您还可以提供一个 options 对象,将 autoClose 设置为 true,以在流被释放时通过 close()或将来的垃圾回收来使流关闭底层的文件描述符。(下同)

UnixOutputStream

(仅在类似UNIX的操作系统上可用。)

  • new UnixOutputStream(fd[, options]): 从指定的文件描述符 fd 创建一个新的 OutputStream

    • options 同上

Win32InputStream

(仅在Windows上可用。)

  • new Win32InputStream(handle[, options]): 从指定的 handle(Windows HANDLE 值)创建一个新的 InputStream

Win32OutputStream

(仅在Windows上可用。)

  • new Win32OutputStream(handle[, options]): 从指定的 handle(Windows HANDLE 值)创建一个新的OutputStream

数据库

SqliteDatabase

  • SqliteDatabase.open(path[, options]): 打开由 path指定的SQLite v3数据库

    • @param path @string 包含数据库的文件系统路径
    • 默认情况下,数据库将以 read-write 方式打开,但您可以通过为 options 对象提供 flags 的属性来自定义此行为,指定包含以下一个或多个值的字符串数组:
      • readonly
      • readwrite
      • create
    • 返回的 SqliteDatabase 对象将允许您对数据库执行查询
  • SqliteDatabase.openInline(encodedContents): 就像 open() 一样,但是数据库的内容以Base64编码的 string 形式提供
    • 我们建议在对Base64进行编码之前对数据库进行gzip压缩,但这是可选的,可以通过查找gzip magic 标记来检测到
    • 该数据库以读写方式打开,但是100%在内存中,并且从不接触文件系统
    • 这对于需要 捆绑(bundle) 预计算数据缓存的 代理(agents) 很有用,例如用于指导动态分析的静态分析数据
  • close(): 关闭数据库
    • 完成数据库操作后,应调用此函数
    • 除非在对象被垃圾回收或脚本被卸载时会发生这种情况,除非您对此情况感到满意。
  • exec(sql): 执行原始SQL查询,其中 sql 是包含查询的文本表示形式的字符串。查询结果将被忽略,因此,该结果仅应用于设置数据库的 queries,例如创建表。
  • prepare(sql): 将提供的SQL编译成 SqliteStatement 对象,其中 sql 是一个字符串,其中包含查询的文本表示形式。

举例:

var db, smt, row, name, bio;db = SqliteDatabase.open('/path/to/people.db');smt = db.prepare('SELECT name, bio FROM people WHERE age = ?');console.log('People whose age is 42:');
smt.bindInteger(1, 42);
while ((row = smt.step()) !== null) {name = row[0];bio = row[1];console.log('Name:', name);console.log('Bio:', bio);
}
smt.reset();
  • dump(): @return string 数据库 dump 到 Base64编码的gzip压缩的blob中

    • 这对于在通过调用 SqliteDatabase.openInline() 加载的 agent 代码中内联缓存很有用

SqliteStatement

  • bindInteger(index, value): bind the integer value to index
  • bindFloat(index, value): bind the floating point value to index
  • bindText(index, value): bind the text value to index
  • bindBlob(index, bytes): bind the blob bytes to index, where bytes is
    • an ArrayBuffer,
    • array of byte values
    • a string
  • bindNull(index): bind a null value to index
  • step(): 启动一个新查询并获取第一个结果,或移至下一个查询。
    • 返回包含按查询指定顺序排列的值的数组
    • 如果到达最后一个结果,则返回 null
    • 如果您打算再次使用此对象,则应在此时调用 reset()
  • reset(): 重置内部状态以允许后续查询

插桩/注入 Instrumentation

Interceptor 拦截器

  • Interceptor.attach(target, callbacks[, data]): 拦截对 target 函数的调用。

    • @param target

      • @NativePointer 指定要拦截其调用的函数的地址
      • 请注意,在32位ARM上
        • 对于ARM函数,此地址的最低有效位必须设置为0
        • 对于Thumb函数,此地址必须设置为1
        • 如果从Frida API(例如 Module.getExportByName())获取地址,则Frida会处理这个细节
    • @param callbacks: 一个包含以下一项或多项的对象:
      • onEnter: function (args): 给定一个参数 args的回调函数

        • args 可作为 NativePointer 对象数组 用于读取或写入 参数
        • 译者注:可以进入函数前 Hook so文件函数 的 参数
      • onLeave: function (retval): 给定一个参数 retval 的回调函数
        • retval 是一个包含原始返回值的 NativePointer 派生的对象

          • 可以调用 retval.replace(1337) 将返回值替换为整数 1337
          • retval.replace(ptr("0x1234")) 替换为指针。
          • 请注意,此对象在 onLeave 调用中回收,因此请勿在回调之外存储和使用它
          • 如果需要存储包含的值,请进行深拷贝,例如:ptr(retval.toString())
    • 如果被hook的函数经常被调用,则 onEnteronLeave 可能是指向使用CModule编译的 native C函数的 NativePointer 值,签名是
      • void onEnter (GumInvocationContext * ic)
      • void onLeave (GumInvocationContext * ic)
      • 在这种情况下,第三个可选参数 data 可以是 可通过gum_invocation_context_get_listener_function_data()访问的 NativePointer
    • 您也可以通过传递函数而不是 callbacks 对象来拦截任意指令(instructions)
      • 该函数具有与 onEnter 相同的签名
      • 但是传递给它的 args 参数只会在以下情况下为您提供合理(sensible)的值:
        • 截获的指令在某个函数的开头
        • 一个点,当寄存器/堆栈尚未偏离该点的位置时
      • 与上面一样,这个函数也可以通过指定 NativePointer 在C中实现,而不是指定一个函数。
    • @return 返回可以调用 detach() 的 listener 对象
    • 请注意,这些函数将被 this 绑定到一个每次调用(线程本地)的对象 来调用,您可以在其中存储任意数据,如果您想在 onEnter 中读取参数并在onLeave 中对其进行操作,那么这将非常有用
    • 举例
    Interceptor.attach(Module.getExportByName('libc.so', 'read'), {onEnter: function (args) {this.fileDescriptor = args[0].toInt32();},onLeave: function (retval) {if (retval.toInt32() > 0) {/* do something with this.fileDescriptor */}}
    });
    
  • 此外,this 对象包含一些有用的属性:

    • returnAddress: @NativePointer
    • context: 寄存器状态
    • errno: (UNIX)当前errno值(可以替换它)
    • lastError: (Windows)当前操作系统错误值(可以替换)
    • threadId: 操作系统线程ID
    • depth: 相对于其他调用的调用深度

    举例:

    Interceptor.attach(Module.getExportByName(null, 'read'), {onEnter: function (args) {console.log('Context information:');console.log('Context  : ' + JSON.stringify(this.context));console.log('Return   : ' + this.returnAddress);console.log('ThreadId : ' + this.threadId);console.log('Depth    : ' + this.depth);console.log('Errornr  : ' + this.err);// Save arguments for processing in onLeave.this.fd = args[0].toInt32();this.buf = args[1];this.count = args[2].toInt32();},onLeave: function (result) {console.log('----------')// Show argument 1 (buf), saved during onEnter.var numBytes = result.toInt32();if (numBytes > 0) {console.log(hexdump(this.buf, { length: numBytes, ansi: true }));}console.log('Result   : ' + numBytes);}
    })
    

性能考量


  • 提供的回调对性能有重大影响。如果您只需要 检查(inspect) 参数但不关心返回值,或者相​​反,请确保忽略不需要的回调;即避免将逻辑放入onEnter并将onLeave留在其中作为空回调

  • 在iPhone 5S上,仅提供 onEnter 时的基本开销可能约为6微秒,而同时提供 onEnteronLeave 则为11微秒。

  • 另外,在拦截 每秒调用数十亿次的函数 的调用时也要小心

    • 尽管 send() 是异步的,但发送单个消息的总开销 并未针对高频进行优化,所以这意味着 Frida让您 根据需要低延迟还是高吞吐量将多个值批处理到 单个 send()调用
  • Interceptor.detachAll(): 分离所有先前附加的回调

  • Interceptor.replace(target, replacement[, data]): 将 target 处的函数替换为 replacement 处的实现

    • 如果要完全或部分替换现有功能的实现,通常使用此方法
    • 使用 NativeCallback 在JavaScript中实现替换
    • 如果被替换的函数被经常调用,您可以使用 CModule 在C中实现替换
    • 然后,您还可以指定第三个可选参数 data
      • @NativePointer: 可通过 gum_invocation_context_get_listener_function_data() 访问
      • 使用 gum_interceptor_get_current_invocation() 获取 GumInvocationContext *
    • 请注意,替换将一直有效,直到调用 Interceptor#revert
    • 如果你想 链接(chain)到原始的实现,你可以通过你的实现中的一个 NativeFunction 同步调用 target ,这将绕过并直接转到原始的实现。这是一个例子:
    var openPtr = Module.getExportByName('libc.so', 'open');
    var open = new NativeFunction(openPtr, 'int', ['pointer', 'int']);
    Interceptor.replace(openPtr, new NativeCallback(function (pathPtr, flags) {var path = pathPtr.readUtf8String();log('Opening "' + path + '"');var fd = open(pathPtr, flags);log('Got fd: ' + fd);return fd;
    }, 'int', ['pointer', 'int']));
    
  • Interceptor.revert(target): 将 target 处的功能还原到先前的实现

  • Interceptor.flush(): 确保所有 挂起(pending) 的更改都已提交到内存中

    • 仅在少数情况下才需要执行此操作,例如 只是使用 NativeFunction 附加(attach())或替换(replace())要调用的函数。
    • 当前线程即将离开JavaScript runtime 或 调用 send()时,挂起的更改将自动刷新
      • 这包括在 send() 之上构建的任何API,例如

        • RPC 方法返回时
        • console API上调用任何方法时

Stalker

  • Stalker.exclude(range): 将指定的内存 range 标记为“excluded”

    • @param range: 具有 basesize 属性的对象,例如Process.getModuleByName()
    • 这意味着 Stalker 在遇到对这样一个范围内的指令的调用时不会跟随执行。因此,您将
      • 能够观察/修改传入的参数和返回的返回值
      • 但看不到之间发生的指令。
    • 有助于提高性能并减少噪音
  • Stalker.follow([threadId, options]): 开始跟踪 threadId(如果省略,则跟踪当前线程),可选择 options 来启用事件

    例如:

    const mainThread = Process.enumerateThreads()[0];Stalker.follow(mainThread.id, {events: {call: true, // CALL instructions: yes please// Other events:ret: false, // RET instructionsexec: false, // all instructions: 不推荐使用,因为有很多数据// 已执行块:粗略执行跟踪block: false, // block executed: coarse execution tracecompile: false // block compiled: useful for coverage(覆盖)},// 只指定以下两个回调中的一个// (见下面的注释)
    });
    
    • onReceive:

      • @param events: 由一个或多个 GumEvent 结构组成的二进制 blob
      • 有关格式的详细信息,请参阅 gumevent.h
      • 使用 Stalker.parse() 检查数据
      onReceive: function (events) {},
    
    • onCallSummary:

      • @param summary: 当前时间窗口中 键值映射(call target --> call 数量 )
      • 为了提高效率,通常会实现 onCallSummary() 而不是 onReceive(),也就是说,当只想知道调用了哪些目标以及调用了多少次时,却不关心调用的发生顺序。
      onCallSummary: function (summary) {},
    
  • transform: 高级用户:这是您可以插入自己的 StalkerTransformer 的方法

    • 当 Stalker 想要重新编译将由 stacked 线程执行的基本代码块时,将同步调用提供的函数。
    • 下面的示例显示了如何 在app自身内存范围内的 被跟踪的(stalked) 线程执行的任何代码中的 每条 ret 指令 之前插入自己的代码。
    transform: function (iterator) {var instruction = iterator.next();var startAddress = instruction.address;var isAppCode = startAddress.compare(appStart) >= 0 &&startAddress.compare(appEnd) === -1;do {if (isAppCode && instruction.mnemonic === 'ret') {iterator.putCmpRegI32('eax', 60);iterator.putJccShortLabel('jb', 'nope', 'no-hint');iterator.putCmpRegI32('eax', 90);iterator.putJccShortLabel('ja', 'nope', 'no-hint');iterator.putCallout(onMatch);iterator.putLabel('nope');}iterator.keep();} while ((instruction = iterator.next()) !== null);
    },
    
    • 它插入代码来检查 eax 寄存器是否包含一个介于60和90之间的值,并在这种情况下在JavaScript中插入一个同步调出(callout back)。回调接收一个参数,它可以访问CPU寄存器,并且还可以修改它们。
      默认的实现为:
    while (iterator.next() !== null)iterator.keep();
    
    • 请注意,不调用 keep()将导致指令被丢弃,这使得您的 transform 可以在需要时完全替换某些(certain)指令。
    • 想要更好的性能? 用C编写回调:
    #include <gum/gumstalker.h>
    static void on_ret(GumCpuContext *cpu_context,gpointer user_data);
    void transform(GumStalkerIterator *iterator,GumStalkerOutput *output,gpointer user_data)
    {cs_insn *insn;while (gum_stalker_iterator_next(iterator, &insn)){if (insn->id == X86_INS_RET){gum_x86_writer_put_nop(output->writer.x86);gum_stalker_iterator_put_callout(iterator,on_ret, NULL, NULL);}gum_stalker_iterator_keep(iterator);}
    }
    static void
    on_ret(GumCpuContext *cpu_context,gpointer user_data)
    {printf("on_ret!\n");
    }
    void process(const GumEvent *event,GumCpuContext *cpu_context,gpointer user_data)
    {switch (event->type){case GUM_CALL:break;case GUM_RET:break;case GUM_EXEC:break;case GUM_BLOCK:break;case GUM_COMPILE:break;default:break;}
    }
    
    const cm = new CModule('code');
    transform: cm.transform,
    onEvent: cm.process,
    data: ptr(1337) /* user_data */
    
    • 您也可以使用混合方法,只使用C编写一些标注。

性能考量


  • 提供的回调对性能有重大影响。如果您只需要周期性的调用 summaries ,而不关心原始事件,或者相反,请确保省略不需要的回调;即避免将逻辑放在 onCallSummary 中,而将 onReceive 作为空回调留在那里。
  • 还要注意 Stalker 可以与 CModule 一起使用,这意味着回调可以用C实现。
  • Stalker.unfollow([threadId]): 停止跟踪 threadId(或当前线程,如果省略)
  • Stalker.parse(events[, options]): 解析GumEvent二进制blob,可选地使用 options 自定义输出。举例:
 onReceive: function (events) {console.log(Stalker.parse(events, {annotate: true, // 显示事件类型stringify: true// 将指针值格式化为字符串而不是 `NativePointer`值// 也就是说,如果只使用 `send()` 而不是实际解析数据代理端,则可以减少开销}));},
  • Stalker.flush(): 清除所有缓冲事件。当您不想等到下一个Stalker.queueDrainInterval勾号(tick)时很有用。
  • Stalker.garbageCollect(): 在 Stalker#unfollow 之后的安全点释放可用的累积内存。这是为了避免竞争条件,在竞争条件下,刚被取消跟随的线程正在执行其最后一条指令。
  • Stalker.addCallProbe(address, callback[, data]): 调用 address 时同步调用callback(请参阅Interceptor#attach#onEnter以获取签名)。返回一个id,稍后可以传递给Stalker#removeCallProbe
    • 也可以使用CModule在C中实现 callback,方法是指定 NativePointer 而不是函数。签名:

      • void onCall (GumCallSite * site, gpointer user_data)
      • 在这种情况下,第三个可选参数 data 可能是 NativePointer,其值作为user_data传递给 callback
  • Stalker.removeCallProbe: 删除由 Stalker#addCallProbe 添加的调用探针
  • Stalker.trustThreshold: 一个整数,指定一段代码需要执行多少次,然后才可以信任它不会发生变化。
    • 指定-1表示不信任(慢)
    • 0表示从头开始信任代码
    • N表示在代码执行N次后信任代码。默认为1
  • Stalker.queueCapacity: 以事件数指定事件队列容量的整数。默认为16384个事件
  • Stalker.queueDrainInterval: 一个整数,指定每次清空事件队列之间的时间(以毫秒为单位)。 默认值为250 ms,这意味着事件队列每秒被耗尽四次。 您也可以将此属性设置为零以禁用定期清空,而当您希望清空队列时,可以调用Stalker.flush()

WeakRef

  • WeakRef.bind(value, fn): 监视 value 并在 value 被垃圾回收后立即调用 fn 回调,否则脚本将被卸载。

    • 返回一个ID,您可以将其传递给 WeakRef.unbind() 进行显式清理。
    • This API is useful if you’re building a language-binding, where you need to free native resources when a JS value is no longer needed. 如果您要构建语言绑定,并且当不再需要JS值时需要释放本地资源,则此API很有用。
  • WeakRef.unbind(id): 停止监视传递给 WeakRef.bind(value, fn)value,并立即调用 fn 回调

ObjC

MacOS平台,暂不研究

  • ObjC.available: @boolean 指定当前进程是否已加载 Objective-C runtime。除这种情况外,不要调用任何其他 ObjC属性或方法。

Java

  • Java.available: @boolean 指定当前进程是否加载了 Java VM,即Dalvik还是ART。除这种情况外,不要调用任何其他 Java 属性或方法

  • Java.androidVersion: @string 指定运行的Android版本

  • Java.enumerateLoadedClasses(callbacks): 枚举当前加载的类,其中 callbacks 是一个指定以下内容的对象:

    • onMatch: function (name, handle): 一个调用

      • @param name 每个加载的类的类名,可以传递给 Java.use() 获取JavaScript包装器。
      • 可以将 handle 转换(Java.cast())为 java.lang.Class
    • onComplete: function (): 当所有类都被枚举时调用
  • Java.enumerateLoadedClassesSync(): enumerateLoadedClasses() 的同步版本,返回包含类名的数组

  • Java.enumerateClassLoaders(callbacks): 枚举Java VM中存在的类加载器(class loaders),其中 callbacks 是一个指定以下内容的对象:

    • onMatch: function (loader): 一个调用

      • @param loader 加载的 class loader,是 特定 java.lang.ClassLoader 的包装
    • onComplete: function (): 当所有 class loader 都被枚举时调用

    注:可以将这样的 loader 传递给 Java.ClassFactory.get() 以便能够在指定的class loader 上 .use()

  • Java.enumerateClassLoadersSync(): 同步版本的……返回数组

  • Java.enumerateMethods(query): 列举匹配 query 的方法

    • @param query: 指定为 "class!method", 允许使用 globs
    • 也可以加 / 和一个或多个修饰符作为后缀:
      • i: 不区分大小写的匹配
      • s: 包括方法签名,如 "putInt" 变成 "putInt(java.lang.String, int): void"
      • u: 仅用户定义的类,忽略系统类
Java.perform(() => {const groups = Java.enumerateMethods('*youtube*!on*')console.log(JSON.stringify(groups, null, 2));
});
[{"loader": "<instance: java.lang.ClassLoader, $className: dalvik.system.PathClassLoader>","classes": [{"name": "com.google.android.apps.youtube.app.watch.nextgenwatch.ui.NextGenWatchLayout","methods": ["onAttachedToWindow","onDetachedFromWindow","onFinishInflate","onInterceptTouchEvent","onLayout","onMeasure","onSizeChanged","onTouchEvent","onViewRemoved"]},{"name": "com.google.android.apps.youtube.app.search.suggest.YouTubeSuggestionProvider","methods": ["onCreate"]},{"name": "com.google.android.libraries.youtube.common.ui.YouTubeButton","methods": ["onInitializeAccessibilityNodeInfo"]},…]}
]
  • Java.scheduleOnMainThread(fn): 在VM的主线程上运行fn

  • Java.perform(fn):

    • 确保当前线程已连接到VM并调用fn。 (在Java的回调中这不是必需的)
    • 如果app的类加载器尚不可用,将推迟调用 fn
    • 如果不需要访问app的类,请使用 Java.performNow()
    Java.perform(function () {var Activity = Java.use('android.app.Activity');Activity.onResume.implementation = function () {send('onResume() got called! Let\'s call the original implementation');this.onResume();};
    });
    
  • Java.performNow(fn): 确保当前线程连接到VM并调用 fn。(在Java的回调中这不是必需的)

  • Java.use(className):

    • 动态获取 className 的JavaScript包装器

      • 可以通过对其调用(call) $new()来调用(invoke)构造函数来实例化对象
      • 对实例调用 $dispose() 显式清理(或等待JavaScript对象被垃圾回收,或等待脚本卸载)
      • 静态和非静态方法都可用,甚至可以替换方法实现并从中引发异常:
    Java.perform(function () {var Activity = Java.use('android.app.Activity');var Exception = Java.use('java.lang.Exception');Activity.onResume.implementation = function () {throw Exception.$new('Oh noes!');};
    });
    
    • 默认情况下使用 app 的类加载器,但可以通过将不同的加载器实例分配给 Java.classFactory.loader 进行定制
    • 请注意,所有方法包装器都提供了一个 clone(options) API,用自定义的NativeFunction opetions 创建新的方法包装器
  • Java.openClassFile(filePath): 在 filePath 中打开 .dex 文件,使用以下方法返回一个对象:

    • load(): 将包含的类加载到VM中
    • getClassNames(): 获取可用类名的数组
  • Java.choose(className, callbacks): 通过扫描Java堆(heap) 来枚举 className类的活动实例(instance),其中 callbacks 是指定以下内容的对象:

    • onMatch: function (instance): 一个调用

      • @param instance: 找到的可以使用(ready-to-use)的活动实例,就像您使用此特定实例的原始 handle 调用 Java.cast()一样
      • 该函数可以返回字符串 stop 以尽早取消枚举
    • onComplete: function (): 列举所有实例后调用
  • Java.retain(obj): 复制JavaScript包装器 obj,以供以后在外部替换方法中使用

    Java.perform(function () {var Activity = Java.use('android.app.Activity');var lastActivity = null;Activity.onResume.implementation = function () {lastActivity = Java.retain(this);this.onResume();};
    });
    
  • Java.cast(handle, klass): 在给定 现有实例handleklass (从 Java.use()返回)下,创建一个JavaScript包装器,这样的包装器还有如下属性:

    • class: 用于获取其类的包装器
    • $className: @string 获取其类名
    var Activity = Java.use('android.app.Activity');
    var activity = Java.cast(ptr('0x1234'), Activity);
    
  • Java.array(type, elements): 创建 Java 数组,生成的Java数组的 行为(behaves)类似于JS数组,但是可以通过引用传递给Java API,以允许它们修改其内容

    • @param type: 指定类型
    • @param elements: JavaScript 数组
    var values = Java.array('int', [ 1003, 1005, 1007 ]);var JString = Java.use('java.lang.String');
    var str = JString.$new(Java.array('byte', [ 0x48, 0x65, 0x69 ]));
    
  • Java.isMainThread(): 确定 调用者(caller)是否正在主线程上运行

  • Java.registerClass(spec): 创建一个新的Java类并为其返回一个包装器,其中spec是一个包含以下内容的对象:

    • name: @string 指定类的名称
    • superClass: (可选) 父类。如果忽略则从 java.lang.Object 继承
    • implements: (可选) 此类实现的接口数组
    • fields: (可选) @Object 指定要公开的每个字段的名称和类型
    • methods: @Object 指定要实现的对象的方法
var SomeBaseClass = Java.use('com.example.SomeBaseClass');
var X509TrustManager = Java.use('javax.net.ssl.X509TrustManager');var MyTrustManager = Java.registerClass({name: 'com.example.MyTrustManager',implements: [X509TrustManager],methods: {checkClientTrusted: function (chain, authType) {},checkServerTrusted: function (chain, authType) {},getAcceptedIssuers: function () {return [];},}
});var MyWeirdTrustManager = Java.registerClass({name: 'com.example.MyWeirdTrustManager',superClass: SomeBaseClass,implements: [X509TrustManager],fields: {description: 'java.lang.String',limit: 'int',},methods: {$init: function () {console.log('Constructor called');},checkClientTrusted: function (chain, authType) {console.log('checkClientTrusted');},checkServerTrusted: [{returnType: 'void',argumentTypes: ['[Ljava.security.cert.X509Certificate;', 'java.lang.String'],implementation: function (chain, authType) {console.log('checkServerTrusted A');}}, {returnType: 'java.util.List',argumentTypes: ['[Ljava.security.cert.X509Certificate;', 'java.lang.String', 'java.lang.String'],implementation: function (chain, authType, host) {console.log('checkServerTrusted B');return null;}}],getAcceptedIssuers: function () {console.log('getAcceptedIssuers');return [];},}
});
  • Java.deoptimizeEverything(): 强制 VM 使用其解释器执行所有操作

    • 在某些情况下,为了防止 绕过(bypassing)方法 hooks 进行优化(optimization),必须使用 ART 的 Instrumentation APIs 来跟踪 runtime
  • Java.vm: @Object 带有如下方法:
    • perform(fn): 确保当前线程已连接到VM并调用fn (在Java回调中这不是必需的)
    • getEnv(): 获取当前线程的 JNIEnv 的包装器。如果当前线程未附加到VM,则抛出异常
    • tryGetEnv(): 尝试获取当前线程的 JNIEnv 的包装器。如果当前线程未附加到VM,则返回null
  • Java.classFactory
    • 用于 implement 的 默认的 class factory,如 Java.use()
    • 使用应用程序(application) 的主(main) 类加载器
  • Java.ClassFactory: @class 具有如下属性:
    • get(classLoader): 获取给定 classLoader 的 class factory 实例

      • 在后台使用的默认 class factory 只与 应用程序(application) 的 main class loader 交互
      • 其他类加载器可以通过 Java.enumerateClassLoaders() 发现,通过这个API与之交互。
    • loader: 只读属性
      • 为当前使用的 class loader 提供包装
      • 对于默认的 class factory,这是通过第一次调用 Java.perform() 来更新的
    • cacheDir:
      • @string 当前正在使用的缓存目录的路径
      • 对于默认的 class factory,这是通过第一次调用 Java.perform() 来更新的
    • tempFileNaming: @object 指定用于临时文件的命名约定。默认为 { prefix: 'frida', suffix: 'dat' }
    • use(className): 类似于 Java.use(),但针对特定的 class loader,下同
    • openClassFile(filePath): 类似于 Java.openClassFile()
    • choose(className, callbacks)
    • retain(obj)
    • cast(handle, klass)
    • array(type, elements)
    • registerClass(spec)

CPU指令

Instruction

  • Instruction.parse(target): 在 NativePointer 表示的内存中的 target 地址处解析指令

    • 请注意,在32位ARM上,对于ARM函数,此地址的最低有效位必须设置为0,对于Thumb函数,此地址必须设置为1。略
    • @return object 具有以下字段:
      • address: @NativePointer该指令的地址(EIP)
      • next: 指向下一条指令的指针,因此可以 parse()
      • size: 该指令的大小
      • mnemonic: @string 指令助记符的表示
      • opStr: @string 指令操作数的表示
      • operands: @object[] 描述每个操作数,每个操作数至少指定 typevalue,但也可能根据体系结构具有其他属性
      • regsRead: 此指令 隐式(implicitly) 读取的 寄存器名数组
      • regsWritten: 该指令隐式写入的 寄存器名称数组
      • groups: 该指令所属的 组名数组
      • toString(): 转换为人类可读的字符串
    • 有关 operandsgroups 的详细信息,请参阅体系结构的 Capstone 文档。

暂不深入

##  Instruction##  X86Writer##  X86Relocator##  x86 enum types##  ArmWriter##  ArmRelocator##  ThumbWriter##  ThumbRelocator##  ARM enum types##  Arm64Writer##  Arm64Relocator

其他

Console

  • console.log(line), console.warn(line), console.error(line):

    • line 写到基于Frida的应用程序的控制台
    • 确切的行为取决于 frida-core 的集成位置,例如:
      • 当通过 frida python 使用Frida时,此输出将转到 stdoutstderr
      • 在使用 frida qml 时,此输出将转到 qDebug ,等等。
    • 属于 ArrayBuffer 对象的参数 将被具有默认选项的 hexdump() 结果替代。

Hexdump

  • hexdump(target[, options]):

    • 从提供的 ArrayBufferNativePointer target 生成一个十六进制转储
    • 可选参数 options 用于定制输出
var libc = Module.findBaseAddress('libc.so');
console.log(hexdump(libc, {offset: 0,length: 64,header: true,ansi: true
}));
           0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F  0123456789ABCDEF
00000000  7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00  .ELF............
00000010  03 00 28 00 01 00 00 00 00 00 00 00 34 00 00 00  ..(.........4...
00000020  34 a8 04 00 00 00 00 05 34 00 20 00 08 00 28 00  4.......4. ...(.
00000030  1e 00 1d 00 06 00 00 00 34 00 00 00 34 00 00 00  ........4...4...

简写

  • int64(v)
  • int64(v)
  • ptr(s): new NativePointer(s)
  • NULL: ptr("0")

主机与注入进程之间的通信

  • recv([type, ]callback)

    • 从基于 frida 的应用程序接收到下一条消息时,请求callback
    • @param type (可选)只接收 type 字段设置为 type 的消息
    • 这只会收到一条消息,需要再次调用recv()才能收到下一条消息
  • send(message[, data])
    • 将JavaScript对象 message 发送到基于Frida的应用程序(它必须可序列化为JSON)
    • 如果还有一些原始的二进制数据要与之一起发送,例如,使用 NativePointer#readByteArray dump 了一些内存,那么可以通过可选的 data 参数传递这个数据。这要求 data 是一个 ArrayBuffer 或0到255之间的整数数组

性能考量


尽管 send() 是异步的,但发送单个消息的总开销 并未针对高频进行优化,所以这意味着 Frida让您 根据需要低延迟还是高吞吐量将多个值批处理到 单个 send()调用

  • rpc.exports: 可以替换或插入的空对象,以向应用程序公开RPC样式的API

    • key:方法名称,value: 导出的函数
    • 此函数可以返回
      • 一个 普通(plain) 值,这样可以立即将其返回给调用方
      • 或者异步返回 Promise 给调用方

    例子:

rpc.exports = {add: function (a, b) {return a + b;},sub: function (a, b) {return new Promise(function (resolve) {setTimeout(function () {resolve(a - b);}, 100);});}
};

从使用Node.js绑定的应用程序中,此API将按如下方式使用:

const frida = require('frida');
const fs = require('fs');
const path = require('path');
const util = require('util');const readFile = util.promisify(fs.readFile);let session, script;
async function run() {const source = await readFile(path.join(__dirname, '_agent.js'), 'utf8');session = await frida.attach('iTunes');script = await session.createScript(source);script.message.connect(onMessage);await script.load();console.log(await script.exports.add(2, 3));console.log(await script.exports.sub(5, 3));
}run().catch(onError);function onError(error) {console.error(error.stack);
}function onMessage(message, data) {if (message.type === 'send') {console.log(message.payload);} else if (message.type === 'error') {console.error(message.stack);}
}

Python版本将非常相似:

import codecs
import fridadef on_message(message, data):if message['type'] == 'send':print(message['payload'])elif message['type'] == 'error':print(message['stack'])session = frida.attach('iTunes')
with codecs.open('./agent.js', 'r', 'utf-8') as f:source = f.read()
script = session.create_script(source)
script.on('message', on_message)
script.load()
print(script.exports.add(2, 3))
print(script.exports.sub(5, 3))
session.detach()
  • 在上面的示例中,我们使用 script.on('message', on_message) 监视来自注入过程(JavaScript端)的任何消息
  • 还可以在 scriptsession 上监视其他通知(notifications)
  • 如果要在目标进程退出时收到通知,请使用 session.on('detached', your_function)

Timing events

  • setTimeout(func, delay[, ...parameters])
  • clearTimeout(id)
  • setInterval(func, delay[, ...parameters])
  • clearInterval(id)
  • setImmediate(func[, ...parameters]): 尽快安排对Frida的JavaScript线程调用“func”,可以选择传递一个或多个“parameters”。
  • clearImmediate(id)

Garbage collection

  • gc(): 强制垃圾收集

    • 对于测试 WeakRef.bind () 逻辑非常有用
    • 但有时在使用 Duktape runtime和它的默认GC试探法时也需要

Frida JavaScript API学习相关推荐

  1. 百度地图JavaScript API 学习之浏览器定位

    浏览器定位示例 百度地图API官方的所有demo示例--请直戳这里 官方浏览器定位demo示例--请直戳这里 后来发现的问题,这里记录一下: 在使用百度地图JS API时,无意中发现谷歌浏览器的浏览器 ...

  2. 百度地图JavaScript API 学习之地址解析

    获取地图数据之地址解析 首先我们需要知道如何进行地址解析以及有哪些地址解析的方式. 其实,百度地图API给我们提供了2种解析方式:地址解析和逆地址解析. 下面我们来认识一下它们.[官方的介绍地址请点这 ...

  3. 百度地图JavaScript API 学习之添加控件

    创建地图之添加控件 官方demo示例和讲解--直戳这里 这个案例就比较简单了,只需要在地图展示的基础上,添加一些与地图控件相关的代码就行了. 说明: 1.可以使用Map.addControl()方法向 ...

  4. 百度地图JavaScript API 学习之根据IP定位

    IP定位示例 官方浏览器定位demo示例--请直戳这里 代码示例 <!DOCTYPE html> <html lang="en"> <head> ...

  5. 百度地图JavaScript API 学习之自定义标注图标(一)

    地图绘制之添加自定义标注图标(一) 通过Icon类可实现自定义标注的图标.附上Icon的类参考链接,自行查看 官方提供了两种方法: 通过参数MarkerOptions的icon属性进行设置 使用Mar ...

  6. javascript:常用API学习Math.random, toString,slice(),substr(),Math.ceil()

    javascript:常用API学习 1.获得随机数:Math.random() 如何随机获得整数? 2.如何转进制:十进制转二进制?: 变量名.toString(进制数) 3. 36进制:能把一个小 ...

  7. 使用基于 WebRTC 的 JavaScript API 在浏览器环境里调用本机摄像头

    HTML5,JavaScript 和现代浏览器这套三驾马车的组合,使得传统的 Web 应用较之过去能实现更多更丰富的同用户交互的功能.摄像头如今已成为智能手机的标配,前端 Web 应用也出现了越来越多 ...

  8. Google maps javascript api v3 叠加层(Overlays)介绍

    很多人尝试google maps api的开发,通常会涉及到在Google maps上进行标注功能的开发.Helloj2ee学习一项技术通常不在看书,而是多以帮助为主.当我看完之后,我将Overlay ...

  9. 百度地图JavaScript API GL

    介绍 百度地图JavaScript API GL v1.0是一套由JavaScript语言编写的应用程序接口. 下面通过一个简单的示例,来学习一下快速创建一张"我的地图", 百度地 ...

最新文章

  1. 英语学习过程中的几点体会(1)
  2. 从头学习计算机网络_我如何通过从头开始构建网络爬虫来自动进行求职
  3. 音频放大电路_低音升压功率放大器电子电路的完整设计
  4. robotframework自动化测试修炼宝典_自动化测试之框架Cucumber和RobotFramework的实战对比...
  5. webserver 交互方式说明
  6. 译文丨伯克利对serverless的看法:简化云编程
  7. 将list的内容转换成固定个数的分组字符串
  8. CodeForces - 1013B And 与运算暴力
  9. [SSH] 设置密钥登陆
  10. mac java安全_关于 Java for Mac OS X 10.4 发行版 7 的安全性内容
  11. scala yield入门详解
  12. 小波包分解、重构 matlab代码
  13. mui架构app-终章(我是怎样决定放弃mui)
  14. EXCEL 利用随机数公式生成随机字母、随机密码
  15. Matlab并行编程
  16. java 微信 图灵机器人_SAE服务下用java实现微信公众账号图灵机器人
  17. 2012-2-25 《数据结构》读书笔记3 栈之迷宫求解
  18. 在php中.=什么意思,在算法中mod是什么意思?
  19. 项目经理需要掌握的硬技能和软技能
  20. java断点续传_用Java实现断点续传(HTTP)

热门文章

  1. chatgpt系列文章-23.2.15(主要还在发现chatgpt的不足,偏探索,像报告)
  2. 工业AI落地,为什么选择华为云EI工业智能体?
  3. Android APK 瘦身
  4. touches和targetTouches的区别
  5. MATLAB荷花变色背景不变
  6. RALM: 实时 Look-alike 算法在微信看一看中的应用
  7. HP服务器硬盘坏了一块,教你如何快速更换
  8. 7. EAL parameters(dpdk参数介绍)
  9. 前端使用print,打印页面功能
  10. 关于Weblogic8.1配置的总结 和下载