又到了发布新版本的时候。这次为构建 Frida 工具的开发者带来了一些重要的新功能,另外还有几项好东西。先从第一部分开始。
毫无疑问,Frida 的 JavaScript API 层级较低,其目的只是提供不局限于某一特定用例的底层构建块。例如,如果你的用例是在 iOS 上截屏,这并不是人们会期待 Frida 本身直接提供的功能。
你可能会问,具有共同功能的不同工具该如何共享 agent 代码。幸运的是,答案不是“复制粘贴”。我们正在形成一个不断壮大的 Frida 专用库生态系统,其中包括 frida-screenshot、frida-uikit、frida-trace 等。
你们有些人可能对为 Java、.NET、Python、Ruby 或 Perl 编写的后端软件插桩所需的 API 感兴趣;也可能想跨不同操作系统和库跟踪密码学 API,或实现其他很酷的想法。我强烈建议把模块发布到 npm,也可以将其命名为 frida-$name,方便他人发现。
现在你可能会问:“但 Frida 不支持 require(),我一开始该如何把 agent 代码拆成多个文件?”问得好!这正是实用的小型命令行工具 frida-compile 登场的地方。
向它提供一个 .js 文件,它会把该文件依赖的其他文件打包为一个文件。与使用 cat 自制的拼接方案不同,最终结果还会嵌入 source map,因此堆栈跟踪中的文件名和行号具有实际意义。各模块也被隔离在独立闭包中,变量不会相互冲突。你还可以使用最新的 JavaScript 语法,例如箭头函数、解构和生成器函数,因为它会将代码编译为 ES5 语法。这意味着代码也能在基于 Duktape 的运行时上执行;在受限 iOS 设备上使用 Frida,或在运行 iOS >= 9 的越狱设备上使用 Frida 时,你必须使用该运行时。
为了缩短开发反馈周期,frida-compile 还通过 -w 提供监视模式,使你在开发 agent 时立即获得增量构建。
理论说得够多了。下面看看如何使用 npm 上现成的 Web 应用框架,并把它注入任意进程。
首先确认已经安装最新版 Node.js。然后创建一个空目录,将以下内容粘贴到名为 “package.json” 的文件中:
{
"name": "hello-frida",
"version": "1.0.0",
"scripts": {
"prepublish": "npm run build",
"build": "frida-compile agent -o _agent.js",
"watch": "frida-compile agent -o _agent.js -w"
},
"devDependencies": {
"express": "^4.14.0",
"frida-compile": "^2.0.6"
}
}然后把以下代码粘贴到 agent.js:
const express = require('express');
const app = express();
app
.get('/ranges', (req, res) => {
res.json(Process.enumerateRangesSync({
protection: '---',
coalesce: true
}));
})
.get('/modules', (req, res) => {
res.json(Process.enumerateModulesSync());
})
.get('/modules/:name', (req, res) => {
try {
res.json(Process.getModuleByName(req.params.name));
} catch (e) {
res.status(404).send(e.message);
}
})
.get('/modules/:name/exports', (req, res) => {
res.json(Module.enumerateExportsSync(req.params.name));
})
.get('/modules/:name/imports', (req, res) => {
res.json(Module.enumerateImportsSync(req.params.name));
})
.get('/objc/classes', (req, res) => {
if (ObjC.available) {
res.json(Object.keys(ObjC.classes));
} else {
res.status(404).send('Objective-C runtime not available in this process');
}
})
.get('/threads', (req, res) => {
res.json(Process.enumerateThreadsSync());
});
app.listen(1337);一步安装 frida-compile 并构建 agent:
$ npm install然后把生成的 _agent.js 加载到正在运行的进程:
$ frida Spotify -l _agent.js现在可以向它发送 HTTP 请求:
$ curl http://127.0.0.1:1337/ranges
$ curl http://127.0.0.1:1337/modules
$ curl http://127.0.0.1:1337/modules/libSystem.B.dylib
$ curl http://127.0.0.1:1337/modules/libSystem.B.dylib/exports
$ curl http://127.0.0.1:1337/modules/libSystem.B.dylib/imports
$ curl http://127.0.0.1:1337/objc/classes
$ curl http://127.0.0.1:1337/threads太棒了。我们用不到 50 行代码就构建了一个包含 7 个不同端点的进程检查 REST API。更酷的是,我们使用了一个为 Node.js 编写的现成 Web 应用框架。实际上,任何依赖 Node.js 内置 net 和 http 模块的现有模块都可以使用,例如 FTP 服务器、IRC 客户端或 NSQ 客户端。
在本版本之前,你可以使用前面提到的 Frida 专用模块,也可以使用 npm 上其他数千个模块,因为它们大多不执行 I/O。现在,本版本还允许使用所有基于 net 和 http 的模块,为 Frida 开启了更多很酷的用例。
如果你好奇它是如何实现的:我向 Frida 添加了 Socket.listen() 和 Socket.connect()。它们是对 GIO 的 SocketListener 和 SocketClient 的轻量封装;这些组件本来就是 Frida 技术栈的一部分,也已供 Frida 自身使用。因此体积保持不变,没有新增依赖。由于 frida-compile 底层使用 browserify,我们只需为 net 和 http 接入自己的内置模块。我直接移植了 Node.js 自身原有的 net 和 http 模块。
本版本还带来了一些其他改进。NativeFunction 长期以来有一个限制:调用要求读取 errno(UNIX)或调用 GetLastError()(Windows)的系统 API 时很难处理。问题在于,从调用 NativeFunction 到尝试读取错误状态之间,Frida 自身的代码可能会破坏当前线程的错误状态。
于是 SystemFunction 登场了。它与 NativeFunction 完全相同,区别在于调用会返回一个对象,其中封装返回值以及紧随其后的错误状态。示例如下:
const open = new SystemFunction(
Module.getExportByName(null, 'open'),
'int',
['pointer', 'int']);
const O_RDONLY = 0;
const path = Memory.allocUtf8String('/inexistent');
const result = open(path, O_RDONLY);
console.log(JSON.stringify(result, null, 2));
/*
* Which on Darwin typically results in the following output:
*
* {
* "value": -1,
* "errno": 2
* }
*
* Where 2 is ENOENT.
*/本版本还允许从传给 Interceptor.replace() 的 NativeCallback 中读取和修改系统错误值,这在替换系统 API 时很有用。请注意,使用 Interceptor.attach() 原本就能这样做,但如果不希望调用原始函数,它就不适用。
另一项值得一提的重要变化是 V8 运行时经过了大幅重构。代码现在更容易理解,添加新功能所需的工作也少得多。不仅如此,参数解析现在还由单一代码路径处理。因此所有 API 对错误或缺失参数都更稳健:忘记参数时会得到 JavaScript 异常,而不是因为某些 API 检查较少而直接让目标进程崩溃。
以上就是亮点。完整变更汇总如下:
8.1.0:
- core:添加 Socket.listen() 和 Socket.connect()
- core:添加 setImmediate() 和 clearImmediate()
- core:改进 set{Timeout,Interval}() 以支持传递参数
- core:修复 Interceptor 脏状态逻辑中与性能有关的错误
8.1.1:
- core:添加 Script.nextTick()
8.1.2:
- core:使 Socket.listen() 和 Socket.connect() 支持 UNIX socket
- core:修复替换函数中 this.errno / this.lastError 的处理
- core:添加 SystemFunction API,以在返回时取得 errno / lastError
- core:修复使用 Stream API 执行 I/O 期间调用 close() 时的崩溃
- core:修复并统一 V8 运行时的参数处理
8.1.3:
- core:暂时在 macOS 上禁用 Mapper,以确认它是否为已报告稳定性问题的根本原因
- core:向 NativeFunction 添加 .call() 和 .apply()
- objc:修复不透明结构体类型的解析
8.1.4:
- core:修复 V8 运行时因错误使用 v8::Eternal 导致的崩溃
- frida-repl:通过 -e 和 -q 添加批处理模式支持
8.1.5:
- node:只为 6.0(LTS)和 7.0 生成预构建文件
8.1.6:
- node:除 6.0 和 7.0 外,也为 4.0 和 5.0 生成预构建文件
8.1.7:
- objc:修复代理某些代理对象时的无限递归
- objc:支持代理非 NSObject 实例
- python:修复移除成员函数形式的信号回调
8.1.8:
- core:实现对单指令 ARM 函数的挂钩
- core:修复某些体系结构上处理不可挂钩函数时的泄漏
- core:修复 setImmediate() 回调处理行为
- core:修复 setTimeout() 中的泄漏
- core:修复 Duktape 运行时处理 setTimeout(0) 和 setImmediate() 时的竞态条件
- core:修复 Duktape 运行时处理 tick 回调时的崩溃
- core:修复 Duktape 运行时中的生命周期问题
- core:修复 Linux 上报告的模块大小
- core:修复较新版 Android 上启动应用时的崩溃
- core:修复尝试启动未安装 Android 应用时的处理
- core:通过动态检测 Dalvik 和 ART 字段偏移,提高对不同 Android 版本和变体的兼容性
- core:修复较新版 Android 上的卸载问题;该问题导致只有第一次 attach() 成功,后续尝试全部超时
- core:将 ObjC 和 Java 移至各自发布到 npm 的模块,并使用 frida-compile 继续将其编入 Frida 内置 JS 运行时
- java:通过动态检测 ArtMethod 字段偏移提高 ART 兼容性
- node:更新依赖
- node:修复未处理的 Promise rejection 问题
8.1.9:
- core:修复脚本卸载时竞态条件导致的释放后使用
8.1.10:
- core:使 ApiResolver 和 DebugSymbol API 可抢占,以避免死锁
8.1.11:
- core:在 macOS 和 iOS 上使用 Mach 异常处理程序,使我们能够可靠捕获已有自身 Mach 异常处理程序的应用中的异常
- core:修复 Duktape 运行时 InvocationContext 写时复制逻辑中的泄漏;该逻辑用于通过 this 在 onEnter 和 onLeave 之间存储数据
8.1.12:
- core:修复 V8 运行时中的 Interceptor 参数替换问题;此前参数只会在第一次被替换
祝使用愉快!
oleavr