diff --git a/lib/msf/base/config.rb b/lib/msf/base/config.rb index d1fa415459..faa3d250c9 100644 --- a/lib/msf/base/config.rb +++ b/lib/msf/base/config.rb @@ -202,6 +202,13 @@ class Config < Hash self.new.history_file end + # Returns the full path to the handler file. + # + # @return [String] path the handler file. + def self.persist_file + self.new.persist_file + end + # Initializes configuration, creating directories as necessary. # # @return [void] @@ -278,6 +285,13 @@ class Config < Hash config_directory + FileSep + "history" end + # Returns the full path to the handler file. + # + # @return [String] path the handler file. + def persist_file + config_directory + FileSep + "persist" + end + # Returns the global module directory. # # @return [String] path to global module directory. diff --git a/lib/msf/base/serializer/readable_text.rb b/lib/msf/base/serializer/readable_text.rb index 52a5aea32e..4551d9d278 100644 --- a/lib/msf/base/serializer/readable_text.rb +++ b/lib/msf/base/serializer/readable_text.rb @@ -781,10 +781,10 @@ class ReadableText # @param col [Integer] the column wrap width. # @return [String] the formatted list of running jobs. def self.dump_jobs(framework, verbose = false, indent = DefaultIndent, col = DefaultColumnWrap) - columns = [ 'Id', 'Name', "Payload", "Payload opts" ] + columns = [ 'Id', 'Name', "Payload", "Payload opts"] if (verbose) - columns += [ "URIPATH", "Start Time", "Handler opts" ] + columns += [ "URIPATH", "Start Time", "Handler opts", "Persist" ] end tbl = Rex::Text::Table.new( @@ -793,6 +793,15 @@ class ReadableText 'Columns' => columns ) + # Get the persistent job info. + if verbose + begin + persist_list = JSON.parse(File.read(Msf::Config.persist_file)) + rescue Errno::ENOENT, JSON::ParserError + persist_list = [] + end + end + # jobs are stored as a hash with the keys being a numeric String job_id. framework.jobs.keys.sort_by(&:to_i).each do |job_id| # Job context is stored as an Array with the 0th element being @@ -827,11 +836,17 @@ class ReadableText row[4] = uripath row[5] = framework.jobs[job_id].start_time row[6] = '' + row[7] = 'false' if pinst.respond_to?(:listener_uri) listener_uri = pinst.listener_uri.strip row[6] = listener_uri unless listener_uri == payload_uri end + + persist_list.each do |e| + row[7] = 'true' if e['mod_options']['Options'] == framework.jobs[job_id.to_s].ctx[1].datastore + end + end tbl << row end diff --git a/lib/msf/ui/console/command_dispatcher/jobs.rb b/lib/msf/ui/console/command_dispatcher/jobs.rb index 797e0b9192..d8777cda15 100644 --- a/lib/msf/ui/console/command_dispatcher/jobs.rb +++ b/lib/msf/ui/console/command_dispatcher/jobs.rb @@ -35,7 +35,9 @@ module Msf "-i" => [ true, "Lists detailed information about a running job."], "-l" => [ false, "List all running jobs." ], "-v" => [ false, "Print more detailed info. Use with -i and -l" ], - "-S" => [ true, "Row search filter." ], + "-p" => [ true, "Add persistence to job by job ID" ], + "-P" => [ false, "Persist all running jobs on restart." ], + "-S" => [ true, "Row search filter." ] ) def commands @@ -117,7 +119,9 @@ module Msf verbose = false dump_list = false dump_info = false + kill_job = false job_id = nil + job_list = nil # Parse the command options @@jobs_opts.parse(args) do |opt, _idx, val| @@ -129,29 +133,31 @@ module Msf # Terminate the supplied job ID(s) when "-k" job_list = build_range_array(val) - if job_list.blank? - print_error("Please specify valid job identifier(s)") - return false - end - print_status("Stopping the following job(s): #{job_list.join(', ')}") - job_list.map(&:to_s).each do |job| - if framework.jobs.key?(job) - print_status("Stopping job #{job}") - framework.jobs.stop_job(job) - else - print_error("Invalid job identifier: #{job}") - end - end + kill_job = true when "-K" print_line("Stopping all jobs...") framework.jobs.each_key do |i| framework.jobs.stop_job(i) end + File.truncate(Msf::Config.persist_file,0) when "-i" # Defer printing anything until the end of option parsing # so we can check for the verbose flag. dump_info = true job_id = val + when "-p" + job_list = build_range_array(val) + job_list.each do |job_id| + add_persist_job(job_id) + end + when "-P" + print_line("Making all jobs persistent ...") + job_list = framework.jobs.map do |k,v| + v.jid.to_s + end + job_list.each do |job_id| + add_persist_job(job_id) + end when "-S", "--search" search_term = val dump_list = true @@ -159,6 +165,7 @@ module Msf cmd_jobs_help return false end + end if dump_list @@ -186,6 +193,77 @@ module Msf print_line("Invalid Job ID") end end + + if kill_job + if job_list.blank? + print_error("Please specify valid job identifier(s)") + return false + end + + print_status("Stopping the following job(s): #{job_list.join(', ')}") + + # Remove the persistent job when match the option of payload. + begin + persist_list = JSON.parse(File.read(Msf::Config.persist_file)) + rescue Errno::ENOENT, JSON::ParserError + persist_list = [] + end + + job_list.map(&:to_s).each do |job| + payload_option = framework.jobs[job.to_s].ctx[1].datastore + persist_list.delete_if{|pjob|pjob['mod_options']['Options'] == payload_option} + end + # Write persist job back to config file. + File.open(Msf::Config.persist_file,"w") do |file| + file.puts(JSON.pretty_generate(persist_list)) + end + + # Stop the job by job id. + job_list.map(&:to_s).each do |job| + if framework.jobs.key?(job) + print_status("Stopping job #{job}") + framework.jobs.stop_job(job) + else + print_error("Invalid job identifier: #{job}") + end + end + end + + end + + # + # Add a persistent job by job id. + # Persistent job would restore on console restarted. + + def add_persist_job(job_id) + if job_id && framework.jobs.has_key?(job_id.to_s) + mod = framework.jobs[job_id.to_s].ctx[0].replicant + payload = framework.jobs[job_id.to_s].ctx[1].replicant + + payload_opts = { + 'Payload' => payload.refname, + 'Options' => payload.datastore, + 'RunAsJob' => true + } + + mod_opts = { + 'mod_name' => mod.fullname, + 'mod_options' => payload_opts + } + + begin + persist_list = JSON.parse(File.read(Msf::Config.persist_file)) + rescue Errno::ENOENT, JSON::ParserError + persist_list = [] + end + persist_list << mod_opts + File.open(Msf::Config.persist_file,"w") do |file| + file.puts(JSON.pretty_generate(persist_list)) + end + print_line("Added persistence to job #{job_id}.") + else + print_line("Invalid Job ID") + end end # @@ -354,6 +432,7 @@ module Msf } tab_complete_generic(fmt, str, words) end + end end end diff --git a/lib/msf/ui/console/driver.rb b/lib/msf/ui/console/driver.rb index 7da0632e19..3210d6dd0c 100644 --- a/lib/msf/ui/console/driver.rb +++ b/lib/msf/ui/console/driver.rb @@ -179,6 +179,22 @@ class Driver < Msf::Ui::Driver } 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 + + unless restore_handlers.nil? + 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|