Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Path: blob/master/lib/msf/ui/console/driver.rb
Views: 11783
# -*- coding: binary -*-1require 'find'2require 'erb'3require 'rexml/document'4require 'fileutils'5require 'digest/md5'67module Msf8module Ui9module Console1011#12# A user interface driver on a console interface.13#14class Driver < Msf::Ui::Driver1516ConfigCore = "framework/core"17ConfigGroup = "framework/ui/console"1819DefaultPrompt = "%undmsf#{Metasploit::Framework::Version::MAJOR}%clr"20DefaultPromptChar = "%clr>"2122#23# Console Command Dispatchers to be loaded after the Core dispatcher.24#25CommandDispatchers = [26CommandDispatcher::Modules,27CommandDispatcher::Jobs,28CommandDispatcher::Resource,29CommandDispatcher::Db,30CommandDispatcher::Creds,31CommandDispatcher::Developer,32CommandDispatcher::DNS33]3435#36# The console driver processes various framework notified events.37#38include FrameworkEventManager3940#41# The console driver is a command shell.42#43include Rex::Ui::Text::DispatcherShell4445include Rex::Ui::Text::Resource4647#48# Initializes a console driver instance with the supplied prompt string and49# prompt character. The optional hash can take extra values that will50# serve to initialize the console driver.51#52# @option opts [Boolean] 'AllowCommandPassthru' (true) Whether to allow53# unrecognized commands to be executed by the system shell54# @option opts [Boolean] 'Readline' (true) Whether to use the readline or not55# @option opts [Boolean] 'RealReadline' (false) Whether to use the system's56# readline library instead of RBReadline57# @option opts [String] 'HistFile' (Msf::Config.history_file) Path to a file58# where we can store command history59# @option opts [Array<String>] 'Resources' ([]) A list of resource files to60# load. If no resources are given, will load the default resource script,61# 'msfconsole.rc' in the user's {Msf::Config.config_directory config62# directory}63# @option opts [Boolean] 'SkipDatabaseInit' (false) Whether to skip64# connecting to the database and running migrations65def initialize(prompt = DefaultPrompt, prompt_char = DefaultPromptChar, opts = {})66choose_readline(opts)6768histfile = opts['HistFile'] || Msf::Config.history_file6970begin71FeatureManager.instance.load_config72rescue StandardError => e73elog(e)74end7576if opts['DeferModuleLoads'].nil?77opts['DeferModuleLoads'] = Msf::FeatureManager.instance.enabled?(Msf::FeatureManager::DEFER_MODULE_LOADS)78end7980# Initialize attributes8182framework_create_options = opts.merge({ 'DeferModuleLoads' => true })8384if Msf::FeatureManager.instance.enabled?(Msf::FeatureManager::DNS)85dns_resolver = Rex::Proto::DNS::CachedResolver.new86dns_resolver.extend(Rex::Proto::DNS::CustomNameserverProvider)87dns_resolver.load_config if dns_resolver.has_config?8889# Defer loading of modules until paths from opts can be added below90framework_create_options = framework_create_options.merge({ 'CustomDnsResolver' => dns_resolver })91end92self.framework = opts['Framework'] || Msf::Simple::Framework.create(framework_create_options)9394if self.framework.datastore['Prompt']95prompt = self.framework.datastore['Prompt']96prompt_char = self.framework.datastore['PromptChar'] || DefaultPromptChar97end9899# Call the parent100super(prompt, prompt_char, histfile, framework, :msfconsole)101102# Temporarily disable output103self.disable_output = true104105# Load pre-configuration106load_preconfig107108# Initialize the user interface to use a different input and output109# handle if one is supplied110input = opts['LocalInput']111input ||= Rex::Ui::Text::Input::Stdio.new112113if !opts['Readline']114input.disable_readline115end116117if (opts['LocalOutput'])118if (opts['LocalOutput'].kind_of?(String))119output = Rex::Ui::Text::Output::File.new(opts['LocalOutput'])120else121output = opts['LocalOutput']122end123else124output = Rex::Ui::Text::Output::Stdio.new125end126127init_ui(input, output)128init_tab_complete129130# Add the core command dispatcher as the root of the dispatcher131# stack132enstack_dispatcher(CommandDispatcher::Core)133134# Report readline error if there was one..135if !@rl_err.nil?136print_error("***")137print_error("* Unable to load readline: #{@rl_err}")138print_error("* Falling back to RbReadLine")139print_error("***")140end141142# Load the other "core" command dispatchers143CommandDispatchers.each do |dispatcher_class|144dispatcher = enstack_dispatcher(dispatcher_class)145dispatcher.load_config(opts['Config'])146end147148if !framework.db || !framework.db.active149if framework.db.error == "disabled"150print_warning("Database support has been disabled")151else152error_msg = "#{framework.db.error.class.is_a?(String) ? "#{framework.db.error.class} " : nil}#{framework.db.error}"153print_warning("No database support: #{error_msg}")154end155end156157# Register event handlers158register_event_handlers159160# Re-enable output161self.disable_output = false162163# Whether or not command passthru should be allowed164self.command_passthru = opts.fetch('AllowCommandPassthru', true)165166# Whether or not to confirm before exiting167self.confirm_exit = opts['ConfirmExit']168169# Initialize the module paths only if we didn't get passed a Framework instance and 'DeferModuleLoads' is false170unless opts['Framework']171# Configure the framework module paths172self.framework.init_module_paths(module_paths: opts['ModulePath'], defer_module_loads: opts['DeferModuleLoads'])173end174175unless opts['DeferModuleLoads']176framework.threads.spawn("ModuleCacheRebuild", true) do177framework.modules.refresh_cache_from_module_files178end179end180181# Load console-specific configuration (after module paths are added)182load_config(opts['Config'])183184# Process things before we actually display the prompt and get rocking185on_startup(opts)186187# Process any resource scripts188if opts['Resource'].blank?189# None given, load the default190default_resource = ::File.join(Msf::Config.config_directory, 'msfconsole.rc')191load_resource(default_resource) if ::File.exist?(default_resource)192else193opts['Resource'].each { |r|194load_resource(r)195}196end197198# Process persistent job handler199begin200restore_handlers = JSON.parse(File.read(Msf::Config.persist_file))201rescue Errno::ENOENT, JSON::ParserError202restore_handlers = nil203end204205if restore_handlers206print_status("Starting persistent handler(s)...")207208restore_handlers.each.with_index do |handler_opts, index|209handler = framework.modules.create(handler_opts['mod_name'])210handler.init_ui(self.input, self.output)211replicant_handler = nil212handler.exploit_simple(handler_opts['mod_options']) do |yielded_replicant_handler|213replicant_handler = yielded_replicant_handler214end215216if replicant_handler.nil? || replicant_handler.error217print_status("Failed to start persistent payload handler ##{index} (#{handler_opts['mod_name']})")218next219end220221if replicant_handler.error.nil?222job_id = handler.job_id223print_status "Persistent payload handler started as Job #{job_id}"224end225end226end227228# Process any additional startup commands229if opts['XCommands'] and opts['XCommands'].kind_of? Array230opts['XCommands'].each { |c|231run_single(c)232}233end234end235236#237# Loads configuration that needs to be analyzed before the framework238# instance is created.239#240def load_preconfig241begin242conf = Msf::Config.load243rescue244wlog("Failed to load configuration: #{$!}")245return246end247248if (conf.group?(ConfigCore))249conf[ConfigCore].each_pair { |k, v|250on_variable_set(true, k, v)251}252end253end254255#256# Loads configuration for the console.257#258def load_config(path=nil)259begin260conf = Msf::Config.load(path)261rescue262wlog("Failed to load configuration: #{$!}")263return264end265266# If we have configuration, process it267if (conf.group?(ConfigGroup))268conf[ConfigGroup].each_pair { |k, v|269case k.downcase270when 'activemodule'271run_single("use #{v}")272when 'activeworkspace'273if framework.db.active274workspace = framework.db.find_workspace(v)275framework.db.workspace = workspace if workspace276end277end278}279end280end281282#283# Generate configuration for the console.284#285def get_config286# Build out the console config group287group = {}288289if (active_module)290group['ActiveModule'] = active_module.fullname291end292293if framework.db.active294unless framework.db.workspace.default?295group['ActiveWorkspace'] = framework.db.workspace.name296end297end298299group300end301302def get_config_core303ConfigCore304end305306def get_config_group307ConfigGroup308end309310#311# Saves configuration for the console.312#313def save_config314begin315Msf::Config.save(ConfigGroup => get_config)316rescue ::Exception317print_error("Failed to save console config: #{$!}")318end319end320321#322# Saves the recent history to the specified file323#324def save_recent_history(path)325num = Readline::HISTORY.length - hist_last_saved - 1326327tmprc = ""328num.times { |x|329tmprc << Readline::HISTORY[hist_last_saved + x] + "\n"330}331332if tmprc.length > 0333print_status("Saving last #{num} commands to #{path} ...")334save_resource(tmprc, path)335else336print_error("No commands to save!")337end338339# Always update this, even if we didn't save anything. We do this340# so that we don't end up saving the "makerc" command itself.341self.hist_last_saved = Readline::HISTORY.length342end343344#345# Creates the resource script file for the console.346#347def save_resource(data, path=nil)348path ||= File.join(Msf::Config.config_directory, 'msfconsole.rc')349350begin351rcfd = File.open(path, 'w')352rcfd.write(data)353rcfd.close354rescue ::Exception355end356end357358#359# Called before things actually get rolling such that banners can be360# displayed, scripts can be processed, and other fun can be had.361#362def on_startup(opts = {})363# Check for modules that failed to load364if framework.modules.module_load_error_by_path.length > 0365wlog("The following modules could not be loaded!")366367framework.modules.module_load_error_by_path.each do |path, _error|368wlog("\t#{path}")369end370end371372if framework.modules.module_load_warnings.length > 0373print_warning("The following modules were loaded with warnings:")374375framework.modules.module_load_warnings.each do |path, _error|376wlog("\t#{path}")377end378end379380if framework.db&.active381framework.db.workspace = framework.db.default_workspace unless framework.db.workspace382end383384framework.events.on_ui_start(Msf::Framework::Revision)385386if $msf_spinner_thread387$msf_spinner_thread.kill388$stderr.print "\r" + (" " * 50) + "\n"389end390391run_single("banner") unless opts['DisableBanner']392393payloads_manifest_errors = []394begin395payloads_manifest_errors = ::MetasploitPayloads.manifest_errors if framework.features.enabled?(::Msf::FeatureManager::METASPLOIT_PAYLOAD_WARNINGS)396rescue ::StandardError => e397$stderr.print('Could not verify the integrity of the Metasploit Payloads manifest')398elog(e)399end400401av_warning_message if (framework.eicar_corrupted? || payloads_manifest_errors.any?)402403if framework.features.enabled?(::Msf::FeatureManager::METASPLOIT_PAYLOAD_WARNINGS)404if payloads_manifest_errors.any?405warn_msg = "Metasploit Payloads manifest errors:\n"406payloads_manifest_errors.each do |file|407warn_msg << "\t#{file[:path]} : #{file[:error]}\n"408end409$stderr.print(warn_msg)410end411end412413opts["Plugins"].each do |plug|414run_single("load '#{plug}'")415end if opts["Plugins"]416417self.on_command_proc = Proc.new { |command| framework.events.on_ui_command(command) }418end419420def av_warning_message421avdwarn = "\e[31m"\422"Warning: This copy of the Metasploit Framework has been corrupted by an installed anti-virus program."\423" We recommend that you disable your anti-virus or exclude your Metasploit installation path, "\424"then restore the removed files from quarantine or reinstall the framework.\e[0m"\425"\n\n"426427$stderr.puts(Rex::Text.wordwrap(avdwarn, 0, 80))428end429430#431# Called when a variable is set to a specific value. This allows the432# console to do extra processing, such as enabling logging or doing433# some other kind of task. If this routine returns false it will indicate434# that the variable is not being set to a valid value.435#436def on_variable_set(glob, var, val)437case var.downcase438when 'sessionlogging'439handle_session_logging(val) if glob440when 'sessiontlvlogging'441handle_session_tlv_logging(val) if glob442when 'consolelogging'443handle_console_logging(val) if glob444when 'loglevel'445handle_loglevel(val) if glob446when 'payload'447handle_payload(val)448when 'ssh_ident'449handle_ssh_ident(val)450end451end452453#454# Called when a variable is unset. If this routine returns false it is an455# indication that the variable should not be allowed to be unset.456#457def on_variable_unset(glob, var)458case var.downcase459when 'sessionlogging'460handle_session_logging('0') if glob461when 'sessiontlvlogging'462handle_session_tlv_logging('false') if glob463when 'consolelogging'464handle_console_logging('0') if glob465when 'loglevel'466handle_loglevel(nil) if glob467end468end469470#471# Proxies to shell.rb's update prompt with our own extras472#473def update_prompt(*args)474if args.empty?475pchar = framework.datastore['PromptChar'] || DefaultPromptChar476p = framework.datastore['Prompt'] || DefaultPrompt477p = "#{p} #{active_module.type}(%bld%red#{active_module.promptname}%clr)" if active_module478super(p, pchar)479else480# Don't squash calls from within lib/rex/ui/text/shell.rb481super(*args)482end483end484485#486# The framework instance associated with this driver.487#488attr_reader :framework489#490# Whether or not to confirm before exiting491#492attr_reader :confirm_exit493#494# Whether or not commands can be passed through.495#496attr_reader :command_passthru497#498# The active module associated with the driver.499#500attr_accessor :active_module501#502# The active session associated with the driver.503#504attr_accessor :active_session505506def stop507framework.events.on_ui_stop()508super509end510511protected512513attr_writer :framework # :nodoc:514attr_writer :confirm_exit # :nodoc:515attr_writer :command_passthru # :nodoc:516517#518# If an unknown command was passed, try to see if it's a valid local519# executable. This is only allowed if command passthru has been permitted520#521def unknown_command(method, line)522if File.basename(method) == 'msfconsole'523print_error('msfconsole cannot be run inside msfconsole')524return525end526527[method, method+".exe"].each do |cmd|528if command_passthru && Rex::FileUtils.find_full_path(cmd)529530self.busy = true531begin532run_unknown_command(line)533rescue ::Errno::EACCES, ::Errno::ENOENT534print_error("Permission denied exec: #{line}")535end536self.busy = false537return538end539end540541if framework.modules.create(method)542super543if prompt_yesno "This is a module we can load. Do you want to use #{method}?"544run_single "use #{method}"545end546547return548end549550super551end552553def run_unknown_command(command)554print_status("exec: #{command}")555print_line('')556system(command)557end558559##560#561# Handlers for various global configuration values562#563##564565#566# SessionLogging.567#568def handle_session_logging(val)569if (val =~ /^(y|t|1)/i)570Msf::Logging.enable_session_logging(true)571framework.sessions.values.each do |session|572Msf::Logging.start_session_log(session)573end574print_line("Session logging enabled.")575else576Msf::Logging.enable_session_logging(false)577framework.sessions.values.each do |session|578Msf::Logging.stop_session_log(session)579end580print_line("Session logging disabled.")581end582end583584#585# ConsoleLogging.586#587def handle_console_logging(val)588if (val =~ /^(y|t|1)/i)589Msf::Logging.enable_log_source('console')590print_line("Console logging is now enabled.")591592set_log_source('console')593594rlog("\n[*] Console logging started: #{Time.now}\n\n", 'console')595else596rlog("\n[*] Console logging stopped: #{Time.now}\n\n", 'console')597598unset_log_source599600Msf::Logging.disable_log_source('console')601print_line("Console logging is now disabled.")602end603end604605#606# This method handles adjusting the global log level threshold.607#608def handle_loglevel(val)609set_log_level(Rex::LogSource, val)610set_log_level(Msf::LogSource, val)611end612613#614# This method handles setting a desired payload615#616# TODO: Move this out of the console driver!617#618def handle_payload(val)619if framework && !framework.payloads.valid?(val)620return false621elsif active_module && (active_module.exploit? || active_module.evasion?)622return false unless active_module.is_payload_compatible?(val)623elsif active_module && !framework.features.enabled?(Msf::FeatureManager::DATASTORE_FALLBACKS)624active_module.datastore.clear_non_user_defined625elsif framework && !framework.features.enabled?(Msf::FeatureManager::DATASTORE_FALLBACKS)626framework.datastore.clear_non_user_defined627end628end629630#631# This method monkeypatches Net::SSH's client identification string632#633# TODO: Move this out of the console driver!634#635def handle_ssh_ident(val)636# HACK: Suppress already initialized constant warning637verbose, $VERBOSE = $VERBOSE, nil638639return false unless val.is_a?(String) && !val.empty?640641require 'net/ssh'642643# HACK: Bypass dynamic constant assignment error644::Net::SSH::Transport::ServerVersion.const_set(:PROTO_VERSION, val)645646true647rescue LoadError648print_error('Net::SSH could not be loaded')649false650rescue NameError651print_error('Invalid constant Net::SSH::Transport::ServerVersion::PROTO_VERSION')652false653ensure654# Restore warning655$VERBOSE = verbose656end657658def handle_session_tlv_logging(val)659return false if val.nil?660661if val.casecmp?('console') || val.casecmp?('true') || val.casecmp?('false')662return true663elsif val.start_with?('file:') && !val.split('file:').empty?664pathname = ::Pathname.new(val.split('file:').last)665666# Check if we want to write the log to file667if ::File.file?(pathname)668if ::File.writable?(pathname)669return true670else671print_status "No write permissions for log output file: #{pathname}"672return false673end674# Check if we want to write the log file to a directory675elsif ::File.directory?(pathname)676if ::File.writable?(pathname)677return true678else679print_status "No write permissions for log output directory: #{pathname}"680return false681end682# Check if the subdirectory exists683elsif ::File.directory?(pathname.dirname)684if ::File.writable?(pathname.dirname)685return true686else687print_status "No write permissions for log output directory: #{pathname.dirname}"688return false689end690else691# Else the directory doesn't exist. Check if we can create it.692begin693::FileUtils.mkdir_p(pathname.dirname)694return true695rescue ::StandardError => e696print_status "Error when trying to create directory #{pathname.dirname}: #{e.message}"697return false698end699end700end701702false703end704705# Require the appropriate readline library based on the user's preference.706#707# @return [void]708def choose_readline(opts)709# Choose a readline library before calling the parent710@rl_err = nil711if opts['RealReadline']712# Remove the gem version from load path to be sure we're getting the713# stdlib readline.714gem_dir = Gem::Specification.find_all_by_name('rb-readline').first.gem_dir715rb_readline_path = File.join(gem_dir, "lib")716index = $LOAD_PATH.index(rb_readline_path)717# Bundler guarantees that the gem will be there, so it should be safe to718# assume we found it in the load path, but check to be on the safe side.719if index720$LOAD_PATH.delete_at(index)721end722end723724begin725require 'readline'726727# Only Windows requires a monkey-patched RbReadline728return unless Rex::Compat.is_windows729730if defined?(::RbReadline) && !defined?(RbReadline.refresh_console_handle)731::RbReadline.instance_eval do732class << self733alias_method :old_rl_move_cursor_relative, :_rl_move_cursor_relative734alias_method :old_rl_get_screen_size, :_rl_get_screen_size735alias_method :old_space_to_eol, :space_to_eol736alias_method :old_insert_some_chars, :insert_some_chars737end738739def self.refresh_console_handle740# hConsoleHandle gets set only when RbReadline detects it is running on Windows.741# Therefore, we don't need to check Rex::Compat.is_windows, we can simply check if hConsoleHandle is nil or not.742@hConsoleHandle = @GetStdHandle.Call(::Readline::STD_OUTPUT_HANDLE) if @hConsoleHandle743end744745def self._rl_move_cursor_relative(*args)746refresh_console_handle747old_rl_move_cursor_relative(*args)748end749750def self._rl_get_screen_size(*args)751refresh_console_handle752old_rl_get_screen_size(*args)753end754755def self.space_to_eol(*args)756refresh_console_handle757old_space_to_eol(*args)758end759760def self.insert_some_chars(*args)761refresh_console_handle762old_insert_some_chars(*args)763end764end765end766rescue ::LoadError => e767if @rl_err.nil? && index768# Then this is the first time the require failed and we have an index769# for the gem version as a fallback.770@rl_err = e771# Put the gem back and see if that works772$LOAD_PATH.insert(index, rb_readline_path)773index = rb_readline_path = nil774retry775else776# Either we didn't have the gem to fall back on, or we failed twice.777# Nothing more we can do here.778raise e779end780end781end782end783784end785end786end787788789