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