1831 lines
69 KiB
Ruby
1831 lines
69 KiB
Ruby
# -*- coding: binary -*-
|
||
|
||
|
||
module Msf
|
||
module Ui
|
||
module Console
|
||
module CommandDispatcher
|
||
|
||
#
|
||
# {CommandDispatcher} for commands related to background jobs in Metasploit Framework.
|
||
#
|
||
class Modules
|
||
|
||
include Msf::Ui::Console::CommandDispatcher
|
||
include Msf::Ui::Console::CommandDispatcher::Common
|
||
|
||
include Rex::Text::Color
|
||
|
||
@@search_opts = Rex::Parser::Arguments.new(
|
||
['-h', '--help'] => [false, 'Help banner'],
|
||
['-I', '--ignore'] => [false, 'Ignore the command if the only match has the same name as the search'],
|
||
['-o', '--output'] => [true, 'Send output to a file in csv format', '<filename>'],
|
||
['-S', '--filter'] => [true, 'Regex pattern used to filter search results', '<filter>'],
|
||
['-u', '--use'] => [false, 'Use module if there is one result'],
|
||
['-s', '--sort-ascending'] => [true, 'Sort search results by the specified column in ascending order', '<column>'],
|
||
['-r', '--sort-descending'] => [true, 'Reverse the order of search results to descending order', '<column>']
|
||
)
|
||
|
||
@@favorite_opts = Rex::Parser::Arguments.new(
|
||
'-h' => [false, 'Help banner'],
|
||
'-c' => [false, 'Clear the contents of the favorite modules file'],
|
||
'-d' => [false, 'Delete module(s) or the current active module from the favorite modules file'],
|
||
'-l' => [false, 'Print the list of favorite modules (alias for `show favorites`)']
|
||
)
|
||
|
||
@@favorites_opts = Rex::Parser::Arguments.new(
|
||
'-h' => [false, 'Help banner']
|
||
)
|
||
|
||
def commands
|
||
{
|
||
"back" => "Move back from the current context",
|
||
"advanced" => "Displays advanced options for one or more modules",
|
||
"info" => "Displays information about one or more modules",
|
||
"options" => "Displays global options or for one or more modules",
|
||
"loadpath" => "Searches for and loads modules from a path",
|
||
"popm" => "Pops the latest module off the stack and makes it active",
|
||
"pushm" => "Pushes the active or list of modules onto the module stack",
|
||
"listm" => "List the module stack",
|
||
"clearm" => "Clear the module stack",
|
||
"previous" => "Sets the previously loaded module as the current module",
|
||
"reload_all" => "Reloads all modules from all defined module paths",
|
||
"search" => "Searches module names and descriptions",
|
||
"show" => "Displays modules of a given type, or all modules",
|
||
"use" => "Interact with a module by name or search term/index",
|
||
"favorite" => "Add module(s) to the list of favorite modules",
|
||
"favorites" => "Print the list of favorite modules (alias for `show favorites`)"
|
||
}
|
||
end
|
||
|
||
#
|
||
# Initializes the datastore cache
|
||
#
|
||
def initialize(driver)
|
||
super
|
||
|
||
@dscache = {}
|
||
@previous_module = nil
|
||
@module_name_stack = []
|
||
# Array of individual modules that have been searched for
|
||
@module_search_results = []
|
||
# Module search results, with additional metadata on what to do if the module is interacted with
|
||
@module_search_results_with_usage_metadata = []
|
||
@@payload_show_results = []
|
||
@dangerzone_map = nil
|
||
end
|
||
|
||
#
|
||
# Returns the name of the command dispatcher.
|
||
#
|
||
def name
|
||
"Module"
|
||
end
|
||
|
||
def cmd_advanced_help
|
||
print_line 'Usage: advanced [mod1 mod2 ...]'
|
||
print_line
|
||
print_line 'Queries the supplied module or modules for advanced options. If no module is given,'
|
||
print_line 'show advanced options for the currently active module.'
|
||
print_line
|
||
end
|
||
|
||
def cmd_advanced(*args)
|
||
if args.empty?
|
||
if (active_module)
|
||
show_advanced_options(active_module)
|
||
return true
|
||
else
|
||
print_error('No module active')
|
||
return false
|
||
end
|
||
end
|
||
|
||
args.each { |name|
|
||
mod = framework.modules.create(name)
|
||
|
||
if (mod == nil)
|
||
print_error("Invalid module: #{name}")
|
||
else
|
||
show_advanced_options(mod)
|
||
end
|
||
}
|
||
end
|
||
|
||
def cmd_info_help
|
||
print_line "Usage: info <module name> [mod2 mod3 ...]"
|
||
print_line
|
||
print_line "Options:"
|
||
print_line "* The flag '-j' will print the data in json format"
|
||
print_line "* The flag '-d' will show the markdown version with a browser. More info, but could be slow."
|
||
print_line "Queries the supplied module or modules for information. If no module is given,"
|
||
print_line "show info for the currently active module."
|
||
print_line
|
||
end
|
||
|
||
def print_module_info(mod, dump_json: false, show_doc: false)
|
||
if dump_json
|
||
print(Serializer::Json.dump_module(mod) + "\n")
|
||
elsif show_doc
|
||
f = Tempfile.new(["#{mod.shortname}_doc", '.html'])
|
||
begin
|
||
print_status("Generating documentation for #{mod.shortname}, then opening #{f.path} in a browser...")
|
||
Msf::Util::DocumentGenerator.spawn_module_document(mod, f)
|
||
ensure
|
||
f.close if f
|
||
end
|
||
else
|
||
print(Serializer::ReadableText.dump_module(mod))
|
||
print("\nView the full module info with the #{Msf::Ui::Tip.highlight('info -d')} command.\n\n")
|
||
end
|
||
end
|
||
|
||
# Handles the index selection formatting
|
||
def print_module_search_results_usage
|
||
last_mod_with_usage_metadata = @module_search_results_with_usage_metadata.last
|
||
index_usage = "use #{@module_search_results_with_usage_metadata.length - 1}"
|
||
index_info = "info #{@module_search_results_with_usage_metadata.length - 1}"
|
||
name_usage = "use #{last_mod_with_usage_metadata[:mod].fullname}"
|
||
|
||
additional_usage_message = ""
|
||
additional_usage_example = (last_mod_with_usage_metadata[:datastore] || {}).first
|
||
if framework.features.enabled?(Msf::FeatureManager::HIERARCHICAL_SEARCH_TABLE) && additional_usage_example
|
||
key, value = additional_usage_example
|
||
additional_usage_message = "\nAfter interacting with a module you can manually set a #{key} with %grnset #{key} '#{value}'%clr"
|
||
end
|
||
print("Interact with a module by name or index. For example %grn#{index_info}%clr, %grn#{index_usage}%clr or %grn#{name_usage}%clr#{additional_usage_message}\n\n")
|
||
end
|
||
|
||
#
|
||
# Displays information about one or more module.
|
||
#
|
||
def cmd_info(*args)
|
||
dump_json = false
|
||
show_doc = false
|
||
|
||
if args.include?('-j')
|
||
args.delete('-j')
|
||
dump_json = true
|
||
end
|
||
|
||
if args.include?('-d')
|
||
args.delete('-d')
|
||
show_doc = true
|
||
end
|
||
|
||
if (args.length == 0)
|
||
if active_module
|
||
print_module_info(active_module, dump_json: dump_json, show_doc: show_doc)
|
||
return true
|
||
else
|
||
cmd_info_help
|
||
return false
|
||
end
|
||
elsif args.include? '-h'
|
||
cmd_info_help
|
||
return false
|
||
end
|
||
|
||
args.each do |arg|
|
||
mod_name = arg
|
||
|
||
additional_datastore_values = nil
|
||
|
||
# Use a module by search index
|
||
index_from_list(@module_search_results_with_usage_metadata, mod_name) do |result|
|
||
mod = result&.[](:mod)
|
||
next unless mod && mod.respond_to?(:fullname)
|
||
|
||
# Module cache object
|
||
mod_name = mod.fullname
|
||
additional_datastore_values = result[:datastore]
|
||
end
|
||
|
||
# Ensure we have a reference name and not a path
|
||
name = trim_path(mod_name, 'modules')
|
||
|
||
# Creates an instance of the module
|
||
mod = framework.modules.create(name)
|
||
|
||
# If any additional datastore values were provided, set these values
|
||
mod.datastore.update(additional_datastore_values) unless additional_datastore_values.nil?
|
||
|
||
if mod.nil?
|
||
print_error("Invalid module: #{name}")
|
||
else
|
||
print_module_info(mod, dump_json: dump_json, show_doc: show_doc)
|
||
end
|
||
end
|
||
end
|
||
|
||
def cmd_options_help
|
||
print_line 'Usage: options [mod1 mod2 ...]'
|
||
print_line
|
||
print_line 'Queries the supplied module or modules for options. If no module is given,'
|
||
print_line 'show options for the currently active module.'
|
||
print_line
|
||
end
|
||
|
||
def cmd_options(*args)
|
||
if args.empty?
|
||
if (active_module)
|
||
show_options(active_module)
|
||
return true
|
||
else
|
||
show_global_options
|
||
return true
|
||
end
|
||
end
|
||
|
||
args.each do |name|
|
||
mod = framework.modules.create(name)
|
||
|
||
if (mod == nil)
|
||
print_error("Invalid module: #{name}")
|
||
else
|
||
show_options(mod)
|
||
end
|
||
end
|
||
end
|
||
|
||
#
|
||
# Tab completion for the advanced command (same as use)
|
||
#
|
||
# @param str (see #cmd_use_tabs)
|
||
# @param words (see #cmd_use_tabs)
|
||
|
||
def cmd_advanced_tabs(str, words)
|
||
cmd_use_tabs(str, words)
|
||
end
|
||
|
||
#
|
||
# Tab completion for the advanced command (same as use)
|
||
#
|
||
# @param str (see #cmd_use_tabs)
|
||
# @param words (see #cmd_use_tabs)
|
||
|
||
def cmd_info_tabs(str, words)
|
||
cmd_use_tabs(str, words)
|
||
end
|
||
|
||
#
|
||
# Tab completion for the advanced command (same as use)
|
||
#
|
||
# @param str (see #cmd_use_tabs)
|
||
# @param words (see #cmd_use_tabs)
|
||
|
||
def cmd_options_tabs(str, words)
|
||
cmd_use_tabs(str, words)
|
||
end
|
||
|
||
def cmd_loadpath_help
|
||
print_line "Usage: loadpath </path/to/modules>"
|
||
print_line
|
||
print_line "Loads modules from the given directory which should contain subdirectories for"
|
||
print_line "module types, e.g. /path/to/modules/exploits"
|
||
print_line
|
||
end
|
||
|
||
#
|
||
# Adds one or more search paths.
|
||
#
|
||
def cmd_loadpath(*args)
|
||
if (args.length == 0 or args.include? "-h")
|
||
cmd_loadpath_help
|
||
return true
|
||
end
|
||
|
||
totals = {}
|
||
overall = 0
|
||
curr_path = nil
|
||
|
||
begin
|
||
# Walk the list of supplied search paths attempting to add each one
|
||
# along the way
|
||
args.each { |path|
|
||
curr_path = path
|
||
|
||
# Load modules, but do not consult the cache
|
||
if (counts = framework.modules.add_module_path(path))
|
||
counts.each_pair { |type, count|
|
||
totals[type] = (totals[type]) ? (totals[type] + count) : count
|
||
|
||
overall += count
|
||
}
|
||
end
|
||
}
|
||
rescue NameError, RuntimeError
|
||
log_error("Failed to add search path #{curr_path}: #{$!}")
|
||
return true
|
||
end
|
||
|
||
added = "Loaded #{overall} modules:\n"
|
||
|
||
totals.sort_by { |type, _count| type }.each { |type, count|
|
||
added << " #{count} #{type} modules\n"
|
||
}
|
||
|
||
print(added)
|
||
end
|
||
|
||
#
|
||
# Tab completion for the loadpath 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_loadpath_tabs(str, words)
|
||
return [] if words.length > 1
|
||
|
||
# This custom completion might better than Readline's... We'll leave it for now.
|
||
#tab_complete_filenames(str,words)
|
||
|
||
paths = []
|
||
if (File.directory?(str))
|
||
paths = Dir.entries(str)
|
||
paths = paths.map { |f|
|
||
if File.directory? File.join(str,f)
|
||
File.join(str,f)
|
||
end
|
||
}
|
||
paths.delete_if { |f| f.nil? or File.basename(f) == '.' or File.basename(f) == '..' }
|
||
else
|
||
d = Dir.glob(str + "*").map { |f| f if File.directory?(f) }
|
||
d.delete_if { |f| f.nil? or f == '.' or f == '..' }
|
||
# If there's only one possibility, descend to the next level
|
||
if (1 == d.length)
|
||
paths = Dir.entries(d[0])
|
||
paths = paths.map { |f|
|
||
if File.directory? File.join(d[0],f)
|
||
File.join(d[0],f)
|
||
end
|
||
}
|
||
paths.delete_if { |f| f.nil? or File.basename(f) == '.' or File.basename(f) == '..' }
|
||
else
|
||
paths = d
|
||
end
|
||
end
|
||
paths.sort!
|
||
return paths
|
||
end
|
||
|
||
def cmd_search_help
|
||
print_line "Usage: search [<options>] [<keywords>:<value>]"
|
||
print_line
|
||
print_line "Prepending a value with '-' will exclude any matching results."
|
||
print_line "If no options or keywords are provided, cached results are displayed."
|
||
print_line
|
||
print @@search_opts.usage
|
||
print_line
|
||
print_line "Keywords:"
|
||
{
|
||
'adapter' => 'Modules with a matching adater reference name',
|
||
'aka' => 'Modules with a matching AKA (also-known-as) name',
|
||
'author' => 'Modules written by this author',
|
||
'arch' => 'Modules affecting this architecture',
|
||
'bid' => 'Modules with a matching Bugtraq ID',
|
||
'cve' => 'Modules with a matching CVE ID',
|
||
'edb' => 'Modules with a matching Exploit-DB ID',
|
||
'check' => 'Modules that support the \'check\' method',
|
||
'date' => 'Modules with a matching disclosure date',
|
||
'description' => 'Modules with a matching description',
|
||
'fullname' => 'Modules with a matching full name',
|
||
'mod_time' => 'Modules with a matching modification date',
|
||
'name' => 'Modules with a matching descriptive name',
|
||
'path' => 'Modules with a matching path',
|
||
'platform' => 'Modules affecting this platform',
|
||
'port' => 'Modules with a matching port',
|
||
'rank' => 'Modules with a matching rank (Can be descriptive (ex: \'good\') or numeric with comparison operators (ex: \'gte400\'))',
|
||
'ref' => 'Modules with a matching ref',
|
||
'reference' => 'Modules with a matching reference',
|
||
'session_type' => 'Modules with a matching session type (SMB, MySQL, Meterpreter, etc)',
|
||
'stage' => 'Modules with a matching stage reference name',
|
||
'stager' => 'Modules with a matching stager reference name',
|
||
'target' => 'Modules affecting this target',
|
||
'type' => 'Modules of a specific type (exploit, payload, auxiliary, encoder, evasion, post, or nop)',
|
||
'action' => 'Modules with a matching action name or description',
|
||
}.each_pair do |keyword, description|
|
||
print_line " #{keyword.ljust 17}: #{description}"
|
||
end
|
||
print_line
|
||
print_line "Supported search columns:"
|
||
{
|
||
'rank' => 'Sort modules by their exploitability rank',
|
||
'date' => 'Sort modules by their disclosure date. Alias for disclosure_date',
|
||
'disclosure_date' => 'Sort modules by their disclosure date',
|
||
'name' => 'Sort modules by their name',
|
||
'type' => 'Sort modules by their type',
|
||
'check' => 'Sort modules by whether or not they have a check method',
|
||
'action' => 'Sort modules by whether or not they have actions',
|
||
}.each_pair do |keyword, description|
|
||
print_line " #{keyword.ljust 17}: #{description}"
|
||
end
|
||
print_line
|
||
print_line "Examples:"
|
||
print_line " search cve:2009 type:exploit"
|
||
print_line " search cve:2009 type:exploit platform:-linux"
|
||
print_line " search cve:2009 -s name"
|
||
print_line " search type:exploit -s type -r"
|
||
print_line
|
||
end
|
||
|
||
#
|
||
# Searches modules for specific keywords
|
||
#
|
||
def cmd_search(*args)
|
||
match = ''
|
||
row_filter = nil
|
||
output_file = nil
|
||
cached = false
|
||
use = false
|
||
count = -1
|
||
search_terms = []
|
||
sort_attribute = 'name'
|
||
valid_sort_attributes = ['action', 'rank','disclosure_date','name','date','type','check']
|
||
reverse_sort = false
|
||
ignore_use_exact_match = false
|
||
|
||
@@search_opts.parse(args) do |opt, idx, val|
|
||
case opt
|
||
when '-S'
|
||
row_filter = val
|
||
when '-h'
|
||
cmd_search_help
|
||
return false
|
||
when '-o'
|
||
output_file = val
|
||
when '-u'
|
||
use = true
|
||
when '-I'
|
||
ignore_use_exact_match = true
|
||
when '-s'
|
||
sort_attribute = val
|
||
when '-r'
|
||
reverse_sort = true
|
||
else
|
||
match += val + ' '
|
||
end
|
||
end
|
||
|
||
if args.empty?
|
||
if @module_search_results_with_usage_metadata.empty?
|
||
cmd_search_help
|
||
return false
|
||
end
|
||
|
||
cached = true
|
||
end
|
||
|
||
if sort_attribute && !valid_sort_attributes.include?(sort_attribute)
|
||
print_error("Supported options for the -s flag are: #{valid_sort_attributes}")
|
||
return false
|
||
end
|
||
|
||
begin
|
||
if cached
|
||
print_status('Displaying cached results')
|
||
else
|
||
search_params = Msf::Modules::Metadata::Search.parse_search_string(match)
|
||
@module_search_results = Msf::Modules::Metadata::Cache.instance.find(search_params)
|
||
|
||
@module_search_results.sort_by! do |module_metadata|
|
||
if sort_attribute == 'action'
|
||
module_metadata.actions&.any? ? 0 : 1
|
||
elsif sort_attribute == 'check'
|
||
module_metadata.check ? 0 : 1
|
||
elsif sort_attribute == 'disclosure_date' || sort_attribute == 'date'
|
||
# Not all modules have disclosure_date, i.e. multi/handler
|
||
module_metadata.disclosure_date || Time.utc(0)
|
||
else
|
||
module_metadata.send(sort_attribute)
|
||
end
|
||
end
|
||
|
||
if reverse_sort
|
||
@module_search_results.reverse!
|
||
end
|
||
end
|
||
|
||
if @module_search_results.empty?
|
||
print_error('No results from search')
|
||
return false
|
||
end
|
||
|
||
if ignore_use_exact_match && @module_search_results.length == 1 &&
|
||
@module_search_results.first.fullname == match.strip
|
||
return false
|
||
end
|
||
|
||
if !search_params.nil? && !search_params['text'].nil?
|
||
search_params['text'][0].each do |t|
|
||
search_terms << t
|
||
end
|
||
end
|
||
|
||
# Generate the table used to display matches
|
||
tbl = generate_module_table('Matching Modules', search_terms, row_filter)
|
||
|
||
@module_search_results_with_usage_metadata = []
|
||
@module_search_results.each do |m|
|
||
@module_search_results_with_usage_metadata << { mod: m }
|
||
count += 1
|
||
tbl << [
|
||
count,
|
||
"#{m.fullname}",
|
||
m.disclosure_date.nil? ? '' : m.disclosure_date.strftime("%Y-%m-%d"),
|
||
m.rank,
|
||
m.check ? 'Yes' : 'No',
|
||
m.name,
|
||
]
|
||
|
||
if framework.features.enabled?(Msf::FeatureManager::HIERARCHICAL_SEARCH_TABLE)
|
||
total_children_rows = (m.actions&.length || 0) + (m.targets&.length || 0) + (m.notes&.[]('AKA')&.length || 0)
|
||
show_child_items = total_children_rows > 1
|
||
next unless show_child_items
|
||
|
||
indent = " \\_ "
|
||
# Note: We still use visual indicators for blank values as it's easier to read
|
||
# We can't always use a generic formatter/styler, as it would be applied to the 'parent' rows too
|
||
blank_value = '.'
|
||
if (m.actions&.length || 0) > 1
|
||
m.actions.each do |action|
|
||
@module_search_results_with_usage_metadata << { mod: m, datastore: { 'ACTION' => action['name'] } }
|
||
count += 1
|
||
tbl << [
|
||
count,
|
||
"#{indent}action: #{action['name']}",
|
||
blank_value,
|
||
blank_value,
|
||
blank_value,
|
||
action['description'],
|
||
]
|
||
end
|
||
end
|
||
|
||
if (m.targets&.length || 0) > 1
|
||
m.targets.each do |target|
|
||
@module_search_results_with_usage_metadata << { mod: m, datastore: { 'TARGET' => target } }
|
||
count += 1
|
||
tbl << [
|
||
count,
|
||
"#{indent}target: #{target}",
|
||
blank_value,
|
||
blank_value,
|
||
blank_value,
|
||
blank_value
|
||
]
|
||
end
|
||
end
|
||
|
||
if (m.notes&.[]('AKA')&.length || 0) > 1
|
||
m.notes['AKA'].each do |aka|
|
||
@module_search_results_with_usage_metadata << { mod: m }
|
||
count += 1
|
||
tbl << [
|
||
count,
|
||
"#{indent}AKA: #{aka}",
|
||
blank_value,
|
||
blank_value,
|
||
blank_value,
|
||
blank_value
|
||
]
|
||
end
|
||
end
|
||
end
|
||
end
|
||
rescue ArgumentError
|
||
print_error("Invalid argument(s)\n")
|
||
cmd_search_help
|
||
return false
|
||
end
|
||
|
||
if output_file
|
||
print_status("Wrote search results to #{output_file}")
|
||
::File.open(output_file, "wb") { |ofd|
|
||
ofd.write(tbl.to_csv)
|
||
}
|
||
return true
|
||
end
|
||
|
||
print_line(tbl.to_s)
|
||
print_module_search_results_usage
|
||
|
||
if @module_search_results.length == 1 && use
|
||
used_module = @module_search_results_with_usage_metadata.first[:mod].fullname
|
||
print_status("Using #{used_module}") if used_module
|
||
cmd_use(used_module, true)
|
||
end
|
||
|
||
true
|
||
end
|
||
|
||
#
|
||
# Tab completion for the search 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_search_tabs(str, words)
|
||
if words.length == 1
|
||
return @@search_opts.option_keys
|
||
end
|
||
|
||
[]
|
||
end
|
||
|
||
def cmd_show_help
|
||
global_opts = %w{all encoders nops exploits payloads auxiliary post plugins info options favorites}
|
||
print_status("Valid parameters for the \"show\" command are: #{global_opts.join(", ")}")
|
||
|
||
module_opts = %w{ missing advanced evasion targets actions }
|
||
print_status("Additional module-specific parameters are: #{module_opts.join(", ")}")
|
||
end
|
||
|
||
#
|
||
# Displays the list of modules based on their type, or all modules if
|
||
# no type is provided.
|
||
#
|
||
def cmd_show(*args)
|
||
if args.empty?
|
||
print_error("Argument required\n")
|
||
cmd_show_help
|
||
return
|
||
end
|
||
|
||
mod = self.active_module
|
||
|
||
args.each { |type|
|
||
case type
|
||
when '-h'
|
||
cmd_show_help
|
||
when 'all'
|
||
show_encoders
|
||
show_nops
|
||
show_exploits
|
||
show_payloads
|
||
show_auxiliary
|
||
show_post
|
||
show_plugins
|
||
when 'encoders'
|
||
show_encoders
|
||
when 'nops'
|
||
show_nops
|
||
when 'exploits'
|
||
show_exploits
|
||
when 'payloads'
|
||
show_payloads
|
||
when 'auxiliary'
|
||
show_auxiliary
|
||
when 'post'
|
||
show_post
|
||
when 'favorites'
|
||
show_favorites
|
||
when 'info'
|
||
cmd_info(*args[1, args.length])
|
||
when 'options'
|
||
if (mod)
|
||
show_options(mod)
|
||
else
|
||
show_global_options
|
||
end
|
||
when 'missing'
|
||
if (mod)
|
||
show_missing(mod)
|
||
else
|
||
print_error("No module selected.")
|
||
end
|
||
when 'advanced'
|
||
if (mod)
|
||
show_advanced_options(mod)
|
||
else
|
||
print_error("No module selected.")
|
||
end
|
||
when 'evasion'
|
||
if (mod)
|
||
show_evasion_options(mod)
|
||
else
|
||
show_evasion
|
||
end
|
||
when 'sessions'
|
||
if (active_module and active_module.respond_to?(:compatible_sessions))
|
||
sessions = active_module.compatible_sessions
|
||
else
|
||
sessions = framework.sessions.keys.sort
|
||
end
|
||
print_line
|
||
print(Serializer::ReadableText.dump_sessions(framework, :session_ids => sessions))
|
||
print_line
|
||
when "plugins"
|
||
show_plugins
|
||
when "targets"
|
||
if (mod and (mod.exploit? or mod.evasion?))
|
||
show_targets(mod)
|
||
else
|
||
print_error("No exploit module selected.")
|
||
end
|
||
when "actions"
|
||
if mod && mod.kind_of?(Msf::Module::HasActions)
|
||
show_actions(mod)
|
||
else
|
||
print_error("No module with actions selected.")
|
||
end
|
||
|
||
else
|
||
print_error("Invalid parameter \"#{type}\", use \"show -h\" for more information")
|
||
end
|
||
}
|
||
end
|
||
|
||
#
|
||
# Tab completion for the show 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_show_tabs(str, words)
|
||
return [] if words.length > 1
|
||
|
||
res = %w{all encoders nops exploits payloads auxiliary post plugins options favorites}
|
||
if (active_module)
|
||
res.concat %w{missing advanced evasion targets actions info}
|
||
if (active_module.respond_to? :compatible_sessions)
|
||
res << "sessions"
|
||
end
|
||
end
|
||
return res
|
||
end
|
||
|
||
def cmd_use_help
|
||
print_line 'Usage: use <name|term|index>'
|
||
print_line
|
||
print_line 'Interact with a module by name or search term/index.'
|
||
print_line 'If a module name is not found, it will be treated as a search term.'
|
||
print_line 'An index from the previous search results can be selected if desired.'
|
||
print_line
|
||
print_line 'Examples:'
|
||
print_line ' use exploit/windows/smb/ms17_010_eternalblue'
|
||
print_line
|
||
print_line ' use eternalblue'
|
||
print_line ' use <name|index>'
|
||
print_line
|
||
print_line ' search eternalblue'
|
||
print_line ' use <name|index>'
|
||
print_line
|
||
print_april_fools_module_use
|
||
end
|
||
|
||
#
|
||
# Uses a module.
|
||
#
|
||
def cmd_use(*args)
|
||
if args.length == 0 || args.first == '-h'
|
||
cmd_use_help
|
||
return false
|
||
end
|
||
|
||
# Divert logic for dangerzone mode
|
||
args = dangerzone_codename_to_module(args)
|
||
|
||
# Try to create an instance of the supplied module name
|
||
mod_name = args[0]
|
||
|
||
additional_datastore_values = nil
|
||
|
||
# Use a module by search index
|
||
index_from_list(@module_search_results_with_usage_metadata, mod_name) do |result|
|
||
mod = result&.[](:mod)
|
||
unless mod && mod.respond_to?(:fullname)
|
||
print_error("Invalid module index: #{mod_name}")
|
||
return false
|
||
end
|
||
|
||
# Module cache object from @module_search_results_with_usage_metadata
|
||
mod_name = mod.fullname
|
||
additional_datastore_values = result[:datastore]
|
||
end
|
||
|
||
# See if the supplied module name has already been resolved
|
||
mod_resolved = args[1] == true ? true : false
|
||
|
||
# Ensure we have a reference name and not a path
|
||
mod_name = trim_path(mod_name, "modules")
|
||
|
||
begin
|
||
mod = framework.modules.create(mod_name)
|
||
|
||
unless mod
|
||
# Checks to see if we have any load_errors for the current module.
|
||
# and if so, returns them to the user.
|
||
load_error = framework.modules.load_error_by_name(mod_name)
|
||
if load_error
|
||
print_error("Failed to load module: #{load_error}")
|
||
return false
|
||
end
|
||
unless mod_resolved
|
||
elog("Module #{mod_name} not found, and no loading errors found. If you're using a custom module" \
|
||
' refer to our wiki: https://docs.metasploit.com/docs/using-metasploit/intermediate/running-private-modules.html')
|
||
|
||
# Avoid trying to use the search result if it exactly matches
|
||
# the module we were trying to load. The module cannot be
|
||
# loaded and searching isn't going to change that.
|
||
mods_found = cmd_search('-I', '-u', *args)
|
||
end
|
||
|
||
unless mods_found
|
||
print_error("Failed to load module: #{mod_name}")
|
||
return false
|
||
end
|
||
end
|
||
rescue Rex::AmbiguousArgumentError => info
|
||
print_error(info.to_s)
|
||
rescue NameError => info
|
||
log_error("The supplied module name is ambiguous: #{$!}.")
|
||
end
|
||
|
||
return false if (mod == nil)
|
||
|
||
# Enstack the command dispatcher for this module type
|
||
dispatcher = nil
|
||
|
||
case mod.type
|
||
when Msf::MODULE_ENCODER
|
||
dispatcher = Msf::Ui::Console::CommandDispatcher::Encoder
|
||
when Msf::MODULE_EXPLOIT
|
||
dispatcher = Msf::Ui::Console::CommandDispatcher::Exploit
|
||
when Msf::MODULE_NOP
|
||
dispatcher = Msf::Ui::Console::CommandDispatcher::Nop
|
||
when Msf::MODULE_PAYLOAD
|
||
dispatcher = Msf::Ui::Console::CommandDispatcher::Payload
|
||
when Msf::MODULE_AUX
|
||
dispatcher = Msf::Ui::Console::CommandDispatcher::Auxiliary
|
||
when Msf::MODULE_POST
|
||
dispatcher = Msf::Ui::Console::CommandDispatcher::Post
|
||
when Msf::MODULE_EVASION
|
||
dispatcher = Msf::Ui::Console::CommandDispatcher::Evasion
|
||
else
|
||
print_error("Unsupported module type: #{mod.type}")
|
||
return false
|
||
end
|
||
|
||
# If there's currently an active module, enqueque it and go back
|
||
if (active_module)
|
||
@previous_module = active_module
|
||
cmd_back()
|
||
end
|
||
|
||
if (dispatcher != nil)
|
||
driver.enstack_dispatcher(dispatcher)
|
||
end
|
||
|
||
# Update the active module
|
||
self.active_module = mod
|
||
|
||
# If a datastore cache exists for this module, then load it up
|
||
if @dscache[active_module.fullname]
|
||
active_module.datastore.update(@dscache[active_module.fullname])
|
||
end
|
||
|
||
# If any additional datastore values were provided, set these values
|
||
unless additional_datastore_values.nil? || additional_datastore_values.empty?
|
||
mod.datastore.update(additional_datastore_values)
|
||
print_status("Additionally setting #{additional_datastore_values.map { |k,v| "#{k} => #{v}" }.join(", ")}")
|
||
if additional_datastore_values['TARGET'] && (mod.exploit? || mod.evasion?)
|
||
mod.import_target_defaults
|
||
end
|
||
end
|
||
|
||
# Choose a default payload when the module is used, not run
|
||
if mod.datastore['PAYLOAD']
|
||
print_status("Using configured payload #{mod.datastore['PAYLOAD']}")
|
||
elsif dispatcher.respond_to?(:choose_payload)
|
||
chosen_payload = dispatcher.choose_payload(mod)
|
||
print_status("No payload configured, defaulting to #{chosen_payload}") if chosen_payload
|
||
end
|
||
|
||
mod.init_ui(driver.input, driver.output)
|
||
end
|
||
|
||
#
|
||
# Command to take to the previously active module
|
||
#
|
||
def cmd_previous(*args)
|
||
if @previous_module
|
||
self.cmd_use(@previous_module.fullname)
|
||
else
|
||
print_error("There isn't a previous module at the moment")
|
||
end
|
||
end
|
||
|
||
#
|
||
# Help for the 'previous' command
|
||
#
|
||
def cmd_previous_help
|
||
print_line "Usage: previous"
|
||
print_line
|
||
print_line "Set the previously loaded module as the current module"
|
||
print_line
|
||
print_line "Previous module: #{@previous_module ? @previous_module.fullname : 'none'}"
|
||
print_line
|
||
end
|
||
|
||
#
|
||
# Command to enqueque a module on the module stack
|
||
#
|
||
def cmd_pushm(*args)
|
||
# could check if each argument is a valid module, but for now let them hang themselves
|
||
if args.count > 0
|
||
args.each do |arg|
|
||
@module_name_stack.push(arg)
|
||
# Note new modules are appended to the array and are only module (full)names
|
||
end
|
||
else #then just push the active module
|
||
if active_module
|
||
#print_status "Pushing the active module"
|
||
@module_name_stack.push(active_module.fullname)
|
||
else
|
||
print_error("There isn't an active module and you didn't specify a module to push")
|
||
return self.cmd_pushm_help
|
||
end
|
||
end
|
||
end
|
||
|
||
#
|
||
# Tab completion for the pushm 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_pushm_tabs(str, words)
|
||
tab_complete_module(str, words)
|
||
end
|
||
|
||
#
|
||
# Help for the 'pushm' command
|
||
#
|
||
def cmd_pushm_help
|
||
print_line "Usage: pushm [module1 [,module2, module3...]]"
|
||
print_line
|
||
print_line "push current active module or specified modules onto the module stack"
|
||
print_line
|
||
end
|
||
|
||
#
|
||
# Command to dequeque a module from the module stack
|
||
#
|
||
def cmd_popm(*args)
|
||
if (args.count > 1 or not args[0].respond_to?("to_i"))
|
||
return self.cmd_popm_help
|
||
elsif args.count == 1
|
||
# then pop 'n' items off the stack, but don't change the active module
|
||
if args[0].to_i >= @module_name_stack.count
|
||
# in case they pass in a number >= the length of @module_name_stack
|
||
@module_name_stack = []
|
||
print_status("The module stack is empty")
|
||
else
|
||
@module_name_stack.pop(args[0].to_i)
|
||
end
|
||
else #then just pop the array and make that the active module
|
||
pop = @module_name_stack.pop
|
||
if pop
|
||
return self.cmd_use(pop)
|
||
else
|
||
print_error("There isn't anything to pop, the module stack is empty")
|
||
end
|
||
end
|
||
end
|
||
|
||
#
|
||
# Help for the 'popm' command
|
||
#
|
||
def cmd_popm_help
|
||
print_line "Usage: popm [n]"
|
||
print_line
|
||
print_line "pop the latest module off of the module stack and make it the active module"
|
||
print_line "or pop n modules off the stack, but don't change the active module"
|
||
print_line
|
||
end
|
||
|
||
def cmd_listm_help
|
||
print_line 'Usage: listm'
|
||
print_line
|
||
print_line 'List the module stack'
|
||
print_line
|
||
end
|
||
|
||
def cmd_listm(*_args)
|
||
if @module_name_stack.empty?
|
||
print_error('The module stack is empty')
|
||
return
|
||
end
|
||
|
||
print_status("Module stack:\n")
|
||
|
||
@module_name_stack.to_enum.with_index.reverse_each do |name, idx|
|
||
print_line("[#{idx}]\t#{name}")
|
||
end
|
||
end
|
||
|
||
def cmd_clearm_help
|
||
print_line 'Usage: clearm'
|
||
print_line
|
||
print_line 'Clear the module stack'
|
||
print_line
|
||
end
|
||
|
||
def cmd_clearm(*_args)
|
||
print_status('Clearing the module stack')
|
||
@module_name_stack.clear
|
||
end
|
||
|
||
#
|
||
# Tab completion for the use 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_use_tabs(str, words)
|
||
return [] if words.length > 1
|
||
|
||
tab_complete_module(str, words)
|
||
end
|
||
|
||
def cmd_reload_all_help
|
||
print_line "Usage: reload_all"
|
||
print_line
|
||
print_line "Reload all modules from all configured module paths. This may take awhile."
|
||
print_line "See also: loadpath"
|
||
print_line
|
||
end
|
||
|
||
#
|
||
# Reload all module paths that we are aware of
|
||
#
|
||
def cmd_reload_all(*args)
|
||
if args.length > 0
|
||
cmd_reload_all_help
|
||
return
|
||
end
|
||
|
||
print_status("Reloading modules from all module paths...")
|
||
framework.modules.reload_modules
|
||
|
||
log_msg = "Please see #{File.join(Msf::Config.log_directory, 'framework.log')} for details."
|
||
|
||
# Check for modules that failed to load
|
||
if framework.modules.module_load_error_by_path.length > 0
|
||
wlog("WARNING! The following modules could not be loaded!")
|
||
|
||
framework.modules.module_load_error_by_path.each do |path, _error|
|
||
wlog("\t#{path}")
|
||
end
|
||
|
||
wlog(log_msg)
|
||
end
|
||
|
||
if framework.modules.module_load_warnings.length > 0
|
||
wlog("The following modules were loaded with warnings:")
|
||
|
||
framework.modules.module_load_warnings.each do |path, _error|
|
||
wlog("\t#{path}")
|
||
end
|
||
|
||
wlog(log_msg)
|
||
end
|
||
|
||
self.driver.run_single('reload')
|
||
self.driver.run_single("banner")
|
||
end
|
||
|
||
def cmd_back_help
|
||
print_line "Usage: back"
|
||
print_line
|
||
print_line "Return to the global dispatcher context"
|
||
print_line
|
||
end
|
||
|
||
#
|
||
# Pop the current dispatcher stack context, assuming it isn't pointed at
|
||
# the core or database backend stack context.
|
||
#
|
||
def cmd_back(*args)
|
||
if (driver.dispatcher_stack.size > 1 and
|
||
driver.current_dispatcher.name != 'Core' and
|
||
driver.current_dispatcher.name != 'Database Backend')
|
||
# Reset the active module if we have one
|
||
if (active_module)
|
||
|
||
# Do NOT reset the UI anymore
|
||
# active_module.reset_ui
|
||
|
||
# Save the module's datastore so that we can load it later
|
||
# if the module is used again
|
||
@dscache[active_module.fullname] = active_module.datastore.dup
|
||
|
||
self.active_module = nil
|
||
end
|
||
|
||
# Destack the current dispatcher
|
||
driver.destack_dispatcher
|
||
end
|
||
end
|
||
|
||
def cmd_favorite_help
|
||
print_line 'Usage: favorite [mod1 mod2 ...]'
|
||
print_line
|
||
print_line "Add one or multiple modules to the list of favorite modules stored in #{Msf::Config.fav_modules_file}"
|
||
print_line 'If no module name is specified, the command will add the active module if there is one'
|
||
print @@favorite_opts.usage
|
||
end
|
||
|
||
#
|
||
# Helper method for cmd_favorite that writes modules to the fav_modules_file
|
||
#
|
||
def favorite_add(modules, favs_file)
|
||
fav_limit = 50
|
||
# obtain useful info about the fav_modules file
|
||
exists, writable, readable, contents = favorite_check_fav_modules(favs_file)
|
||
|
||
# if the fav_modules file exists, check the file permissions
|
||
if exists
|
||
case
|
||
when !writable
|
||
print_error("Unable to save module(s) to the favorite modules file because it is not writable")
|
||
return
|
||
when !readable
|
||
print_error("Unable to save module(s) to the favorite modules file because it is not readable")
|
||
return
|
||
end
|
||
end
|
||
|
||
fav_count = 0
|
||
if contents
|
||
fav_count = contents.split.size
|
||
end
|
||
|
||
modules = modules.uniq # prevent modules from being added more than once
|
||
modules.each do |name|
|
||
mod = framework.modules.create(name)
|
||
if (mod == nil)
|
||
print_error("Invalid module: #{name}")
|
||
next
|
||
end
|
||
|
||
if contents && contents.include?(mod.fullname)
|
||
print_warning("Module #{mod.fullname} has already been favorited and will not be added to the favorite modules file")
|
||
next
|
||
end
|
||
|
||
if fav_count >= fav_limit
|
||
print_error("Favorite module limit (#{fav_limit}) exceeded. No more modules will be added.")
|
||
return
|
||
end
|
||
|
||
File.open(favs_file, 'a+') { |file| file.puts(mod.fullname) }
|
||
print_good("Added #{mod.fullname} to the favorite modules file")
|
||
fav_count += 1
|
||
end
|
||
return
|
||
end
|
||
|
||
#
|
||
# Helper method for cmd_favorite that deletes modules from the fav_modules_file
|
||
#
|
||
def favorite_del(modules, delete_all, favs_file)
|
||
# obtain useful info about the fav_modules file
|
||
exists, writable, readable, contents = favorite_check_fav_modules(favs_file)
|
||
|
||
if delete_all
|
||
custom_message = 'clear the contents of'
|
||
else
|
||
custom_message = 'delete module(s) from'
|
||
end
|
||
|
||
case # error handling based on the existence / permissions of the fav_modules file
|
||
when !exists
|
||
print_warning("Unable to #{custom_message} the favorite modules file because it does not exist")
|
||
return
|
||
when !writable
|
||
print_error("Unable to #{custom_message} the favorite modules file because it is not writable")
|
||
return
|
||
when !readable
|
||
unless delete_all
|
||
print_error("Unable to #{custom_message} the favorite modules file because it is not readable")
|
||
return
|
||
end
|
||
when contents.empty?
|
||
print_warning("Unable to #{custom_message} the favorite modules file because it is already empty")
|
||
return
|
||
end
|
||
|
||
if delete_all
|
||
File.write(favs_file, '')
|
||
print_good("Favorite modules file cleared")
|
||
return
|
||
end
|
||
|
||
modules = modules.uniq # prevent modules from being deleted more than once
|
||
contents = contents.split
|
||
modules.each do |name|
|
||
mod = framework.modules.create(name)
|
||
if (mod == nil)
|
||
print_error("Invalid module: #{name}")
|
||
next
|
||
end
|
||
|
||
unless contents.include?(mod.fullname)
|
||
print_warning("Module #{mod.fullname} cannot be deleted because it is not in the favorite modules file")
|
||
next
|
||
end
|
||
|
||
contents.delete(mod.fullname)
|
||
print_status("Removing #{mod.fullname} from the favorite modules file")
|
||
end
|
||
|
||
# clear the contents of the fav_modules file if removing the module(s) makes it empty
|
||
if contents.length == 0
|
||
File.write(favs_file, '')
|
||
return
|
||
end
|
||
|
||
File.open(favs_file, 'w') { |file| file.puts(contents.join("\n")) }
|
||
end
|
||
|
||
#
|
||
# Helper method for cmd_favorite that checks if the fav_modules file exists and is readable / writable
|
||
#
|
||
def favorite_check_fav_modules(favs_file)
|
||
exists = false
|
||
writable = false
|
||
readable = false
|
||
contents = ''
|
||
|
||
if File.exist?(favs_file)
|
||
exists = true
|
||
end
|
||
|
||
if File.writable?(favs_file)
|
||
writable = true
|
||
end
|
||
|
||
if File.readable?(favs_file)
|
||
readable = true
|
||
contents = File.read(favs_file)
|
||
end
|
||
|
||
return exists, writable, readable, contents
|
||
end
|
||
|
||
#
|
||
# Add modules to or delete modules from the fav_modules file
|
||
#
|
||
def cmd_favorite(*args)
|
||
valid_custom_args = ['-c', '-d', '-l']
|
||
favs_file = Msf::Config.fav_modules_file
|
||
|
||
# always display the help banner if -h is provided or if multiple options are provided
|
||
if args.include?('-h') || args.select{ |arg| arg if valid_custom_args.include?(arg) }.length > 1
|
||
cmd_favorite_help
|
||
return
|
||
end
|
||
|
||
# if no arguments were provided, check if there is an active module to add
|
||
if args.empty?
|
||
unless active_module
|
||
print_error('No module has been provided to favorite.')
|
||
cmd_favorite_help
|
||
return
|
||
end
|
||
|
||
args = [active_module.fullname]
|
||
favorite_add(args, favs_file)
|
||
return
|
||
end
|
||
|
||
case args[0]
|
||
when '-c'
|
||
args.delete('-c')
|
||
unless args.empty?
|
||
print_error('Option `-c` does not support arguments.')
|
||
cmd_favorite_help
|
||
return
|
||
end
|
||
|
||
favorite_del(args, true, favs_file)
|
||
when '-d'
|
||
args.delete('-d')
|
||
if args.empty?
|
||
unless active_module
|
||
print_error('No module has been provided to delete.')
|
||
cmd_favorite_help
|
||
return
|
||
end
|
||
|
||
args = [active_module.fullname]
|
||
end
|
||
|
||
favorite_del(args, false, favs_file)
|
||
when '-l'
|
||
args.delete('-l')
|
||
unless args.empty?
|
||
print_error('Option `-l` does not support arguments.')
|
||
cmd_favorite_help
|
||
return
|
||
end
|
||
cmd_show('favorites')
|
||
else # no valid options, but there are arguments
|
||
if args[0].start_with?('-')
|
||
print_error('Invalid option provided')
|
||
cmd_favorite_help
|
||
return
|
||
end
|
||
|
||
favorite_add(args, favs_file)
|
||
end
|
||
end
|
||
|
||
def cmd_favorites_help
|
||
print_line 'Usage: favorites'
|
||
print_line
|
||
print_line 'Print the list of favorite modules (alias for `show favorites`)'
|
||
print_line 'You can use the %grnfavorite%clr command to add the current module to your favorites list'
|
||
print @@favorites_opts.usage
|
||
end
|
||
|
||
#
|
||
# Print the list of favorite modules from the fav_modules file (alias for `show favorites`)
|
||
#
|
||
def cmd_favorites(*args)
|
||
if args.empty?
|
||
cmd_show('favorites')
|
||
return
|
||
end
|
||
|
||
# always display the help banner if the command is called with arguments
|
||
unless args.include?('-h')
|
||
print_error('Invalid option(s) provided')
|
||
end
|
||
|
||
cmd_favorites_help
|
||
end
|
||
|
||
#
|
||
# Tab complete module names
|
||
#
|
||
def tab_complete_module(str, words)
|
||
res = []
|
||
module_metadata = Msf::Modules::Metadata::Cache.instance.get_metadata
|
||
module_metadata.each do |m|
|
||
res << "#{m.type}/#{m.ref_name}"
|
||
end
|
||
framework.modules.module_types.each do |mtyp|
|
||
mset = framework.modules.module_names(mtyp)
|
||
mset.each do |mref|
|
||
res << mtyp + '/' + mref
|
||
end
|
||
end
|
||
|
||
return dangerzone_modules_to_codenames(res.sort) if dangerzone_active?
|
||
return res.uniq.sort
|
||
end
|
||
|
||
def print_april_fools_module_use
|
||
return unless ENV['APRILFOOLSMODULEUSE'] || Time.now.strftime("%m%d") == "0401"
|
||
|
||
banner = Msf::Ui::Banner.readfile('help-using-a-module.txt')
|
||
print_line("%grn#{banner}%clr")
|
||
end
|
||
|
||
#
|
||
# Convert squirrel names back to regular module names
|
||
#
|
||
def dangerzone_codename_to_module(args)
|
||
return args unless dangerzone_active? && args.length > 0 && args[0].length > 0
|
||
return args unless args[0] =~ /^[A-Z]/
|
||
args[0] = dangerzone_codename_to_module_name(args[0])
|
||
args
|
||
end
|
||
|
||
#
|
||
# Determine if dangerzone mode is active via date or environment variable
|
||
#
|
||
def dangerzone_active?
|
||
active = Time.now.strftime("%m%d") == "0401" || Rex::Compat.getenv('DANGERZONE').to_i > 0
|
||
if active && @dangerzone_map.nil?
|
||
dangerzone_build_map
|
||
end
|
||
active
|
||
end
|
||
|
||
#
|
||
# Convert module names to squirrel names
|
||
#
|
||
def dangerzone_modules_to_codenames(names)
|
||
(names + @dangerzone_map.keys.grep(/^[A-Z]+/)).sort
|
||
end
|
||
|
||
def dangerzone_codename_to_module_name(cname)
|
||
@dangerzone_map[cname] || cname
|
||
end
|
||
|
||
def dangerzone_module_name_to_codename(mname)
|
||
@dangerzone_map[mname] || mname
|
||
end
|
||
|
||
def dangerzone_build_map
|
||
return unless @dangerzone_map.nil?
|
||
|
||
@dangerzone_map = {}
|
||
|
||
res = []
|
||
%W{exploit auxiliary}.each do |mtyp|
|
||
mset = framework.modules.module_names(mtyp)
|
||
mset.each do |mref|
|
||
res << mtyp + '/' + mref
|
||
end
|
||
end
|
||
|
||
words_a = ::File.readlines(::File.join(
|
||
::Msf::Config.data_directory, "wordlists", "dangerzone_a.txt"
|
||
)).map{|line| line.strip.upcase}
|
||
|
||
words_b = ::File.readlines(::File.join(
|
||
::Msf::Config.data_directory, "wordlists", "dangerzone_b.txt"
|
||
)).map{|line| line.strip.upcase}
|
||
|
||
aidx = -1
|
||
bidx = -1
|
||
|
||
res.sort.each do |mname|
|
||
word_a = words_a[ (aidx += 1) % words_a.length ]
|
||
word_b = words_b[ (bidx += 1) % words_b.length ]
|
||
cname = word_a + word_b
|
||
|
||
while @dangerzone_map[cname]
|
||
aidx += 1
|
||
word_a = words_a[ (aidx += 1) % words_a.length ]
|
||
cname = word_a + word_b
|
||
end
|
||
|
||
@dangerzone_map[mname] = cname
|
||
@dangerzone_map[cname] = mname
|
||
end
|
||
end
|
||
|
||
#
|
||
# Module list enumeration
|
||
#
|
||
|
||
def show_encoders # :nodoc:
|
||
# If an active module has been selected and it's an exploit, get the
|
||
# list of compatible encoders and display them
|
||
if (active_module and active_module.exploit? == true)
|
||
show_module_metadata('Compatible Encoders', active_module.compatible_encoders)
|
||
else
|
||
show_module_metadata('Encoders', 'encoder')
|
||
end
|
||
end
|
||
|
||
def show_nops # :nodoc:
|
||
show_module_metadata('NOP Generators', 'nop')
|
||
end
|
||
|
||
def show_exploits # :nodoc:
|
||
show_module_metadata('Exploits', 'exploit')
|
||
end
|
||
|
||
def show_payloads # :nodoc:
|
||
# If an active module has been selected and it's an exploit, get the
|
||
# list of compatible payloads and display them
|
||
if active_module && (active_module.exploit? || active_module.evasion?)
|
||
@@payload_show_results = active_module.compatible_payloads
|
||
|
||
show_module_metadata('Compatible Payloads', @@payload_show_results)
|
||
else
|
||
# show_module_set(‘Payloads’, framework.payloads, regex, minrank, opts)
|
||
show_module_metadata('Payloads', 'payload')
|
||
end
|
||
end
|
||
|
||
def show_auxiliary # :nodoc:
|
||
show_module_metadata('Auxiliary','auxiliary')
|
||
end
|
||
|
||
def show_post # :nodoc:
|
||
show_module_metadata('Post','post')
|
||
end
|
||
|
||
def show_evasion # :nodoc:
|
||
show_module_metadata('Evasion','evasion')
|
||
end
|
||
|
||
def show_favorites # :nodoc:
|
||
favs_file = Msf::Config.fav_modules_file
|
||
|
||
unless File.exist?(favs_file)
|
||
print_error("The favorite modules file does not exist")
|
||
return
|
||
end
|
||
|
||
if File.zero?(favs_file)
|
||
print_warning("The favorite modules file is empty")
|
||
return
|
||
end
|
||
|
||
unless File.readable?(favs_file)
|
||
print_error("Unable to read from #{favs_file}")
|
||
return
|
||
end
|
||
|
||
# create module set using the saved modules
|
||
fav_modules = {}
|
||
|
||
# get the full module names from the favorites file and use then to search the MetaData Cache for matching modules
|
||
saved_favs = File.readlines(favs_file).map(&:strip)
|
||
saved_favs.each do |mod|
|
||
# populate hash with module fullname and module object
|
||
fav_modules[mod] = framework.modules[mod]
|
||
end
|
||
|
||
fav_modules.each do |fullname, mod_obj|
|
||
if mod_obj.nil?
|
||
print_warning("#{favs_file} contains a module that can not be found - #{fullname}.")
|
||
end
|
||
end
|
||
|
||
# find cache module instance and add it to @module_search_results
|
||
@module_search_results = Msf::Modules::Metadata::Cache.instance.find('fullname' => [saved_favs, []])
|
||
|
||
# This scenario is for when a module fullname is a substring of other module fullnames
|
||
# Example, searching for the payload/windows/meterpreter/reverse_tcp module can result in matches for:
|
||
# - windows/meterpreter/reverse_tcp_allports
|
||
# - windows/meterpreter/reverse_tcp_dns
|
||
# So if @module_search_results is greater than the amount of fav_modules, we need to filter the results to be more accurate
|
||
if fav_modules.length < @module_search_results.length
|
||
filtered_results = []
|
||
fav_modules.each do |fullname, _mod_obj|
|
||
filtered_results << @module_search_results.select do |search_result|
|
||
search_result.fullname == fullname
|
||
end
|
||
end
|
||
@module_search_results = filtered_results.flatten.sort_by(&:fullname)
|
||
end
|
||
@module_search_results_with_usage_metadata = @module_search_results.map { |mod| { mod: mod, datastore: {} } }
|
||
|
||
show_module_metadata('Favorites', fav_modules)
|
||
print_module_search_results_usage
|
||
end
|
||
|
||
def show_missing(mod) # :nodoc:
|
||
mod_opt = Serializer::ReadableText.dump_options(mod, ' ', true)
|
||
print("\nModule options (#{mod.fullname}):\n\n#{mod_opt}\n") if (mod_opt and mod_opt.length > 0)
|
||
|
||
# If it's an exploit and a payload is defined, create it and
|
||
# display the payload's options
|
||
if (mod.exploit? and mod.datastore['PAYLOAD'])
|
||
p = framework.payloads.create(mod.datastore['PAYLOAD'])
|
||
|
||
if (!p)
|
||
print_error("Invalid payload defined: #{mod.datastore['PAYLOAD']}\n")
|
||
return
|
||
end
|
||
|
||
p.share_datastore(mod.datastore)
|
||
|
||
if (p)
|
||
p_opt = Serializer::ReadableText.dump_options(p, ' ', true)
|
||
print("\nPayload options (#{mod.datastore['PAYLOAD']}):\n\n#{p_opt}\n") if (p_opt and p_opt.length > 0)
|
||
end
|
||
end
|
||
end
|
||
|
||
def show_global_options
|
||
columns = [ 'Option', 'Current Setting', 'Description' ]
|
||
tbl = Table.new(
|
||
Table::Style::Default,
|
||
'Header' => 'Global Options:',
|
||
'Prefix' => "\n",
|
||
'Postfix' => "\n",
|
||
'Columns' => columns
|
||
)
|
||
[
|
||
[ 'ConsoleLogging', framework.datastore['ConsoleLogging'] || "false", 'Log all console input and output' ],
|
||
[ 'LogLevel', framework.datastore['LogLevel'] || "0", 'Verbosity of logs (default 0, max 3)' ],
|
||
[ 'MinimumRank', framework.datastore['MinimumRank'] || "0", 'The minimum rank of exploits that will run without explicit confirmation' ],
|
||
[ 'SessionLogging', framework.datastore['SessionLogging'] || "false", 'Log all input and output for sessions' ],
|
||
[ 'SessionTlvLogging', framework.datastore['SessionTlvLogging'] || "false", 'Log all incoming and outgoing TLV packets' ],
|
||
[ 'TimestampOutput', framework.datastore['TimestampOutput'] || "false", 'Prefix all console output with a timestamp' ],
|
||
[ 'Prompt', framework.datastore['Prompt'] || Msf::Ui::Console::Driver::DefaultPrompt.to_s.gsub(/%.../,"") , "The prompt string" ],
|
||
[ 'PromptChar', framework.datastore['PromptChar'] || Msf::Ui::Console::Driver::DefaultPromptChar.to_s.gsub(/%.../,""), "The prompt character" ],
|
||
[ 'PromptTimeFormat', framework.datastore['PromptTimeFormat'] || Time::DATE_FORMATS[:db].to_s, 'Format for timestamp escapes in prompts' ],
|
||
[ 'MeterpreterPrompt', framework.datastore['MeterpreterPrompt'] || '%undmeterpreter%clr', 'The meterpreter prompt string' ],
|
||
].each { |r| tbl << r }
|
||
|
||
print(tbl.to_s)
|
||
end
|
||
|
||
def show_targets(mod) # :nodoc:
|
||
case mod
|
||
when Msf::Exploit
|
||
mod_targs = Serializer::ReadableText.dump_exploit_targets(mod, '', "\nExploit targets:")
|
||
print("#{mod_targs}\n") if (mod_targs and mod_targs.length > 0)
|
||
when Msf::Evasion
|
||
mod_targs = Serializer::ReadableText.dump_evasion_targets(mod, '', "\nEvasion targets:")
|
||
print("#{mod_targs}\n") if (mod_targs and mod_targs.length > 0)
|
||
end
|
||
end
|
||
|
||
def show_actions(mod) # :nodoc:
|
||
mod_actions = Serializer::ReadableText.dump_module_actions(mod, ' ')
|
||
print("\n#{mod.type.capitalize} actions:\n\n#{mod_actions}\n") if (mod_actions and mod_actions.length > 0)
|
||
end
|
||
|
||
def show_advanced_options(mod) # :nodoc:
|
||
mod_opt = Serializer::ReadableText.dump_advanced_options(mod, ' ')
|
||
print("\nModule advanced options (#{mod.fullname}):\n\n#{mod_opt}\n") if (mod_opt and mod_opt.length > 0)
|
||
|
||
# If it's an exploit and a payload is defined, create it and
|
||
# display the payload's options
|
||
if (mod.exploit? and mod.datastore['PAYLOAD'])
|
||
p = framework.payloads.create(mod.datastore['PAYLOAD'])
|
||
|
||
if (!p)
|
||
print_error("Invalid payload defined: #{mod.datastore['PAYLOAD']}\n")
|
||
return
|
||
end
|
||
|
||
p.share_datastore(mod.datastore)
|
||
|
||
if (p)
|
||
p_opt = Serializer::ReadableText.dump_advanced_options(p, ' ')
|
||
print("\nPayload advanced options (#{mod.datastore['PAYLOAD']}):\n\n#{p_opt}\n") if (p_opt and p_opt.length > 0)
|
||
end
|
||
end
|
||
print("\nView the full module info with the #{Msf::Ui::Tip.highlight('info')}, or #{Msf::Ui::Tip.highlight('info -d')} command.\n\n")
|
||
end
|
||
|
||
def show_evasion_options(mod) # :nodoc:
|
||
mod_opt = Serializer::ReadableText.dump_evasion_options(mod, ' ')
|
||
print("\nModule evasion options:\n\n#{mod_opt}\n") if (mod_opt and mod_opt.length > 0)
|
||
|
||
# If it's an exploit and a payload is defined, create it and
|
||
# display the payload's options
|
||
if (mod.evasion? and mod.datastore['PAYLOAD'])
|
||
p = framework.payloads.create(mod.datastore['PAYLOAD'])
|
||
|
||
if (!p)
|
||
print_error("Invalid payload defined: #{mod.datastore['PAYLOAD']}\n")
|
||
return
|
||
end
|
||
|
||
p.share_datastore(mod.datastore)
|
||
|
||
if (p)
|
||
p_opt = Serializer::ReadableText.dump_evasion_options(p, ' ')
|
||
print("\nPayload evasion options (#{mod.datastore['PAYLOAD']}):\n\n#{p_opt}\n") if (p_opt and p_opt.length > 0)
|
||
end
|
||
end
|
||
end
|
||
|
||
def show_plugins # :nodoc:
|
||
tbl = Table.new(
|
||
Table::Style::Default,
|
||
'Header' => 'Loaded Plugins',
|
||
'Prefix' => "\n",
|
||
'Postfix' => "\n",
|
||
'Columns' => [ 'Name', 'Description' ]
|
||
)
|
||
|
||
framework.plugins.each { |plugin|
|
||
tbl << [ plugin.name, plugin.desc ]
|
||
}
|
||
|
||
# create an instance of core to call the list_plugins
|
||
core = Msf::Ui::Console::CommandDispatcher::Core.new(driver)
|
||
core.list_plugins
|
||
print(tbl.to_s)
|
||
end
|
||
|
||
# @param [table_name] used to name table
|
||
# @param [module_filter] this will either be a modules fullname, or it will be an Array(show payloads/encoders)
|
||
# or a Hash(show favorites) containing fullname
|
||
# @param [compatible_mod] handles logic for if there is an active module when the
|
||
# `show` command is run
|
||
#
|
||
# Handles the filtering of modules that will be generated into a table
|
||
def show_module_metadata(table_name, module_filter)
|
||
count = -1
|
||
tbl = generate_module_table(table_name)
|
||
|
||
if module_filter.is_a?(Array) || module_filter.is_a?(Hash)
|
||
module_filter.sort.each do |_mod_fullname, mod_obj|
|
||
mod = nil
|
||
|
||
begin
|
||
mod = mod_obj.new
|
||
rescue ::Exception
|
||
end
|
||
next unless mod
|
||
|
||
count += 1
|
||
tbl << add_record(mod, count, true)
|
||
end
|
||
else
|
||
results = Msf::Modules::Metadata::Cache.instance.find(
|
||
'type' => [[module_filter], []]
|
||
)
|
||
# Loop over each module and gather data
|
||
results.each do |mod, _value|
|
||
count += 1
|
||
tbl << add_record(mod, count, false)
|
||
end
|
||
end
|
||
print(tbl.to_s)
|
||
end
|
||
|
||
# @param [mod] current module being passed in
|
||
# @param [count] passes the count for each record
|
||
# @param [compatible_mod] handles logic for if there is an active module when the
|
||
# `show` command is run
|
||
#
|
||
# Adds a record for a table, also handle logic for whether the module is currently
|
||
# handling compatible payloads/encoders
|
||
def add_record(mod, count, compatible_mod)
|
||
if compatible_mod
|
||
check = mod.has_check? ? 'Yes' : 'No'
|
||
else
|
||
check = mod.check ? 'Yes' : 'No'
|
||
end
|
||
[
|
||
count,
|
||
mod.fullname,
|
||
mod.disclosure_date.nil? ? '' : mod.disclosure_date.strftime('%Y-%m-%d'),
|
||
mod.rank,
|
||
check,
|
||
mod.name
|
||
]
|
||
end
|
||
|
||
def generate_module_table(type, search_terms = [], row_filter = nil) # :nodoc:
|
||
table_hierarchy_formatters = framework.features.enabled?(Msf::FeatureManager::HIERARCHICAL_SEARCH_TABLE) ? [Msf::Ui::Console::TablePrint::BlankFormatter.new] : []
|
||
|
||
Table.new(
|
||
Table::Style::Default,
|
||
'Header' => type,
|
||
'Prefix' => "\n",
|
||
'Postfix' => "\n",
|
||
'SearchTerm' => row_filter,
|
||
'SortIndex' => -1,
|
||
# For now, don't perform any word wrapping on the search table as it breaks the workflow of
|
||
# copying module names in conjunction with the `use <paste-buffer>` command
|
||
'WordWrap' => false,
|
||
'Columns' => [
|
||
'#',
|
||
'Name',
|
||
'Disclosure Date',
|
||
'Rank',
|
||
'Check',
|
||
'Description'
|
||
],
|
||
'ColProps' => {
|
||
'Rank' => {
|
||
'Formatters' => [
|
||
*table_hierarchy_formatters,
|
||
Msf::Ui::Console::TablePrint::RankFormatter.new
|
||
],
|
||
'Stylers' => [
|
||
Msf::Ui::Console::TablePrint::RankStyler.new
|
||
]
|
||
},
|
||
'Name' => {
|
||
'Strip' => false,
|
||
'Stylers' => [Msf::Ui::Console::TablePrint::HighlightSubstringStyler.new(search_terms)]
|
||
},
|
||
'Check' => {
|
||
'Formatters' => [
|
||
*table_hierarchy_formatters,
|
||
]
|
||
},
|
||
'Disclosure Date' => {
|
||
'Formatters' => [
|
||
*table_hierarchy_formatters,
|
||
]
|
||
},
|
||
'Description' => {
|
||
'Stylers' => [
|
||
Msf::Ui::Console::TablePrint::HighlightSubstringStyler.new(search_terms)
|
||
]
|
||
}
|
||
}
|
||
)
|
||
end
|
||
end
|
||
end
|
||
end
|
||
end
|
||
end
|