Files
metasploit-gs/lib/msf/core/payload.rb
T
Adam Cammack e8dfffdcc0 Remove unused payload compatibility code
This original version of the convention checker has not been used in
quite some time, now all of that is covered in
lib/msf/core/module/compatibility.rb
2020-11-17 10:01:43 -06:00

681 lines
18 KiB
Ruby

# -*- coding: binary -*-
require 'msf/core'
require 'metasm'
module Msf
###
#
# This class represents the base class for a logical payload. The framework
# automatically generates payload combinations at runtime which are all
# extended for this Payload as a base class.
#
###
class Payload < Msf::Module
require 'rex/payloads'
require 'msf/core/payload/single'
require 'msf/core/payload/generic'
require 'msf/core/payload/stager'
# Platform specific includes
require 'msf/core/payload/aix'
require 'msf/core/payload/bsd'
require 'msf/core/payload/linux'
require 'msf/core/payload/osx'
require 'msf/core/payload/solaris'
require 'msf/core/payload/windows'
require 'msf/core/payload/netware'
require 'msf/core/payload/java'
require 'msf/core/payload/android'
require 'msf/core/payload/firefox'
require 'msf/core/payload/mainframe'
require 'msf/core/payload/hardware'
require 'metasploit/framework/compiler/mingw'
# Universal payload includes
require 'msf/core/payload/multi'
##
#
# Payload types
#
##
module Type
#
# Single payload type. These types of payloads are self contained and
# do not go through any staging.
#
Single = (1 << 0)
#
# The stager half of a staged payload. Its responsibility in life is to
# read in the stage and execute it.
#
Stager = (1 << 1)
#
# The stage half of a staged payload. This payload performs whatever
# arbitrary task it's designed to do, possibly making use of the same
# connection that the stager used to read the stage in on, if
# applicable.
#
Stage = (1 << 2)
end
#
# Creates an instance of a payload module using the supplied information.
#
def initialize(info = {})
super
self.can_cleanup = true
#
# Gets the Dependencies if the payload requires external help
# to work
#
self.module_info['Dependencies'] = self.module_info['Dependencies'] || []
# If this is a staged payload but there is no stage information,
# then this is actually a stager + single combination. Set up the
# information hash accordingly.
if self.class.include?(Msf::Payload::Single) and
self.class.include?(Msf::Payload::Stager)
self.module_info['Stage'] = {}
if self.module_info['Payload']
self.module_info['Stage']['Payload'] = self.module_info['Payload']['Payload'] || ""
self.module_info['Stage']['Assembly'] = self.module_info['Payload']['Assembly'] || ""
self.module_info['Stage']['Offsets'] = self.module_info['Payload']['Offsets'] || {}
else
self.module_info['Stage']['Payload'] = ""
self.module_info['Stage']['Assembly'] = ""
self.module_info['Stage']['Offsets'] = {}
end
@staged = true
else
@staged = false
end
# Update the module info hash with the connection type
# that is derived from the handler for this payload. This is
# used for compatibility filtering purposes.
self.module_info['ConnectionType'] = connection_type
end
##
#
# Accessors
#
##
#
# Returns MODULE_PAYLOAD to indicate that this is a payload module.
#
def self.type
return Msf::MODULE_PAYLOAD
end
#
# Returns MODULE_PAYLOAD to indicate that this is a payload module.
#
def type
return Msf::MODULE_PAYLOAD
end
#
# Returns the string of bad characters for this payload, if any.
#
def badchars
return self.module_info['BadChars']
end
#
# The list of registers that should be saved by any NOP generators or
# encoders, if possible.
#
def save_registers
return self.module_info['SaveRegisters']
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 string version of the payload type
#
def payload_type_s
case payload_type
when Type::Stage
return "stage"
when Type::Stager
return "stager"
when Type::Single
return "single"
else
return "unknown"
end
end
#
# This method returns whether or not this payload uses staging.
#
def staged?
(@staged or payload_type == Type::Stager or payload_type == Type::Stage)
end
#
# This method returns an optional cached size value
#
def self.cached_size
csize = (const_defined?('CachedSize')) ? const_get('CachedSize') : nil
csize == :dynamic ? nil : csize
end
#
# This method returns whether the payload generates variable-sized output
#
def self.dynamic_size?
csize = (const_defined?('CachedSize')) ? const_get('CachedSize') : nil
csize == :dynamic
end
#
# This method returns an optional cached size value
#
def cached_size
self.class.cached_size
end
#
# This method returns whether the payload generates variable-sized output
#
def dynamic_size?
self.class.dynamic_size?
end
#
# Returns the payload's size. If the payload is staged, the size of the
# first stage is returned.
#
def size
pl = nil
begin
pl = generate()
rescue Metasploit::Framework::Compiler::Mingw::UncompilablePayloadError
rescue NoCompatiblePayloadError
rescue PayloadItemSizeError
end
pl ||= ''
pl.length
end
#
# Returns the raw payload that has not had variable substitution occur.
#
def payload
return module_info['Payload'] ? module_info['Payload']['Payload'] : nil
end
#
# Returns the assembly string that describes the payload if one exists.
#
def assembly
return module_info['Payload'] ? module_info['Payload']['Assembly'] : nil
end
#
# Sets the assembly string that describes the payload
# If this method is used to define the payload, a payload with no offsets will be created
#
def assembly=(asm)
module_info['Payload'] ||= {'Offsets' => {} }
module_info['Payload']['Assembly'] = asm
end
#
# Returns the offsets to variables that must be substitute, if any.
#
def offsets
return module_info['Payload'] ? module_info['Payload']['Offsets'] : nil
end
#
# Returns the compiler dependencies if the payload has one
#
def dependencies
module_info['Dependencies']
end
#
# Returns the staging convention that the payload uses, if any. This is
# used to make sure that only compatible stagers and stages are built
# (where assumptions are made about register/environment initialization
# state and hand-off).
#
def convention
module_info['Convention']
end
#
# Returns the module's connection type, such as reverse, bind, noconn,
# or whatever else the case may be.
#
def connection_type
handler_klass.general_handler_type
end
#
# Returns the method used by the payload to resolve symbols for the purpose
# of calling functions, such as ws2ord.
#
def symbol_lookup
module_info['SymbolLookup']
end
#
# Return the connection associated with this payload, or none if there
# isn't one.
#
def handler_klass
return module_info['Handler'] || Msf::Handler::None
end
#
# Returns the session class that is associated with this payload and will
# be used to create a session as necessary.
#
def session
return module_info['Session']
end
##
#
# Generation & variable substitution
#
##
#
# Generates the payload and returns the raw buffer to the caller.
#
def generate
internal_generate
end
#
# Generates the payload and returns the raw buffer to the caller,
# handling any post-processing tasks, such as prepended code stubs.
def generate_complete
apply_prepends(generate)
end
#
# Convert raw bytes to metasm-ready 'db' encoding format
# eg. "\x90\xCC" => "db 0x90,0xCC"
#
# @param raw [Array] Byte array to encode.
#
def raw_to_db(raw)
raw.unpack("C*").map {|c| "0x%.2x" % c}.join(",")
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.
#
# Supported packing types:
#
# - ADDR (foo.com, 1.2.3.4)
# - ADDR6 (foo.com, fe80::1234:5678:8910:1234)
# - ADDR16MSB, ADD16LSB, ADDR22MSB, ADD22LSB (foo.com, 1.2.3.4)
# Advanced packing types for 16/16 and 22/10 bits substitution. The 16
# bits types uses two offsets indicating where the 16 bits pair will be
# substituted, while the 22 bits types uses two offsets indicating the
# instructions where the 22/10 bits pair will be substituted. Normally
# these are offsets to "sethi" and "or" instructions on SPARC architecture.
# - HEX (0x12345678, "\x41\x42\x43\x44")
# - RAW (raw bytes)
#
def substitute_vars(raw, offsets)
offsets.each_pair { |name, info|
offset, pack = info
# Give the derived class a chance to substitute this variable
next if (replace_var(raw, name, offset, pack) == true)
# Now it's our turn...
if ((val = datastore[name]))
if (pack == 'ADDR')
val = Rex::Socket.resolv_nbo(val)
# Someone gave us a funky address (ipv6?)
if(val.length == 16)
raise RuntimeError, "IPv6 address specified for IPv4 payload."
end
elsif (pack == 'ADDR6')
val = Rex::Socket.resolv_nbo(val)
# Convert v4 to the v6ish address
if(val.length == 4)
nip = "fe80::5efe:" + val.unpack("C*").join(".")
val = Rex::Socket.resolv_nbo(nip)
end
elsif (['ADDR16MSB', 'ADDR16LSB', 'ADDR22MSB', 'ADDR22LSB'].include?(pack))
val = Rex::Socket.resolv_nbo(val)
# Someone gave us a funky address (ipv6?)
if(val.length == 16)
raise RuntimeError, "IPv6 address specified for IPv4 payload."
end
elsif (pack == 'RAW')
# Just use the raw value...
else
# Check to see if the value is a hex string. If so, convert
# it.
if val.kind_of?(String)
if val =~ /^\\x/n
val = [ val.gsub(/\\x/n, '') ].pack("H*").unpack(pack)[0]
elsif val =~ /^0x/n
val = val.hex
end
end
# NOTE:
# Packing assumes integer format at this point, should fix...
val = [ val.to_i ].pack(pack)
end
# Substitute it
if (['ADDR16MSB', 'ADDR16LSB'].include?(pack))
if (offset.length != 2)
raise RuntimeError, "Missing value for payload offset, there must be two offsets."
end
if (pack == 'ADDR16LSB')
val = val.unpack('N').pack('V')
end
raw[offset[0], 2] = val[0, 2]
raw[offset[1], 2] = val[2, 2]
elsif (['ADDR22MSB', 'ADDR22LSB'].include?(pack))
if (offset.length != 2)
raise RuntimeError, "Missing value for payload offset, there must be two offsets."
end
if (pack == 'ADDR22LSB')
val = val.unpack('N').pack('V')
end
hi = (0xfffffc00 & val) >> 10
lo = 0x3ff & val
ins = raw[offset[0], 4]
raw[offset[0], 4] = ins | hi
ins = raw[offset[1], 4]
raw[offset[1], 4] = ins | lo
else
raw[offset, val.length] = val
end
else
wlog("Missing value for payload offset #{name}, skipping.",
'core', LEV_3)
end
}
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, 'Platform' => self.platform) { |name, mod|
encoders << [ name, mod ]
}
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) { |name, mod|
nops << [ name, mod ]
}
return nops
end
#
# A placeholder stub, to be overriden by mixins
#
def apply_prepends(raw)
raw
end
##
#
# Event notifications.
#
##
#
# Once an exploit completes and a session has been created on behalf of the
# payload, the framework will call the payload's on_session notification
# routine to allow it to manipulate the session prior to handing off
# control to the user.
#
def on_session(session)
# If this payload is associated with an exploit, inform the exploit
# that a session has been created and potentially shut down any
# open sockets. This allows active exploits to continue hammering
# on a service until a session is created.
if (assoc_exploit)
# Signal that a new session is created by calling the exploit's
# on_new_session handler. The default behavior is to set an
# instance variable, which the exploit will have to check.
begin
assoc_exploit.on_new_session(session)
rescue ::Exception => e
dlog("#{assoc_exploit.refname}: on_new_session handler triggered exception: #{e.class} #{e} #{e.backtrace}", 'core', LEV_1) rescue nil
end
# Set the abort sockets flag only if the exploit is not passive
# and the connection type is not 'find'
if (
(assoc_exploit.exploit_type == Exploit::Type::Remote) and
(assoc_exploit.passive? == false) and
(connection_type != 'find')
)
assoc_exploit.abort_sockets
end
end
end
#
# This attribute designates if the payload supports onsession()
# method calls (typically to clean up artifacts)
#
attr_accessor :can_cleanup
#
# This attribute holds the string that should be prepended to the buffer
# when it's generated.
#
attr_accessor :prepend
#
# This attribute holds the string that should be appended to the buffer
# when it's generated.
#
attr_accessor :append
#
# This attribute holds the string that should be prepended to the encoded
# version of the payload (in front of the encoder as well).
#
attr_accessor :prepend_encoder
#
# If this payload is associated with an exploit, the assoc_exploit
# attribute will point to that exploit instance.
#
attr_accessor :assoc_exploit
#
# The amount of space available to the payload, which may be nil,
# indicating that the smallest possible payload should be used.
#
attr_accessor :available_space
protected
#
# If the payload has assembly that needs to be compiled, do so now.
#
# Blobs will be cached in the framework's PayloadSet
#
# @see PayloadSet#check_blob_cache
# @param asm [String] Assembly code to be assembled into a raw payload
# @return [String] The final, assembled payload
# @raise ArgumentError if +asm+ is blank
def build(asm, off={})
if(asm.nil? or asm.empty?)
raise ArgumentError, "Assembly must not be empty"
end
# Use the refname so blobs can be flushed when the module gets
# reloaded and use the hash value to ensure that we're actually
# getting the right blob for the given assembly.
cache_key = refname + asm.hash.to_s
cache_entry = framework.payloads.check_blob_cache(cache_key)
off.each_pair { |option, val|
if (val[1] == 'RAW')
asm = asm.gsub(/#{option}/){ datastore[option] }
off.delete(option)
end
}
# If there is a valid cache entry, then we don't need to worry about
# rebuilding the assembly
if cache_entry
# Update the local offsets from the cache
off.each_key { |option|
off[option] = cache_entry[1][option]
}
# Return the cached payload blob
return cache_entry[0].dup
end
# Assemble the payload from the assembly
a = self.arch
if a.kind_of? Array
a = self.arch.first
end
cpu = case a
when ARCH_X86 then Metasm::Ia32.new
when ARCH_X64 then Metasm::X86_64.new
when ARCH_PPC then Metasm::PowerPC.new
when ARCH_ARMLE then Metasm::ARM.new
when ARCH_MIPSLE then Metasm::MIPS.new(:little)
when ARCH_MIPSBE then Metasm::MIPS.new(:big)
else
elog("Broken payload #{refname} has arch unsupported with assembly: #{module_info["Arch"].inspect}")
elog("Call stack:\n#{caller.join("\n")}")
return ""
end
sc = Metasm::Shellcode.assemble(cpu, asm).encoded
# Calculate the actual offsets now that it's been built
off.each_pair { |option, val|
off[option] = [ sc.offset_of_reloc(option) || val[0], val[1] ]
}
# Cache the payload blob
framework.payloads.add_blob_cache(cache_key, sc.data, off)
# Return a duplicated copy of the assembled payload
sc.data.dup
end
#
# Generate the payload using our local payload blob and offsets
#
def internal_generate
# Build the payload, either by using the raw payload blob defined in the
# module or by actually assembling it
if assembly and !assembly.empty?
raw = build(assembly, offsets)
else
raw = payload.dup
end
# If the payload is generated and there are offsets to substitute,
# do that now.
if (raw and offsets)
substitute_vars(raw, offsets)
end
return raw
end
##
#
# Custom merge operations for payloads
#
##
#
# Merge the name to prefix the existing one and separate them
# with a comma
#
def merge_name(info, val)
if (info['Name'])
info['Name'] = val + ',' + info['Name']
else
info['Name'] = val
end
end
end
end