隆重介绍_全新的_ Swift 桥接!Swift 从版本 5 起已实现 ABI 稳定,这个期待已久的桥接让 Frida 能够很好地配合 Swift 编写的二进制文件。无论你认为 Swift 是静态语言还是动态语言,有一点可以确定:随着这个 Frida 版本发布,它变得动态多了。
元数据
逆向工程师开始分析二进制文件时,通常首先要了解其中定义的各种数据结构。因此,最合理的起点是构建与 ObjC.classes 和 ObjC.protocols API 对应的 Swift 能力。不过 Swift 还有结构体、枚举等一等类型,而且 Swift 运行时并未提供 Objective-C 意义上的反射原语,所以我们必须挖得更深一些。
幸运的是,Swift 编译器会为二进制文件定义的每种类型生成元数据。撰写本文时,这些元数据封装在 include/swift/ABI/Metadata.h 中定义的 C++ 结构体 TargetTypeContextDescriptor 里。该数据结构包含类型名称、字段、方法(如适用),以及取决于具体类型的其他有用数据。这些数据结构由相对指针引用(定义于 include/swift/Basic/RelativePointer.h)。在 Mach-O 二进制文件中,它们存放在 __swift5_types 节。
因此,为了转储类型,Frida 基本上会遍历这些数据结构并逐一解析,与 dsdump 的做法非常相似;区别在于你无需构建 Swift 编译器即可进行实验。
Frida 还有一个优势:能够探查用 Swift 编写的 Apple 内部 dylib。得益于私有 getsectiondata API,我们无需解析 dyld_shared_cache,即可轻松获得节偏移。
获得元数据后,就能轻松为对象实例和不同类型的值创建 JavaScript 包装器。
调用约定
为了与 Objective-C 桥接能力相当,Swift 桥接必须支持调用 Swift 函数,而事实证明这同样并不简单。
Swift 定义了自己的调用约定 swiftcall。简而言之,它力求尽可能高效:对于大小不超过 4 个寄存器容量的结构体,不浪费加载和存储指令,而是直接通过寄存器传递。由于这很快会占满宝贵的 8 个参数寄存器(AARCH64 上为 x0–x7),它不使用第一个寄存器传递 self 参数。此外还定义了 error 寄存器,供被调用方保存其抛出的错误。
上述过程在 Swift 编译器文档中称为“物理降级”,由后端 LLVM 实现。
物理降级之前的过程称为“语义降级”,即编译器前端确定值由谁“拥有”,以及采用直接还是间接方式传递。有些结构体即使小于 4 个寄存器,也必须间接传递;例如泛型结构体在编译时无法确定精确内存布局,或者其中包含必须始终位于内存中的弱引用。
为了调用 Swift 函数,我们必须同时实现语义降级和物理降级。物理降级通过 JIT 编译的适配函数实现(得益于 Arm64Writer API),负责必要的 SystemV–swiftcall 转换。语义降级则利用类型元数据判断值是否应直接传递。
编译器文档是深入了解该调用约定的绝佳资料。
拦截
由于 Swift 直接通过寄存器传递结构体,寄存器与实际参数之间并非一一对应。
既然已经拥有类型的 JavaScript 包装器,也能从 JS 运行时调用 Swift 函数,合理的下一步就是扩展 Interceptor 以支持 Swift 函数。
对于未剥离符号的函数,我们使用简单的正则表达式解析参数类型和名称,返回值也同样处理。解析完成后,获取类型元数据并确定类型布局,再为每个参数构造 JS 包装器,把 Swift 参数值传给它,无论该值占用多少个寄存器。
结语
请注意,该桥接仍处于非常早期的开发阶段,因此:
- 目前仅支持 Darwin arm64(e)。
- 性能尚未达到最佳状态,部分边界情况可能处理不当,出现一些错误也属预期。
- API 在短期到中期内可能发生破坏性变更。
- 非常欢迎提交 PR 和 issue!
有关当前 API 的最新资料,请参阅文档。
尽情体验吧!
15.1.0 的变更
- 实现 Swift 桥接,使 Frida 能够:
- 浏览 Swift 模块及其中实现的类型,包括类、结构体、枚举和协议。
- 为对象实例和值创建 JavaScript 包装器。
- 从 JavaScript 运行时调用采用
swiftcall调用约定的函数。 - 拦截 Swift 函数并自动解析其参数和返回值。
- 修复 i/macOS 回归:与 iOS 15 支持相关的改动破坏了附加到 Apple 系统守护进程的能力。
- gadget:在 connect 模式中新增 interaction.parameters;这些参数会“反映”到应用信息的
parameters.config下。感谢 @mrmacete!
15.1.1 的变更
- gumjs:修复 V8 运行时中的 Swift 生命周期逻辑。
15.1.2 的变更
- control-service:修复信号连接,使 Device.output 等信号能由远程 frida-server 正确发出。感谢 @mrmacete!
- gadget:修复在 Frida 15 前期重构中遗漏的“runtime”选项。
- relocator:优化 x86 RIP 相对代码的处理,在可行时仅调整偏移。
- gumjs:新增 ESM 支持,让 frida-compile 等工具能输出更好的代码。
- gumjs:解析后丢弃源代码。
- gumjs:修复编译为 QuickJS 字节码时的泄漏。
- java:公开 JNIEnv->GetDirectBufferAddress。感谢 @pandasauce!
15.1.3 的变更
- objc:修复 Proxy 的 respondsToSelector 实现。感谢 @hot3eed!
- gumjs:修复缺少模块时 V8 运行时崩溃。
- gumjs:发出 ESM 入口点抛出的 V8 异常。
15.1.4 的变更
- gumjs:修复 QuickJS 运行时中与弱引用回调有关的重复释放。感谢 @mrmacete!
- gumjs:在无关的 NativeCallback 调用中忽略 Interceptor 上下文,从而安全忽略调用栈上层的无效 Interceptor 上下文,改用最小但正确的回调上下文。感谢 @mrmacete!
- gumjs:修复 ESM 模块名称规范化逻辑。
- gumjs:新增 Process 的 cwd、home 和 tmp 目录 getter。
- swift:将元数据缓存性能提升约 3 倍。感谢 @hot3eed!
- node:发布 v15 而非 v13 的 Electron 预构建包。
15.1.5 的变更
- gumjs:修复 QuickJS 将大数字转换为字符串时的崩溃。感谢 @vfsfitvnm!
- gumjs:向 CModule 公开 GError 和 GIConv。感谢 @0xDC00!
- droidy:支持 ADB 服务器主机/端口环境变量。感谢 @amirpeles90!
- swift:改进非 Darwin 平台上的加载行为。
15.1.6 的变更
- swift:修复非 Darwin 平台加载期间的崩溃。感谢 @hot3eed!
15.1.7 的变更
- swift:修复旧版操作系统上的 CoreSymbolication 集成。感谢 @hot3eed!
- python:为 Android/ARM 新增 setup.py 下载逻辑;不过 CI 尚未构建并上传此类二进制文件。
15.1.8 的变更
- darwin:支持在 SRD 环境中工作。感谢 @Nessphoro!
- darwin:支持使用较新的 iOS SDK 构建。
15.1.9 的变更
- x86-relocator:修复 RIP 相对指令的补丁。这是 15.1.2 引入的回归,会导致 Stalker 不可靠。
- portal-service:只要从 PortalService 清除会话 ID,就始终移除 ClusterNode 会话,从而避免 NULL 解引用和泄漏。感谢 @mrmacete!
- frida-portal:修复 –help 输出中的拼写错误。
15.1.10 的变更
- p2p:妥善处理不受支持的 ICE candidate。
- p2p:暂时禁用 ICE-TCP。
- p2p:重构 PeerSocket,修复同步问题。
- socket:修复 WebService 拆卸。
- vala:优化服务器端 GDBus 回复处理,使 RPC 和网络协议减少往返并提升性能。
- x86-writer:在间接分支之后或内部加入 UD2 指令。
- x86-writer:尽可能生成更大的 NOP。
- stalker:提升 x86/64 后端性能。
- objc-api-resolver:防范已释放的 ObjC 类。感谢 @mrmacete!
- gumjs:修复 V8 Interceptor.{replace,revert}() 回归。感谢 @3vilWind!
- gumjs:用 regsAccessed 和 operand.access 扩展 Instruction API。感谢 @3vilWind!
15.1.11 的变更
- x86-writer:新增 put_sahf() 和 put_lahf()。
- x86-relocator:修复超出范围的 Jcc 分支目标处理。感谢 @0xDC00!
- stalker:优化目标地址获取。
- stalker:避免高开销的 XCHG 指令。
- stalker:优化 IC 序言以使用 SAHF/LAHF。
- memory:改进 scan(),支持正则表达式 pattern。感谢 @hot3eed!
- kernel:在支持的平台上从 all_image_info 获取基址。感谢 @mrmacete!
- windows:提升 dbghelp 回溯器可靠性。感谢 @HonicRoku!
15.1.12 的变更
- agent:修复卸载时遇到 NO_REPLY_EXPECTED 调用而挂起的问题;此前仍会等待回复。由于服务器端 DBus 代码由 Vala 编译器生成,且它之前(Frida <= 15.1.9)会忽略 NO_REPLY_EXPECTED,这个错误一直未被发现。
- android:迁移到 NDK r24 Beta 1。
15.1.13 的变更
- linux:改进 glibc 系统上的模块解析。
- fruity:修复 dyld v4 情况下的 spawn(未越狱 iOS 15.x)。感谢 @mrmacete!
- objc-api-resolver:通过互斥锁防范 objc_disposeClassPair()。感谢 @mrmacete!
- gumjs:更新 Kernel.scan*(),使其与 Memory.scan*() 一致。感谢 @hot3eed!
- stalker:修复 x86 上生成的分支操作码。
- stalker:修复 Thumb IT AL 处理。
- stalker:在 x86 上通过 PLT 处理被排除的 Linux 调用。
- stalker:修复 x86 上的 Linux 异常处理。
- java:新增 Java.backtrace(),目前不保证 API 稳定性。
15.1.14 的变更
- backtracer:改进模糊回溯器,使其包含直接调用者,并在已知栈末尾时避免越界遍历。
- windows:实现 Thread.try_get_ranges()。
- linux:实现 Thread.try_get_ranges()。
- ios:在 SRD 系统上向 launchd 登记。
- stalker:修复 x86 调用深度代码中的意外覆盖。
- node:同时发布 Node.js v17 的预构建包。
hot3eed