Really fix payload recalculation

Instead of deleting all non-symbolics before the re-adding phase of
PayloadSet#recalculate, store a list of old module names, populate a
list of new ones during the re-adding phase, and finally remove any
non-symbolic module that was in the old list but wasn't in the new list.

Also includes a minor refactoring to make ModuleManager its own thing
instead of being an awkard subclass of ModuleSet. Now PayloadSet doesn't
need to know about the existence of framework.modules, which makes the
separation a little more natural.

[FixRM #7037]
bug/bundler_fix
James Lee 2012-12-03 22:23:40 -06:00
parent 6bd4306214
commit f4476cb1b7
7 changed files with 150 additions and 185 deletions

View File

@ -375,7 +375,6 @@ class DBManager
refresh.each {|md| md.destroy }
refresh = nil
stime = Time.now.to_f
[
[ 'exploit', framework.exploits ],
[ 'auxiliary', framework.auxiliary ],

View File

@ -12,11 +12,15 @@ require 'msf/core'
require 'msf/core/module_set'
module Msf
# 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.
# 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
class ModuleManager #< ModuleSet
include Msf::Framework::Offspring
require 'msf/core/payload_set'
# require here so that Msf::ModuleManager is already defined
@ -39,25 +43,14 @@ module Msf
# Maps module type directory to its module type.
TYPE_BY_DIRECTORY = Msf::Modules::Loader::Base::DIRECTORY_BY_TYPE.invert
# 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.
#
# @param (see Msf::ModuleSet#add_module)
# @return (see Msf::ModuleSet#add_module)
def add_module(mod, name, file_paths)
# Call {Msf::ModuleSet#add_module} with same arguments
dup = super
def [](key)
names = key.split("/")
type = names.shift
# 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)
module_set = module_set_by_type[type]
# Notify the framework that a module was loaded
framework.events.on_module_load(name, dup)
dup
module_reference_name = names.join("/")
module_set.create(module_reference_name)
end
# Creates a module instance using the supplied reference name.
@ -91,7 +84,7 @@ module Msf
end
# @param [Msf::Framework] framework The framework for which this instance is managing the modules.
# @param framework [Msf::Framework] The framework for which this instance is managing the modules.
# @param [Array<String>] types List of module types to load. Defaults to all module types in {Msf::MODULE_TYPES}.
def initialize(framework, types=Msf::MODULE_TYPES)
#
@ -113,18 +106,18 @@ module Msf
types.each { |type|
init_module_set(type)
}
super(nil)
end
protected
# 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.
# 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.
#
# @param [Class] mod a Msf::Module subclass
# @param mod [Class] A subclass of Msf::Module
# @return [void]
def auto_subscribe_module(mod)
# If auto-subscribe has been disabled

View File

@ -24,8 +24,8 @@ module Msf::ModuleManager::Cache
def load_cached_module(type, reference_name)
loaded = false
module_info = self.module_info_by_path.values.find { |module_info|
module_info[:type] == type and module_info[:reference_name] == reference_name
module_info = self.module_info_by_path.values.find { |inner_info|
inner_info[:type] == type and inner_info[:reference_name] == reference_name
}
if module_info
@ -116,8 +116,9 @@ module Msf::ModuleManager::Cache
typed_module_set = module_set(type)
# Don't want to trigger as {Msf::ModuleSet#create} so check for key instead of using ||= which would call
# {Msf::ModuleSet#[]} which would potentially call {Msf::ModuleSet#create}.
# Don't want to trigger as {Msf::ModuleSet#create} so check for
# key instead of using ||= which would call {Msf::ModuleSet#[]}
# which would potentially call {Msf::ModuleSet#create}.
unless typed_module_set.has_key? reference_name
typed_module_set[reference_name] = Msf::SymbolicModule
end
@ -126,4 +127,4 @@ module Msf::ModuleManager::Cache
self.module_info_by_path
end
end
end

View File

@ -57,21 +57,16 @@ module Msf::ModuleManager::Loading
# categorized accordingly.
#
def on_module_load(mod, type, name, modinfo)
# 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.
dup = module_set_by_type[type].add_module(mod, name, modinfo)
if (type != Msf::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, modinfo)
end
# Automatically subscribe a wrapper around this module to the necessary
# event providers based on whatever events it wishes to receive.
auto_subscribe_module(dup)
module_set_by_type[type].add_module(mod, name, modinfo)
# Notify the framework that a module was loaded
framework.events.on_module_load(name, dup)
dup
end
protected

View File

@ -33,8 +33,9 @@ class Msf::ModuleSet < Hash
# Create an instance of the supplied module by its name
#
# @param [String] name the module reference name.
# @return [Msf::Module] instance of the named module.
# @param name [String] The module reference name.
# @return [Msf::Module,nil] Instance of the named module or nil if it
# could not be created.
def create(name)
klass = fetch(name, nil)
instance = nil
@ -42,15 +43,7 @@ class Msf::ModuleSet < Hash
# If there is no module associated with this class, then try to demand
# load it.
if klass.nil? or klass == Msf::SymbolicModule
# If we are the root module set, then we need to try each module
# type's demand loading until we find one that works for us.
if module_type.nil?
Msf::MODULE_TYPES.each { |type|
framework.modules.load_cached_module(type, name)
}
else
framework.modules.load_cached_module(module_type, name)
end
framework.modules.load_cached_module(module_type, name)
recalculate
@ -168,17 +161,6 @@ class Msf::ModuleSet < Hash
def on_module_reload(mod)
end
# @!attribute [rw] postpone_recalc
# Whether or not recalculations should be postponed. This is used
# from the context of the {#each_module_list} handler in order to
# prevent the demand loader from calling recalc for each module if
# it's possible that more than one module may be loaded. This field
# is not initialized until used.
#
# @return [true] if {#recalculate} should not be called immediately
# @return [false] if {#recalculate} should be called immediately
attr_accessor :postpone_recalculate
# Dummy placeholder to recalculate aliases and other fun things.
#
# @return [void]
@ -194,8 +176,6 @@ class Msf::ModuleSet < Hash
(self[name]) ? true : false
end
protected
# Adds a module with a the supplied name.
#
# @param [Class] mod The module class: a subclass of Msf::Module.
@ -226,25 +206,24 @@ class Msf::ModuleSet < Hash
mod
end
protected
# Load all modules that are marked as being symbolic.
#
# @return [void]
def demand_load_modules
found_symbolics = false
# Pre-scan the module list for any symbolic modules
self.each_pair { |name, mod|
if (mod == Msf::SymbolicModule)
self.postpone_recalculate = true
found_symbolics = true
mod = create(name)
next if (mod.nil?)
end
}
# If we found any symbolic modules, then recalculate.
if (self.postpone_recalculate)
self.postpone_recalculate = false
if (found_symbolics)
recalculate
end
end

View File

@ -63,15 +63,17 @@ class Msf::Modules::Loader::Base
# Regex that can distinguish regular ruby source from unit test source.
UNIT_TEST_REGEX = /rb\.(ut|ts)\.rb$/
# @param [Msf::ModuleManager] module_manager The module manager that caches the loaded modules.
# @param [Msf::ModuleManager] module_manager The module manager that
# caches the loaded modules.
def initialize(module_manager)
@module_manager = module_manager
end
# Returns whether the path can be loaded this module loader.
#
# @abstract Override and determine from properties of the path or the file to which the path points whether it is
# loadable using {#load_modules} for the subclass.
# @abstract Override and determine from properties of the path or the
# file to which the path points whether it is loadable using
# {#load_modules} for the subclass.
#
# @param path (see #load_modules)
# @return [Boolean]
@ -81,22 +83,33 @@ class Msf::Modules::Loader::Base
# Loads a module from the supplied path and module_reference_name.
#
# @param [String] parent_path The path under which the module exists. This is not necessarily the same path as passed
# to {#load_modules}: it may just be derived from that path.
# @param [String] parent_path The path under which the module exists.
# This is not necessarily the same path as passed to
# {#load_modules}: it may just be derived from that path.
# @param [String] type The type of module.
# @param [String] module_reference_name The canonical name for referring to the module.
# @param [Hash] options Options used to force loading and track statistics
# @option options [Hash{String => Integer}] :count_by_type Maps the module type to the number of module loaded
# @option options [Boolean] :force (false) whether to force loading of the module even if the module has not changed.
# @option options [Hash{String => Boolean}] :recalculate_by_type Maps type to whether its
# {Msf::ModuleManager::ModuleSets#module_set} needs to be recalculated.
# @param [String] module_reference_name The canonical name for
# referring to the module.
# @param [Hash] options Options used to force loading and track
# statistics
# @option options [Hash{String => Integer}] :count_by_type Maps the
# module type to the number of module loaded
# @option options [Boolean] :force (false) whether to force loading of
# the module even if the module has not changed.
# @option options [Hash{String => Boolean}] :recalculate_by_type Maps
# type to whether its {Msf::ModuleManager::ModuleSets#module_set}
# needs to be recalculated.
# @option options [Boolean] :reload (false) whether this is a reload.
#
# @return [false] if :force is false and parent_path has not changed.
# @return [false] if exception encountered while parsing module content
# @return [false] if the module is incompatible with the Core or API version.
# @return [false] if the module does not implement a Metasploit(\d+) class.
# @return [false] if exception encountered while parsing module
# content
# @return [false] if the module is incompatible with the Core or API
# version.
# @return [false] if the module does not implement a Metasploit(\d+)
# class.
# @return [false] if the module's is_usable method returns false.
# @return [true] if all those condition pass and the module is successfully loaded.
# @return [true] if all those condition pass and the module is
# successfully loaded.
#
# @see #read_module_content
# @see Msf::ModuleManager::Loading#file_changed?
@ -121,8 +134,8 @@ class Msf::Modules::Loader::Base
module_content = read_module_content(parent_path, type, module_reference_name)
if module_content.empty?
# read_module_content is responsible for calling {#load_error}, so just return here.
return false
# read_module_content is responsible for calling {#load_error}, so just return here.
return false
end
try_eval_module = lambda { |namespace_module|
@ -139,9 +152,9 @@ class Msf::Modules::Loader::Base
begin
namespace_module.version_compatible!(module_path, module_reference_name)
rescue Msf::Modules::VersionCompatibilityError => version_compatibility_error
load_error(module_path, version_compatibility_error)
load_error(module_path, version_compatibility_error)
else
load_error(module_path, error)
load_error(module_path, error)
end
return false
@ -150,17 +163,17 @@ class Msf::Modules::Loader::Base
begin
namespace_module.version_compatible!(module_path, module_reference_name)
rescue Msf::Modules::VersionCompatibilityError => version_compatibility_error
load_error(module_path, version_compatibility_error)
load_error(module_path, version_compatibility_error)
return false
end
begin
metasploit_class = namespace_module.metasploit_class!(module_path, module_reference_name)
metasploit_class = namespace_module.metasploit_class!(module_path, module_reference_name)
rescue Msf::Modules::MetasploitClassCompatibilityError => error
load_error(module_path, error)
load_error(module_path, error)
return false
return false
end
unless usable?(metasploit_class)
@ -227,12 +240,15 @@ class Msf::Modules::Loader::Base
# Loads all of the modules from the supplied path.
#
# @note Only paths where {#loadable?} returns true should be passed to this method.
# @note Only paths where {#loadable?} returns true should be passed to
# this method.
#
# @param [String] path Path under which there are modules
# @param [Hash] options
# @option options [Boolean] force (false) whether to force loading of the module even if the module has not changed.
# @return [Hash{String => Integer}] Maps module type to number of modules loaded
# @option options [Boolean] force (false) Whether to force loading of
# the module even if the module has not changed.
# @return [Hash{String => Integer}] Maps module type to number of
# modules loaded
def load_modules(path, options={})
options.assert_valid_keys(:force)
@ -396,28 +412,28 @@ class Msf::Modules::Loader::Base
raise ::NotImplementedError
end
# Records the load error to {Msf::ModuleManager::Loading#module_load_error_by_path} and the log.
#
# @param [String] module_path Path to the module as returned by {#module_path}.
# @param [Exception, #class, #to_s, #backtrace] error the error that cause the module not to load.
# @return [void]
#
# @see #module_path
def load_error(module_path, error)
# module_load_error_by_path does not get the backtrace because the value is echoed to the msfconsole where
# backtraces should not appear.
module_manager.module_load_error_by_path[module_path] = "#{error.class} #{error}"
# Records the load error to {Msf::ModuleManager::Loading#module_load_error_by_path} and the log.
#
# @param [String] module_path Path to the module as returned by {#module_path}.
# @param [Exception, #class, #to_s, #backtrace] error the error that cause the module not to load.
# @return [void]
#
# @see #module_path
def load_error(module_path, error)
# module_load_error_by_path does not get the backtrace because the value is echoed to the msfconsole where
# backtraces should not appear.
module_manager.module_load_error_by_path[module_path] = "#{error.class} #{error}"
log_lines = []
log_lines << "#{module_path} failed to load due to the following error:"
log_lines << error.class.to_s
log_lines << error.to_s
log_lines << "Call stack:"
log_lines += error.backtrace
log_lines = []
log_lines << "#{module_path} failed to load due to the following error:"
log_lines << error.class.to_s
log_lines << error.to_s
log_lines << "Call stack:"
log_lines += error.backtrace
log_message = log_lines.join("\n")
elog(log_message)
end
log_message = log_lines.join("\n")
elog(log_message)
end
# @return [Msf::ModuleManager] The module manager for which this loader is loading modules.
attr_reader :module_manager
@ -480,11 +496,14 @@ class Msf::Modules::Loader::Base
namespace_module_name
end
# Returns an Array of names to make a fully qualified module name to wrap the Metasploit(1|2|3) class so that it
# doesn't overwrite other (metasploit) module's classes. Invalid module name characters are escaped by using 'H*'
# unpacking and prefixing each code with X so the code remains a valid module name when it starts with a digit.
# Returns an Array of names to make a fully qualified module name to
# wrap the Metasploit(1|2|3) class so that it doesn't overwrite other
# (metasploit) module's classes. Invalid module name characters are
# escaped by using 'H*' unpacking and prefixing each code with X so
# the code remains a valid module name when it starts with a digit.
#
# @param [String] uniq_module_reference_name The unique canonical name for the module including type.
# @param [String] uniq_module_reference_name The unique canonical name
# for the module including type.
# @return [Array<String>] {NAMESPACE_MODULE_NAMES} + <derived-constant-safe names>
#
# @see namespace_module
@ -513,8 +532,9 @@ class Msf::Modules::Loader::Base
end
namespace_module = create_namespace_module(namespace_module_names)
# Get the parent module from the created module so that restore_namespace_module can remove namespace_module's
# constant if needed.
# Get the parent module from the created module so that
# restore_namespace_module can remove namespace_module's constant if
# needed.
parent_module = namespace_module.parent
begin
@ -557,21 +577,21 @@ class Msf::Modules::Loader::Base
if parent_module
# If there is a current module with relative_name
if parent_module.const_defined?(relative_name)
# if the current value isn't the value to be restored.
if parent_module.const_get(relative_name) != namespace_module
# remove_const is private, so use send to bypass
parent_module.send(:remove_const, relative_name)
# if the current value isn't the value to be restored.
if parent_module.const_get(relative_name) != namespace_module
# remove_const is private, so use send to bypass
parent_module.send(:remove_const, relative_name)
# if there was a previous module, not set it to the name
if namespace_module
parent_module.const_set(relative_name, namespace_module)
end
end
# if there was a previous module, not set it to the name
if namespace_module
parent_module.const_set(relative_name, namespace_module)
end
end
else
# if there was a previous module, but there isn't a current module, then restore the previous module
if namespace_module
parent_module.const_set(relative_name, namespace_module)
end
# if there was a previous module, but there isn't a current module, then restore the previous module
if namespace_module
parent_module.const_set(relative_name, namespace_module)
end
end
end
end

View File

@ -20,12 +20,9 @@ class PayloadSet < ModuleSet
# Creates an instance of a payload set which is just a specialized module
# set class that has custom handling for payloads.
#
def initialize(manager)
def initialize
super(MODULE_PAYLOAD)
# A reference to the ModuleManager instance
self.manager = manager
# A hash of each of the payload types that holds an array
# for all of the associated modules
self.payload_type_modules = {}
@ -74,60 +71,36 @@ class PayloadSet < ModuleSet
# of singles, stagers, and stages.
#
def recalculate
# Reset the current hash associations for all non-symbolic modules
self.each_pair { |key, v|
manager.delete(key) if (v != SymbolicModule)
}
old_keys = self.keys
new_keys = []
self.delete_if { |k, v|
v != SymbolicModule
}
# Initialize a temporary hash
_temp = {}
# Populate the temporary hash
_singles.each_pair { |name, op|
_temp[name] = op
}
# Recalculate single payloads
_temp.each_pair { |name, op|
_singles.each_pair { |name, op|
mod, handler = op
# Build the payload dupe using the determined handler
# and module
p = build_payload(handler, mod)
# Sets the modules derived name
p.refname = name
# Add it to the set
add_single(p, name, op[5])
new_keys.push name
# Cache the payload's size
begin
sizes[name] = p.new.size
# Don't cache generic payload sizes.
rescue NoCompatiblePayloadError
end
}
# Initialize a temporary hash
_temp = {}
# Populate the temporary hash
_stagers.each_pair { |stager_name, op|
_temp[stager_name] = op
}
# Recalculate staged payloads
_temp.each_pair { |stager_name, op|
mod, handler = op
_stagers.each_pair { |stager_name, op|
stager_mod, handler, stager_platform, stager_arch, stager_inst = op
# Walk the array of stages
_stages.each_pair { |stage_name, ip|
stage_mod, junk, stage_platform, stage_arch, stage_inst = ip
stage_mod, _, stage_platform, stage_arch, stage_inst = ip
# No intersection between platforms on the payloads?
if ((stager_platform) and
@ -179,12 +152,20 @@ class PayloadSet < ModuleSet
'files' => op[5]['files'] + ip[5]['files'],
'paths' => op[5]['paths'] + ip[5]['paths'],
'type' => op[5]['type']})
new_keys.push combined
# Cache the payload's size
sizes[combined] = p.new.size
}
}
# Blow away anything that was cached but didn't exist during the
# recalculation
self.delete_if do |k, v|
next if v == SymbolicModule
!!(old_keys.include?(k) and not new_keys.include?(k))
end
flush_blob_cache
end
@ -276,8 +257,7 @@ class PayloadSet < ModuleSet
# returns an instance of that payload.
#
def find_payload_from_set(set, platform, arch, handler, session, payload_type)
set.each do |m|
name,mod = m
set.each do |name, mod|
p = mod.new
# We can't substitute one generic with another one.
@ -303,6 +283,8 @@ class PayloadSet < ModuleSet
#
def add_single(p, name, modinfo)
p.framework = framework
p.refname = name
p.file_path = modinfo['files'][0]
# Associate this class with the single payload's name
self[name] = p
@ -310,9 +292,6 @@ class PayloadSet < ModuleSet
# Add the singles hash
singles[name] = p
# Add it to the global module set
manager.add_module(p, name, modinfo)
dlog("Built single payload #{name}.", 'core', LEV_2)
end
@ -322,13 +301,12 @@ class PayloadSet < ModuleSet
#
def add_stage(p, full_name, stage_name, handler_type, modinfo)
p.framework = framework
p.refname = full_name
p.file_path = modinfo['files'][0]
# Associate this stage's full name with the payload class in the set
self[full_name] = p
# Add the full name association in the global module set
manager.add_module(p, full_name, modinfo)
# Create the hash entry for this stage and then create
# the associated entry for the handler type
stages[stage_name] = {} if (!stages[stage_name])
@ -445,7 +423,7 @@ protected
return klass
end
attr_accessor :manager, :payload_type_modules # :nodoc:
attr_accessor :payload_type_modules # :nodoc:
attr_writer :stages, :singles, :sizes # :nodoc:
attr_accessor :_instances # :nodoc: