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/jobs.rb
Views: 11788
# frozen_string_literal: true1# -*- coding: binary -*-23#4# Rex5#678module Msf9module Ui10module Console11module CommandDispatcher12#13# {CommandDispatcher} for commands related to background jobs in Metasploit Framework.14#15class Jobs16include Msf::Ui::Console::CommandDispatcher17include Msf::Ui::Console::CommandDispatcher::Common1819@@handler_opts = Rex::Parser::Arguments.new(20"-h" => [ false, "Help Banner"],21"-x" => [ false, "Shut the Handler down after a session is established"],22"-p" => [ true, "The payload to configure the handler for"],23"-P" => [ true, "The RPORT/LPORT to configure the handler for"],24"-H" => [ true, "The RHOST/LHOST to configure the handler for"],25"-e" => [ true, "An Encoder to use for Payload Stage Encoding"],26"-n" => [ true, "The custom name to give the handler job"]27)2829@@jobs_opts = Rex::Parser::Arguments.new(30"-h" => [ false, "Help banner." ],31"-k" => [ true, "Terminate jobs by job ID and/or range." ],32"-K" => [ false, "Terminate all running jobs." ],33"-i" => [ true, "Lists detailed information about a running job."],34"-l" => [ false, "List all running jobs." ],35"-v" => [ false, "Print more detailed info. Use with -i and -l" ],36"-p" => [ true, "Add persistence to job by job ID" ],37"-P" => [ false, "Persist all running jobs on restart." ],38"-S" => [ true, "Row search filter." ]39)4041def commands42{43"jobs" => "Displays and manages jobs",44"rename_job" => "Rename a job",45"kill" => "Kill a job",46"handler" => "Start a payload handler as job"47}48end4950#51# Returns the name of the command dispatcher.52#53def name54"Job"55end5657def cmd_rename_job_help58print_line "Usage: rename_job [ID] [Name]"59print_line60print_line "Example: rename_job 0 \"meterpreter HTTPS special\""61print_line62print_line "Rename a job that's currently active."63print_line "You may use the jobs command to see what jobs are available."64print_line65end6667def cmd_rename_job(*args)68if args.include?('-h') || args.length != 2 || args[0] !~ /^\d+$/69cmd_rename_job_help70return false71end7273job_id = args[0].to_s74job_name = args[1].to_s7576unless framework.jobs[job_id]77print_error("Job #{job_id} does not exist.")78return false79end8081# This is not respecting the Protected access control, but this seems to be the only way82# to rename a job. If you know a more appropriate way, patches accepted.83framework.jobs[job_id].send(:name=, job_name)84print_status("Job #{job_id} updated")8586true87end8889#90# Tab completion for the rename_job command91#92# @param str [String] the string currently being typed before tab was hit93# @param words [Array<String>] the previously completed words on the command line. words is always94# at least 1 when tab completion has reached this stage since the command itself has been completed9596def cmd_rename_job_tabs(_str, words)97return [] if words.length > 198framework.jobs.keys99end100101def cmd_jobs_help102print_line "Usage: jobs [options]"103print_line104print_line "Active job manipulation and interaction."105print @@jobs_opts.usage106end107108#109# Displays and manages running jobs for the active instance of the110# framework.111#112def cmd_jobs(*args)113# Make the default behavior listing all jobs if there were no options114# or the only option is the verbose flag115args.unshift("-l") if args.empty? || args == ["-v"]116117verbose = false118dump_list = false119dump_info = false120kill_job = false121job_id = nil122job_list = nil123124# Parse the command options125@@jobs_opts.parse(args) do |opt, _idx, val|126case opt127when "-v"128verbose = true129when "-l"130dump_list = true131# Terminate the supplied job ID(s)132when "-k"133job_list = build_range_array(val)134kill_job = true135when "-K"136print_line("Stopping all jobs...")137framework.jobs.each_key do |i|138framework.jobs.stop_job(i)139end140File.write(Msf::Config.persist_file, '') if File.writable?(Msf::Config.persist_file)141when "-i"142# Defer printing anything until the end of option parsing143# so we can check for the verbose flag.144dump_info = true145job_id = val146when "-p"147job_list = build_range_array(val)148if job_list.blank?149print_error('Please specify valid job identifier(s)')150return151end152job_list.each do |job_id|153add_persist_job(job_id)154end155when "-P"156print_line("Making all jobs persistent ...")157job_list = framework.jobs.map do |k,v|158v.jid.to_s159end160job_list.each do |job_id|161add_persist_job(job_id)162end163when "-S", "--search"164search_term = val165dump_list = true166when "-h"167cmd_jobs_help168return false169end170171end172173if dump_list174print("\n#{Serializer::ReadableText.dump_jobs(framework, verbose)}\n")175end176if dump_info177if job_id && framework.jobs[job_id.to_s]178job = framework.jobs[job_id.to_s]179mod = job.ctx[0]180181output = "\n"182output += "Name: #{mod.name}"183output += ", started at #{job.start_time}" if job.start_time184print_line(output)185186show_options(mod) if mod.options.has_options?187188if verbose189mod_opt = Serializer::ReadableText.dump_advanced_options(mod, ' ')190if mod_opt && !mod_opt.empty?191print_line("\nModule advanced options:\n\n#{mod_opt}\n")192end193end194else195print_line("Invalid Job ID")196end197end198199if kill_job200if job_list.blank?201print_error("Please specify valid job identifier(s)")202return false203end204205print_status("Stopping the following job(s): #{job_list.join(', ')}")206207# Remove the persistent job when match the option of payload.208begin209persist_list = JSON.parse(File.read(Msf::Config.persist_file))210rescue Errno::ENOENT, JSON::ParserError211persist_list = []212end213214# Remove persistence by job id.215job_list.map(&:to_s).each do |job_id|216job_id = job_id.to_i < 0 ? framework.jobs.keys[job_id.to_i] : job_id217if framework.jobs.key?(job_id)218ctx_1 = framework.jobs[job_id.to_s].ctx[1]219next if ctx_1.nil? || !ctx_1.respond_to?(:datastore) # next if no payload context in the job220payload_option = ctx_1.datastore221persist_list.delete_if{|pjob|pjob['mod_options']['Options'] == payload_option.to_h}222end223end224# Write persist job back to config file.225File.open(Msf::Config.persist_file,"w") do |file|226file.puts(JSON.pretty_generate(persist_list))227end228229# Stop the job by job id.230job_list.map(&:to_s).each do |job_id|231job_id = job_id.to_i < 0 ? framework.jobs.keys[job_id.to_i] : job_id232if framework.jobs.key?(job_id)233print_status("Stopping job #{job_id}")234framework.jobs.stop_job(job_id)235else236print_error("Invalid job identifier: #{job_id}")237end238end239end240241end242243#244# Add a persistent job by job id.245# Persistent job would restore on console restarted.246247def add_persist_job(job_id)248if job_id && framework.jobs.has_key?(job_id.to_s)249handler_ctx = framework.jobs[job_id.to_s].ctx[1]250unless handler_ctx and handler_ctx.respond_to?(:replicant)251print_error("Add persistent job failed: job #{job_id} is not payload handler.")252return253end254255mod = framework.jobs[job_id.to_s].ctx[0].replicant256payload = framework.jobs[job_id.to_s].ctx[1].replicant257258payload_opts = {259'Payload' => payload.refname,260'Options' => payload.datastore.to_h,261'RunAsJob' => true262}263264mod_opts = {265'mod_name' => mod.fullname,266'mod_options' => payload_opts267}268269begin270persist_list = JSON.parse(File.read(Msf::Config.persist_file))271rescue Errno::ENOENT, JSON::ParserError272persist_list = []273end274persist_list << mod_opts275File.open(Msf::Config.persist_file,"w") do |file|276file.puts(JSON.pretty_generate(persist_list))277end278print_line("Added persistence to job #{job_id}.")279else280print_line("Invalid Job ID")281end282end283284#285# Tab completion for the jobs command286#287# @param str [String] the string currently being typed before tab was hit288# @param words [Array<String>] the previously completed words on the command line. words is always289# at least 1 when tab completion has reached this stage since the command itself has been completed290291def cmd_jobs_tabs(_str, words)292return @@jobs_opts.option_keys if words.length == 1293294if words.length == 2 && @@jobs_opts.include?(words[1]) && @@jobs_opts.arg_required?(words[1])295return framework.jobs.keys296end297298[]299end300301def cmd_kill_help302print_line "Usage: kill <job1> [job2 ...]"303print_line304print_line "Equivalent to 'jobs -k job1 -k job2 ...'"305end306307def cmd_kill(*args)308cmd_jobs("-k", *args)309end310311#312# Tab completion for the kill command313#314# @param str [String] the string currently being typed before tab was hit315# @param words [Array<String>] the previously completed words on the command line. words is always316# at least 1 when tab completion has reached this stage since the command itself has been completed317318def cmd_kill_tabs(_str, words)319return [] if words.length > 1320framework.jobs.keys321end322323def cmd_handler_help324print_line "Usage: handler [options]"325print_line326print_line "Spin up a Payload Handler as background job."327print @@handler_opts.usage328end329330# Allows the user to setup a payload handler as a background job from a single command.331def cmd_handler(*args)332# Display the help banner if no arguments were passed333if args.empty?334cmd_handler_help335return336end337338exit_on_session = false339payload_module = nil340port = nil341host = nil342job_name = nil343stage_encoder = nil344345# Parse the command options346@@handler_opts.parse(args) do |opt, _idx, val|347case opt348when "-x"349exit_on_session = true350when "-p"351payload_module = framework.payloads.create(val)352if payload_module.nil?353print_error "Invalid Payload Name Supplied!"354return355end356when "-P"357port = val358when "-H"359host = val360when "-n"361job_name = val362when "-e"363encoder_module = framework.encoders.create(val)364if encoder_module.nil?365print_error "Invalid Encoder Name Supplied"366end367stage_encoder = encoder_module.refname368when "-h"369cmd_handler_help370return371end372end373374# If we are missing any of the required options, inform the user about each375# missing options, and not just one. Then exit so they can try again.376print_error "You must select a payload with -p <payload>" if payload_module.nil?377print_error "You must select a port(RPORT/LPORT) with -P <port number>" if port.nil?378print_error "You must select a host(RHOST/LHOST) with -H <hostname or address>" if host.nil?379if payload_module.nil? || port.nil? || host.nil?380print_error "Please supply missing arguments and try again."381return382end383384handler = framework.modules.create('exploit/multi/handler')385payload_datastore = payload_module.datastore386387# Set The RHOST or LHOST for the payload388if payload_datastore.has_key? "LHOST"389payload_datastore['LHOST'] = host390elsif payload_datastore.has_key? "RHOST"391payload_datastore['RHOST'] = host392else393print_error "Could not determine how to set Host on this payload..."394return395end396397# Set the RPORT or LPORT for the payload398if payload_datastore.has_key? "LPORT"399payload_datastore['LPORT'] = port400elsif payload_datastore.has_key? "RPORT"401payload_datastore['RPORT'] = port402else403print_error "Could not determine how to set Port on this payload..."404return405end406407# Set StageEncoder if selected408if stage_encoder.present?409payload_datastore["EnableStageEncoding"] = true410payload_datastore["StageEncoder"] = stage_encoder411end412413# Merge payload datastore options into the handler options414handler_opts = {415'Payload' => payload_module.refname,416'LocalInput' => driver.input,417'LocalOutput' => driver.output,418'ExitOnSession' => exit_on_session,419'RunAsJob' => true420}421422handler.datastore.reverse_merge!(payload_datastore)423handler.datastore.merge!(handler_opts)424425# Launch our Handler and get the Job ID426handler.exploit_simple(handler_opts)427job_id = handler.job_id428429# Customise the job name if the user asked for it430if job_name.present?431framework.jobs[job_id.to_s].send(:name=, job_name)432end433434print_status "Payload handler running as background job #{job_id}."435end436437def cmd_handler_tabs(str, words)438fmt = {439'-h' => [ nil ],440'-x' => [ nil ],441'-p' => [ framework.payloads.module_refnames ],442'-P' => [ true ],443'-H' => [ :address ],444'-e' => [ framework.encoders.module_refnames ],445'-n' => [ true ]446}447tab_complete_generic(fmt, str, words)448end449450end451end452end453end454end455456457