lib/msf/ui/console/command_dispatcher/core.rb
# -*- coding: binary -*-
#
# Rex
#
#
# Project
#
require 'msf/core/opt_condition'
require 'optparse'
module Msf
module Ui
module Console
module CommandDispatcher
###
#
# Command dispatcher for core framework commands, such as module loading,
# session interaction, and other general things.
#
###
class Core
include Msf::Ui::Console::CommandDispatcher
include Msf::Ui::Console::CommandDispatcher::Common
include Msf::Ui::Console::ModuleOptionTabCompletion
# Session command options
@@sessions_opts = Rex::Parser::Arguments.new(
["-c", "--command"] => [ true, "Run a command on the session given with -i, or all", "<command>" ],
["-C", "--meterpreter-command"] => [ true, "Run a Meterpreter Command on the session given with -i, or all", "<command>" ],
["-h", "--help"] => [ false, "Help banner" ],
["-i", "--interact"] => [ true, "Interact with the supplied session ID", "<id>" ],
["-l", "--list"] => [ false, "List all active sessions" ],
["-v", "--list-verbose"] => [ false, "List all active sessions in verbose mode" ],
["-d", "--list-inactive"] => [ false, "List all inactive sessions" ],
["-q", "--quiet"] => [ false, "Quiet mode" ],
["-k", "--kill"] => [ true, "Terminate sessions by session ID and/or range", "<id>" ],
["-K", "--kill-all"] => [ false, "Terminate all sessions" ],
["-s", "--script"] => [ true, "Run a script or module on the session given with -i, or all", "<script>" ],
["-u", "--upgrade"] => [ true, "Upgrade a shell to a meterpreter session on many platforms", "<id>" ],
["-t", "--timeout"] => [ true, "Set a response timeout (default: 15)", "<seconds>" ],
["-S", "--search"] => [ true, "Row search filter. (ex: sessions --search 'last_checkin:less_than:10s session_id:5 session_type:meterpreter')", "<filter>"],
["-x", "--list-extended"] => [ false, "Show extended information in the session table" ],
["-n", "--name"] => [ true, "Name or rename a session by ID", "<id> <name>" ])
@@threads_opts = Rex::Parser::Arguments.new(
["-h", "--help"] => [ false, "Help banner." ],
["-k", "--kill"] => [ true, "Terminate the specified thread ID.", "<id>" ],
["-K", "--kill-all"] => [ false, "Terminate all non-critical threads." ],
["-i", "--info"] => [ true, "Lists detailed information about a thread.", "<id>" ],
["-l", "--list"] => [ false, "List all background threads." ],
["-v", "--verbose"] => [ false, "Print more detailed info. Use with -i and -l" ])
@@tip_opts = Rex::Parser::Arguments.new(
["-h", "--help"] => [ false, "Help banner." ])
@@debug_opts = Rex::Parser::Arguments.new(
["-h", "--help"] => [ false, "Help banner." ],
["-d", "--datastore"] => [ false, "Display the datastore information." ],
["-c", "--commands"] => [ false, "Display command history." ],
["-e", "--errors"] => [ false, "Display the most recent Error and Stack Trace." ],
["-l", "--logs"] => [ false, "Display the most recent logs." ],
["-v", "--version"] => [ false, "Display versions and install info." ],
["-s", "--database"] => [ false, "Display database statistics." ])
@@connect_opts = Rex::Parser::Arguments.new(
["-h", "--help"] => [ false, "Help banner." ],
["-p", "--proxies"] => [ true, "List of proxies to use.", "<proxies>" ],
["-C", "--crlf"] => [ false, "Try to use CRLF for EOL sequence." ],
["-c", "--comm"] => [ true, "Specify which Comm to use.", "<comm>" ],
["-i", "--send-contents"] => [ true, "Send the contents of a file.", "<file>" ],
["-P", "--source-port"] => [ true, "Specify source port.", "<port>" ],
["-S", "--source-address"] => [ true, "Specify source address.", "<address>" ],
["-s", "--ssl"] => [ false, "Connect with SSL." ],
["-u", "--udp"] => [ false, "Switch to a UDP socket." ],
["-w", "--timeout"] => [ true, "Specify connect timeout.", "<seconds>" ],
["-z", "--try-connection"] => [ false, "Just try to connect, then return." ])
@@history_opts = Rex::Parser::Arguments.new(
["-h", "--help"] => [ false, "Help banner." ],
["-a", "--all-commands"] => [ false, "Show all commands in history." ],
["-n"] => [ true, "Show the last n commands.", "<num>" ],
["-c", "--clear"] => [ false, "Clear command history and history file." ])
@@save_opts = Rex::Parser::Arguments.new(
["-h", "--help"] => [ false, "Help banner." ],
["-r", "--reload-default"] => [ false, "Reload default options for the active module." ],
["-l", "--load"] => [ false, "Load the saved options for the active module." ],
["-d", "--delete-all"] => [ false, "Delete saved options for all modules from the config file." ])
# set command options
@@setg_opts = Rex::Parser::Arguments.new(
["-h", "--help"] => [ false, "Help banner."],
["-c", "--clear"] => [ false, "Clear the values, explicitly setting to nil (default)"]
)
@@set_opts = @@setg_opts.merge(
["-g", "--global"] => [ false, "Operate on global datastore variables"]
)
# unset command options
@@unsetg_opts = Rex::Parser::Arguments.new(
["-h", "--help"] => [ false, "Help banner."],
)
@@unset_opts = @@unsetg_opts.merge(
["-g", "--global"] => [ false, "Operate on global datastore variables"]
)
SESSION_TYPE = 'session_type'
SESSION_ID = 'session_id'
LAST_CHECKIN = 'last_checkin'
LESS_THAN = 'less_than'
GREATER_THAN = 'greater_than'
VALID_SESSION_SEARCH_PARAMS =
[
LAST_CHECKIN,
SESSION_ID,
SESSION_TYPE
]
VALID_OPERATORS =
[
LESS_THAN,
GREATER_THAN
]
private_constant :VALID_SESSION_SEARCH_PARAMS
private_constant :VALID_OPERATORS
private_constant :SESSION_TYPE
private_constant :SESSION_ID
private_constant :LAST_CHECKIN
private_constant :GREATER_THAN
private_constant :LESS_THAN
# Returns the list of commands supported by this command dispatcher
def commands
{
"?" => "Help menu",
"banner" => "Display an awesome metasploit banner",
"cd" => "Change the current working directory",
"connect" => "Communicate with a host",
"color" => "Toggle color",
"debug" => "Display information useful for debugging",
"exit" => "Exit the console",
"features" => "Display the list of not yet released features that can be opted in to",
"get" => "Gets the value of a context-specific variable",
"getg" => "Gets the value of a global variable",
"grep" => "Grep the output of another command",
"help" => "Help menu",
"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",
"set" => "Sets a context-specific variable to a value",
"setg" => "Sets a global variable to a value",
"sleep" => "Do nothing for the specified number of seconds",
"tips" => "Show a list of useful productivity tips",
"threads" => "View and manipulate background threads",
"unload" => "Unload a framework plugin",
"unset" => "Unsets one or more context-specific variables",
"unsetg" => "Unsets one or more global variables",
"version" => "Show the framework and console library version numbers",
"spool" => "Write console output into a file as well the screen"
}
end
#
# Initializes the datastore cache
#
def initialize(driver)
super
@cache_payloads = nil
@previous_module = nil
@previous_target = nil
@history_limit = 100
end
def deprecated_commands
['tip']
end
#
# Returns the name of the command dispatcher.
#
def name
"Core"
end
def cmd_color_help
print_line "Usage: color <'true'|'false'|'auto'>"
print_line
print_line "Enable or disable color output."
print_line
end
def cmd_color(*args)
case args[0]
when "auto"
driver.output.auto_color
when "true"
driver.output.enable_color
when "false"
driver.output.disable_color
else
cmd_color_help
return
end
end
#
# Tab completion for the color command
#
# @param str [String] the string currently being typed before tab was hit
# @param words [Array<String>] the previously completed words on the command line. words is always
# at least 1 when tab completion has reached this stage since the command itself has been completed
#
def cmd_color_tabs(str, words)
return [] if words.length > 1
%w[auto true false]
end
def cmd_cd_help
print_line "Usage: cd <directory>"
print_line
print_line "Change the current working directory"
print_line
end
#
# Change the current working directory
#
def cmd_cd(*args)
if(args.length == 0)
print_error("No path specified")
return
end
begin
Dir.chdir(args.join(" ").strip)
rescue ::Exception
print_error("The specified path does not exist")
end
end
def cmd_cd_tabs(str, words)
tab_complete_directory(str, words)
end
def cmd_banner_help
print_line "Usage: banner"
print_line
print_line "Print a stunning ascii art banner along with version information and module counts"
print_line
end
#
# Display one of the fabulous banners.
#
def cmd_banner(*args)
banner = "%cya" + Banner.to_s + "%clr\n\n"
stats = framework.stats
version = "%yelmetasploit v#{Metasploit::Framework::VERSION}%clr",
exp_aux_pos = "#{stats.num_exploits} exploits - #{stats.num_auxiliary} auxiliary - #{stats.num_post} post",
pay_enc_nop = "#{stats.num_payloads} payloads - #{stats.num_encoders} encoders - #{stats.num_nops} nops",
eva = "#{stats.num_evasion} evasion",
padding = 48
banner << (" =[ %-#{padding+8}s]\n" % version)
banner << ("+ -- --=[ %-#{padding}s]\n" % exp_aux_pos)
banner << ("+ -- --=[ %-#{padding}s]\n" % pay_enc_nop)
banner << ("+ -- --=[ %-#{padding}s]\n" % eva)
banner << "\n"
banner << Rex::Text.wordwrap('Metasploit Documentation: https://docs.metasploit.com/', indent = 0, cols = 60)
# Display the banner
print_line(banner)
end
#
# Tab completion for the tips command
#
# @param str [String] the string currently being typed before tab was hit
# @param words [Array<String>] the previously completed words on the command line. words is always
# at least 1 when tab completion has reached this stage since the command itself has been completed
def cmd_tips_tabs(str, words)
if words.length == 1
return @@tip_opts.option_keys.select { |opt| opt.start_with?(str) }
end
[]
end
def cmd_tips_help
print_line "Usage: tips [options]"
print_line
print_line "Print a useful list of productivity tips on how to use Metasploit"
print @@tip_opts.usage
end
alias cmd_tip_help cmd_tips_help
#
# Display useful productivity tips to the user.
#
def cmd_tips(*args)
if args.include?("-h") || args.include?("--help")
cmd_tip_help
else
tbl = Table.new(
Table::Style::Default,
'Columns' => %w[Id Tip]
)
Tip.all.each_with_index do |tip, index|
tbl << [ index, tip ]
end
print(tbl.to_s)
end
end
alias cmd_tip cmd_tips
#
# Tab completion for the debug command
#
# @param str [String] the string currently being typed before tab was hit
# @param words [Array<String>] the previously completed words on the command line. words is always
# at least 1 when tab completion has reached this stage since the command itself has been completed
def cmd_debug_tabs(str, words)
if words.length >= 1
return @@debug_opts.option_keys.select do |opt|
opt.start_with?(str) && !words.include?(opt)
end
end
[]
end
def cmd_debug_help
print_line "Usage: debug [options]"
print_line
print_line("Print a set of information in a Markdown format to be included when opening an Issue on Github. " +
"This information helps us fix problems you encounter and should be included when you open a new issue: " +
Debug.issue_link)
print @@debug_opts.usage
end
#
# Display information useful for debugging errors.
#
def cmd_debug(*args)
if args.empty?
print_line Debug.all(framework, driver)
return
end
if args.include?("-h") || args.include?("--help")
cmd_debug_help
else
output = ""
@@debug_opts.parse(args) do |opt|
case opt
when '-d'
output << Debug.datastore(framework, driver)
when '-c'
output << Debug.history(driver)
when '-e'
output << Debug.errors
when '-l'
output << Debug.logs
when '-v'
output << Debug.versions(framework)
when '-s'
output << Debug.database_configuration(framework)
end
end
if output.empty?
print_line("Valid argument was not given.")
cmd_debug_help
else
output = Debug.preamble + output
print_line output
end
end
end
#
# Tab completion for the connect command
#
# @param str [String] the string currently being typed before tab was hit
# @param words [Array<String>] the previously completed words on the command line. words is always
# at least 1 when tab completion has reached this stage since the command itself has been completed
def cmd_connect_tabs(str, words)
if words.length == 1
return @@connect_opts.option_keys.select do |opt|
opt.start_with?(str) && !words.include?(opt)
end
end
case words[-1]
when '-c', '--comm'
# Rex::Socket::Comm
end
[]
end
def cmd_connect_help
print_line "Usage: connect [options] <host> <port>"
print_line
print_line "Communicate with a host, similar to interacting via netcat, taking advantage of"
print_line "any configured session pivoting."
print @@connect_opts.usage
end
#
# Talk to a host
#
def cmd_connect(*args)
if args.length < 2 or args.include?("-h") or args.include?("--help")
cmd_connect_help
return false
end
crlf = false
commval = nil
fileval = nil
proxies = nil
srcaddr = nil
srcport = nil
ssl = false
udp = false
cto = nil
justconn = false
aidx = 0
@@connect_opts.parse(args) do |opt, idx, val|
case opt
when "-C"
crlf = true
aidx = idx + 1
when "-c"
commval = val
aidx = idx + 2
when "-i"
fileval = val
aidx = idx + 2
when "-P"
srcport = val
aidx = idx + 2
when "-p"
proxies = val
aidx = idx + 2
when "-S"
srcaddr = val
aidx = idx + 2
when "-s"
ssl = true
aidx = idx + 1
when "-w"
cto = val.to_i
aidx = idx + 2
when "-u"
udp = true
aidx = idx + 1
when "-z"
justconn = true
aidx = idx + 1
end
end
commval = "Local" if commval =~ /local/i
if fileval
begin
raise "Not a file" if File.ftype(fileval) != "file"
infile = ::File.open(fileval)
rescue
print_error("Can't read from '#{fileval}': #{$!}")
return false
end
end
args = args[aidx .. -1]
if args.length < 2
print_error("You must specify a host and port")
return false
end
host = args[0]
port = args[1]
comm = nil
if commval
begin
if Rex::Socket::Comm.const_defined?(commval)
comm = Rex::Socket::Comm.const_get(commval)
end
rescue NameError
end
if not comm
session = framework.sessions.get(commval)
if session.kind_of?(Msf::Session::Comm)
comm = session
end
end
if not comm
print_error("Invalid comm '#{commval}' selected")
return false
end
end
begin
klass = udp ? ::Rex::Socket::Udp : ::Rex::Socket::Tcp
sock = klass.create({
'Comm' => comm,
'Proxies' => proxies,
'SSL' => ssl,
'PeerHost' => host,
'PeerPort' => port,
'LocalHost' => srcaddr,
'LocalPort' => srcport,
'Timeout' => cto,
'Context' => {
'Msf' => framework
}
})
rescue
print_error("Unable to connect: #{$!}")
return false
end
_, lhost, lport = sock.getlocalname()
print_status("Connected to #{host}:#{port} (via: #{lhost}:#{lport})")
if justconn
sock.close
infile.close if infile
return true
end
cin = infile || driver.input
cout = driver.output
begin
# Console -> Network
c2n = framework.threads.spawn("ConnectConsole2Network", false, cin, sock) do |input, output|
while true
begin
res = input.gets
break if not res
if crlf and (res =~ /^\n$/ or res =~ /[^\r]\n$/)
res.gsub!(/\n$/, "\r\n")
end
output.write res
rescue ::EOFError, ::IOError
break
end
end
end
# Network -> Console
n2c = framework.threads.spawn("ConnectNetwork2Console", false, sock, cout, c2n) do |input, output, cthr|
while true
begin
res = input.read(65535)
break if not res
output.print res
rescue ::EOFError, ::IOError
break
end
end
Thread.kill(cthr)
end
c2n.join
rescue ::Interrupt
c2n.kill
n2c.kill
end
c2n.join
n2c.join
sock.close rescue nil
infile.close if infile
true
end
#
# Instructs the driver to stop executing.
#
def cmd_exit(*args)
forced = false
forced = true if (args[0] and args[0] =~ /-y/i)
if(framework.sessions.length > 0 and not forced)
print_status("You have active sessions open, to exit anyway type \"exit -y\"")
return
elsif(driver.confirm_exit and not forced)
print("Are you sure you want to exit Metasploit? [y/N]: ")
response = gets.downcase.chomp
if(response == "y" || response == "yes")
driver.stop
else
return
end
end
driver.stop
end
alias cmd_quit cmd_exit
def cmd_features_help
print_line <<~CMD_FEATURE_HELP
Enable or disable unreleased features that Metasploit supports
Usage:
features set feature_name [true/false]
features print
Subcommands:
set - Enable or disable a given feature
print - show all available features and their current configuration
Examples:
View available features:
features print
Enable a feature:
features set new_feature true
Disable a feature:
features set new_feature false
CMD_FEATURE_HELP
end
#
# This method handles the features command which allows a user to opt into enabling
# features that are not yet released to everyone by default.
#
def cmd_features(*args)
args << 'print' if args.empty?
action, *rest = args
case action
when 'set'
feature_name, value = rest
unless framework.features.exists?(feature_name)
print_warning("Feature name '#{feature_name}' is not available. Either it has been removed, integrated by default, or does not exist in this version of Metasploit.")
print_warning("Currently supported features: #{framework.features.names.join(', ')}") if framework.features.all.any?
print_warning('There are currently no features to toggle.') if framework.features.all.empty?
return
end
unless %w[true false].include?(value)
print_warning('Please specify true or false to configure this feature.')
return
end
framework.features.set(feature_name, value == 'true')
print_line("#{feature_name} => #{value}")
# Some flags may require a full console restart
if framework.features.requires_restart?(feature_name)
print_warning("Run the #{Msf::Ui::Tip.highlight("save")} command and restart the console for this feature to take effect.")
else
# Reload the current module, as feature flags may impact the available module options etc
driver.run_single("reload") if driver.active_module
end
when 'print'
if framework.features.all.empty?
print_line 'There are no features to enable at this time. Either the features have been removed, or integrated by default.'
return
end
features_table = Table.new(
Table::Style::Default,
'Header' => 'Features table',
'Prefix' => "\n",
'Postfix' => "\n",
'Columns' => [
'#',
'Name',
'Enabled',
'Description',
]
)
framework.features.all.each.with_index do |feature, index|
features_table << [
index,
feature[:name],
feature[:enabled].to_s,
feature[:description]
]
end
print_line features_table.to_s
else
cmd_features_help
end
rescue StandardError => e
elog(e)
print_error(e.message)
end
#
# Tab completion for the features command
#
# @param _str [String] The string currently being typed before tab was hit
# @param words [Array<String>] The previously completed words on the command line. words is always
# at least 1 when tab completion has reached this stage since the command itself has been completed
def cmd_features_tabs(_str, words)
if words.length == 1
return %w[set print]
end
_command_name, action, *rest = words
ret = []
case action
when 'set'
feature_name, _value = rest
if framework.features.exists?(feature_name)
ret += %w[true false]
else
ret += framework.features.names
end
end
ret
end
def cmd_history(*args)
length = Readline::HISTORY.length
if length < @history_limit
limit = length
else
limit = @history_limit
end
@@history_opts.parse(args) do |opt, idx, val|
case opt
when '-a'
limit = length
when '-n'
return cmd_history_help unless val && val.match(/\A[-+]?\d+\z/)
if length < val.to_i
limit = length
else
limit = val.to_i
end
when '-c'
if Readline::HISTORY.respond_to?(:clear)
Readline::HISTORY.clear
elsif defined?(RbReadline)
RbReadline.clear_history
else
print_error('Could not clear history, skipping file')
return false
end
# Portable file truncation?
if File.writable?(Msf::Config.history_file)
File.write(Msf::Config.history_file, '')
end
print_good('Command history and history file cleared')
return true
when '-h'
cmd_history_help
return false
end
end
start = length - limit
pad_len = length.to_s.length
(start..length-1).each do |pos|
cmd_num = (pos + 1).to_s
print_line "#{cmd_num.ljust(pad_len)} #{Readline::HISTORY[pos]}"
end
end
def cmd_history_help
print_line "Usage: history [options]"
print_line
print_line "Shows the command history."
print_line
print_line "If -n is not set, only the last #{@history_limit} commands will be shown."
print_line 'If -c is specified, the command history and history file will be cleared.'
print_line 'Start commands with a space to avoid saving them to history.'
print @@history_opts.usage
end
def cmd_history_tabs(str, words)
return [] if words.length > 1
@@history_opts.option_keys
end
def cmd_sleep_help
print_line "Usage: sleep <seconds>"
print_line
print_line "Do nothing the specified number of seconds. This is useful in rc scripts."
print_line
end
#
# Causes process to pause for the specified number of seconds
#
def cmd_sleep(*args)
return if not (args and args.length == 1)
Rex::ThreadSafe.sleep(args[0].to_f)
end
def cmd_threads_help
print_line "Usage: threads [options]"
print_line
print_line "Background thread management."
print_line @@threads_opts.usage()
end
#
# Displays and manages running background threads
#
def cmd_threads(*args)
# Make the default behavior listing all jobs if there were no options
# or the only option is the verbose flag
if (args.length == 0 or args == ["-v"])
args.unshift("-l")
end
verbose = false
dump_list = false
dump_info = false
thread_id = nil
# Parse the command options
@@threads_opts.parse(args) { |opt, idx, val|
case opt
when "-v"
verbose = true
when "-l"
dump_list = true
# Terminate the supplied thread id
when "-k"
val = val.to_i
if not framework.threads[val]
print_error("No such thread")
else
print_line("Terminating thread: #{val}...")
framework.threads.kill(val)
end
when "-K"
print_line("Killing all non-critical threads...")
framework.threads.each_index do |i|
t = framework.threads[i]
next if not t
next if t[:tm_crit]
framework.threads.kill(i)
end
when "-i"
# Defer printing anything until the end of option parsing
# so we can check for the verbose flag.
dump_info = true
thread_id = val.to_i
when "-h"
cmd_threads_help
return false
end
}
if (dump_list)
tbl = Table.new(
Table::Style::Default,
'Header' => "Background Threads",
'Prefix' => "\n",
'Postfix' => "\n",
'Columns' =>
[
'ID',
'Status',
'Critical',
'Name',
'Started'
]
)
framework.threads.each_index do |i|
t = framework.threads[i]
next if not t
tbl << [ i.to_s, t.status || "dead", t[:tm_crit] ? "True" : "False", t[:tm_name].to_s, t[:tm_time].to_s ]
end
print(tbl.to_s)
end
if (dump_info)
thread = framework.threads[thread_id]
if (thread)
output = "\n"
output += " ID: #{thread_id}\n"
output += "Name: #{thread[:tm_name]}\n"
output += "Info: #{thread.status || "dead"}\n"
output += "Crit: #{thread[:tm_crit] ? "True" : "False"}\n"
output += "Time: #{thread[:tm_time].to_s}\n"
if (verbose)
output += "\n"
output += "Thread Source\n"
output += "=============\n"
thread[:tm_call].each do |c|
output += " #{c.to_s}\n"
end
output += "\n"
end
print(output + "\n")
else
print_line("Invalid Thread ID")
end
end
end
#
# Tab completion for the threads command
#
# @param str [String] the string currently being typed before tab was hit
# @param words [Array<String>] the previously completed words on the command line. words is always
# at least 1 when tab completion has reached this stage since the command itself has been completed
def cmd_threads_tabs(str, words)
if words.length == 1
return @@threads_opts.option_keys
end
if words.length == 2 && @@threads_opts.include?(words[1]) && @@threads_opts.arg_required?(words[1])
return framework.threads.each_index.map{ |idx| idx.to_s }
end
[]
end
def cmd_load_help
print_line "Usage: load <option> [var=val var=val ...]"
print_line
print_line "Loads a plugin from the supplied path."
print_line "For a list of built-in plugins, do: load -l"
print_line "For a list of loaded plugins, do: load -s"
print_line "The optional var=val options are custom parameters that can be passed to plugins."
print_line
end
def list_plugins
plugin_directories = {
'Framework' => Msf::Config.plugin_directory,
'User' => Msf::Config.user_plugin_directory
}
plugin_directories.each do |type, plugin_directory|
items = Dir.entries(plugin_directory).keep_if { |n| n.match(/^.+\.rb$/)}
next if items.empty?
print_status("Available #{type} plugins:")
items.sort.each do |item|
print_line(" * #{item.split('.').first}")
end
print_line
end
end
def load_plugin(args)
path = args[0]
opts = {
'LocalInput' => driver.input,
'LocalOutput' => driver.output,
'ConsoleDriver' => driver
}
# Parse any extra options that should be passed to the plugin
args.each { |opt|
k, v = opt.split(/\=/)
opts[k] = v if (k and v)
}
# If no absolute path was supplied, check the base and user plugin directories
if (path !~ /#{File::SEPARATOR}/)
plugin_file_name = path
# If the plugin isn't in the user directory (~/.msf3/plugins/), use the base
path = Msf::Config.user_plugin_directory + File::SEPARATOR + plugin_file_name
if not File.exist?( path + ".rb" )
# If the following "path" doesn't exist it will be caught when we attempt to load
path = Msf::Config.plugin_directory + File::SEPARATOR + plugin_file_name
end
end
# Load that plugin!
begin
if (inst = framework.plugins.load(path, opts))
print_status("Successfully loaded plugin: #{inst.name}")
end
rescue ::Exception => e
elog("Error loading plugin #{path}", error: e)
print_error("Failed to load plugin from #{path}: #{e}")
end
end
#
# Loads a plugin from the supplied path. If no absolute path is supplied,
# the framework root plugin directory is used.
#
def cmd_load(*args)
case args[0]
when '-l'
list_plugins
when '-h', nil, ''
cmd_load_help
when '-s'
framework.plugins.each{ |p| print_line p.name }
else
load_plugin(args)
end
end
#
# Tab completion for the load command
#
# @param str [String] the string currently being typed before tab was hit
# @param words [Array<String>] the previously completed words on the command line. words is always
# at least 1 when tab completion has reached this stage since the command itself has been completed
def cmd_load_tabs(str, words)
tabs = []
if (not words[1] or not words[1].match(/^\//))
# then let's start tab completion in the scripts/resource directories
begin
[
Msf::Config.user_plugin_directory,
Msf::Config.plugin_directory
].each do |dir|
next if not ::File.exist? dir
tabs += ::Dir.new(dir).find_all { |e|
path = dir + File::SEPARATOR + e
::File.file?(path) and File.readable?(path)
}
end
rescue Exception
end
else
tabs += tab_complete_filenames(str,words)
end
return tabs.map{|e| e.sub(/\.rb/, '')} - framework.plugins.map(&:name)
end
def cmd_route_help
print_line "Route traffic destined to a given subnet through a supplied session."
print_line
print_line "Usage:"
print_line " route [add/remove] subnet netmask [comm/sid]"
print_line " route [add/remove] cidr [comm/sid]"
print_line " route [get] <host or network>"
print_line " route [flush]"
print_line " route [print]"
print_line
print_line "Subcommands:"
print_line " add - make a new route"
print_line " remove - delete a route; 'del' is an alias"
print_line " flush - remove all routes"
print_line " get - display the route for a given target"
print_line " print - show all active routes"
print_line
print_line "Examples:"
print_line " Add a route for all hosts from 192.168.0.0 to 192.168.0.255 through session 1"
print_line " route add 192.168.0.0 255.255.255.0 1"
print_line " route add 192.168.0.0/24 1"
print_line
print_line " Delete the above route"
print_line " route remove 192.168.0.0/24 1"
print_line " route del 192.168.0.0 255.255.255.0 1"
print_line
print_line " Display the route that would be used for the given host or network"
print_line " route get 192.168.0.11"
print_line
end
#
# This method handles the route command which allows a user to specify
# which session a given subnet should route through.
#
def cmd_route(*args)
begin
args << 'print' if args.length == 0
action = args.shift
case action
when "add", "remove", "del"
subnet = args.shift
subnet, cidr_mask = subnet.split("/")
if Rex::Socket.is_ip_addr?(args.first)
netmask = args.shift
elsif Rex::Socket.is_ip_addr?(subnet)
cidr_mask ||= Rex::Socket.is_ipv6?(subnet) ? 128 : 32
netmask = Rex::Socket.addr_ctoa(cidr_mask, v6: Rex::Socket.is_ipv6?(subnet))
end
netmask = args.shift if netmask.nil?
gateway_name = args.shift
if (subnet.nil? || netmask.nil? || gateway_name.nil?)
print_error("Missing arguments to route #{action}.")
return false
end
case gateway_name
when /local/i
gateway = Rex::Socket::Comm::Local
when /^(-1|[0-9]+)$/
session = framework.sessions.get(gateway_name)
if session.kind_of?(Msf::Session::Comm)
gateway = session
elsif session.nil?
print_error("Not a session: #{gateway_name}")
return false
else
print_error("Cannot route through the specified session (not a Comm)")
return false
end
else
print_error("Invalid gateway")
return false
end
msg = "Route "
if action == "remove" or action == "del"
worked = Rex::Socket::SwitchBoard.remove_route(subnet, netmask, gateway)
msg << (worked ? "removed" : "not found")
else
worked = Rex::Socket::SwitchBoard.add_route(subnet, netmask, gateway)
msg << (worked ? "added" : "already exists")
end
print_status(msg)
when "get"
if (args.length == 0)
print_error("You must supply an IP address.")
return false
end
comm = Rex::Socket::SwitchBoard.best_comm(args[0])
if ((comm) and
(comm.kind_of?(Msf::Session)))
print_line("#{args[0]} routes through: Session #{comm.sid}")
else
print_line("#{args[0]} routes through: Local")
end
when "flush"
Rex::Socket::SwitchBoard.flush_routes
when "print"
# IPv4 Table
tbl_ipv4 = Table.new(
Table::Style::Default,
'Header' => "IPv4 Active Routing Table",
'Prefix' => "\n",
'Postfix' => "\n",
'Columns' =>
[
'Subnet',
'Netmask',
'Gateway',
],
'ColProps' =>
{
'Subnet' => { 'Width' => 17 },
'Netmask' => { 'Width' => 17 },
})
# IPv6 Table
tbl_ipv6 = Table.new(
Table::Style::Default,
'Header' => "IPv6 Active Routing Table",
'Prefix' => "\n",
'Postfix' => "\n",
'Columns' =>
[
'Subnet',
'Netmask',
'Gateway',
],
'ColProps' =>
{
'Subnet' => { 'Width' => 17 },
'Netmask' => { 'Width' => 17 },
})
# Populate Route Tables
Rex::Socket::SwitchBoard.each { |route|
if (route.comm.kind_of?(Msf::Session))
gw = "Session #{route.comm.sid}"
else
gw = route.comm.name.split(/::/)[-1]
end
tbl_ipv4 << [ route.subnet, route.netmask, gw ] if Rex::Socket.is_ipv4?(route.netmask)
tbl_ipv6 << [ route.subnet, route.netmask, gw ] if Rex::Socket.is_ipv6?(route.netmask)
}
# Print Route Tables
print(tbl_ipv4.to_s) if tbl_ipv4.rows.length > 0
print(tbl_ipv6.to_s) if tbl_ipv6.rows.length > 0
if (tbl_ipv4.rows.length + tbl_ipv6.rows.length) < 1
print_status("There are currently no routes defined.")
elsif (tbl_ipv4.rows.length < 1) && (tbl_ipv6.rows.length > 0)
print_status("There are currently no IPv4 routes defined.")
elsif (tbl_ipv4.rows.length > 0) && (tbl_ipv6.rows.length < 1)
print_status("There are currently no IPv6 routes defined.")
end
else
cmd_route_help
end
rescue => error
elog(error)
print_error(error.message)
end
end
#
# Tab completion for the route command
#
# @param str [String] the string currently being typed before tab was hit
# @param words [Array<String>] the previously completed words on the command line. words is always
# at least 1 when tab completion has reached this stage since the command itself has been completed
def cmd_route_tabs(str, words)
if words.length == 1
return %w{add remove get flush print}
end
ret = []
case words[1]
when "remove", "del"
Rex::Socket::SwitchBoard.each { |route|
case words.length
when 2
ret << route.subnet
when 3
if route.subnet == words[2]
ret << route.netmask
end
when 4
if route.subnet == words[2]
ret << route.comm.sid.to_s if route.comm.kind_of? Msf::Session
end
end
}
ret
when "add"
# We can't really complete the subnet and netmask args without
# diving pretty deep into all sessions, so just be content with
# completing sids for the last arg
if words.length == 4
ret = framework.sessions.keys.map { |k| k.to_s }
end
# The "get" command takes one arg, but we can't complete it either...
end
ret
end
#
# Tab completion for the save command
#
# @param str [String] the string currently being typed before tab was hit
# @param words [Array<String>] the previously completed words on the command line. words is always
# at least 1 when tab completion has reached this stage since the command itself has been completed
def cmd_save_tabs(str, words)
if words.length == 1
@@save_opts.option_keys.select { |opt| opt.start_with?(str) }
end
end
# Print save help information
def cmd_save_help
print_line 'Usage: save [options]'
print_line
print_line 'Save the active datastore contents to disk for automatic use across restarts of the console'
print_line "The configuration is stored in #{Msf::Config.config_file}"
print @@save_opts.usage
end
#
# Deletes or saves the active datastore contents to disk for automatic use across
# restarts of the console.
#
def cmd_save(*args)
if args.include?('-h') || args.include?('--help')
cmd_save_help
return false
end
if args.empty?
# Save config for current module
# Save the console config
driver.save_config
begin
FeatureManager.instance.save_config
rescue StandardException => e
elog(e)
end
# Save the framework's datastore
begin
framework.save_config
if driver.framework.dns_resolver
driver.framework.dns_resolver.save_config
end
if active_module
active_module.save_config
end
rescue
log_error("Save failed: #{$!}")
return false
end
print_line("Saved configuration to: #{Msf::Config.config_file}")
end
@@save_opts.parse(args) do |opt|
case opt
when '-d'
# Delete all saved options for modules from the config file.
# No framework options will be deleted.
begin
ini = Rex::Parser::Ini.new(::Msf::Config.config_file)
ini.delete_if { |k, _v| !k.start_with?('framework') }
ini.to_file(::Msf::Config.config_file)
rescue StandardError
print_error("Failed to delete console config: #{$!}")
end
if active_module
active_module.import_defaults
end
print_line('Deleted saved configs for all modules.')
when '-r'
active_module.import_defaults
print_line('Reloaded default options.')
when '-l'
active_module.load_config
print_line("Loaded config from #{Msf::Config.config_file}.")
when '-h'
cmd_save_help
return false
else
print_line("Unknown option: #{opt}")
print(@@save_opts.usage)
end
end
end
def cmd_spool_help
print_line "Usage: spool <off>|<filename>"
print_line
print_line "Example:"
print_line " spool /tmp/console.log"
print_line
end
def cmd_spool_tabs(str, words)
tab_complete_filenames(str, words)
end
def cmd_spool(*args)
if args.include?('-h') or args.empty?
cmd_spool_help
return
end
color = driver.output.config[:color]
if args[0] == "off"
stdout = Rex::Ui::Text::Output::Stdio.new
driver.init_ui(driver.input, stdout)
active_module.init_ui(driver.input, stdout) if defined?(active_module) && active_module
msg = "Spooling is now disabled"
else
stdout = Rex::Ui::Text::Output::Tee.new(args[0])
driver.init_ui(driver.input, stdout)
active_module.init_ui(driver.input, stdout) if defined?(active_module) && active_module
msg = "Spooling to file #{args[0]}..."
end
# Restore color
driver.output.config[:color] = color
print_status(msg)
end
def cmd_sessions_help
print_line('Usage: sessions [options] or sessions [id]')
print_line
print_line('Active session manipulation and interaction.')
print(@@sessions_opts.usage)
print_line
print_line('Many options allow specifying session ranges using commas and dashes.')
print_line('For example: sessions -s checkvm -i 1,3-5 or sessions -k 1-2,5,6')
print_line
end
#
# Provides an interface to the sessions currently active in the framework.
#
def cmd_sessions(*args)
begin
method = nil
quiet = false
show_active = false
show_inactive = false
show_extended = false
verbose = false
sid = nil
cmds = []
script = nil
response_timeout = 15
search_term = nil
session_name = nil
has_script_arguments = false
# any arguments that don't correspond to an option or option arg will
# be put in here
extra = []
if args.length == 1 && args[0] =~ /-?\d+/
method = 'interact'
sid = args[0].to_i
else
# Parse the command options
@@sessions_opts.parse(args) do |opt, idx, val|
next if has_script_arguments
case opt
when "-q", "--quiet"
quiet = true
# Run a command on all sessions, or the session given with -i
when "-c", "--command"
method = 'cmd'
cmds << val if val
when "-C", "--meterpreter-command"
method = 'meterp-cmd'
cmds << val if val
# Display the list of inactive sessions
when "-d", "--list-inactive"
show_inactive = true
method = 'list_inactive'
when "-x", "--list-extended"
show_extended = true
when "-v", "--list-verbose"
verbose = true
# Do something with the supplied session identifier instead of
# all sessions.
when "-i", "--interact"
sid = val
# Display the list of active sessions
when "-l", "--list"
show_active = true
method = 'list'
when "-k", "--kill"
method = 'kill'
sid = val || false
when "-K", "--kill-all"
method = 'killall'
# Run a script or module on specified sessions
when "-s", "--script"
unless script
method = 'script'
script = val
# Assume any parameter after the script name is a flag/argument we want to pass to the script itself.
extra += args[(idx + 1)..-1]
has_script_arguments = true
end
# Upload and exec to the specific command session
when "-u", "--upgrade"
method = 'upexec'
sid = val || false
# Search for specific session
when "-S", "--search"
search_term = val
# Display help banner
when "-h", "--help"
cmd_sessions_help
return false
when "-t", "--timeout"
if val.to_s =~ /^\d+$/
response_timeout = val.to_i
end
when "-n", "--name"
method = 'name'
session_name = val
else
extra << val
end
end
end
if !method && sid
method = 'interact'
end
unless sid.nil? || method == 'interact'
session_list = build_range_array(sid)
if session_list.blank?
print_error('Please specify valid session identifier(s)')
return false
end
end
if show_inactive && !framework.db.active
print_warning("Database not connected; list of inactive sessions unavailable")
end
if search_term
matching_sessions = get_matching_sessions(search_term)
# check for nil value indicating validation has found invalid input in search helper functions. Error will have been printed already
unless matching_sessions
return nil
end
if matching_sessions.empty?
print_error('No matching sessions.')
return nil
end
end
last_known_timeout = nil
# Now, perform the actual method
case method
when 'cmd'
if cmds.length < 1
print_error("No command specified!")
return false
end
cmds.each do |cmd|
if sid
sessions = session_list
elsif matching_sessions
sessions = matching_sessions
else
sessions = framework.sessions.keys.sort
end
if sessions.blank?
print_error("Please specify valid session identifier(s) using -i")
return false
end
sessions.each do |s|
session = verify_session(s)
next unless session
print_status("Running '#{cmd}' on #{session.type} session #{s} (#{session.session_host})")
if session.respond_to?(:response_timeout)
last_known_timeout = session.response_timeout
session.response_timeout = response_timeout
end
begin
case session.type.downcase
when 'meterpreter'
# If session.sys is nil, dont even try..
unless session.sys
print_error("Session #{s} does not have stdapi loaded, skipping...")
next
end
c, c_args = cmd.split(' ', 2)
begin
data = session.sys.process.capture_output(c, c_args,
{
'Channelized' => true,
'Subshell' => true,
'Hidden' => true
}, response_timeout)
print_line(data) unless data.blank?
rescue ::Rex::Post::Meterpreter::RequestError
print_error("Failed: #{$!.class} #{$!}")
rescue ::Rex::TimeoutError
print_error("Operation timed out. Timeout currently #{session.response_timeout} seconds, you can configure this with %grnsessions -c <cmd> --timeout <value>%clr")
end
when 'shell', 'powershell'
output = session.shell_command(cmd)
print_line(output) if output
when 'mssql', 'postgresql', 'mysql'
session.run_cmd(cmd, driver.output)
end
ensure
# Restore timeout for each session
if session.respond_to?(:response_timeout) && last_known_timeout
session.response_timeout = last_known_timeout
end
end
# If the session isn't a meterpreter or shell type, it
# could be a VNC session (which can't run commands) or
# something custom (which we don't know how to run
# commands on), so don't bother.
end
end
when 'meterp-cmd'
if cmds.length < 1
print_error("No command specified!")
return false
end
if sid
sessions = session_list
else
sessions = framework.sessions.keys.sort
end
if sessions.blank?
print_error("Please specify valid session identifier(s) using -i")
return false
end
cmds.each do |cmd|
sessions.each do |session|
begin
session = verify_session(session)
unless session.type == 'meterpreter'
print_error "Session ##{session.sid} is not a Meterpreter shell. Skipping..."
next
end
next unless session
print_status("Running '#{cmd}' on #{session.type} session #{session.sid} (#{session.session_host})")
if session.respond_to?(:response_timeout)
last_known_timeout = session.response_timeout
session.response_timeout = response_timeout
session.on_run_command_error_proc = log_on_timeout_error("Send timed out. Timeout currently #{session.response_timeout} seconds, you can configure this with %grnsessions -C <cmd> --timeout <value>%clr")
end
output = session.run_cmd(cmd, driver.output)
ensure
if session.respond_to?(:response_timeout) && last_known_timeout
session.response_timeout = last_known_timeout
session.on_run_command_error_proc = nil
end
end
end
end
when 'kill'
print_status("Killing the following session(s): #{session_list.join(', ')}")
session_list.each do |sess_id|
session = framework.sessions.get(sess_id)
if session
if session.respond_to?(:response_timeout)
last_known_timeout = session.response_timeout
session.response_timeout = response_timeout
end
print_status("Killing session #{sess_id}")
begin
session.kill
ensure
if session.respond_to?(:response_timeout) && last_known_timeout
session.response_timeout = last_known_timeout
end
end
else
print_error("Invalid session identifier: #{sess_id}")
end
end
when 'killall'
if matching_sessions
print_status('Killing matching sessions...')
print_line
print(Serializer::ReadableText.dump_sessions(framework, show_active: show_active, show_inactive: show_inactive, show_extended: show_extended, verbose: verbose, sessions: matching_sessions))
print_line
else
matching_sessions = framework.sessions
print_status('Killing all sessions...')
end
matching_sessions.each do |_session_id, session|
next unless session
if session.respond_to?(:response_timeout)
last_known_timeout = session.response_timeout
session.response_timeout = response_timeout
end
begin
session.kill
ensure
if session.respond_to?(:response_timeout) && last_known_timeout
session.response_timeout = last_known_timeout
end
end
end
when 'interact'
while sid
session = verify_session(sid)
if session
if session.respond_to?(:response_timeout)
last_known_timeout = session.response_timeout
session.response_timeout = response_timeout
session.on_run_command_error_proc = log_on_timeout_error("Send timed out. Timeout currently #{session.response_timeout} seconds, you can configure this with %grnsessions --interact <id> --timeout <value>%clr")
end
print_status("Starting interaction with #{session.name}...\n") unless quiet
begin
self.active_session = session
sid = session.interact(driver.input.dup, driver.output)
self.active_session = nil
driver.input.reset_tab_completion if driver.input.supports_readline
ensure
if session.respond_to?(:response_timeout) && last_known_timeout
session.response_timeout = last_known_timeout
session.on_run_command_error_proc = nil
end
end
else
sid = nil
end
end
when 'script'
unless script
print_error("No script or module specified!")
return false
end
sessions = sid ? session_list : framework.sessions.keys.sort
sessions.each do |sess_id|
session = verify_session(sess_id, true)
# @TODO: Not interactive sessions can or cannot have scripts run on them?
if session == false # specifically looking for false
# if verify_session returned false, sess_id is valid, but not interactive
session = framework.sessions.get(sess_id)
end
if session
if session.respond_to?(:response_timeout)
last_known_timeout = session.response_timeout
session.response_timeout = response_timeout
session.on_run_command_error_proc = log_on_timeout_error("Send timed out. Timeout currently #{session.response_timeout} seconds, you can configure this with %grnsessions --timeout <value> --script <script> <id>%clr")
end
begin
print_status("Session #{sess_id} (#{session.session_host}):")
print_status("Running #{script} on #{session.type} session" +
" #{sess_id} (#{session.session_host})")
begin
session.init_ui(driver.input, driver.output)
session.execute_script(script, *extra)
rescue ::Exception => e
log_error("Error executing script or module: #{e.class} #{e}")
end
ensure
if session.respond_to?(:response_timeout) && last_known_timeout
session.response_timeout = last_known_timeout
session.on_run_command_error_proc = nil
end
session.reset_ui
end
else
print_error("Invalid session identifier: #{sess_id}")
end
end
when 'upexec'
print_status("Executing 'post/multi/manage/shell_to_meterpreter' on " +
"session(s): #{session_list}")
session_list.each do |sess_id|
session = verify_session(sess_id)
if session
if session.respond_to?(:response_timeout)
last_known_timeout = session.response_timeout
session.response_timeout = response_timeout
end
begin
session.init_ui(driver.input, driver.output)
session.execute_script('post/multi/manage/shell_to_meterpreter')
session.reset_ui
ensure
if session.respond_to?(:response_timeout) && last_known_timeout
session.response_timeout = last_known_timeout
end
end
end
if session_list.count > 1
print_status("Sleeping 5 seconds to allow the previous handler to finish..")
sleep(5)
end
end
when 'list', 'list_inactive', nil
print_line
print(Serializer::ReadableText.dump_sessions(framework, show_active: show_active, show_inactive: show_inactive, show_extended: show_extended, verbose: verbose, sessions: matching_sessions))
print_line
when 'name'
if session_name.blank?
print_error('Please specify a valid session name')
return false
end
sessions = sid ? session_list : nil
if sessions.nil? || sessions.empty?
print_error("Please specify valid session identifier(s) using -i")
return false
end
sessions.each do |s|
if framework.sessions[s].respond_to?(:name=)
framework.sessions[s].name = session_name
print_status("Session #{s} named to #{session_name}")
else
print_error("Session #{s} cannot be named")
end
end
end
rescue IOError, EOFError, Rex::StreamClosedError
print_status("Session stream closed.")
rescue ::Interrupt
raise $!
rescue ::Exception
log_error("Session manipulation failed: #{$!} #{$!.backtrace.inspect}")
end
# Reset the active session
self.active_session = nil
true
end
def get_matching_sessions(search_term)
matching_sessions = {}
terms = search_term.split
id_searches = []
type_searches = []
checkin_searches = []
searches = []
# Group search terms by what's being searched for
terms.each do |term|
case term.split(':').first
when SESSION_ID
id_searches << term
when SESSION_TYPE
type_searches << term
when LAST_CHECKIN
checkin_searches << term
else
print_error("Please provide valid search term. Given: #{term.split(':').first}. Supported keywords are: #{VALID_SESSION_SEARCH_PARAMS.join(', ')}")
return nil
end
end
# Group results by search term - OR filters
[id_searches, type_searches].each do |search|
next if search.empty?
id_matches = {}
search.each do |term|
matches = filter_sessions_by_search(term)
return unless matches
id_matches = id_matches.merge(matches)
end
searches << id_matches
end
# Retrieve checkin search results. AND filter with a max length of 2
unless checkin_searches.empty?
unless validate_checkin_searches(checkin_searches)
return
end
checkin_matches = filter_sessions_by_search(checkin_searches.first)
if checkin_searches[1]
matches = filter_sessions_by_search(checkin_searches[1])
checkin_matches = checkin_matches.select { |session_id, session| matches[session_id] == session }
end
searches << checkin_matches
end
# AND all the results together for final session list
if searches.empty?
print_error('Please provide a valid search query.')
return nil
else
matching_sessions = searches.first
searches[1..].each do |result_set|
matching_sessions = matching_sessions.select { |session_id, session| result_set[session_id] == session }
end
end
matching_sessions
end
def validate_checkin_searches(checkin_searches)
checkin_searches.each do |search_term|
unless search_term.split(':').length == 3
print_error('Please only specify last_checkin, before or after, and a time. Ex: last_checkin:before:1m30s')
return false
end
time_value = search_term.split(':')[2]
time_unit_string = time_value.gsub(/[^a-zA-Z]/, '')
unless time_unit_string == time_unit_string.squeeze
print_error('Please do not provide duplicate time units in your query')
return false
end
operator = checkin_searches[0].split(':')[1]
unless VALID_OPERATORS.include?(operator)
print_error("Please specify less_than or greater_than for checkin query. Ex: last_checkin:less_than:1m30s. Given: #{operator}")
return false
end
end
if checkin_searches.length > 2
print_error("Too many checkin searches. Max: 2. Given: #{checkin_searches.length}")
return false
elsif checkin_searches.length == 2
_, operator1, value1 = checkin_searches[0].split(':')
_, operator2, value2 = checkin_searches[1].split(':')
unless VALID_OPERATORS.include?(operator1) && VALID_OPERATORS.include?(operator2)
print_error('last_checkin can only be searched for using before or after. Ex: last_checkin:before:1m30s')
return false
end
if operator1 == operator2
print_error("Cannot search for last_checkin with two #{operator1} arguments.")
return false
end
if (operator1 == GREATER_THAN && parse_duration(value2) < parse_duration(value1)) || (operator1 == LESS_THAN && parse_duration(value1) < parse_duration(value2))
print_error('After value must be a larger duration than the before value.')
return false
end
end
true
end
def filter_sessions_by_search(search_term)
matching_sessions = {}
field, = search_term.split(':')
framework.sessions.each do |session_id, session|
if !session.respond_to?(:last_checkin) && (field == LAST_CHECKIN)
next
end
matches_search = evaluate_search_criteria(session, search_term)
return nil if matches_search.nil?
case field
when LAST_CHECKIN
if session.last_checkin && evaluate_search_criteria(session, search_term)
matching_sessions[session_id] = session
end
when SESSION_TYPE, SESSION_ID
matching_sessions[session_id] = session if evaluate_search_criteria(session, search_term)
else
print_error("Unrecognized search term: #{field}")
return nil
end
end
matching_sessions
end
def evaluate_search_criteria(session, search_term)
field, operator, value = search_term.split(':')
case field
when LAST_CHECKIN
last_checkin_time = session.last_checkin
offset = parse_duration(value)
return nil unless offset
threshold_time = Time.now - offset
case operator
when GREATER_THAN
return threshold_time > last_checkin_time
when LESS_THAN
return threshold_time < last_checkin_time
end
when SESSION_ID
return session.sid.to_s == operator
when SESSION_TYPE
return session.type.casecmp?(operator)
end
end
def parse_duration(duration)
total_time = 0
time_tokens = duration.scan(/(?:\d+\.?\d*|\.\d+)/).zip(duration.scan(/[a-zA-Z]+/))
time_tokens.each do |value, unit|
if unit.nil? || value.nil?
print_error('Please specify both time units and amounts')
return nil
end
case unit.downcase
when 'd'
total_time += value.to_f * 86400
when 'h'
total_time += value.to_f * 3600
when 'm'
total_time += value.to_f * 60
when 's'
total_time += value.to_f
else
print_error("Unrecognized time format: #{value}")
return nil
end
end
total_time.to_i
end
#
# Tab completion for the sessions command
#
# @param str [String] the string currently being typed before tab was hit
# @param words [Array<String>] the previously completed words on the command line. words is always
# at least 1 when tab completion has reached this stage since the command itself has been completed
def cmd_sessions_tabs(str, words)
if words.length == 1
return @@sessions_opts.option_keys.select { |opt| opt.start_with?(str) }
end
case words[-1]
when "-i", "--interact", "-k", "--kill", "-u", "--upgrade"
return framework.sessions.keys.map { |k| k.to_s }
when "-c", "--command"
# Can't really complete commands hehe
when "-s", "--search"
# XXX: Complete scripts
end
[]
end
def cmd_set_help
print_line "Usage: set [options] [name] [value]"
print_line
print_line "Set the given option to value. If value is omitted, print the current value."
print_line "If both are omitted, print options that are currently set."
print_line
print_line "If run from a module context, this will set the value in the module's"
print_line "datastore. Use -g to operate on the global datastore."
print_line
print_line "If setting a PAYLOAD, this command can take an index from `show payloads'."
print @@set_opts.usage if framework.features.enabled?(Msf::FeatureManager::DATASTORE_FALLBACKS)
print_line
end
#
# Sets a name to a value in a context aware environment.
#
def cmd_set(*args)
# Figure out if these are global variables
global = false
append = false
clear = false
# Manually parse options to allow users to set the strings
# such as `-g` in a datastore value
loop do
if args[0] == '-g' || args[0] == '--global'
args.shift
global = true
elsif args[0] == '-a'
args.shift
append = true
elsif (args[0] == '-c' || args[0] == '--clear') && framework.features.enabled?(Msf::FeatureManager::DATASTORE_FALLBACKS)
args.shift
clear = true
else
break
end
end
valid_options = []
# Determine which data store we're operating on
if (active_module and global == false)
datastore = active_module.datastore
tab_complete_option_names(active_module, '', []).each do |opt_name|
valid_options << opt_name
option = active_module.options[opt_name]
next unless option
# aliases that are defined for backwards compatibility are not tab completed but are still valid option names
valid_options += active_module.options[opt_name].aliases
end
else
global = true
datastore = self.framework.datastore
end
# Dump the contents of the active datastore if no args were supplied
if (args.length == 0)
# If we aren't dumping the global data store, then go ahead and
# dump it first
if (!global)
print("\n" +
Msf::Serializer::ReadableText.dump_datastore(
"Global", framework.datastore))
end
# Dump the active datastore
print("\n" +
Msf::Serializer::ReadableText.dump_datastore(
(global) ? "Global" : "Module: #{active_module.refname}",
datastore) + "\n")
return true
elsif args.length == 1 && !clear
if global || valid_options.any? { |vo| vo.casecmp?(args[0]) }
print_line("#{args[0]} => #{datastore[args[0]]}")
return true
else
message = "Unknown datastore option: #{args[0]}."
suggestion = DidYouMean::SpellChecker.new(dictionary: valid_options).correct(args[0]).first
message << " Did you mean #{suggestion}?" if suggestion
print_error(message)
cmd_set_help
return false
end
end
# Set the supplied name to the supplied value
name, *values_array = args
if clear
value = nil
elsif name.casecmp?('RHOST') || name.casecmp?('RHOSTS')
# Wrap any values which contain spaces in quotes to ensure it's parsed correctly later
value = values_array.map { |value| value.include?(' ') ? "\"#{value}\"" : value }.join(' ')
else
value = values_array.join(' ')
end
# Set PAYLOAD
if name.upcase == 'PAYLOAD' && active_module && (active_module.exploit? || active_module.evasion?) && !clear
value = trim_path(value, 'payload')
index_from_list(payload_show_results, value) do |mod|
return false unless mod && mod.respond_to?(:first)
# [name, class] from payload_show_results
value = mod.first
end
end
unless global || valid_options.any? { |vo| vo.casecmp?(name) }
message = "Unknown datastore option: #{name}."
suggestion = DidYouMean::SpellChecker.new(dictionary: valid_options).correct(name).first
message << " Did you mean #{suggestion}?" if suggestion
print_warning(message)
end
# If the driver indicates that the value is not valid, bust out.
if (driver.on_variable_set(global, name, value) == false)
print_error("The value specified for #{name} is not valid.")
return false
end
# Save the old value before changing it, in case we need to compare it
old_value = datastore[name]
begin
if append
datastore[name] = datastore[name] + value
else
datastore[name] = value
end
rescue Msf::OptionValidateError => e
print_error(e.message)
elog('Exception encountered in cmd_set', error: e)
end
# Set PAYLOAD from TARGET
if name.upcase == 'TARGET' && active_module && (active_module.exploit? || active_module.evasion?)
active_module.import_target_defaults
end
# If the new SSL value already set in datastore[name] is different from the old value, warn the user
if name.casecmp('SSL') == 0 && datastore[name] != old_value
print_warning("Changing the SSL option's value may require changing RPORT!")
end
if name.casecmp?('SessionTlvLogging')
# Check if we need to append the default filename if user provided an output directory
if datastore[name].start_with?('file:')
pathname = ::Pathname.new(datastore[name].split('file:').last)
if ::File.directory?(pathname)
datastore[name] = ::File.join(datastore[name], 'sessiontlvlogging.txt')
end
end
framework.sessions.each { |_index, session| session.initialize_tlv_logging(datastore[name]) if session.type.casecmp? 'meterpreter' }
end
print_line("#{name} => #{datastore[name]}")
end
def payload_show_results
Msf::Ui::Console::CommandDispatcher::Modules.class_variable_get(:@@payload_show_results)
end
#
# Tab completion for the set command
#
# @param str [String] the string currently being typed before tab was hit
# @param words [Array<String>] the previously completed words on the command line. words is always
# at least 1 when tab completion has reached this stage since the command itself has been completed
def cmd_set_tabs(str, words)
# A value has already been specified
if words.length > 3
return []
elsif words.length == 3 and words[1] != '-g' and words[1] != '--global'
return []
end
# A value needs to be specified, show tab completion options where possible
if words.length == 3 or (words.length == 2 and words[1][0] != '-')
return tab_complete_option_values(active_module, str, words, opt: words[-1])
end
option_names = tab_complete_option_names(active_module, str, words)
if words.length == 1
# Only the command has been provided, offer options which immediately follow the command
options = @@set_opts.option_keys.select { |opt| opt.start_with?(str) }
return options + option_names
end
option_names
end
def cmd_setg_help
print_line "Usage: setg [option] [value]"
print_line
print_line "Exactly like set -g, set a value in the global datastore."
print @@setg_opts.usage if framework.features.enabled?(Msf::FeatureManager::DATASTORE_FALLBACKS)
print_line
end
#
# Tab completion for the unset command
#
# @param str [String] the string currently being typed before tab was hit
# @param words [Array<String>] the previously completed words on the command
# line. `words` is always at least 1 when tab completion has reached this
# stage since the command itself has been completed.
def cmd_unset_tabs(str, words)
datastore_names = tab_complete_module_datastore_names(active_module, str, words)
if words.length == 1
# Only the command has been provided, offer options which immediately follow the command
options = @@unset_opts.option_keys.select { |opt| opt.start_with?(str) }
return options + datastore_names
end
datastore_names
end
#
# Sets the supplied variables in the global datastore.
#
def cmd_setg(*args)
args.unshift('-g')
cmd_set(*args)
end
#
# Tab completion for the setg command
#
# @param str [String] the string currently being typed before tab was hit
# @param words [Array<String>] the previously completed words on the command line. words is always
# at least 1 when tab completion has reached this stage since the command itself has been completed
def cmd_setg_tabs(str, words)
cmd_set_tabs(str, words)
end
def cmd_unload_help
print_line "Usage: unload <plugin name>"
print_line
print_line "Unloads a plugin by its symbolic name. Use 'show plugins' to see a list of"
print_line "currently loaded plugins."
print_line
end
#
# Unloads a plugin by its name.
#
def cmd_unload(*args)
if (args.length == 0)
cmd_unload_help
return false
end
# Find a plugin within the plugins array
plugin = framework.plugins.find { |p| p.name.downcase == args[0].downcase }
# Unload the plugin if it matches the name we're searching for
if plugin
print("Unloading plugin #{args[0]}...")
framework.plugins.unload(plugin)
print_line("unloaded.")
end
end
#
# Tab completion for the unload command
#
# @param str [String] the string currently being typed before tab was hit
# @param words [Array<String>] the previously completed words on the command line. words is always
# at least 1 when tab completion has reached this stage since the command itself has been completed
def cmd_unload_tabs(str, words)
return [] if words.length > 1
tabs = []
framework.plugins.each { |k| tabs.push(k.name) }
return tabs
end
def cmd_get_help
print_line "Usage: get var1 [var2 ...]"
print_line
print_line "The get command is used to get the value of one or more variables."
print_line
end
#
# Gets a value if it's been set.
#
def cmd_get(*args)
# Figure out if these are global variables
global = false
if (args[0] == '-g')
args.shift
global = true
end
# No arguments? No cookie.
if args.empty?
global ? cmd_getg_help : cmd_get_help
return false
end
# Determine which data store we're operating on
if (active_module && !global)
datastore = active_module.datastore
else
datastore = framework.datastore
end
args.each { |var| print_line("#{var} => #{datastore[var]}") }
end
#
# Tab completion for the get command
#
# @param str [String] the string currently being typed before tab was hit
# @param words [Array<String>] the previously completed words on the command line. words is always
# at least 1 when tab completion has reached this stage since the command itself has been completed
def cmd_get_tabs(str, words)
datastore = active_module ? active_module.datastore : self.framework.datastore
datastore.keys
end
def cmd_getg_help
print_line "Usage: getg var1 [var2 ...]"
print_line
print_line "Exactly like get -g, get global variables"
print_line
end
#
# Gets variables in the global data store.
#
def cmd_getg(*args)
args.unshift('-g')
cmd_get(*args)
end
#
# Tab completion for the getg command
#
# @param str [String] the string currently being typed before tab was hit
# @param words [Array<String>] the previously completed words on the command line. words is always
# at least 1 when tab completion has reached this stage since the command itself has been completed
def cmd_getg_tabs(str, words)
self.framework.datastore.keys
end
def cmd_unset_help
if framework.features.enabled?(Msf::FeatureManager::DATASTORE_FALLBACKS)
print_line "Usage: unset [-g] var1 var2 var3 ..."
print_line
print_line "The unset command is used to unset one or more variables."
print_line "To flush all entries, specify 'all' as the variable name."
print_line "With -g, operates on global datastore variables."
print_line
else
print_line "Usage: unset [options] var1 var2 var3 ..."
print_line
print_line "The unset command is used to unset one or more variables which have been set by the user."
print_line "To update all entries, specify 'all' as the variable name."
print @@unset_opts.usage
print_line
end
end
#
# Unsets a value if it's been set.
#
def cmd_unset(*args)
if framework.features.enabled?(Msf::FeatureManager::DATASTORE_FALLBACKS)
return cmd_unset_with_fallbacks(*args)
end
# Figure out if these are global variables
global = false
if (args[0] == '-g')
args.shift
global = true
end
# Determine which data store we're operating on
if (active_module and global == false)
datastore = active_module.datastore
else
datastore = framework.datastore
end
# No arguments? No cookie.
if (args.length == 0)
cmd_unset_help
return false
end
# If all was specified, then flush all of the entries
if args[0] == 'all'
print_line("Flushing datastore...")
# Re-import default options into the module's datastore
if (active_module and global == false)
active_module.import_defaults
# Or simply clear the global datastore
else
datastore.clear
end
return true
end
while ((val = args.shift))
if (driver.on_variable_unset(global, val) == false)
print_error("The variable #{val} cannot be unset at this time.")
next
end
print_line("Unsetting #{val}...")
datastore.delete(val)
end
end
#
# Unsets a value if it's been set, resetting the value back to a default value
#
def cmd_unset_with_fallbacks(*args)
if args.include?('-h') || args.include?('--help')
cmd_unset_help
return
end
# Figure out if these are global variables
global = false
@@unset_opts.parse(args) do |opt, idx, val|
case opt
when '-g'
global = true
end
end
variable_names = args.reject { |arg| arg.start_with?('-') }
# No variable names? No cookie.
if variable_names.empty?
cmd_unset_help
return false
end
# Determine which data store we're operating on
if active_module && !global
datastore = active_module.datastore
else
datastore = framework.datastore
end
is_all_variables = variable_names[0] == 'all'
if is_all_variables
variable_names = datastore.keys
variable_names += Msf::DataStore::GLOBAL_KEYS if global
variable_names += ['PAYLOAD'] if !global && active_module && (active_module.exploit? || active_module.evasion?)
variable_names = variable_names.uniq(&:downcase)
end
print_line("Unsetting datastore...") if is_all_variables
variable_names.each do |variable_name|
if driver.on_variable_unset(global, variable_name) == false
print_error("The variable #{variable_name} cannot be unset at this time.") # unless variable_name.casecmp?('PAYLOAD')
next
end
print_line("Unsetting #{variable_name}...") unless is_all_variables
datastore.unset(variable_name)
end
# Do a final pass over the datastore. If a user has unset a variable - but it continues to have a value either through
# option defaults, or being globally set it might be confusing to users. In this scenario, log out a helpful message.
#
# i.e. the scenario of a user unsetting 'RHOSTS', but the value continues to inherit from the global framework datastore.
unless is_all_variables
variable_names.each do |variable_name|
search_result = datastore.search_for(variable_name)
if search_result.fallback?
print_warning(
"Variable #{variable_name.inspect} unset - but will continue to use #{search_result.fallback_key.inspect} as a fallback preference. " \
"If this is not desired, either run #{Msf::Ui::Tip.highlight("set #{variable_name} new_value")} or #{Msf::Ui::Tip.highlight("unset #{search_result.fallback_key}")}"
)
elsif !global && search_result.global?
print_warning(
"Variable #{variable_name.inspect} unset - but will continue to use the globally set value as a preference. " \
"If this is not desired, either run #{Msf::Ui::Tip.highlight("set --clear #{variable_name}")} or #{Msf::Ui::Tip.highlight("unsetg #{variable_name}")}"
)
elsif !search_result.value.nil?
print_warning(
"Variable #{variable_name.inspect} unset - but will use a default value still. " \
"If this is not desired, set it to a new value or attempt to clear it with #{Msf::Ui::Tip.highlight("set --clear #{variable_name}")}"
)
end
end
end
end
def cmd_unsetg_help
print_line "Usage: unsetg [options] var1 var2 var3 ..."
print_line
print_line "Exactly like unset -g, unset global variables, or all"
print @@unsetg_opts.usage if framework.features.enabled?(Msf::FeatureManager::DATASTORE_FALLBACKS)
print_line
end
#
# Unsets variables in the global data store.
#
def cmd_unsetg(*args)
args.unshift('-g')
cmd_unset(*args)
end
#
# Tab completion for the unsetg command
#
# @param str [String] the string currently being typed before tab was hit
# @param words [Array<String>] the previously completed words on the command line. words is always
# at least 1 when tab completion has reached this stage since the command itself has been completed
def cmd_unsetg_tabs(str, words)
tab_complete_datastore_names(framework.datastore, str, words)
end
alias cmd_unsetg_help cmd_unset_help
#
# Returns the revision of the framework and console library
#
def cmd_version(*args)
print_line("Framework: #{Msf::Framework::Version}")
print_line("Console : #{Msf::Framework::Version}")
end
def cmd_grep_help
cmd_grep '-h'
end
#
# Greps the output of another console command, usage is similar the shell grep command
# grep [options] pattern other_cmd [other command's args], similar to the shell's grep [options] pattern file
# however it also includes -k to keep lines and -s to skip lines. grep -k 5 is useful for keeping table headers
#
# @param args [Array<String>] Args to the grep command minimally including a pattern & a command to search
# @return [String,nil] Results matching the regular expression given
def cmd_grep(*args)
match_mods = {:insensitive => false}
output_mods = {:count => false, :invert => false}
opts = OptionParser.new do |opts|
opts.banner = "Usage: grep [OPTIONS] [--] PATTERN CMD..."
opts.separator "Grep the results of a console command (similar to Linux grep command)"
opts.separator ""
opts.on '-m num', '--max-count num', 'Stop after num matches.', Integer do |max|
match_mods[:max] = max
end
opts.on '-A num', '--after-context num', 'Show num lines of output after a match.', Integer do |num|
output_mods[:after] = num
end
opts.on '-B num', '--before-context num', 'Show num lines of output before a match.', Integer do |num|
output_mods[:before] = num
end
opts.on '-C num', '--context num', 'Show num lines of output around a match.', Integer do |num|
output_mods[:before] = output_mods[:after] = num
end
opts.on '-v', '--[no-]invert-match', 'Invert match.' do |invert|
match_mods[:invert] = invert
end
opts.on '-i', '--[no-]ignore-case', 'Ignore case.' do |insensitive|
match_mods[:insensitive] = insensitive
end
opts.on '-c', '--count', 'Only print a count of matching lines.' do |count|
output_mods[:count] = count
end
opts.on '-k num', '--keep-header num', 'Keep (include) num lines at start of output', Integer do |num|
output_mods[:keep] = num
end
opts.on '-s num', '--skip-header num', 'Skip num lines of output before attempting match.', Integer do |num|
output_mods[:skip] = num
end
opts.on '-h', '--help', 'Help banner.' do
return print(remove_lines(opts.help, '--generate-completions'))
end
# Internal use
opts.on '--generate-completions str', 'Return possible tab completions for given string.' do |str|
return opts.candidate str
end
end
# OptionParser#order allows us to take the rest of the line for the command
pattern, *rest = opts.order(args)
cmd = Shellwords.shelljoin(rest)
return print(opts.help) if !pattern || cmd.empty?
rx = Regexp.new(pattern, match_mods[:insensitive])
# redirect output after saving the old one and getting a new output buffer to use for redirect
orig_output = driver.output
# we use a rex buffer but add a write method to the instance, which is
# required in order to be valid $stdout
temp_output = Rex::Ui::Text::Output::Buffer.new
temp_output.extend Rex::Ui::Text::Output::Buffer::Stdout
driver.init_ui(driver.input, temp_output)
# run the desired command to be grepped
driver.run_single(cmd)
# restore original output
driver.init_ui(driver.input, orig_output)
# dump the command's output so we can grep it
cmd_output = temp_output.dump_buffer
# Bail if the command failed
if cmd_output =~ /Unknown command:/
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.
all_lines = cmd_output.lines.select {|line| line}
# control matching based on remaining match_mods (:insensitive was already handled)
if match_mods[:invert]
statement = 'not line =~ rx'
else
statement = 'line =~ rx'
end
our_lines = []
count = 0
all_lines.each_with_index do |line, line_num|
next if (output_mods[:skip] and line_num < output_mods[:skip])
our_lines << line if (output_mods[:keep] and line_num < output_mods[:keep])
# we don't want to keep processing if we have a :max and we've reached it already (not counting skips/keeps)
break if match_mods[:max] and count >= match_mods[:max]
if eval statement
count += 1
# we might get a -A/after and a -B/before at the same time
our_lines += retrieve_grep_lines(all_lines,line_num,output_mods[:before], output_mods[:after])
end
end
# now control output based on remaining output_mods such as :count
return print_status(count.to_s) if output_mods[:count]
our_lines.each {|line| print line}
end
#
# Tab completion for the grep command
#
# @param str [String] the string currently being typed before tab was hit
# @param words [Array<String>] the previously completed words on the command line. words is always
# at least 1 when tab completion has reached this stage since the command itself has been completed
def cmd_grep_tabs(str, words)
str = '-' if str.empty? # default to use grep's options
tabs = cmd_grep '--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
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(remove_lines(opts.help, '--generate-completions'))
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
protected
#
# verifies that a given session_id is valid and that the session is interactive.
# The various return values allow the caller to make better decisions on what
# action can & should be taken depending on the capabilities of the session
# and the caller's objective while making it simple to use in the nominal case
# where the caller needs session_id to match an interactive session
#
# @param session_id [String] A session id, which is an integer as a string
# @param quiet [Boolean] True means the method will produce no error messages
# @return [session] if the given session_id is valid and session is interactive
# @return [false] if the given session_id is valid, but not interactive
# @return [nil] if the given session_id is not valid at all
def verify_session(session_id, quiet = false)
session = framework.sessions.get(session_id)
if session
if session.interactive?
session
else
print_error("Session #{session_id} is non-interactive.") unless quiet
false
end
else
print_error("Invalid session identifier: #{session_id}") unless quiet
nil
end
end
#
# Custom error code to handle timeout errors
#
# @param message [String] The message to be printed when a timeout error is hit
# @return [Proc] proc function that prints the specified error when the error types match
def log_on_timeout_error(message)
proc do |e|
next unless e.is_a?(Rex::TimeoutError) || e.is_a?(Timeout::Error)
elog(e)
print_error(message)
:handled
end
end
#
# Returns an array of lines at the provided line number plus any before and/or after lines requested
# from all_lines by supplying the +before+ and/or +after+ parameters which are always positive
#
# @param all_lines [Array<String>] An array of all lines being considered for matching
# @param line_num [Integer] The line number in all_lines which has satisfied the match
# @param after [Integer] The number of lines after the match line to include (should always be positive)
# @param before [Integer] The number of lines before the match line to include (should always be positive)
# @return [Array<String>] Array of lines including the line at line_num and any +before+ and/or +after+
def retrieve_grep_lines(all_lines,line_num, before = nil, after = nil)
after = after.to_i.abs
before = before.to_i.abs
start = line_num - before
start = 0 if start < 0
finish = line_num + after
all_lines.slice(start..finish)
end
end
end end end end