624e19fd8b
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 commitb3371e8bfeAuthor: 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 commit5417419f35Author: James Lee <egypt@metasploit.com> Date: Tue Feb 28 16:58:16 2012 -0700 Whitespace commit9036875c29Author: 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. commitf34b51c629Author: James Lee <egypt@metasploit.com> Date: Tue Feb 28 16:48:42 2012 -0700 Clean up rdoc commite61a066345Author: 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) commit4c70161097Author: 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 commitc5ff89fe3dAuthor: 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 commit462d0188a1Author: HD Moore <hd_moore@rapid7.com> Date: Tue Feb 28 03:55:10 2012 -0600 Fix up the logic (reversed by accident) commit2b2b0adaecAuthor: 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 commit547a4ab4c6Author: HD Moore <hd_moore@rapid7.com> Date: Mon Feb 27 22:16:03 2012 -0600 Fix typo introduced commit27a7b7961eAuthor: HD Moore <hd_moore@rapid7.com> Date: Mon Feb 27 22:11:38 2012 -0600 More session.session_host tweaks commite447302a1aAuthor: HD Moore <hd_moore@rapid7.com> Date: Mon Feb 27 22:08:20 2012 -0600 Additional tunnel_peer changes commit93369fcffaAuthor: HD Moore <hd_moore@rapid7.com> Date: Mon Feb 27 22:06:21 2012 -0600 Additional changes to session.session_host commitc3552f66d1Author: HD Moore <hd_moore@rapid7.com> Date: Mon Feb 27 22:00:19 2012 -0600 Merge changes into the new branch
1154 lines
27 KiB
Ruby
1154 lines
27 KiB
Ruby
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
|
|
|
|
#
|
|
# 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 }
|
|
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:
|
|
#
|
|
# 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
|
|
|