使用 IDA 9.5 逆向分析 Apple DriverKit 驱动程序
使用 IDA 9.5 逆向分析 Apple DriverKit 驱动程序
IDA Pro 9.5 (macOS, Linux, Windows) - 强大的反汇编程序、反编译器和多功能调试器
A powerful disassembler, decompiler and a versatile debugger. In one tool.
请访问原文链接:https://sysin.org/blog/ida-pro/ 查看最新版。原创作品,转载请保留出处。
作者主页:sysin.org

IDA 9.5:Apple DriverKit 共享缓存
Apple 正在将第三方驱动程序从内核迁移到 DriverKit。DriverKit 驱动程序(即 dext)是将内核的 C++ 驱动程序框架 IOKit 编译成用户空间库后的产物。它仍然由带有虚方法的 C++ 类构成,并通过向内核发送消息来执行工作。
IDA 9.5 在加载驱动程序后即可恢复其结构,包括类及其布局、虚方法表,以及驱动程序调用内核时使用的函数名称。
新增功能
DriverKit 类型库
IDA 现在内置了从 19 到 25 的所有 DriverKit SDK 版本的类型定义,支持 x86-64 和 arm64,并涵盖 USB、HID、PCI、网络、音频、视频、MIDI、串行、SCSI 和块存储等设备类别。不同版本中的类大小可能发生变化,因此 IDA 会识别驱动程序所针对的 SDK 版本,并应用相应版本的类型定义。
各版本包含的内容如下(x86-64 和 arm64 之间的类型数量最多相差 1 个):
| DriverKit 版本 | 类型数量 | DriverKit 类数量 | 已命名的内核调用数量 |
|---|---|---|---|
| 19(仅限 x86-64) | 1,137 | 47 | 190 |
| 20 | 1,405 | 61 | 230 |
| 21 | 2,011 | 90 | 312 |
| 22 | 2,241 | 97 | 349 |
| 23 | 2,244 | 97 | 350 |
| 24 | 2,391 | 105 | 356 |
| 25 | 2,879 | 125 | 375 |
类和虚方法表
每个 DriverKit 驱动程序都包含一个描述其自身类的表,其中记录了每个类的名称、父类、大小和虚方法表。IDA 会读取该表。每个类都会转换为一个与其父类关联的结构体,而每个虚方法表都会转换为带类型的列表,其中每个条目都标明对应的方法。这同样适用于 Apple 私有框架中的类,即使这些类没有任何 SDK 文档说明。
先使用 SDK,再使用驱动程序。 SDK 提供字段名称,驱动程序提供大小。当 SDK 定义了某个类时,IDA 会使用其字段定义;如果驱动程序中的类大小与 SDK 不同,IDA 会在 Output 窗口中提示。当 SDK 中缺少某个类或仅提供其名称时,IDA 会根据驱动程序中的信息构建该类:先包含父类,再填充空间直至达到驱动程序记录的大小,最后以指向该类私有数据的指针结束。
类表本身也会进行类型化处理。每条记录都会转换为一个结构体,清晰显示类的名称、父类、大小以及可供调用的方法,而不再是一串没有结构的字节。

IDA 9.5 之前的 Karabiner 虚拟键盘驱动程序: 类名称逐字节显示。

IDA 9.5 之后的同一驱动程序: 类型化的类描述,包含类名称、父类 IOUserHIDDevice 和大小信息。
按名称识别内核调用
驱动程序发送给内核的每条消息都带有一个 64 位 ID。现在,IDA 可以根据 SDK 中的 IORPCMessageID 枚举显示其名称,例如将 0xAB6F76DDE6D693F2 显示为 IOService_Start_ID。

Apple DriverKit 库中的分发函数,根据每条传入消息的 ID 进行路由。
即使符号被剥离也能识别名称
每个调用都有一个负责发送请求的代理函数,以及一个负责处理请求并返回响应的 _Invoke 函数。即使没有符号,IDA 也能根据代码识别这两类函数:

一个被剥离符号的代理函数,根据其发送的请求被命名为 IOBufferMemoryDescriptor_SetLength。

对应的 _Invoke 函数,根据其返回的响应进行命名。
内核缓存
在 arm64 内核缓存中,负责处理这些调用的内核扩展也会获得相同的名称,因此可以从驱动程序一路跟踪到内核。内核扩展自身的符号还可以为 SDK 未公开的私有调用提供名称,而没有符号的内核代理函数也会根据其代码进行命名。内核缓存中的类布局也得到了改进:对于内核类型定义中仅提供名称的类,IDA 会根据记录的大小构建其布局,并将每个虚方法表归属于对应的类。

IDA 9.5 之前的 iOS 16 内核缓存中的 USB 类别: 未命名的 16 字节常量。

IDA 9.5 之后的同一内核缓存: 每个常量都根据其路由的调用命名,例如 IOUSBHostDevice_Reset_ID。

一个没有符号的内核代理函数,根据其加载的请求被命名为 IOService_Stop。图中展示了同一函数的两个代码片段。
为什么这很重要
现在,每个驱动程序都能获得与其构建时所使用的 SDK 对应的类型定义,涵盖所有设备类别。与 IDA 的 macOS SDK 类型库相比,DriverKit 25 类型库新增了约 80 个 macOS 类型库中缺少的类,主要涉及视频、音频、网络、HID、MIDI、SCSI 和串行设备,同时还修正了部分布局不正确的类,例如 USB 主机类和网络队列类。
在 SDK 类型定义的基础上,IDA 还会恢复驱动程序自身的类,因此,从 SDK 继承的字段会显示为具有名称的成员,虚函数调用会显示其实际调用的方法,而且可以从驱动程序一路跟踪请求进入内核。
在 IDA 测试套件中的样本上,IDA 可以恢复以下信息:
- macOS 26 DriverKit 运行时(DriverKit 25,58 个库):全部 599 个类均按照精确大小完成布局,899 个虚方法表均获得名称和类型定义。
- Apple 的 DriverKit、HIDDriverKit 和 NetworkingDriverKit 库: 在数据中的 148 处和代码中的 149 处,共有 93 个内核调用获得名称。
- 五个驱动程序: 包括 Karabiner 的开源虚拟键盘和鼠标驱动程序,以及三个以太网驱动程序;其分发函数中共有 50 个调用获得名称。
- 一个 iOS 16 内核缓存: 336 个内核调用获得名称,其中 91 个是 SDK 未声明的私有调用。
有两个独立来源可以验证类布局:Apple 的 SDK 头文件与 Apple 自身二进制文件中的类表相互吻合,两者对共同发现的全部 162 个类的大小定义完全一致。
试试看
请访问官网或者本文原文链接查看。

Apple 正在将第三方驱动程序从内核迁移到 DriverKit。IDA 9.5 在加载驱动程序后即可恢复其结构,包括类及其布局、虚方法表,以及驱动程序调用内核时使用的函数名称。
浙公网安备 33010602011771号