Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
rapid7
GitHub Repository: rapid7/metasploit-framework
Path: blob/master/lib/msf/ui/console/driver.rb
21546 views
1
# -*- coding: binary -*-
2
require 'find'
3
require 'erb'
4
require 'rexml/document'
5
require 'fileutils'
6
require 'digest/md5'
7
8
module Msf
9
module Ui
10
module Console
11
12
#
13
# A user interface driver on a console interface.
14
#
15
class Driver < Msf::Ui::Driver
16
17
ConfigCore = "framework/core"
18
ConfigGroup = "framework/ui/console"
19
20
DefaultPrompt = "%undmsf%clr"
21
DefaultPromptChar = "%clr>"
22
23
#
24
# Console Command Dispatchers to be loaded after the Core dispatcher.
25
#
26
CommandDispatchers = [
27
CommandDispatcher::Modules,
28
CommandDispatcher::Jobs,
29
CommandDispatcher::Resource,
30
CommandDispatcher::Db,
31
CommandDispatcher::Creds,
32
CommandDispatcher::Developer,
33
CommandDispatcher::DNS
34
]
35
36
#
37
# The console driver processes various framework notified events.
38
#
39
include FrameworkEventManager
40
41
#
42
# The console driver is a command shell.
43
#
44
include Rex::Ui::Text::DispatcherShell
45
46
include Rex::Ui::Text::Resource
47
48
#
49
# Initializes a console driver instance with the supplied prompt string and
50
# prompt character. The optional hash can take extra values that will
51
# serve to initialize the console driver.
52
#
53
# @option opts [Boolean] 'AllowCommandPassthru' (true) Whether to allow
54
# unrecognized commands to be executed by the system shell
55
# @option opts [Boolean] 'Readline' (true) Whether to use the readline or not
56
# @option opts [String] 'HistFile' (Msf::Config.history_file) Path to a file
57
# where we can store command history
58
# @option opts [Array<String>] 'Resources' ([]) A list of resource files to
59
# load. If no resources are given, will load the default resource script,
60
# 'msfconsole.rc' in the user's {Msf::Config.config_directory config
61
# directory}
62
# @option opts [Boolean] 'SkipDatabaseInit' (false) Whether to skip
63
# connecting to the database and running migrations
64
def initialize(prompt = DefaultPrompt, prompt_char = DefaultPromptChar, opts = {})
65
setup_readline
66
67
histfile = opts['HistFile'] || Msf::Config.history_file
68
69
begin
70
FeatureManager.instance.load_config
71
rescue StandardError => e
72
elog(e)
73
end
74
75
# Check if files have been modified and force immediate loading if so
76
has_modified_metasploit_files = !Msf::Modules::Metadata::Store.valid_checksum?
77
78
if has_modified_metasploit_files
79
current_checksum = Msf::Modules::Metadata::Store.get_current_checksum
80
Msf::Modules::Metadata::Store.update_cache_checksum(current_checksum)
81
# Force immediate module loading when files have changed
82
opts['DeferModuleLoads'] = false
83
end
84
85
if opts['DeferModuleLoads'].nil?
86
opts['DeferModuleLoads'] = Msf::FeatureManager.instance.enabled?(Msf::FeatureManager::DEFER_MODULE_LOADS)
87
end
88
89
# Initialize attributes
90
91
framework_create_options = opts.merge({ 'DeferModuleLoads' => true })
92
93
if Msf::FeatureManager.instance.enabled?(Msf::FeatureManager::DNS)
94
dns_resolver = Rex::Proto::DNS::CachedResolver.new
95
dns_resolver.extend(Rex::Proto::DNS::CustomNameserverProvider)
96
dns_resolver.load_config if dns_resolver.has_config?
97
98
# Defer loading of modules until paths from opts can be added below
99
framework_create_options = framework_create_options.merge({ 'CustomDnsResolver' => dns_resolver })
100
end
101
self.framework = opts['Framework'] || Msf::Simple::Framework.create(framework_create_options)
102
103
if self.framework.datastore['Prompt']
104
prompt = self.framework.datastore['Prompt']
105
prompt_char = self.framework.datastore['PromptChar'] || DefaultPromptChar
106
end
107
108
# Call the parent
109
super(prompt, prompt_char, histfile, framework, :msfconsole)
110
111
# Temporarily disable output
112
self.disable_output = true
113
114
# Load pre-configuration
115
load_preconfig
116
117
# Initialize the user interface to use a different input and output
118
# handle if one is supplied
119
input = opts['LocalInput']
120
input ||= Rex::Ui::Text::Input::Stdio.new
121
122
if !opts['Readline']
123
input.disable_readline
124
end
125
126
if (opts['LocalOutput'])
127
if (opts['LocalOutput'].kind_of?(String))
128
output = Rex::Ui::Text::Output::File.new(opts['LocalOutput'])
129
else
130
output = opts['LocalOutput']
131
end
132
else
133
output = Rex::Ui::Text::Output::Stdio.new
134
end
135
136
init_ui(input, output)
137
init_tab_complete
138
139
# Add the core command dispatcher as the root of the dispatcher
140
# stack
141
enstack_dispatcher(CommandDispatcher::Core)
142
143
# Load the other "core" command dispatchers
144
CommandDispatchers.each do |dispatcher_class|
145
dispatcher = enstack_dispatcher(dispatcher_class)
146
dispatcher.load_config(opts['Config'])
147
end
148
149
if !framework.db || !framework.db.active
150
if framework.db.error == "disabled"
151
print_warning("Database support has been disabled")
152
else
153
error_msg = "#{framework.db.error.class.is_a?(String) ? "#{framework.db.error.class} " : nil}#{framework.db.error}"
154
print_warning("No database support: #{error_msg}")
155
end
156
end
157
158
# Register event handlers
159
register_event_handlers
160
161
# Re-enable output
162
self.disable_output = false
163
164
# Whether or not command passthru should be allowed
165
self.command_passthru = opts.fetch('AllowCommandPassthru', true)
166
167
# Whether or not to confirm before exiting
168
self.confirm_exit = opts['ConfirmExit']
169
170
# Initialize the module paths only if we didn't get passed a Framework instance and 'DeferModuleLoads' is false
171
unless opts['Framework']
172
# Configure the framework module paths
173
self.framework.init_module_paths(module_paths: opts['ModulePath'], defer_module_loads: opts['DeferModuleLoads'])
174
end
175
176
# Refresh module cache if modules are modified, or we're not deferring loads
177
if has_modified_metasploit_files || !opts['DeferModuleLoads']
178
framework.threads.spawn("ModuleCacheRebuild", true) do
179
framework.modules.refresh_cache_from_module_files
180
end
181
end
182
183
# Load console-specific configuration (after module paths are added)
184
load_config(opts['Config'])
185
186
# Process things before we actually display the prompt and get rocking
187
on_startup(opts)
188
189
# Process any resource scripts
190
if opts['Resource'].blank?
191
# None given, load the default
192
default_resource = ::File.join(Msf::Config.config_directory, 'msfconsole.rc')
193
load_resource(default_resource) if ::File.exist?(default_resource)
194
else
195
opts['Resource'].each { |r|
196
load_resource(r)
197
}
198
end
199
200
# Process persistent job handler
201
begin
202
restore_handlers = JSON.parse(File.read(Msf::Config.persist_file))
203
rescue Errno::ENOENT, JSON::ParserError
204
restore_handlers = nil
205
end
206
207
if restore_handlers
208
print_status("Starting persistent handler(s)...")
209
210
restore_handlers.each.with_index do |handler_opts, index|
211
handler = framework.modules.create(handler_opts['mod_name'])
212
handler.init_ui(self.input, self.output)
213
replicant_handler = nil
214
handler.exploit_simple(handler_opts['mod_options']) do |yielded_replicant_handler|
215
replicant_handler = yielded_replicant_handler
216
end
217
218
if replicant_handler.nil? || replicant_handler.error
219
print_status("Failed to start persistent payload handler ##{index} (#{handler_opts['mod_name']})")
220
next
221
end
222
223
if replicant_handler.error.nil?
224
job_id = handler.job_id
225
print_status "Persistent payload handler started as Job #{job_id}"
226
end
227
end
228
end
229
230
# Process any additional startup commands
231
if opts['XCommands'] and opts['XCommands'].kind_of? Array
232
opts['XCommands'].each { |c|
233
run_single(c)
234
}
235
end
236
end
237
238
#
239
# Loads configuration that needs to be analyzed before the framework
240
# instance is created.
241
#
242
def load_preconfig
243
begin
244
conf = Msf::Config.load
245
rescue
246
wlog("Failed to load configuration: #{$!}")
247
return
248
end
249
250
if (conf.group?(ConfigCore))
251
conf[ConfigCore].each_pair { |k, v|
252
on_variable_set(true, k, v)
253
}
254
end
255
end
256
257
#
258
# Loads configuration for the console.
259
#
260
def load_config(path=nil)
261
begin
262
conf = Msf::Config.load(path)
263
rescue
264
wlog("Failed to load configuration: #{$!}")
265
return
266
end
267
268
# If we have configuration, process it
269
if (conf.group?(ConfigGroup))
270
conf[ConfigGroup].each_pair { |k, v|
271
case k.downcase
272
when 'activemodule'
273
run_single("use #{v}")
274
when 'activeworkspace'
275
if framework.db.active
276
workspace = framework.db.find_workspace(v)
277
framework.db.workspace = workspace if workspace
278
end
279
end
280
}
281
end
282
end
283
284
#
285
# Generate configuration for the console.
286
#
287
def get_config
288
# Build out the console config group
289
group = {}
290
291
if (active_module)
292
group['ActiveModule'] = active_module.fullname
293
end
294
295
if framework.db.active
296
unless framework.db.workspace.default?
297
group['ActiveWorkspace'] = framework.db.workspace.name
298
end
299
end
300
301
group
302
end
303
304
def get_config_core
305
ConfigCore
306
end
307
308
def get_config_group
309
ConfigGroup
310
end
311
312
#
313
# Saves configuration for the console.
314
#
315
def save_config
316
begin
317
Msf::Config.save(ConfigGroup => get_config)
318
rescue ::Exception
319
print_error("Failed to save console config: #{$!}")
320
end
321
end
322
323
#
324
# Saves the recent history to the specified file
325
#
326
def save_recent_history(path)
327
num = Readline::HISTORY.length - hist_last_saved - 1
328
329
tmprc = ""
330
num.times { |x|
331
tmprc << Readline::HISTORY[hist_last_saved + x] + "\n"
332
}
333
334
if tmprc.length > 0
335
print_status("Saving last #{num} commands to #{path} ...")
336
save_resource(tmprc, path)
337
else
338
print_error("No commands to save!")
339
end
340
341
# Always update this, even if we didn't save anything. We do this
342
# so that we don't end up saving the "makerc" command itself.
343
self.hist_last_saved = Readline::HISTORY.length
344
end
345
346
#
347
# Creates the resource script file for the console.
348
#
349
def save_resource(data, path=nil)
350
path ||= File.join(Msf::Config.config_directory, 'msfconsole.rc')
351
352
begin
353
rcfd = File.open(path, 'w')
354
rcfd.write(data)
355
rcfd.close
356
rescue ::Exception
357
end
358
end
359
360
#
361
# Called before things actually get rolling such that banners can be
362
# displayed, scripts can be processed, and other fun can be had.
363
#
364
def on_startup(opts = {})
365
# Check for modules that failed to load
366
if framework.modules.module_load_error_by_path.length > 0
367
wlog("The following modules could not be loaded!")
368
369
framework.modules.module_load_error_by_path.each do |path, _error|
370
wlog("\t#{path}")
371
end
372
end
373
374
if framework.modules.module_load_warnings.length > 0
375
print_warning("The following modules were loaded with warnings:")
376
377
framework.modules.module_load_warnings.each do |path, _error|
378
wlog("\t#{path}")
379
end
380
end
381
382
if framework.db&.active
383
framework.db.workspace = framework.db.default_workspace unless framework.db.workspace
384
end
385
386
framework.events.on_ui_start(Msf::Framework::Revision)
387
388
if $msf_spinner_thread
389
$msf_spinner_thread.kill
390
$stderr.print "\r" + (" " * 50) + "\n"
391
end
392
393
run_single("banner") unless opts['DisableBanner']
394
395
payloads_manifest_errors = []
396
begin
397
payloads_manifest_errors = ::MetasploitPayloads.manifest_errors if framework.features.enabled?(::Msf::FeatureManager::METASPLOIT_PAYLOAD_WARNINGS)
398
rescue ::StandardError => e
399
$stderr.print('Could not verify the integrity of the Metasploit Payloads manifest')
400
elog(e)
401
end
402
403
av_warning_message if (framework.eicar_corrupted? || payloads_manifest_errors.any?)
404
405
if framework.features.enabled?(::Msf::FeatureManager::METASPLOIT_PAYLOAD_WARNINGS)
406
if payloads_manifest_errors.any?
407
warn_msg = "Metasploit Payloads manifest errors:\n"
408
payloads_manifest_errors.each do |file|
409
warn_msg << "\t#{file[:path]} : #{file[:error]}\n"
410
end
411
$stderr.print(warn_msg)
412
end
413
end
414
415
opts["Plugins"].each do |plug|
416
run_single("load '#{plug}'")
417
end if opts["Plugins"]
418
419
self.on_command_proc = Proc.new { |command| framework.events.on_ui_command(command) }
420
end
421
422
def av_warning_message
423
avdwarn = "\e[31m"\
424
"Warning: This copy of the Metasploit Framework has been corrupted by an installed anti-virus program."\
425
" We recommend that you disable your anti-virus or exclude your Metasploit installation path, "\
426
"then restore the removed files from quarantine or reinstall the framework.\e[0m"\
427
"\n\n"
428
429
$stderr.puts(Rex::Text.wordwrap(avdwarn, 0, 80))
430
end
431
432
#
433
# Called when a variable is set to a specific value. This allows the
434
# console to do extra processing, such as enabling logging or doing
435
# some other kind of task. If this routine returns false it will indicate
436
# that the variable is not being set to a valid value.
437
#
438
def on_variable_set(glob, var, val)
439
case var.downcase
440
when 'sessionlogging'
441
handle_session_logging(val) if glob
442
when 'sessiontlvlogging'
443
handle_session_tlv_logging(val) if glob
444
when 'consolelogging'
445
handle_console_logging(val) if glob
446
when 'loglevel'
447
handle_loglevel(val) if glob
448
when 'payload'
449
handle_payload(val)
450
when 'ssh_ident'
451
handle_ssh_ident(val)
452
end
453
end
454
455
#
456
# Called when a variable is unset. If this routine returns false it is an
457
# indication that the variable should not be allowed to be unset.
458
#
459
def on_variable_unset(glob, var)
460
case var.downcase
461
when 'sessionlogging'
462
handle_session_logging('0') if glob
463
when 'sessiontlvlogging'
464
handle_session_tlv_logging('false') if glob
465
when 'consolelogging'
466
handle_console_logging('0') if glob
467
when 'loglevel'
468
handle_loglevel(nil) if glob
469
end
470
end
471
472
#
473
# Proxies to shell.rb's update prompt with our own extras
474
#
475
def update_prompt(*args)
476
if args.empty?
477
pchar = framework.datastore['PromptChar'] || DefaultPromptChar
478
p = framework.datastore['Prompt'] || DefaultPrompt
479
p = "#{p} #{active_module.type}(%bld%red#{active_module.promptname}%clr)" if active_module
480
super(p, pchar)
481
else
482
# Don't squash calls from within lib/rex/ui/text/shell.rb
483
super(*args)
484
end
485
end
486
487
#
488
# The framework instance associated with this driver.
489
#
490
attr_reader :framework
491
#
492
# Whether or not to confirm before exiting
493
#
494
attr_reader :confirm_exit
495
#
496
# Whether or not commands can be passed through.
497
#
498
attr_reader :command_passthru
499
#
500
# The active module associated with the driver.
501
#
502
attr_accessor :active_module
503
#
504
# The active session associated with the driver.
505
#
506
attr_accessor :active_session
507
508
def stop
509
framework.events.on_ui_stop()
510
super
511
end
512
513
protected
514
515
attr_writer :framework # :nodoc:
516
attr_writer :confirm_exit # :nodoc:
517
attr_writer :command_passthru # :nodoc:
518
519
#
520
# If an unknown command was passed, try to see if it's a valid local
521
# executable. This is only allowed if command passthru has been permitted
522
#
523
def unknown_command(method, line)
524
if File.basename(method) == 'msfconsole'
525
print_error('msfconsole cannot be run inside msfconsole')
526
return
527
end
528
529
[method, method+".exe"].each do |cmd|
530
if command_passthru && Rex::FileUtils.find_full_path(cmd)
531
532
self.busy = true
533
begin
534
run_unknown_command(line)
535
rescue ::Errno::EACCES, ::Errno::ENOENT
536
print_error("Permission denied exec: #{line}")
537
end
538
self.busy = false
539
return
540
end
541
end
542
543
if framework.modules.create(method)
544
super
545
if prompt_yesno "This is a module we can load. Do you want to use #{method}?"
546
run_single "use #{method}"
547
end
548
549
return
550
end
551
552
super
553
end
554
555
def run_unknown_command(command)
556
print_status("exec: #{command}")
557
print_line('')
558
system(command)
559
end
560
561
##
562
#
563
# Handlers for various global configuration values
564
#
565
##
566
567
#
568
# SessionLogging.
569
#
570
def handle_session_logging(val)
571
if (val =~ /^(y|t|1)/i)
572
Msf::Logging.enable_session_logging(true)
573
framework.sessions.values.each do |session|
574
Msf::Logging.start_session_log(session)
575
end
576
print_line("Session logging enabled.")
577
else
578
Msf::Logging.enable_session_logging(false)
579
framework.sessions.values.each do |session|
580
Msf::Logging.stop_session_log(session)
581
end
582
print_line("Session logging disabled.")
583
end
584
end
585
586
#
587
# ConsoleLogging.
588
#
589
def handle_console_logging(val)
590
if (val =~ /^(y|t|1)/i)
591
Msf::Logging.enable_log_source('console')
592
print_line("Console logging is now enabled.")
593
594
set_log_source('console')
595
596
rlog("\n[*] Console logging started: #{Time.now}\n\n", 'console')
597
else
598
rlog("\n[*] Console logging stopped: #{Time.now}\n\n", 'console')
599
600
unset_log_source
601
602
Msf::Logging.disable_log_source('console')
603
print_line("Console logging is now disabled.")
604
end
605
end
606
607
#
608
# This method handles adjusting the global log level threshold.
609
#
610
def handle_loglevel(val)
611
set_log_level(Rex::LogSource, val)
612
set_log_level(Msf::LogSource, val)
613
end
614
615
#
616
# This method handles setting a desired payload
617
#
618
# TODO: Move this out of the console driver!
619
#
620
def handle_payload(val)
621
if framework && !framework.payloads.valid?(val)
622
return false
623
elsif active_module && (active_module.exploit? || active_module.evasion?)
624
return false unless active_module.is_payload_compatible?(val)
625
end
626
end
627
628
#
629
# This method monkeypatches Net::SSH's client identification string
630
#
631
# TODO: Move this out of the console driver!
632
#
633
def handle_ssh_ident(val)
634
# HACK: Suppress already initialized constant warning
635
verbose, $VERBOSE = $VERBOSE, nil
636
637
return false unless val.is_a?(String) && !val.empty?
638
639
require 'net/ssh'
640
641
# HACK: Bypass dynamic constant assignment error
642
::Net::SSH::Transport::ServerVersion.const_set(:PROTO_VERSION, val)
643
644
true
645
rescue LoadError
646
print_error('Net::SSH could not be loaded')
647
false
648
rescue NameError
649
print_error('Invalid constant Net::SSH::Transport::ServerVersion::PROTO_VERSION')
650
false
651
ensure
652
# Restore warning
653
$VERBOSE = verbose
654
end
655
656
def handle_session_tlv_logging(val)
657
return false if val.nil?
658
659
if val.casecmp?('console') || val.casecmp?('true') || val.casecmp?('false')
660
return true
661
elsif val.start_with?('file:') && !val.split('file:').empty?
662
pathname = ::Pathname.new(val.split('file:').last)
663
664
# Check if we want to write the log to file
665
if ::File.file?(pathname)
666
if ::File.writable?(pathname)
667
return true
668
else
669
print_status "No write permissions for log output file: #{pathname}"
670
return false
671
end
672
# Check if we want to write the log file to a directory
673
elsif ::File.directory?(pathname)
674
if ::File.writable?(pathname)
675
return true
676
else
677
print_status "No write permissions for log output directory: #{pathname}"
678
return false
679
end
680
# Check if the subdirectory exists
681
elsif ::File.directory?(pathname.dirname)
682
if ::File.writable?(pathname.dirname)
683
return true
684
else
685
print_status "No write permissions for log output directory: #{pathname.dirname}"
686
return false
687
end
688
else
689
# Else the directory doesn't exist. Check if we can create it.
690
begin
691
::FileUtils.mkdir_p(pathname.dirname)
692
return true
693
rescue ::StandardError => e
694
print_status "Error when trying to create directory #{pathname.dirname}: #{e.message}"
695
return false
696
end
697
end
698
end
699
700
false
701
end
702
703
# Require the appropriate readline library based on the user's preference.
704
#
705
# @return [void]
706
def setup_readline
707
require 'readline'
708
709
# Only Windows requires a monkey-patched RbReadline
710
return unless Rex::Compat.is_windows
711
712
if defined?(::RbReadline) && !defined?(RbReadline.refresh_console_handle)
713
::RbReadline.instance_eval do
714
class << self
715
alias_method :old_rl_move_cursor_relative, :_rl_move_cursor_relative
716
alias_method :old_rl_get_screen_size, :_rl_get_screen_size
717
alias_method :old_space_to_eol, :space_to_eol
718
alias_method :old_insert_some_chars, :insert_some_chars
719
end
720
721
def self.refresh_console_handle
722
# hConsoleHandle gets set only when RbReadline detects it is running on Windows.
723
# Therefore, we don't need to check Rex::Compat.is_windows, we can simply check if hConsoleHandle is nil or not.
724
@hConsoleHandle = @GetStdHandle.Call(::Readline::STD_OUTPUT_HANDLE) if @hConsoleHandle
725
end
726
727
def self._rl_move_cursor_relative(*args)
728
refresh_console_handle
729
old_rl_move_cursor_relative(*args)
730
end
731
732
def self._rl_get_screen_size(*args)
733
refresh_console_handle
734
old_rl_get_screen_size(*args)
735
end
736
737
def self.space_to_eol(*args)
738
refresh_console_handle
739
old_space_to_eol(*args)
740
end
741
742
def self.insert_some_chars(*args)
743
refresh_console_handle
744
old_insert_some_chars(*args)
745
end
746
end
747
end
748
end
749
end
750
751
end
752
end
753
end
754
755