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/command_dispatcher/developer.rb
Views: 11784
# -*- coding: binary -*-12class Msf::Ui::Console::CommandDispatcher::Developer34include Msf::Ui::Console::CommandDispatcher56@@irb_opts = Rex::Parser::Arguments.new(7'-h' => [false, 'Help menu.' ],8'-e' => [true, 'Expression to evaluate.']9)1011@@time_opts = Rex::Parser::Arguments.new(12['-h', '--help'] => [ false, 'Help banner.' ],13'--cpu' => [false, 'Profile the CPU usage.'],14'--memory' => [false, 'Profile the memory usage.']15)1617@@_servicemanager_opts = Rex::Parser::Arguments.new(18['-l', '--list'] => [false, 'View the currently running services' ]19)2021@@_historymanager_opts = Rex::Parser::Arguments.new(22'-h' => [false, 'Help menu.' ],23['-l', '--list'] => [true, 'View the current history manager contexts.'],24['-d', '--debug'] => [true, 'Debug the current history manager contexts.']25)2627def initialize(driver)28super29@modified_files = modified_file_paths(print_errors: false)30end3132def name33'Developer'34end3536def commands37commands = {38'irb' => 'Open an interactive Ruby shell in the current context',39'pry' => 'Open the Pry debugger on the current module or Framework',40'edit' => 'Edit the current module or a file with the preferred editor',41'reload_lib' => 'Reload Ruby library files from specified paths',42'log' => 'Display framework.log paged to the end if possible',43'time' => 'Time how long it takes to run a particular command'44}45if framework.features.enabled?(Msf::FeatureManager::MANAGER_COMMANDS)46commands['_servicemanager'] = 'Interact with the Rex::ServiceManager'47commands['_historymanager'] = 'Interact with the Rex::Ui::Text::Shell::HistoryManager'48end49commands50end5152def local_editor53framework.datastore['LocalEditor'] ||54Rex::Compat.getenv('VISUAL') ||55Rex::Compat.getenv('EDITOR') ||56Msf::Util::Helper.which('vim') ||57Msf::Util::Helper.which('vi')58end5960def local_pager61framework.datastore['LocalPager'] ||62Rex::Compat.getenv('PAGER') ||63Rex::Compat.getenv('MANPAGER') ||64Msf::Util::Helper.which('less') ||65Msf::Util::Helper.which('more')66end6768# XXX: This will try to reload *any* .rb and break on modules69def reload_file(path, print_errors: true)70full_path = File.expand_path(path)7172unless File.exist?(full_path) && full_path.end_with?('.rb')73print_error("#{full_path} must exist and be a .rb file") if print_errors74return75end7677# The file must exist to reach this, so we try our best here78if full_path.start_with?(Msf::Config.module_directory, Msf::Config.user_module_directory)79print_error('Reloading Metasploit modules is not supported (try "reload")') if print_errors80return81end8283print_status("Reloading #{full_path}")84load full_path85end8687# @return [Array<String>] The list of modified file paths since startup88def modified_file_paths(print_errors: true)89files, is_success = modified_files9091unless is_success92print_error("Git is not available") if print_errors93files = []94end9596@modified_files ||= []97@modified_files |= files.map do |file|98next if file.end_with?('_spec.rb') || file.end_with?("spec_helper.rb")99File.join(Msf::Config.install_root, file)100end.compact101@modified_files102end103104def cmd_irb_help105print_line 'Usage: irb'106print_line107print_line 'Open an interactive Ruby shell in the current context.'108print @@irb_opts.usage109end110111#112# Open an interactive Ruby shell in the current context113#114def cmd_irb(*args)115expressions = []116117# Parse the command options118@@irb_opts.parse(args) do |opt, idx, val|119case opt120when '-e'121expressions << val122when '-h'123cmd_irb_help124return false125end126end127128if expressions.empty?129print_status('Starting IRB shell...')130131framework.history_manager.with_context(name: :irb) do132begin133if active_module134print_status("You are in #{active_module.fullname}\n")135Rex::Ui::Text::IrbShell.new(active_module).run136else137print_status("You are in the \"framework\" object\n")138Rex::Ui::Text::IrbShell.new(framework).run139end140rescue141print_error("Error during IRB: #{$!}\n\n#{$@.join("\n")}")142end143end144145# Reset tab completion146if (driver.input.supports_readline)147driver.input.reset_tab_completion148end149else150# XXX: No vprint_status here either151if framework.datastore['VERBOSE'].to_s == 'true'152print_status("You are executing expressions in #{binding.receiver}")153end154155expressions.each { |expression| eval(expression, binding) }156end157end158159#160# Tab completion for the irb command161#162def cmd_irb_tabs(_str, words)163return [] if words.length > 1164165@@irb_opts.option_keys166end167168def cmd_pry_help169print_line 'Usage: pry'170print_line171print_line 'Open the Pry debugger on the current module or Framework.'172print_line173end174175#176# Open the Pry debugger on the current module or Framework177#178def cmd_pry(*args)179if args.include?('-h')180cmd_pry_help181return182end183184begin185require 'pry'186rescue LoadError187print_error('Failed to load Pry, try "gem install pry"')188return189end190191print_status('Starting Pry shell...')192193Pry.config.history_load = false194framework.history_manager.with_context(history_file: Msf::Config.pry_history, name: :pry) do195if active_module196print_status("You are in the \"#{active_module.fullname}\" module object\n")197active_module.pry198else199print_status("You are in the \"framework\" object\n")200framework.pry201end202end203end204205def cmd_edit_help206print_line 'Usage: edit [file/to/edit]'207print_line208print_line "Edit the currently active module or a local file with #{local_editor}."209print_line 'To change the preferred editor, you can "setg LocalEditor".'210print_line 'If a library file is specified, it will automatically be reloaded after editing.'211print_line 'Otherwise, you can reload the active module with "reload" or "rerun".'212print_line213end214215#216# Edit the current module or a file with the preferred editor217#218def cmd_edit(*args)219editing_module = false220221if args.length > 0222path = File.expand_path(args[0])223elsif active_module224editing_module = true225path = active_module.file_path226end227228unless path229print_error('Nothing to edit. Try using a module first or specifying a library file to edit.')230return231end232233editor = local_editor234235unless editor236# ed(1) is the standard editor237editor = 'ed'238print_warning("LocalEditor or $VISUAL/$EDITOR should be set. Falling back on #{editor}.")239end240241# XXX: No vprint_status in this context?242# XXX: VERBOSE is a string instead of Bool??243print_status("Launching #{editor} #{path}") if framework.datastore['VERBOSE'].to_s == 'true'244245unless system(*editor.split, path)246print_error("Could not execute #{editor} #{path}")247return248end249250return if editing_module251252reload_file(path)253end254255#256# Tab completion for the edit command257#258def cmd_edit_tabs(str, words)259tab_complete_filenames(str, words)260end261262def cmd_reload_lib_help263cmd_reload_lib('-h')264end265266#267# Reload Ruby library files from specified paths268#269def cmd_reload_lib(*args)270files = []271options = OptionParser.new do |opts|272opts.banner = 'Usage: reload_lib lib/to/reload.rb [...]'273opts.separator ''274opts.separator 'Reload Ruby library files from specified paths.'275opts.separator ''276277opts.on '-h', '--help', 'Help banner.' do278return print(opts.help)279end280281opts.on '-a', '--all', 'Reload all* changed files in your current Git working tree.282*Excludes modules and non-Ruby files.' do283files.concat(modified_file_paths)284end285end286287# The remaining unparsed arguments are files288files.concat(options.order(args))289files.uniq!290291return print(options.help) if files.empty?292293files.each do |file|294reload_file(file)295rescue ScriptError, StandardError => e296print_error("Error while reloading file #{file.inspect}: #{e}:\n#{e.backtrace.to_a.map { |line| " #{line}" }.join("\n")}")297end298end299300#301# Tab completion for the reload_lib command302#303def cmd_reload_lib_tabs(str, words)304tab_complete_filenames(str, words)305end306307def cmd_log_help308print_line 'Usage: log'309print_line310print_line 'Display framework.log paged to the end if possible.'311print_line 'To change the preferred pager, you can "setg LocalPager".'312print_line 'For full effect, "setg LogLevel 3" before running modules.'313print_line314print_line "Log location: #{File.join(Msf::Config.log_directory, 'framework.log')}"315print_line316end317318#319# Display framework.log paged to the end if possible320#321def cmd_log(*args)322path = File.join(Msf::Config.log_directory, 'framework.log')323324# XXX: +G isn't portable and may hang on large files325pager = local_pager.to_s.include?('less') ? "#{local_pager} +G" : local_pager326327unless pager328pager = 'tail -n 50'329print_warning("LocalPager or $PAGER/$MANPAGER should be set. Falling back on #{pager}.")330end331332# XXX: No vprint_status in this context?333# XXX: VERBOSE is a string instead of Bool??334print_status("Launching #{pager} #{path}") if framework.datastore['VERBOSE'].to_s == 'true'335336unless system(*pager.split, path)337print_error("Could not execute #{pager} #{path}")338end339end340341#342# Interact with framework's service manager343#344def cmd__servicemanager(*args)345if args.include?('-h') || args.include?('--help')346cmd__servicemanager_help347return false348end349350opts = {}351@@_servicemanager_opts.parse(args) do |opt, idx, val|352case opt353when '-l', '--list'354opts[:list] = true355end356end357358if opts.empty?359opts[:list] = true360end361362if opts[:list]363table = Rex::Text::Table.new(364'Header' => 'Services',365'Indent' => 1,366'Columns' => ['Id', 'Name', 'References']367)368Rex::ServiceManager.instance.each.with_index do |(name, instance), id|369# TODO: Update rex-core to support querying the reference count370table << [id, name, instance.instance_variable_get(:@_references)]371end372373if table.rows.empty?374print_status("No framework services are currently running.")375else376print_line(table.to_s)377end378end379end380381#382# Tab completion for the _servicemanager command383#384def cmd__servicemanager_tabs(_str, words)385return [] if words.length > 1386387@@_servicemanager_opts.option_keys388end389390def cmd__servicemanager_help391print_line 'Usage: _servicemanager'392print_line393print_line 'Manage running framework services'394print @@_servicemanager_opts.usage395print_line396end397398#399# Interact with framework's history manager400#401def cmd__historymanager(*args)402if args.include?('-h') || args.include?('--help')403cmd__historymanager_help404return false405end406407opts = {}408@@_historymanager_opts.parse(args) do |opt, idx, val|409case opt410when '-l', '--list'411opts[:list] = true412when '-d', '--debug'413opts[:debug] = val.nil? ? true : val.downcase.start_with?(/t|y/)414end415end416417if opts.empty?418opts[:list] = true419end420421if opts.key?(:debug)422framework.history_manager._debug = opts[:debug]423print_status("HistoryManager debugging is now #{opts[:debug] ? 'on' : 'off'}")424end425426if opts[:list]427table = Rex::Text::Table.new(428'Header' => 'History contexts',429'Indent' => 1,430'Columns' => ['Id', 'File', 'Name']431)432framework.history_manager._contexts.each.with_index do |context, id|433table << [id, context[:history_file], context[:name]]434end435436if table.rows.empty?437print_status("No history contexts present.")438else439print_line(table.to_s)440end441end442end443444#445# Tab completion for the _historymanager command446#447def cmd__historymanager_tabs(_str, words)448return [] if words.length > 1449450@@_historymanager_opts.option_keys451end452453def cmd__historymanager_help454print_line 'Usage: _historymanager'455print_line456print_line 'Manage the history manager'457print @@_historymanager_opts.usage458print_line459end460461#462# Time how long in seconds a command takes to execute463#464def cmd_time(*args)465if args.empty? || args.first == '-h' || args.first == '--help'466cmd_time_help467return true468end469470profiler = nil471while args.first == '--cpu' || args.first == '--memory'472profiler = args.shift473end474475begin476start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)477command = Shellwords.shelljoin(args)478479case profiler480when '--cpu'481Metasploit::Framework::Profiler.record_cpu do482driver.run_single(command)483end484when '--memory'485Metasploit::Framework::Profiler.record_memory do486driver.run_single(command)487end488else489driver.run_single(command)490end491ensure492end_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)493elapsed_time = end_time - start_time494print_good("Command #{command.inspect} completed in #{elapsed_time} seconds")495end496end497498def cmd_time_help499print_line 'Usage: time [options] [command]'500print_line501print_line 'Time how long a command takes to execute in seconds. Also supports profiling options.'502print_line503print_line ' Usage:'504print_line ' * time db_import ./db_import.html'505print_line ' * time show exploits'506print_line ' * time reload_all'507print_line ' * time missing_command'508print_line ' * time --cpu db_import ./db_import.html'509print_line ' * time --memory db_import ./db_import.html'510print @@time_opts.usage511print_line512end513514private515516def modified_files517# Using an array avoids shelling out, so we avoid escaping/quoting518changed_files = %w[git diff --name-only]519begin520output, status = Open3.capture2e(*changed_files, chdir: Msf::Config.install_root)521is_success = status.success?522output = output.split("\n")523rescue => e524elog(e)525output = []526is_success = false527end528return output, is_success529end530end531532533