46a877678d
This makes some of the channel related Meterpreter code accessible to other locations in the framework which enables other sessions to also support pivoting capabilities.
205 lines
5.5 KiB
Ruby
205 lines
5.5 KiB
Ruby
# -*- coding: binary -*-
|
|
|
|
# todo: refactor this so it's no longer under Meterpreter so it can be used elsewhere
|
|
require 'rex/post/channel'
|
|
require 'rex/post/meterpreter/channels/socket_abstraction'
|
|
|
|
module Msf::Sessions
|
|
|
|
class SshCommandShellBind < Msf::Sessions::CommandShell
|
|
|
|
include Msf::Session::Comm
|
|
include Rex::Post::Channel::Container
|
|
|
|
class TcpClientChannel
|
|
include Rex::IO::StreamAbstraction
|
|
|
|
#
|
|
# Create a new TcpClientChannel instance.
|
|
#
|
|
# @param client [SshCommandShellBind] The command shell session that this
|
|
# channel instance belongs to.
|
|
# @param cid [Integer] The channel ID.
|
|
# @param ssh_channel [Net::SSH::Connection::Channel] The connected SSH
|
|
# channel.
|
|
# @param params [Rex::Socket::Parameters] The parameters that were used to
|
|
# open the channel.
|
|
def initialize(client, cid, ssh_channel, params)
|
|
initialize_abstraction
|
|
|
|
@client = client
|
|
@cid = cid
|
|
@ssh_channel = ssh_channel
|
|
@params = params
|
|
|
|
ssh_channel.on_close do |ch|
|
|
dlog("ssh_channel#on_close closing sock")
|
|
rsock.close
|
|
end
|
|
|
|
ssh_channel.on_data do |ch, data|
|
|
#dlog("ssh_channel#on_data received #{data.length} bytes")
|
|
rsock.syswrite(data)
|
|
end
|
|
|
|
ssh_channel.on_eof do |ch|
|
|
dlog("ssh_channel#on_eof closing sock")
|
|
rsock.close
|
|
end
|
|
|
|
lsock.extend(Rex::Post::Channel::SocketAbstraction::SocketInterface)
|
|
lsock.channel = self
|
|
|
|
rsock.extend(Rex::Post::Channel::SocketAbstraction::SocketInterface)
|
|
rsock.channel = self
|
|
|
|
client.add_channel(self)
|
|
end
|
|
|
|
def closed?
|
|
@cid.nil?
|
|
end
|
|
|
|
def close
|
|
return if @cid.nil?
|
|
|
|
cleanup_abstraction
|
|
@ssh_channel.close
|
|
@client.remove_channel(@cid)
|
|
@cid = nil
|
|
end
|
|
|
|
#
|
|
# Read *length* bytes from the channel. If the operation times out, the data
|
|
# that was read will be returned or nil if no data was read.
|
|
#
|
|
def read(length = nil)
|
|
if @cid.nil?
|
|
raise IOError, 'Channel has been closed.', caller
|
|
end
|
|
|
|
buf = ''
|
|
length = 65536 if length.nil?
|
|
|
|
begin
|
|
while buf.length < length
|
|
buf << lsock.recv(length - buf.length)
|
|
end
|
|
rescue StandardError
|
|
buf = nil if buf.empty?
|
|
end
|
|
|
|
buf
|
|
end
|
|
|
|
#
|
|
# Write *buf* to the channel, optionally truncating it to *length* bytes.
|
|
#
|
|
# @param [String] buf The data to write to the channel.
|
|
# @param [Integer] length An optional length to truncate *data* to before
|
|
# sending it.
|
|
def write(buf, length = nil)
|
|
if @cid.nil?
|
|
raise IOError, 'Channel has been closed.', caller
|
|
end
|
|
|
|
if !length.nil? && buf.length >= length
|
|
buf = buf[0..length]
|
|
end
|
|
|
|
@ssh_channel.send_data(buf)
|
|
buf.length
|
|
end
|
|
|
|
attr_reader :cid
|
|
attr_reader :client
|
|
attr_reader :params
|
|
end
|
|
|
|
def initialize(ssh_connection, rstream, opts = {})
|
|
@ssh_connection = ssh_connection
|
|
@sock = ssh_connection.transport.socket
|
|
initialize_channels
|
|
@channel_ticker = 0
|
|
super(rstream, opts)
|
|
end
|
|
|
|
#
|
|
# Create a network socket using this session. At this time, only TCP client
|
|
# connections can be made (like SSH port forwarding) while TCP server sockets
|
|
# can not be opened (SSH reverse port forwarding). The SSH specification does
|
|
# not define a UDP channel, so that is not supported either.
|
|
#
|
|
# @param params [Rex::Socket::Parameters] The parameters that should be used
|
|
# to open the socket.
|
|
#
|
|
# @raise [Rex::ConnectionError] If the connection fails, timesout or is not
|
|
# supported, a ConnectionError will be raised.
|
|
# @return [TcpClientChannel] The connected TCP client channel.
|
|
def create(params)
|
|
# Notify handlers before we create the socket
|
|
notify_before_socket_create(self, params)
|
|
|
|
mutex = Mutex.new
|
|
condition = ConditionVariable.new
|
|
ssh_channel = msf_channel = nil
|
|
|
|
if params.proto == 'tcp' && !params.server
|
|
ssh_channel = @ssh_connection.open_channel('direct-tcpip', :string, params.peerhost, :long, params.peerport, :string, params.localhost, :long, params.localport) do |new_channel|
|
|
msf_channel = TcpClientChannel.new(self, @channel_ticker += 1, new_channel, params)
|
|
mutex.synchronize {
|
|
condition.signal
|
|
}
|
|
end
|
|
end
|
|
|
|
raise ::Rex::ConnectionError.new if ssh_channel.nil?
|
|
|
|
ssh_channel.on_open_failed do |ch, code, desc|
|
|
wlog("failed to open SSH channel (code=#{code.inspect}, description=#{desc.inspect})")
|
|
mutex.synchronize {
|
|
condition.signal
|
|
}
|
|
end
|
|
|
|
mutex.synchronize {
|
|
condition.wait(mutex, params.timeout)
|
|
}
|
|
|
|
raise ::Rex::ConnectionError.new if msf_channel.nil?
|
|
|
|
sock = msf_channel.lsock
|
|
|
|
# Notify now that we've created the socket
|
|
notify_socket_created(self, sock, params)
|
|
sock
|
|
end
|
|
|
|
def cleanup
|
|
channels.values.each do |channel|
|
|
channel.close
|
|
end
|
|
|
|
super
|
|
end
|
|
|
|
attr_reader :sock
|
|
attr_reader :ssh_connection
|
|
|
|
#
|
|
# Create a sessions instance from an SshConnection. This will handle creating
|
|
# a new command stream.
|
|
#
|
|
# @param ssh_connection [Net::SSH::Connection] The SSH connection to create a
|
|
# session instance for.
|
|
# @param opts [Hash] Optional parameters to pass to the session object.
|
|
#
|
|
# @return [SshCommandShellBind] A new session instance.
|
|
def self.from_ssh_socket(ssh_connection, opts = {})
|
|
command_stream = Net::SSH::CommandStream.new(ssh_connection)
|
|
self.new(ssh_connection, command_stream.lsock, opts)
|
|
end
|
|
end
|
|
|
|
end
|