“desc_race”(CVE-2021-30955)针对 iOS 15.0 - 15.1.1 的漏洞利用(具有稳定的内核读/写原语)
将 IOSurfaceClient 数组的容量增加到 0x2000,目标是写入一个内容完全受控的指针,然后使用 IOSurfaceRootUserClient 接口实现内核读/写。数组大小为 0x2000 * 8 字节,因此位于 KHEAP_KEXT 的大映射区中,与 KHEAP_DEFAULT 相同。
然后使用一个带有 0x4000 字节 ool 描述符的辅助 kmsg 分配一个 0x4000 字节的内核缓冲区,该缓冲区将从后方被覆盖。然后使用分配的消息触发该漏洞,称为双重 copyin kmsg,将其放置在辅助内核缓冲区之后。此消息包含一个 ool 端口描述符,其计数为 0x2000,因此将相当接近 IOSurfaceClient 数组。
然后您应该接收辅助消息。如果竞争成功,ool 端口描述符将被泄露,我们就能定位 IOSurfaceClient 数组的地址。
然后再次分配一个 0x4000 字节的内核缓冲区,这将占用前述的内核缓冲区。这次我构造了一个带有正确主体的伪造 kmsg 头。然后我销毁双重 copyin kmsg,内核将从我们的伪造头开始。我使用一个伪造的 mach_msg_ool_descriotpor_t 并触发
vm_copy_discard(),并带有完全受控的副本。在销毁副本的过程中,最有价值的代码行位于 _vm_map_entry_unlink_ll:
1
2
3
4
5
6
#define _vm_map_entry_unlink_ll(hdr, entry) \
MACRO_BEGIN \
(hdr)->nentries--; \
(entry)->vme_next->vme_prev = (entry)->vme_prev; \
(entry)->vme_prev->vme_next = (entry)->vme_next; \
MACRO_END
entry 在我们的控制之下,这为我们提供了一个完美的读/写原语。我利用这一点向 IOSurfaceClient 数组写入一个受控指针,然后结合 IOSurfaceRootUserClient 接口实现内核读/写。
在开发此漏洞利用的过程中,我反复阅读了 bazad 的文章“One byte to rule them all”,并在他的漏洞利用中使用了相同的技术,即伪造 vm_copy_t。但有一些不同之处。
XNU 对消息进行签名,我们无法再接收损坏的 kmsg。
在销毁过程中,我设置了 vm_object 的 mapping_in_progress,让内核自旋,从而不会因 zone 检查而崩溃。
非常感谢 bazad 的精彩文章,感谢 WangTielei 让我知道 IOSurfaceClient 接口现在无法用于读/写原语,也感谢 pedantcoder 的善意。