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