Skip to content

Commit 657fadb

Browse files
committed
Land rapid7#7662, Payload Handler Console Command
2 parents a54c0c4 + 74b3a00 commit 657fadb

File tree

2 files changed

+167
-40
lines changed

2 files changed

+167
-40
lines changed

features/commands/help.feature

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ Feature: Help command
6464
6565
Command Description
6666
------- -----------
67+
handler Start a payload handler as job
6768
jobs Displays and manages jobs
6869
kill Kill a job
6970
rename_job Rename a job

lib/msf/ui/console/command_dispatcher/jobs.rb

Lines changed: 166 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
1+
# frozen_string_literal: true
12
# -*- coding: binary -*-
23

4+
#
5+
# Rex
6+
#
7+
38
require 'rex/ui/text/output/buffer/stdout'
49

510
module Msf
@@ -12,6 +17,16 @@ module CommandDispatcher
1217
class Jobs
1318
include Msf::Ui::Console::CommandDispatcher
1419

20+
@@handler_opts = Rex::Parser::Arguments.new(
21+
"-h" => [ false, "Help Banner"],
22+
"-x" => [ false, "Shut the Handler down after a session is established"],
23+
"-p" => [ true, "The payload to configure the handler for"],
24+
"-P" => [ true, "The RPORT/LPORT to configure the handler for"],
25+
"-H" => [ true, "The RHOST/LHOST to configure the handler for"],
26+
"-e" => [ true, "An Encoder to use for Payload Stage Encoding"],
27+
"-n" => [ true, "The custom name to give the handler job"]
28+
)
29+
1530
@@jobs_opts = Rex::Parser::Arguments.new(
1631
"-h" => [ false, "Help banner." ],
1732
"-k" => [ true, "Terminate jobs by job ID and/or range." ],
@@ -26,6 +41,7 @@ def commands
2641
"jobs" => "Displays and manages jobs",
2742
"rename_job" => "Rename a job",
2843
"kill" => "Kill a job",
44+
"handler" => "Start a payload handler as job"
2945
}
3046
end
3147

@@ -75,7 +91,7 @@ def cmd_rename_job(*args)
7591
# @param words [Array<String>] the previously completed words on the command line. words is always
7692
# at least 1 when tab completion has reached this stage since the command itself has been completed
7793

78-
def cmd_rename_job_tabs(str, words)
94+
def cmd_rename_job_tabs(_str, words)
7995
return [] if words.length > 1
8096
framework.jobs.keys
8197
end
@@ -94,49 +110,49 @@ def cmd_jobs_help
94110
def cmd_jobs(*args)
95111
# Make the default behavior listing all jobs if there were no options
96112
# or the only option is the verbose flag
97-
args.unshift("-l") if args.length == 0 || args == ["-v"]
113+
args.unshift("-l") if args.empty? || args == ["-v"]
98114

99115
verbose = false
100116
dump_list = false
101117
dump_info = false
102118
job_id = nil
103119

104120
# Parse the command options
105-
@@jobs_opts.parse(args) do |opt, idx, val|
121+
@@jobs_opts.parse(args) do |opt, _idx, val|
106122
case opt
107-
when "-v"
108-
verbose = true
109-
when "-l"
110-
dump_list = true
123+
when "-v"
124+
verbose = true
125+
when "-l"
126+
dump_list = true
111127
# Terminate the supplied job ID(s)
112-
when "-k"
113-
job_list = build_range_array(val)
114-
if job_list.blank?
115-
print_error("Please specify valid job identifier(s)")
116-
return false
117-
end
118-
print_status("Stopping the following job(s): #{job_list.join(', ')}")
119-
job_list.map(&:to_s).each do |job|
120-
if framework.jobs.has_key?(job)
121-
print_status("Stopping job #{job}")
122-
framework.jobs.stop_job(job)
123-
else
124-
print_error("Invalid job identifier: #{job}")
125-
end
126-
end
127-
when "-K"
128-
print_line("Stopping all jobs...")
129-
framework.jobs.each_key do |i|
130-
framework.jobs.stop_job(i)
131-
end
132-
when "-i"
133-
# Defer printing anything until the end of option parsing
134-
# so we can check for the verbose flag.
135-
dump_info = true
136-
job_id = val
137-
when "-h"
138-
cmd_jobs_help
128+
when "-k"
129+
job_list = build_range_array(val)
130+
if job_list.blank?
131+
print_error("Please specify valid job identifier(s)")
139132
return false
133+
end
134+
print_status("Stopping the following job(s): #{job_list.join(', ')}")
135+
job_list.map(&:to_s).each do |job|
136+
if framework.jobs.key?(job)
137+
print_status("Stopping job #{job}")
138+
framework.jobs.stop_job(job)
139+
else
140+
print_error("Invalid job identifier: #{job}")
141+
end
142+
end
143+
when "-K"
144+
print_line("Stopping all jobs...")
145+
framework.jobs.each_key do |i|
146+
framework.jobs.stop_job(i)
147+
end
148+
when "-i"
149+
# Defer printing anything until the end of option parsing
150+
# so we can check for the verbose flag.
151+
dump_info = true
152+
job_id = val
153+
when "-h"
154+
cmd_jobs_help
155+
return false
140156
end
141157
end
142158

@@ -157,7 +173,7 @@ def cmd_jobs(*args)
157173

158174
if verbose
159175
mod_opt = Serializer::ReadableText.dump_advanced_options(mod, ' ')
160-
if mod_opt && mod_opt.length > 0
176+
if mod_opt && !mod_opt.empty?
161177
print_line("\nModule advanced options:\n\n#{mod_opt}\n")
162178
end
163179
end
@@ -174,10 +190,8 @@ def cmd_jobs(*args)
174190
# @param words [Array<String>] the previously completed words on the command line. words is always
175191
# at least 1 when tab completion has reached this stage since the command itself has been completed
176192

177-
def cmd_jobs_tabs(str, words)
178-
if words.length == 1
179-
return @@jobs_opts.fmt.keys
180-
end
193+
def cmd_jobs_tabs(_str, words)
194+
return @@jobs_opts.fmt.keys if words.length == 1
181195

182196
if words.length == 2 && (@@jobs_opts.fmt[words[1]] || [false])[0]
183197
return framework.jobs.keys
@@ -204,10 +218,122 @@ def cmd_kill(*args)
204218
# @param words [Array<String>] the previously completed words on the command line. words is always
205219
# at least 1 when tab completion has reached this stage since the command itself has been completed
206220

207-
def cmd_kill_tabs(str, words)
221+
def cmd_kill_tabs(_str, words)
208222
return [] if words.length > 1
209223
framework.jobs.keys
210224
end
225+
226+
def cmd_handler_help
227+
print_line "Usage: handler [options]"
228+
print_line
229+
print_line "Spin up a Payload Handler as background job."
230+
print @@handler_opts.usage
231+
end
232+
233+
# Allows the user to setup a payload handler as a background job from a single command.
234+
def cmd_handler(*args)
235+
# Display the help banner if no arguments were passed
236+
if args.empty?
237+
cmd_handler_help
238+
return
239+
end
240+
241+
exit_on_session = false
242+
payload_module = nil
243+
port = nil
244+
host = nil
245+
job_name = nil
246+
stage_encoder = nil
247+
248+
# Parse the command options
249+
@@handler_opts.parse(args) do |opt, _idx, val|
250+
case opt
251+
when "-x"
252+
exit_on_session = true
253+
when "-p"
254+
payload_module = framework.payloads.create(val)
255+
if payload_module.nil?
256+
print_error "Invalid Payload Name Supplied!"
257+
return
258+
end
259+
when "-P"
260+
port = val
261+
when "-H"
262+
host = val
263+
when "-n"
264+
job_name = val
265+
when "-e"
266+
encoder_module = framework.encoders.create(val)
267+
if encoder_module.nil?
268+
print_error "Invalid Encoder Name Supplied"
269+
end
270+
stage_encoder = encoder_module.refname
271+
when "-h"
272+
cmd_handler_help
273+
return
274+
end
275+
end
276+
277+
# If we are missing any of the required options, inform the user about each
278+
# missing options, and not just one. Then exit so they can try again.
279+
print_error "You must select a payload with -p <payload>" if payload_module.nil?
280+
print_error "You must select a port(RPORT/LPORT) with -P <port number>" if port.nil?
281+
print_error "You must select a host(RHOST/LHOST) with -H <hostname or address>" if host.nil?
282+
if payload_module.nil? || port.nil? || host.nil?
283+
print_error "Please supply missing arguments and try again."
284+
return
285+
end
286+
287+
handler = framework.modules.create('exploit/multi/handler')
288+
payload_datastore = payload_module.datastore
289+
290+
# Set The RHOST or LHOST for the payload
291+
if payload_datastore.has_key? "LHOST"
292+
payload_datastore['LHOST'] = host
293+
elsif payload_datastore.has_key? "RHOST"
294+
payload_datastore['RHOST'] = host
295+
else
296+
print_error "Could not determine how to set Host on this payload..."
297+
return
298+
end
299+
300+
# Set the RPORT or LPORT for the payload
301+
if payload_datastore.has_key? "LPORT"
302+
payload_datastore['LPORT'] = port
303+
elsif payload_datastore.has_key? "RPORT"
304+
payload_datastore['RPORT'] = port
305+
else
306+
print_error "Could not determine how to set Port on this payload..."
307+
return
308+
end
309+
310+
# Set StageEncoder if selected
311+
if stage_encoder.present?
312+
payload_datastore["EnableStageEncoding"] = true
313+
payload_datastore["StageEncoder"] = stage_encoder
314+
end
315+
316+
# Merge payload datastore options into the handler options
317+
handler_opts = {
318+
'Payload' => payload_module.refname,
319+
'LocalInput' => driver.input,
320+
'LocalOutput' => driver.output,
321+
'ExitOnSession' => exit_on_session,
322+
'RunAsJob' => true
323+
}
324+
handler.datastore.reverse_merge!(payload_datastore)
325+
326+
# Launch our Handler and get the Job ID
327+
handler.exploit_simple(handler_opts)
328+
job_id = handler.job_id
329+
330+
# Customise the job name if the user asked for it
331+
if job_name.present?
332+
framework.jobs[job_id.to_s].send(:name=, job_name)
333+
end
334+
335+
print_status "Payload Handler Started as Job #{job_id}"
336+
end
211337
end
212338
end
213339
end

0 commit comments

Comments
 (0)