lib/msf/core/module_manager/loading.rb
# -*- coding: binary -*-
#
# Gems
#
require 'active_support/concern'
#
# Project
#
# Deals with loading modules for the {Msf::ModuleManager}
module Msf::ModuleManager::Loading
extend ActiveSupport::Concern
#
# CONSTANTS
#
# Classes that can be used to load modules.
LOADER_CLASSES = [
Msf::Modules::Loader::Directory,
Msf::Modules::Loader::Executable # TODO: XXX: When this is the first loader we can load normal exploits, but not payloads
]
# Maps module type directory to its module type.
DIRECTORY_BY_TYPE = Msf::Modules::Loader::Base::DIRECTORY_BY_TYPE
TYPE_BY_DIRECTORY = Msf::Modules::Loader::Base::TYPE_BY_DIRECTORY
def file_changed?(path)
changed = false
module_info = self.module_info_by_path[path]
# if uncached then it counts as changed
# Payloads can't be cached due to stage/stager matching
if module_info.nil? or module_info[:type] == Msf::MODULE_PAYLOAD
changed = true
else
begin
current_modification_time = ::File.mtime(path).to_i
rescue ::Errno::ENOENT
# if the file does not exist now, that's a change
changed = true
else
cached_modification_time = module_info[:modification_time].to_i
# if the file's modification time's different from the cache, then it's changed
if current_modification_time != cached_modification_time
changed = true
end
end
end
changed
end
# Return errors associated with the supplied reference name.
#
# @param name [String] e.g. 'auxiliary/scanner/msmail/host_id'
# It may optionally be prefixed with a "<type>/", in which case we
# will pass it to `get_module_error(module_reference_name, type)`.
# Otherwise, we step through all sets until we find one that
# matches.
# @return (error) which will return either an error or nil.
def load_error_by_name(name)
return load_error_by_name(self.aliases[name]) if self.aliases[name]
names = name.split("/")
potential_type_or_directory = names.first
if DIRECTORY_BY_TYPE.has_key? potential_type_or_directory
type = potential_type_or_directory
# if first name is a type directory
else
type = TYPE_BY_DIRECTORY[potential_type_or_directory]
end
error = nil
if type
module_reference_name = names[1 .. -1].join("/")
error = get_module_error(module_reference_name, type)
else
module_set_by_type.each do |type, _set|
module_reference_name = "#{type}/#{name}"
error = load_error_by_name(module_reference_name)
break if error
end
end
error
end
attr_accessor :module_load_error_by_path, :module_load_warnings
# Called when a module is initially loaded such that it can be categorized
# accordingly.
#
# @param class_or_module [Class<Msf::Module>, ::Module] either a module Class
# or a payload Module.
# @param type [String] The module type.
# @param reference_name The module reference name.
# @param info [Hash{String => Array}] additional information about the module
# @option info [Array<String>] 'files' List of paths to the ruby source files
# where +class_or_module+ is defined.
# @option info [Array<String>] 'paths' List of module reference names.
# @option info [String] 'type' The module type, should match positional
# +type+ argument.
# @return [void]
def on_module_load(class_or_module, type, reference_name, info={})
module_set = module_set_by_type[type]
module_set.add_module(class_or_module, reference_name, info)
path = info['files'].first
cache_in_memory(
class_or_module,
:path => path,
:reference_name => reference_name,
:type => type
)
# Automatically subscribe a wrapper around this module to the necessary
# event providers based on whatever events it wishes to receive.
auto_subscribe_module(class_or_module)
# Notify the framework that a module was loaded
framework.events.on_module_load(reference_name, class_or_module)
# Clear and add aliases, if any (payloads cannot)
if class_or_module.respond_to?(:realname) && aliased_as = self.inv_aliases[class_or_module.realname]
aliased_as.each do |a|
self.aliases.delete a
end
self.inv_aliases.delete class_or_module.realname
end
if class_or_module.respond_to? :aliases
class_or_module.aliases.each do |a|
self.aliases[a] = class_or_module.realname
end
self.inv_aliases[class_or_module.realname] = class_or_module.aliases unless class_or_module.aliases.empty?
end
end
protected
# Return list of {LOADER_CLASSES} instances that load modules into this module manager
def loaders
unless instance_variable_defined? :@loaders
@loaders = LOADER_CLASSES.collect { |klass|
klass.new(self)
}
end
@loaders
end
# Load all of the modules from the supplied directory or archive
#
# @param [String] path Path to a directory
# @param [Hash] options
# @option options [Boolean] :force Whether the force loading the modules even if they are unchanged and already
# loaded.
# @option options [Array] :modules An array of regex patterns to search for specific modules
# @return [Hash{String => Integer}] Maps module type to number of modules loaded
def load_modules(path, options={})
options.assert_valid_keys(:force, :whitelist, :recalculate)
count_by_type = {}
loaders.each do |loader|
if loader.loadable?(path)
count_by_type.merge!(loader.load_modules(path, options)) do |key, prev, now|
prev + now
end
end
end
count_by_type
end
# Get a specific modules errors from the supplied module_reference_name and type
#
# @param module_reference_name [String] e.g. 'scanner/msmail/host_id'
# @param type [String] this will be the type of module e.g. 'auxiliary'
#
# These @params will the be used to loop through `module_info_by_path` [Hash] to check for
# any matching modules paths. This path becomes the key and returns the associated error.
def get_module_error(module_reference_name, type)
module_info_by_path.each do |mod_path, module_value|
if module_value[:reference_name] == module_reference_name && module_value[:type] == type
return module_load_error_by_path[mod_path]
end
end
nil
end
end