metasploit-framework/lib/msf/core/module_manager.rb

297 lines
6.3 KiB
Ruby
Raw Normal View History

require 'find'
require 'Msf/Core'
module Msf
###
#
# ModuleSet
# ---------
#
# A module set contains zero or more named module classes of an arbitrary
# type.
#
###
class ModuleSet < Hash
def initialize(type)
self.module_type = type
self.full_names = {}
self.ambiguous_names = {}
end
# Create an instance of the supplied module by its name
def create(name)
# If the supplied name is known-ambiguous, prevent its creation
if (ambiguous_names[name])
raise(NameError.new("The supplied module name is ambiguous", name),
caller)
end
# If not by short name, then by full name, or so sayeth the spider
if ((klass = self[name]) == nil)
klass = full_names[name]
end
# Otherwise, try to create it
return (klass) ? klass.new : nil
end
# Enumerates each module class in the set
def each_module(&block)
return each_value(&block)
end
# Adds a module with a supplied short name, full name, and associated
# module class
def add_module(short_name, full_name, module_class)
if (self[short_name])
ambiguous_names << short_name
else
self[short_name] = module_class
end
full_names[full_name] = module_class
end
attr_reader :module_type, :full_names
protected
attr_writer :module_type, :full_names
attr_accessor :ambiguous_names
end
###
#
# ModuleManager
# -------------
#
# 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 reload support
# - add unload support
#
###
class ModuleManager < Array
def initialize()
self.module_paths = []
self.module_history = {}
self.module_history_mtime = {}
self.modules_by_type = {}
self.modules = []
MODULE_TYPES.each { |type|
self.modules_by_type[type] = ModuleSet.new(type)
}
end
#
# Accessors by module type
#
# Returns the set of loaded encoder module classes
def encoders
return modules_by_type[MODULE_ENCODER]
end
# Returns the set of loaded exploit module classes
def exploits
return modules_by_type[MODULE_EXPLOIT]
end
# Returns the set of loaded nop module classes
def nops
return modules_by_type[MODULE_NOPS]
end
# Returns the set of loaded payload module classes
def payloads
return modules_by_type[MODULE_PAYLOAD]
end
# Returns the set of loaded recon module classes
def recon
return modules_by_type[MODULE_RECON]
end
#
# Module path management
#
# Adds a path to be searched for new modules
def add_module_path(path)
module_paths << path
load_modules(path)
end
# Removes a path from which to search for modules
def remove_module_path(path)
module_paths.delete(path)
end
protected
# Load all of the modules from the supplied module path (independent of
# module type)
def load_modules(path)
loaded = {}
Find.find(path) { |file|
# If the file doesn't end in the expected extension...
next 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_1)
end
# Substitute the base path
path_base = file.sub(path + File::SEPARATOR, '')
# Extract the type of module
md = path_base.match(/^(.*?)#{File::SEPARATOR}/)
next if (!md)
# Use the de-pluralized version of the type as necessary
type = md[1].sub(/s$/, '').downcase
# Extract the module namespace
md = path_base.match(/^(.*)#{File::SEPARATOR}(.*?)$/)
next if (!md)
# Prefix Msf to the namespace
namespace = 'Msf::' + md[1].sub(File::SEPARATOR, "::")
dlog("Loading #{type} module from #{path_base}...", 'core', LEV_1)
# Get the module and grab the current number of constants
old_constants = []
mod = mod_from_name(namespace)
if (mod)
old_constants = mod.constants
end
# Load the file
begin
if (!load(file))
elog("Failed to load from file #{file}.")
next
end
rescue LoadError
elog("LoadError: #{$!}.")
next
end
# Incase we hadn't gotten the module yet...
mod = mod_from_name(namespace)
if (!mod)
elog("Load did not create expected namespace #{namespace}.")
next
end
added = mod.constants - old_constants
if (added.length > 1)
elog("Loaded file contained more than one class (#{file}).")
next
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.")
next
end
else
added = mod.const_get(added[0])
end
ilog("Loaded #{type} module #{added} from #{file}.", 'core', LEV_1)
# Do some processing on the loaded module to get it into the
# right associations
on_module_load(type, added)
# Append the added module to the hash of file->module
loaded[file] = added
}
# 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)
return loaded.values
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 = Object
name.split('::').each { |m|
begin
obj = obj.const_get(m)
rescue NameError
obj = nil
break
end
}
return obj
end
# Called when a module is initially loaded such that it can be
# categorized accordingly
def on_module_load(type, mod)
# Extract the module name information
mod_full_name = mod.to_s.gsub('::', '_')
mod_full_name.sub!(/^Msf_(.*?)_/, '')
mod_short_name = mod_full_name
if ((md = mod_full_name.match(/_(.*)$/)))
mod_short_name = md[1]
end
# Add the module class to the list of modules and add it to the
# type separated set of module classes
modules << mod
modules_by_type[type].add_module(mod_short_name, mod_full_name, mod)
end
attr_accessor :modules, :modules_by_type
attr_accessor :module_paths
attr_accessor :module_history, :module_history_mtime
end
end