从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,但实际上只是简单替换而没有正确处理引用计数。
关键问题在于:
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 引用计数操作
// 释放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()技术实现内核内存读取:
- 创建伪造任务端口
- 设置
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