6
6
#
7
7
# KNOWN ISSUES
8
8
#
9
- # 1) Interactive channels (ie shell) do not work well/at all. Neither do pivots. Issue
10
- # seems to be multiple threads writing to the pipe or some writes not being synchronized
11
- # and bypassing the OpenPipeSock methods.
12
- # 2) A peek named pipe operation is carried out before every read to prevent blocking. This
13
- # generates extra traffic.
9
+ # 1) A peek named pipe operation is carried out before every read to prevent blocking. This
10
+ # generates extra traffic. SMB echo requests are also generated to force the packet
11
+ # dispatcher to perform a read.
14
12
#
15
13
16
14
#
22
20
# an issue when there are multiple writes since it will cause select to return which
23
21
# triggers a read, but there is nothing to read since the pipe will already have read
24
22
# the response. This read will then hold the mutex while the socket read waits to timeout.
23
+ # A peek operation on the pipe fixes this.
25
24
#
26
25
class OpenPipeSock < Rex ::Proto ::SMB ::SimpleClient ::OpenPipe
27
- attr_accessor :mutex , :chunk_size , : last_comm, :write_queue , :write_thread , :read_buff
26
+ attr_accessor :mutex , :last_comm , :write_queue , :write_thread , :read_buff , :echo_thread , :server_max_buffer_size
28
27
29
- def initialize ( *args )
28
+ STATUS_BUFFER_OVERFLOW = 0x80000005
29
+
30
+ def initialize ( *args , server_max_buffer_size :)
30
31
super ( *args )
31
32
self . client = args [ 0 ]
32
33
self . mutex = Mutex . new # synchronize read/writes
33
34
self . last_comm = Time . now # last successfull read/write
34
35
self . write_queue = Queue . new # queue message to send
35
36
self . write_thread = Thread . new { dispatcher }
37
+ self . echo_thread = Thread . new { force_read }
36
38
self . read_buff = ''
39
+ self . server_max_buffer_size = server_max_buffer_size
40
+ self . chunk_size = server_max_buffer_size - 260
37
41
end
38
42
39
- # Check if there are any bytes to read and return number available.
40
- # Access must be synchronized.
43
+ # Check if there are any bytes to read and return number available. Access must be synchronized.
41
44
def peek_named_pipe
42
45
# 0x23 is the PeekNamedPipe operation. Last 16 bits is our pipes file id (FID).
43
46
setup = [ 0x23 , self . file_id ] . pack ( 'vv' )
@@ -46,11 +49,34 @@ def peek_named_pipe
46
49
avail = 0
47
50
begin
48
51
avail = pkt . to_s [ pkt [ 'Payload' ] . v [ 'ParamOffset' ] +4 , 2 ] . unpack ( 'v' ) [ 0 ]
52
+ self . last_comm = Time . now
49
53
rescue
50
54
end
55
+
56
+ if ( avail == 0 ) and ( pkt [ 'Payload' ] [ 'SMB' ] . v [ 'ErrorClass' ] == STATUS_BUFFER_OVERFLOW )
57
+ avail = self . client . default_max_buffer_size
58
+ end
59
+
51
60
avail
52
61
end
53
62
63
+ # Send echo request to force select() to return in the packet dispatcher and read from the socket.
64
+ # This allows "channel -i" and "shell" to work.
65
+ def force_read
66
+ wait = 0.5 # smaller is faster but generates more traffic
67
+ while true
68
+ elapsed = Time . now - self . last_comm
69
+ if elapsed > wait
70
+ self . mutex . synchronize do
71
+ self . client . echo ( )
72
+ self . last_comm = Time . now
73
+ end
74
+ else
75
+ Rex ::ThreadSafe . sleep ( wait -elapsed )
76
+ end
77
+ end
78
+ end
79
+
54
80
# Runs as a thread and synchronizes writes. Allows write operations to return
55
81
# immediately instead of waiting for the mutex.
56
82
def dispatcher
@@ -74,8 +100,8 @@ def dispatcher
74
100
def close
75
101
# Give the meterpreter shutdown command a chance
76
102
self . write_queue . close
77
- while self . write_queue . size > 0
78
- sleep ( 0.1 )
103
+ if self . write_queue . size > 0
104
+ sleep ( 1.0 )
79
105
end
80
106
self . write_thread . kill
81
107
@@ -90,25 +116,32 @@ def close
90
116
def read ( count )
91
117
data = ''
92
118
begin
93
- self . mutex . synchronize do
94
- avail = peek_named_pipe
95
- if avail > 0
96
- while count > 0
97
- buff = super ( [ count , self . chunk_size ] . min )
98
- self . last_comm = Time . now
99
- count -= buff . length
100
- data += buff
119
+ if count > self . read_buff . length
120
+ # need more data to satisfy request
121
+ self . mutex . synchronize do
122
+ avail = peek_named_pipe
123
+ if avail > 0
124
+ left = [ count -self . read_buff . length , avail ] . max
125
+ while left > 0
126
+ buff = super ( [ left , self . chunk_size ] . min )
127
+ self . last_comm = Time . now
128
+ left -= buff . length
129
+ self . read_buff += buff
130
+ end
101
131
end
102
132
end
103
133
end
104
134
rescue
105
135
end
106
136
137
+ data = self . read_buff [ 0 , [ count , self . read_buff . length ] . min ]
138
+ self . read_buff = self . read_buff [ data . length ..-1 ]
139
+
107
140
if data . length == 0
108
141
# avoid full throttle polling
109
- Rex ::ThreadSafe . sleep ( 0.1 )
142
+ Rex ::ThreadSafe . sleep ( 0.2 )
110
143
end
111
- return data
144
+ data
112
145
end
113
146
114
147
def put ( data )
@@ -157,7 +190,7 @@ def initialize(*args)
157
190
def create_pipe ( path )
158
191
pkt = self . client . create_pipe ( path , Rex ::Proto ::SMB ::Constants ::CREATE_ACCESS_EXIST )
159
192
file_id = pkt [ 'Payload' ] . v [ 'FileID' ]
160
- self . pipe = OpenPipeSock . new ( self . client , path , self . client . last_tree_id , file_id )
193
+ self . pipe = OpenPipeSock . new ( self . client , path , self . client . last_tree_id , file_id , server_max_buffer_size : self . server_max_buffer_size )
161
194
end
162
195
end
163
196
@@ -202,7 +235,6 @@ def initialize(info={})
202
235
register_advanced_options (
203
236
[
204
237
OptString . new ( 'SMBDirect' , [ true , 'The target port is a raw SMB service (not NetBIOS)' , true ] ) ,
205
- OptInt . new ( 'CHUNKSIZE' , [ false , 'Max pipe read/write size per request' , 4096 ] ) # > 4096 is unreliable
206
238
] , Msf ::Handler ::BindNamedPipe )
207
239
208
240
self . conn_threads = [ ]
@@ -236,7 +268,6 @@ def start_handler
236
268
smbdomain = datastore [ 'SMBDomain' ]
237
269
smbdirect = datastore [ 'SMBDirect' ]
238
270
smbshare = "\\ \\ #{ rhost } \\ IPC$"
239
- chunk_size = datastore [ 'CHUNKSIZE' ]
240
271
241
272
# Ignore this if one of the required options is missing
242
273
return if not rhost
@@ -304,7 +335,6 @@ def start_handler
304
335
return
305
336
end
306
337
307
- pipe . chunk_size = chunk_size
308
338
vprint_status ( "Opened pipe \\ #{ pipe_name } " )
309
339
310
340
# Increment the has connection counter
0 commit comments