Class: Msf::Ui::Console::Driver
- Defined in:
- lib/msf/ui/console/driver.rb
Overview
A user interface driver on a console interface.
Constant Summary collapse
- ConfigCore =
"framework/core"
- ConfigGroup =
"framework/ui/console"
- DefaultPrompt =
"%undmsf#{Metasploit::Framework::Version::MAJOR}%clr"
- DefaultPromptChar =
"%clr>"
- CommandDispatchers =
Console Command Dispatchers to be loaded after the Core dispatcher.
[ CommandDispatcher::Modules, CommandDispatcher::Jobs, CommandDispatcher::Resource, CommandDispatcher::Db, CommandDispatcher::Creds, CommandDispatcher::Developer ]
Instance Attribute Summary collapse
-
#active_module ⇒ Object
The active module associated with the driver.
-
#active_session ⇒ Object
The active session associated with the driver.
-
#command_passthru ⇒ Object
Whether or not commands can be passed through.
-
#confirm_exit ⇒ Object
Whether or not to confirm before exiting.
-
#framework ⇒ Object
The framework instance associated with this driver.
Attributes included from Rex::Ui::Text::DispatcherShell
#blocked, #busy, #dispatcher_stack
Attributes included from Rex::Ui::Text::Shell
#cont_flag, #cont_prompt, #disable_output, #hist_last_saved, #histfile, #input, #local_hostname, #local_username, #log_source, #name, #on_command_proc, #on_print_proc, #output, #prompt, #prompt_char, #stop_count, #stop_flag, #tab_complete_proc
Instance Method Summary collapse
- #av_warning_message ⇒ Object
-
#choose_readline(opts) ⇒ void
protected
Require the appropriate readline library based on the user’s preference.
-
#get_config ⇒ Object
Generate configuration for the console.
- #get_config_core ⇒ Object
- #get_config_group ⇒ Object
-
#handle_console_logging(val) ⇒ Object
protected
ConsoleLogging.
-
#handle_loglevel(val) ⇒ Object
protected
This method handles adjusting the global log level threshold.
-
#handle_payload(val) ⇒ Object
protected
This method handles setting a desired payload.
-
#handle_session_logging(val) ⇒ Object
protected
SessionLogging.
- #handle_session_tlv_logging(val) ⇒ Object protected
-
#handle_ssh_ident(val) ⇒ Object
protected
This method monkeypatches Net::SSH’s client identification string.
-
#initialize(prompt = DefaultPrompt, prompt_char = DefaultPromptChar, opts = {}) ⇒ Driver
constructor
Initializes a console driver instance with the supplied prompt string and prompt character.
-
#load_config(path = nil) ⇒ Object
Loads configuration for the console.
-
#load_preconfig ⇒ Object
Loads configuration that needs to be analyzed before the framework instance is created.
-
#on_startup(opts = {}) ⇒ Object
Called before things actually get rolling such that banners can be displayed, scripts can be processed, and other fun can be had.
-
#on_variable_set(glob, var, val) ⇒ Object
Called when a variable is set to a specific value.
-
#on_variable_unset(glob, var) ⇒ Object
Called when a variable is unset.
- #run_unknown_command(command) ⇒ Object protected
-
#save_config ⇒ Object
Saves configuration for the console.
-
#save_recent_history(path) ⇒ Object
Saves the recent history to the specified file.
-
#save_resource(data, path = nil) ⇒ Object
Creates the resource script file for the console.
- #stop ⇒ Object
-
#unknown_command(method, line) ⇒ Object
protected
If an unknown command was passed, try to see if it’s a valid local executable.
-
#update_prompt(*args) ⇒ Object
Proxies to shell.rb’s update prompt with our own extras.
Methods included from Rex::Ui::Text::Resource
Methods included from Rex::Ui::Text::DispatcherShell
#append_dispatcher, #block_command, #blocked_command?, #current_dispatcher, #destack_dispatcher, #enstack_dispatcher, #help_to_s, #remove_dispatcher, #run_command, #run_single, #shellsplitex, #tab_complete, #tab_complete_helper, #tab_complete_stub, #unblock_command
Methods included from Rex::Ui::Text::Shell
#_print_prompt, #format_prompt, #get_input_line, #init_tab_complete, #init_ui, #log_input, #log_output, #parse_line, #print, #print_error, #print_good, #print_line, #print_status, #print_warning, #prompt_yesno, #reset_ui, #run, #set_log_source, #stopped?, #supports_color?, #tab_complete, #unset_log_source
Methods included from FrameworkEventManager
#deregister_event_handlers, #on_session_close, #on_session_fail, #on_session_open, #register_event_handlers
Methods included from SessionEvent
#on_session_close, #on_session_command, #on_session_download, #on_session_filedelete, #on_session_interact, #on_session_open, #on_session_output, #on_session_upload
Methods inherited from Driver
Constructor Details
#initialize(prompt = DefaultPrompt, prompt_char = DefaultPromptChar, opts = {}) ⇒ Driver
Initializes a console driver instance with the supplied prompt string and prompt character. The optional hash can take extra values that will serve to initialize the console driver.
65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 |
# File 'lib/msf/ui/console/driver.rb', line 65 def initialize(prompt = DefaultPrompt, prompt_char = DefaultPromptChar, opts = {}) choose_readline(opts) histfile = opts['HistFile'] || Msf::Config.history_file # Initialize attributes # Defer loading of modules until paths from opts can be added below = opts.merge('DeferModuleLoads' => true) self.framework = opts['Framework'] || Msf::Simple::Framework.create() if self.framework.datastore['Prompt'] prompt = self.framework.datastore['Prompt'] prompt_char = self.framework.datastore['PromptChar'] || DefaultPromptChar end # Call the parent super(prompt, prompt_char, histfile, framework, :msfconsole) # Temporarily disable output self.disable_output = true # Load pre-configuration load_preconfig # Initialize the user interface to use a different input and output # handle if one is supplied input = opts['LocalInput'] input ||= Rex::Ui::Text::Input::Stdio.new if !opts['Readline'] input.disable_readline end if (opts['LocalOutput']) if (opts['LocalOutput'].kind_of?(String)) output = Rex::Ui::Text::Output::File.new(opts['LocalOutput']) else output = opts['LocalOutput'] end else output = Rex::Ui::Text::Output::Stdio.new end init_ui(input, output) init_tab_complete # Add the core command dispatcher as the root of the dispatcher # stack enstack_dispatcher(CommandDispatcher::Core) # Report readline error if there was one.. if !@rl_err.nil? print_error("***") print_error("* Unable to load readline: #{@rl_err}") print_error("* Falling back to RbReadLine") print_error("***") end # Load the other "core" command dispatchers CommandDispatchers.each do |dispatcher_class| dispatcher = enstack_dispatcher(dispatcher_class) dispatcher.load_config(opts['Config']) end begin FeatureManager.instance.load_config rescue StandardException => e elog(e) end if !framework.db || !framework.db.active if framework.db.error == "disabled" print_warning("Database support has been disabled") else error_msg = "#{framework.db.error.class.is_a?(String) ? "#{framework.db.error.class} " : nil}#{framework.db.error}" print_warning("No database support: #{error_msg}") end end # Register event handlers register_event_handlers # Re-enable output self.disable_output = false # Whether or not command passthru should be allowed self.command_passthru = opts.fetch('AllowCommandPassthru', true) # Whether or not to confirm before exiting self.confirm_exit = opts['ConfirmExit'] # Initialize the module paths only if we didn't get passed a Framework instance and 'DeferModuleLoads' is false unless opts['Framework'] || opts['DeferModuleLoads'] # Configure the framework module paths self.framework.init_module_paths(module_paths: opts['ModulePath']) end unless opts['DeferModuleLoads'] framework.threads.spawn("ModuleCacheRebuild", true) do framework.modules.refresh_cache_from_module_files end end # Load console-specific configuration (after module paths are added) load_config(opts['Config']) # Process things before we actually display the prompt and get rocking on_startup(opts) # Process any resource scripts if opts['Resource'].blank? # None given, load the default default_resource = ::File.join(Msf::Config.config_directory, 'msfconsole.rc') load_resource(default_resource) if ::File.exist?(default_resource) else opts['Resource'].each { |r| load_resource(r) } end # Process persistent job handler begin restore_handlers = JSON.parse(File.read(Msf::Config.persist_file)) rescue Errno::ENOENT, JSON::ParserError restore_handlers = nil end if restore_handlers print_status("Starting persistent handler(s)...") restore_handlers.each do |handler_opts| handler = framework.modules.create(handler_opts['mod_name']) handler.exploit_simple(handler_opts['mod_options']) end end # Process any additional startup commands if opts['XCommands'] and opts['XCommands'].kind_of? Array opts['XCommands'].each { |c| run_single(c) } end end |
Instance Attribute Details
#active_module ⇒ Object
The active module associated with the driver.
456 457 458 |
# File 'lib/msf/ui/console/driver.rb', line 456 def active_module @active_module end |
#active_session ⇒ Object
The active session associated with the driver.
460 461 462 |
# File 'lib/msf/ui/console/driver.rb', line 460 def active_session @active_session end |
#command_passthru ⇒ Object
Whether or not commands can be passed through.
452 453 454 |
# File 'lib/msf/ui/console/driver.rb', line 452 def command_passthru @command_passthru end |
#confirm_exit ⇒ Object
Whether or not to confirm before exiting
448 449 450 |
# File 'lib/msf/ui/console/driver.rb', line 448 def confirm_exit @confirm_exit end |
#framework ⇒ Object
The framework instance associated with this driver.
444 445 446 |
# File 'lib/msf/ui/console/driver.rb', line 444 def framework @framework end |
Instance Method Details
#av_warning_message ⇒ Object
376 377 378 379 380 381 382 383 384 |
# File 'lib/msf/ui/console/driver.rb', line 376 def avdwarn = "\e[31m"\ "Warning: This copy of the Metasploit Framework has been corrupted by an installed anti-virus program."\ " We recommend that you disable your anti-virus or exclude your Metasploit installation path, "\ "then restore the removed files from quarantine or reinstall the framework.\e[0m"\ "\n\n" $stderr.puts(Rex::Text.wordwrap(avdwarn, 0, 80)) end |
#choose_readline(opts) ⇒ void (protected)
This method returns an undefined value.
Require the appropriate readline library based on the user’s preference.
658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 |
# File 'lib/msf/ui/console/driver.rb', line 658 def choose_readline(opts) # Choose a readline library before calling the parent @rl_err = nil if opts['RealReadline'] # Remove the gem version from load path to be sure we're getting the # stdlib readline. gem_dir = Gem::Specification.find_all_by_name('rb-readline').first.gem_dir rb_readline_path = File.join(gem_dir, "lib") index = $LOAD_PATH.index(rb_readline_path) # Bundler guarantees that the gem will be there, so it should be safe to # assume we found it in the load path, but check to be on the safe side. if index $LOAD_PATH.delete_at(index) end end begin require 'readline' rescue ::LoadError => e if @rl_err.nil? && index # Then this is the first time the require failed and we have an index # for the gem version as a fallback. @rl_err = e # Put the gem back and see if that works $LOAD_PATH.insert(index, rb_readline_path) index = rb_readline_path = nil retry else # Either we didn't have the gem to fall back on, or we failed twice. # Nothing more we can do here. raise e end end end |
#get_config ⇒ Object
Generate configuration for the console.
259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 |
# File 'lib/msf/ui/console/driver.rb', line 259 def get_config # Build out the console config group group = {} if (active_module) group['ActiveModule'] = active_module.fullname end if framework.db.active unless framework.db.workspace.default? group['ActiveWorkspace'] = framework.db.workspace.name end end group end |
#get_config_core ⇒ Object
276 277 278 |
# File 'lib/msf/ui/console/driver.rb', line 276 def get_config_core ConfigCore end |
#get_config_group ⇒ Object
280 281 282 |
# File 'lib/msf/ui/console/driver.rb', line 280 def get_config_group ConfigGroup end |
#handle_console_logging(val) ⇒ Object (protected)
ConsoleLogging.
537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 |
# File 'lib/msf/ui/console/driver.rb', line 537 def handle_console_logging(val) if (val =~ /^(y|t|1)/i) Msf::Logging.enable_log_source('console') print_line("Console logging is now enabled.") set_log_source('console') rlog("\n[*] Console logging started: #{Time.now}\n\n", 'console') else rlog("\n[*] Console logging stopped: #{Time.now}\n\n", 'console') unset_log_source Msf::Logging.disable_log_source('console') print_line("Console logging is now disabled.") end end |
#handle_loglevel(val) ⇒ Object (protected)
This method handles adjusting the global log level threshold.
558 559 560 561 |
# File 'lib/msf/ui/console/driver.rb', line 558 def handle_loglevel(val) set_log_level(Rex::LogSource, val) set_log_level(Msf::LogSource, val) end |
#handle_payload(val) ⇒ Object (protected)
This method handles setting a desired payload
TODO: Move this out of the console driver!
568 569 570 571 572 573 574 575 576 577 578 |
# File 'lib/msf/ui/console/driver.rb', line 568 def handle_payload(val) if framework && !framework.payloads.valid?(val) return false elsif active_module && (active_module.exploit? || active_module.evasion?) return false unless active_module.is_payload_compatible?(val) elsif active_module && !framework.features.enabled?(Msf::FeatureManager::DATASTORE_FALLBACKS) active_module.datastore.clear_non_user_defined elsif framework && !framework.features.enabled?(Msf::FeatureManager::DATASTORE_FALLBACKS) framework.datastore.clear_non_user_defined end end |
#handle_session_logging(val) ⇒ Object (protected)
SessionLogging.
524 525 526 527 528 529 530 531 532 |
# File 'lib/msf/ui/console/driver.rb', line 524 def handle_session_logging(val) if (val =~ /^(y|t|1)/i) Msf::Logging.enable_session_logging(true) print_line("Session logging will be enabled for future sessions.") else Msf::Logging.enable_session_logging(false) print_line("Session logging will be disabled for future sessions.") end end |
#handle_session_tlv_logging(val) ⇒ Object (protected)
608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 |
# File 'lib/msf/ui/console/driver.rb', line 608 def handle_session_tlv_logging(val) return false if val.nil? if val.casecmp?('console') || val.casecmp?('true') || val.casecmp?('false') return true elsif val.start_with?('file:') && !val.split('file:').empty? pathname = ::Pathname.new(val.split('file:').last) # Check if we want to write the log to file if ::File.file?(pathname) if ::File.writable?(pathname) return true else print_status "No write permissions for log output file: #{pathname}" return false end # Check if we want to write the log file to a directory elsif ::File.directory?(pathname) if ::File.writable?(pathname) return true else print_status "No write permissions for log output directory: #{pathname}" return false end # Check if the subdirectory exists elsif ::File.directory?(pathname.dirname) if ::File.writable?(pathname.dirname) return true else print_status "No write permissions for log output directory: #{pathname.dirname}" return false end else # Else the directory doesn't exist. Check if we can create it. begin ::FileUtils.mkdir_p(pathname.dirname) return true rescue ::StandardError => e print_status "Error when trying to create directory #{pathname.dirname}: #{e.}" return false end end end false end |
#handle_ssh_ident(val) ⇒ Object (protected)
This method monkeypatches Net::SSH’s client identification string
TODO: Move this out of the console driver!
585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 |
# File 'lib/msf/ui/console/driver.rb', line 585 def handle_ssh_ident(val) # HACK: Suppress already initialized constant warning verbose, $VERBOSE = $VERBOSE, nil return false unless val.is_a?(String) && !val.empty? require 'net/ssh' # HACK: Bypass dynamic constant assignment error ::Net::SSH::Transport::ServerVersion.const_set(:PROTO_VERSION, val) true rescue LoadError print_error('Net::SSH could not be loaded') false rescue NameError print_error('Invalid constant Net::SSH::Transport::ServerVersion::PROTO_VERSION') false ensure # Restore warning $VERBOSE = verbose end |
#load_config(path = nil) ⇒ Object
Loads configuration for the console.
232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 |
# File 'lib/msf/ui/console/driver.rb', line 232 def load_config(path=nil) begin conf = Msf::Config.load(path) rescue wlog("Failed to load configuration: #{$!}") return end # If we have configuration, process it if (conf.group?(ConfigGroup)) conf[ConfigGroup].each_pair { |k, v| case k.downcase when 'activemodule' run_single("use #{v}") when 'activeworkspace' if framework.db.active workspace = framework.db.find_workspace(v) framework.db.workspace = workspace if workspace end end } end end |
#load_preconfig ⇒ Object
Loads configuration that needs to be analyzed before the framework instance is created.
214 215 216 217 218 219 220 221 222 223 224 225 226 227 |
# File 'lib/msf/ui/console/driver.rb', line 214 def load_preconfig begin conf = Msf::Config.load rescue wlog("Failed to load configuration: #{$!}") return end if (conf.group?(ConfigCore)) conf[ConfigCore].each_pair { |k, v| on_variable_set(true, k, v) } end end |
#on_startup(opts = {}) ⇒ Object
Called before things actually get rolling such that banners can be displayed, scripts can be processed, and other fun can be had.
336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 |
# File 'lib/msf/ui/console/driver.rb', line 336 def on_startup(opts = {}) # Check for modules that failed to load if framework.modules.module_load_error_by_path.length > 0 wlog("The following modules could not be loaded!") framework.modules.module_load_error_by_path.each do |path, _error| wlog("\t#{path}") 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| wlog("\t#{path}") end end if framework.db&.active framework.db.workspace = framework.db.default_workspace unless framework.db.workspace end framework.events.on_ui_start(Msf::Framework::Revision) if $msf_spinner_thread $msf_spinner_thread.kill $stderr.print "\r" + (" " * 50) + "\n" end run_single("banner") unless opts['DisableBanner'] if framework.eicar_corrupted? opts["Plugins"].each do |plug| run_single("load '#{plug}'") end if opts["Plugins"] self.on_command_proc = Proc.new { |command| framework.events.on_ui_command(command) } end |
#on_variable_set(glob, var, val) ⇒ Object
Called when a variable is set to a specific value. This allows the console to do extra processing, such as enabling logging or doing some other kind of task. If this routine returns false it will indicate that the variable is not being set to a valid value.
392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 |
# File 'lib/msf/ui/console/driver.rb', line 392 def on_variable_set(glob, var, val) case var.downcase when 'sessionlogging' handle_session_logging(val) if glob when 'sessiontlvlogging' handle_session_tlv_logging(val) if glob when 'consolelogging' handle_console_logging(val) if glob when 'loglevel' handle_loglevel(val) if glob when 'payload' handle_payload(val) when 'ssh_ident' handle_ssh_ident(val) end end |
#on_variable_unset(glob, var) ⇒ Object
Called when a variable is unset. If this routine returns false it is an indication that the variable should not be allowed to be unset.
413 414 415 416 417 418 419 420 421 422 423 424 |
# File 'lib/msf/ui/console/driver.rb', line 413 def on_variable_unset(glob, var) case var.downcase when 'sessionlogging' handle_session_logging('0') if glob when 'sessiontlvlogging' handle_session_tlv_logging('false') if glob when 'consolelogging' handle_console_logging('0') if glob when 'loglevel' handle_loglevel(nil) if glob end end |
#run_unknown_command(command) ⇒ Object (protected)
509 510 511 512 513 |
# File 'lib/msf/ui/console/driver.rb', line 509 def run_unknown_command(command) print_status("exec: #{command}") print_line('') system(command) end |
#save_config ⇒ Object
Saves configuration for the console.
287 288 289 290 291 292 293 |
# File 'lib/msf/ui/console/driver.rb', line 287 def save_config begin Msf::Config.save(ConfigGroup => get_config) rescue ::Exception print_error("Failed to save console config: #{$!}") end end |
#save_recent_history(path) ⇒ Object
Saves the recent history to the specified file
298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 |
# File 'lib/msf/ui/console/driver.rb', line 298 def save_recent_history(path) num = Readline::HISTORY.length - hist_last_saved - 1 tmprc = "" num.times { |x| tmprc << Readline::HISTORY[hist_last_saved + x] + "\n" } if tmprc.length > 0 print_status("Saving last #{num} commands to #{path} ...") save_resource(tmprc, path) else print_error("No commands to save!") end # Always update this, even if we didn't save anything. We do this # so that we don't end up saving the "makerc" command itself. self.hist_last_saved = Readline::HISTORY.length end |
#save_resource(data, path = nil) ⇒ Object
Creates the resource script file for the console.
321 322 323 324 325 326 327 328 329 330 |
# File 'lib/msf/ui/console/driver.rb', line 321 def save_resource(data, path=nil) path ||= File.join(Msf::Config.config_directory, 'msfconsole.rc') begin rcfd = File.open(path, 'w') rcfd.write(data) rcfd.close rescue ::Exception end end |
#stop ⇒ Object
462 463 464 465 |
# File 'lib/msf/ui/console/driver.rb', line 462 def stop framework.events.on_ui_stop() super end |
#unknown_command(method, line) ⇒ Object (protected)
If an unknown command was passed, try to see if it’s a valid local executable. This is only allowed if command passthru has been permitted
477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 |
# File 'lib/msf/ui/console/driver.rb', line 477 def unknown_command(method, line) if File.basename(method) == 'msfconsole' print_error('msfconsole cannot be run inside msfconsole') return end [method, method+".exe"].each do |cmd| if command_passthru && Rex::FileUtils.find_full_path(cmd) self.busy = true begin run_unknown_command(line) rescue ::Errno::EACCES, ::Errno::ENOENT print_error("Permission denied exec: #{line}") end self.busy = false return end end if framework.modules.create(method) super if prompt_yesno "This is a module we can load. Do you want to use #{method}?" run_single "use #{method}" end return end super end |
#update_prompt(*args) ⇒ Object
Proxies to shell.rb’s update prompt with our own extras
429 430 431 432 433 434 435 436 437 438 439 |
# File 'lib/msf/ui/console/driver.rb', line 429 def update_prompt(*args) if args.empty? pchar = framework.datastore['PromptChar'] || DefaultPromptChar p = framework.datastore['Prompt'] || DefaultPrompt p = "#{p} #{active_module.type}(%bld%red#{active_module.promptname}%clr)" if active_module super(p, pchar) else # Don't squash calls from within lib/rex/ui/text/shell.rb super(*args) end end |