CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!
CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!
Path: blob/master/lib/msf/ui/console/command_dispatcher/developer.rb
Views: 1904
# -*- 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) do132begin133reline_autocomplete = Reline.autocompletion134if active_module135print_status("You are in #{active_module.fullname}\n")136Rex::Ui::Text::IrbShell.new(active_module).run137else138print_status("You are in the \"framework\" object\n")139Rex::Ui::Text::IrbShell.new(framework).run140end141rescue142print_error("Error during IRB: #{$!}\n\n#{$@.join("\n")}")143ensure144Reline.autocompletion = reline_autocomplete if defined? reline_autocomplete145end146end147148# Reset tab completion149if (driver.input.supports_readline)150driver.input.reset_tab_completion151end152else153# XXX: No vprint_status here either154if framework.datastore['VERBOSE'].to_s == 'true'155print_status("You are executing expressions in #{binding.receiver}")156end157158expressions.each { |expression| eval(expression, binding) }159end160end161162#163# Tab completion for the irb command164#165def cmd_irb_tabs(_str, words)166return [] if words.length > 1167168@@irb_opts.option_keys169end170171def cmd_pry_help172print_line 'Usage: pry'173print_line174print_line 'Open the Pry debugger on the current module or Framework.'175print_line176end177178#179# Open the Pry debugger on the current module or Framework180#181def cmd_pry(*args)182if args.include?('-h')183cmd_pry_help184return185end186187begin188require 'pry'189rescue LoadError190print_error('Failed to load Pry, try "gem install pry"')191return192end193194print_status('Starting Pry shell...')195196Pry.config.history_load = false197framework.history_manager.with_context(history_file: Msf::Config.pry_history, name: :pry) do198if active_module199print_status("You are in the \"#{active_module.fullname}\" module object\n")200active_module.pry201else202print_status("You are in the \"framework\" object\n")203framework.pry204end205end206end207208def cmd_edit_help209print_line 'Usage: edit [file/to/edit]'210print_line211print_line "Edit the currently active module or a local file with #{local_editor}."212print_line 'To change the preferred editor, you can "setg LocalEditor".'213print_line 'If a library file is specified, it will automatically be reloaded after editing.'214print_line 'Otherwise, you can reload the active module with "reload" or "rerun".'215print_line216end217218#219# Edit the current module or a file with the preferred editor220#221def cmd_edit(*args)222editing_module = false223224if args.length > 0225path = File.expand_path(args[0])226elsif active_module227editing_module = true228path = active_module.file_path229end230231unless path232print_error('Nothing to edit. Try using a module first or specifying a library file to edit.')233return234end235236editor = local_editor237238unless editor239# ed(1) is the standard editor240editor = 'ed'241print_warning("LocalEditor or $VISUAL/$EDITOR should be set. Falling back on #{editor}.")242end243244# XXX: No vprint_status in this context?245# XXX: VERBOSE is a string instead of Bool??246print_status("Launching #{editor} #{path}") if framework.datastore['VERBOSE'].to_s == 'true'247248unless system(*editor.split, path)249print_error("Could not execute #{editor} #{path}")250return251end252253return if editing_module254255reload_file(path)256end257258#259# Tab completion for the edit command260#261def cmd_edit_tabs(str, words)262tab_complete_filenames(str, words)263end264265def cmd_reload_lib_help266cmd_reload_lib('-h')267end268269#270# Reload Ruby library files from specified paths271#272def cmd_reload_lib(*args)273files = []274options = OptionParser.new do |opts|275opts.banner = 'Usage: reload_lib lib/to/reload.rb [...]'276opts.separator ''277opts.separator 'Reload Ruby library files from specified paths.'278opts.separator ''279280opts.on '-h', '--help', 'Help banner.' do281return print(opts.help)282end283284opts.on '-a', '--all', 'Reload all* changed files in your current Git working tree.285*Excludes modules and non-Ruby files.' do286files.concat(modified_file_paths)287end288end289290# The remaining unparsed arguments are files291files.concat(options.order(args))292files.uniq!293294return print(options.help) if files.empty?295296files.each do |file|297reload_file(file)298rescue ScriptError, StandardError => e299print_error("Error while reloading file #{file.inspect}: #{e}:\n#{e.backtrace.to_a.map { |line| " #{line}" }.join("\n")}")300end301end302303#304# Tab completion for the reload_lib command305#306def cmd_reload_lib_tabs(str, words)307tab_complete_filenames(str, words)308end309310def cmd_log_help311print_line 'Usage: log'312print_line313print_line 'Display framework.log paged to the end if possible.'314print_line 'To change the preferred pager, you can "setg LocalPager".'315print_line 'For full effect, "setg LogLevel 3" before running modules.'316print_line317print_line "Log location: #{File.join(Msf::Config.log_directory, 'framework.log')}"318print_line319end320321#322# Display framework.log paged to the end if possible323#324def cmd_log(*args)325path = File.join(Msf::Config.log_directory, 'framework.log')326327# XXX: +G isn't portable and may hang on large files328pager = local_pager.to_s.include?('less') ? "#{local_pager} +G" : local_pager329330unless pager331pager = 'tail -n 50'332print_warning("LocalPager or $PAGER/$MANPAGER should be set. Falling back on #{pager}.")333end334335# XXX: No vprint_status in this context?336# XXX: VERBOSE is a string instead of Bool??337print_status("Launching #{pager} #{path}") if framework.datastore['VERBOSE'].to_s == 'true'338339unless system(*pager.split, path)340print_error("Could not execute #{pager} #{path}")341end342end343344#345# Interact with framework's service manager346#347def cmd__servicemanager(*args)348if args.include?('-h') || args.include?('--help')349cmd__servicemanager_help350return false351end352353opts = {}354@@_servicemanager_opts.parse(args) do |opt, idx, val|355case opt356when '-l', '--list'357opts[:list] = true358end359end360361if opts.empty?362opts[:list] = true363end364365if opts[:list]366table = Rex::Text::Table.new(367'Header' => 'Services',368'Indent' => 1,369'Columns' => ['Id', 'Name', 'References']370)371Rex::ServiceManager.instance.each.with_index do |(name, instance), id|372# TODO: Update rex-core to support querying the reference count373table << [id, name, instance.instance_variable_get(:@_references)]374end375376if table.rows.empty?377print_status("No framework services are currently running.")378else379print_line(table.to_s)380end381end382end383384#385# Tab completion for the _servicemanager command386#387def cmd__servicemanager_tabs(_str, words)388return [] if words.length > 1389390@@_servicemanager_opts.option_keys391end392393def cmd__servicemanager_help394print_line 'Usage: _servicemanager'395print_line396print_line 'Manage running framework services'397print @@_servicemanager_opts.usage398print_line399end400401#402# Interact with framework's history manager403#404def cmd__historymanager(*args)405if args.include?('-h') || args.include?('--help')406cmd__historymanager_help407return false408end409410opts = {}411@@_historymanager_opts.parse(args) do |opt, idx, val|412case opt413when '-l', '--list'414opts[:list] = true415when '-d', '--debug'416opts[:debug] = val.nil? ? true : val.downcase.start_with?(/t|y/)417end418end419420if opts.empty?421opts[:list] = true422end423424if opts.key?(:debug)425framework.history_manager._debug = opts[:debug]426print_status("HistoryManager debugging is now #{opts[:debug] ? 'on' : 'off'}")427end428429if opts[:list]430table = Rex::Text::Table.new(431'Header' => 'History contexts',432'Indent' => 1,433'Columns' => ['Id', 'File', 'Name']434)435framework.history_manager._contexts.each.with_index do |context, id|436table << [id, context[:history_file], context[:name]]437end438439if table.rows.empty?440print_status("No history contexts present.")441else442print_line(table.to_s)443end444end445end446447#448# Tab completion for the _historymanager command449#450def cmd__historymanager_tabs(_str, words)451return [] if words.length > 1452453@@_historymanager_opts.option_keys454end455456def cmd__historymanager_help457print_line 'Usage: _historymanager'458print_line459print_line 'Manage the history manager'460print @@_historymanager_opts.usage461print_line462end463464#465# Time how long in seconds a command takes to execute466#467def cmd_time(*args)468if args.empty? || args.first == '-h' || args.first == '--help'469cmd_time_help470return true471end472473profiler = nil474while args.first == '--cpu' || args.first == '--memory'475profiler = args.shift476end477478begin479start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)480command = Shellwords.shelljoin(args)481482case profiler483when '--cpu'484Metasploit::Framework::Profiler.record_cpu do485driver.run_single(command)486end487when '--memory'488Metasploit::Framework::Profiler.record_memory do489driver.run_single(command)490end491else492driver.run_single(command)493end494ensure495end_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)496elapsed_time = end_time - start_time497print_good("Command #{command.inspect} completed in #{elapsed_time} seconds")498end499end500501def cmd_time_help502print_line 'Usage: time [options] [command]'503print_line504print_line 'Time how long a command takes to execute in seconds. Also supports profiling options.'505print_line506print_line ' Usage:'507print_line ' * time db_import ./db_import.html'508print_line ' * time show exploits'509print_line ' * time reload_all'510print_line ' * time missing_command'511print_line ' * time --cpu db_import ./db_import.html'512print_line ' * time --memory db_import ./db_import.html'513print @@time_opts.usage514print_line515end516517private518519def modified_files520# Temporary work-around until Open3 gets fixed on Windows 11:521# https://github.com/ruby/open3/issues/9522return [] if Rex::Compat.is_cygwin || Rex::Compat.is_windows523524# Using an array avoids shelling out, so we avoid escaping/quoting525changed_files = %w[git diff --name-only]526begin527output, status = Open3.capture2e(*changed_files, chdir: Msf::Config.install_root)528is_success = status.success?529output = output.split("\n")530rescue => e531elog(e)532output = []533is_success = false534end535return output, is_success536end537end538539540