Files
metasploit-gs/lib/msf/core/payload_set.rb
T
James Lee f4476cb1b7 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]
2012-12-03 22:23:40 -06:00

434 lines
11 KiB
Ruby

# -*- coding: binary -*-
require 'msf/core'
require 'msf/core/module_manager'
module Msf
###
#
# This class is a special case of the generic module set class because
# payloads are generated in terms of combinations between various
# components, such as a stager and a stage. As such, the payload set
# needs to be built on the fly and cannot be simply matched one-to-one
# with a payload module. Yeah, the term module is kind of overloaded
# here, but eat it!
#
###
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
super(MODULE_PAYLOAD)
# A hash of each of the payload types that holds an array
# for all of the associated modules
self.payload_type_modules = {}
# Initialize the hash entry for each type to an empty list
[
Payload::Type::Single,
Payload::Type::Stager,
Payload::Type::Stage
].each { |type|
self.payload_type_modules[type] = {}
}
# Initialize hashes for each of the stages and singles. Stagers
# never exist independent. The stages hash will have entries that
# point to another hash that point to the per-stager implementation
# payload class. For instance:
#
# ['windows/shell']['reverse_tcp']
#
# Singles will simply point to the single payload class.
self.stages = {}
self.singles = {}
# Hash that caches the sizes of payloads
self.sizes = {}
# Single instance cache of modules for use with doing quick referencing
# of attributes that would require an instance.
self._instances = {}
# Initializes an empty blob cache
@blob_cache = {}
end
#
# Performs custom filtering during each_module enumeration. This allows us
# to filter out certain stagers as necessary.
#
def each_module_filter(opts, name, mod)
return false
end
#
# This method builds the hash of alias names based on all the permutations
# of singles, stagers, and stages.
#
def recalculate
old_keys = self.keys
new_keys = []
# Recalculate single payloads
_singles.each_pair { |name, op|
mod, handler = op
# Build the payload dupe using the determined handler
# and module
p = build_payload(handler, mod)
# 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
}
# Recalculate staged payloads
_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, _, stage_platform, stage_arch, stage_inst = ip
# No intersection between platforms on the payloads?
if ((stager_platform) and
(stage_platform) and
(stager_platform & stage_platform).empty?)
dlog("Stager #{stager_name} and stage #{stage_name} have incompatible platforms: #{stager_platform.names} - #{stage_platform.names}", 'core', LEV_2)
next
end
# No intersection between architectures on the payloads?
if ((stager_arch) and
(stage_arch) and
((stager_arch & stage_arch).empty?))
dlog("Stager #{stager_name} and stage #{stage_name} have incompatible architectures: #{stager_arch.join} - #{stage_arch.join}", 'core', LEV_2)
next
end
# If the stage has a convention, make sure it's compatible with
# the stager's
if ((stage_inst) and (stage_inst.compatible?(stager_inst) == false))
dlog("Stager #{stager_name} and stage #{stage_name} are incompatible.", 'core', LEV_2)
next
end
# Build the payload dupe using the handler, stager,
# and stage
p = build_payload(handler, stager_mod, stage_mod)
# If the stager has an alias for the handler type (such as is the
# case for ordinal based stagers), use it in preference of the
# handler's actual type.
if (stager_mod.respond_to?('handler_type_alias') == true)
handler_type = stager_mod.handler_type_alias
else
handler_type = handler.handler_type
end
# Associate the name as a combination of the stager and stage
combined = stage_name
# If a valid handler exists for this stager, then combine it
combined += '/' + handler_type
# Sets the modules derived name
p.refname = combined
# Add the stage
add_stage(p, combined, stage_name, handler_type, {
'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
#
# This method is called when a new payload module class is loaded up. For
# the payload set we simply create an instance of the class and do some
# magic to figure out if it's a single, stager, or stage. Depending on
# which it is, we add it to the appropriate list.
#
def add_module(pmodule, name, modinfo = nil)
if (md = name.match(/^(singles|stagers|stages)#{File::SEPARATOR}(.*)$/))
ptype = md[1]
name = md[2]
end
# Duplicate the Payload base class and extend it with the module
# class that is passed in. This allows us to inspect the actual
# module to see what type it is, and to grab other information for
# our own evil purposes.
instance = build_payload(pmodule).new
# Create an array of information about this payload module
pinfo =
[
pmodule,
instance.handler_klass,
instance.platform,
instance.arch,
instance,
modinfo
]
# Use the module's preferred alias if it has one
name = instance.alias if (instance.alias)
# Store the module and alias name for this payload. We
# also convey other information about the module, such as
# the platforms and architectures it supports
payload_type_modules[instance.payload_type][name] = pinfo
#
# Disable sending singles over stagers for now
#
=begin
# If the payload happens to be a single, but has no defined
# connection, then it can also be staged. Insert it into
# the staged list.
if ((instance.payload_type == Payload::Type::Single) and
((instance.handler_klass == Msf::Handler::None) or
(instance.handler_klass == nil)))
payload_type_modules[Payload::Type::Stage][name] = pinfo
end
=end
end
#
# Looks for a payload that matches the specified requirements and
# returns an instance of that payload.
#
def find_payload(platform, arch, handler, session, payload_type)
# Pre-filter based on platform and architecture.
each_module(
'Platform' => platform,
'Arch' => arch) { |name, mod|
p = mod.new
# We can't substitute one generic with another one.
next if (p.kind_of?(Msf::Payload::Generic))
# Check to see if the handler classes match.
next if (handler and not p.handler_klass.ancestors.include?(handler))
# Check to see if the session classes match.
next if (session and not p.session.ancestors.include?(session))
# Check for matching payload types
next if (payload_type and p.payload_type != payload_type)
return p
}
return nil
end
#
# Looks for a payload from a given set that matches the specified requirements and
# returns an instance of that payload.
#
def find_payload_from_set(set, platform, arch, handler, session, payload_type)
set.each do |name, mod|
p = mod.new
# We can't substitute one generic with another one.
next if (p.kind_of?(Msf::Payload::Generic))
# Check to see if the handler classes match.
next if (handler and p.handler_klass != handler)
# Check to see if the session classes match.
next if (session and p.session != session)
# Check for matching payload types
next if (payload_type and p.payload_type != payload_type)
return p
end
return nil
end
#
# This method adds a single payload to the set and adds it to the singles
# hash.
#
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
# Add the singles hash
singles[name] = p
dlog("Built single payload #{name}.", 'core', LEV_2)
end
#
# This method adds a stage payload to the set and adds it to the stages
# hash using the supplied handler type.
#
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
# Create the hash entry for this stage and then create
# the associated entry for the handler type
stages[stage_name] = {} if (!stages[stage_name])
# Add it to this stage's stager hash
stages[stage_name][handler_type] = p
dlog("Built staged payload #{full_name}.", 'core', LEV_2)
end
#
# Returns a single read-only instance of the supplied payload name such
# that specific attributes, like compatibility, can be evaluated. The
# payload instance returned should NOT be used for anything other than
# reading.
#
def instance(name)
if (self._instances[name] == nil)
self._instances[name] = create(name)
end
self._instances[name]
end
#
# Returns the hash of payload stagers that have been loaded.
#
def stagers
_stagers
end
#
# When a payload module is reloaded, the blob cache entry associated with
# it must be removed (if one exists)
#
def on_module_reload(mod)
@blob_cache.delete(mod.refname + "-stg0")
@blob_cache.delete(mod.refname + "-stg1")
end
#
# Adds a blob to the blob cache so that the payload does not have to be
# recompiled in the future
#
def add_blob_cache(key, blob, offsets)
@blob_cache[key] = [ blob, offsets ]
end
#
# Checks to see if a payload has a blob cache entry. If it does, the blob
# is returned to the caller.
#
def check_blob_cache(key)
@blob_cache[key]
end
#
# Flushes all entries from the blob cache
#
def flush_blob_cache
@blob_cache.clear
end
#
# The list of stages that have been loaded.
#
attr_reader :stages
#
# The list of singles that have been loaded.
#
attr_reader :singles
#
# The sizes of all the built payloads thus far.
#
attr_reader :sizes
protected
#
# Return the hash of single payloads
#
def _singles
return payload_type_modules[Payload::Type::Single] || {}
end
#
# Return the hash of stager payloads
#
def _stagers
return payload_type_modules[Payload::Type::Stager] || {}
end
#
# Return the hash of stage payloads
#
def _stages
return payload_type_modules[Payload::Type::Stage] || {}
end
#
# Builds a duplicate, extended version of the Payload base
# class using the supplied modules.
#
def build_payload(*modules)
klass = Class.new(Payload)
# Remove nil modules
modules.compact!
# Include the modules supplied to us with the mad skillz
# spoonfu style
klass.include(*modules.reverse)
return klass
end
attr_accessor :payload_type_modules # :nodoc:
attr_writer :stages, :singles, :sizes # :nodoc:
attr_accessor :_instances # :nodoc:
end
end