Skip to content

Commit 50fc3b1

Browse files
author
Brent Cook
committed
Land rapid7#7086, Add 'continue' and 'tries' wget-like options to meterpreter 'download'
2 parents 6e221ca + efdf7c4 commit 50fc3b1

File tree

4 files changed

+146
-23
lines changed

4 files changed

+146
-23
lines changed

lib/rex/post/meterpreter/extensions/stdapi/fs/dir.rb

Lines changed: 46 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -195,17 +195,53 @@ def Dir.unlink(path)
195195
# Downloads the contents of a remote directory a
196196
# local directory, optionally in a recursive fashion.
197197
#
198-
def Dir.download(dst, src, recursive = false, force = true, glob = nil, timestamp = nil, &stat)
198+
def Dir.download(dst, src, opts, force = true, glob = nil, &stat)
199+
recursive = false
200+
continue = false
201+
tries = false
202+
tries_no = 0
203+
tries_cnt = 0
204+
if opts
205+
timestamp = opts["timestamp"]
206+
recursive = true if opts["recursive"]
207+
continue = true if opts["continue"]
208+
tries = true if opts["tries"]
209+
tries_no = opts["tries_no"]
210+
end
211+
begin
212+
dir_files = self.entries(src, glob)
213+
rescue Rex::TimeoutError
214+
if (tries && (tries_no == 0 || tries_cnt < tries_no))
215+
tries_cnt += 1
216+
stat.call('error listing - retry #', tries_cnt, src) if (stat)
217+
retry
218+
else
219+
stat.call('error listing directory - giving up', src, dst) if (stat)
220+
raise
221+
end
222+
end
199223

200-
self.entries(src, glob).each { |src_sub|
224+
dir_files.each { |src_sub|
201225
dst_item = dst + ::File::SEPARATOR + client.unicode_filter_encode(src_sub)
202226
src_item = src + client.fs.file.separator + client.unicode_filter_encode(src_sub)
203227

204228
if (src_sub == '.' or src_sub == '..')
205229
next
206230
end
207231

208-
src_stat = client.fs.filestat.new(src_item)
232+
tries_cnt = 0
233+
begin
234+
src_stat = client.fs.filestat.new(src_item)
235+
rescue Rex::TimeoutError
236+
if (tries && (tries_no == 0 || tries_cnt < tries_no))
237+
tries_cnt += 1
238+
stat.call('error opening file - retry #', tries_cnt, src_item) if (stat)
239+
retry
240+
else
241+
stat.call('error opening file - giving up', tries_cnt, src_item) if (stat)
242+
raise
243+
end
244+
end
209245

210246
if (src_stat.file?)
211247
if timestamp
@@ -215,7 +251,11 @@ def Dir.download(dst, src, recursive = false, force = true, glob = nil, timestam
215251
stat.call('downloading', src_item, dst_item) if (stat)
216252

217253
begin
218-
result = client.fs.file.download_file(dst_item, src_item)
254+
if (continue || tries) # allow to file.download to log messages
255+
result = client.fs.file.download_file(dst_item, src_item, opts, &stat)
256+
else
257+
result = client.fs.file.download_file(dst_item, src_item, opts)
258+
end
219259
stat.call(result, src_item, dst_item) if (stat)
220260
rescue ::Rex::Post::Meterpreter::RequestError => e
221261
if force
@@ -236,10 +276,10 @@ def Dir.download(dst, src, recursive = false, force = true, glob = nil, timestam
236276
end
237277

238278
stat.call('mirroring', src_item, dst_item) if (stat)
239-
download(dst_item, src_item, recursive, force, glob, timestamp, &stat)
279+
download(dst_item, src_item, opts, force, glob, &stat)
240280
stat.call('mirrored', src_item, dst_item) if (stat)
241281
end
242-
}
282+
} # entries
243283
end
244284

245285
#

lib/rex/post/meterpreter/extensions/stdapi/fs/file.rb

Lines changed: 64 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -280,7 +280,8 @@ def File.is_glob?(name)
280280
# If a block is given, it will be called before each file is downloaded and
281281
# again when each download is complete.
282282
#
283-
def File.download(dest, src_files, timestamp = nil, &stat)
283+
def File.download(dest, src_files, opts = nil, &stat)
284+
timestamp = opts["timestamp"] if opts
284285
[*src_files].each { |src|
285286
if (::File.basename(dest) != File.basename(src))
286287
# The destination when downloading is a local file so use this
@@ -294,15 +295,23 @@ def File.download(dest, src_files, timestamp = nil, &stat)
294295
end
295296

296297
stat.call('downloading', src, dest) if (stat)
297-
result = download_file(dest, src)
298+
result = download_file(dest, src, opts, &stat)
298299
stat.call(result, src, dest) if (stat)
299300
}
300301
end
301302

302303
#
303304
# Download a single file.
304305
#
305-
def File.download_file(dest_file, src_file)
306+
def File.download_file(dest_file, src_file, opts = nil, &stat)
307+
continue=false
308+
tries=false
309+
tries_no=0
310+
if opts
311+
continue = true if opts["continue"]
312+
tries = true if opts["tries"]
313+
tries_no = opts["tries_no"]
314+
end
306315
src_fd = client.fs.file.new(src_file, "rb")
307316

308317
# Check for changes
@@ -318,12 +327,61 @@ def File.download_file(dest_file, src_file)
318327
dir = ::File.dirname(dest_file)
319328
::FileUtils.mkdir_p(dir) if dir and not ::File.directory?(dir)
320329

321-
dst_fd = ::File.new(dest_file, "wb")
330+
if continue
331+
# continue downloading the file - skip downloaded part in the source
332+
dst_fd = ::File.new(dest_file, "ab")
333+
begin
334+
dst_fd.seek(0, ::IO::SEEK_END)
335+
in_pos = dst_fd.pos
336+
src_fd.seek(in_pos)
337+
stat.call('continuing from ', in_pos, src_file) if (stat)
338+
rescue
339+
# if we can't seek, download again
340+
stat.call('error continuing - downloading from scratch', src_file, dest_file) if (stat)
341+
dst_fd.close
342+
dst_fd = ::File.new(dest_file, "wb")
343+
end
344+
else
345+
dst_fd = ::File.new(dest_file, "wb")
346+
end
322347

323348
# Keep transferring until EOF is reached...
324349
begin
325-
while ((data = src_fd.read) != nil)
326-
dst_fd.write(data)
350+
if tries
351+
# resume when timeouts encountered
352+
seek_back = false
353+
tries_cnt = 0
354+
begin # while
355+
begin # exception
356+
if seek_back
357+
in_pos = dst_fd.pos
358+
src_fd.seek(in_pos)
359+
seek_back = false
360+
stat.call('resuming at ', in_pos, src_file) if (stat)
361+
else
362+
# succesfully read and wrote - reset the counter
363+
tries_cnt = 0
364+
end
365+
data = src_fd.read
366+
rescue Rex::TimeoutError
367+
# timeout encountered - either seek back and retry or quit
368+
if (tries && (tries_no == 0 || tries_cnt < tries_no))
369+
tries_cnt += 1
370+
seek_back = true
371+
stat.call('error downloading - retry #', tries_cnt, src_file) if (stat)
372+
retry
373+
else
374+
stat.call('error downloading - giving up', src_file, dest_file) if (stat)
375+
raise
376+
end
377+
end
378+
dst_fd.write(data) if (data != nil)
379+
end while (data != nil)
380+
else
381+
# do the simple copying quiting on the first error
382+
while ((data = src_fd.read) != nil)
383+
dst_fd.write(data)
384+
end
327385
end
328386
rescue EOFError
329387
ensure

lib/rex/post/meterpreter/ui/console/command_dispatcher/extapi/clipboard.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -376,7 +376,8 @@ def download_file( dest_folder, source )
376376
dest = File.join( dest_folder, base )
377377

378378
if stat.directory?
379-
client.fs.dir.download( dest, source, true, true ) { |step, src, dst|
379+
client.fs.dir.download( dest, source, {"recursive" => true}, true ) { |step, src, dst|
380+
# client.fs.dir.download( dest, source, true, true ) { |step, src, dst|
380381
print_line( "#{step.ljust(11)} : #{src} -> #{dst}" )
381382
client.framework.events.on_session_download( client, src, dest ) if msf_loaded?
382383
}

lib/rex/post/meterpreter/ui/console/command_dispatcher/stdapi/fs.rb

Lines changed: 34 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ class Console::CommandDispatcher::Stdapi::Fs
2424
#
2525
@@download_opts = Rex::Parser::Arguments.new(
2626
"-h" => [ false, "Help banner." ],
27+
"-c" => [ false, "Resume getting a partially-downloaded file." ],
28+
"-l" => [ true, "Set the limit of retries (0 unlimits)." ],
2729
"-r" => [ false, "Download recursively." ],
2830
"-t" => [ false, "Timestamp downloaded files." ])
2931
#
@@ -333,17 +335,29 @@ def cmd_download(*args)
333335
end
334336

335337
recursive = false
336-
timestamp = false
337338
src_items = []
338339
last = nil
339340
dest = nil
341+
continue = false
342+
tries = false
343+
tries_no = 0
344+
opts = {}
340345

341346
@@download_opts.parse(args) { |opt, idx, val|
342347
case opt
343348
when "-r"
344349
recursive = true
350+
opts['recursive'] = true
351+
when "-c"
352+
continue = true
353+
opts['continue'] = true
354+
when "-l"
355+
tries = true
356+
tries_no = val.to_i
357+
opts['tries'] = true
358+
opts['tries_no'] = tries_no
345359
when "-t"
346-
timestamp = true
360+
opts['timestamp'] = '_' + Time.now.iso8601
347361
when nil
348362
src_items << last if (last)
349363
last = val
@@ -371,10 +385,6 @@ def cmd_download(*args)
371385
dest = ::File.dirname(dest)
372386
end
373387

374-
if timestamp
375-
ts = '_' + Time.now.iso8601
376-
end
377-
378388
# Go through each source item and download them
379389
src_items.each { |src|
380390
glob = nil
@@ -397,7 +407,8 @@ def cmd_download(*args)
397407
src_path = file['path'] + client.fs.file.separator + file['name']
398408
dest_path = src_path.tr(src_separator, ::File::SEPARATOR)
399409

400-
client.fs.file.download(dest_path, src_path, ts) do |step, src, dst|
410+
client.fs.file.download(dest_path, src_path, opts) do |step, src, dst|
411+
puts step
401412
print_status("#{step.ljust(11)}: #{src} -> #{dst}")
402413
client.framework.events.on_session_download(client, src, dest) if msf_loaded?
403414
end
@@ -409,14 +420,27 @@ def cmd_download(*args)
409420

410421
else
411422
# Perform direct matching
412-
stat = client.fs.file.stat(src)
423+
tries_cnt = 0
424+
begin
425+
stat = client.fs.file.stat(src)
426+
rescue Rex::TimeoutError
427+
if (tries && (tries_no == 0 || tries_cnt < tries_no))
428+
tries_cnt += 1
429+
print_error("Error opening: #{src} - retry (#{tries_cnt})")
430+
retry
431+
else
432+
print_error("Error opening: #{src} - giving up")
433+
raise
434+
end
435+
end
436+
413437
if (stat.directory?)
414-
client.fs.dir.download(dest, src, recursive, true, glob, ts) do |step, src, dst|
438+
client.fs.dir.download(dest, src, opts, true, glob) do |step, src, dst|
415439
print_status("#{step.ljust(11)}: #{src} -> #{dst}")
416440
client.framework.events.on_session_download(client, src, dest) if msf_loaded?
417441
end
418442
elsif (stat.file?)
419-
client.fs.file.download(dest, src, ts) do |step, src, dst|
443+
client.fs.file.download(dest, src, opts) do |step, src, dst|
420444
print_status("#{step.ljust(11)}: #{src} -> #{dst}")
421445
client.framework.events.on_session_download(client, src, dest) if msf_loaded?
422446
end

0 commit comments

Comments
 (0)