1
mirror of https://github.com/rapid7/metasploit-framework synced 2024-11-12 11:52:01 +01:00

Commit the Ruby end for TCP server channels, the modified TCP client channels and the support for pivoting a reverse_tcp meterpreter.

git-svn-id: file:///home/svn/framework3/trunk@8384 4d416f70-5f16-0410-b530-b9f4589650da
This commit is contained in:
Stephen Fewer 2010-02-06 17:59:25 +00:00
parent a80d1ad2ee
commit fd2469db24
14 changed files with 393 additions and 196 deletions

View File

@ -1,3 +1,5 @@
require 'rex/socket'
module Msf
module Handler
@ -67,28 +69,38 @@ module ReverseTcp
ex = false
# Switch to IPv6 ANY address if the LHOST is also IPv6
addr = Rex::Socket.resolv_nbo(datastore['LHOST'])
# First attempt to bind ANY_ADDR. If that fails, the user probably has
# something else listening on one interface. Try again with the
# specific LHOST. Use the any addr for whatever LHOST was, ipv4 or 6.
# First attempt to bind LHOST. If that fails, the user probably has
# something else listening on that interface. Try again with ANY_ADDR.
any = (addr.length == 4) ? "0.0.0.0" : "::0"
[ any, Rex::Socket.addr_ntoa(addr) ].each { |ip|
[ Rex::Socket.addr_ntoa(addr), any ].each { |ip|
begin
print_status("Handler trying to bind to #{ip}") if ip != any
self.listener_sock = Rex::Socket::TcpServer.create(
'LocalHost' => ip,
'LocalPort' => datastore['LPORT'].to_i,
'Comm' => comm,
'Context' =>
{
'Msf' => framework,
'MsfPayload' => self,
'MsfExploit' => assoc_exploit
})
ex = false
comm_used = Rex::Socket::SwitchBoard.best_comm( ip )
comm_used = Rex::Socket::Comm::Local if comm_used == nil
if( comm_used.respond_to?( :type ) and comm_used.respond_to?( :sid ) )
via = "via the #{comm_used.type} on session #{comm_used.sid}"
else
via = ""
end
print_status("Started reverse handler on #{ip}:#{datastore['LPORT']} #{via}")
break
rescue
ex = $!
print_error("Handler failed to bind to #{ip}")
print_error("Handler failed to bind to #{ip}:#{datastore['LPORT']}")
end
}
raise ex if (ex)
@ -114,8 +126,6 @@ module ReverseTcp
self.listener_thread = Thread.new {
client = nil
print_status("Started reverse handler on port #{datastore['LPORT']}")
begin
# Accept a client connection
begin

View File

@ -32,22 +32,26 @@ module Stream
# of, at most, 32768 bytes.
#
def write(buf, opts = {})
tsent = 0
bidx = 0
total_sent = 0
total_length = buf.length
begin
while (bidx < buf.length)
sent = fd.syswrite(buf[bidx, 32768])
bidx += sent if sent > 0
tsent += sent
while( total_sent < total_length )
s = Rex::ThreadSafe.select( nil, [ fd ], nil, 0.2 )
if( s == nil || s[0] == nil )
next
end
data = buf[0, 32768]
sent = fd.syswrite( data )
if sent > 0
total_sent += sent
buf[0, sent] = ""
end
end
rescue ::IOError, ::Errno::EPIPE
return nil if (fd.abortive_close == true)
raise $!
end
tsent
total_sent
end
#
@ -90,7 +94,7 @@ module Stream
#
def fd
self
end
end
##
#
@ -290,7 +294,7 @@ module Stream
# This flag indicates whether or not an abortive close has been issued.
#
attr_accessor :abortive_close
protected
end

View File

@ -4,6 +4,7 @@ require 'thread'
require 'rex/socket'
require 'rex/post/meterpreter/extensions/stdapi/tlv'
require 'rex/post/meterpreter/extensions/stdapi/net/socket_subsystem/tcp_client_channel'
require 'rex/post/meterpreter/extensions/stdapi/net/socket_subsystem/tcp_server_channel'
require 'rex/logging'
module Rex
@ -31,23 +32,21 @@ class Socket
##
#
# Initialize the socket subsystem and start monitoring sockets
# as they come in.
# Initialize the socket subsystem and start monitoring sockets as they come in.
#
def initialize(client)
self.client = client
self.monitored_sockets = []
self.monitored_socket_channels = {}
self.client = client
# register the inbound handler for the tcp server channel (allowing us to receive new client connections to a tcp server channel)
client.register_inbound_handler( Rex::Post::Meterpreter::Extensions::Stdapi::Net::SocketSubsystem::TcpServerChannel )
# Start monitoring the sockets
self.monitor_sockets
end
#
# Terminate the monitor thread.
# Deregister the inbound handler for the tcp server channel
#
def shutdown
monitor_thread.kill
client.deregister_inbound_handler( Rex::Post::Meterpreter::Extensions::Stdapi::Net::SocketSubsystem::TcpServerChannel )
end
##
@ -61,39 +60,23 @@ class Socket
# supplied in the socket parameters instance. The 'params' argument
# is expected to be of type Rex::Socket::Parameters.
#
def create(params)
channel = nil
res = nil
# begin
if (params.tcp?)
if (params.server?)
channel = create_tcp_server(params)
else
channel = create_tcp_client(params)
end
# Add this channel's right socket to the socket monitor
add_monitored_socket(channel.rsock, channel)
# If we get a valid channel back, create a stream
# representation of the left side of the socket for
# the caller to use
if (channel != nil)
res = channel.lsock
end
elsif (params.udp?)
if (params.server?)
res = create_udp_server(params)
else
res = create_udp_client(params)
end
# TODO: Datagram wrapper
def create( params )
res = nil
if( params.tcp? )
if( params.server? )
res = create_tcp_server( params )
else
res = create_tcp_client( params )
end
# rescue
# end
elsif( params.udp? )
if( params.server? )
res = create_udp_server( params )
else
res = create_udp_client( params )
end
end
return res
end
@ -101,6 +84,15 @@ class Socket
# Create a TCP server channel.
#
def create_tcp_server(params)
begin
return SocketSubsystem::TcpServerChannel.open(client, params)
rescue ::Rex::Post::Meterpreter::RequestError => e
case e.result
when 10000 .. 10100
raise ::Rex::ConnectionError.new
end
raise e
end
end
#
@ -108,7 +100,11 @@ class Socket
#
def create_tcp_client(params)
begin
return SocketSubsystem::TcpClientChannel.open(client, params)
channel = SocketSubsystem::TcpClientChannel.open(client, params)
if( channel != nil )
return channel.lsock
end
return nil
rescue ::Rex::Post::Meterpreter::RequestError => e
case e.result
when 10000 .. 10100
@ -132,104 +128,6 @@ class Socket
protected
##
#
# Socket monitoring
#
##
#
# Monitors zero or more sockets and handles forwarding traffic
# to the remote half of the associated channel.
#
def monitor_sockets
self.monitor_thread = ::Thread.new {
loop do
# Watch for data
begin
socks = select(monitored_sockets, nil, nil, 0.25)
rescue StreamClosedError => e
channel = monitored_socket_channels[e.stream.object_id]
dlog("monitor_channels: channel #{channel} closed (#{e.stream})",'meterpreter', LEV_3)
if (channel)
begin
channel.close
rescue IOError
end
remove_monitored_socket(e.stream)
end
next
end
# No data?
if (socks == nil || socks[0] == nil)
next
end
# Enumerate through each of the indicated sockets
socks[0].each { |sock|
channel = monitored_socket_channels[sock.object_id]
closed = false
data = nil
if (channel == nil)
remove_monitored_socket(sock)
next
end
begin
data = sock.sysread(16384)
rescue
closed = true
end
if (data == nil)
closed = true
end
# If the socket closed, notify the other side and remove
# this socket from the monitored socket list
if (closed)
channel.close_write
remove_monitored_socket(sock)
# Otherwise, write the data to the remote side
else
channel.write(data)
end
}
end
}
end
#
# Adds a socket to the list of monitored sockets.
#
def add_monitored_socket(sock, channel)
monitored_sockets << sock
monitored_socket_channels[sock.object_id] = channel
end
#
# Removes a socket from the list of monitored sockets.
#
def remove_monitored_socket(sock)
monitored_socket_channels.delete(sock.object_id)
monitored_sockets.delete(sock)
end
attr_accessor :monitored_sockets, :monitored_socket_channels # :nodoc:
attr_accessor :monitor_thread # :nodoc:
attr_accessor :client # :nodoc:
end

View File

@ -1,5 +1,7 @@
#!/usr/bin/env ruby
require 'thread'
require 'rex/post/meterpreter/channel'
require 'rex/post/meterpreter/channels/stream'
require 'rex/post/meterpreter/extensions/stdapi/tlv'
@ -20,7 +22,13 @@ module SocketSubsystem
#
###
class TcpClientChannel < Rex::Post::Meterpreter::Stream
class << self
def cls
return CHANNEL_CLASS_STREAM
end
end
module SocketInterface
def type?
'tcp'
@ -28,7 +36,6 @@ class TcpClientChannel < Rex::Post::Meterpreter::Stream
def getsockname
return super if not channel
# Find the first host in our chain (our address)
hops = 0
csock = channel.client.sock
@ -36,7 +43,6 @@ class TcpClientChannel < Rex::Post::Meterpreter::Stream
csock = csock.channel.client.sock
hops += 1
end
tmp,caddr,cport = csock.getsockname
tmp,raddr,rport = csock.getpeername
maddr,mport = [ channel.params.localhost, channel.params.localport ]
@ -52,7 +58,26 @@ class TcpClientChannel < Rex::Post::Meterpreter::Stream
attr_accessor :channel
end
#
# Simple mixin for lsock in order to help avoid a ruby interpreter issue with ::Socket.pair
# Instead of writing to the lsock, reading from the rsock and then writing to the channel,
# we use this mixin to directly write to the channel.
#
# Note: This does not work with OpenSSL as OpenSSL is implemented nativly and requires a real
# socket to write to and we cant intercept the sockets syswrite at a native level.
#
# Note: The deadlock only seems to effect the Ruby build for cygwin.
#
module DirectChannelWrite
def syswrite( buf )
channel._write( buf )
end
attr_accessor :channel
end
##
#
# Factory
@ -99,15 +124,18 @@ class TcpClientChannel < Rex::Post::Meterpreter::Stream
#
# Passes the channel initialization information up to the base class.
#
def initialize(client, cid, type, flags)
super(client, cid, type, flags)
def initialize( client, cid, type, flags )
super( client, cid, type, flags )
# Implement some of the required socket interfaces on the local side of
# the stream abstraction.
lsock.extend(SocketInterface)
rsock.extend(SocketInterface)
lsock.extend( SocketInterface )
lsock.extend( DirectChannelWrite )
lsock.channel = self
rsock.extend( SocketInterface )
rsock.channel = self
monitor_rsock()
end
#
@ -133,7 +161,67 @@ class TcpClientChannel < Rex::Post::Meterpreter::Stream
response = client.send_request(request)
return true
end
protected
def monitor_rsock
self.monitor_thread = ::Thread.new {
loop do
closed = false
buf = nil
begin
s = Rex::ThreadSafe.select( [ self.rsock ], nil, nil, 0.2 )
if( s == nil || s[0] == nil )
next
end
rescue Exception => e
closed = true
end
if( closed == false )
begin
buf = self.rsock.sysread( 32768 )
closed = true if( buf == nil )
rescue
closed = true
end
end
if( closed == false )
total_sent = 0
total_length = buf.length
while( total_sent < total_length )
begin
data = buf[0, buf.length]
sent = self.write( data )
# sf: Only remove the data off the queue is syswrite was successfull.
# This way we naturally perform a resend if a failure occured.
# Catches an edge case with meterpreter TCP channels where remote send
# failes gracefully and a resend is required.
if( sent > 0 )
total_sent += sent
buf[0, sent] = ""
end
rescue ::IOError => e
closed = true
break
end
end
end
if( closed )
self.close_write
::Thread.exit
end
end
}
end
attr_accessor :monitor_thread
end

View File

@ -0,0 +1,167 @@
require 'timeout'
require 'thread'
require 'rex/socket/parameters'
require 'rex/post/meterpreter/channels/stream'
require 'rex/post/meterpreter/extensions/stdapi/tlv'
require 'rex/post/meterpreter/extensions/stdapi/net/socket_subsystem/tcp_client_channel'
module Rex
module Post
module Meterpreter
module Extensions
module Stdapi
module Net
module SocketSubsystem
class TcpServerChannel < Rex::Post::Meterpreter::Channel
#
# This is a class variable to store all pending client tcp connections which have not been passed
# off via a call to the respective server tcp channels accept method. The dictionary key is the
# tcp server channel instance and the values held are an array of pending tcp client channels
# connected to the tcp server channel.
#
@@server_channels = {}
class << self
include Rex::Post::Meterpreter::InboundPacketHandler
#
# This is the request handler which is registerd to the respective meterpreter instance via
# Rex::Post::Meterpreter::Extensions::Stdapi::Net::Socket. All incoming requests from the meterpreter
# for a 'tcp_channel_open' will be processed here. We create a new TcpClientChannel for each request
# received and store it in the respective tcp server channels list of new pending client channels.
# These new tcp client channels are passed off via a call the the tcp server channels accept() method.
#
def request_handler( client, packet )
if( packet.method == "tcp_channel_open" )
cid = packet.get_tlv_value( TLV_TYPE_CHANNEL_ID )
pid = packet.get_tlv_value( TLV_TYPE_CHANNEL_PARENTID )
localhost = packet.get_tlv_value( TLV_TYPE_LOCAL_HOST )
localport = packet.get_tlv_value( TLV_TYPE_LOCAL_PORT )
peerhost = packet.get_tlv_value( TLV_TYPE_PEER_HOST )
peerport = packet.get_tlv_value( TLV_TYPE_PEER_PORT )
if( cid == nil or pid == nil )
return false
end
server_channel = client.find_channel( pid )
if( server_channel == nil )
return false
end
params = Rex::Socket::Parameters.from_hash(
{
'Proto' => 'tcp',
'LocalHost' => localhost,
'LocalPort' => localport,
'PeerHost' => peerhost,
'PeerPort' => peerport,
'Comm' => server_channel.client
}
)
client_channel = TcpClientChannel.new( client, cid, TcpClientChannel, CHANNEL_FLAG_SYNCHRONOUS )
client_channel.params = params
if( @@server_channels[server_channel] == nil )
@@server_channels[server_channel] = []
end
@@server_channels[server_channel] << client_channel
return true
end
return false
end
def cls
return CHANNEL_CLASS_STREAM
end
end
#
# Open a new tcp server channel on the remote end.
#
def TcpServerChannel.open(client, params)
c = Channel.create(client, 'stdapi_net_tcp_server', self, CHANNEL_FLAG_SYNCHRONOUS,
[
{
'type' => TLV_TYPE_LOCAL_HOST,
'value' => params.localhost
},
{
'type' => TLV_TYPE_LOCAL_PORT,
'value' => params.localport
}
] )
c.params = params
c
end
#
# Simply initilize this instance.
#
def initialize(client, cid, type, flags)
super(client, cid, type, flags)
# add this instance to the class variables dictionary of tcp server channels
@@server_channels[self] = []
end
#
# Accept a new tcp client connection form this tcp server channel. This method does not block
# and returns nil if no new client connection is available.
#
def accept_nonblock
result = nil
if( @@server_channels[self].length > 0 )
channel = @@server_channels[self].shift
result = channel.lsock
end
return result
end
#
# Accept a new tcp client connection form this tcp server channel. This method will block indefinatly
# if no timeout is specified.
#
def accept( opts={} )
timeout = opts['Timeout'] || -1
if( timeout == -1 )
result = _accept
else
begin
::Timeout.timeout( timeout ) {
result = _accept
}
rescue Timeout::Error
result = nil
end
end
return result
end
protected
def _accept
while( true )
if( @@server_channels[self].empty? )
Rex::ThreadSafe.sleep( 0.2 )
next
end
result = accept_nonblock
break if result != nil
end
return result
end
end
end; end; end; end; end; end; end

View File

@ -53,6 +53,7 @@ TLV_TYPE_CHANNEL_TYPE = TLV_META_TYPE_STRING | 51
TLV_TYPE_CHANNEL_DATA = TLV_META_TYPE_RAW | 52
TLV_TYPE_CHANNEL_DATA_GROUP = TLV_META_TYPE_GROUP | 53
TLV_TYPE_CHANNEL_CLASS = TLV_META_TYPE_UINT | 54
TLV_TYPE_CHANNEL_PARENTID = TLV_META_TYPE_UINT | 55
TLV_TYPE_SEEK_WHENCE = TLV_META_TYPE_UINT | 70
TLV_TYPE_SEEK_OFFSET = TLV_META_TYPE_UINT | 71

View File

@ -1,4 +1,5 @@
require 'socket'
require 'thread'
require 'resolv'
require 'rex/exceptions'
@ -390,23 +391,41 @@ module Socket
rescue ::Exception
return '127.0.0.1'
end
end
end
# sf: This create a socket pair using native ruby sockets and will work
# on Windows where ::Socket.pair is not implemented.
# Note: OpenSSL requires native ruby sockets for its io.
def self.socket_pair
begin
pair = ::Socket.pair(::Socket::AF_UNIX, ::Socket::SOCK_STREAM, 0)
# Windows does not support Socket.pair, so we emulate it
rescue ::NotImplementedError
srv = TCPServer.new('localhost', 0)
rsock = TCPSocket.new(srv.addr[3], srv.addr[1])
lsock = srv.accept
srv.close
[lsock, rsock]
end
lsock = nil
rsock = nil
laddr = '127.0.0.1'
lport = 0
threads = []
mutex = ::Mutex.new
threads << ::Thread.new {
server = nil
mutex.synchronize {
threads << ::Thread.new {
mutex.synchronize {
rsock = ::TCPSocket.new( laddr, lport )
}
}
server = ::Socket.new( ::Socket::AF_INET, ::Socket::SOCK_STREAM, 0 )
server.bind( ::Socket.sockaddr_in( 0, laddr ) )
lport, caddr = ::Socket.unpack_sockaddr_in( server.getsockname )
server.listen( 1 )
}
lsock, saddr = server.accept
server.close
}
threads.each { |t| t.join }
return [lsock, rsock]
end
##
#
# Class initialization

View File

@ -19,6 +19,7 @@ module Rex::Socket::Ip
# Creates the client using the supplied hash.
#
def self.create(hash = {})
hash['Proto'] = 'ip'
self.create_param(Rex::Socket::Parameters.from_hash(hash))
end
@ -28,7 +29,6 @@ module Rex::Socket::Ip
#
def self.create_param(param)
param.proto = 'ip'
Rex::Socket.create_param(param)
end

View File

@ -158,11 +158,17 @@ class Rex::Socket::Parameters
# The context that was passed in, if any.
self.context = hash['Context'] || {}
# If no comm was supplied, try to use the comm that is best fit to
# handle the provided host based on the current routing table.
if (self.comm == nil and self.peerhost)
self.comm = Rex::Socket::SwitchBoard.best_comm(self.peerhost)
if( self.server )
if (self.comm == nil and self.localhost)
self.comm = Rex::Socket::SwitchBoard.best_comm(self.localhost)
end
else
if (self.comm == nil and self.peerhost)
self.comm = Rex::Socket::SwitchBoard.best_comm(self.peerhost)
end
end
# If we still haven't found a comm, we default to the local comm.

View File

@ -29,8 +29,9 @@ begin
#
# Creates an SSL TCP instance.
#
def self.create(hash)
def self.create(hash = {})
raise RuntimeError, "No OpenSSL support" if not @@loaded_openssl
hash['SSL'] = true
self.create_param(Rex::Socket::Parameters.from_hash(hash))
end

View File

@ -27,7 +27,10 @@ module Rex::Socket::SslTcpServer
#
##
def self.create(hash)
def self.create(hash = {})
hash['Proto'] = 'tcp'
hash['Server'] = true
hash['SSL'] = true
self.create_param(Rex::Socket::Parameters.from_hash(hash))
end
@ -39,7 +42,6 @@ module Rex::Socket::SslTcpServer
param.proto = 'tcp'
param.server = true
param.ssl = true
Rex::Socket.create_param(param)
end

View File

@ -20,7 +20,8 @@ module Rex::Socket::Tcp
#
# Creates the client using the supplied hash.
#
def self.create(hash)
def self.create(hash = {})
hash['Proto'] = 'tcp'
self.create_param(Rex::Socket::Parameters.from_hash(hash))
end
@ -30,7 +31,6 @@ module Rex::Socket::Tcp
#
def self.create_param(param)
param.proto = 'tcp'
Rex::Socket.create_param(param)
end

View File

@ -23,7 +23,9 @@ module Rex::Socket::TcpServer
#
# Creates the server using the supplied hash.
#
def self.create(hash)
def self.create(hash = {})
hash['Proto'] = 'tcp'
hash['Server'] = true
self.create_param(Rex::Socket::Parameters.from_hash(hash))
end
@ -34,7 +36,6 @@ module Rex::Socket::TcpServer
def self.create_param(param)
param.proto = 'tcp'
param.server = true
Rex::Socket.create_param(param)
end

View File

@ -19,6 +19,7 @@ module Rex::Socket::Udp
# Creates the client using the supplied hash.
#
def self.create(hash = {})
hash['Proto'] = 'udp'
self.create_param(Rex::Socket::Parameters.from_hash(hash))
end
@ -28,7 +29,6 @@ module Rex::Socket::Udp
#
def self.create_param(param)
param.proto = 'udp'
Rex::Socket.create_param(param)
end