b4b73529d3
remove single flag pivot to search flag added support for search session type adds search session id support remove stale references reshuffle code fix time parsing, add command support fix search list, reduce duplicated code testing added killall with search lists table of killed sessions sessions are no longer represented by ids addresses feedback on code structure and search behavior some test reshuffling, switch raised errors to printed ones add checkin validation, rest of cmd_sessions tests add time parsing test refactoring test reformatting and adjusted error validation make error handling more explicit, add test context fixes sub quotes, make constant rubocopping switch before and after to greater than and less than mbetter incorporate constants update example
1175 lines
34 KiB
Ruby
1175 lines
34 KiB
Ruby
# -*- coding: binary -*-
|
|
|
|
|
|
module Msf
|
|
module Serializer
|
|
|
|
# This class formats information in a plain-text format that
|
|
# is meant to be displayed on a console or some other non-GUI
|
|
# medium.
|
|
class ReadableText
|
|
|
|
#Default number of characters to wrap at.
|
|
DefaultColumnWrap = 70
|
|
#Default number of characters to indent.
|
|
DefaultIndent = 2
|
|
|
|
# Returns a formatted string that contains information about
|
|
# the supplied module instance.
|
|
#
|
|
# @param mod [Msf::Module] the module to dump information for.
|
|
# @param indent [String] the indentation to use.
|
|
# @return [String] formatted text output of the dump.
|
|
def self.dump_module(mod, indent = " ")
|
|
case mod.type
|
|
when Msf::MODULE_PAYLOAD
|
|
return dump_payload_module(mod, indent)
|
|
when Msf::MODULE_NOP
|
|
return dump_basic_module(mod, indent)
|
|
when Msf::MODULE_ENCODER
|
|
return dump_basic_module(mod, indent)
|
|
when Msf::MODULE_EXPLOIT
|
|
return dump_exploit_module(mod, indent)
|
|
when Msf::MODULE_AUX
|
|
return dump_auxiliary_module(mod, indent)
|
|
when Msf::MODULE_POST
|
|
return dump_post_module(mod, indent)
|
|
when Msf::MODULE_EVASION
|
|
return dump_evasion_module(mod, indent)
|
|
else
|
|
return dump_generic_module(mod, indent)
|
|
end
|
|
end
|
|
|
|
# Dumps an exploit's targets.
|
|
#
|
|
# @param mod [Msf::Exploit] the exploit module to dump targets
|
|
# for.
|
|
# @param indent [String] the indentation to use (only the length
|
|
# matters).
|
|
# @param h [String] the string to display as the table heading.
|
|
# @return [String] the string form of the table.
|
|
def self.dump_exploit_targets(mod, indent = '', h = nil)
|
|
tbl = Rex::Text::Table.new(
|
|
'Indent' => indent.length,
|
|
'Header' => h,
|
|
'Columns' =>
|
|
[
|
|
'IsTarget',
|
|
'Id',
|
|
'Name',
|
|
],
|
|
'SortIndex' => 1,
|
|
'ColProps' => {
|
|
'IsTarget' => {
|
|
'Stylers' => [Msf::Ui::Console::TablePrint::RowIndicatorStyler.new],
|
|
'ColumnStylers' => [Msf::Ui::Console::TablePrint::OmitColumnHeader.new],
|
|
'Width' => 2
|
|
}
|
|
}
|
|
)
|
|
|
|
mod.targets.each_with_index do |target, idx|
|
|
is_target = mod.target == target
|
|
|
|
tbl << [is_target, idx.to_s, target.name || 'All' ]
|
|
end
|
|
|
|
tbl.to_s + "\n"
|
|
end
|
|
|
|
def self.dump_evasion_targets(mod, indent = '', h = nil)
|
|
tbl = Rex::Text::Table.new(
|
|
'Indent' => indent.length,
|
|
'Header' => h,
|
|
'Columns' =>
|
|
[
|
|
'IsTarget',
|
|
'Id',
|
|
'Name',
|
|
],
|
|
'SortIndex' => 1,
|
|
'ColProps' => {
|
|
'IsTarget' => {
|
|
'Stylers' => [Msf::Ui::Console::TablePrint::RowIndicatorStyler.new],
|
|
'ColumnStylers' => [Msf::Ui::Console::TablePrint::OmitColumnHeader.new],
|
|
'Width' => 2
|
|
}
|
|
}
|
|
)
|
|
|
|
mod.targets.each_with_index do |target, idx|
|
|
is_target = mod.target == target
|
|
|
|
tbl << [is_target, idx.to_s, target.name || 'All' ]
|
|
end
|
|
|
|
tbl.to_s + "\n"
|
|
end
|
|
|
|
# Dumps the exploit's selected target
|
|
#
|
|
# @param mod [Msf::Exploit] the exploit module.
|
|
# @param indent [String] the indentation to use (only the length
|
|
# matters)
|
|
# @param h [String] the string to display as the table heading.
|
|
# @return [String] the string form of the table.
|
|
def self.dump_exploit_target(mod, indent = '', h = nil)
|
|
tbl = Rex::Text::Table.new(
|
|
'Indent' => indent.length,
|
|
'Header' => h,
|
|
'Columns' =>
|
|
[
|
|
'Id',
|
|
'Name',
|
|
])
|
|
|
|
tbl << [ mod.target_index, mod.target.name || 'All' ]
|
|
|
|
tbl.to_s + "\n"
|
|
end
|
|
|
|
# Dumps the evasion module's selected target
|
|
#
|
|
# @param mod [Msf::Evasion] The evasion module.
|
|
# @param indent [String] The indentation to use (only the length matters)
|
|
# @param h [String] The string to display as the table heading.
|
|
# @return [String] The strong form of the table.
|
|
def self.dump_evasion_target(mod, indent = '', h = nil)
|
|
tbl = Rex::Text::Table.new(
|
|
'Indent' => indent.length,
|
|
'Header' => h,
|
|
'Columns' =>
|
|
[
|
|
'Id',
|
|
'Name',
|
|
])
|
|
|
|
tbl << [ mod.target_index, mod.target.name || 'All' ]
|
|
|
|
tbl.to_s + "\n"
|
|
end
|
|
|
|
# Dumps a module's actions
|
|
#
|
|
# @param mod [Msf::Module] the module.
|
|
# @param indent [String] the indentation to use (only the length
|
|
# matters)
|
|
# @param h [String] the string to display as the table heading.
|
|
# @return [String] the string form of the table.
|
|
def self.dump_module_actions(mod, indent = '', h = nil)
|
|
tbl = Rex::Text::Table.new(
|
|
'Indent' => indent.length,
|
|
'Header' => h,
|
|
'Columns' =>
|
|
[
|
|
'ActionEnabled',
|
|
'Name',
|
|
'Description'
|
|
],
|
|
'SortIndex' => 1,
|
|
'ColProps' => {
|
|
'ActionEnabled' => {
|
|
'Stylers' => [Msf::Ui::Console::TablePrint::RowIndicatorStyler.new],
|
|
'ColumnStylers' => [Msf::Ui::Console::TablePrint::OmitColumnHeader.new],
|
|
'Width' => 2
|
|
}
|
|
}
|
|
)
|
|
|
|
mod.actions.each_with_index { |target, idx|
|
|
action_enabled = mod.action == target
|
|
|
|
tbl << [ action_enabled, target.name || 'All' , target.description || '' ]
|
|
}
|
|
|
|
tbl.to_s + "\n"
|
|
end
|
|
|
|
# Dumps the module's selected action
|
|
#
|
|
# @param mod [Msf::Module] the module.
|
|
# @param indent [String] the indentation to use (only the length
|
|
# matters)
|
|
# @param h [String] the string to display as the table heading.
|
|
# @return [String] the string form of the table.
|
|
def self.dump_module_action(mod, indent = '', h = nil)
|
|
tbl = Rex::Text::Table.new(
|
|
'Indent' => indent.length,
|
|
'Header' => h,
|
|
'Columns' =>
|
|
[
|
|
'Name',
|
|
'Description',
|
|
])
|
|
|
|
tbl << [ mod.action.name || 'All', mod.action.description || '' ]
|
|
|
|
tbl.to_s + "\n"
|
|
end
|
|
|
|
# Dumps the table of payloads that are compatible with the supplied
|
|
# exploit.
|
|
#
|
|
# @param exploit [Msf::Exploit] the exploit module.
|
|
# @param indent [String] the indentation to use (only the length
|
|
# matters)
|
|
# @param h [String] the string to display as the table heading.
|
|
# @return [String] the string form of the table.
|
|
def self.dump_compatible_payloads(exploit, indent = '', h = nil)
|
|
tbl = Rex::Text::Table.new(
|
|
'Indent' => indent.length,
|
|
'Header' => h,
|
|
'Columns' =>
|
|
[
|
|
'Name',
|
|
'Description',
|
|
])
|
|
|
|
exploit.compatible_payloads.each { |entry|
|
|
tbl << [ entry[0], entry[1].new.description ]
|
|
}
|
|
|
|
tbl.to_s + "\n"
|
|
end
|
|
|
|
def self.dump_traits(mod, indent=' ')
|
|
output = ''
|
|
|
|
unless mod.side_effects.empty?
|
|
output << "Module side effects:\n"
|
|
mod.side_effects.each { |side_effect|
|
|
output << indent + side_effect + "\n"
|
|
}
|
|
output << "\n"
|
|
end
|
|
|
|
unless mod.stability.empty?
|
|
output << "Module stability:\n"
|
|
mod.stability.each { |stability|
|
|
output << indent + stability + "\n"
|
|
}
|
|
output << "\n"
|
|
end
|
|
|
|
unless mod.reliability.empty?
|
|
output << "Module reliability:\n"
|
|
mod.reliability.each { |reliability|
|
|
output << indent + reliability + "\n"
|
|
}
|
|
output << "\n"
|
|
end
|
|
|
|
output
|
|
end
|
|
|
|
# Dumps information about an exploit module.
|
|
#
|
|
# @param mod [Msf::Exploit] the exploit module.
|
|
# @param indent [String] the indentation to use.
|
|
# @return [String] the string form of the information.
|
|
def self.dump_exploit_module(mod, indent = '')
|
|
output = "\n"
|
|
output << " Name: #{mod.name}\n"
|
|
output << " Module: #{mod.fullname}\n"
|
|
output << " Platform: #{mod.platform_to_s}\n"
|
|
output << " Arch: #{mod.arch_to_s}\n"
|
|
output << " Privileged: " + (mod.privileged? ? "Yes" : "No") + "\n"
|
|
output << " License: #{mod.license}\n"
|
|
output << " Rank: #{mod.rank_to_s.capitalize}\n"
|
|
output << " Disclosed: #{mod.disclosure_date}\n" if mod.disclosure_date
|
|
output << "\n"
|
|
|
|
# Authors
|
|
output << "Provided by:\n"
|
|
mod.each_author { |author|
|
|
output << indent + author.to_s + "\n"
|
|
}
|
|
output << "\n"
|
|
|
|
output << dump_traits(mod)
|
|
|
|
# Targets
|
|
output << "Available targets:\n"
|
|
output << dump_exploit_targets(mod, indent)
|
|
|
|
# Check
|
|
output << "Check supported:\n"
|
|
output << "#{indent}#{mod.has_check? ? 'Yes' : 'No'}\n\n"
|
|
|
|
# Options
|
|
if (mod.options.has_options?)
|
|
output << "Basic options:\n"
|
|
output << dump_options(mod, indent)
|
|
output << "\n"
|
|
end
|
|
|
|
# Payload information
|
|
if (mod.payload_info.length)
|
|
output << "Payload information:\n"
|
|
if (mod.payload_space)
|
|
output << indent + "Space: " + mod.payload_space.to_s + "\n"
|
|
end
|
|
if (mod.payload_badchars)
|
|
output << indent + "Avoid: " + mod.payload_badchars.length.to_s + " characters\n"
|
|
end
|
|
output << "\n"
|
|
end
|
|
|
|
# Description
|
|
output << dump_description(mod, indent)
|
|
|
|
# References
|
|
output << dump_references(mod, indent)
|
|
|
|
# Notes
|
|
output << dump_notes(mod, indent)
|
|
|
|
return output
|
|
|
|
end
|
|
|
|
# Dumps information about an auxiliary module.
|
|
#
|
|
# @param mod [Msf::Auxiliary] the auxiliary module.
|
|
# @param indent [String] the indentation to use.
|
|
# @return [String] the string form of the information.
|
|
def self.dump_auxiliary_module(mod, indent = '')
|
|
output = "\n"
|
|
output << " Name: #{mod.name}\n"
|
|
output << " Module: #{mod.fullname}\n"
|
|
output << " License: #{mod.license}\n"
|
|
output << " Rank: #{mod.rank_to_s.capitalize}\n"
|
|
output << " Disclosed: #{mod.disclosure_date}\n" if mod.disclosure_date
|
|
output << "\n"
|
|
|
|
# Authors
|
|
output << "Provided by:\n"
|
|
mod.each_author { |author|
|
|
output << indent + author.to_s + "\n"
|
|
}
|
|
output << "\n"
|
|
|
|
output << dump_traits(mod)
|
|
|
|
# Actions
|
|
if mod.actions.any?
|
|
output << "Available actions:\n"
|
|
output << dump_module_actions(mod)
|
|
end
|
|
|
|
# Check
|
|
has_check = mod.has_check?
|
|
output << "Check supported:\n"
|
|
output << "#{indent}#{has_check ? 'Yes' : 'No'}\n\n"
|
|
|
|
# Options
|
|
if (mod.options.has_options?)
|
|
output << "Basic options:\n"
|
|
output << dump_options(mod, indent)
|
|
output << "\n"
|
|
end
|
|
|
|
# Description
|
|
output << dump_description(mod, indent)
|
|
|
|
# References
|
|
output << dump_references(mod, indent)
|
|
|
|
# Notes
|
|
output << dump_notes(mod, indent)
|
|
|
|
return output
|
|
end
|
|
|
|
# Dumps information about a post module.
|
|
#
|
|
# @param mod [Msf::Post] the post module.
|
|
# @param indent [String] the indentation to use.
|
|
# @return [String] the string form of the information.
|
|
def self.dump_post_module(mod, indent = '')
|
|
output = "\n"
|
|
output << " Name: #{mod.name}\n"
|
|
output << " Module: #{mod.fullname}\n"
|
|
output << " Platform: #{mod.platform_to_s}\n"
|
|
output << " Arch: #{mod.arch_to_s}\n"
|
|
output << " Rank: #{mod.rank_to_s.capitalize}\n"
|
|
output << " Disclosed: #{mod.disclosure_date}\n" if mod.disclosure_date
|
|
output << "\n"
|
|
|
|
# Authors
|
|
output << "Provided by:\n"
|
|
mod.each_author.each do |author|
|
|
output << indent + author.to_s + "\n"
|
|
end
|
|
output << "\n"
|
|
|
|
output << dump_traits(mod)
|
|
|
|
# Compatible session types
|
|
if mod.session_types
|
|
output << "Compatible session types:\n"
|
|
mod.session_types.sort.each do |type|
|
|
output << indent + type.capitalize + "\n"
|
|
end
|
|
output << "\n"
|
|
end
|
|
|
|
# Actions
|
|
if mod.actions.any?
|
|
output << "Available actions:\n"
|
|
output << dump_module_actions(mod)
|
|
end
|
|
|
|
# Options
|
|
if (mod.options.has_options?)
|
|
output << "Basic options:\n"
|
|
output << dump_options(mod, indent)
|
|
output << "\n"
|
|
end
|
|
|
|
# Description
|
|
output << dump_description(mod, indent)
|
|
|
|
# References
|
|
output << dump_references(mod, indent)
|
|
|
|
# Notes
|
|
output << dump_notes(mod, indent)
|
|
|
|
return output
|
|
end
|
|
|
|
# Dumps information about an evasion module.
|
|
#
|
|
# @param mod [Msf::Evasion] The evasion module instance.
|
|
# @param indent [String] The indentation to use.
|
|
# @return [String] The string form of the information
|
|
def self.dump_evasion_module(mod, indent = '')
|
|
output = "\n"
|
|
output << " Name: #{mod.name}\n"
|
|
output << " Module: #{mod.fullname}\n"
|
|
output << " Platform: #{mod.platform_to_s}\n"
|
|
output << " Arch: #{mod.arch_to_s}\n"
|
|
output << " Privileged: " + (mod.privileged? ? "Yes" : "No") + "\n"
|
|
output << " License: #{mod.license}\n"
|
|
output << " Rank: #{mod.rank_to_s.capitalize}\n"
|
|
output << " Disclosed: #{mod.disclosure_date}\n" if mod.disclosure_date
|
|
output << "\n"
|
|
|
|
# Authors
|
|
output << "Provided by:\n"
|
|
mod.each_author { |author|
|
|
output << indent + author.to_s + "\n"
|
|
}
|
|
output << "\n"
|
|
|
|
# Check
|
|
output << "Check supported:\n"
|
|
output << "#{indent}#{mod.has_check? ? 'Yes' : 'No'}\n\n"
|
|
|
|
# Options
|
|
if (mod.options.has_options?)
|
|
output << "Basic options:\n"
|
|
output << dump_options(mod, indent)
|
|
output << "\n"
|
|
end
|
|
|
|
# Description
|
|
output << dump_description(mod, indent)
|
|
|
|
# References
|
|
output << dump_references(mod, indent)
|
|
|
|
return output
|
|
end
|
|
|
|
# Dumps information about a payload module.
|
|
#
|
|
# @param mod [Msf::Payload] the payload module.
|
|
# @param indent [String] the indentation to use.
|
|
# @return [String] the string form of the information.
|
|
def self.dump_payload_module(mod, indent = '')
|
|
# General
|
|
output = "\n"
|
|
output << " Name: #{mod.name}\n"
|
|
output << " Module: #{mod.fullname}\n"
|
|
output << " Platform: #{mod.platform_to_s}\n"
|
|
output << " Arch: #{mod.arch_to_s}\n"
|
|
output << "Needs Admin: " + (mod.privileged? ? "Yes" : "No") + "\n"
|
|
output << " Total size: #{mod.size}\n"
|
|
output << " Rank: #{mod.rank_to_s.capitalize}\n"
|
|
output << "\n"
|
|
|
|
# Authors
|
|
output << "Provided by:\n"
|
|
mod.each_author { |author|
|
|
output << indent + author.to_s + "\n"
|
|
}
|
|
output << "\n"
|
|
|
|
# Options
|
|
if (mod.options.has_options?)
|
|
output << "Basic options:\n"
|
|
output << dump_options(mod)
|
|
output << "\n"
|
|
end
|
|
|
|
# Description
|
|
output << dump_description(mod, indent)
|
|
output << "\n"
|
|
|
|
return output
|
|
end
|
|
|
|
# Dumps information about a module, just the basics.
|
|
#
|
|
# @param mod [Msf::Module] the module.
|
|
# @param indent [String] the indentation to use.
|
|
# @return [String] the string form of the information.
|
|
def self.dump_basic_module(mod, indent = '')
|
|
# General
|
|
output = "\n"
|
|
output << " Name: #{mod.name}\n"
|
|
output << " Module: #{mod.fullname}\n"
|
|
output << " Platform: #{mod.platform_to_s}\n"
|
|
output << " Arch: #{mod.arch_to_s}\n"
|
|
output << " Rank: #{mod.rank_to_s.capitalize}\n"
|
|
output << "\n"
|
|
|
|
# Authors
|
|
output << "Provided by:\n"
|
|
mod.each_author { |author|
|
|
output << indent + author.to_s + "\n"
|
|
}
|
|
output << "\n"
|
|
|
|
output << dump_traits(mod)
|
|
|
|
# Description
|
|
output << dump_description(mod, indent)
|
|
|
|
output << dump_references(mod, indent)
|
|
|
|
output << "\n"
|
|
|
|
return output
|
|
|
|
end
|
|
|
|
#No current use
|
|
def self.dump_generic_module(mod, indent = '')
|
|
end
|
|
|
|
# Dumps the list of options associated with the
|
|
# supplied module.
|
|
#
|
|
# @param mod [Msf::Module] the module.
|
|
# @param indent [String] the indentation to use.
|
|
# @param missing [Boolean] dump only empty required options.
|
|
# @return [String] the string form of the information.
|
|
def self.dump_options(mod, indent = '', missing = false)
|
|
options = mod.options.map { |_name, option| option }
|
|
options_grouped_by_conditions = options.group_by(&:conditions)
|
|
|
|
options_with_conditions = ''.dup
|
|
options_without_conditions = ''.dup
|
|
|
|
options_grouped_by_conditions.each do |conditions, options|
|
|
tbl = Rex::Text::Table.new(
|
|
'Indent' => indent.length,
|
|
'Columns' =>
|
|
[
|
|
'Name',
|
|
'Current Setting',
|
|
'Required',
|
|
'Description'
|
|
])
|
|
|
|
options.sort_by(&:name).each do |opt|
|
|
name = opt.name
|
|
if mod.datastore.is_a?(Msf::DataStoreWithFallbacks)
|
|
val = mod.datastore[name]
|
|
else
|
|
val = mod.datastore[name].nil? ? opt.default : mod.datastore[name]
|
|
end
|
|
|
|
next if (opt.advanced?)
|
|
next if (opt.evasion?)
|
|
next if (missing && opt.valid?(val))
|
|
|
|
desc = opt.desc.dup
|
|
|
|
# Hint at RPORT proto by regexing mixins
|
|
if name == 'RPORT' && opt.kind_of?(Msf::OptPort)
|
|
mod.class.included_modules.each do |m|
|
|
case m.name
|
|
when /tcp/i, /HttpClient$/
|
|
desc << ' (TCP)'
|
|
break
|
|
when /udp/i
|
|
desc << ' (UDP)'
|
|
break
|
|
end
|
|
end
|
|
end
|
|
|
|
tbl << [ name, opt.display_value(val), opt.required? ? "yes" : "no", desc ]
|
|
end
|
|
|
|
next if conditions.any? && tbl.rows.empty?
|
|
|
|
if conditions.any?
|
|
options_with_conditions << "\n\n#{indent}When #{Msf::OptCondition.format_conditions(mod, options.first)}:\n\n"
|
|
options_with_conditions << tbl.to_s
|
|
else
|
|
options_without_conditions << tbl.to_s
|
|
end
|
|
end
|
|
|
|
result = "#{options_without_conditions}#{options_with_conditions}"
|
|
result
|
|
end
|
|
|
|
# Dumps the advanced options associated with the supplied module.
|
|
#
|
|
# @param mod [Msf::Module] the module.
|
|
# @param indent [String] the indentation to use.
|
|
# @return [String] the string form of the information.
|
|
def self.dump_advanced_options(mod, indent = '')
|
|
options = mod.options.map { |_name, option| option }
|
|
options_grouped_by_conditions = options.group_by(&:conditions)
|
|
|
|
options_with_conditions = ''.dup
|
|
options_without_conditions = ''.dup
|
|
|
|
options_grouped_by_conditions.each do |conditions, options|
|
|
tbl = Rex::Text::Table.new(
|
|
'Indent' => indent.length,
|
|
'Columns' =>
|
|
[
|
|
'Name',
|
|
'Current Setting',
|
|
'Required',
|
|
'Description'
|
|
])
|
|
|
|
options.sort_by(&:name).each do |opt|
|
|
next unless opt.advanced?
|
|
|
|
name = opt.name
|
|
if mod.datastore.is_a?(Msf::DataStoreWithFallbacks)
|
|
val = mod.datastore[name]
|
|
else
|
|
val = mod.datastore[name].nil? ? opt.default : mod.datastore[name]
|
|
end
|
|
tbl << [ name, opt.display_value(val), opt.required? ? "yes" : "no", opt.desc ]
|
|
end
|
|
|
|
next if conditions.any? && tbl.rows.empty?
|
|
|
|
if conditions.any?
|
|
options_with_conditions << "\n\n#{indent}Active when #{Msf::OptCondition.format_conditions(mod, options.first)}:\n\n"
|
|
options_with_conditions << tbl.to_s
|
|
else
|
|
options_without_conditions << tbl.to_s
|
|
end
|
|
end
|
|
|
|
result = "#{options_without_conditions}#{options_with_conditions}"
|
|
result
|
|
end
|
|
|
|
# Dumps the evasion options associated with the supplied module.
|
|
#
|
|
# @param mod [Msf::Module] the module.
|
|
# @param indent [String] the indentation to use.
|
|
# @return [String] the string form of the information.
|
|
def self.dump_evasion_options(mod, indent = '')
|
|
options = mod.options.map { |_name, option| option }
|
|
options_grouped_by_conditions = options.group_by(&:conditions)
|
|
|
|
options_with_conditions = ''.dup
|
|
options_without_conditions = ''.dup
|
|
|
|
options_grouped_by_conditions.each do |conditions, options|
|
|
tbl = Rex::Text::Table.new(
|
|
'Indent' => indent.length,
|
|
'Columns' =>
|
|
[
|
|
'Name',
|
|
'Current Setting',
|
|
'Required',
|
|
'Description'
|
|
])
|
|
|
|
options.sort_by(&:name).each do |opt|
|
|
next unless opt.evasion?
|
|
|
|
name = opt.name
|
|
if mod.datastore.is_a?(Msf::DataStoreWithFallbacks)
|
|
val = mod.datastore[name]
|
|
else
|
|
val = mod.datastore[name].nil? ? opt.default : mod.datastore[name]
|
|
end
|
|
tbl << [ name, opt.display_value(val), opt.required? ? "yes" : "no", opt.desc ]
|
|
end
|
|
|
|
next if conditions.any? && tbl.rows.empty?
|
|
|
|
if conditions.any?
|
|
options_with_conditions << "\n\n#{indent}When #{Msf::OptCondition.format_conditions(mod, options.first)}:\n\n"
|
|
options_with_conditions << tbl.to_s
|
|
else
|
|
options_without_conditions << tbl.to_s
|
|
end
|
|
end
|
|
result = "#{options_without_conditions}#{options_with_conditions}"
|
|
result
|
|
end
|
|
|
|
# Dumps the references associated with the supplied module.
|
|
#
|
|
# @param mod [Msf::Module] the module.
|
|
# @param indent [String] the indentation to use.
|
|
# @return [String] the string form of the information.
|
|
def self.dump_references(mod, indent = '')
|
|
output = ''
|
|
|
|
if (mod.respond_to?(:references) && mod.references && mod.references.length > 0)
|
|
output << "References:\n"
|
|
|
|
mod.references.each do |ref|
|
|
case ref.ctx_id
|
|
when 'LOGO', 'SOUNDTRACK'
|
|
output << indent + ref.to_s + "\n"
|
|
Rex::Compat.open_browser(ref.ctx_val) if Rex::Compat.getenv('FUEL_THE_HYPE_MACHINE')
|
|
else
|
|
output << indent + ref.to_s + "\n"
|
|
end
|
|
end
|
|
|
|
output << "\n"
|
|
end
|
|
|
|
output
|
|
end
|
|
|
|
# Dumps the notes associated with the supplied module.
|
|
#
|
|
# @param mod [Msf::Module] the module.
|
|
# @param indent [String] the indentation to use.
|
|
# @return [String] the string form of the information.
|
|
def self.dump_notes(mod, indent = '')
|
|
output = ''
|
|
|
|
mod.notes.each do |name, val|
|
|
next unless val.present?
|
|
|
|
case name
|
|
when 'AKA'
|
|
output << "Also known as:\n"
|
|
val.each { |aka| output << "#{indent}#{aka}\n" }
|
|
when 'NOCVE'
|
|
output << "CVE not available for the following reason:\n" \
|
|
"#{indent}#{val}\n"
|
|
when 'RelatedModules'
|
|
output << "Related modules:\n"
|
|
val.each { |related| output << "#{indent}#{related}\n" }
|
|
when 'Stability', 'SideEffects', 'Reliability'
|
|
# Handled by dump_traits
|
|
next
|
|
else
|
|
output << "#{name}:\n"
|
|
|
|
case val
|
|
when Array
|
|
val.each { |v| output << "#{indent}#{v}\n" }
|
|
when Hash
|
|
val.each { |k, v| output << "#{indent}#{k}: #{v}\n" }
|
|
else
|
|
# Display the raw note
|
|
output << "#{indent}#{val}\n"
|
|
end
|
|
end
|
|
|
|
output << "\n"
|
|
end
|
|
|
|
output
|
|
end
|
|
|
|
# Dumps the contents of a datastore.
|
|
#
|
|
# @param name [String] displayed as the table header.
|
|
# @param ds [Msf::DataStore] the DataStore to dump.
|
|
# @param indent [Integer] the indentation size.
|
|
# @param col [Integer] the column width.
|
|
# @return [String] the formatted DataStore contents.
|
|
def self.dump_datastore(name, ds, indent = DefaultIndent, col = DefaultColumnWrap)
|
|
tbl = Rex::Text::Table.new(
|
|
'Indent' => indent,
|
|
'Header' => name,
|
|
'Columns' =>
|
|
[
|
|
'Name',
|
|
'Value'
|
|
])
|
|
|
|
ds.keys.sort.each { |k|
|
|
tbl << [ k, (ds[k] != nil) ? ds[k].to_s : '' ]
|
|
}
|
|
|
|
return ds.length > 0 ? tbl.to_s : "#{tbl.header_to_s}No entries in data store.\n"
|
|
end
|
|
|
|
# Dumps the list of sessions.
|
|
#
|
|
# @param framework [Msf::Framework] the framework to dump.
|
|
# @param opts [Hash] the options to dump with.
|
|
# @option opts :verbose [Boolean] gives more information if set to
|
|
# true.
|
|
# @option opts :indent [Integer] set the indentation amount.
|
|
# @return [String] the formatted list of sessions.
|
|
def self.dump_sessions(framework, opts={})
|
|
output = ""
|
|
verbose = opts[:verbose] || false
|
|
sessions = opts[:sessions] || framework.sessions
|
|
show_active = opts[:show_active] || false
|
|
show_inactive = opts[:show_inactive] || false
|
|
# if show_active and show_inactive are false the caller didn't
|
|
# specify either flag; default to displaying active sessions
|
|
show_active = true if !(show_active || show_inactive)
|
|
show_extended = opts[:show_extended] || false
|
|
indent = opts[:indent] || DefaultIndent
|
|
|
|
return dump_sessions_verbose(framework, opts) if verbose
|
|
|
|
if show_active
|
|
columns = []
|
|
columns << 'Id'
|
|
columns << 'Name'
|
|
columns << 'Type'
|
|
columns << 'Checkin?' if show_extended
|
|
columns << 'Enc?' if show_extended
|
|
columns << 'Local URI' if show_extended
|
|
columns << 'Information'
|
|
columns << 'Connection'
|
|
|
|
tbl = Rex::Text::Table.new(
|
|
'Header' => "Active sessions",
|
|
'Columns' => columns,
|
|
'Indent' => indent)
|
|
|
|
sessions.each do |session_id, session|
|
|
row = create_msf_session_row(session, show_extended)
|
|
tbl << row
|
|
end
|
|
|
|
output << (tbl.rows.count > 0 ? tbl.to_s : "#{tbl.header_to_s}No active sessions.\n")
|
|
end
|
|
|
|
if show_inactive
|
|
output << "\n" if show_active
|
|
|
|
columns = []
|
|
columns << 'Closed'
|
|
columns << 'Opened'
|
|
columns << 'Reason Closed'
|
|
columns << 'Type'
|
|
columns << 'Address'
|
|
|
|
tbl = Rex::Text::Table.new(
|
|
'Header' => "Inactive sessions",
|
|
'Columns' => columns,
|
|
'Indent' => indent,
|
|
'SortIndex' => 1)
|
|
|
|
if framework.db.active
|
|
framework.db.sessions.each do |session|
|
|
unless session.closed_at.nil?
|
|
row = create_mdm_session_row(session, show_extended)
|
|
tbl << row
|
|
end
|
|
end
|
|
end
|
|
|
|
output << (tbl.rows.count > 0 ? tbl.to_s : "#{tbl.header_to_s}No inactive sessions.\n")
|
|
end
|
|
|
|
# return formatted listing of sessions
|
|
output
|
|
end
|
|
|
|
# Creates a table row that represents the specified session.
|
|
#
|
|
# @param session [Msf::Session] session used to create a table row.
|
|
# @param show_extended [Boolean] Indicates if extended information will be included in the row.
|
|
# @return [Array] table row of session data.
|
|
def self.create_msf_session_row(session, show_extended)
|
|
row = []
|
|
row << session.sid.to_s
|
|
row << session.sname.to_s
|
|
row << session.type.to_s
|
|
if session.respond_to?(:session_type)
|
|
row[-1] << " #{session.session_type}"
|
|
elsif session.respond_to?(:platform)
|
|
row[-1] << " #{session.platform}"
|
|
end
|
|
|
|
if show_extended
|
|
if session.respond_to?(:last_checkin) && session.last_checkin
|
|
row << "#{(Time.now.to_i - session.last_checkin.to_i)}s ago"
|
|
else
|
|
row << '?'
|
|
end
|
|
|
|
if session.respond_to?(:tlv_enc_key) && session.tlv_enc_key && session.tlv_enc_key[:key]
|
|
row << 'Y'
|
|
else
|
|
row << 'N'
|
|
end
|
|
|
|
if session.exploit_datastore && session.exploit_datastore.has_key?('LURI') && !session.exploit_datastore['LURI'].empty?
|
|
row << "(#{session.exploit_datastore['LURI']})"
|
|
else
|
|
row << '?'
|
|
end
|
|
end
|
|
|
|
sinfo = session.info.to_s
|
|
sinfo = sinfo.gsub(/[\r\n\t]+/, ' ')
|
|
# Arbitrarily cut info at 80 columns
|
|
if sinfo.length > 80
|
|
sinfo = "#{sinfo[0,77]}..."
|
|
end
|
|
row << sinfo
|
|
|
|
row << "#{session.tunnel_to_s} (#{session.session_host})"
|
|
|
|
# return complete row
|
|
row
|
|
end
|
|
|
|
# Creates a table row that represents the specified session.
|
|
#
|
|
# @param session [Mdm::Session] session used to create a table row.
|
|
# @param show_extended [Boolean] Indicates if extended information will be included in the row.
|
|
# @return [Array] table row of session data.
|
|
def self.create_mdm_session_row(session, show_extended)
|
|
row = []
|
|
row << session.closed_at.to_s
|
|
row << session.opened_at.to_s
|
|
row << session.close_reason
|
|
row << session.stype
|
|
if session.respond_to?(:platform) && !session.platform.nil?
|
|
row[-1] << " #{session.platform}"
|
|
end
|
|
row << (!session.host.nil? ? session.host.address : nil)
|
|
|
|
# return complete row
|
|
row
|
|
end
|
|
|
|
# Dumps the list of active sessions in verbose mode
|
|
#
|
|
# @param framework [Msf::Framework] the framework to dump.
|
|
# @param opts [Hash] the options to dump with.
|
|
# @return [String] the formatted list of sessions.
|
|
def self.dump_sessions_verbose(framework, opts={})
|
|
out = "Active sessions\n" +
|
|
"===============\n\n"
|
|
|
|
if framework.sessions.length == 0
|
|
out << "No active sessions.\n"
|
|
return out
|
|
end
|
|
|
|
sessions = opts[:sessions] || framework.sessions
|
|
|
|
sessions.each do |session_id, session|
|
|
sess_info = session.info.to_s
|
|
sess_id = session.sid.to_s
|
|
sess_name = session.sname.to_s
|
|
sess_tunnel = session.tunnel_to_s + " (#{session.session_host})"
|
|
sess_via = session.via_exploit.to_s
|
|
sess_type = session.type.to_s
|
|
sess_uuid = session.payload_uuid.to_s
|
|
sess_luri = session.exploit_datastore['LURI'] || "" if session.exploit_datastore
|
|
sess_enc = 'No'
|
|
if session.respond_to?(:tlv_enc_key) && session.tlv_enc_key && session.tlv_enc_key[:key]
|
|
sess_enc = "Yes (AES-#{session.tlv_enc_key[:key].length * 8}-CBC)"
|
|
end
|
|
|
|
sess_checkin = "<none>"
|
|
sess_registration = "No"
|
|
|
|
if session.respond_to?(:platform) && session.platform
|
|
sess_type << " #{session.platform}"
|
|
end
|
|
|
|
if session.respond_to?(:last_checkin) && session.last_checkin
|
|
sess_checkin = "#{(Time.now.to_i - session.last_checkin.to_i)}s ago @ #{session.last_checkin.to_s}"
|
|
end
|
|
|
|
if !session.payload_uuid.nil? && session.payload_uuid.registered
|
|
sess_registration = "Yes"
|
|
if session.payload_uuid.name
|
|
sess_registration << " - Name=\"#{session.payload_uuid.name}\""
|
|
end
|
|
end
|
|
|
|
out << " Session ID: #{sess_id}\n"
|
|
out << " Name: #{sess_name}\n"
|
|
out << " Type: #{sess_type}\n"
|
|
out << " Info: #{sess_info}\n"
|
|
out << " Tunnel: #{sess_tunnel}\n"
|
|
out << " Via: #{sess_via}\n"
|
|
out << " Encrypted: #{sess_enc}\n"
|
|
out << " UUID: #{sess_uuid}\n"
|
|
out << " CheckIn: #{sess_checkin}\n"
|
|
out << " Registered: #{sess_registration}\n"
|
|
unless (sess_luri || '').empty?
|
|
out << " LURI: #{sess_luri}\n"
|
|
end
|
|
|
|
out << "\n"
|
|
end
|
|
|
|
out << "\n"
|
|
return out
|
|
end
|
|
|
|
# Dumps the list of running jobs.
|
|
#
|
|
# @param framework [Msf::Framework] the framework.
|
|
# @param verbose [Boolean] if true, also prints the payload, LPORT, URIPATH
|
|
# and start time, if they exist, for each job.
|
|
# @param indent [Integer] the indentation amount.
|
|
# @param col [Integer] the column wrap width.
|
|
# @return [String] the formatted list of running jobs.
|
|
def self.dump_jobs(framework, verbose = false, indent = DefaultIndent, col = DefaultColumnWrap)
|
|
columns = [ 'Id', 'Name', "Payload", "Payload opts"]
|
|
|
|
if (verbose)
|
|
columns += [ "URIPATH", "Start Time", "Handler opts", "Persist" ]
|
|
end
|
|
|
|
tbl = Rex::Text::Table.new(
|
|
'Indent' => indent,
|
|
'Header' => "Jobs",
|
|
'Columns' => columns
|
|
)
|
|
|
|
# Get the persistent job info.
|
|
if verbose
|
|
begin
|
|
persist_list = JSON.parse(File.read(Msf::Config.persist_file))
|
|
rescue Errno::ENOENT, JSON::ParserError
|
|
persist_list = []
|
|
end
|
|
end
|
|
|
|
# jobs are stored as a hash with the keys being a numeric String job_id.
|
|
framework.jobs.keys.sort_by(&:to_i).each do |job_id|
|
|
# Job context is stored as an Array with the 0th element being
|
|
# the running module. If that module is an exploit, ctx will also
|
|
# contain its payload.
|
|
exploit_mod, _payload_mod = framework.jobs[job_id].ctx
|
|
row = []
|
|
row[0] = job_id
|
|
row[1] = framework.jobs[job_id].name
|
|
|
|
pinst = exploit_mod.respond_to?(:payload_instance) ? exploit_mod.payload_instance : nil
|
|
payload_uri = ''
|
|
|
|
if pinst.nil?
|
|
row[2] = ""
|
|
row[3] = ""
|
|
else
|
|
row[2] = pinst.refname
|
|
row[3] = ""
|
|
if pinst.respond_to?(:payload_uri)
|
|
payload_uri = pinst.payload_uri.strip
|
|
row[3] << payload_uri
|
|
end
|
|
if pinst.respond_to?(:luri)
|
|
row[3] << pinst.luri
|
|
end
|
|
if pinst.respond_to?(:comm_string)
|
|
via = pinst.comm_string
|
|
if via
|
|
row[3] << " #{via}"
|
|
end
|
|
end
|
|
end
|
|
|
|
if verbose
|
|
uripath = exploit_mod.get_resource if exploit_mod.respond_to?(:get_resource)
|
|
uripath ||= exploit_mod.datastore['URIPATH']
|
|
row[4] = uripath
|
|
row[5] = framework.jobs[job_id].start_time
|
|
row[6] = ''
|
|
row[7] = 'false'
|
|
|
|
if pinst.respond_to?(:listener_uri)
|
|
listener_uri = pinst.listener_uri.strip
|
|
row[6] = listener_uri unless listener_uri == payload_uri
|
|
end
|
|
|
|
persist_list.each do |e|
|
|
handler_ctx = framework.jobs[job_id.to_s].ctx[1]
|
|
if handler_ctx && handler_ctx.respond_to?(:datastore)
|
|
row[7] = 'true' if e['mod_options']['Options'] == handler_ctx.datastore
|
|
end
|
|
end
|
|
|
|
end
|
|
tbl << row
|
|
end
|
|
|
|
return framework.jobs.keys.length > 0 ? tbl.to_s : "#{tbl.header_to_s}No active jobs.\n"
|
|
end
|
|
|
|
# Dumps the module description
|
|
#
|
|
# @param mod [Msf::Module] the module.
|
|
# @param indent [String] the indentation string
|
|
# @return [String] the string description
|
|
def self.dump_description(mod, indent)
|
|
description = mod.description
|
|
|
|
output = "Description:\n"
|
|
output << word_wrap_description(description, indent)
|
|
output << "\n\n"
|
|
end
|
|
|
|
# @param str [String] the string to wrap.
|
|
# @param indent [String] the indentation string
|
|
# @return [String] the wrapped string.
|
|
def self.word_wrap_description(str, indent = '')
|
|
return '' if str.blank?
|
|
|
|
str_lines = str.strip.lines(chomp: true)
|
|
# Calculate the preceding whitespace length of each line
|
|
smallest_preceding_whitespace = nil
|
|
str_lines[1..].to_a.each do |line|
|
|
preceding_whitespace = line[/^\s+/]
|
|
if preceding_whitespace && (smallest_preceding_whitespace.nil? || preceding_whitespace.length < smallest_preceding_whitespace)
|
|
smallest_preceding_whitespace = preceding_whitespace.length
|
|
end
|
|
end
|
|
|
|
# Normalize any existing left-most whitespace on each line; Ignoring the first line which won't have any preceding whitespace
|
|
result = str_lines.map.with_index do |line, index|
|
|
next if line.blank?
|
|
|
|
"#{indent}#{index == 0 || smallest_preceding_whitespace.nil? ? line : line[smallest_preceding_whitespace..]}"
|
|
end.join("\n")
|
|
|
|
result
|
|
end
|
|
end
|
|
|
|
end end
|