Files
metasploit-gs/lib/msf/base/sessions/ssh_command_shell_bind.rb
T
Spencer McIntyre 46a877678d Refactor some channel related Meterpreter code
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.
2021-06-30 09:25:25 -04:00

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