Files
metasploit-gs/lib/msf/core/module_manager.rb
T
HD Moore bdfd2c5152 Recon modules and the recon event subsystem have been temporarily removed.
The 'auxiliary' system is designed to replace it and recon features will
slowly be moved back into the framework


git-svn-id: file:///home/svn/incoming/trunk@3438 4d416f70-5f16-0410-b530-b9f4589650da
2006-01-24 03:59:44 +00:00

678 lines
16 KiB
Ruby

require 'find'
require 'msf/core'
module Msf
###
#
# A module set contains zero or more named module classes of an arbitrary
# type.
#
###
class ModuleSet < Hash
include Framework::Offspring
#
# Initializes a module set that will contain modules of a specific type and
# expose the mechanism necessary to create instances of them.
#
def initialize(type = nil)
self.module_type = type
# Hashes that convey the supported architectures and platforms for a
# given module
self.mod_arch_hash = {}
self.mod_platform_hash = {}
self.mod_sorted = nil
self.mod_ranked = nil
self.mod_extensions = []
self.mod_ambiguous = {}
end
#
# Create an instance of the supplied module by its name
#
def create(name)
if (mod_ambiguous[name])
raise Rex::AmbiguousArgumentError.new(name),
"The module name #{name} is ambiguous.", caller
end
klass = self[name]
instance = nil
# If the klass is valid for this name, try to create it
if (klass)
instance = klass.new
end
# Notify any general subscribers of the creation event
if (instance)
self.framework.events.on_module_created(instance)
end
return instance
end
#
# Checks to see if the supplied module name is valid.
#
def valid?(name)
(self[name]) ? true : false
end
#
# Enumerates each module class in the set.
#
def each_module(opts = {}, &block)
mod_sorted = self.sort if (mod_sorted == nil)
each_module_list(mod_sorted, opts, &block)
end
#
# Enumerates each module class in the set based on their relative ranking
# to one another. Modules that are ranked higher are shown first.
#
def each_module_ranked(opts = {}, &block)
mod_ranked = rank_modules if (mod_ranked == nil)
each_module_list(mod_ranked, opts, &block)
end
#
# Custom each_module filtering if an advanced set supports doing extended
# filtering. Returns true if the entry should be filtered.
#
def each_module_filter(opts, name, entry)
return false
end
#
# Dummy placeholder to relcalculate aliases and other fun things.
#
def recalculate
end
attr_reader :module_type
protected
#
# Enumerates the modules in the supplied array with possible limiting
# factors.
#
def each_module_list(ary, opts, &block)
ary.each { |entry|
name, mod = entry
# Filter out incompatible architectures
if (opts['Arch'])
if (!mod_arch_hash[mod])
mod_arch_hash[mod] = mod.new.arch
end
next if ((mod_arch_hash[mod] & opts['Arch']).empty? == true)
end
# Filter out incompatible platforms
if (opts['Platform'])
if (!mod_platform_hash[mod])
mod_platform_hash[mod] = mod.new.platform
end
next if ((mod_platform_hash[mod] & opts['Platform']).empty? == true)
end
# Custom filtering
next if (each_module_filter(opts, name, entry) == true)
block.call(name, mod)
}
end
#
# Ranks modules based on their constant rank value, if they have one.
#
def rank_modules
mod_ranked = self.sort { |a, b|
a_name, a_mod = a
b_name, b_mod = b
# Extract the ranking between the two modules
a_rank = a[1].const_defined?('Rank') ? a[1].const_get('Rank') : NormalRanking
b_rank = b[1].const_defined?('Rank') ? b[1].const_get('Rank') : NormalRanking
# Compare their relevant rankings. Since we want highest to lowest,
# we compare b_rank to a_rank in terms of higher/lower precedence
b_rank <=> a_rank
}
end
#
# Adds a module with a the supplied name.
#
def add_module(module_class, name, file_path = nil)
# Duplicate the module class so that we can operate on a
# framework-specific copy of it.
dup = module_class.dup
# Set the module's name so that it can be referenced when
# instances are created.
dup.framework = framework
dup.refname = name
dup.file_path = file_path
dup.orig_cls = module_class
if (self[name])
mod_ambiguous[name] = true
wlog("The module #{dup.refname} is ambiguous with #{self[name].refname}.")
else
self[name] = dup
end
# Invalidate the sorted array
invalidate_cache
# Return the duplicated instance for use
dup
end
#
# Invalidates the sorted and ranked module caches.
#
def invalidate_cache
mod_sorted = nil
mod_ranked = nil
end
attr_writer :module_type
attr_accessor :mod_arch_hash, :mod_platform_hash
attr_accessor :mod_sorted, :mod_ranked
attr_accessor :mod_extensions, :mod_ambiguous
end
###
#
# Upper management decided to throw in some middle management
# because the modules were getting out of hand. This bad boy
# takes care of the work of managing the interaction with
# modules in terms of loading and instantiation.
#
# TODO:
#
# - add unload support
#
###
class ModuleManager < ModuleSet
require 'msf/core/payload_set'
include Framework::Offspring
#
# Initializes an instance of the overall module manager using the supplied
# framework instance.
#
def initialize(framework)
self.module_paths = []
self.module_history = {}
self.module_history_mtime = {}
self.module_sets = {}
self.framework = framework
MODULE_TYPES.each { |type|
case type
when MODULE_PAYLOAD
instance = PayloadSet.new(self)
else
instance = ModuleSet.new(type)
end
self.module_sets[type] = instance
# Set the module set's framework reference
instance.framework = framework
}
super
end
#
# Creates a module using the supplied name.
#
def create(name)
# Check to see if it has a module type prefix. If it does,
# try to load it from the specific module set for that type.
if (md = name.match(/^(#{MODULE_TYPES.join('|')})\/(.*)$/))
module_sets[md[1]].create(md[2])
# Otherwise, just try to load it by name.
else
super
end
end
#
# Accessors by module type
#
#
# Returns the set of loaded encoder module classes.
#
def encoders
return module_sets[MODULE_ENCODER]
end
#
# Returns the set of loaded exploit module classes.
#
def exploits
return module_sets[MODULE_EXPLOIT]
end
#
# Returns the set of loaded nop module classes.
#
def nops
return module_sets[MODULE_NOP]
end
#
# Returns the set of loaded payload module classes.
#
def payloads
return module_sets[MODULE_PAYLOAD]
end
#
# Returns the set of loaded auxiliary module classes.
#
def auxiliary
return module_sets[MODULE_AUX]
end
##
#
# Module path management
#
##
#
# Adds a path to be searched for new modules.
#
def add_module_path(path)
path.sub!(/#{File::SEPARATOR}$/, '')
# Make sure the path is a valid directory before we try to rock the
# house
if (File.directory?(path) == false)
raise NameError, "The path supplied is not a valid directory.",
caller
end
module_paths << path
return load_modules(path)
end
#
# Removes a path from which to search for modules.
#
def remove_module_path(path)
module_paths.delete(path)
end
def register_type_extension(type, ext)
end
#
# Reloads the module specified in mod. This can either be an instance of a
# module or a module class.
#
def reload_module(mod)
refname = mod.refname
ds = mod.datastore.dup
dlog("Reloading module #{refname}...", 'core')
if (mod.file_path)
begin
if (!load(mod.file_path))
elog("Failed to load module from #{mod.file_path}")
return nil
end
rescue
elog("Failed to reload module #{mod} from #{mod.file_path}: #{$!}")
raise $!
end
end
# Remove the original reference to this module
self.delete(mod.refname)
# Indicate that the module is being loaded again so that any necessary
# steps can be taken to extend it properly.
on_module_load(mod.orig_cls, mod.type, refname, mod.file_path)
# Create a new instance of the module
if (mod = create(refname))
mod.datastore.update(ds)
else
elog("Failed to create instance of #{refname} after reload.", 'core')
end
mod
end
#
# Overrides the module set method for adding a module so that some extra
# steps can be taken to subscribe the module and notify the event
# dispatcher.
#
def add_module(mod, name, file_path = nil)
# Call the module set implementation of add_module
dup = super
# Automatically subscribe a wrapper around this module to the necessary
# event providers based on whatever events it wishes to receive. We
# only do this if we are the module manager instance, as individual
# module sets need not subscribe.
auto_subscribe_module(dup)
# Notify the framework that a module was loaded
framework.events.on_module_load(name, dup)
end
protected
#
# Load all of the modules from the supplied module path (independent of
# module type).
#
def load_modules(path)
loaded = {}
recalc = {}
counts = {}
delay = {}
ks = true
# Try to load modules from all the files in the supplied path
Find.find(path) { |file|
# Skip unit test files
next if (file =~ /rb\.ut\.rb$/)
# Skip test-suite files
next if (file =~ /rb\.ts\.rb$/)
begin
load_module_from_file(path, file,
loaded, recalc, counts)
rescue NameError
# If we get a name error, it's possible that this module depends
# on another one that we haven't loaded yet. Let's postpone
# the load operation for now so that we can resolve all
# dependencies. This is pretty much a hack.
delay[file] = $!
end
}
# Keep processing all delayed module loads until we've gotten
# all the dependencies resolved or until we just suck.
while (ks == true)
ks = false
delay.each_key { |file|
begin
# Load the module from the file...
load_module_from_file(path, file,
loaded, recalc, counts)
# Remove this file path from the list of delay load files
# because if we get here it means all when swell...maybe.
delay.delete(file)
# Keep scanning since we just successfully loaded a delay load
# module.
ks = true
# Trap the name error and flag this file path as still needing to
# be delay loaded.
rescue NameError
delay[file] = $!
end
}
end
# Log all modules that failed to load due to problems
delay.each_pair { |file, err|
elog("Failed to load module from #{file}: #{err}")
}
# Cache the loaded file mtimes
loaded.each_key {|file|
module_history_mtime[file] = File.new(file).mtime
}
# Cache the loaded file module associations
module_history.update(loaded)
# Perform any required recalculations for the individual module types
# that actually had load changes
recalc.each_key { |key|
module_sets[key].recalculate
}
# Return per-module loaded counts
return counts
end
#
# Loads a module from the supplied file.
#
def load_module_from_file(path, file, loaded, recalc, counts)
# If the file doesn't end in the expected extension...
return if (!file.match(/\.rb$/))
# If the file on disk hasn't changed with what we have stored in the
# cache, then there's no sense in loading it
if (!has_module_file_changed?(file))
dlog("Cached module from file #{file} has not changed.", 'core',
LEV_2)
end
# Substitute the base path
path_base = file.sub(path + File::SEPARATOR, '')
# Derive the name from the path with the exclusion of the .rb
name = path_base.match(/^(.+?)#{File::SEPARATOR}(.*)(.rb?)$/)[2]
# Chop off the file name
path_base.sub!(/(.+)(#{File::SEPARATOR}.+)(.rb?)$/, '\1')
# Extract the module's namespace from its path
mod = mod_from_name(path_base)
if (m = path_base.match(/^(.+?)#{File::SEPARATOR}+?/))
type = m[1]
else
type = path_base
end
type.sub!(/s$/, '')
# Get the module and grab the current number of constants
old_constants = mod.constants
# Load the file like it aint no thang
begin
if (!load(file))
elog("Failed to load from file #{file}.")
return
end
rescue NameError
added = mod.constants - old_constants
# Super hack. If a constant was added (which will represent the
# module), then we need to remove it so that our logic for
# detecting new classes in the future will work when we
# subsequently try to reload it.
r = mod.module_eval { remove_const(added[0]) } if (added[0])
# Re-raise the name error so that the caller catches it and adds this
# file path to the list of files that are to be delay loaded.
raise NameError, $!
rescue LoadError
elog("LoadError: #{$!}.")
return
end
added = mod.constants - old_constants
if (added.length > 1)
elog("Loaded file contained more than one class (#{file}).")
return
end
# If nothing was added, check to see if there's anything
# in the cache
if (added.empty?)
if (module_history[file])
added = module_history[file]
else
elog("Loaded #{file} but no classes were added.")
return
end
else
added = mod.const_get(added[0])
end
# If the module indicates that it is not usable on this system, then we
# will not try to use it.
usable = false
begin
usable = added.is_usable
# If no method is defined, assume that this module is usable.
rescue NoMethodError
usable = true
rescue
elog("Exception caught during is_usable check: #{$!}")
end
if (usable == false)
ilog("Skipping module in #{file} because is_usable returned false.",
'core', LEV_1)
return
end
ilog("Loaded #{type} module #{added} from #{file}.", 'core', LEV_2)
# Do some processing on the loaded module to get it into the
# right associations
on_module_load(added, type, name, file)
# Set this module type as needing recalculation
recalc[type] = true
# Append the added module to the hash of file->module
loaded[file] = added
# The number of loaded modules this round
counts[type] = (counts[type]) ? (counts[type] + 1) : 1
end
#
# Checks to see if the supplied file has changed (if it's even in the
# cache).
#
def has_module_file_changed?(file)
return (module_history_mtime[file] != File.new(file).mtime)
end
#
# Returns the module object that is associated with the supplied module
# name.
#
def mod_from_name(name)
obj = Msf
name.split(File::SEPARATOR).each { |m|
# Up-case the first letter and any prefixed by _
m.gsub!(/^[a-z]/) { |s| s.upcase }
m.gsub!(/(_[a-z])/) { |s| s[1..1].upcase }
begin
obj = obj.const_get(m)
rescue NameError
obj = obj.const_set(m, ::Module.new)
end
}
return obj
end
#
# Called when a module is initially loaded such that it can be
# categorized accordingly.
#
def on_module_load(mod, type, name, file_path)
# Payload modules require custom loading as the individual files
# may not directly contain a logical payload that a user would
# reference, such as would be the case with a payload stager or
# stage. As such, when payload modules are loaded they are handed
# off to a special payload set. The payload set, in turn, will
# automatically create all the permutations after all the payload
# modules have been loaded.
if (type != MODULE_PAYLOAD)
# Add the module class to the list of modules and add it to the
# type separated set of module classes
add_module(mod, name, file_path)
end
module_sets[type].add_module(mod, name, file_path)
end
#
# This method automatically subscribes a module to whatever event providers
# it wishes to monitor. This can be used to allow modules to automatically
# execute or perform other tasks when certain events occur. For instance,
# when a new host is detected, other aux modules may wish to run such
# that they can collect more information about the host that was detected.
#
def auto_subscribe_module(mod)
# If auto-subscribe has been disabled
if (framework.datastore['DisableAutoSubscribe'] and
framework.datastore['DisableAutoSubscribe'] =~ /^(y|1|t)/)
return
end
# If auto-subscription is enabled (which it is by default), figure out
# if it subscribes to any particular interfaces.
inst = nil
#
# Exploit event subscriber check
#
if (mod.include?(ExploitEvent) == true)
framework.events.add_exploit_subscriber((inst) ? inst : (inst = mod.new))
end
#
# Session event subscriber check
#
if (mod.include?(SessionEvent) == true)
framework.events.add_session_subscriber((inst) ? inst : (inst = mod.new))
end
end
attr_accessor :modules, :module_sets # :nodoc:
attr_accessor :module_paths # :nodoc:
attr_accessor :module_history, :module_history_mtime # :nodoc:
end
end