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

1156 lines
27 KiB
Ruby
Raw Normal View History

require 'msf/core'
module Msf
###
#
# The module base class is responsible for providing the common interface
# that is used to interact with modules at the most basic levels, such as
# by inspecting a given module's attributes (name, dsecription, version,
# authors, etc) and by managing the module's data store.
#
###
class Module
# Modules can subscribe to a user-interface, and as such they include the
# UI subscriber module. This provides methods like print, print_line, etc.
# User interfaces are designed to be medium independent, and as such the
# user interface subscribes are designed to provide a flexible way of
# interacting with the user, n stuff.
include Rex::Ui::Subscriber
# Make include public so we can runtime extend
public_class_method :include
class << self
include Framework::Offspring
#
# Class method to figure out what type of module this is
#
def type
raise NotImplementedError
end
def fullname
return type + '/' + refname
end
def shortname
return refname.split('/')[-1]
end
#
# Returns this module's ranking.
#
def rank
(const_defined?('Rank')) ? const_get('Rank') : NormalRanking
end
#
# Returns this module's ranking as a string representation.
#
def rank_to_s
RankingName[rank]
end
#
# Returns this module's ranking as a string for display.
#
def rank_to_h
rank_to_s.gsub('Rank', '').downcase
end
#
# The module's name that is assigned it it by the framework
# or derived from the path that the module is loaded from.
#
attr_accessor :refname
#
# This attribute holds the non-duplicated copy of the module
# implementation. This attribute is used for reloading purposes so that
# it can be re-duplicated.
#
attr_accessor :orig_cls
#
# The path from which the module was loaded.
#
attr_accessor :file_path
#
# Override the default Class#inspect which is useless for the way
# modules get loaded
#
def inspect
"#<Class for #{refname}>"
end
end
#
# Returns the class reference to the framework
#
def framework
return self.class.framework
end
#
# This method allows modules to tell the framework if they are usable
# on the system that they are being loaded on in a generic fashion.
# By default, all modules are indicated as being usable. An example of
# where this is useful is if the module depends on something external to
# ruby, such as a binary.
#
def self.is_usable
true
end
require 'msf/core/module/author'
require 'msf/core/module/platform_list'
require 'msf/core/module/reference'
require 'msf/core/module/target'
require 'msf/core/module/auxiliary_action'
require 'msf/core/module/has_actions'
#
# Creates an instance of an abstract module using the supplied information
# hash.
#
def initialize(info = {})
@module_info_copy = info.dup
self.module_info = info
generate_uuid
set_defaults
# Initialize module compatibility hashes
init_compat
# Fixup module fields as needed
info_fixups
# Transform some of the fields to arrays as necessary
self.author = Author.transform(module_info['Author'])
self.arch = Rex::Transformer.transform(module_info['Arch'], Array, [ String ], 'Arch')
self.platform = PlatformList.transform(module_info['Platform'])
self.references = Rex::Transformer.transform(module_info['References'], Array, [ SiteReference, Reference ], 'Ref')
# Create and initialize the option container for this module
self.options = OptionContainer.new
self.options.add_options(info['Options'], self.class)
self.options.add_advanced_options(info['AdvancedOptions'], self.class)
self.options.add_evasion_options(info['EvasionOptions'], self.class)
# Create and initialize the data store for this module
self.datastore = ModuleDataStore.new(self)
# Import default options into the datastore
import_defaults
self.privileged = module_info['Privileged'] || false
self.license = module_info['License'] || MSF_LICENSE
# Allow all modules to track their current workspace
register_advanced_options(
[
OptString.new('WORKSPACE', [ false, "Specify the workspace for this module" ]),
OptBool.new('VERBOSE', [ false, 'Enable detailed status messages', false ])
], Msf::Module)
end
#
# Creates a fresh copy of an instantiated module
#
def replicant
obj = self.class.new
self.instance_variables.each { |k|
v = instance_variable_get(k)
v = v.dup rescue v
obj.instance_variable_set(k, v)
}
obj.datastore = self.datastore.copy
obj.user_input = self.user_input
obj.user_output = self.user_output
obj.module_store = self.module_store.clone
obj
end
#
# Overwrite the Subscriber print_(status|error|good) to do time stamps
#
def print_prefix
if(
datastore['TimestampOutput'] =~ /^(t|y|1)/i or
framework.datastore['TimestampOutput'] =~ /^(t|y|1)/i
)
prefix = "[#{Time.now.strftime("%Y.%m.%d-%H:%M:%S")}] "
xn ||= datastore['ExploitNumber']
xn ||= framework.datastore['ExploitNumber']
if xn.is_a?(Fixnum)
prefix << "[%04d] " % xn
end
return prefix
end
''
end
def print_status(msg='')
super(print_prefix + msg)
end
def print_error(msg='')
super(print_prefix + msg)
end
def print_good(msg='')
super(print_prefix + msg)
end
#
# Overwrite the Subscriber print_line to do custom prefixes
#
def print_line_prefix
datastore['CustomPrintPrefix'] || framework.datastore['CustomPrintPrefix'] || ''
end
def print_line(msg='')
super(print_line_prefix + msg)
end
# Verbose version of #print_status
def vprint_status(msg)
print_status(msg) if datastore['VERBOSE'] || framework.datastore['VERBOSE']
end
# Verbose version of #print_error
def vprint_error(msg)
print_error(msg) if datastore['VERBOSE'] || framework.datastore['VERBOSE']
end
# Verbose version of #print_good
def vprint_good(msg)
print_good(msg) if datastore['VERBOSE'] || framework.datastore['VERBOSE']
end
# Verbose version of #print_line
def vprint_line(msg)
print_line(msg) if datastore['VERBOSE'] || framework.datastore['VERBOSE']
end
# Verbose version of #print_debug
def vprint_debug(msg)
print_debug(msg) if datastore['VERBOSE'] || framework.datastore['VERBOSE']
end
#
# Returns the module's framework full reference name. This is the
# short name that end-users work with (refname) plus the type
# of module prepended. Ex:
#
# payloads/windows/shell/reverse_tcp
#
def fullname
return self.class.fullname
end
#
# Returns the module's framework reference name. This is the
# short name that end-users work with. Ex:
#
# windows/shell/reverse_tcp
#
def refname
return self.class.refname
end
#
# Returns the module's rank.
#
def rank
return self.class.rank
end
#
# Returns the module's rank in string format.
#
def rank_to_s
return self.class.rank_to_s
end
#
# Returns the module's rank in display format.
#
def rank_to_h
return self.class.rank_to_h
end
#
# Returns the module's framework short name. This is a
# possibly conflicting name used for things like console
# prompts.
#
# reverse_tcp
#
def shortname
return self.class.shortname
end
#
# Returns the unduplicated class associated with this module.
#
def orig_cls
return self.class.orig_cls
end
#
# The path to the file in which the module can be loaded from.
#
def file_path
self.class.file_path
end
#
# Return the module's name from the module information hash.
#
def name
module_info['Name']
end
#
# Returns the module's alias, if it has one. Otherwise, the module's
# name is returned.
#
def alias
module_info['Alias']
end
#
# Return the module's description.
#
def description
module_info['Description']
end
#
# Return the module's legacy version information.
#
def version
module_info['Version'].split(/,/).map { |ver|
ver.gsub(/\$Rev.s.on:\s+|\s+\$$/, '')
}.join(',')
end
#
# Returns the disclosure date, if known.
#
def disclosure_date
date_str = Date.parse(module_info['DisclosureDate'].to_s) rescue nil
end
#
# Returns the hash that describes this module's compatibilities.
#
def compat
module_info['Compat'] || {}
end
#
# Returns the address of the last target host (rough estimate)
#
def target_host
if(self.respond_to?('rhost'))
return rhost()
end
if(self.datastore['RHOST'])
return self.datastore['RHOST']
end
nil
end
Merge session-host-rework branch back to master Squashed commit of the following: commit 2f4e8df33c5b4baa8d6fd67b400778a3f93482aa Author: James Lee <egypt@metasploit.com> Date: Tue Feb 28 16:31:03 2012 -0700 Clean up some rdoc comments This adds categories for the various interfaces that meterpreter and shell sessions implement so they are grouped logically in the docs. commit 9d31bc1b35845f7279148412f49bda56a39c9d9d Author: James Lee <egypt@metasploit.com> Date: Tue Feb 28 13:00:25 2012 -0700 Combine the docs into one output dir There's really no need to separate the API sections into their own directory. Combining them makes it much easier to read. commit eadd7fc136a9e7e4d9652d55dfb86e6f318332e0 Author: James Lee <egypt@metasploit.com> Date: Tue Feb 28 08:27:22 2012 -0700 Keep the order of iface attributes the same accross rubies 1.8 doesn't maintain insertion order for Hash keys like 1.9 does so we end up with ~random order for the display with the previous technique. Switch to an Array instead of a Hash so it's always the same. commit 6f66dd40f39959711f9bacbda99717253a375d21 Author: James Lee <egypt@metasploit.com> Date: Tue Feb 28 08:23:35 2012 -0700 Fix a few more compiler warnings commit f39cb536a80c5000a5b9ca1fec5902300ae4b440 Author: James Lee <egypt@metasploit.com> Date: Tue Feb 28 08:17:39 2012 -0700 Fix a type-safety warning commit 1e52785f38146515409da3724f858b9603d19454 Author: James Lee <egypt@metasploit.com> Date: Mon Feb 27 15:21:36 2012 -0700 LHOST should be OptAddress, not OptAddressRange commit acef978aa4233c7bd0b00ef63646eb4da5457f67 Author: James Lee <egypt@metasploit.com> Date: Sun Feb 26 17:45:59 2012 -0700 Fix a couple of warnings and a typo commit 29d87f88790aa1b3e5db6df650ecfb3fb93c675b Author: HD Moore <hdm@digitaloffense.net> Date: Mon Feb 27 11:54:29 2012 -0600 Fix ctype vs content_type typo commit 83b5400356c47dd1973e6be3aa343084dfd09c73 Author: Gregory Man <man.gregory@gmail.com> Date: Sun Feb 26 15:38:33 2012 +0200 Fixed scripts/meterpreter/enum_firefox to work with firefox > 3.6.x commit 49c2c80b347820d02348d694cc71f1b3028b4365 Author: Steve Tornio <swtornio@gmail.com> Date: Sun Feb 26 07:13:13 2012 -0600 add osvdb ref commit e18e1fe97b89c3a2b8c22bc6c18726853d2c2bee Author: Matt Andreko <mandreko@gmail.com> Date: Sat Feb 25 18:02:56 2012 -0500 Added aspx target to msfvenom. This in turn added it to msfencode as well. Ref: https://github.com/rapid7/metasploit-framework/pull/188 Tested on winxp with IIS in .net 1.1 and 2.0 modes commit e6aa5072112d79bbf8a4d2289cf8d301db3932f5 Author: Joshua J. Drake <github.jdrake@qoop.org> Date: Sat Feb 25 13:00:48 2012 -0600 Fixes #6308: Fall back to 127.0.0.1 when SocketError is raised from the resolver commit b3371e8bfeea4d84f9d0cba100352b57d7e9e78b Author: James Lee <egypt@metasploit.com> Date: Tue Feb 28 17:07:42 2012 -0700 Simplify logic for whether an inner iface has the same address commit 5417419f35a40d1c08ca11ca40744722692d3b0d Author: James Lee <egypt@metasploit.com> Date: Tue Feb 28 16:58:16 2012 -0700 Whitespace commit 9036875c2918439ae23e11ee7b958e30ccc29545 Author: James Lee <egypt@metasploit.com> Date: Tue Feb 28 16:53:45 2012 -0700 Set session info before worrying about address get_interfaces can take a while on Linux, grab uid and hostname earlier so we can give the user an idea of what they popped as soon as possible. commit f34b51c6291031ab25b5bfb1ac6307a516ab0ee9 Author: James Lee <egypt@metasploit.com> Date: Tue Feb 28 16:48:42 2012 -0700 Clean up rdoc commit e61a0663454400ec66f59a80d18b0baff4cb8cd9 Author: HD Moore <hd_moore@rapid7.com> Date: Tue Feb 28 04:54:45 2012 -0600 Ensure the architecture is only the first word (not the full WOW64 message in some cases) commit 4c701610976a92298c1182eecc9291a1b301e43b Author: HD Moore <hd_moore@rapid7.com> Date: Tue Feb 28 04:49:17 2012 -0600 More paranoia code, just in case RHOST is set to whitespace commit c5ff89fe3dc9061e0fa9f761e6530f6571989d28 Author: HD Moore <hd_moore@rapid7.com> Date: Tue Feb 28 04:47:01 2012 -0600 A few more small bug fixes to handle cases with an empty string target host resulting in a bad address commit 462d0188a1298f29ac83b10349aec6737efc5b19 Author: HD Moore <hd_moore@rapid7.com> Date: Tue Feb 28 03:55:10 2012 -0600 Fix up the logic (reversed by accident) commit 2b2b0adaec2448423dbd3ec54d90a5721965e2df Author: HD Moore <hd_moore@rapid7.com> Date: Mon Feb 27 23:29:52 2012 -0600 Automatically parse system information and populate the db, identify and report NAT when detected, show the real session_host in the sessions -l listing commit 547a4ab4c62dc3248f847dd5d305ad3b74157348 Author: HD Moore <hd_moore@rapid7.com> Date: Mon Feb 27 22:16:03 2012 -0600 Fix typo introduced commit 27a7b7961e61894bdecd55310a8f45d0917c5a5c Author: HD Moore <hd_moore@rapid7.com> Date: Mon Feb 27 22:11:38 2012 -0600 More session.session_host tweaks commit e447302a1a9915795e89b5e29c89ff2ab9b6209b Author: HD Moore <hd_moore@rapid7.com> Date: Mon Feb 27 22:08:20 2012 -0600 Additional tunnel_peer changes commit 93369fcffaf8c6b00d992526b4083acfce036bb3 Author: HD Moore <hd_moore@rapid7.com> Date: Mon Feb 27 22:06:21 2012 -0600 Additional changes to session.session_host commit c3552f66d158685909e2c8b51dfead7c240c4f40 Author: HD Moore <hd_moore@rapid7.com> Date: Mon Feb 27 22:00:19 2012 -0600 Merge changes into the new branch
2012-02-29 01:28:47 +00:00
#
# Returns the address of the last target port (rough estimate)
#
def target_port
if(self.respond_to?('rport'))
return rport()
end
if(self.datastore['RPORT'])
return self.datastore['RPORT']
end
nil
end
#
# Returns the current workspace
#
def workspace
self.datastore['WORKSPACE'] ||
(framework.db and framework.db.active and framework.db.workspace and framework.db.workspace.name)
end
#
# Returns the username that instantiated this module, this tries a handful of methods
# to determine what actual user ran this module.
#
def owner
# Generic method to configure a module owner
username = self.datastore['MODULE_OWNER'].to_s.strip
# Specific method used by the commercial products
if username.empty?
username = self.datastore['PROUSER'].to_s.strip
end
# Fallback when neither prior method is available, common for msfconsole
if username.empty?
username = (ENV['LOGNAME'] || ENV['USERNAME'] || ENV['USER'] || "unknown").to_s.strip
end
username
end
#
# Scans the parent module reference to populate additional information. This
# is used to inherit common settings (owner, workspace, parent uuid, etc).
#
def register_parent(ref)
self.datastore['WORKSPACE'] = (ref.datastore['WORKSPACE'] ? ref.datastore['WORKSPACE'].dup : nil)
self.datastore['PROUSER'] = (ref.datastore['PROUSER'] ? ref.datastore['PROUSER'].dup : nil)
self.datastore['MODULE_OWNER'] = ref.owner.dup
self.datastore['ParentUUID'] = ref.uuid.dup
end
#
# Returns whether or not this module is compatible with the supplied
# module.
#
def compatible?(mod)
ch = nil
# Invalid module? Shoot, we can't compare that.
return true if (mod == nil)
# Determine which hash to used based on the supplied module type
if (mod.type == MODULE_ENCODER)
ch = self.compat['Encoder']
elsif (mod.type == MODULE_NOP)
ch = self.compat['Nop']
elsif (mod.type == MODULE_PAYLOAD)
ch = self.compat['Payload']
else
return true
end
# Enumerate each compatibility item in our hash to find out
# if we're compatible with this sucker.
ch.each_pair do |k,v|
# Get the value of the current key from the module, such as
# the ConnectionType for a stager (ws2ord, for instance).
mval = mod.module_info[k]
# Reject a filled compat item on one side, but not the other
if (v and not mval)
dlog("Module #{mod.refname} is incompatible with #{self.refname} for #{k}: limiter was #{v}")
return false
end
# Track how many of our values matched the module
mcnt = 0
# Values are whitespace separated
sv = v.split(/\s+/)
mv = mval.split(/\s+/)
sv.each do |x|
dlog("Checking compat [#{mod.refname} with #{self.refname}]: #{x} to #{mv.join(", ")}", 'core', LEV_3)
# Verify that any negate values are not matched
if (x[0,1] == '-' and mv.include?(x[1, x.length-1]))
dlog("Module #{mod.refname} is incompatible with #{self.refname} for #{k}: limiter was #{x}, value was #{mval}", 'core', LEV_1)
return false
end
mcnt += 1 if mv.include?(x)
end
# No values matched, reject this module
if (mcnt == 0)
dlog("Module #{mod.refname} is incompatible with #{self.refname} for #{k}: limiter was #{v}, value was #{mval}", 'core', LEV_1)
return false
end
end
dlog("Module #{mod.refname} is compatible with #{self.refname}", "core", LEV_1)
# If we get here, we're compatible.
return true
end
#
# Return the module's abstract type.
#
def type
raise NotImplementedError
end
#
# Return a comma separated list of author for this module.
#
def author_to_s
return author.collect { |author| author.to_s }.join(", ")
end
#
# Enumerate each author.
#
def each_author(&block)
author.each(&block)
end
#
# Return a comma separated list of supported architectures, if any.
#
def arch_to_s
return arch.join(", ")
end
#
# Enumerate each architecture.
#
def each_arch(&block)
arch.each(&block)
end
#
# Return whether or not the module supports the supplied architecture.
#
def arch?(what)
return true if (what == ARCH_ANY)
return arch.index(what) != nil
end
#
# Return a comma separated list of supported platforms, if any.
#
def platform_to_s
return ((platform.all?) ? [ "All" ] : platform.names).join(", ")
end
#
# Checks to see if this module is compatible with the supplied platform
#
def platform?(what)
(platform & what).empty? == false
end
#
# Returns whether or not the module requires or grants high privileges.
#
def privileged?
return (privileged == true)
end
#
# The default communication subsystem for this module. We may need to move
# this somewhere else.
#
def comm
return Rex::Socket::Comm::Local
end
#
# Overrides the class' own datastore with the one supplied. This is used
# to allow modules to share datastores, such as a payload sharing an
# exploit module's datastore.
#
def share_datastore(ds)
self.datastore = ds
self.datastore.import_options(self.options)
end
#
# Imports default options into the module's datastore, optionally clearing
# all of the values currently set in the datastore.
#
def import_defaults(clear_datastore = true)
# Clear the datastore if the caller asked us to
self.datastore.clear if clear_datastore
self.datastore.import_options(self.options, 'self', true)
# If there are default options, import their values into the datastore
if (module_info['DefaultOptions'])
self.datastore.import_options_from_hash(module_info['DefaultOptions'], true, 'self')
end
end
#
# This method ensures that the options associated with this module all
# have valid values according to each required option in the option
# container.
#
def validate
self.options.validate(self.datastore)
end
#
# Returns true if this module is being debugged. The debug flag is set
# by setting datastore['DEBUG'] to 1|true|yes
#
def debugging?
(datastore['DEBUG'] || '') =~ /^(1|t|y)/i
end
#
# Indicates whether the module supports IPv6. This is true by default,
# but certain modules require additional work to be compatible or are
# hardcoded in terms of application support and should be skipped.
#
def support_ipv6?
true
end
#
# This provides a standard set of search filters for every module.
# The search terms are in the form of:
# {
# "text" => [ [ "include_term1", "include_term2", ...], [ "exclude_term1", "exclude_term2"], ... ],
# "cve" => [ [ "include_term1", "include_term2", ...], [ "exclude_term1", "exclude_term2"], ... ]
# }
#
# Returns true on no match, false on match
#
def search_filter(search_string)
return false if not search_string
search_string += " "
# Split search terms by space, but allow quoted strings
terms = search_string.split(/\"/).collect{|t| t.strip==t ? t : t.split(' ')}.flatten
terms.delete('')
# All terms are either included or excluded
res = {}
terms.each do |t|
f,v = t.split(":", 2)
if not v
v = f
f = 'text'
end
next if v.length == 0
f.downcase!
v.downcase!
res[f] ||=[ [], [] ]
if v[0,1] == "-"
next if v.length == 1
res[f][1] << v[1,v.length-1]
else
res[f][0] << v
end
end
k = res
refs = self.references.map{|x| [x.ctx_id, x.ctx_val].join("-") }
is_exploit = (self.type == "exploit")
is_auxiliary = (self.type == "auxiliary")
is_post = (self.type == "post")
is_server = (self.respond_to?(:stance) and self.stance == "aggressive")
is_client = (self.respond_to?(:stance) and self.stance == "passive")
[0,1].each do |mode|
match = false
k.keys.each do |t|
next if k[t][mode].length == 0
k[t][mode].each do |w|
# Reset the match flag for each keyword for inclusive search
match = false if mode == 0
# Convert into a case-insensitive regex
r = Regexp.new(Regexp.escape(w), true)
case t
when 'text'
terms = [self.name, self.fullname, self.description] + refs + self.author.map{|x| x.to_s}
if self.respond_to?(:targets) and self.targets
terms = terms + self.targets.map{|x| x.name}
end
match = [t,w] if terms.any? { |x| x =~ r }
when 'name'
match = [t,w] if self.name =~ r
when 'path'
match = [t,w] if self.fullname =~ r
when 'author'
match = [t,w] if self.author.map{|x| x.to_s}.any? { |a| a =~ r }
when 'os', 'platform'
match = [t,w] if self.platform_to_s =~ r or self.arch_to_s =~ r
if not match and self.respond_to?(:targets) and self.targets
match = [t,w] if self.targets.map{|x| x.name}.any? { |t| t =~ r }
end
when 'port'
match = [t,w] if self.datastore['RPORT'].to_s =~ r
when 'type'
match = [t,w] if (w == "exploit" and is_exploit)
match = [t,w] if (w == "auxiliary" and is_auxiliary)
match = [t,w] if (w == "post" and is_post)
when 'app'
match = [t,w] if (w == "server" and is_server)
match = [t,w] if (w == "client" and is_client)
when 'cve'
match = [t,w] if refs.any? { |ref| ref =~ /^cve\-/i and ref =~ r }
when 'bid'
match = [t,w] if refs.any? { |ref| ref =~ /^bid\-/i and ref =~ r }
when 'osvdb'
match = [t,w] if refs.any? { |ref| ref =~ /^osvdb\-/i and ref =~ r }
when 'edb'
match = [t,w] if refs.any? { |ref| ref =~ /^edb\-/i and ref =~ r }
end
break if match
end
# Filter this module if no matches for a given keyword type
if mode == 0 and not match
return true
end
end
# Filter this module if we matched an exlusion keyword (-value)
if mode == 1 and match
return true
end
end
false
end
##
#
# Just some handy quick checks
#
##
#
# Returns true if this module is an exploit module.
#
def exploit?
return (type == MODULE_EXPLOIT)
end
#
# Returns true if this module is a payload module.
#
def payload?
return (type == MODULE_PAYLOAD)
end
#
# Returns true if this module is an encoder module.
#
def encoder?
return (type == MODULE_ENCODER)
end
#
# Returns true if this module is a nop module.
#
def nop?
return (type == MODULE_NOP)
end
#
# Returns true if this module is an auxiliary module.
#
def auxiliary?
return (type == MODULE_AUX)
end
#
# Returns true if this module is an post-exploitation module.
#
def post?
return (type == MODULE_POST)
end
#
# Returns false since this is the real module
#
def self.cached?
false
end
#
# Read a value from the module store
#
def [](k)
self.module_store[k]
end
#
# Store a value into the module
#
def []=(k,v)
self.module_store[k] = v
end
#
# The array of zero or more authors.
#
attr_reader :author
#
# The array of zero or more architectures.
#
attr_reader :arch
#
# The array of zero or more platforms.
#
attr_reader :platform
#
# The reference count for the module.
#
attr_reader :references
#
# The module-specific datastore instance.
#
attr_reader :datastore
#
# The module-specific options.
#
attr_reader :options
#
# Whether or not this module requires privileged access.
#
attr_reader :privileged
#
# The license under which this module is provided.
#
attr_reader :license
#
# The job identifier that this module is running as, if any.
#
attr_accessor :job_id
#
# A generic hash used for passing additional information to modules
#
attr_accessor :module_store
#
# The last exception to occur using this module
#
attr_accessor :error
#
# A unique identifier for this module instance
#
attr_reader :uuid
protected
attr_writer :uuid
def generate_uuid
self.uuid = Rex::Text.rand_text_alphanumeric(8).downcase
end
#
# The list of options that support merging in an information hash.
#
UpdateableOptions = [ "Name", "Description", "Alias", "PayloadCompat" ]
#
# Sets the modules unsupplied info fields to their default values.
#
def set_defaults
self.module_info = {
'Name' => 'No module name',
'Description' => 'No module description',
'Version' => '0',
'Author' => nil,
'Arch' => nil, # No architectures by default.
'Platform' => [], # No platforms by default.
'Ref' => nil,
'Privileged' => false,
'License' => MSF_LICENSE,
}.update(self.module_info)
self.module_store = {}
end
#
# This method initializes the module's compatibility hashes by normalizing
# them into one single hash. As it stands, modules can define
# compatibility in their supplied info hash through:
#
2012-06-06 23:04:54 +00:00
# Compat:: direct compat definitions
# PayloadCompat:: payload compatibilities
# EncoderCompat:: encoder compatibilities
# NopCompat:: nop compatibilities
#
# In the end, the module specific compatibilities are merged as sub-hashes
# of the primary Compat hash key to make checks more uniform.
#
def init_compat
c = module_info['Compat']
if (c == nil)
c = module_info['Compat'] = Hash.new
end
# Initialize the module sub compatibilities
c['Payload'] = Hash.new if (c['Payload'] == nil)
c['Encoder'] = Hash.new if (c['Encoder'] == nil)
c['Nop'] = Hash.new if (c['Nop'] == nil)
# Update the compat-derived module specific compatibilities from
# the specific ones to make a uniform view of compatibilities
c['Payload'].update(module_info['PayloadCompat'] || {})
c['Encoder'].update(module_info['EncoderCompat'] || {})
c['Nop'].update(module_info['NopCompat'] || {})
end
#
# Register options with a specific owning class.
#
def info_fixups
# Each reference should be an array consisting of two elements
refs = module_info['References']
if(refs and not refs.empty?)
refs.each_index do |i|
if !(refs[i].respond_to?('[]') and refs[i].length == 2)
refs[i] = nil
end
end
# Purge invalid references
refs.delete(nil)
end
end
#
# Register options with a specific owning class.
#
def register_options(options, owner = self.class)
self.options.add_options(options, owner)
self.datastore.import_options(self.options, 'self', true)
import_defaults(false)
end
#
# Register advanced options with a specific owning class.
#
def register_advanced_options(options, owner = self.class)
self.options.add_advanced_options(options, owner)
self.datastore.import_options(self.options, 'self', true)
import_defaults(false)
end
#
# Register evasion options with a specific owning class.
#
def register_evasion_options(options, owner = self.class)
self.options.add_evasion_options(options, owner)
self.datastore.import_options(self.options, 'self', true)
import_defaults(false)
end
#
# Removes the supplied options from the module's option container
# and data store.
#
def deregister_options(*names)
names.each { |name|
self.options.remove_option(name)
self.datastore.delete(name)
}
end
#
# Checks to see if a derived instance of a given module implements a method
# beyond the one that is provided by a base class. This is a pretty lame
# way of doing it, but I couldn't find a better one, so meh.
#
def derived_implementor?(parent, method_name)
(self.method(method_name).to_s.match(/#{parent}[^:]/)) ? false : true
end
#
# Merges options in the info hash in a sane fashion, as some options
# require special attention.
#
def merge_info(info, opts)
opts.each_pair { |name, val|
merge_check_key(info, name, val)
}
return info
end
#
# Updates information in the supplied info hash and merges other
# information. This method is used to override things like Name, Version,
# and Description without losing the ability to merge architectures,
# platforms, and options.
#
def update_info(info, opts)
opts.each_pair { |name, val|
# If the supplied option name is one of the ones that we should
# override by default
if (UpdateableOptions.include?(name) == true)
# Only if the entry is currently nil do we use our value
if (info[name] == nil)
info[name] = val
end
# Otherwise, perform the merge operation like normal
else
merge_check_key(info, name, val)
end
}
return info
end
#
# Checks and merges the supplied key/value pair in the supplied hash.
#
def merge_check_key(info, name, val)
if (self.respond_to?("merge_info_#{name.downcase}"))
eval("merge_info_#{name.downcase}(info, val)")
else
# If the info hash already has an entry for this name
if (info[name])
# If it's not an array, convert it to an array and merge the
# two
if (info[name].kind_of?(Array) == false)
curr = info[name]
info[name] = [ curr ]
end
# If the value being merged is an array, add each one
if (val.kind_of?(Array) == true)
val.each { |v|
if (info[name].include?(v) == false)
info[name] << v
end
}
# Otherwise just add the value
elsif (info[name].include?(val) == false)
info[name] << val
end
# Otherwise, just set the value equal if no current value
# exists
else
info[name] = val
end
end
end
#
# Merge aliases with an underscore delimiter.
#
def merge_info_alias(info, val)
merge_info_string(info, 'Alias', val, '_')
end
#
# Merges the module name.
#
def merge_info_name(info, val)
merge_info_string(info, 'Name', val, ', ', true)
end
#
# Merges the module description.
#
def merge_info_description(info, val)
merge_info_string(info, 'Description', val)
end
#
# Merge the module version.
#
def merge_info_version(info, val)
merge_info_string(info, 'Version', val)
end
#
# Merges a given key in the info hash with a delimiter.
#
def merge_info_string(info, key, val, delim = ', ', inverse = false)
if (info[key])
if (inverse == true)
info[key] = info[key] + delim + val
else
info[key] = val + delim + info[key]
end
else
info[key] = val
end
end
#
# Merges options.
#
def merge_info_options(info, val, advanced = false, evasion = false)
key_name = ((advanced) ? 'Advanced' : (evasion) ? 'Evasion' : '') + 'Options'
new_cont = OptionContainer.new
new_cont.add_options(val, advanced, evasion)
cur_cont = OptionContainer.new
cur_cont.add_options(info[key_name] || [], advanced, evasion)
new_cont.each_option { |name, option|
next if (cur_cont.get(name))
info[key_name] = [] if (!info[key_name])
info[key_name] << option
}
end
#
# Merges advanced options.
#
def merge_info_advanced_options(info, val)
merge_info_options(info, val, true, false)
end
#
# Merges advanced options.
#
def merge_info_evasion_options(info, val)
merge_info_options(info, val, false, true)
end
attr_accessor :module_info # :nodoc:
attr_writer :author, :arch, :platform, :references, :datastore, :options # :nodoc:
attr_writer :privileged # :nodoc:
attr_writer :license # :nodoc:
end
#
# Alias the data types so people can reference them just by Msf:: and not
# Msf::Module::
#
Author = Msf::Module::Author
Reference = Msf::Module::Reference
SiteReference = Msf::Module::SiteReference
Platform = Msf::Module::Platform
Target = Msf::Module::Target
end