@i41nbeer
mptcp_usr_connectx 是 AP_MULTIPATH 套接字族中 connectx 系统调用的处理函数。
该函数的逻辑未能正确处理非 AF_INET 或 AF_INET6 的源和目标 sockaddr:
// verify sa_len for AF_INET:
if (dst->sa_family == AF_INET &&
dst->sa_len != sizeof(mpte->__mpte_dst_v4)) {
mptcplog((LOG_ERR, "%s IPv4 dst len %u\n", __func__, dst->sa_len), MPTCP_SOCKET_DBG, MPTCP_LOGLVL_ERR);
error = EINVAL;
goto out;
}
// verify sa_len for AF_INET6:
if (dst->sa_family == AF_INET6 &&
dst->sa_len != sizeof(mpte->__mpte_dst_v6)) {
mptcplog((LOG_ERR, "%s IPv6 dst len %u\n", __func__, dst->sa_len), MPTCP_SOCKET_DBG, MPTCP_LOGLVL_ERR);
error = EINVAL;
goto out;
}
// code doesn't bail if sa_family was neither AF_INET nor AF_INET6
if (!(mpte->mpte_flags & MPTE_SVCTYPE_CHECKED)) {
if (mptcp_entitlement_check(mp_so) < 0) {
error = EPERM;
goto out;
}
mpte->mpte_flags |= MPTE_SVCTYPE_CHECKED;
}
// memcpy with sa_len up to 255:
if ((mp_so->so_state & (SS_ISCONNECTED|SS_ISCONNECTING)) == 0) {
memcpy(&mpte->mpte_dst, dst, dst->sa_len);
}
观察你溢出的结构体内部,你会发现可以命中以下两个字段:
if (mpte->mpte_itfinfo_size > MPTE_ITFINFO_SIZE) _FREE(mpte->mpte_itfinfo, M_TEMP);
mpte_itfinfo_size 紧邻 mpte_itfinfo。
当结构体初始化时,mpte_itfinfo 指针指向一个小的内联数组。如果添加的子流数量超过该数组容量,它们会被放入堆缓冲区,此时 mpte_itfinfo 将指向该缓冲区。
如果你拥有另一个漏洞(例如 async_wake 的内核堆泄露漏洞),你可以用任意有效的 zone 对象覆盖 mpte_itfinfo 字段,然后该对象会被释放(实际上,你还可以将其覆盖为指向该对象内部的偏移量,以实现更有趣的操作!)
然而,我们并没有那个漏洞。
另一种方法是部分覆盖该指针。如果我们用 NULL 字节部分覆盖它,可以将其指向一个按 256 字节、65K、16MB 或 4GB 对齐的值。
在本 exploit 中,我选择了 3 字节的 NULL 覆盖,这会导致 mpte_itfinfo 地址向下取整到下一个 16MB 边界后执行 kfree。
利用流程如下:
交替分配 16MB 的 ipc_kmsg 和一批 mptcp 套接字。目标是让一个 kalloc.2048 分配落在那个 16MB 边界上。
利用该漏洞释放其中一个 ipc_kmsg,将该页面移至 intermediate 列表,并将 16MB 对齐的分配置于 kalloc.2048 的 intermediate 页面空闲列表中。
分配大量填充了 2047 字节的管道;这些管道的后备缓冲区将从 kalloc.2048 中分配,希望包含我们 16MB 对齐的地址。
第二次触发漏洞,释放同一个地址,然后从 kalloc.2048 分配一批预分配的 ipc_kmsg 缓冲区。
现在,我们期望得到一个 ipc_kmsg(我们可以向它发送消息并接收)和一个管道缓冲区(我们可以读写)相互重叠。
我使用了 extra_recipe 中的线程异常端口技巧,向预分配的 ipc_kmsg 缓冲区发送消息。每次我们检查每个管道,看是否有任何管道包含该消息。当找到正确的 (ipc_kmsg, 管道) 对时,我们可以重写消息,向自己发送一个位于管道缓冲区内的假端口。我将该假端口构造得类似 async_wake 中的那个(基于 @qwertyoruiopz 和 @marcograss 的 yalu 10.2),以提供早期的内核读取原语。
利用内核读取原语,我找到内核任务,并制作一个假端口,以便通过 mach_vm_read/mach_vm_write 实现更方便的内核内存读写。
注意事项:要连接 mptcp 套接字,你需要 com.apple.developer.networking.multipath 授权,这需要苹果开发者证书,任何人都可以从苹果购买。
可靠性: 这是一个安全研究工具,远非完美。不过,大多数时候它应该能工作,并且当它正常工作时,会做好清理工作,因此后续不会导致内核恐慌。
提高成功概率的方法:
支持的设备: 应适用于 iOS 11.0 - 11.3.1(含)。已在以下设备上测试:iPod Touch 6g、iPhone 6s、iPhone SE、iPhone 7、iPhone 8
API: #include "sploit.h" 并调用 go() 来运行 exploit。 如果成功,你可以使用 kmem.h 中的函数来读写内核内存。
注意: 多人已从补丁中公开 bindiff 了这个漏洞(或者他们的 0day 已被修复 ;) 阅读他们的文章以获取更多细节): @elvanderb 于 5 月 31 日在巴黎的 rump.beer 上就这个漏洞做了 lightning talk:https://www.rump.beer/2018/slides/ios_48h.pdf @jaakerblom 于 6 月 1 日在 GitHub 上发布了一个可用的 exploit:https://github.com/potmdehex/multipath_kfree John 的技术与我类似,但他使用的是两字节溢出而非三字节,并替换为不同的对象。不错的内容!