CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
rapid7

CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!

GitHub Repository: rapid7/metasploit-framework
Path: blob/master/lib/msf/ui/console/command_dispatcher/jobs.rb
Views: 1904
1
# frozen_string_literal: true
2
# -*- coding: binary -*-
3
4
#
5
# Rex
6
#
7
8
9
module Msf
10
module Ui
11
module Console
12
module CommandDispatcher
13
#
14
# {CommandDispatcher} for commands related to background jobs in Metasploit Framework.
15
#
16
class Jobs
17
include Msf::Ui::Console::CommandDispatcher
18
include Msf::Ui::Console::CommandDispatcher::Common
19
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
30
@@jobs_opts = Rex::Parser::Arguments.new(
31
"-h" => [ false, "Help banner." ],
32
"-k" => [ true, "Terminate jobs by job ID and/or range." ],
33
"-K" => [ false, "Terminate all running jobs." ],
34
"-i" => [ true, "Lists detailed information about a running job."],
35
"-l" => [ false, "List all running jobs." ],
36
"-v" => [ false, "Print more detailed info. Use with -i and -l" ],
37
"-p" => [ true, "Add persistence to job by job ID" ],
38
"-P" => [ false, "Persist all running jobs on restart." ],
39
"-S" => [ true, "Row search filter." ]
40
)
41
42
def commands
43
{
44
"jobs" => "Displays and manages jobs",
45
"rename_job" => "Rename a job",
46
"kill" => "Kill a job",
47
"handler" => "Start a payload handler as job"
48
}
49
end
50
51
#
52
# Returns the name of the command dispatcher.
53
#
54
def name
55
"Job"
56
end
57
58
def cmd_rename_job_help
59
print_line "Usage: rename_job [ID] [Name]"
60
print_line
61
print_line "Example: rename_job 0 \"meterpreter HTTPS special\""
62
print_line
63
print_line "Rename a job that's currently active."
64
print_line "You may use the jobs command to see what jobs are available."
65
print_line
66
end
67
68
def cmd_rename_job(*args)
69
if args.include?('-h') || args.length != 2 || args[0] !~ /^\d+$/
70
cmd_rename_job_help
71
return false
72
end
73
74
job_id = args[0].to_s
75
job_name = args[1].to_s
76
77
unless framework.jobs[job_id]
78
print_error("Job #{job_id} does not exist.")
79
return false
80
end
81
82
# This is not respecting the Protected access control, but this seems to be the only way
83
# to rename a job. If you know a more appropriate way, patches accepted.
84
framework.jobs[job_id].send(:name=, job_name)
85
print_status("Job #{job_id} updated")
86
87
true
88
end
89
90
#
91
# Tab completion for the rename_job command
92
#
93
# @param str [String] the string currently being typed before tab was hit
94
# @param words [Array<String>] the previously completed words on the command line. words is always
95
# at least 1 when tab completion has reached this stage since the command itself has been completed
96
97
def cmd_rename_job_tabs(_str, words)
98
return [] if words.length > 1
99
framework.jobs.keys
100
end
101
102
def cmd_jobs_help
103
print_line "Usage: jobs [options]"
104
print_line
105
print_line "Active job manipulation and interaction."
106
print @@jobs_opts.usage
107
end
108
109
#
110
# Displays and manages running jobs for the active instance of the
111
# framework.
112
#
113
def cmd_jobs(*args)
114
# Make the default behavior listing all jobs if there were no options
115
# or the only option is the verbose flag
116
args.unshift("-l") if args.empty? || args == ["-v"]
117
118
verbose = false
119
dump_list = false
120
dump_info = false
121
kill_job = false
122
job_id = nil
123
job_list = nil
124
125
# Parse the command options
126
@@jobs_opts.parse(args) do |opt, _idx, val|
127
case opt
128
when "-v"
129
verbose = true
130
when "-l"
131
dump_list = true
132
# Terminate the supplied job ID(s)
133
when "-k"
134
job_list = build_range_array(val)
135
kill_job = true
136
when "-K"
137
print_line("Stopping all jobs...")
138
framework.jobs.each_key do |i|
139
framework.jobs.stop_job(i)
140
end
141
File.write(Msf::Config.persist_file, '') if File.writable?(Msf::Config.persist_file)
142
when "-i"
143
# Defer printing anything until the end of option parsing
144
# so we can check for the verbose flag.
145
dump_info = true
146
job_id = val
147
when "-p"
148
job_list = build_range_array(val)
149
if job_list.blank?
150
print_error('Please specify valid job identifier(s)')
151
return
152
end
153
job_list.each do |job_id|
154
add_persist_job(job_id)
155
end
156
when "-P"
157
print_line("Making all jobs persistent ...")
158
job_list = framework.jobs.map do |k,v|
159
v.jid.to_s
160
end
161
job_list.each do |job_id|
162
add_persist_job(job_id)
163
end
164
when "-S", "--search"
165
search_term = val
166
dump_list = true
167
when "-h"
168
cmd_jobs_help
169
return false
170
end
171
172
end
173
174
if dump_list
175
print("\n#{Serializer::ReadableText.dump_jobs(framework, verbose)}\n")
176
end
177
if dump_info
178
if job_id && framework.jobs[job_id.to_s]
179
job = framework.jobs[job_id.to_s]
180
mod = job.ctx[0]
181
182
output = "\n"
183
output += "Name: #{mod.name}"
184
output += ", started at #{job.start_time}" if job.start_time
185
print_line(output)
186
187
show_options(mod) if mod.options.has_options?
188
189
if verbose
190
mod_opt = Serializer::ReadableText.dump_advanced_options(mod, ' ')
191
if mod_opt && !mod_opt.empty?
192
print_line("\nModule advanced options:\n\n#{mod_opt}\n")
193
end
194
end
195
else
196
print_line("Invalid Job ID")
197
end
198
end
199
200
if kill_job
201
if job_list.blank?
202
print_error("Please specify valid job identifier(s)")
203
return false
204
end
205
206
print_status("Stopping the following job(s): #{job_list.join(', ')}")
207
208
# Remove the persistent job when match the option of payload.
209
begin
210
persist_list = JSON.parse(File.read(Msf::Config.persist_file))
211
rescue Errno::ENOENT, JSON::ParserError
212
persist_list = []
213
end
214
215
# Remove persistence by job id.
216
job_list.map(&:to_s).each do |job_id|
217
job_id = job_id.to_i < 0 ? framework.jobs.keys[job_id.to_i] : job_id
218
if framework.jobs.key?(job_id)
219
ctx_1 = framework.jobs[job_id.to_s].ctx[1]
220
next if ctx_1.nil? || !ctx_1.respond_to?(:datastore) # next if no payload context in the job
221
payload_option = ctx_1.datastore
222
persist_list.delete_if{|pjob|pjob['mod_options']['Options'] == payload_option.to_h}
223
end
224
end
225
# Write persist job back to config file.
226
File.open(Msf::Config.persist_file,"w") do |file|
227
file.puts(JSON.pretty_generate(persist_list))
228
end
229
230
# Stop the job by job id.
231
job_list.map(&:to_s).each do |job_id|
232
job_id = job_id.to_i < 0 ? framework.jobs.keys[job_id.to_i] : job_id
233
if framework.jobs.key?(job_id)
234
print_status("Stopping job #{job_id}")
235
framework.jobs.stop_job(job_id)
236
else
237
print_error("Invalid job identifier: #{job_id}")
238
end
239
end
240
end
241
242
end
243
244
#
245
# Add a persistent job by job id.
246
# Persistent job would restore on console restarted.
247
248
def add_persist_job(job_id)
249
if job_id && framework.jobs.has_key?(job_id.to_s)
250
handler_ctx = framework.jobs[job_id.to_s].ctx[1]
251
unless handler_ctx and handler_ctx.respond_to?(:replicant)
252
print_error("Add persistent job failed: job #{job_id} is not payload handler.")
253
return
254
end
255
256
mod = framework.jobs[job_id.to_s].ctx[0].replicant
257
payload = framework.jobs[job_id.to_s].ctx[1].replicant
258
259
payload_opts = {
260
'Payload' => payload.refname,
261
'Options' => payload.datastore.to_h,
262
'RunAsJob' => true
263
}
264
265
mod_opts = {
266
'mod_name' => mod.fullname,
267
'mod_options' => payload_opts
268
}
269
270
begin
271
persist_list = JSON.parse(File.read(Msf::Config.persist_file))
272
rescue Errno::ENOENT, JSON::ParserError
273
persist_list = []
274
end
275
persist_list << mod_opts
276
File.open(Msf::Config.persist_file,"w") do |file|
277
file.puts(JSON.pretty_generate(persist_list))
278
end
279
print_line("Added persistence to job #{job_id}.")
280
else
281
print_line("Invalid Job ID")
282
end
283
end
284
285
#
286
# Tab completion for the jobs command
287
#
288
# @param str [String] the string currently being typed before tab was hit
289
# @param words [Array<String>] the previously completed words on the command line. words is always
290
# at least 1 when tab completion has reached this stage since the command itself has been completed
291
292
def cmd_jobs_tabs(_str, words)
293
return @@jobs_opts.option_keys if words.length == 1
294
295
if words.length == 2 && @@jobs_opts.include?(words[1]) && @@jobs_opts.arg_required?(words[1])
296
return framework.jobs.keys
297
end
298
299
[]
300
end
301
302
def cmd_kill_help
303
print_line "Usage: kill <job1> [job2 ...]"
304
print_line
305
print_line "Equivalent to 'jobs -k job1 -k job2 ...'"
306
end
307
308
def cmd_kill(*args)
309
cmd_jobs("-k", *args)
310
end
311
312
#
313
# Tab completion for the kill command
314
#
315
# @param str [String] the string currently being typed before tab was hit
316
# @param words [Array<String>] the previously completed words on the command line. words is always
317
# at least 1 when tab completion has reached this stage since the command itself has been completed
318
319
def cmd_kill_tabs(_str, words)
320
return [] if words.length > 1
321
framework.jobs.keys
322
end
323
324
def cmd_handler_help
325
print_line "Usage: handler [options]"
326
print_line
327
print_line "Spin up a Payload Handler as background job."
328
print @@handler_opts.usage
329
end
330
331
# Allows the user to setup a payload handler as a background job from a single command.
332
def cmd_handler(*args)
333
# Display the help banner if no arguments were passed
334
if args.empty?
335
cmd_handler_help
336
return
337
end
338
339
exit_on_session = false
340
payload_module = nil
341
port = nil
342
host = nil
343
job_name = nil
344
stage_encoder = nil
345
346
# Parse the command options
347
@@handler_opts.parse(args) do |opt, _idx, val|
348
case opt
349
when "-x"
350
exit_on_session = true
351
when "-p"
352
payload_module = framework.payloads.create(val)
353
if payload_module.nil?
354
print_error "Invalid Payload Name Supplied!"
355
return
356
end
357
when "-P"
358
port = val
359
when "-H"
360
host = val
361
when "-n"
362
job_name = val
363
when "-e"
364
encoder_module = framework.encoders.create(val)
365
if encoder_module.nil?
366
print_error "Invalid Encoder Name Supplied"
367
end
368
stage_encoder = encoder_module.refname
369
when "-h"
370
cmd_handler_help
371
return
372
end
373
end
374
375
# If we are missing any of the required options, inform the user about each
376
# missing options, and not just one. Then exit so they can try again.
377
print_error "You must select a payload with -p <payload>" if payload_module.nil?
378
print_error "You must select a port(RPORT/LPORT) with -P <port number>" if port.nil?
379
print_error "You must select a host(RHOST/LHOST) with -H <hostname or address>" if host.nil?
380
if payload_module.nil? || port.nil? || host.nil?
381
print_error "Please supply missing arguments and try again."
382
return
383
end
384
385
handler = framework.modules.create('exploit/multi/handler')
386
payload_datastore = payload_module.datastore
387
388
# Set The RHOST or LHOST for the payload
389
if payload_datastore.has_key? "LHOST"
390
payload_datastore['LHOST'] = host
391
elsif payload_datastore.has_key? "RHOST"
392
payload_datastore['RHOST'] = host
393
else
394
print_error "Could not determine how to set Host on this payload..."
395
return
396
end
397
398
# Set the RPORT or LPORT for the payload
399
if payload_datastore.has_key? "LPORT"
400
payload_datastore['LPORT'] = port
401
elsif payload_datastore.has_key? "RPORT"
402
payload_datastore['RPORT'] = port
403
else
404
print_error "Could not determine how to set Port on this payload..."
405
return
406
end
407
408
# Set StageEncoder if selected
409
if stage_encoder.present?
410
payload_datastore["EnableStageEncoding"] = true
411
payload_datastore["StageEncoder"] = stage_encoder
412
end
413
414
# Merge payload datastore options into the handler options
415
handler_opts = {
416
'Payload' => payload_module.refname,
417
'LocalInput' => driver.input,
418
'LocalOutput' => driver.output,
419
'ExitOnSession' => exit_on_session,
420
'RunAsJob' => true
421
}
422
423
handler.datastore.reverse_merge!(payload_datastore)
424
handler.datastore.merge!(handler_opts)
425
426
# Launch our Handler and get the Job ID
427
handler.exploit_simple(handler_opts)
428
job_id = handler.job_id
429
430
# Customise the job name if the user asked for it
431
if job_name.present?
432
framework.jobs[job_id.to_s].send(:name=, job_name)
433
end
434
435
print_status "Payload handler running as background job #{job_id}."
436
end
437
438
def cmd_handler_tabs(str, words)
439
fmt = {
440
'-h' => [ nil ],
441
'-x' => [ nil ],
442
'-p' => [ framework.payloads.module_refnames ],
443
'-P' => [ true ],
444
'-H' => [ :address ],
445
'-e' => [ framework.encoders.module_refnames ],
446
'-n' => [ true ]
447
}
448
tab_complete_generic(fmt, str, words)
449
end
450
451
end
452
end
453
end
454
end
455
end
456
457