• Welcome to the world's largest Chinese hacker forum

    Welcome to the world's largest Chinese hacker forum, our forum registration is open! You can now register for technical communication with us, this is a free and open to the world of the BBS, we founded the purpose for the study of network security, please don't release business of black/grey, or on the BBS posts, to seek help hacker if violations, we will permanently frozen your IP and account, thank you for your cooperation. Hacker attack and defense cracking or network Security

    business please click here: Creation Security  From CNHACKTEAM

Linux内核漏洞分析


Recommended Posts

该漏洞是Linux内核(5.12.4版之前)的近场通信(NFC)子系统中的发布后使用(UAF)。不过为了介绍方便,我们会在5.15版本中使用。

Si是由以下内容组成的插座:

套接字(AF_NFC,SOCK_STREAM,NFC_SOCKPROTO_LLCP)

那么假设我们将Si绑定到地址A,Si会加载一个指向类型为:nfc_llcp_local的对象L的指针。但是,如果Si的绑定失败,L将被释放,而Si到L仍然被定义。所以以后取消引用Sito L的计算,会让内核变成一种奇怪的状态。

1.png

当绑定失败释放l时,补丁会删除指向l的指针Si,详情请点击此处。https://www.openwall.com/lists/oss-security/2021/05/11/4

2.png

io_uring

输入/输出(I/O)可以同步或异步执行。对于前者,当我们请求一个I/O操作时,我们在继续其他工作之前等待它完成。对于后者,我们可以同时执行其他任务。假设异步I/O是同步I/O的超集。

Io_uring机制实现异步I/O,在设置一个io_uring实例后,我们得到两个相关的环形缓冲区:一个用于提交,一个用于完成。每次提交都是一个在内核上执行I/O操作的请求。完成是一个从I/O请求返回状态和数据的对象。我们从提交队列条目数组中检索一个对象,用操作码和相关数据初始化它,然后发出系统调用io_uring_enter进行提交。

我们还可以用IORING_SETUP_SQPOLL标志设置io_uring实例,这使内部内核线程能够轮询提交队列中的新提交。这通过确保始终有一个内核线程等待执行请求来减少系统调用开销。当这个内核线程从内核堆中的对象加载其凭证时,我们将利用这一点。

Userfaultfd

一种系统调用,使用户能够创建文件描述符,以实现用户空间中的按需分页。我们广泛使用这个系统调用,通过处理copy_from_user和copy_to_user中产生的保护漏洞来管理内核堆。

msgsnd 和 msgrcv

支持消息队列进程间通信的系统调用。它们是消息发送和消息接收。

页面漏洞

通常,当一个线程试图访问一个与物理帧没有关联的虚拟内存地址时,内存管理单元会发出一个页面漏洞。在这种情况下,内核可以通过为指定页面创建一个新的页表条目来解决这个问题。但是,在这个更一般的上下文中,我们还指试图访问以某种方式受保护的内存(即写入只读内存)所导致的一般保护漏洞。这是因为可以配置userfaultfd来处理“缺页”漏洞和保护漏洞。

假设运行环境

1.包含漏洞的内核版本5.15被重新引入。请注意,io_ring_ctx在5.12版本中不能立即使用,因为它是从kmalloc-4k发布的。但也许我们可以通过a)泄露或猜测io_ring_ctx对象的地址,b)将其伪造成一个列表,然后由kfree在其所有节点上“销毁”,或者覆盖L中的一个指针,该指针也被传递给kfree。

2.sysctl旋钮vm.unprivileged_userfaultfd=1。

3.默认情况下或通过用户名称空间使用CAP_NET_RAW函数。

战略概述

是泄漏并覆盖io_ring_ctx类型的对象r的策略。这样,我们可以控制R的sq_creds字段.请注意,选择R类型是因为从kmalloc-2k发出的R和L类型的对象是相似的。

$R \to$ sq_creds在提交队列轮询器(SQP)线程执行请求时替换其当前凭据。如果sq _ credits指向一个结构化信用对象$C$,那么$C$有uid、gid等。设置为0 ,那么SQP线程将执行I/O请求,就像调用用户是根用户一样。

因此,通过向io_uring实例(由受控的R管理)发出请求,我们可以作为非特权用户读写磁盘上的文件。这里的演示只是读取/etc/shadow。但要获得根权限,只需编辑/etc/passwd以进入一个根shell。

组件

我们使用以下组件和技术:

三个 NFC 套接字:S1、S2 和 S3;

六个线程:main、X、Y、Z、T 和 H;

msgsnd + msgrcv 用于泄漏 R。

userfaultfd + setxattr 用于写入 R 并确保 R 在利用期间和使用之后不会被另一个线程重新分配。

背景

使用三个 NFC 接口,我们可以释放L三次。但是,每次释放之后,我们需要覆盖L的某些部分,我们称之为local header,以确保随后的释放不会导致崩溃。具体来说,我们将 refcount 设置为 1,这样在下一次释放时不会将 refcount 设置为 $-1$,从而引发警告或导致进程崩溃。此外,我们确保L的第一个 list_head 字段定义明确。

使用线程 X、Y、Z,我们继续协调:再次分配 L,将L保存在内存中,从L读取并写入 L。这样,L就用R 表示,因为我们将曾经用于Si的相同对象重新分配给L为R。目的是通过我们的利用逻辑证明 L≡R。

用户使用setxattr 技术分配一个 kvalue 对象,其大小和内容由用户确定。它允许我们从内核对象开始写入。然而,当我们从用户空间完成对kvalue的写入后,kvalue被释放。

msgsnd + msgrcv 技术也是已知的,msgsnd 分配一个 msg_msg 对象,该对象充当标头,后跟消息文本,其长度和内容由用户确定。如果我们希望 msgrcv 正常工作,msg_msg 对象字段应该保持良好定义。因此,我们不能从内核对象的开头开始写。另一方面,msgrcv 将消息文本复制到用户空间,然后释放 msg_msg 对象。用户确定消息文本的长度以及接收和释放 msg_msg 的时间。

我们使用 userfaultfd 通过 setxattr 在内核上下文中暂停线程。我们使用该技术的两种变体。当读取用户空间地址时,会出现一个页面错误。另一个问题是在写入用户空间地址时出现页面错误。它们分别对应于copy_from_user和copy_to_user。前者与setxattr关联,后者与msgrcv关联。

“解除阻塞线程 X”通常意味着在内核上下文中处理由线程 X 引起的页面漏洞。一个线程不能处理它自己的页面漏洞,而是将它委托给层次结构中更上层的线程。但是,不一定是下一个,即线程 Z 解除线程 main 和线程 Y 的阻塞,而线程 Y 解除线程 X 的阻塞。

如果我们正在处理 setxattr 页面漏洞,那么“处理页面漏洞”意味着我们使用 ioctl 为 userfaultfd 子系统提供一个新缓冲区。然后这允许继续写入 kvalue (L) ,然后释放 kvalue (L)。同样,对于 msgrcv 页面漏洞,我们取消保护缓冲区并继续读取。建议读者查看上面链接的 Vitaly Nikolenko 的解释。这将解释我们在下一节中说的“特定偏移处的页面漏洞”时的意思。

方法描述

首先,我们关闭 S1,释放 L。然后我们分配 7 msgsnd 缓冲区,删除额外的分配。现在L是免费的,我们可以使用 setxattr 重新分配它。我们传递给 setxattr 的值是受内存保护的,因此在某个偏移量处读取将导致页面漏洞。使用 userfaultfd 我们注册相应的范围,以便线程 X 可以捕获页面漏洞。偏移量是我们自己为L类型定义的标头的大小。我们需要覆盖L的标头,以便再次释放它(当我们关闭 S2 时)不会导致早期崩溃。该标头仅包含一个 list_head 和一个 refcount 字段,我们将其计数器值设置为 1。

3.png

其次,从线程 X 中,我们从主线程中捕获 setxattr 中发出的页面漏洞。现在我们在主线程中做同样的事情,不同之处在于,我们为一个新值注册一个新范围,我们希望线程 Y 捕获下一个页面漏洞,并且页面漏洞发生的偏移量是 struct msg_msg +8。我们仍然用我们的标头覆盖 L。

4.png

第三,从线程 Y,我们捕获由 X 执行的 setxattr 中发出的前一个页面漏洞。然而,这一次在我们关闭 S3,第三次释放L之后,我们再次通过 msgsnd 分配 L。然后我们创建一个写入保护缓冲区并将其注册到 userfaultfd,我们的用户空间接收器 msg_msg 位于写入保护缓冲区的负偏移处。我们将 msg_msg 对象的地址传递给 msgrcv。当 msgrcv,特别是 store_msg 写入 msg_msg 的偏移量时,将发出另一个页面漏洞。此偏移量由 struct msg_msg 标头的大小决定。

5.png

此时,main 暂停以将L固定。我们稍后通过处理 main 中的 setxattr 发出的页面漏洞来解除对 main 的阻塞,这将释放L以重新分配为 R。此外,Y 会暂停以将L固定到位。 Y 的延续会将 L(此时为 R)中的数据泄漏到用户空间,然后 Y 将通过解决 X 发出的 setxattr 页面漏洞来继续线程 X。最后,当 Y 解决 X 的页面漏洞时,X 会暂停并继续写入L(R)。

第四,从线程 Z 中,我们捕获由 Y 执行的 msgrcv 中发出的页面漏洞。

现在 X 通过处理它的页面漏洞来解除对 main 的阻塞。这将继续 setxattr 写入L并随后第四次释放 L。然后我们通过 io_uring_setup 系统调用分配一个 io_ring_ctx 对象,因此 L\equiv R$。最后,Z 尝试获取一个锁,如果成功,它将导致它创建一个新线程 T,并使用 setxattr 再次分配 R。但是,此时,线程 Y 持有锁,因此 Z 暂停尝试获取它。

6.png

第五,Z 通过处理写入保护页漏洞来解除对线程 Y 的阻塞。这首先将 R 复制到用户空间。但是通过这样做,它也释放了R。因此,我们放弃上述锁定,确保 Z 继续。当 Z 继续时,它使用带有一个值的setxattr,当读取该值时会导致第一个字节出现页面漏洞。线程 T 捕捉到这个页面漏洞并且不处理它。因此,R 不能用于其他线程的重新分配和潜在的损坏。

7.png

在我们有了R$的数据之后,我们将当前的R 定位到用户空间缓冲区的sq_creds偏移量78(缩放因素 sizeof(uint64))。在读取泄露的 sq_creds 后,我们从中减去 176 。目的是让R 到sq_creds指向当前struct credt对象的“后面”。然后我们将新值写回我们的用户空间缓冲区。最后,我们通过处理 setxattr 发出的页面漏洞来解除对线程 X 的阻塞。这会将我们的用户空间缓冲区写入 R,维护除我们操作的 sq_creds 之外的所有字段。但是通过这样做,我们也释放了 R。所以我们再次使用 setxattr 并在 H 中捕获页面漏洞,方法与 T 中相同。

8.png

最后,我们从线程 T 中直接使用 open 获取目录“/etc/”的文件描述符。然后我们向 io_uring 实例发出 openat 请求。检索完成队列条目后,我们有了一个文件描述符“/etc/shadow”。然后我们发出一个读取请求,将“/etc/shadow”的内容复制到用户空间缓冲区中。

9.png

最终结果如下:

10.png


Link to comment
Share on other sites