Skip to content

Commit

Permalink
Land #10625, repeat command to repeat commands
Browse files Browse the repository at this point in the history
  • Loading branch information
wvu authored and msjenkins-r7 committed Sep 20, 2018
1 parent 8fbbff3 commit 058eabb
Show file tree
Hide file tree
Showing 3 changed files with 95 additions and 8 deletions.
82 changes: 79 additions & 3 deletions lib/msf/ui/console/command_dispatcher/core.rb
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ def commands
"history" => "Show command history",
"load" => "Load a framework plugin",
"quit" => "Exit the console",
"repeat" => "Repeat a list of commands",
"route" => "Route traffic through a session",
"save" => "Saves the active datastores",
"sessions" => "Dump session listings and display information about sessions",
Expand Down Expand Up @@ -2013,8 +2014,8 @@ def cmd_grep(*args)
end

# OptionParser#order allows us to take the rest of the line for the command
pattern, *cmd = opts.order(args)
cmd = cmd.join(" ")
pattern, *rest = opts.order(args)
cmd = Shellwords.shelljoin(rest)
return print(opts.help) if !pattern || cmd.empty?

rx = Regexp.new(pattern, match_mods[:insensitive])
Expand All @@ -2038,7 +2039,7 @@ def cmd_grep(*args)

# Bail if the command failed
if cmd_output =~ /Unknown command:/
print_error("Unknown command: #{args[0]}.")
print_error("Unknown command: '#{rest[0]}'.")
return false
end
# put lines into an array so we can access them more easily and split('\n') doesn't work on the output obj.
Expand Down Expand Up @@ -2087,6 +2088,81 @@ def cmd_grep_tabs(str, words)
tabs
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
#
Expand Down
16 changes: 12 additions & 4 deletions lib/rex/ui/text/dispatcher_shell.rb
Original file line number Diff line number Diff line change
Expand Up @@ -432,7 +432,7 @@ def tab_complete_helper(dispatcher, str, words)
#
# Run a single command line.
#
def run_single(line)
def run_single(line, propagate_errors: false)
arguments = parse_line(line)
method = arguments.shift
found = false
Expand All @@ -453,17 +453,27 @@ def run_single(line)
run_command(dispatcher, method, arguments)
found = true
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
error = $!

print_error(
"Error while running command #{method}: #{$!}" +
"\n\nCall stack:\n#{$@.join("\n")}")
rescue ::Exception

raise if propagate_errors
rescue ::Exception => e
error = $!

print_error(
"Error while running command #{method}: #{$!}")

raise if propagate_errors
end

# If the dispatcher stack changed as a result of this command,
Expand All @@ -490,8 +500,6 @@ def run_command(dispatcher, method, arguments)
else
dispatcher.send('cmd_' + method, *arguments)
end
rescue OptionParser::ParseError => e
print_error("#{method}: #{e.message}")
ensure
self.busy = false
end
Expand Down
5 changes: 4 additions & 1 deletion modules/auxiliary/scanner/ssl/openssl_heartbleed.rb
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,10 @@ def initialize
STARTTLS may also be vulnerable.
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' => [
'Neel Mehta', # Vulnerability discovery
Expand Down

0 comments on commit 058eabb

Please sign in to comment.