Land #10625, repeat command to repeat commands

4.x
William Vu 2018-09-20 15:24:03 -05:00 committed by Metasploit
parent 8fbbff30db
commit 058eabbd24
No known key found for this signature in database
GPG Key ID: CDFB5FA52007B954
3 changed files with 95 additions and 8 deletions

View File

@ -106,6 +106,7 @@ class Core
"history" => "Show command history", "history" => "Show command history",
"load" => "Load a framework plugin", "load" => "Load a framework plugin",
"quit" => "Exit the console", "quit" => "Exit the console",
"repeat" => "Repeat a list of commands",
"route" => "Route traffic through a session", "route" => "Route traffic through a session",
"save" => "Saves the active datastores", "save" => "Saves the active datastores",
"sessions" => "Dump session listings and display information about sessions", "sessions" => "Dump session listings and display information about sessions",
@ -2013,8 +2014,8 @@ class Core
end end
# OptionParser#order allows us to take the rest of the line for the command # OptionParser#order allows us to take the rest of the line for the command
pattern, *cmd = opts.order(args) pattern, *rest = opts.order(args)
cmd = cmd.join(" ") cmd = Shellwords.shelljoin(rest)
return print(opts.help) if !pattern || cmd.empty? return print(opts.help) if !pattern || cmd.empty?
rx = Regexp.new(pattern, match_mods[:insensitive]) rx = Regexp.new(pattern, match_mods[:insensitive])
@ -2038,7 +2039,7 @@ class Core
# Bail if the command failed # Bail if the command failed
if cmd_output =~ /Unknown command:/ if cmd_output =~ /Unknown command:/
print_error("Unknown command: #{args[0]}.") print_error("Unknown command: '#{rest[0]}'.")
return false return false
end end
# put lines into an array so we can access them more easily and split('\n') doesn't work on the output obj. # put lines into an array so we can access them more easily and split('\n') doesn't work on the output obj.
@ -2087,6 +2088,81 @@ class Core
tabs tabs
end end
def cmd_repeat_help
cmd_repeat '-h'
end
#
# Repeats (loops) a given list of commands
#
def cmd_repeat(*args)
looper = method :loop
opts = OptionParser.new do |opts|
opts.banner = 'Usage: repeat [OPTIONS] COMMAND...'
opts.separator 'Repeat (loop) a ;-separated list of msfconsole commands indefinitely, or for a'
opts.separator 'number of iterations or a certain amount of time.'
opts.separator ''
opts.on '-t SECONDS', '--time SECONDS', 'Number of seconds to repeat COMMAND...', Integer do |n|
looper = ->(&block) do
# While CLOCK_MONOTONIC is a Linux thing, Ruby emulates it for *BSD, MacOS, and Windows
ending_time = Process.clock_gettime(Process::CLOCK_MONOTONIC, :second) + n
while Process.clock_gettime(Process::CLOCK_MONOTONIC, :second) < ending_time
block.call
end
end
end
opts.on '-n TIMES', '--number TIMES', 'Number of times to repeat COMMAND..', Integer do |n|
looper = n.method(:times)
end
opts.on '-h', '--help', 'Help banner.' do
return print(opts.help)
end
# Internal use
opts.on '--generate-completions str', 'Return possible tab completions for given string.' do |str|
return opts.candidate str
end
end
cmds = opts.order(args).slice_when do |prev, _|
# If the last character of a shellword was a ';' it's probably to
# delineate commands and we can remove it
prev[-1] == ';' && prev[-1] = ''
end.map do |c|
Shellwords.shelljoin(c)
end
# Print help if we have no commands, or all the commands are empty
return cmd_repeat '-h' if cmds.all? &:empty?
begin
looper.call do
cmds.each do |c|
driver.run_single c, propagate_errors: true
end
end
rescue ::Exception
# Stop looping on exception
nil
end
end
# Almost the exact same as grep
def cmd_repeat_tabs(str, words)
str = '-' if str.empty? # default to use repeat's options
tabs = cmd_repeat '--generate-completions', str
# if not an opt, use normal tab comp.
# @todo uncomment out next line when tab_completion normalization is complete RM7649 or
# replace with new code that permits "nested" tab completion
# tabs = driver.get_all_commands if (str and str =~ /\w/)
tabs
end
# #
# Provide tab completion for option values # Provide tab completion for option values
# #

View File

@ -432,7 +432,7 @@ module DispatcherShell
# #
# Run a single command line. # Run a single command line.
# #
def run_single(line) def run_single(line, propagate_errors: false)
arguments = parse_line(line) arguments = parse_line(line)
method = arguments.shift method = arguments.shift
found = false found = false
@ -453,17 +453,27 @@ module DispatcherShell
run_command(dispatcher, method, arguments) run_command(dispatcher, method, arguments)
found = true found = true
end end
rescue ::Interrupt
print_error("#{method}: Interrupted")
raise if propagate_errors
rescue OptionParser::ParseError => e
print_error("#{method}: #{e.message}")
raise if propagate_errors
rescue rescue
error = $! error = $!
print_error( print_error(
"Error while running command #{method}: #{$!}" + "Error while running command #{method}: #{$!}" +
"\n\nCall stack:\n#{$@.join("\n")}") "\n\nCall stack:\n#{$@.join("\n")}")
rescue ::Exception
raise if propagate_errors
rescue ::Exception => e
error = $! error = $!
print_error( print_error(
"Error while running command #{method}: #{$!}") "Error while running command #{method}: #{$!}")
raise if propagate_errors
end end
# If the dispatcher stack changed as a result of this command, # If the dispatcher stack changed as a result of this command,
@ -490,8 +500,6 @@ module DispatcherShell
else else
dispatcher.send('cmd_' + method, *arguments) dispatcher.send('cmd_' + method, *arguments)
end end
rescue OptionParser::ParseError => e
print_error("#{method}: #{e.message}")
ensure ensure
self.busy = false self.busy = false
end end

View File

@ -109,7 +109,10 @@ class MetasploitModule < Msf::Auxiliary
STARTTLS may also be vulnerable. STARTTLS may also be vulnerable.
The module supports several actions, allowing for scanning, dumping of The module supports several actions, allowing for scanning, dumping of
memory contents, and private key recovery. memory contents, and private key recovery. The `repeat` command can be
used to make running the `DUMP` many times more convenient. As in:
repeat -t 60 run; sleep 2
To run every two seconds for one minute.
}, },
'Author' => [ 'Author' => [
'Neel Mehta', # Vulnerability discovery 'Neel Mehta', # Vulnerability discovery