diff --git a/lib/msf/ui/console/command_dispatcher/core.rb b/lib/msf/ui/console/command_dispatcher/core.rb index c1debf9814..534834671e 100644 --- a/lib/msf/ui/console/command_dispatcher/core.rb +++ b/lib/msf/ui/console/command_dispatcher/core.rb @@ -18,6 +18,7 @@ require 'msf/ui/console/command_dispatcher/auxiliary' require 'msf/ui/console/command_dispatcher/post' require 'msf/ui/console/command_dispatcher/jobs' require 'msf/ui/console/command_dispatcher/resource' +require 'msf/ui/console/command_dispatcher/modules' require 'msf/util/document_generator' module Msf @@ -83,8 +84,7 @@ class Core "-k" => [ true, "Keep (include) arg lines at start of output." ], "-c" => [ false, "Only print a count of matching lines." ]) - @@search_opts = Rex::Parser::Arguments.new( - "-h" => [ false, "Help banner." ]) + @@irb_opts = Rex::Parser::Arguments.new( "-h" => [ false, "Help banner." ], @@ -100,42 +100,29 @@ class Core def commands { "?" => "Help menu", - "back" => "Move back from the current context", "banner" => "Display an awesome metasploit banner", "cd" => "Change the current working directory", "connect" => "Communicate with a host", "color" => "Toggle color", "exit" => "Exit the console", - "edit" => "Edit the current module with $VISUAL or $EDITOR", "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", - "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", "irb" => "Drop into irb scripting mode", "load" => "Load a framework plugin", - "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", - "previous" => "Sets the previously loaded module as the current module", "quit" => "Exit the console", - "reload_all" => "Reloads all modules from all defined module paths", "route" => "Route traffic through a session", "save" => "Saves the active datastores", - "search" => "Searches module names and descriptions", "sess" => "Interact with a given session", "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", - "show" => "Displays modules of a given type, or all modules", "sleep" => "Do nothing for the specified number of seconds", "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", - "use" => "Selects a module by name", "version" => "Show the framework and console library version numbers", "spool" => "Write console output into a file as well the screen" } @@ -183,81 +170,8 @@ class Core driver.update_prompt 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 - # Check for modules that failed to load - if framework.modules.module_load_error_by_path.length > 0 - print_error("WARNING! The following modules could not be loaded!") - - framework.modules.module_load_error_by_path.each do |path, error| - print_error("\t#{path}: #{error}") - end - end - - if framework.modules.module_load_warnings.length > 0 - print_warning("The following modules were loaded with warnings:") - framework.modules.module_load_warnings.each do |path, error| - print_warning("\t#{path}: #{error}") - end - end - - cmd_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 - - # Restore the prompt - prompt = framework.datastore['Prompt'] || Msf::Ui::Console::Driver::DefaultPrompt - prompt_char = framework.datastore['PromptChar'] || Msf::Ui::Console::Driver::DefaultPromptChar - driver.update_prompt("#{prompt} ", prompt_char, true) - end - end def cmd_cd_help print_line "Usage: cd " @@ -539,37 +453,6 @@ class Core true end - def local_editor - Rex::Compat.getenv('VISUAL') || Rex::Compat.getenv('EDITOR') || '/usr/bin/vim' - end - - def cmd_edit_help - msg = "Edit the currently active module" - msg = "#{msg} #{local_editor ? "with #{local_editor}" : "($VISUAL or $EDITOR must be set first)"}." - print_line "Usage: edit" - print_line - print_line msg - print_line "When done editing, you must reload the module with 'reload' or 'rexploit'." - print_line - end - - # - # Edit the currently active module - # - def cmd_edit - unless local_editor - print_error "$VISUAL or $EDITOR must be set first. Try 'export EDITOR=/usr/bin/vim'" - return - end - if active_module - path = active_module.file_path - print_status "Launching #{local_editor} #{path}" - system(local_editor,path) - else - print_error "Nothing to edit -- try using a module first." - end - end - # # Instructs the driver to stop executing. # @@ -610,170 +493,6 @@ class Core Rex::ThreadSafe.sleep(args[0].to_f) 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 [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 - - # - # 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) - if dump_json - print(Serializer::Json.dump_module(active_module) + "\n") - elsif show_doc - f = Rex::Quickfile.new(["#{active_module.shortname}_doc", '.html']) - begin - print_status("Generating documentation for #{active_module.shortname}, then opening #{f.path} in a browser...") - Msf::Util::DocumentGenerator.spawn_module_document(active_module, f) - ensure - f.close if f - end - else - print(Serializer::ReadableText.dump_module(active_module)) - end - return true - else - cmd_info_help - return false - end - elsif args.include? "-h" - cmd_info_help - return false - end - - args.each { |name| - mod = framework.modules.create(name) - - if (mod == nil) - print_error("Invalid module: #{name}") - elsif dump_json - print(Serializer::Json.dump_module(mod) + "\n") - elsif show_doc - f = Rex::Quickfile.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)) - 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_irb_help print_line "Usage: irb" print_line @@ -1233,208 +952,6 @@ class Core print_line("Saved configuration to: #{Msf::Config.config_file}") end - def cmd_loadpath_help - print_line "Usage: loadpath " - 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.each_pair { |type, count| - added << " #{count} #{type}#{count != 1 ? 's' : ''}\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] 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 [keywords]" - print_line - print_line "Keywords:" - { - 'app' => 'Modules that are client or server attacks', - 'author' => 'Modules written by this author', - 'bid' => 'Modules with a matching Bugtraq ID', - 'cve' => 'Modules with a matching CVE ID', - 'edb' => 'Modules with a matching Exploit-DB ID', - 'name' => 'Modules with a matching descriptive name', - 'platform' => 'Modules affecting this platform', - 'ref' => 'Modules with a matching ref', - 'type' => 'Modules of a specific type (exploit, auxiliary, or post)', - }.each_pair do |keyword, description| - print_line " #{keyword.ljust 10}: #{description}" - end - print_line - print_line "Examples:" - print_line " search cve:2009 type:exploit app:client" - print_line - end - - # - # Searches modules for specific keywords - # - def cmd_search(*args) - match = '' - @@search_opts.parse(args) { |opt, idx, val| - case opt - when "-t" - print_error("Deprecated option. Use type:#{val} instead") - cmd_search_help - return - when "-h" - cmd_search_help - return - else - match += val + " " - end - } - - if framework.db - if framework.db.migrated && framework.db.modules_cached - search_modules_sql(match) - return - else - print_warning("Module database cache not built yet, using slow search") - end - else - print_warning("Database not connected, using slow search") - end - - tbl = generate_module_table("Matching Modules") - [ - framework.exploits, - framework.auxiliary, - framework.post, - framework.payloads, - framework.nops, - framework.encoders - ].each do |mset| - mset.each do |m| - o = mset.create(m[0]) rescue nil - - # Expected if modules are loaded without the right pre-requirements - next if not o - - if not o.search_filter(match) - tbl << [ o.fullname, o.disclosure_date.nil? ? "" : o.disclosure_date.strftime(DISCLOSURE_DATE_FORMAT), o.rank_to_s, o.name ] - end - end - end - print_line(tbl.to_s) - - end - - # Prints table of modules matching the search_string. - # - # @param (see Msf::DBManager#search_modules) - # @return [void] - def search_modules_sql(search_string) - tbl = generate_module_table("Matching Modules") - framework.db.search_modules(search_string).each do |o| - tbl << [ o.fullname, o.disclosure_date.nil? ? "" : o.disclosure_date.strftime(DISCLOSURE_DATE_FORMAT), RankingName[o.rank].to_s, o.name ] - end - print_line(tbl.to_s) - end - - # - # Tab completion for the search command - # - # @param str [String] the string currently being typed before tab was hit - # @param words [Array] 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.fmt.keys - end - - case (words[-1]) - when "-r" - return RankingName.sort.map{|r| r[1]} - when "-t" - return %w{auxiliary encoder exploit nop payload post} - end - - [] - end def cmd_spool_help print_line "Usage: spool |" @@ -2051,122 +1568,6 @@ class Core cmd_set_tabs(str, words) end - def cmd_show_help - global_opts = %w{all encoders nops exploits payloads auxiliary plugins info options} - 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) - mod = self.active_module - - args << "all" if (args.length == 0) - - 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 '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 - print_error("No module selected.") - 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?) - 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] 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} - if (active_module) - res.concat(%w{ missing advanced evasion targets actions }) - if (active_module.respond_to? :compatible_sessions) - res << "sessions" - end - end - return res - end def cmd_unload_help print_line "Usage: unload " @@ -2390,203 +1791,6 @@ class Core alias cmd_unsetg_help cmd_unset_help - def cmd_use_help - print_line "Usage: use module_name" - print_line - print_line "The use command is used to interact with a module of a given name." - print_line - end - - # - # Uses a module. - # - def cmd_use(*args) - if (args.length == 0) - cmd_use_help - return false - end - - # Try to create an instance of the supplied module name - mod_name = args[0] - - begin - mod = framework.modules.create(mod_name) - unless mod - # Try one more time; see #4549 - sleep CMD_USE_TIMEOUT - mod = framework.modules.create(mod_name) - unless mod - 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 - 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 - - @cache_payloads = nil - mod.init_ui(driver.input, driver.output) - - # Update the command prompt - prompt = framework.datastore['Prompt'] || Msf::Ui::Console::Driver::DefaultPrompt - prompt_char = framework.datastore['PromptChar'] || Msf::Ui::Console::Driver::DefaultPromptChar - driver.update_prompt("#{prompt} #{mod.type}(%bld%red#{mod.shortname}%clr) ", prompt_char, true) - end - - # - # Command to take to the previously active module - # - def cmd_previous() - 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 - 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] 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]] - 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 - - # - # Tab completion for the use command - # - # @param str [String] the string currently being typed before tab was hit - # @param words [Array] 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 completd - - def cmd_use_tabs(str, words) - return [] if words.length > 1 - - tab_complete_module(str, words) - end # # Returns the revision of the framework and console library @@ -2744,22 +1948,6 @@ class Core tabs end - # - # Tab complete module names - # - def tab_complete_module(str, words) - res = [] - framework.modules.module_types.each do |mtyp| - mset = framework.modules.module_names(mtyp) - mset.each do |mref| - res << mtyp + '/' + mref - end - end - - return res.sort - end - - # # Provide tab completion for option values # @@ -3062,252 +2250,7 @@ class Core return binary_paths.include? Msf::Config.install_root end - # - # Module list enumeration - # - def show_encoders(regex = nil, minrank = nil, opts = nil) # :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_set("Compatible Encoders", active_module.compatible_encoders, regex, minrank, opts) - else - show_module_set("Encoders", framework.encoders, regex, minrank, opts) - end - end - - def show_nops(regex = nil, minrank = nil, opts = nil) # :nodoc: - show_module_set("NOP Generators", framework.nops, regex, minrank, opts) - end - - def show_exploits(regex = nil, minrank = nil, opts = nil) # :nodoc: - show_module_set("Exploits", framework.exploits, regex, minrank, opts) - end - - def show_payloads(regex = nil, minrank = nil, opts = nil) # :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 and active_module.exploit? == true) - show_module_set("Compatible Payloads", active_module.compatible_payloads, regex, minrank, opts) - else - show_module_set("Payloads", framework.payloads, regex, minrank, opts) - end - end - - def show_auxiliary(regex = nil, minrank = nil, opts = nil) # :nodoc: - show_module_set("Auxiliary", framework.auxiliary, regex, minrank, opts) - end - - def show_post(regex = nil, minrank = nil, opts = nil) # :nodoc: - show_module_set("Post", framework.post, regex, minrank, opts) - end - - def show_options(mod) # :nodoc: - mod_opt = Serializer::ReadableText.dump_options(mod, ' ') - 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, ' ') - print("\nPayload options (#{mod.datastore['PAYLOAD']}):\n\n#{p_opt}\n") if (p_opt and p_opt.length > 0) - end - end - - # Print the selected target - if (mod.exploit? and mod.target) - mod_targ = Serializer::ReadableText.dump_exploit_target(mod, ' ') - print("\nExploit target:\n\n#{mod_targ}\n") if (mod_targ and mod_targ.length > 0) - end - - # Print the selected action - if mod.kind_of?(Msf::Module::HasActions) && mod.action - mod_action = Serializer::ReadableText.dump_module_action(mod, ' ') - print("\n#{mod.type.capitalize} action:\n\n#{mod_action}\n") if (mod_action and mod_action.length > 0) - end - - # Uncomment this line if u want target like msf2 format - #print("\nTarget: #{mod.target.name}\n\n") - 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' ], - [ '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' ], - ].each { |r| tbl << r } - - print(tbl.to_s) - end - - def show_targets(mod) # :nodoc: - mod_targs = Serializer::ReadableText.dump_exploit_targets(mod, ' ') - print("\nExploit targets:\n\n#{mod_targs}\n") if (mod_targs and mod_targs.length > 0) - 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 - 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.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_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' => 'Plugins', - 'Prefix' => "\n", - 'Postfix' => "\n", - 'Columns' => [ 'Name', 'Description' ] - ) - - framework.plugins.each { |plugin| - tbl << [ plugin.name, plugin.desc ] - } - - print(tbl.to_s) - end - - def show_module_set(type, module_set, regex = nil, minrank = nil, opts = nil) # :nodoc: - tbl = generate_module_table(type) - module_set.sort.each { |refname, mod| - o = nil - - begin - o = mod.new - rescue ::Exception - end - next if not o - - # handle a search string, search deep - if ( - not regex or - o.name.match(regex) or - o.description.match(regex) or - o.refname.match(regex) or - o.references.map{|x| [x.ctx_id + '-' + x.ctx_val, x.to_s]}.join(' ').match(regex) or - o.author.to_s.match(regex) - ) - if (not minrank or minrank <= o.rank) - show = true - if opts - mod_opt_keys = o.options.keys.map { |x| x.downcase } - - opts.each do |opt,val| - if !mod_opt_keys.include?(opt.downcase) || (val != nil && o.datastore[opt] != val) - show = false - end - end - end - if (opts == nil or show == true) - tbl << [ refname, o.disclosure_date.nil? ? "" : o.disclosure_date.strftime(DISCLOSURE_DATE_FORMAT), o.rank_to_s, o.name ] - end - end - end - } - - print(tbl.to_s) - end - - def generate_module_table(type) # :nodoc: - Table.new( - Table::Style::Default, - 'Header' => type, - 'Prefix' => "\n", - 'Postfix' => "\n", - 'Columns' => [ 'Name', 'Disclosure Date', 'Rank', 'Description' ] - ) - 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 diff --git a/lib/msf/ui/console/command_dispatcher/modules.rb b/lib/msf/ui/console/command_dispatcher/modules.rb new file mode 100644 index 0000000000..73d3fb4489 --- /dev/null +++ b/lib/msf/ui/console/command_dispatcher/modules.rb @@ -0,0 +1,1120 @@ +# -*- coding: binary -*- + +# +# Rex +# + +require 'rex/ui/text/output/buffer/stdout' + + +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 + + @@search_opts = Rex::Parser::Arguments.new( + "-h" => [ false, "Help banner."] + ) + + def commands + { + "back" => "Move back from the current context", + "edit" => "Edit the current module with $VISUAL or $EDITOR", + "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", + "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" => "Selects a module by name", + } + end + + # + # Initializes the datastore cache + # + def initialize(driver) + super + + @dscache = {} + @cache_payloads = nil + @previous_module = nil + @module_name_stack = [] + end + + # + # Returns the name of the command dispatcher. + # + def name + "Modules" + end + + + def local_editor + Rex::Compat.getenv('VISUAL') || Rex::Compat.getenv('EDITOR') || '/usr/bin/vim' + end + + def cmd_edit_help + msg = "Edit the currently active module" + msg = "#{msg} #{local_editor ? "with #{local_editor}" : "($VISUAL or $EDITOR must be set first)"}." + print_line "Usage: edit" + print_line + print_line msg + print_line "When done editing, you must reload the module with 'reload' or 'rexploit'." + print_line + end + + # + # Edit the currently active module + # + def cmd_edit + unless local_editor + print_error "$VISUAL or $EDITOR must be set first. Try 'export EDITOR=/usr/bin/vim'" + return + end + if active_module + path = active_module.file_path + print_status "Launching #{local_editor} #{path}" + system(local_editor,path) + else + print_error "Nothing to edit -- try using a module first." + end + 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 [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 + + # + # 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) + if dump_json + print(Serializer::Json.dump_module(active_module) + "\n") + elsif show_doc + f = Rex::Quickfile.new(["#{active_module.shortname}_doc", '.html']) + begin + print_status("Generating documentation for #{active_module.shortname}, then opening #{f.path} in a browser...") + Msf::Util::DocumentGenerator.spawn_module_document(active_module, f) + ensure + f.close if f + end + else + print(Serializer::ReadableText.dump_module(active_module)) + end + return true + else + cmd_info_help + return false + end + elsif args.include? "-h" + cmd_info_help + return false + end + + args.each { |name| + mod = framework.modules.create(name) + + if (mod == nil) + print_error("Invalid module: #{name}") + elsif dump_json + print(Serializer::Json.dump_module(mod) + "\n") + elsif show_doc + f = Rex::Quickfile.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)) + 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 " + 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.each_pair { |type, count| + added << " #{count} #{type}#{count != 1 ? 's' : ''}\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] 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 [keywords]" + print_line + print_line "Keywords:" + { + 'app' => 'Modules that are client or server attacks', + 'author' => 'Modules written by this author', + 'bid' => 'Modules with a matching Bugtraq ID', + 'cve' => 'Modules with a matching CVE ID', + 'edb' => 'Modules with a matching Exploit-DB ID', + 'name' => 'Modules with a matching descriptive name', + 'platform' => 'Modules affecting this platform', + 'ref' => 'Modules with a matching ref', + 'type' => 'Modules of a specific type (exploit, auxiliary, or post)', + }.each_pair do |keyword, description| + print_line " #{keyword.ljust 10}: #{description}" + end + print_line + print_line "Examples:" + print_line " search cve:2009 type:exploit app:client" + print_line + end + + # + # Searches modules for specific keywords + # + def cmd_search(*args) + match = '' + @@search_opts.parse(args) { |opt, idx, val| + case opt + when "-t" + print_error("Deprecated option. Use type:#{val} instead") + cmd_search_help + return + when "-h" + cmd_search_help + return + else + match += val + " " + end + } + + if framework.db + if framework.db.migrated && framework.db.modules_cached + search_modules_sql(match) + return + else + print_warning("Module database cache not built yet, using slow search") + end + else + print_warning("Database not connected, using slow search") + end + + tbl = generate_module_table("Matching Modules") + [ + framework.exploits, + framework.auxiliary, + framework.post, + framework.payloads, + framework.nops, + framework.encoders + ].each do |mset| + mset.each do |m| + o = mset.create(m[0]) rescue nil + + # Expected if modules are loaded without the right pre-requirements + next if not o + + if not o.search_filter(match) + tbl << [ o.fullname, o.disclosure_date.nil? ? "" : o.disclosure_date.strftime(DISCLOSURE_DATE_FORMAT), o.rank_to_s, o.name ] + end + end + end + print_line(tbl.to_s) + + end + + # Prints table of modules matching the search_string. + # + # @param (see Msf::DBManager#search_modules) + # @return [void] + def search_modules_sql(search_string) + tbl = generate_module_table("Matching Modules") + framework.db.search_modules(search_string).each do |o| + tbl << [ o.fullname, o.disclosure_date.nil? ? "" : o.disclosure_date.strftime(DISCLOSURE_DATE_FORMAT), RankingName[o.rank].to_s, o.name ] + end + print_line(tbl.to_s) + end + + # + # Tab completion for the search command + # + # @param str [String] the string currently being typed before tab was hit + # @param words [Array] 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.fmt.keys + end + + case (words[-1]) + when "-r" + return RankingName.sort.map{|r| r[1]} + when "-t" + return %w{auxiliary encoder exploit nop payload post} + end + + [] + end + + def cmd_show_help + global_opts = %w{all encoders nops exploits payloads auxiliary plugins info options} + 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) + mod = self.active_module + + args << "all" if (args.length == 0) + + 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 '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 + print_error("No module selected.") + 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?) + 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] 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} + if (active_module) + res.concat(%w{ missing advanced evasion targets actions }) + if (active_module.respond_to? :compatible_sessions) + res << "sessions" + end + end + return res + end + + def cmd_use_help + print_line "Usage: use module_name" + print_line + print_line "The use command is used to interact with a module of a given name." + print_line + end + + # + # Uses a module. + # + def cmd_use(*args) + if (args.length == 0) + cmd_use_help + return false + end + + # Try to create an instance of the supplied module name + mod_name = args[0] + + begin + mod = framework.modules.create(mod_name) + unless mod + # Try one more time; see #4549 + sleep CMD_USE_TIMEOUT + mod = framework.modules.create(mod_name) + unless mod + 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 + 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 + + @cache_payloads = nil + mod.init_ui(driver.input, driver.output) + + # Update the command prompt + prompt = framework.datastore['Prompt'] || Msf::Ui::Console::Driver::DefaultPrompt + prompt_char = framework.datastore['PromptChar'] || Msf::Ui::Console::Driver::DefaultPromptChar + driver.update_prompt("#{prompt} #{mod.type}(%bld%red#{mod.shortname}%clr) ", prompt_char, true) + end + + # + # Command to take to the previously active module + # + def cmd_previous() + 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 + 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] 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]] + 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 + + # + # Tab completion for the use command + # + # @param str [String] the string currently being typed before tab was hit + # @param words [Array] 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 completd + + 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 + + # Check for modules that failed to load + if framework.modules.module_load_error_by_path.length > 0 + print_error("WARNING! The following modules could not be loaded!") + + framework.modules.module_load_error_by_path.each do |path, error| + print_error("\t#{path}: #{error}") + end + end + + if framework.modules.module_load_warnings.length > 0 + print_warning("The following modules were loaded with warnings:") + framework.modules.module_load_warnings.each do |path, error| + print_warning("\t#{path}: #{error}") + end + end + + 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 + + # Restore the prompt + prompt = framework.datastore['Prompt'] || Msf::Ui::Console::Driver::DefaultPrompt + prompt_char = framework.datastore['PromptChar'] || Msf::Ui::Console::Driver::DefaultPromptChar + driver.update_prompt("#{prompt} ", prompt_char, true) + end + end + + # + # Tab complete module names + # + def tab_complete_module(str, words) + res = [] + framework.modules.module_types.each do |mtyp| + mset = framework.modules.module_names(mtyp) + mset.each do |mref| + res << mtyp + '/' + mref + end + end + + return res.sort + end + + # + # Module list enumeration + # + + def show_encoders(regex = nil, minrank = nil, opts = nil) # :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_set("Compatible Encoders", active_module.compatible_encoders, regex, minrank, opts) + else + show_module_set("Encoders", framework.encoders, regex, minrank, opts) + end + end + + def show_nops(regex = nil, minrank = nil, opts = nil) # :nodoc: + show_module_set("NOP Generators", framework.nops, regex, minrank, opts) + end + + def show_exploits(regex = nil, minrank = nil, opts = nil) # :nodoc: + show_module_set("Exploits", framework.exploits, regex, minrank, opts) + end + + def show_payloads(regex = nil, minrank = nil, opts = nil) # :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 and active_module.exploit? == true) + show_module_set("Compatible Payloads", active_module.compatible_payloads, regex, minrank, opts) + else + show_module_set("Payloads", framework.payloads, regex, minrank, opts) + end + end + + def show_auxiliary(regex = nil, minrank = nil, opts = nil) # :nodoc: + show_module_set("Auxiliary", framework.auxiliary, regex, minrank, opts) + end + + def show_post(regex = nil, minrank = nil, opts = nil) # :nodoc: + show_module_set("Post", framework.post, regex, minrank, opts) + end + + def show_options(mod) # :nodoc: + mod_opt = Serializer::ReadableText.dump_options(mod, ' ') + 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, ' ') + print("\nPayload options (#{mod.datastore['PAYLOAD']}):\n\n#{p_opt}\n") if (p_opt and p_opt.length > 0) + end + end + + # Print the selected target + if (mod.exploit? and mod.target) + mod_targ = Serializer::ReadableText.dump_exploit_target(mod, ' ') + print("\nExploit target:\n\n#{mod_targ}\n") if (mod_targ and mod_targ.length > 0) + end + + # Print the selected action + if mod.kind_of?(Msf::Module::HasActions) && mod.action + mod_action = Serializer::ReadableText.dump_module_action(mod, ' ') + print("\n#{mod.type.capitalize} action:\n\n#{mod_action}\n") if (mod_action and mod_action.length > 0) + end + + # Uncomment this line if u want target like msf2 format + #print("\nTarget: #{mod.target.name}\n\n") + 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' ], + [ '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' ], + ].each { |r| tbl << r } + + print(tbl.to_s) + end + + def show_targets(mod) # :nodoc: + mod_targs = Serializer::ReadableText.dump_exploit_targets(mod, ' ') + print("\nExploit targets:\n\n#{mod_targs}\n") if (mod_targs and mod_targs.length > 0) + 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 + 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.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_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' => 'Plugins', + 'Prefix' => "\n", + 'Postfix' => "\n", + 'Columns' => [ 'Name', 'Description' ] + ) + + framework.plugins.each { |plugin| + tbl << [ plugin.name, plugin.desc ] + } + + print(tbl.to_s) + end + + def show_module_set(type, module_set, regex = nil, minrank = nil, opts = nil) # :nodoc: + tbl = generate_module_table(type) + module_set.sort.each { |refname, mod| + o = nil + + begin + o = mod.new + rescue ::Exception + end + next if not o + + # handle a search string, search deep + if ( + not regex or + o.name.match(regex) or + o.description.match(regex) or + o.refname.match(regex) or + o.references.map{|x| [x.ctx_id + '-' + x.ctx_val, x.to_s]}.join(' ').match(regex) or + o.author.to_s.match(regex) + ) + if (not minrank or minrank <= o.rank) + show = true + if opts + mod_opt_keys = o.options.keys.map { |x| x.downcase } + + opts.each do |opt,val| + if !mod_opt_keys.include?(opt.downcase) || (val != nil && o.datastore[opt] != val) + show = false + end + end + end + if (opts == nil or show == true) + tbl << [ refname, o.disclosure_date.nil? ? "" : o.disclosure_date.strftime(DISCLOSURE_DATE_FORMAT), o.rank_to_s, o.name ] + end + end + end + } + + print(tbl.to_s) + end + + def generate_module_table(type) # :nodoc: + Table.new( + Table::Style::Default, + 'Header' => type, + 'Prefix' => "\n", + 'Postfix' => "\n", + 'Columns' => [ 'Name', 'Disclosure Date', 'Rank', 'Description' ] + ) + end + + end + end + end + end +end diff --git a/lib/msf/ui/console/driver.rb b/lib/msf/ui/console/driver.rb index 58bc968402..af604a5639 100644 --- a/lib/msf/ui/console/driver.rb +++ b/lib/msf/ui/console/driver.rb @@ -30,6 +30,7 @@ class Driver < Msf::Ui::Driver # Console Command Dispatchers to be loaded after the Core dispatcher. # CommandDispatchers = [ + CommandDispatcher::Modules, CommandDispatcher::Jobs, CommandDispatcher::Resource ]