1100 lines
25 KiB
Ruby
1100 lines
25 KiB
Ruby
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
|
|
|
|
#
|
|
# Override the default Class#inspect which is useless for the way
|
|
# modules get loaded
|
|
#
|
|
def inspect
|
|
"#<Class for #{refname}>"
|
|
end
|
|
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" ])
|
|
], 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
|
|
|
|
#
|
|
# 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 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 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
|
|
return false if (v and not mval)
|
|
|
|
# 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
|
|
|
|
# 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
|
|
|
|
|
|
#
|
|
# 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 '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_exploit)
|
|
match = [t,w] if (w == "client" and is_client_exploit)
|
|
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 }
|
|
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
|
|
|