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

1129 lines
26 KiB
Ruby
Raw Normal View History

require 'msf/core'
module Msf
###
#
# This module exposes an interface that is used when wanting to receive
# notifications about events pertaining to exploitation.
#
###
module ExploitEvent
#
# This method is called when an exploit succeeds.
#
def on_exploit_success(exploit, session)
end
end
###
#
# The exploit class acts as the base class for all exploit modules. It
# provides a common interface for interacting with exploits at the most basic
# level.
#
###
class Exploit < Msf::Module
##
#
# Default compatibility settings for exploit modules.
#
##
module CompatDefaults
#
# Default compatibility specifications for payloads
#
Payload =
{
# Support reverse, bind, find, and noconn connection types
# for all exploits unless expressly disabled.
'ConnectionType' => 'reverse bind find noconn none tunnel',
}
end
##
#
# The various check codes that can be returned from the ``check'' routine.
#
##
module CheckCode
#
# The target is safe and is therefore not exploitable.
#
Safe = [ 0, "The target is not exploitable." ]
#
# The target is running the service in question but may not be
# exploitable.
#
Detected = [ 1, "The target service is running, but could not be validated." ]
#
# The target appears to be vulnerable.
#
Appears = [ 2, "The target appears to be vulnerable." ]
#
# The target is vulnerable.
#
Vulnerable = [ 3, "The target is vulnerable." ]
#
# The exploit does not support the check method.
#
Unsupported = [ 4, "This exploit does not support check." ]
end
#
# The various basic types of exploits
#
module Type
#
# Indicates that the exploit is a remote exploit.
#
Remote = "remote"
#
# Indicates that the exploit is a local exploit.
#
Local = "local"
#
# Indicates that the exploit can work anywhere it damn pleases.
#
Omni = "omnipresent"
end
#
# The types of stances an exploit can take, such as passive or aggressive.
# Stances indicate whether or not the exploit triggers the exploit without
# waiting for one or more conditions to be met (aggressive) or whether it
# must wait for certain conditions to be satisfied before the exploit can
# be initiated (passive)
#
module Stance
#
# Used to indicate that an exploit takes an aggressive stance. This
# means that the exploit proactively triggers a vulnerability.
#
Aggressive = "aggresive"
#
# Used to indicate that an exploit takes a passive stance. This means
# that the exploit waits for interaction from a client or other entity
# before being able to trigger the vulnerability.
#
Passive = "passive"
end
###
#
# The local exploit class is a specialization of the exploit module class that
# is geared toward exploits that are performed locally. Locally, in this
# case, is defined as an exploit that is realized by means other than network
# communication.
#
###
class Local < Exploit
#
# Returns the fact that this exploit is a local exploit.
#
def exploit_type
Exploit::Type::Local
end
end
###
#
# The remote exploit class is a specialization of the exploit module class
# that is geared toward exploits that are performed against targets other than
# the local machine. This typically implies exploiting other machines via a
# network connection, though it is not limited to this scope.
#
###
class Remote < Exploit
#
# Initializes the socket array.
#
def initialize(info)
super
self.sockets = Array.new
end
#
# Returns the fact that this exploit is a remote exploit.
#
def exploit_type
Exploit::Type::Remote
end
#
# Adds a socket to the list of sockets opened by this exploit.
#
def add_socket(sock)
self.sockets << sock
end
#
# Removes a socket from the list of sockets.
#
def remove_socket(sock)
self.sockets.delete(sock)
end
#
# This method is called once a new session has been created on behalf of
# this exploit instance and all socket connections created by this
# exploit should be closed.
#
def abort_sockets
sockets.delete_if { |sock|
if (sock.respond_to?('abortive_close'))
sock.abortive_close = true
end
begin
disconnect(sock)
rescue
end
true
}
end
protected
#
# The list of sockets established by this exploit.
#
attr_accessor :sockets
end
#
# All exploit mixins should be added to the list below
#
# Behavior
require 'msf/core/exploit/brute'
require 'msf/core/exploit/brutetargets'
# Payload
require 'msf/core/exploit/egghunter'
require 'msf/core/exploit/seh'
require 'msf/core/exploit/kernel_mode'
# Protocol
require 'msf/core/exploit/tcp'
require 'msf/core/exploit/udp'
require 'msf/core/exploit/smb'
require 'msf/core/exploit/ftp'
require 'msf/core/exploit/http'
require 'msf/core/exploit/smtp'
require 'msf/core/exploit/dcerpc'
require 'msf/core/exploit/sunrpc'
require 'msf/core/exploit/mssql'
require 'msf/core/exploit/arkeia'
require 'msf/core/exploit/ndmp'
require 'msf/core/exploit/imap'
# Networks
require 'msf/core/exploit/lorcon'
require 'msf/core/exploit/pcap'
require 'msf/core/exploit/capture'
#
# Returns an array of all of the exploit mixins. Lame algorithm right now.
# We search the Msf::Exploit namespace for all modules that do not have any
# constants in them. In the future we can replace this with a better
# algorithm. It's just important that it returns an array of all of the
# mixin modules.
#
def self.mixins
mixins = []
wl = [ Msf::Exploit ]
visited = {}
until wl.length == 0
wl.delete_if { |mod|
mod.constants.each { |const|
child = mod.const_get(const)
next if child.to_s !~ /^Msf::Exploit/
next if visited[child]
next if child.kind_of?(::Module) == false
visited[child] = true
if child.constants.length > 0
wl << child
else
mixins << child
end
}
true
}
end
return mixins
end
#
# Creates an instance of the exploit module. Mad skillz.
#
def initialize(info = {})
# Ghetto compat mirroring for payload compatibilities. This mirrors
#
# Payload => Compat => xyz
#
# to
#
# Compat => Payload => xyz
if (info['Payload'] and info['Payload']['Compat'])
info['Compat'] = Hash.new if (info['Compat'] == nil)
info['Compat']['Payload'] = Hash.new if (info['Compat']['Payload'] == nil)
info['Compat']['Payload'].update(info['Payload']['Compat'])
end
# Call the parent constructor after making any necessary modifications
# to the information hash.
super(info)
self.targets = Rex::Transformer.transform(info['Targets'], Array,
[ Target ], 'Targets')
self.default_target = info['DefaultTarget']
self.payload_info = info['Payload'] || {}
self.session_count = 0
self.active_timeout = 120
if (info['Payload'] and info['Payload']['ActiveTimeout'])
self.active_timeout = info['Payload']['ActiveTimeout'].to_i
end
# All exploits can increase the delay when waiting for a session.
# However, this only applies to aggressive exploits.
if aggressive?
register_advanced_options(
[
OptInt.new('WfsDelay', [ false, "Additional delay when waiting for a session", 0 ])
], Msf::Exploit)
end
end
##
#
# Core exploit interface
#
# These are the methods that exploits will override to perform various
# tasks, such as checking a target to see if it's vulnerable, automatically
# selecting a target, or performing an exploit.
#
##
#
# Checks to see if the target is vulnerable, returning unsupported if it's
# not supported.
#
# This method is designed to be overriden by exploit modules.
#
def check
CheckCode::Unsupported
end
#
# Kicks off the actual exploit. Prior to this call, the framework will
# have validated the data store using the options associated with this
# exploit module. It will also pre-generate the desired payload, though
# exploits can re-generate the payload if necessary.
#
# This method is designed to be overriden by exploit modules.
#
def exploit
end
#
# Performs last-minute sanity checking of exploit parameters. This method
# is called during automated exploitation attempts and allows an
# exploit to filter bad targets, obtain more information, and choose
# better targets based on the available data. Returning anything that
# evaluates to "false" will cause this specific exploit attempt to
# be skipped. This method can and will change datastore values and
# may interact with the backend database.
#
def autofilter
true
end
#
# Prepares the module for exploitation, initializes any state, and starts
# the payload handler.
#
def setup
# Reset the session counts to zero.
reset_session_counts
if (payload_instance)
# Configure the payload handler
payload_instance.exploit_config = {
'active_timeout' => self.active_timeout
}
# Set up the payload handlers
payload_instance.setup_handler
# Start the payload handler
payload_instance.start_handler
end
end
#
# Performs any cleanup that may be necessary, such as disconnecting
# connections and any other such fun things. If a payload is active then
# its handler cleanup routines are called as well.
#
def cleanup
if (payload_instance)
payload_instance.cleanup_handler
end
end
#
# Generates the encoded version of the supplied payload using the payload
# requirements specific to this exploit. The encoded instance is returned
# to the caller. This method is exposed in the manner that it is such
# that passive exploits and re-generate an encoded payload on the fly
# rather than having to use the pre-generated one.
#
# The return value is an EncodedPayload instance.
#
def generate_payload(pinst = nil)
# Set the encoded payload to the result of the encoding process
self.payload = generate_single_payload(pinst)
# Save the payload instance
self.payload_instance = (pinst) ? pinst : self.payload_instance
return self.payload
end
#
# Allows the payload handler to spawn a new monitor
#
def add_handler(opts={})
return if not self.payload_instance
self.payload_instance.add_handler(opts)
end
#
# This method generates a non-cached payload which is typically useful for
# passive exploits that will have more than one client.
#
def generate_single_payload(pinst = nil, platform = nil, arch = nil)
if (target == nil)
raise MissingTargetError, "No target has been specified.",
caller
end
# If a payload instance was supplied, use it, otherwise
# use the active payload instance
real_payload = (pinst) ? pinst : self.payload_instance
if (real_payload == nil)
raise MissingPayloadError, "No payload has been selected.",
caller
end
# If this is a generic payload, then we should specify the platform
# and architecture so that it knows how to pass things on.
if real_payload.kind_of?(Msf::Payload::Generic)
# Convert the architecture specified into an array.
if arch and arch.kind_of?(String)
arch = [ arch ]
end
# Define the explicit platform and architecture information only if
# it's been specified.
if platform
real_payload.explicit_platform = Msf::Module::PlatformList.transform(platform)
end
if arch
real_payload.explicit_arch = arch
end
# Force it to reset so that it will find updated information.
real_payload.reset
end
# Duplicate the exploit payload requirements
reqs = self.payload_info.dup
# Pass save register requirements to the NOP generator
reqs['SaveRegisters'] = nop_save_registers
reqs['Prepend'] = payload_prepend
reqs['PrependEncoder'] = payload_prepend_encoder
reqs['BadChars'] = payload_badchars
reqs['Append'] = payload_append
reqs['MaxNops'] = payload_max_nops
reqs['MinNops'] = payload_min_nops
reqs['Encoder'] = datastore['ENCODER']
reqs['Nop'] = datastore['NOP']
reqs['EncoderType'] = payload_encoder_type
reqs['EncoderOptions'] = payload_encoder_options
reqs['ExtendedOptions'] = payload_extended_options
# Call the encode begin routine.
encode_begin(real_payload, reqs)
# Generate the encoded payload.
encoded = EncodedPayload.create(real_payload, reqs)
# Call the encode end routine which is expected to return the actual
# encoded payload instance.
return encode_end(real_payload, reqs, encoded)
end
#
# Re-generates an encoded payload, typically called after something in the
# datastore has changed. An optional platform and architecture can be
# supplied as well.
#
def regenerate_payload(platform = nil, arch = nil)
generate_single_payload(nil, platform, arch)
end
#
# Called prior to encoding a payload.
#
def encode_begin(real_payload, reqs)
end
#
# Called after an encoded payload has been generated. This gives exploits
# or mixins a chance to alter the encoded payload.
#
def encode_end(real_payload, reqs, encoded)
encoded
end
##
#
# Feature detection
#
# These methods check to see if there is a derived implementation of
# various methods as a way of inferring whether or not a given exploit
# supports the feature.
#
##
#
# Returns true if the exploit module supports the check method.
#
def supports_check?
derived_implementor?(Msf::Exploit, 'check')
end
#
# Returns true if the exploit module supports the exploit method.
#
def supports_exploit?
derived_implementor?(Msf::Exploit, 'exploit')
end
#
# Returns a hash of the capabilities this exploit module has support for,
# such as whether or not it supports check and exploit.
#
def capabilities
{
'check' => supports_check?,
'exploit' => supports_exploit?
}
end
##
#
# Getters/Setters
#
# Querying and set interfaces for some of the exploit's attributes.
#
##
#
# Returns MODULE_EXPLOIT to indicate that this is an exploit module.
#
def self.type
MODULE_EXPLOIT
end
#
# Returns MODULE_EXPLOIT to indicate that this is an exploit module.
#
def type
MODULE_EXPLOIT
end
#
# If we don't know the exploit type, then I guess it's omnipresent!
#
def exploit_type
Type::Omni
end
#
# Generally, all exploits take an aggressive stance.
#
def stance
module_info['Stance'] || Stance::Aggressive
end
#
# Returns true if the exploit has an aggressive stance.
#
def aggressive?
(stance == Stance::Aggressive)
end
#
# Returns if the exploit has a passive stance.
#
def passive?
(stance == Stance::Passive)
end
#
# Returns the active target for this exploit. If not target has been
# defined, nil is returned. If no target was defined but there is a
# default target, that one will be automatically used.
#
def target
target_idx = datastore['TARGET']
# Use the default target if one was not supplied.
if (target_idx == nil and default_target and default_target >= 0)
target_idx = default_target
end
return (target_idx) ? targets[target_idx.to_i] : nil
end
#
# The target index that has been selected.
#
def target_index
datastore['TARGET'].to_i
end
#
# Returns the target's platform, or the one assigned to the module itself.
#
def target_platform
(target and target.platform) ? target.platform : platform
end
#
# Returns the target's architecture, or the one assigned to the module
# itself.
#
def target_arch
(target and target.arch) ? target.arch : (arch == []) ? nil : arch
end
#
# Returns a list of compatible payloads based on platform, architecture,
# and size requirements.
#
def compatible_payloads
payloads = []
c_platform = (target and target.platform) ? target.platform : platform
c_arch = (target and target.arch) ? target.arch : (arch == []) ? nil : arch
framework.payloads.each_module(
'Platform' => c_platform,
'Arch' => c_arch ) { |name, mod|
# Skip over payloads that are too big
if ((payload_space) and
(framework.payloads.sizes[name]) and
(framework.payloads.sizes[name] > payload_space))
dlog("#{refname}: Skipping payload #{name} for being too large", 'core',
LEV_1)
next
end
# Are we compatible in terms of conventions and connections and
# what not?
next if (compatible?(framework.payloads.instance(name)) == false)
# If the payload is privileged but the exploit does not give
# privileged access, then fail it.
next if (self.privileged == false and framework.payloads.instance(name).privileged == true)
# This one be compatible!
payloads << [ name, mod ]
}
return payloads;
end
#
# Returns a list of compatible encoders based on architecture
#
def compatible_encoders
encoders = []
c_platform = (target and target.platform) ? target.platform : platform
c_arch = (target and target.arch) ? target.arch : (arch == []) ? nil : arch
framework.encoders.each_module(
'Arch' => c_arch) { |name, mod|
encoders << [ name, mod ]
}
return encoders;
end
#
# This method returns the number of bytes that should be adjusted to the
# stack pointer prior to executing any code. The number of bytes to adjust
# is indicated to the routine through the payload 'StackAdjustment'
# attribute or through a target's payload 'StackAdjustment' attribute.
#
def stack_adjustment
if (target and target.payload_stack_adjustment)
adj = target.payload_stack_adjustment
else
adj = payload_info['StackAdjustment']
end
# Get the architecture for the current target or use the one specific to
# this exploit
arch = (target and target.arch) ? target.arch : self.arch
# Default to x86 if we can't find a list of architectures
if (arch and arch.empty? == false)
arch = arch.join(", ")
else
arch = 'x86'
end
(adj != nil) ? Rex::Arch::adjust_stack_pointer(arch, adj) || '' : ''
end
#
# Return any text that should be prepended to the payload. The payload
# module is passed so that the exploit can take a guess at architecture
# and platform if it's a multi exploit. This automatically takes into
# account any require stack adjustments.
#
def payload_prepend(payload_module = self.payload_instance)
if (target and target.payload_prepend)
p = target.payload_prepend
else
p = payload_info['Prepend'] || ''
end
stack_adjustment + p
end
#
# Return any text that should be appended to the payload. The payload
# module is passed so that the exploit can take a guess at architecture
# and platform if it's a multi exploit.
#
def payload_append(payload_module = self.payload_instance)
if (target and target.payload_append)
target.payload_append
else
payload_info['Append'] || ''
end
end
#
# Return any text that should be prepended to the encoder of the payload.
# The payload module is passed so that the exploit can take a guess
# at architecture and platform if it's a multi exploit.
#
def payload_prepend_encoder(payload_module = self.payload_instance)
if (target and target.payload_prepend_encoder)
p = target.payload_prepend_encoder
else
p = payload_info['PrependEncoder'] || ''
end
p
end
#
# Maximum number of nops to use as a hint to the framework.
# Nil signifies that the framework should decide.
#
def payload_max_nops
if (target and target.payload_max_nops)
target.payload_max_nops
else
payload_info['MaxNops'] || nil
end
end
#
# Minimum number of nops to use as a hint to the framework.
# Nil snigifies that the framework should decide.
#
def payload_min_nops
if (target and target.payload_min_nops)
target.payload_min_nops
else
payload_info['MinNops'] || nil
end
end
#
# Returns the maximum amount of room the exploit has for a payload.
#
def payload_space
if (target and target.payload_space)
target.payload_space
elsif (payload_info['Space'])
payload_info['Space'].to_i
else
nil
end
end
#
# Returns the bad characters that cannot be in any payload used by this
# exploit.
#
def payload_badchars
if (target and target.payload_badchars)
target.payload_badchars
else
payload_info['BadChars']
end
end
#
# Returns the payload encoder type that is associated with either the
# current target of the exploit in general.
#
def payload_encoder_type
if (target and target.payload_encoder_type)
target.payload_encoder_type
else
payload_info['EncoderType']
end
end
#
# Returns the payload encoder option hash that is used to initialize the
# datastore of the encoder that is selected when generating an encoded
# payload.
#
def payload_encoder_options
if (target and target.payload_encoder_options)
target.payload_encoder_options
else
payload_info['EncoderOptions']
end
end
#
# Returns the payload extended options hash which is used to provide
# a location to store extended information that may be useful to
# a particular type of payload or mixin.
#
def payload_extended_options
if target and target.payload_extended_options
target.payload_extended_options
else
payload_info['ExtendedOptions']
end
end
##
#
# NOP requirements
#
# Hints to the nop generator on how it should perform if it's used.
#
##
#
# Returns the list of registers that the NOP generator should save,
# if any. It will use the current target's save registers in precedence
# over those defined globally for the exploit module.
#
# If there are no save registers, nil is returned.
#
def nop_save_registers
if (target and target.save_registers)
return target.save_registers
else
return module_info['SaveRegisters']
end
end
#
# Returns the first compatible NOP generator for this exploit's payload
# instance.
#
def nop_generator
return nil if (!payload_instance)
payload_instance.compatible_nops.each { |nopname, nopmod|
return nopmod.new
}
end
#
# Generates a nop sled of a supplied length and returns it to the caller.
#
def make_nops(count)
nop_sled = nil
# If there is no payload instance then we can't succeed.
return nil if (!payload_instance)
payload_instance.compatible_nops.each { |nopname, nopmod|
# Create an instance of the nop module
nop = nopmod.new
# The list of save registers
save_regs = nop_save_registers || []
if (save_regs.empty? == true)
save_regs = nil
end
begin
nop.copy_ui(self)
nop_sled = nop.generate_sled(count,
'BadChars' => payload_badchars || '',
'SaveRegisters' => save_regs)
rescue
wlog("#{self.refname}: Nop generator #{nop.refname} failed to generate sled for exploit: #{$!}",
'core', LEV_0)
end
break
}
nop_sled
end
##
#
# Utility methods for generating random text that implicitly uses the
# exploit's bad character set.
#
##
#
# Generate random text characters avoiding the exploit's bad
# characters.
#
def rand_text(length)
Rex::Text.rand_text(length, payload_badchars)
end
#
# Generate random english-like avoiding the exploit's bad
# characters.
#
def rand_text_english(length)
Rex::Text.rand_text_english(length, payload_badchars)
end
#
# Generate random alpha characters avoiding the exploit's bad
# characters.
#
def rand_text_alpha(length)
Rex::Text.rand_text_alpha(length, payload_badchars)
end
#
# Generate random alpha upper characters avoiding the exploit's bad
# characters.
#
def rand_text_alpha_upper(length)
Rex::Text.rand_text_alpha_upper(length, payload_badchars)
end
#
# Generate random alphan lower characters avoiding the exploit's bad
# characters.
#
def rand_text_alpha_lower(length)
Rex::Text.rand_text_alpha_lower(length, payload_badchars)
end
#
# Generate random alphanumeric characters avoiding the exploit's bad
# characters.
#
def rand_text_alphanumeric(length)
Rex::Text.rand_text_alphanumeric(length, payload_badchars)
end
#
# Generate a non-repeating static random string
#
def pattern_create(length)
Rex::Text.pattern_create(length)
end
#
# The default "wait for session" delay is zero for all exploits.
#
def wfs_delay
(datastore['WfsDelay'] || 0).to_i
end
##
#
# Handler interaction
#
##
#
# Passes the connection to the associated payload handler to see if the
# exploit succeeded and a connection has been established. The return
# value can be one of the Handler::constants.
#
def handler(*args)
return self.payload_instance.handler(*args) if (self.payload_instance)
end
##
#
# Session tracking
#
##
#
# This is called by the payload when a new session is created
#
def on_new_session(session)
self.session_count += 1
end
#
# A boolean for whether a session has been created yet
#
def session_created?
(self.session_count > 0) ? true : false
end
#
# Reset the session counter to zero (which occurs during set up of the
# exploit prior to calling exploit).
#
def reset_session_counts
self.session_count = 0
end
##
#
# Attributes
#
##
#
# The list of targets.
#
attr_reader :targets
#
# The default target.
#
attr_reader :default_target
#
# The payload requirement hash.
#
attr_reader :payload_info
#
# The active payload instance.
#
attr_accessor :payload_instance
#
# The encoded payload instance. An instance of an
# EncodedPayload object.
#
attr_accessor :payload
#
# The number of active sessions created by this instance
#
attr_reader :session_count
protected
#
# Writable copy of the list of targets.
#
attr_writer :targets
#
# Writable copy of the default target.
#
attr_writer :default_target
#
# Writable copy of the payload requirement hash.
#
attr_writer :payload_info
#
# Number of sessions created by this exploit instance.
#
attr_writer :session_count
#
# Maximum number of seconds for active handlers
#
attr_accessor :active_timeout
#
# Overrides the base class method and serves to initialize default
# compatibilities for exploits
#
def init_compat
super
#
# Merge in payload compatible defaults
#
p = module_info['Compat']['Payload']
CompatDefaults::Payload.each_pair { |k,v|
(p[k]) ? p[k] << " #{v}" : p[k] = v
}
#
# Set the default save registers if none have been explicitly
# specified.
#
if (module_info['SaveRegisters'] == nil)
module_info['SaveRegisters'] = [ 'esp', 'ebp' ]
end
end
end
end