0x00 概述
2020年8月,FreeBSD发布更新,修复了条件竞争(TOCTOU)漏洞,该漏洞可能会被不具有特权的用户空间恶意程序利用,从而实现特权提升。一位名为m00nbsd的研究人员将该漏洞报告给ZDI,并提供了该漏洞的详细描述和漏洞验证代码详情,该漏洞的编号为ZDI-20-949/CVE-2020-7460。
攻击者利用32位sendmsg()系统调用中存在的TOCTOU漏洞,可以以无特权的用户在FreeBSD上执行内核代码。该漏洞影响自2014年以来的所有FreeBSD内核,已经为该漏洞分配了CVE-2020-7460编号。在进行漏洞的详细分析之前,我们首先可以通过演示视频快速了解该漏洞的实际利用方式。
演示视频:https://youtu.be/LBFfX90Acw8
0x01 漏洞详情
我们直接跳到漏洞所在的位置,即freebsd32_copyin_control()函数。该函数由两个循环组成,如下所示:
// // ----------------------- FIRST LOOP ----------------------- // while (idx < buflen) { error = copyin(buf + idx, &msglen, sizeof(msglen)); if (error) return (error); if (msglen < sizeof(struct cmsghdr)) return (EINVAL); msglen = FREEBSD32_ALIGN(msglen); if (idx + msglen > buflen) return (EINVAL); idx += msglen; msglen += CMSG_ALIGN(sizeof(struct cmsghdr)) - FREEBSD32_ALIGN(sizeof(struct cmsghdr)); len += CMSG_ALIGN(msglen); } if (len > MCLBYTES) return (EINVAL); // // ALLOCATE KERNEL MEMORY // m = m_get(M_WAITOK, MT_CONTROL); if (len > MLEN) MCLGET(m, M_WAITOK); m->m_len = len; // // ----------------------- SECOND LOOP ----------------------- // md = mtod(m, void *); while (buflen > 0) { error = copyin(buf, md, sizeof(struct cmsghdr)); if (error) break; msglen = *(u_int *)md; msglen = FREEBSD32_ALIGN(msglen); /* Modify the message length to account for alignment. */ *(u_int *)md = msglen + CMSG_ALIGN(sizeof(struct cmsghdr)) - FREEBSD32_ALIGN(sizeof(struct cmsghdr)); md = (char *)md + CMSG_ALIGN(sizeof(struct cmsghdr)); buf += FREEBSD32_ALIGN(sizeof(struct cmsghdr)); buflen -= FREEBSD32_ALIGN(sizeof(struct cmsghdr)); msglen -= FREEBSD32_ALIGN(sizeof(struct cmsghdr)); if (msglen > 0) { error = copyin(buf, md, msglen); // <<-------- OVERFLOW if (error) break; md = (char *)md + CMSG_ALIGN(msglen); buf += msglen; buflen -= msglen; } }
我们来看看这里发生了什么。第一个循环从用户区域获取数据。这个数据是一组连续的cmsghdr结构:
struct cmsghdr { socklen_t cmsg_len; /* data byte count, including hdr */ int cmsg_level; /* originating protocol */ int cmsg_type; /* protocol-specific type */ /* u_char cmsg_data[]; */ };
第一个循环执行长度检查,并确保缓冲区的总长度(len字节)适合随后分配的内核缓冲区,该缓冲区的大小为MLEN字节。
一旦通过了长度检查,就会分配所述的内核缓冲区,第二个循环将用户空间数据复制到缓冲区中。内核执行了一些处理,将结构从32位转换为64位,但在这里是无关的,因此我们将忽略它。
在第二个循环结束之后,内核缓冲区将以以下格式结束:
但是,在这里存在一个TOCTOU漏洞:在第一个循环和第二个循环之间,用户空间可能修改了其cmsghdr结构的cmsg_len字段,并且最终的总长度现在超过了MLEN。
假设在第一个循环之后,用户空间增加了最后一个cmsg_len字段的值:
随后就发生内核堆溢出。
0x02 触发漏洞
我们可以通过32位sendmsg()系统调用触发这个堆溢出,其格式如下:
ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags); struct msghdr { void *msg_name; /* optional address */ socklen_t msg_namelen; /* size of address */ struct iovec *msg_iov; /* scatter/gather array */ int msg_iovlen; /* # elements in msg_iov */ void *msg_control; /* ancillary data, see below */ socklen_t msg_controllen; /* ancillary data buffer len */ int msg_flags; /* flags on received message */ };
msg_control就是用户内存缓冲区的开始位置,也是连续的cmsghdr结构所在的位置。
要触发此漏洞,我们需要:
1、创建连续有效的cmsghdr结构块;
2、在我们的代码块中,循环生成一个调用sendmsg()的线程。
3、派生出另一个线程,该线程增加最后一个cmsg_len字段的值,然后循环返回到正确的值。
完成后,我们只需要等待几秒钟,两个线程就可以运行了。很快,就会发生内核崩溃(Kernel Panic)。我们已经有了一个比较好的开始。
0x03 改进原语
我们希望知道何时触发堆溢出,以便让我们了解什么时候停止两个竞争线程。也就是说,我们不想让线程保持太长时间的运行,这样会以一种极端的方式实现内核内存溢出,可能会导致内核崩溃(Panic)。与此不同,我们希望在第一次成功溢出后就立即停止,以提高漏洞利用的可靠性,并限制对内存的破坏。
为此,我们可以利用以下技巧:
这个思路包括利用copyin()的行为,该函数用于将用户数据复制到内核内存中。为了防止用户区域给出未映射的页面(或一般意义上的垃圾),copyin()会适当处理内核中的页面错误,并在复制过程中遇到未映射的页面时安全地返回EFAULT。EFAULT依次由系统调用返回。
在堆溢出的情况下,我们可以将未映射的页面恰好放置在我们希望复制结束的位置。然后,copyin()将复制所有内容,直到这个未映射的页面,在该页面上将出现错误,然后返回EFAULT,这将导致sendmsg()也返回EFAULT。
因此,如果sendmsg()返回EFAULT,则未映射的页面被命中,此时溢出就已经被触发。
我们现在可以选择写入的内容、写入的大小,并且能够确切知道溢出触发的时间。这样一来,原本的漏洞利用原语就得到了改进。
0x04 Mbufs和FreeBSD中的堆布局
我们溢出的内核缓冲区是mbuf。Mbuf是一种特殊的结构,使用FreeBSD的常规区域分配器分配。这里不需要知道太多细节,只需要知道mbuf在大页面上一个接一个的定位。但由于堆溢出的存在,我们很有可能覆盖内存中的下一个mbuf。
从漏洞利用的角度来看,mbuf结构有一个非常有意思的字段。在这里,我们将使用m->m_ext.ext_free field字段,它是指向函数的指针,并且具有以下原型:
void (*ext_free)(struct mbuf *the_current_mbuf_being_freed);
当内核希望释放mbuf时,它将检查mbuf中是否包含某些标志。如果这个检查成功,内核将调用mbuf的ext_free函数,并期望该函数释放mbuf。
我们将在漏洞利用中使用它。
0x05 控制mbufs的释放
当前的事务状态是堆溢出,让我们可以覆盖内存中的下一个mbuf,因此,我们可以修补下一个mbuf的ext_free字段。
那么,究竟如何让ext_free被调用呢?一旦检测到堆溢出成功,就必须迅速触发内存中下一个mbuf的释放。
这个过程可以通过在本地启动的简单UDP服务器/客户端配对来实现:
1、客户端使用常规的sendto()将数据包发送到服务器。这会导致mbuf在内核中分配。可以将其视为是PushMbuf()原语,该原语基本上分配mbuf。
2、服务器使用常规的recvfrom()接收这些数据包。这将导致释放已经分配的mbuf,并调用ext_free。这可以看作是释放mbuf的PopMbuf()原语。
现在,让我们看看如何使用这些原语:
1、使用PushMbuf()将大量mbuf PUSH到内核中。这样一来,就填满了堆。
2、使用PopMbuf() POP出刚刚PUSH的mbuf的50%,这样一来,会在分配映射中产生漏洞。
3、触发堆溢出,并覆盖紧随其后的一些mbuf的ext_free。
4、一旦我们得知触发了堆溢出,就可以使用PopMbuf() POP出剩下50%的mbuf。
5、如果幸运的话,这样就会释放我们刚刚重写了ext_free的mubf。因此,ext_free被调用,并跳转到我们控制的地址。如果这样无效,那么就只能返回步骤1,然后重试,直到成功为止。
这个过程通常在不到1秒的时间内成功完成,并且内核跳转到我们完全控制的ext_free地址。在这里,COP/JOP/ROP链开始。
0x06 利用链小工具
我们刚刚设法让内核跳转到我们控制的地址中。寄存器的状态非常简单:
%rdi = 释放的mbuf的地址
在这里,%rdi指向我们重写的mbuf。如果我们基于特定偏移量(%rdi)利用COP/JOP链,那么实际上就是位于我们能控制内容的缓冲区中,我们之前已经设法溢出了这个缓冲区。
但遗憾的是,很少有可以链接在一起的小工具,而且由于mbuf内容已经被需要保证有效的字段消耗了一部分,因此我们的空间非常有限。
可以看到:
1、由于缺少比较理想的小工具,我们不得不使用一些其他的COP/JOP小工具。
2、我们很快就转向了ROP,因为ROP更容易,而且我们不能将COP/JOP维持太长时间。
3、考虑到空间不足的问题,ROP必须迅速退出内核,并跳转到用户空间Shellcode,这样我们就可以不受约束地获得完整的执行。
考虑到上述情况,我们还是选择使用利用链。在这里,我最终得到了由13个小工具组成的利用链,它们可以执行以下操作:
1、保存寄存器上下文,以便随后正确地还原。
2、将用户空间页表标记为可执行。这是FreeBSD对Meltdown漏洞缓解措施中添加的一个约束措施。执行内核时,用户页表有一个“不执行”(NX)位,因此内核无法跳转到任何用户地址。所以,利用链必须修补页表,设置NX,鉴于FreeBSD不会随机化其PML4 Slot,所以这并不是很复杂。
3、在CPU上禁用SMEP和SMAP。
4、最后,跳转到我们构建的用户级别Shellcode。
这并不是一个直接的漏洞利用方式,但它确实有效。
0x07 获取root权限
我们终于在内核模式下执行了Shellcode。现在,我们可以任意进行操作了。
为了使这个过程尽可能简单,我修补了线程的UID字段,将其UID设置为0。然后,我继续恢复最初保存为链的一部分的内核状态,然后内核继续执行,好像什么都没发生一样。最后,我们就成功调用了setuid(0),这意味着已经获取了root权限。
当然,要实现这一点,在没有实际内核代码执行程序的情况下,直接将UID作为任意位置/任意内容写入的一部分进行修补也可以,这样就不需要实际的内核代码执行,但是我还是希望尝试能否实现代码执行。
0x08 完整步骤
我们回顾一下:
1、在本地创建UDP服务器/客户端配对。
2、创建用户空间Shellcode。
3、使用UDP客户端,我们分配了许多mbuf,并使用服务器在内核堆分配映射中创建了漏洞。
4、我们将两个线程相互竞争,以触发sendmsg()中的堆溢出,这样一来就可以覆盖mbuf。在溢出的mbuf中,我们编写了一个新的ext_free指针和COP/JOP/ROP链的数据。
5、我们检测到成功溢出,并使用UDP服务器释放分配的mbuf,这将导致ext_free被调用并启动链。
6、利用链执行,禁用内存/CPU保护,并跳转到我们的用户空间Shellcode。
7、成功实现漏洞利用。
0x09 漏洞利用
在这里提供了概念验证代码,可以从无特权的用户实现root shell。我们观察到该概念验证的可靠性为90%。
https://github.com/thezdi/PoC/tree/master/CVE-2020-7460
0x0A 总结
根据FreeBSD提供的信息,i386以及其他32位平台不受此漏洞威胁。对于易受攻击的系统,建议尽快更新到0-day披露日期之后发布的FreeBSD稳定版本或发行版本,然后重新启动。通过这种方式,理论上可以实现漏洞修复。FreeBSD团队仅用两个星期就构建和发布了补丁,效率相对较高。
本文翻译自:https://www.thezdi.com/blog/2020/9/1/cve-2020-7460-freebsd-kernel-privilege-escalation如若转载,请注明原文地址