lib/msf/ui/console/module_option_tab_completion.rb
module Msf
module Ui
module Console
###
#
# Module-specific tab completion helper.
#
###
module ModuleOptionTabCompletion
#
# Tab completion for datastore names
#
# @param datastore [Msf::DataStore]
# @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(datastore, _str, _words)
keys = (
Msf::DataStoreWithFallbacks::GLOBAL_KEYS +
datastore.keys
)
keys.concat(datastore.options.values.flat_map(&:fallbacks)) if datastore.is_a?(Msf::DataStoreWithFallbacks)
keys.uniq! { |key| key.downcase }
keys
end
#
# Tab completion for a module's datastore names
#
# @param mod [Msf::Module]
# @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_module_datastore_names(mod, str, words)
datastore = mod ? mod.datastore : framework.datastore
keys = tab_complete_datastore_names(datastore, str, words)
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_module_datastore_names(mod, str, words) || [ ]
if !mod
return res
end
mod.options.sorted.each do |e|
name, _opt = e
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:)
if words.last.casecmp?('SessionTlvLogging')
return %w[console true false file:<file>]
end
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, Msf::OptRhosts
case str
when /^file:(.*)/
files = tab_complete_filenames(Regexp.last_match(1), words)
res += files.map { |f| 'file:' + f } if files
when %r{^(.*)/\d{0,2}$}
left = Regexp.last_match(1)
if Rex::Socket.is_ipv4?(left)
res << left + '/32'
res << left + '/24'
res << left + '/16'
end
when /^(.*)\-$/
left = Regexp.last_match(1)
if Rex::Socket.is_ipv4?(left)
res << str + str[0, str.length - 1]
end
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
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.module_refnames
end
#
# Provide valid encoders options for the current exploit or payload
#
def option_values_encoders
framework.encoders.module_refnames
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)
return [] unless framework.db.active
return [] if mod.datastore['RHOST'].nil?
host_addresses = mod.datastore['RHOST'].split.map do |addr|
address, _scope = addr.split('%', 2)
address
end
hosts = framework.db.hosts({:address => host_addresses, :workspace => framework.db.workspace})
return [] if hosts.empty?
res = []
hosts.each do |host|
host.services.each do |service|
res << service.port.to_s
end
end
res.uniq
end
end
end
end
end