555a9f2559
[Fixes #36737359] Refactor Msf::ModuleManager into concerns so its easier to understand and duplicate code can be made DRY. The refactoring also ensures that when loading from directories, Fastlibs, or reloading, the wrapper module will always be named so that activesupport/dependencies will function.
1149 lines
27 KiB
Ruby
1149 lines
27 KiB
Ruby
# -*- coding: binary -*-
|
|
require 'msf/core'
|
|
|
|
module Msf
|
|
|
|
###
|
|
#
|
|
# The module base class is responsible for providing the common interface
|
|
# that is used to interact with modules at the most basic levels, such as
|
|
# by inspecting a given module's attributes (name, dsecription, version,
|
|
# authors, etc) and by managing the module's data store.
|
|
#
|
|
###
|
|
class Module
|
|
|
|
# Modules can subscribe to a user-interface, and as such they include the
|
|
# UI subscriber module. This provides methods like print, print_line, etc.
|
|
# User interfaces are designed to be medium independent, and as such the
|
|
# user interface subscribes are designed to provide a flexible way of
|
|
# interacting with the user, n stuff.
|
|
include Rex::Ui::Subscriber
|
|
|
|
# Make include public so we can runtime extend
|
|
public_class_method :include
|
|
|
|
class << self
|
|
include Framework::Offspring
|
|
|
|
#
|
|
# Class method to figure out what type of module this is
|
|
#
|
|
def type
|
|
raise NotImplementedError
|
|
end
|
|
|
|
def fullname
|
|
return type + '/' + refname
|
|
end
|
|
|
|
def shortname
|
|
return refname.split('/')[-1]
|
|
end
|
|
|
|
#
|
|
# Returns this module's ranking.
|
|
#
|
|
def rank
|
|
(const_defined?('Rank')) ? const_get('Rank') : NormalRanking
|
|
end
|
|
|
|
#
|
|
# Returns this module's ranking as a string representation.
|
|
#
|
|
def rank_to_s
|
|
RankingName[rank]
|
|
end
|
|
|
|
#
|
|
# Returns this module's ranking as a string for display.
|
|
#
|
|
def rank_to_h
|
|
rank_to_s.gsub('Rank', '').downcase
|
|
end
|
|
#
|
|
# The module's name that is assigned it it by the framework
|
|
# or derived from the path that the module is loaded from.
|
|
#
|
|
attr_accessor :refname
|
|
|
|
#
|
|
# This attribute holds the non-duplicated copy of the module
|
|
# implementation. This attribute is used for reloading purposes so that
|
|
# it can be re-duplicated.
|
|
#
|
|
attr_accessor :orig_cls
|
|
|
|
#
|
|
# The path from which the module was loaded.
|
|
#
|
|
attr_accessor :file_path
|
|
end
|
|
|
|
#
|
|
# Returns the class reference to the framework
|
|
#
|
|
def framework
|
|
return self.class.framework
|
|
end
|
|
|
|
#
|
|
# This method allows modules to tell the framework if they are usable
|
|
# on the system that they are being loaded on in a generic fashion.
|
|
# By default, all modules are indicated as being usable. An example of
|
|
# where this is useful is if the module depends on something external to
|
|
# ruby, such as a binary.
|
|
#
|
|
def self.is_usable
|
|
true
|
|
end
|
|
|
|
require 'msf/core/module/author'
|
|
require 'msf/core/module/platform_list'
|
|
require 'msf/core/module/reference'
|
|
require 'msf/core/module/target'
|
|
require 'msf/core/module/auxiliary_action'
|
|
require 'msf/core/module/has_actions'
|
|
|
|
#
|
|
# Creates an instance of an abstract module using the supplied information
|
|
# hash.
|
|
#
|
|
def initialize(info = {})
|
|
|
|
@module_info_copy = info.dup
|
|
|
|
|
|
self.module_info = info
|
|
generate_uuid
|
|
|
|
set_defaults
|
|
|
|
# Initialize module compatibility hashes
|
|
init_compat
|
|
|
|
# Fixup module fields as needed
|
|
info_fixups
|
|
|
|
# Transform some of the fields to arrays as necessary
|
|
self.author = Author.transform(module_info['Author'])
|
|
self.arch = Rex::Transformer.transform(module_info['Arch'], Array, [ String ], 'Arch')
|
|
self.platform = PlatformList.transform(module_info['Platform'])
|
|
self.references = Rex::Transformer.transform(module_info['References'], Array, [ SiteReference, Reference ], 'Ref')
|
|
|
|
# Create and initialize the option container for this module
|
|
self.options = OptionContainer.new
|
|
self.options.add_options(info['Options'], self.class)
|
|
self.options.add_advanced_options(info['AdvancedOptions'], self.class)
|
|
self.options.add_evasion_options(info['EvasionOptions'], self.class)
|
|
|
|
# Create and initialize the data store for this module
|
|
self.datastore = ModuleDataStore.new(self)
|
|
|
|
# Import default options into the datastore
|
|
import_defaults
|
|
|
|
self.privileged = module_info['Privileged'] || false
|
|
self.license = module_info['License'] || MSF_LICENSE
|
|
|
|
# Allow all modules to track their current workspace
|
|
register_advanced_options(
|
|
[
|
|
OptString.new('WORKSPACE', [ false, "Specify the workspace for this module" ]),
|
|
OptBool.new('VERBOSE', [ false, 'Enable detailed status messages', false ])
|
|
], Msf::Module)
|
|
|
|
end
|
|
|
|
#
|
|
# Creates a fresh copy of an instantiated module
|
|
#
|
|
def replicant
|
|
|
|
obj = self.class.new
|
|
self.instance_variables.each { |k|
|
|
v = instance_variable_get(k)
|
|
v = v.dup rescue v
|
|
obj.instance_variable_set(k, v)
|
|
}
|
|
|
|
obj.datastore = self.datastore.copy
|
|
obj.user_input = self.user_input
|
|
obj.user_output = self.user_output
|
|
obj.module_store = self.module_store.clone
|
|
obj
|
|
end
|
|
|
|
#
|
|
# Overwrite the Subscriber print_(status|error|good) to do time stamps
|
|
#
|
|
|
|
def print_prefix
|
|
if(
|
|
datastore['TimestampOutput'] =~ /^(t|y|1)/i or
|
|
framework.datastore['TimestampOutput'] =~ /^(t|y|1)/i
|
|
)
|
|
prefix = "[#{Time.now.strftime("%Y.%m.%d-%H:%M:%S")}] "
|
|
|
|
xn ||= datastore['ExploitNumber']
|
|
xn ||= framework.datastore['ExploitNumber']
|
|
if xn.is_a?(Fixnum)
|
|
prefix << "[%04d] " % xn
|
|
end
|
|
|
|
return prefix
|
|
end
|
|
''
|
|
end
|
|
|
|
def print_status(msg='')
|
|
super(print_prefix + msg)
|
|
end
|
|
|
|
def print_error(msg='')
|
|
super(print_prefix + msg)
|
|
end
|
|
|
|
def print_good(msg='')
|
|
super(print_prefix + msg)
|
|
end
|
|
|
|
|
|
#
|
|
# Overwrite the Subscriber print_line to do custom prefixes
|
|
#
|
|
|
|
def print_line_prefix
|
|
datastore['CustomPrintPrefix'] || framework.datastore['CustomPrintPrefix'] || ''
|
|
end
|
|
|
|
def print_line(msg='')
|
|
super(print_line_prefix + msg)
|
|
end
|
|
|
|
# Verbose version of #print_status
|
|
def vprint_status(msg)
|
|
print_status(msg) if datastore['VERBOSE'] || framework.datastore['VERBOSE']
|
|
end
|
|
# Verbose version of #print_error
|
|
def vprint_error(msg)
|
|
print_error(msg) if datastore['VERBOSE'] || framework.datastore['VERBOSE']
|
|
end
|
|
# Verbose version of #print_good
|
|
def vprint_good(msg)
|
|
print_good(msg) if datastore['VERBOSE'] || framework.datastore['VERBOSE']
|
|
end
|
|
# Verbose version of #print_line
|
|
def vprint_line(msg)
|
|
print_line(msg) if datastore['VERBOSE'] || framework.datastore['VERBOSE']
|
|
end
|
|
# Verbose version of #print_debug
|
|
def vprint_debug(msg)
|
|
print_debug(msg) if datastore['VERBOSE'] || framework.datastore['VERBOSE']
|
|
end
|
|
|
|
#
|
|
# Returns the module's framework full reference name. This is the
|
|
# short name that end-users work with (refname) plus the type
|
|
# of module prepended. Ex:
|
|
#
|
|
# payloads/windows/shell/reverse_tcp
|
|
#
|
|
def fullname
|
|
return self.class.fullname
|
|
end
|
|
|
|
#
|
|
# Returns the module's framework reference name. This is the
|
|
# short name that end-users work with. Ex:
|
|
#
|
|
# windows/shell/reverse_tcp
|
|
#
|
|
def refname
|
|
return self.class.refname
|
|
end
|
|
|
|
#
|
|
# Returns the module's rank.
|
|
#
|
|
def rank
|
|
return self.class.rank
|
|
end
|
|
|
|
#
|
|
# Returns the module's rank in string format.
|
|
#
|
|
def rank_to_s
|
|
return self.class.rank_to_s
|
|
end
|
|
|
|
#
|
|
# Returns the module's rank in display format.
|
|
#
|
|
def rank_to_h
|
|
return self.class.rank_to_h
|
|
end
|
|
|
|
#
|
|
# Returns the module's framework short name. This is a
|
|
# possibly conflicting name used for things like console
|
|
# prompts.
|
|
#
|
|
# reverse_tcp
|
|
#
|
|
def shortname
|
|
return self.class.shortname
|
|
end
|
|
|
|
#
|
|
# Returns the unduplicated class associated with this module.
|
|
#
|
|
def orig_cls
|
|
return self.class.orig_cls
|
|
end
|
|
|
|
#
|
|
# The path to the file in which the module can be loaded from.
|
|
#
|
|
def file_path
|
|
self.class.file_path
|
|
end
|
|
|
|
#
|
|
# Return the module's name from the module information hash.
|
|
#
|
|
def name
|
|
module_info['Name']
|
|
end
|
|
|
|
#
|
|
# Returns the module's alias, if it has one. Otherwise, the module's
|
|
# name is returned.
|
|
#
|
|
def alias
|
|
module_info['Alias']
|
|
end
|
|
|
|
#
|
|
# Return the module's description.
|
|
#
|
|
def description
|
|
module_info['Description']
|
|
end
|
|
|
|
#
|
|
# Return the module's legacy version information.
|
|
#
|
|
def version
|
|
module_info['Version'].split(/,/).map { |ver|
|
|
ver.gsub(/\$Rev.s.on:\s+|\s+\$$/, '')
|
|
}.join(',')
|
|
end
|
|
|
|
#
|
|
# Returns the disclosure date, if known.
|
|
#
|
|
def disclosure_date
|
|
date_str = Date.parse(module_info['DisclosureDate'].to_s) rescue nil
|
|
end
|
|
|
|
#
|
|
# Returns the hash that describes this module's compatibilities.
|
|
#
|
|
def compat
|
|
module_info['Compat'] || {}
|
|
end
|
|
|
|
#
|
|
# Returns the address of the last target host (rough estimate)
|
|
#
|
|
def target_host
|
|
if(self.respond_to?('rhost'))
|
|
return rhost()
|
|
end
|
|
|
|
if(self.datastore['RHOST'])
|
|
return self.datastore['RHOST']
|
|
end
|
|
|
|
nil
|
|
end
|
|
|
|
#
|
|
# Returns the address of the last target port (rough estimate)
|
|
#
|
|
def target_port
|
|
if(self.respond_to?('rport'))
|
|
return rport()
|
|
end
|
|
|
|
if(self.datastore['RPORT'])
|
|
return self.datastore['RPORT']
|
|
end
|
|
|
|
nil
|
|
end
|
|
|
|
#
|
|
# Returns the current workspace
|
|
#
|
|
def workspace
|
|
self.datastore['WORKSPACE'] ||
|
|
(framework.db and framework.db.active and framework.db.workspace and framework.db.workspace.name)
|
|
end
|
|
|
|
#
|
|
# Returns the username that instantiated this module, this tries a handful of methods
|
|
# to determine what actual user ran this module.
|
|
#
|
|
def owner
|
|
# Generic method to configure a module owner
|
|
username = self.datastore['MODULE_OWNER'].to_s.strip
|
|
|
|
# Specific method used by the commercial products
|
|
if username.empty?
|
|
username = self.datastore['PROUSER'].to_s.strip
|
|
end
|
|
|
|
# Fallback when neither prior method is available, common for msfconsole
|
|
if username.empty?
|
|
username = (ENV['LOGNAME'] || ENV['USERNAME'] || ENV['USER'] || "unknown").to_s.strip
|
|
end
|
|
|
|
username
|
|
end
|
|
|
|
#
|
|
# Scans the parent module reference to populate additional information. This
|
|
# is used to inherit common settings (owner, workspace, parent uuid, etc).
|
|
#
|
|
def register_parent(ref)
|
|
self.datastore['WORKSPACE'] = (ref.datastore['WORKSPACE'] ? ref.datastore['WORKSPACE'].dup : nil)
|
|
self.datastore['PROUSER'] = (ref.datastore['PROUSER'] ? ref.datastore['PROUSER'].dup : nil)
|
|
self.datastore['MODULE_OWNER'] = ref.owner.dup
|
|
self.datastore['ParentUUID'] = ref.uuid.dup
|
|
end
|
|
|
|
#
|
|
# Returns whether or not this module is compatible with the supplied
|
|
# module.
|
|
#
|
|
def compatible?(mod)
|
|
ch = nil
|
|
|
|
# Invalid module? Shoot, we can't compare that.
|
|
return true if (mod == nil)
|
|
|
|
# Determine which hash to used based on the supplied module type
|
|
if (mod.type == MODULE_ENCODER)
|
|
ch = self.compat['Encoder']
|
|
elsif (mod.type == MODULE_NOP)
|
|
ch = self.compat['Nop']
|
|
elsif (mod.type == MODULE_PAYLOAD)
|
|
ch = self.compat['Payload']
|
|
else
|
|
return true
|
|
end
|
|
|
|
# Enumerate each compatibility item in our hash to find out
|
|
# if we're compatible with this sucker.
|
|
ch.each_pair do |k,v|
|
|
|
|
# Get the value of the current key from the module, such as
|
|
# the ConnectionType for a stager (ws2ord, for instance).
|
|
mval = mod.module_info[k]
|
|
|
|
# Reject a filled compat item on one side, but not the other
|
|
if (v and not mval)
|
|
dlog("Module #{mod.refname} is incompatible with #{self.refname} for #{k}: limiter was #{v}")
|
|
return false
|
|
end
|
|
|
|
# Track how many of our values matched the module
|
|
mcnt = 0
|
|
|
|
# Values are whitespace separated
|
|
sv = v.split(/\s+/)
|
|
mv = mval.split(/\s+/)
|
|
|
|
sv.each do |x|
|
|
|
|
dlog("Checking compat [#{mod.refname} with #{self.refname}]: #{x} to #{mv.join(", ")}", 'core', LEV_3)
|
|
|
|
# Verify that any negate values are not matched
|
|
if (x[0,1] == '-' and mv.include?(x[1, x.length-1]))
|
|
dlog("Module #{mod.refname} is incompatible with #{self.refname} for #{k}: limiter was #{x}, value was #{mval}", 'core', LEV_1)
|
|
return false
|
|
end
|
|
|
|
mcnt += 1 if mv.include?(x)
|
|
end
|
|
|
|
# No values matched, reject this module
|
|
if (mcnt == 0)
|
|
dlog("Module #{mod.refname} is incompatible with #{self.refname} for #{k}: limiter was #{v}, value was #{mval}", 'core', LEV_1)
|
|
return false
|
|
end
|
|
|
|
end
|
|
|
|
dlog("Module #{mod.refname} is compatible with #{self.refname}", "core", LEV_1)
|
|
|
|
|
|
# If we get here, we're compatible.
|
|
return true
|
|
end
|
|
|
|
#
|
|
# Return the module's abstract type.
|
|
#
|
|
def type
|
|
raise NotImplementedError
|
|
end
|
|
|
|
#
|
|
# Return a comma separated list of author for this module.
|
|
#
|
|
def author_to_s
|
|
return author.collect { |author| author.to_s }.join(", ")
|
|
end
|
|
|
|
#
|
|
# Enumerate each author.
|
|
#
|
|
def each_author(&block)
|
|
author.each(&block)
|
|
end
|
|
|
|
#
|
|
# Return a comma separated list of supported architectures, if any.
|
|
#
|
|
def arch_to_s
|
|
return arch.join(", ")
|
|
end
|
|
|
|
#
|
|
# Enumerate each architecture.
|
|
#
|
|
def each_arch(&block)
|
|
arch.each(&block)
|
|
end
|
|
|
|
#
|
|
# Return whether or not the module supports the supplied architecture.
|
|
#
|
|
def arch?(what)
|
|
return true if (what == ARCH_ANY)
|
|
|
|
return arch.index(what) != nil
|
|
end
|
|
|
|
#
|
|
# Return a comma separated list of supported platforms, if any.
|
|
#
|
|
def platform_to_s
|
|
return ((platform.all?) ? [ "All" ] : platform.names).join(", ")
|
|
end
|
|
|
|
#
|
|
# Checks to see if this module is compatible with the supplied platform
|
|
#
|
|
def platform?(what)
|
|
(platform & what).empty? == false
|
|
end
|
|
|
|
#
|
|
# Returns whether or not the module requires or grants high privileges.
|
|
#
|
|
def privileged?
|
|
return (privileged == true)
|
|
end
|
|
|
|
#
|
|
# The default communication subsystem for this module. We may need to move
|
|
# this somewhere else.
|
|
#
|
|
def comm
|
|
return Rex::Socket::Comm::Local
|
|
end
|
|
|
|
#
|
|
# Overrides the class' own datastore with the one supplied. This is used
|
|
# to allow modules to share datastores, such as a payload sharing an
|
|
# exploit module's datastore.
|
|
#
|
|
def share_datastore(ds)
|
|
self.datastore = ds
|
|
self.datastore.import_options(self.options)
|
|
end
|
|
|
|
#
|
|
# Imports default options into the module's datastore, optionally clearing
|
|
# all of the values currently set in the datastore.
|
|
#
|
|
def import_defaults(clear_datastore = true)
|
|
# Clear the datastore if the caller asked us to
|
|
self.datastore.clear if clear_datastore
|
|
|
|
self.datastore.import_options(self.options, 'self', true)
|
|
|
|
# If there are default options, import their values into the datastore
|
|
if (module_info['DefaultOptions'])
|
|
self.datastore.import_options_from_hash(module_info['DefaultOptions'], true, 'self')
|
|
end
|
|
end
|
|
|
|
#
|
|
# This method ensures that the options associated with this module all
|
|
# have valid values according to each required option in the option
|
|
# container.
|
|
#
|
|
def validate
|
|
self.options.validate(self.datastore)
|
|
end
|
|
|
|
#
|
|
# Returns true if this module is being debugged. The debug flag is set
|
|
# by setting datastore['DEBUG'] to 1|true|yes
|
|
#
|
|
def debugging?
|
|
(datastore['DEBUG'] || '') =~ /^(1|t|y)/i
|
|
end
|
|
|
|
#
|
|
# Indicates whether the module supports IPv6. This is true by default,
|
|
# but certain modules require additional work to be compatible or are
|
|
# hardcoded in terms of application support and should be skipped.
|
|
#
|
|
def support_ipv6?
|
|
true
|
|
end
|
|
|
|
#
|
|
# This provides a standard set of search filters for every module.
|
|
# The search terms are in the form of:
|
|
# {
|
|
# "text" => [ [ "include_term1", "include_term2", ...], [ "exclude_term1", "exclude_term2"], ... ],
|
|
# "cve" => [ [ "include_term1", "include_term2", ...], [ "exclude_term1", "exclude_term2"], ... ]
|
|
# }
|
|
#
|
|
# Returns true on no match, false on match
|
|
#
|
|
def search_filter(search_string)
|
|
return false if not search_string
|
|
|
|
search_string += " "
|
|
|
|
# Split search terms by space, but allow quoted strings
|
|
terms = search_string.split(/\"/).collect{|t| t.strip==t ? t : t.split(' ')}.flatten
|
|
terms.delete('')
|
|
|
|
# All terms are either included or excluded
|
|
res = {}
|
|
|
|
terms.each do |t|
|
|
f,v = t.split(":", 2)
|
|
if not v
|
|
v = f
|
|
f = 'text'
|
|
end
|
|
next if v.length == 0
|
|
f.downcase!
|
|
v.downcase!
|
|
res[f] ||=[ [], [] ]
|
|
if v[0,1] == "-"
|
|
next if v.length == 1
|
|
res[f][1] << v[1,v.length-1]
|
|
else
|
|
res[f][0] << v
|
|
end
|
|
end
|
|
|
|
k = res
|
|
|
|
refs = self.references.map{|x| [x.ctx_id, x.ctx_val].join("-") }
|
|
is_exploit = (self.type == "exploit")
|
|
is_auxiliary = (self.type == "auxiliary")
|
|
is_post = (self.type == "post")
|
|
is_server = (self.respond_to?(:stance) and self.stance == "aggressive")
|
|
is_client = (self.respond_to?(:stance) and self.stance == "passive")
|
|
|
|
[0,1].each do |mode|
|
|
match = false
|
|
k.keys.each do |t|
|
|
next if k[t][mode].length == 0
|
|
|
|
k[t][mode].each do |w|
|
|
# Reset the match flag for each keyword for inclusive search
|
|
match = false if mode == 0
|
|
|
|
# Convert into a case-insensitive regex
|
|
r = Regexp.new(Regexp.escape(w), true)
|
|
|
|
case t
|
|
when 'text'
|
|
terms = [self.name, self.fullname, self.description] + refs + self.author.map{|x| x.to_s}
|
|
if self.respond_to?(:targets) and self.targets
|
|
terms = terms + self.targets.map{|x| x.name}
|
|
end
|
|
match = [t,w] if terms.any? { |x| x =~ r }
|
|
when 'name'
|
|
match = [t,w] if self.name =~ r
|
|
when 'path'
|
|
match = [t,w] if self.fullname =~ r
|
|
when 'author'
|
|
match = [t,w] if self.author.map{|x| x.to_s}.any? { |a| a =~ r }
|
|
when 'os', 'platform'
|
|
match = [t,w] if self.platform_to_s =~ r or self.arch_to_s =~ r
|
|
if not match and self.respond_to?(:targets) and self.targets
|
|
match = [t,w] if self.targets.map{|x| x.name}.any? { |t| t =~ r }
|
|
end
|
|
when 'port'
|
|
match = [t,w] if self.datastore['RPORT'].to_s =~ r
|
|
when 'type'
|
|
match = [t,w] if (w == "exploit" and is_exploit)
|
|
match = [t,w] if (w == "auxiliary" and is_auxiliary)
|
|
match = [t,w] if (w == "post" and is_post)
|
|
when 'app'
|
|
match = [t,w] if (w == "server" and is_server)
|
|
match = [t,w] if (w == "client" and is_client)
|
|
when 'cve'
|
|
match = [t,w] if refs.any? { |ref| ref =~ /^cve\-/i and ref =~ r }
|
|
when 'bid'
|
|
match = [t,w] if refs.any? { |ref| ref =~ /^bid\-/i and ref =~ r }
|
|
when 'osvdb'
|
|
match = [t,w] if refs.any? { |ref| ref =~ /^osvdb\-/i and ref =~ r }
|
|
when 'edb'
|
|
match = [t,w] if refs.any? { |ref| ref =~ /^edb\-/i and ref =~ r }
|
|
end
|
|
break if match
|
|
end
|
|
# Filter this module if no matches for a given keyword type
|
|
if mode == 0 and not match
|
|
return true
|
|
end
|
|
end
|
|
# Filter this module if we matched an exlusion keyword (-value)
|
|
if mode == 1 and match
|
|
return true
|
|
end
|
|
end
|
|
|
|
false
|
|
end
|
|
|
|
##
|
|
#
|
|
# Just some handy quick checks
|
|
#
|
|
##
|
|
|
|
#
|
|
# Returns true if this module is an exploit module.
|
|
#
|
|
def exploit?
|
|
return (type == MODULE_EXPLOIT)
|
|
end
|
|
|
|
#
|
|
# Returns true if this module is a payload module.
|
|
#
|
|
def payload?
|
|
return (type == MODULE_PAYLOAD)
|
|
end
|
|
|
|
#
|
|
# Returns true if this module is an encoder module.
|
|
#
|
|
def encoder?
|
|
return (type == MODULE_ENCODER)
|
|
end
|
|
|
|
#
|
|
# Returns true if this module is a nop module.
|
|
#
|
|
def nop?
|
|
return (type == MODULE_NOP)
|
|
end
|
|
|
|
#
|
|
# Returns true if this module is an auxiliary module.
|
|
#
|
|
def auxiliary?
|
|
return (type == MODULE_AUX)
|
|
end
|
|
|
|
#
|
|
# Returns true if this module is an post-exploitation module.
|
|
#
|
|
def post?
|
|
return (type == MODULE_POST)
|
|
end
|
|
|
|
#
|
|
# Returns false since this is the real module
|
|
#
|
|
def self.cached?
|
|
false
|
|
end
|
|
|
|
#
|
|
# Read a value from the module store
|
|
#
|
|
def [](k)
|
|
self.module_store[k]
|
|
end
|
|
|
|
#
|
|
# Store a value into the module
|
|
#
|
|
def []=(k,v)
|
|
self.module_store[k] = v
|
|
end
|
|
|
|
#
|
|
# The array of zero or more authors.
|
|
#
|
|
attr_reader :author
|
|
#
|
|
# The array of zero or more architectures.
|
|
#
|
|
attr_reader :arch
|
|
#
|
|
# The array of zero or more platforms.
|
|
#
|
|
attr_reader :platform
|
|
#
|
|
# The reference count for the module.
|
|
#
|
|
attr_reader :references
|
|
#
|
|
# The module-specific datastore instance.
|
|
#
|
|
attr_reader :datastore
|
|
#
|
|
# The module-specific options.
|
|
#
|
|
attr_reader :options
|
|
#
|
|
# Whether or not this module requires privileged access.
|
|
#
|
|
attr_reader :privileged
|
|
#
|
|
# The license under which this module is provided.
|
|
#
|
|
attr_reader :license
|
|
|
|
#
|
|
# The job identifier that this module is running as, if any.
|
|
#
|
|
attr_accessor :job_id
|
|
|
|
#
|
|
# A generic hash used for passing additional information to modules
|
|
#
|
|
attr_accessor :module_store
|
|
|
|
#
|
|
# The last exception to occur using this module
|
|
#
|
|
attr_accessor :error
|
|
|
|
#
|
|
# A unique identifier for this module instance
|
|
#
|
|
attr_reader :uuid
|
|
|
|
protected
|
|
attr_writer :uuid
|
|
def generate_uuid
|
|
self.uuid = Rex::Text.rand_text_alphanumeric(8).downcase
|
|
end
|
|
#
|
|
# The list of options that support merging in an information hash.
|
|
#
|
|
UpdateableOptions = [ "Name", "Description", "Alias", "PayloadCompat" ]
|
|
|
|
#
|
|
# Sets the modules unsupplied info fields to their default values.
|
|
#
|
|
def set_defaults
|
|
self.module_info = {
|
|
'Name' => 'No module name',
|
|
'Description' => 'No module description',
|
|
'Version' => '0',
|
|
'Author' => nil,
|
|
'Arch' => nil, # No architectures by default.
|
|
'Platform' => [], # No platforms by default.
|
|
'Ref' => nil,
|
|
'Privileged' => false,
|
|
'License' => MSF_LICENSE,
|
|
}.update(self.module_info)
|
|
self.module_store = {}
|
|
end
|
|
|
|
#
|
|
# This method initializes the module's compatibility hashes by normalizing
|
|
# them into one single hash. As it stands, modules can define
|
|
# compatibility in their supplied info hash through:
|
|
#
|
|
# Compat:: direct compat definitions
|
|
# PayloadCompat:: payload compatibilities
|
|
# EncoderCompat:: encoder compatibilities
|
|
# NopCompat:: nop compatibilities
|
|
#
|
|
# In the end, the module specific compatibilities are merged as sub-hashes
|
|
# of the primary Compat hash key to make checks more uniform.
|
|
#
|
|
def init_compat
|
|
c = module_info['Compat']
|
|
|
|
if (c == nil)
|
|
c = module_info['Compat'] = Hash.new
|
|
end
|
|
|
|
# Initialize the module sub compatibilities
|
|
c['Payload'] = Hash.new if (c['Payload'] == nil)
|
|
c['Encoder'] = Hash.new if (c['Encoder'] == nil)
|
|
c['Nop'] = Hash.new if (c['Nop'] == nil)
|
|
|
|
# Update the compat-derived module specific compatibilities from
|
|
# the specific ones to make a uniform view of compatibilities
|
|
c['Payload'].update(module_info['PayloadCompat'] || {})
|
|
c['Encoder'].update(module_info['EncoderCompat'] || {})
|
|
c['Nop'].update(module_info['NopCompat'] || {})
|
|
end
|
|
|
|
#
|
|
# Register options with a specific owning class.
|
|
#
|
|
def info_fixups
|
|
# Each reference should be an array consisting of two elements
|
|
refs = module_info['References']
|
|
if(refs and not refs.empty?)
|
|
refs.each_index do |i|
|
|
if !(refs[i].respond_to?('[]') and refs[i].length == 2)
|
|
refs[i] = nil
|
|
end
|
|
end
|
|
|
|
# Purge invalid references
|
|
refs.delete(nil)
|
|
end
|
|
end
|
|
|
|
#
|
|
# Register options with a specific owning class.
|
|
#
|
|
def register_options(options, owner = self.class)
|
|
self.options.add_options(options, owner)
|
|
self.datastore.import_options(self.options, 'self', true)
|
|
import_defaults(false)
|
|
end
|
|
|
|
#
|
|
# Register advanced options with a specific owning class.
|
|
#
|
|
def register_advanced_options(options, owner = self.class)
|
|
self.options.add_advanced_options(options, owner)
|
|
self.datastore.import_options(self.options, 'self', true)
|
|
import_defaults(false)
|
|
end
|
|
|
|
#
|
|
# Register evasion options with a specific owning class.
|
|
#
|
|
def register_evasion_options(options, owner = self.class)
|
|
self.options.add_evasion_options(options, owner)
|
|
self.datastore.import_options(self.options, 'self', true)
|
|
import_defaults(false)
|
|
end
|
|
|
|
#
|
|
# Removes the supplied options from the module's option container
|
|
# and data store.
|
|
#
|
|
def deregister_options(*names)
|
|
names.each { |name|
|
|
self.options.remove_option(name)
|
|
self.datastore.delete(name)
|
|
}
|
|
end
|
|
|
|
#
|
|
# Checks to see if a derived instance of a given module implements a method
|
|
# beyond the one that is provided by a base class. This is a pretty lame
|
|
# way of doing it, but I couldn't find a better one, so meh.
|
|
#
|
|
def derived_implementor?(parent, method_name)
|
|
(self.method(method_name).to_s.match(/#{parent}[^:]/)) ? false : true
|
|
end
|
|
|
|
#
|
|
# Merges options in the info hash in a sane fashion, as some options
|
|
# require special attention.
|
|
#
|
|
def merge_info(info, opts)
|
|
opts.each_pair { |name, val|
|
|
merge_check_key(info, name, val)
|
|
}
|
|
|
|
return info
|
|
end
|
|
|
|
#
|
|
# Updates information in the supplied info hash and merges other
|
|
# information. This method is used to override things like Name, Version,
|
|
# and Description without losing the ability to merge architectures,
|
|
# platforms, and options.
|
|
#
|
|
def update_info(info, opts)
|
|
opts.each_pair { |name, val|
|
|
# If the supplied option name is one of the ones that we should
|
|
# override by default
|
|
if (UpdateableOptions.include?(name) == true)
|
|
# Only if the entry is currently nil do we use our value
|
|
if (info[name] == nil)
|
|
info[name] = val
|
|
end
|
|
# Otherwise, perform the merge operation like normal
|
|
else
|
|
merge_check_key(info, name, val)
|
|
end
|
|
}
|
|
|
|
return info
|
|
end
|
|
|
|
#
|
|
# Checks and merges the supplied key/value pair in the supplied hash.
|
|
#
|
|
def merge_check_key(info, name, val)
|
|
if (self.respond_to?("merge_info_#{name.downcase}"))
|
|
eval("merge_info_#{name.downcase}(info, val)")
|
|
else
|
|
# If the info hash already has an entry for this name
|
|
if (info[name])
|
|
# If it's not an array, convert it to an array and merge the
|
|
# two
|
|
if (info[name].kind_of?(Array) == false)
|
|
curr = info[name]
|
|
info[name] = [ curr ]
|
|
end
|
|
|
|
# If the value being merged is an array, add each one
|
|
if (val.kind_of?(Array) == true)
|
|
val.each { |v|
|
|
if (info[name].include?(v) == false)
|
|
info[name] << v
|
|
end
|
|
}
|
|
# Otherwise just add the value
|
|
elsif (info[name].include?(val) == false)
|
|
info[name] << val
|
|
end
|
|
# Otherwise, just set the value equal if no current value
|
|
# exists
|
|
else
|
|
info[name] = val
|
|
end
|
|
end
|
|
end
|
|
|
|
#
|
|
# Merge aliases with an underscore delimiter.
|
|
#
|
|
def merge_info_alias(info, val)
|
|
merge_info_string(info, 'Alias', val, '_')
|
|
end
|
|
|
|
#
|
|
# Merges the module name.
|
|
#
|
|
def merge_info_name(info, val)
|
|
merge_info_string(info, 'Name', val, ', ', true)
|
|
end
|
|
|
|
#
|
|
# Merges the module description.
|
|
#
|
|
def merge_info_description(info, val)
|
|
merge_info_string(info, 'Description', val)
|
|
end
|
|
|
|
#
|
|
# Merge the module version.
|
|
#
|
|
def merge_info_version(info, val)
|
|
merge_info_string(info, 'Version', val)
|
|
end
|
|
|
|
#
|
|
# Merges a given key in the info hash with a delimiter.
|
|
#
|
|
def merge_info_string(info, key, val, delim = ', ', inverse = false)
|
|
if (info[key])
|
|
if (inverse == true)
|
|
info[key] = info[key] + delim + val
|
|
else
|
|
info[key] = val + delim + info[key]
|
|
end
|
|
else
|
|
info[key] = val
|
|
end
|
|
end
|
|
|
|
#
|
|
# Merges options.
|
|
#
|
|
def merge_info_options(info, val, advanced = false, evasion = false)
|
|
|
|
key_name = ((advanced) ? 'Advanced' : (evasion) ? 'Evasion' : '') + 'Options'
|
|
|
|
new_cont = OptionContainer.new
|
|
new_cont.add_options(val, advanced, evasion)
|
|
cur_cont = OptionContainer.new
|
|
cur_cont.add_options(info[key_name] || [], advanced, evasion)
|
|
|
|
new_cont.each_option { |name, option|
|
|
next if (cur_cont.get(name))
|
|
|
|
info[key_name] = [] if (!info[key_name])
|
|
info[key_name] << option
|
|
}
|
|
end
|
|
|
|
#
|
|
# Merges advanced options.
|
|
#
|
|
def merge_info_advanced_options(info, val)
|
|
merge_info_options(info, val, true, false)
|
|
end
|
|
|
|
#
|
|
# Merges advanced options.
|
|
#
|
|
def merge_info_evasion_options(info, val)
|
|
merge_info_options(info, val, false, true)
|
|
end
|
|
|
|
attr_accessor :module_info # :nodoc:
|
|
attr_writer :author, :arch, :platform, :references, :datastore, :options # :nodoc:
|
|
attr_writer :privileged # :nodoc:
|
|
attr_writer :license # :nodoc:
|
|
|
|
end
|
|
|
|
#
|
|
# Alias the data types so people can reference them just by Msf:: and not
|
|
# Msf::Module::
|
|
#
|
|
Author = Msf::Module::Author
|
|
Reference = Msf::Module::Reference
|
|
SiteReference = Msf::Module::SiteReference
|
|
Platform = Msf::Module::Platform
|
|
Target = Msf::Module::Target
|
|
|
|
end
|
|
|