从0到tfp0第二部分:voucher_swap EXP详解
字数 2254 2025-08-24 10:10:13

从零到tfp0:voucher_swap漏洞利用详解

1. 漏洞背景

1.1 引用计数机制

引用计数是一种内存管理技术,通过跟踪对象被引用的次数来决定何时释放内存。在XNU内核中:

  • Mach端口(ipc_port_t)使用io_references成员作为引用计数
  • Voucher使用iv_refs成员作为引用计数
  • os_refcnt_t类型限制最大值为0x0fffffff(7个f),超过会触发内核panic

1.2 漏洞根源

漏洞位于task_swap_mach_voucher函数中,这是一个由MIG生成的Mach API函数。该函数本应交换新旧voucher,但实际上只是简单替换而没有正确处理引用计数。

关键问题在于:

  1. new_voucher的引用计数被减少了两次
  2. old_voucher的引用计数被增加一次

2. 漏洞分析

2.1 漏洞函数流程

task_swap_mach_voucher函数的引用计数变化:

  1. 第4839行: new_voucher引用计数+1 (convert_port_to_voucher)
  2. 第4841行: old_voucher引用计数+1 (convert_port_to_voucher)
  3. 第4843行: 执行old_voucher = new_voucher
  4. 第4844行: new_voucher引用计数-1 (ipc_voucher_release)
  5. 第4857行: new_voucher引用计数再次-1 (因为old_voucher现在是new_voucher)

2.2 利用可能性

通过精心构造可以:

  1. new_voucher的引用计数减为0,导致对象被释放但保留指针
  2. old_voucher的引用计数增加到接近最大值

3. 关键数据结构

3.1 IPC Voucher结构

ipc_voucher结构体重要成员:

  • iv_refs: 引用计数
  • iv_port: 关联的端口指针
  • 其他voucher特定数据

3.2 Thread结构中的voucher

线程(thread)结构中的ith_voucher成员存储voucher引用,可通过以下函数操作:

  • thread_get_mach_voucher: 读取voucher引用
  • thread_set_mach_voucher: 设置voucher引用

4. 利用技术

4.1 堆风水技术

4.1.1 OOL端口描述符

Mach消息中的OOL(Out-Of-Line)端口描述符可用于堆喷射:

  • MACH_MSG_OOL_PORTS_DESCRIPTOR: 发送OOL端口数组
  • 分配内核内存并用端口指针填充

4.1.2 管道缓冲区

管道缓冲区特性:

  • 默认最大16MB(1024个管道,每个16KB)
  • 位于内核虚拟地址空间
  • 可用于精确的内存布局控制

4.2 Zone垃圾回收

voucher位于专用zone中,通过:

  1. 分配大量voucher并释放
  2. 触发zone垃圾回收
  3. 使释放的内存可用于其他对象分配

5. 漏洞利用步骤

5.1 初始准备

  1. 创建专用线程存储voucher指针
  2. 创建喷射用的管道(最多1024个)
  3. 喷射大量端口(约8000个)填充内存空洞

5.2 堆布局

  1. 喷射voucher(约16个块,每块80个voucher)
  2. 选择特定voucher作为目标(uaf_voucher_port)
  3. 继续喷射端口以准备后续GC

5.3 触发漏洞

  1. 存储voucher指针到线程
  2. 使用voucher_release释放引用计数
  3. 使目标voucher的引用计数减为0

5.4 内存占用

  1. 创建OOL端口指针覆盖释放的voucher
  2. 触发GC使释放的内存可重用
  3. 用伪造对象占用释放的voucher内存

5.5 信息泄露

  1. 调用thread_get_mach_voucher获取voucher端口
  2. 修改iv_refs指向管道缓冲区
  3. 定位与伪造端口重叠的管道缓冲区

5.6 内核读原语

  1. 设置mach_port_request_notification构造读取原语
  2. 找到base port地址
  3. 计算自身任务端口地址

5.7 获取内核权限

  1. 获取主机端口地址和ipc_space_kernel
  2. 定位内核任务端口
  3. 获取vm_map地址
  4. 伪造完整的内核任务端口(tfp0)

6. 关键函数与技术

6.1 引用计数操作

// 释放voucher引用
void voucher_release(mach_port_t voucher_port) {
    task_swap_mach_voucher(mach_task_self(), voucher_port, MACH_PORT_NULL);
}

// 增加voucher引用
void voucher_reference(mach_port_t voucher_port) {
    task_swap_mach_voucher(mach_task_self(), MACH_PORT_NULL, voucher_port);
}

6.2 内存读取原语

通过pid_for_task()技术实现内核内存读取:

  1. 创建伪造任务端口
  2. 设置bsd_info指向目标地址
  3. 调用pid_for_task读取32位值

7. 防御与缓解

  1. 引用计数上限(0x0fffffff)防止整数溢出
  2. Zone隔离限制内存破坏范围
  3. 类型检查防止直接对象伪造

8. 参考文献

  1. Project Zero Issue tracker - voucher_swap漏洞详情
  2. iOS 10 - Kernel Heap Revisited - Stefan Esser
  3. Mac OS X Internals: A Systems Approach
  4. MacOS and iOS Internals, Volume III: Security & Insecurity
  5. CanSecWest 2017 - Port(al) to the iOS Core
从零到tfp0:voucher_ swap漏洞利用详解 1. 漏洞背景 1.1 引用计数机制 引用计数是一种内存管理技术,通过跟踪对象被引用的次数来决定何时释放内存。在XNU内核中: Mach端口( ipc_port_t )使用 io_references 成员作为引用计数 Voucher使用 iv_refs 成员作为引用计数 os_refcnt_t 类型限制最大值为 0x0fffffff (7个f),超过会触发内核panic 1.2 漏洞根源 漏洞位于 task_swap_mach_voucher 函数中,这是一个由MIG生成的Mach API函数。该函数本应交换新旧voucher,但实际上只是简单替换而没有正确处理引用计数。 关键问题在于: new_voucher 的引用计数被减少了两次 old_voucher 的引用计数被增加一次 2. 漏洞分析 2.1 漏洞函数流程 task_swap_mach_voucher 函数的引用计数变化: 第4839行: new_voucher 引用计数+1 ( convert_port_to_voucher ) 第4841行: old_voucher 引用计数+1 ( convert_port_to_voucher ) 第4843行: 执行 old_voucher = new_voucher 第4844行: new_voucher 引用计数-1 ( ipc_voucher_release ) 第4857行: new_voucher 引用计数再次-1 (因为 old_voucher 现在是 new_voucher ) 2.2 利用可能性 通过精心构造可以: 将 new_voucher 的引用计数减为0,导致对象被释放但保留指针 将 old_voucher 的引用计数增加到接近最大值 3. 关键数据结构 3.1 IPC Voucher结构 ipc_voucher 结构体重要成员: iv_refs : 引用计数 iv_port : 关联的端口指针 其他voucher特定数据 3.2 Thread结构中的voucher 线程( thread )结构中的 ith_voucher 成员存储voucher引用,可通过以下函数操作: thread_get_mach_voucher : 读取voucher引用 thread_set_mach_voucher : 设置voucher引用 4. 利用技术 4.1 堆风水技术 4.1.1 OOL端口描述符 Mach消息中的OOL(Out-Of-Line)端口描述符可用于堆喷射: MACH_MSG_OOL_PORTS_DESCRIPTOR : 发送OOL端口数组 分配内核内存并用端口指针填充 4.1.2 管道缓冲区 管道缓冲区特性: 默认最大16MB(1024个管道,每个16KB) 位于内核虚拟地址空间 可用于精确的内存布局控制 4.2 Zone垃圾回收 voucher位于专用zone中,通过: 分配大量voucher并释放 触发zone垃圾回收 使释放的内存可用于其他对象分配 5. 漏洞利用步骤 5.1 初始准备 创建专用线程存储voucher指针 创建喷射用的管道(最多1024个) 喷射大量端口(约8000个)填充内存空洞 5.2 堆布局 喷射voucher(约16个块,每块80个voucher) 选择特定voucher作为目标( uaf_voucher_port ) 继续喷射端口以准备后续GC 5.3 触发漏洞 存储voucher指针到线程 使用 voucher_release 释放引用计数 使目标voucher的引用计数减为0 5.4 内存占用 创建OOL端口指针覆盖释放的voucher 触发GC使释放的内存可重用 用伪造对象占用释放的voucher内存 5.5 信息泄露 调用 thread_get_mach_voucher 获取voucher端口 修改 iv_refs 指向管道缓冲区 定位与伪造端口重叠的管道缓冲区 5.6 内核读原语 设置 mach_port_request_notification 构造读取原语 找到 base port 地址 计算自身任务端口地址 5.7 获取内核权限 获取主机端口地址和 ipc_space_kernel 定位内核任务端口 获取 vm_map 地址 伪造完整的内核任务端口(tfp0) 6. 关键函数与技术 6.1 引用计数操作 6.2 内存读取原语 通过 pid_for_task() 技术实现内核内存读取: 创建伪造任务端口 设置 bsd_info 指向目标地址 调用 pid_for_task 读取32位值 7. 防御与缓解 引用计数上限( 0x0fffffff )防止整数溢出 Zone隔离限制内存破坏范围 类型检查防止直接对象伪造 8. 参考文献 Project Zero Issue tracker - voucher_ swap漏洞详情 iOS 10 - Kernel Heap Revisited - Stefan Esser Mac OS X Internals: A Systems Approach MacOS and iOS Internals, Volume III: Security & Insecurity CanSecWest 2017 - Port(al) to the iOS Core