是时候彻底改造 spawn() API,并修正 spawn gating 和 child gating API 中一些不够完善的地方了。
启动进程(spawn())
假设你正在使用 Frida 的 Python 绑定,目前会这样做:
pid = device.spawn(["/bin/cat", "/etc/passwd"])或者这样启动一个 iOS 应用:
pid = device.spawn(["com.apple.mobilesafari"])嗯,这个 API 真正能做的差不多也就这些了……只有一项例外,而 Python 和 Node.js 绑定还没有公开它。稍后我们会讲到。在此之前,先看看 frida-core 中的底层 API,各语言绑定正是将它公开给不同语言:
namespace Frida {
…
public class Device : GLib.Object {
…
public async uint spawn (string path,
string[] argv, string[] envp)
throws Frida.Error;
public uint spawn_sync (string path,
string[] argv, string[] envp)
throws Frida.Error;
}
…
}顺便说一下,这是 Vala 代码,frida-core 正是用这种语言编写的。它是一种类似 C#、可以编译为 C 的语言,而且非常棒。不过说远了。第一个方法 spawn() 是异步的,允许调用线程在调用进行期间处理其他事情;而 spawn_sync() 会阻塞,直到操作完成。
这两个方法会编译为下面三个 C 函数:
void frida_device_spawn (FridaDevice * self,
const gchar * path,
gchar ** argv, int argv_length,
gchar ** envp, int envp_length,
GAsyncReadyCallback callback, gpointer user_data);
guint frida_device_spawn_finish (FridaDevice * self,
GAsyncResult * result, GError ** error);
guint frida_device_spawn_sync (FridaDevice * self,
const gchar * path,
gchar ** argv, int argv_length,
gchar ** envp, int envp_length,
GError ** error);前两个函数共同构成 spawn():你先调用第一个函数并传入回调;回调被调用后,再调用第二个函数 spawn_finish(),并传入回调收到的 GAsyncResult。返回值是 PID;如果操作失败,则由 error 输出参数说明出了什么问题。如果你好奇,这就是 GIO 的异步模式。
至于第三个函数 spawn_sync(),Frida 的 Python 绑定使用的就是它。我们的 Node.js 绑定实际使用前两个函数,因为这些绑定完全异步。将来,如果能通过集成 Python 3.5 引入的 async/await 支持,也把 Python 绑定迁移为完全异步,那会很不错。
言归正传,回到上面的示例。我提到过,有一项功能尚未公开。仔细查看 frida-core API,你会注意到其中有一个 envp 字符串数组。查看绑定的内部实现便会发现,我们确实没有公开它,实际上做的是:
envp = g_get_environ ();
envp_length = g_strv_length (envp);这意味着,我们只是把 Python 进程碰巧拥有的环境原样传了过去。如果实际的启动操作发生在完全不同的系统上,例如连接的 iOS 或 Android 设备上,这显然很不妥。这个问题之所以没有那么严重,是因为启动 iOS 和 Android 应用时会忽略 envp,只有启动普通程序时才使用它。
旧 API 的另一个问题是,声明 string[] envp 意味着它不可为空;如果声明为 string[]? envp,它才可以为空。因此,我们无法区分“不提供任何环境”(直觉上意味着“使用默认值”)和“使用空环境”这两种意图。
在着手修复 API 的这一点时,我意识到,是时候一并解决它长期存在的其他几个问题了,例如允许:
- 在默认环境的基础上仅提供少数额外环境变量
- 设置工作目录
- 自定义 stdio 重定向
- 传递特定于平台的选项
此前,我们一直将 stdio 重定向到自己的管道,并通过 Device 上的 output 信号传输所有输出。另外还有 Device.input(),用于写入 stdin。这些 API 保持不变,唯一的区别是我们不再默认执行这种重定向。不过,你们中的大多数人大概并未深受其扰,因为我们此前没有为 iOS 和 Android 应用实现这种重定向。从这个版本开始,我们终于为 iOS 应用实现了它。
现在,你大概已经想知道新 API 长什么样了。来看看:
namespace Frida {
…
public class Device : GLib.Object {
…
public async uint spawn (string program,
Frida.SpawnOptions? options = null)
throws Frida.Error;
public uint spawn_sync (string program,
Frida.SpawnOptions? options = null)
throws Frida.Error;
}
…
public class SpawnOptions : GLib.Object {
public string[]? argv { get; set; }
public string[]? envp { get; set; }
public string[]? env { get; set; }
public string? cwd { get; set; }
public Frida.Stdio stdio { get; set; }
public GLib.VariantDict aux { get; }
public SpawnOptions ();
}
…
}回到开头的 Python 示例,它们仍然可以原样工作。不过,除了:
device.spawn(["com.apple.mobilesafari"])现在也可以这样做:
device.spawn("com.apple.mobilesafari")因为第一个参数是要启动的 program。你仍然可以在这里传入 argv,它将用于设置 argv 选项,也就是说,argv[0] 会被用作 program 参数。还可以这样做:
device.spawn("/bin/busybox", argv=["/bin/cat", "/etc/passwd"])如果想替换整个环境,而不是使用默认值:
device.spawn("/bin/ls", envp={ "CLICOLOR": "1" })不过,在大多数情况下,你可能只想增加或覆盖几个环境变量,现在这也可以做到:
device.spawn("/bin/ls", env={ "CLICOLOR": "1" })你可能还想使用不同的工作目录:
device.spawn("/bin/ls", cwd="/etc")或者,你或许想重定向 stdio:
device.spawn("/bin/ls", stdio="pipe")正如之前提到的,stdio 的默认值是 inherit。
现在,我们已经介绍了所有 SpawnOptions,只剩最后一项:aux。这是一个存放平台特定选项的字典。使用 Python 绑定设置这些选项非常简单:任何无法识别的关键字参数最终都会进入这个字典。
例如,启动 Safari 并让它打开指定 URL:
device.spawn("com.apple.mobilesafari", url="https://frida.re")或者,你可能想在禁用 ASLR 的情况下启动 i/macOS 程序:
device.spawn("/bin/ls", aslr="disable")另一个例子是使用指定 activity 启动 Android 应用:
spawn("com.android.settings", activity=".SecuritySettings")以上其实就是我们目前支持的全部 aux 选项。好处在于,我们可以增加新选项,而不必更新绑定。
不过,在继续之前,先快速看看使用 Node.js 绑定时这个新 API 会是什么样子:
const pid = await device.spawn('/bin/sh', {
argv: ['/bin/sh', '-c', 'ls /'],
env: {
'BADGER': 'badger-badger-badger',
'SNAKE': true,
'MUSHROOM': 42,
},
cwd: '/usr',
stdio: 'pipe',
aslr: 'auto'
});如你所见,第二个参数是选项对象,无法识别的选项会进入 aux 字典。
11.0.0 中的其余变化
简单总结一下其余变化,先从 Device 类开始:
- 为了符合语法,enumerate_pending_spawns() 现已改名为 enumerate_pending_spawn()。
- spawned 信号已重命名为 spawn-added,并且现在还新增了 spawn-removed。
- delivered 信号已重命名为 child-added,并且现在还新增了 child-removed。
最后一项变化是,Child 类的 path、argv 和 envp 属性现在都可以为空。这样就能区分例如“未提供 envp”和“提供了空 envp”。
11.0.1 中的变化
- core:修复 32 位 ARM 进程中 agent 线程的栈对齐
- core:修复脆弱的 SELinux 规则补丁逻辑
11.0.2 中的变化
- core:修复 i/macOS 上 spawn() 逻辑中的 Mach 端口泄漏(长期存在的问题)
11.0.3 中的变化
- core:修复 arm64 上 tls_base 计算错误导致 iPhone 8 和 X 崩溃的问题
11.0.4 中的变化
- python:更新元数据并提升依赖版本
11.0.5 中的变化
- core:修复与 Electra 的 Tweak Injector 的兼容性问题
- core:修复 iOS 上 enumerate_processes() 中进程名被截断的问题
- java:Java.registerClass() 现在支持重载
- packaging:此后每个新版本都会提供 Fedora 和 Ubuntu 软件包
11.0.6 中的变化
- python:修复依赖项规范,使从 PyPI 安装后 REPL 能够再次工作
11.0.7 中的变化
- core:更完善地覆盖 Windows 上的 child gating API
- python:从 PyPI 安装时,Linux 的 2.7 二进制文件不再损坏
11.0.8 中的变化
- core:修复某些平台上设置系统会话时因竞态条件而导致崩溃的问题
- python:修复解释器关闭时的死锁
11.0.9 中的变化
- java:修复早期插桩期间无法调用 Java.registerClass() 的问题
11.0.10 中的变化
- core:修复枚举具有空 DT_GNU_HASH 的 ELF 模块导入/导出时发生的崩溃
11.0.11 中的变化
- java:修复类型兼容性检查;该问题通常会导致调用错误的重载,继而使虚拟机 abort()
11.0.12 中的变化
- core:修复与最新版 macOS Mojave 和 iOS 12 测试版的兼容性问题
- core:改进系统会话(即 PID 0)中可用的 iOS Kernel API
- frida-trace:修复跟踪器脚本通过 send() 发送任意数据时发生的崩溃
11.0.13 中的变化
- core:修复拆卸从未 load() 过的脚本时发生的卡死
结语(EOF)
差不多就是这些了。如果你还没有读过上周发布的 Frida 10.8 版本介绍,请务必前往这里阅读。
用得开心!
oleavr