Files
metasploit-gs/lib/msf/ui/console/module_option_tab_completion.rb
T
2021-01-21 22:59:36 +00:00

359 lines
11 KiB
Ruby

module Msf
module Ui
module Console
###
#
# Module-specific tab completion helper.
#
###
module ModuleOptionTabCompletion
#
# Tab completion for datastore names
#
# @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 tab_complete_datastore_names(mod, _str, _words)
datastore = mod ? mod.datastore : framework.datastore
keys = datastore.keys
if mod
keys = keys.delete_if do |name|
!(mod_opt = mod.options[name]).nil? && !Msf::OptCondition.show_option(mod, mod_opt)
end
end
keys
end
#
# Tab completion options values
#
def tab_complete_option(mod, str, words)
if str.end_with?('=')
option_name = str.chop
option_value = ''
::Readline.completion_append_character = ' '
return tab_complete_option_values(mod, option_value, words, opt: option_name).map { |value| "#{str}#{value}" }
elsif str.include?('=')
str_split = str.split('=')
option_name = str_split[0].strip
option_value = str_split[1].strip
::Readline.completion_append_character = ' '
return tab_complete_option_values(mod, option_value, words, opt: option_name).map { |value| "#{option_name}=#{value}" }
end
::Readline.completion_append_character = ''
tab_complete_option_names(mod, str, words).map { |name| "#{name}=" }
end
#
# Provide tab completion for name values
#
def tab_complete_option_names(mod, str, words)
res = tab_complete_datastore_names(mod, str, words) || [ ]
# There needs to be a better way to register global options, but for
# now all we have is an ad-hoc list of opts that the shell treats
# specially.
res += %w[
ConsoleLogging
LogLevel
MinimumRank
SessionLogging
TimestampOutput
Prompt
PromptChar
PromptTimeFormat
MeterpreterPrompt
]
if !mod
return res
end
mod.options.sorted.each do |e|
name, _opt = e
next unless Msf::OptCondition.show_option(mod, _opt)
res << name
end
# Exploits provide these three default options
if mod.exploit?
res << 'PAYLOAD'
res << 'NOP'
res << 'TARGET'
res << 'ENCODER'
elsif mod.evasion?
res << 'PAYLOAD'
res << 'TARGET'
res << 'ENCODER'
elsif mod.payload?
res << 'ENCODER'
end
if mod.is_a?(Msf::Module::HasActions)
res << 'ACTION'
end
if ((mod.exploit? || mod.evasion?) && mod.datastore['PAYLOAD'])
p = framework.payloads.create(mod.datastore['PAYLOAD'])
if p
p.options.sorted.each do |e|
name, _opt = e
res << name
end
end
end
unless str.blank?
res = res.select { |term| term.upcase.start_with?(str.upcase) }
res = res.map do |term|
if str == str.upcase
str + term[str.length..-1].upcase
elsif str == str.downcase
str + term[str.length..-1].downcase
else
str + term[str.length..-1]
end
end
end
return res
end
#
# Provide tab completion for option values
#
def tab_complete_option_values(mod, str, words, opt:)
res = []
# With no module, we have nothing to complete
if !mod
return res
end
# Well-known option names specific to exploits
if mod.exploit?
return option_values_payloads(mod) if opt.upcase == 'PAYLOAD'
return option_values_targets(mod) if opt.upcase == 'TARGET'
return option_values_nops if opt.upcase == 'NOPS'
return option_values_encoders if opt.upcase == 'STAGEENCODER'
elsif mod.evasion?
return option_values_payloads(mod) if opt.upcase == 'PAYLOAD'
return option_values_targets(mod) if opt.upcase == 'TARGET'
end
# Well-known option names specific to modules with actions
if mod.is_a?(Msf::Module::HasActions)
return option_values_actions(mod) if opt.upcase == 'ACTION'
end
# The ENCODER option works for evasions, payloads and exploits
if ((mod.evasion? || mod.exploit? || mod.payload?) && (opt.upcase == 'ENCODER'))
return option_values_encoders
end
# Well-known option names specific to post-exploitation
if (mod.post? || mod.exploit?)
return option_values_sessions(mod) if opt.upcase == 'SESSION'
end
# Is this option used by the active module?
mod.options.each_key do |key|
if key.downcase == opt.downcase
res.concat(option_values_dispatch(mod, mod.options[key], str, words))
end
end
# How about the selected payload?
if ((mod.evasion? || mod.exploit?) && mod.datastore['PAYLOAD'])
if p = framework.payloads.create(mod.datastore['PAYLOAD'])
p.options.each_key do |key|
res.concat(option_values_dispatch(mod, p.options[key], str, words)) if key.downcase == opt.downcase
end
end
end
return res
end
#
# Provide possible option values based on type
#
def option_values_dispatch(mod, o, str, words)
res = []
res << o.default.to_s if o.default
case o
when Msf::OptAddress
case o.name.upcase
when 'RHOST'
option_values_target_addrs(mod).each do |addr|
res << addr
end
when 'LHOST', 'SRVHOST', 'REVERSELISTENERBINDADDRESS'
rh = mod.datastore['RHOST'] || framework.datastore['RHOST']
if rh && !rh.empty?
res << Rex::Socket.source_address(rh)
else
res += tab_complete_source_address
res += tab_complete_source_interface(o)
end
end
when Msf::OptAddressRange
case str
when /^file:(.*)/
files = tab_complete_filenames(Regexp.last_match(1), words)
res += files.map { |f| 'file:' + f } if files
when %r{/$}
res << str + '32'
res << str + '24'
res << str + '16'
when /\-$/
res << str + str[0, str.length - 1]
else
option_values_target_addrs(mod).each do |addr|
res << addr
end
end
when Msf::OptPort
case o.name.upcase
when 'RPORT'
option_values_target_ports(mod).each do |port|
res << port
end
end
if res.empty?
res << rand(1..65534).to_s
end
when Msf::OptEnum
o.enums.each do |val|
res << val
end
when Msf::OptPath
files = tab_complete_filenames(str, words)
res += files if files
when Msf::OptBool
res << 'true'
res << 'false'
when Msf::OptString
if (str =~ /^file:(.*)/)
files = tab_complete_filenames(Regexp.last_match(1), words)
res += files.map { |f| 'file:' + f } if files
end
end
return res
end
# XXX: We repurpose OptAddressLocal#interfaces, so we can't put this in Rex
def tab_complete_source_interface(o)
return [] unless o.is_a?(Msf::OptAddressLocal)
o.interfaces
end
#
# Provide valid payload options for the current exploit
#
def option_values_payloads(mod)
if @cache_payloads && mod == @previous_module && mod.target == @previous_target
return @cache_payloads
end
@previous_module = mod
@previous_target = mod.target
@cache_payloads = mod.compatible_payloads.map do |refname, _payload|
refname
end
@cache_payloads
end
#
# Provide valid session options for the current post-exploit module
#
def option_values_sessions(mod)
if mod.respond_to?(:compatible_sessions)
mod.compatible_sessions.map { |sid| sid.to_s }
end
end
#
# Provide valid target options for the current exploit
#
def option_values_targets(mod)
res = []
if mod.targets
1.upto(mod.targets.length) { |i| res << (i - 1).to_s }
res += mod.targets.map(&:name)
end
return res
end
#
# Provide valid action options for the current module
#
def option_values_actions(mod)
res = []
if mod.actions
mod.actions.each { |i| res << i.name }
end
return res
end
#
# Provide valid nops options for the current exploit
#
def option_values_nops
framework.nops.map { |refname, _mod| refname }
end
#
# Provide valid encoders options for the current exploit or payload
#
def option_values_encoders
framework.encoders.map { |refname, _mod| refname }
end
#
# Provide the target addresses
#
def option_values_target_addrs(mod)
res = [ ]
res << Rex::Socket.source_address
return res if !framework.db.active
# List only those hosts with matching open ports?
mport = mod.datastore['RPORT']
if mport
mport = mport.to_i
hosts = {}
framework.db.services.each do |service|
if service.port == mport
hosts[service.host.address] = true
end
end
hosts.keys.each do |host|
res << host
end
# List all hosts in the database
else
framework.db.hosts.each do |host|
res << host.address
end
end
return res
end
#
# Provide the target ports
#
def option_values_target_ports(mod)
res = [ ]
return res if !framework.db.active
return res if !mod.datastore['RHOST']
host = framework.db.has_host?(framework.db.workspace, mod.datastore['RHOST'])
return res if !host
framework.db.services.each do |service|
if service.host_id == host.id
res << service.port.to_s
end
end
return res
end
end
end
end
end