【PAPER.0x05】论文笔记:DirtyCred: Escalating Privilege in Linux Kernel
本文最后更新于:2023年12月30日 晚上
新瓶装旧酒的 114514 种写法
0x00. 一切开始之前
DirtyCred 在 blackhat usa 2022上的演讲 ppt 和发在 CCS 上的论文笔者很早就看过了,简而言之这个利用手法的思路其实算是比较容易理解的,所以理论上来说并没有必要专门写一篇博客,但是最近刚好需要做关于 DirtyCred 的技术分享,以及 12 🈷笔者暂且一篇技术型博客都还没有写,为了不破坏曾经给自己定下来的规矩,笔者决定简单水一篇关于 DirtyCred 的博客:)
Abstract
DirtyPipe 在安全社区引起了一阵狂欢,但这个手法限制太多了,因此作者给出新的类似的利用方法——DirtyCred
,通过篡改凭证(credential)结构体来进行利用,同时作者还给出了防范手段
KEYWORDS:OS Security; Kernel Exploitation; Privilege Escalation
0x01. Introduction
Linux 对于黑客们而言非常流行,但 KASLR、CFI 等防护手段让利用变得困难,因此 CVE-2022-0847 (DirtyPipe)的出现引发了热潮——她不需要与众多内核防护措施,但仍存在不够普适的缺陷
笔者注:这里作者为了抬高自己的手法在前面贬低了一嘴 DirtyPipe 做铺垫,但其实将漏洞转换为 DirtyPipe 已经是非常常见的利用方式了,就笔者所知 DirtyPipe 比 DirtyCred 实际上适用性是更加广泛的,现在在 CTF 与真实世界漏洞利用中大家都倾向于将漏洞转为 DirtyPipe、而并非 DirtyCred 进行利用,
但是为了发论文嘛肯定得自吹自擂一手
本文提出一种通用利用手法——DirtyCred
,其并不基于管道机制或是 CVE-2022-0847,而是通过堆漏洞将低权限的 credentials 结构体替换为高权限的 credentials,简而言之本文贡献如下:
- 提出了新的利用手法 DirtyCred,其可以规避许多内核保护机制
- 证明了 DirtyCred 可以在真实世界漏洞中广泛进行利用
- 分析现有内核保护机制并设计出了一种新的保护机制
0x02. Background & Threat Model
2.1 Credentials in Linux kernel
凭证(credentials)指内核中包含权限信息的东西,在 Linux 内核中被实现为带有权限信息的内核对象(包括 cred
、file
、inode
,本文仅用前两个因为第三个仅会在创建文件时被分配):
- Linux 内核中每个进程都有一个指向
cred
对象的指针,其中有着该进程的权限信息,如当进程要访问文件时便会检查 cred 的 UID;cred 对象还描述了能力(capability),如CAP_NET_BIND_SERVICE
说明了进程可以将一个套接字绑定到一个端口上 - Linux 内核中每个文件都有着对应的权限设置,且与 inode 对象绑定,在进程要打开文件前内核会调用
inode_permission
检查权限,在文件打开后file
对象用以记录权限设置
2.2 Kernel Heap Memory Management
内核堆对象有两种缓存池:
- 通用缓存(Generic Caches):相同大小的都能从这类池中进行分配
- 专用缓存(Dedicated Caches):出于安全与性能目的考虑设计的仅供某些结构体分配的池
2.3 Threat Model
作者假设非特权本地用户可以访问 Linux 系统,目的是利用堆内存损坏漏洞进行提权,并假设上游(5.15)所有缓解措施与保护机制都已启用(包括KASLR、SMAP、SMEP、CFI、KPTI 等)
0x03. Technical Overview & Challenges
3.1 Overview
作者使用 CVE-2021-4154 作为例子说明 DirtyCred,这是一个 fs_context
对象错误引用 file
对象所导致的类型混淆错误,这允许将正在使用中的 file
进行释放
如图 1 所示:
- 首先打开可写文件
/tmp/x
分配一个file
,之后尝试进行数据写入,权限检查通过,之后 DirtyCred 将文件写入暂停 - 接下来触发漏洞将
file
结构体释放 - 之后打开只读文件
/etc/passwd
重新取回该file
对象,并恢复数据写入,此时便成功完成越权写
该例子只是用来说明 DirtyCred 如何利用 file 结构体进行利用,如 Section 2 所言,除了 file
以外,cred
也可以作为利用对象
从真实世界案例中可以看出 DirtyCred 不会劫持控制流,本质上是操纵内存中的内核对象,因此许多现有的控制流防护手段无法进行防御,一些工作(如 AUTOSLAB)则在对抗 DirtyCred,但如第 8 节所言其仍无法防御这种手段
3.2 Technical Challenges
DirtyCred 仍面临一些挑战:
- DirtyCred 需要 invalid-free capability 去释放掉一个低权限对象并重新分配为高权限对象,这种能力通常难以获得,因此 DirtyCred 需要将漏洞的不同能力转为所需能力,我们将在第 4 节进行描述
- DirtyCred 需要暂停文件写入操作,这同样是一个挑战,我们将在第 5 节中介绍可以达成该目标的多种机制
- DirtyCred 的关键步骤是要用高权限凭证替换掉低权限的,但对于低权限用户而言分配高权限凭证也是一个挑战,我们将在第 6 节中给出解决方案
0x04. Pivoting Vulnerability Capability
Pwn 人基本功了嗷,憋和👴说你不会嗷
虽然 CVE-2021-4154 展示了 DirtyCred 的威力,但实战中我们并不一定有这样的能力,因此我们需要进行能力转换(笔者注:漏洞迁移的概念)
4.1 Pivoting OOB & UAF Write
如图 2 所示,我们可以通过 partial overwrite 将包含指向凭证对象的指针的内核对象进行复写,使得两个指针指向同一个凭证对象,从而进行 DirtyCred,由于堆喷连续分配的结构体通常分组来自相同 slab,因此可行性很高
4.2 Pivoting DF
通用缓存(如 kmalloc-96
)与专用缓存(如 cred_jar
)间存在隔离,但我们可以通过释放缓存页面并重新分配的方式使得跨缓存的内存操作成为可能,如图 3 所示,我们先分配大量对象,通过漏洞我们有两个指针指向同一对象,在大量分配后我们将其大量释放,并保留一个垂悬指针,之后再分配为凭证结构体对象,以此进行 DirtyCred
笔者注:这个手法也出现在笔者近期供给某些 CTF 比赛中的 kernel pwn 题中,可惜怎么都是 0 解(恼)
但如图 3(f) 所示,漏洞对象大小不一定匹配凭证对象大小,从而无法 DirtyCred,这种情况下我们需要保有两个指针,将其中一个释放掉从而构造出空内存槽以分配为凭证结构体,再进行释放以获取所需能力
0x05. Extending Time Window
DirtyCred 需要我们延长权限检查到写入之间的时间窗口
5.1 Exploitation of Userfaultfd & FUSE
简而言之长话短说可以参见这里
userfaultfd 允许我们在用户态手动处理缺页异常,FUSE 允许我们实现用户空间文件系统,这为延长时间窗口提供了可行性,如读写注册了 userfaultfd 的内存页或是读写 FUSE 文件来触发我们的自定义 handler 函数
下面我们以 userfaultfd 为例(FUSE 利用方式类似),DirtyCred 通过系统调用 writev()
进行文件写入,不同于 write()
系统调用,其使用 iovec
向量来传递数据,如 List 1 所示,4.13 版本前的系统调用 writev()
首先进行权限检查,之后再通过 iovec
向量导入用户空间数据,最后才是写入,因此 DirtyCred 可以很轻易地使用 userfaultfd 的特性来获得合适的时间窗口
这项技术初见于 CVE-2016-4557,但现在已不再可用
5.2 Alternative Exploitation of Userfaultfd & FUSE
如 List 2 所示,在内核版本 4.13 之后导入 iovec
向量的步骤被移动到了权限检查之前,这使得我们不再能扩大权限检查与文件写入间的时间窗口,为了解决这个问题,DirtyCred 将利用 Linux 文件系统的设计
内核文件系统的设计遵循严格的层次关系,高层接口统一而低层接口各异,写入文件时会调用高层接口,如 List 3 所示, generic_perform_write()
为统一的高层接口,在第 15 ~ 17 行其会调用对应文件系统的写入操作,出于性能考虑内核在写入前会拷贝 iovec 向量数据,从而触发缺页异常,由此 DirtyCred 可以在第 10 行使用 userfaultfd 来延长时间窗口
与通过导入 iovec
来暂停内核的执行相比,对文件系统的设计进行利用则更难被缓解,如 List 3 中注释所言,移除 iovec 的 page fault 有可能造成死锁;将 page fault 移到权限检查前可能解决问题,但这会对性能造成影响,且仍存在潜在的被绕过的可能,如 DirtyCred 可以在 paeg fault 之后再移除该页,从而在拷贝时再次 page fault 以暂停(有点扯了)
5.3 Exploitation of Lock in Filesystem
Linux 文件系统存在锁机制(如 List 4 为 ext4 中的锁),这为 DirtyCred 创造了机会,我们可以创建两个进程 A 与 B 同时写入同一文件,在 A 持有锁进行写入时 B 陷入等待,而在 generic_perform_write()
之前权限检查早已完成,由此 DirtyCred 可以通过写入大量数据来创造一个较长的时间窗口以完成 file 的替换(据作者观察写入 4GB 文件大概需要好几秒)
0x06. Allocating Privileged Object
DirtyCred 需要在内核空间分配特权对象,本节叙述如何以一个低权限用户做到这一点
6.1 Allocation from Userspace
cred
对象代表了对应内核进程的权限,因此 DirtyCred 可以通过执行 root-SUID 程序来创建 root 进程(而不用寻找这类程序中的漏洞),这样的程序包括 sudo、pkexec 等
DirtyCred 除了替换 cred
对象以外也可以通过替换 file
对象来提权,不过 file
对象的分配远比 cred
容易,以对应权限打开文件即可
6.2 Allocation from Kernel Space
除了从用户空间分配特权对象以外,DirtyCred 也可以从内核空间分配特权对象,如生成新的特权内核线程完成特权凭证对象的分配,主要有两种方式:
- 调用内核代码触发内核在内部生成特权线程,如通过向 工作队列 提交任务让内核创建新的工作者线程
- 唤醒 usermode helper,如通过加载内核模块来让内核启动特权用户态 usermode helper 程序(如 modprobe)
0x07. Evaluation
作者设计了两个实验来在真实世界漏洞上评估 DirtyCred
7.1 Experiment Design & Setup
作者引入了一种自动化方法(设计实现参见附录 A)寻找可供 DirtyCred 利用的内核对象,并应用于 5.16.15 版本的内核
作者还探讨了针对实际漏洞利用的可行性,如第 4 节所言,若漏洞未直接提供交换凭证结构体的能力,则需要我们进行转换
作者假设 Linux 内核使用其最先进的防护手段,并在评估中仅选择 2019 年以后的 CVE,数据集如表 2 所示,这涵盖了几乎所有的堆上漏洞类型
7.2 Experiment Result
可利用对象。表 1 展示了不同缓存池中可以被利用的内核对象,几乎所有通用缓存池(除了 kmalloc-8
)都有可供 DirtyCred 利用对象:
可利用性。表 2 显示了 DirtyCred 在不同漏洞上的可利用性,在开启所有防护的情况下其在 24 个漏洞中的 16 个上都完成了利用,这表明了其通用性与强大
还有一些关于利用失败的阐述,这里笔者就不抄了
0x08. Defence Against DirtyCred
现有各种防护主要针对控制流劫持,难以对抗 DirtyCred,作者认为一种有效的对抗方式是隔离高权限与低权限对象,比较直接的想法是创建不同的缓存池,但这仍能通过跨缓存的页回收完成利用
基于上述考虑,作者设计的解决方案是为高权限对象在虚拟内存区域创建一个缓存(创建的凭证对象 ID 为 GLOBAL_ROOT_UID
时或以写权限打开文件时则使用 vmalloc 分配),同时低权限对象保留在正常内存区域,从而隔离开内存页;但运行时权限更改(如 setuid 系统调用)仍能破坏这种机制,作者的解决方案是为更改操作添加检查,若是改为 GLOBAL_ROOT_UID
则将高权限凭证对象复制到 vmalloc 区域而非更改原始对象,但这需要未来的内核开发遵循相同的模式,因此作者仍在探索替代解决方案
性能评估结果如表 3 所示:
在这项工作中,作者称其主要目标是提高Linux社区的意识,而不是构建一个安全、高效的防御解决方案,且作者将探索替代防御解决方案作为未来研究的一部分
0x09. Related Work
这一节主要介绍与论文相关的两方面工作——内核利用(exploitation)与内核防护(defence),都是常识性内容所以这里笔者就不摘抄了:)
0x10. Discussion & Future Work
这节讨论之前没提到的一些点:
Escaping container. 容器中的文件并不提供交换命名空间的能力,但一项最近的研究显示攻击者可以被动等待 runC 进程,由此可以通过覆写进程来在 host 侧以 root 运行命令,DirtyCred 可以以此完成容器逃逸;利用 cred 对象则不需要被动等待,通过交换 cred 获得
SYS_ADMIN
权限使得攻击者可以挂载 cgroup 后利用notify_no_release
机制来在 host 侧以 root 执行命令,作者在这里给出了案例Rooting Android. 安卓也是 Linux 内核,DirtyCred 可以通过文中讨论的两种方式完成安卓提权,不过实战中安卓内核有着更严格的访问限制,直接交换 cred 是可行的,对于文件而言可以首先覆写共享库以完成沙箱逃逸,之后再覆写内核模块,作者在 0day 漏洞上用 DirtyCred 完成了利用并获得更Google 的致谢
Cross version/architecture exploitation. DirtyCred 的利用是跨版本跨架构的,因为其基本不需要内核信息,也不需要特定于架构的数据
Other way to pivot capability. 虚拟内存区上的漏洞更难被转化为 DirtyCred,但不意味着无法使用 DirtyCred,如这个工作将 vmalloc 区上的越界写漏洞 CVE-2021-34866 转为任意内存读写
Stability. 稳定性主要受内存布局与漏洞触发方式影响,最近的一个工作提出了一系列方法提高利用稳定性
TOCTOU. (Time-Of-Check to Time-of-Use) DirtyCred 在关键时间窗口交换凭证对象,直觉告诉我们现有的 TOCTOU 防御方法可能会阻碍 DirtyCred,但针对最近一篇关于 TOCTOU 的研究文章分析,实际上并不会,因为 DirtyCred 使用了意外的释放操作,而这在各种分析过程中不可见
0x11. Conclusion
作者开发了 Linux 内核通用利用手法 DirtyCred,很好很强大,撒花~🌸🌸🌸