使用 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 共享缓存

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 之前的类记录:类名称以单独的字节显示

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

IDA 9.5 之后的类记录:包含名称、父类和大小的类型化结构体

IDA 9.5 之后的同一驱动程序: 类型化的类描述,包含类名称、父类 IOUserHIDDevice 和大小信息。

按名称识别内核调用

驱动程序发送给内核的每条消息都带有一个 64 位 ID。现在,IDA 可以根据 SDK 中的 IORPCMessageID 枚举显示其名称,例如将 0xAB6F76DDE6D693F2 显示为 IOService_Start_ID。

根据命名常量比较传入消息 ID 的分发函数

Apple DriverKit 库中的分发函数,根据每条传入消息的 ID 进行路由。

即使符号被剥离也能识别名称

每个调用都有一个负责发送请求的代理函数,以及一个负责处理请求并返回响应的 _Invoke 函数。即使没有符号,IDA 也能根据代码识别这两类函数:

被剥离符号的代理函数,被命名为 IOBufferMemoryDescriptor_SetLength

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

根据返回的响应命名的被剥离符号的 _Invoke 函数

对应的 _Invoke 函数,根据其返回的响应进行命名。

内核缓存

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

IDA 9.5 之前的 iOS 16 内核缓存:未命名的 16 字节常量

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

IDA 9.5 之后的 iOS 16 内核缓存:根据路由调用命名的常量

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

没有符号的内核代理函数,被命名为 IOService_Stop

一个没有符号的内核代理函数,根据其加载的请求被命名为 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 个类的大小定义完全一致。

试试看

请访问官网或者本文原文链接查看。

posted @ 2026-10-23 10:03  sysin  阅读(4)  评论(0)    收藏  举报