5f8181efa7
Payloads without any of the specified badchars will no longer be encoded by default. This should hopefully lead to less surprising results when using simple payloads (especially commands. Things that had incomplete badchar analysis may break as a result, since not everything will be encoded by default anymore. Sorry in advance if they do.
531 lines
16 KiB
Ruby
531 lines
16 KiB
Ruby
# -*- coding: binary -*-
|
|
|
|
require 'msf/core'
|
|
|
|
module Msf
|
|
|
|
###
|
|
#
|
|
# This class wrappers an encoded payload buffer and the means used to create
|
|
# one.
|
|
#
|
|
###
|
|
class EncodedPayload
|
|
|
|
include Framework::Offspring
|
|
|
|
#
|
|
# This method creates an encoded payload instance and returns it to the
|
|
# caller.
|
|
#
|
|
def self.create(pinst, reqs = {})
|
|
# Create the encoded payload instance
|
|
p = EncodedPayload.new(pinst.framework, pinst, reqs)
|
|
|
|
p.generate(reqs['Raw'])
|
|
|
|
return p
|
|
end
|
|
|
|
#
|
|
# Creates an instance of an EncodedPayload.
|
|
#
|
|
def initialize(framework, pinst, reqs)
|
|
self.framework = framework
|
|
self.pinst = pinst
|
|
self.reqs = reqs
|
|
self.space = reqs['Space']
|
|
end
|
|
|
|
#
|
|
# This method generates the full encoded payload and returns the encoded
|
|
# payload buffer.
|
|
#
|
|
# @return [String] The encoded payload.
|
|
def generate(raw = nil)
|
|
self.raw = raw
|
|
self.encoded = nil
|
|
self.nop_sled_size = 0
|
|
self.nop_sled = nil
|
|
self.encoder = nil
|
|
self.nop = nil
|
|
|
|
# Increase thread priority as necessary. This is done
|
|
# to ensure that the encoding and sled generation get
|
|
# enough time slices from the ruby thread scheduler.
|
|
priority = Thread.current.priority
|
|
|
|
if (priority == 0)
|
|
Thread.current.priority = 1
|
|
end
|
|
|
|
begin
|
|
# First, validate
|
|
pinst.validate()
|
|
|
|
# Tell the payload how much space is available
|
|
pinst.available_space = self.space
|
|
|
|
# Generate the raw version of the payload first
|
|
generate_raw() if self.raw.nil?
|
|
|
|
# If encoder is set, it could be an encoders list
|
|
# The form is "<encoder>:<iteration>, <encoder2>:<iteration>"...
|
|
if reqs['Encoder']
|
|
encoder_str = reqs['Encoder']
|
|
encoder_str.scan(/([^:, ]+):?([^,]+)?/).map do |encoder_opt|
|
|
reqs['Encoder'] = encoder_opt[0]
|
|
|
|
self.iterations = (encoder_opt[1] || reqs['Iterations']).to_i
|
|
self.iterations = 1 if self.iterations < 1
|
|
|
|
# Encode the payload with every encoders in the list
|
|
encode()
|
|
# Encoded payload is now the raw payload to be encoded by the next encoder
|
|
self.raw = self.encoded
|
|
end
|
|
else
|
|
self.iterations = reqs['Iterations'].to_i
|
|
self.iterations = 1 if self.iterations < 1
|
|
# No specified encoder, let BadChars or ForceEncode do their job
|
|
encode()
|
|
end
|
|
|
|
# Build the NOP sled
|
|
generate_sled()
|
|
|
|
# Finally, set the complete payload definition
|
|
self.encoded = (self.nop_sled || '') + self.encoded
|
|
ensure
|
|
# Restore the thread priority
|
|
Thread.current.priority = priority
|
|
end
|
|
|
|
# Return the complete payload
|
|
return encoded
|
|
end
|
|
|
|
#
|
|
# Generates the raw payload from the payload instance. This populates the
|
|
# {#raw} attribute.
|
|
#
|
|
# @return [String] The raw, unencoded payload.
|
|
def generate_raw
|
|
self.raw = (reqs['Prepend'] || '') + pinst.generate_complete + (reqs['Append'] || '')
|
|
|
|
# If an encapsulation routine was supplied, then we should call it so
|
|
# that we can get the real raw payload.
|
|
if reqs['EncapsulationRoutine']
|
|
self.raw = reqs['EncapsulationRoutine'].call(reqs, raw)
|
|
end
|
|
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['Encoder'] || reqs['ForceEncode'] || has_chars?(reqs['BadChars'])
|
|
encoders = pinst.compatible_encoders
|
|
|
|
# Make sure the encoder name from the user has the same String#encoding
|
|
# as the framework's list of encoder names so we can compare them later.
|
|
# This is important for when we get input from RPC.
|
|
if reqs['Encoder']
|
|
reqs['Encoder'] = reqs['Encoder'].encode(framework.encoders.keys[0].encoding)
|
|
end
|
|
|
|
# If the caller had a preferred encoder, use this encoder only
|
|
if ((reqs['Encoder']) and (preferred = framework.encoders[reqs['Encoder']]))
|
|
encoders = [ [reqs['Encoder'], preferred] ]
|
|
elsif (reqs['Encoder'])
|
|
wlog("#{pinst.refname}: Failed to find preferred encoder #{reqs['Encoder']}")
|
|
raise NoEncodersSucceededError, "Failed to find preferred encoder #{reqs['Encoder']}"
|
|
end
|
|
|
|
encoders.each { |encname, encmod|
|
|
self.encoder = encmod.new
|
|
self.encoded = nil
|
|
|
|
# If the encoding is requested by an exploit check compatibility
|
|
# options first of all. For the 'generic/none' encoder compatibility
|
|
# options don't apply.
|
|
if (reqs['Exploit'] &&
|
|
!reqs['Exploit'].compatible?(self.encoder) &&
|
|
encname !~ /generic\/none/)
|
|
wlog("#{pinst.refname}: Encoder #{encoder.refname} doesn't match the exploit Compat options",
|
|
'core', LEV_1)
|
|
next
|
|
end
|
|
|
|
# If there is an encoder type restriction, check to see if this
|
|
# encoder matches with what we're searching for.
|
|
if ((reqs['EncoderType']) and
|
|
(self.encoder.encoder_type.split(/\s+/).include?(reqs['EncoderType']) == false))
|
|
wlog("#{pinst.refname}: Encoder #{encoder.refname} is not a compatible encoder type: #{reqs['EncoderType']} != #{self.encoder.encoder_type}",
|
|
'core', LEV_1)
|
|
next
|
|
end
|
|
|
|
# If the exploit did not explicitly request a kind of encoder and
|
|
# the current encoder has a manual ranking, then it should not be
|
|
# considered as a valid encoder. A manual ranking tells the
|
|
# framework that an encoder must be explicitly defined as the
|
|
# encoder of choice for an exploit.
|
|
if ((reqs['EncoderType'].nil?) and
|
|
(reqs['Encoder'].nil?) and
|
|
(self.encoder.rank == ManualRanking))
|
|
wlog("#{pinst.refname}: Encoder #{encoder.refname} is manual ranked and was not defined as a preferred encoder.",
|
|
'core', LEV_1)
|
|
next
|
|
end
|
|
|
|
# If the caller explicitly requires register preservation, make sure
|
|
# that the module in question can handle it. This is mostly used by
|
|
# the stage encoder path.
|
|
if (reqs['ForceSaveRegisters'] and
|
|
reqs['EncoderOptions'] and
|
|
(reqs['EncoderOptions']['SaveRegisters'].to_s.length > 0) and
|
|
(! self.encoder.can_preserve_registers?))
|
|
wlog("#{pinst.refname}: Encoder #{encoder.refname} does not preserve registers and the caller needs #{reqs['EncoderOptions']['SaveRegisters']} preserved.",
|
|
'core', LEV_1)
|
|
next
|
|
end
|
|
|
|
# Import the datastore from payload (and likely exploit by proxy)
|
|
self.encoder.share_datastore(pinst.datastore)
|
|
|
|
# If we have any encoder options, import them into the datastore
|
|
# of the encoder.
|
|
if (reqs['EncoderOptions'])
|
|
self.encoder.datastore.import_options_from_hash(reqs['EncoderOptions'])
|
|
end
|
|
|
|
# Validate the encoder to make sure it's properly initialized.
|
|
begin
|
|
self.encoder.validate
|
|
rescue ::Exception
|
|
wlog("#{pinst.refname}: Failed to validate encoder #{encoder.refname}: #{$!}",
|
|
'core', LEV_1)
|
|
next
|
|
end
|
|
|
|
# Tell the encoder how much space is available
|
|
self.encoder.available_space = self.space
|
|
|
|
eout = self.raw.dup
|
|
|
|
next_encoder = false
|
|
|
|
# Try encoding with the current encoder
|
|
#
|
|
# NOTE: Using more than one iteration may cause successive iterations to switch
|
|
# to using a different encoder.
|
|
#
|
|
1.upto(self.iterations) do |iter|
|
|
err_start = "#{pinst.refname}: iteration #{iter}"
|
|
|
|
begin
|
|
eout = self.encoder.encode(eout, reqs['BadChars'], nil, pinst.platform)
|
|
rescue EncodingError
|
|
wlog("#{err_start}: Encoder #{encoder.refname} failed: #{$!}", 'core', LEV_1)
|
|
dlog("#{err_start}: Call stack\n#{$@.join("\n")}", 'core', LEV_3)
|
|
next_encoder = true
|
|
break
|
|
|
|
rescue ::Exception
|
|
elog("#{err_start}: Broken encoder #{encoder.refname}: #{$!}", 'core', LEV_0)
|
|
dlog("#{err_start}: Call stack\n#{$@.join("\n")}", 'core', LEV_1)
|
|
next_encoder = true
|
|
break
|
|
end
|
|
|
|
# Get the minimum number of nops to use
|
|
min = (reqs['MinNops'] || 0).to_i
|
|
min = 0 if reqs['DisableNops']
|
|
|
|
# Check to see if we have enough room for the minimum requirements
|
|
if ((reqs['Space']) and (reqs['Space'] < eout.length + min))
|
|
wlog("#{err_start}: Encoded payload version is too large (#{eout.length} bytes) with encoder #{encoder.refname}",
|
|
'core', LEV_1)
|
|
next_encoder = true
|
|
break
|
|
end
|
|
|
|
ilog("#{err_start}: Successfully encoded with encoder #{encoder.refname} (size is #{eout.length})",
|
|
'core', LEV_0)
|
|
end
|
|
|
|
next if next_encoder
|
|
|
|
self.encoded = eout
|
|
break
|
|
}
|
|
|
|
# If the encoded payload is nil, raise an exception saying that we
|
|
# suck at life.
|
|
if (self.encoded == nil)
|
|
self.encoder = 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
|
|
unless reqs['BadChars'].blank?
|
|
ilog("#{pinst.refname}: payload contains no badchars, skipping automatic encoding", 'core', LEV_0)
|
|
end
|
|
|
|
self.encoded = raw
|
|
end
|
|
|
|
# Prefix the prepend encoder value
|
|
self.encoded = (reqs['PrependEncoder'] || '') + self.encoded
|
|
self.encoded << (reqs['AppendEncoder'] || '')
|
|
end
|
|
|
|
#
|
|
# Construct a NOP sled if necessary
|
|
#
|
|
def generate_sled
|
|
min = reqs['MinNops'] || 0
|
|
space = reqs['Space']
|
|
pad_nops = reqs['PadNops']
|
|
|
|
self.nop_sled_size = min
|
|
|
|
# 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))
|
|
self.nop_sled_size = reqs['Space'] - self.encoded.length
|
|
end
|
|
|
|
# If the maximum number of NOPs has been exceeded, wrap it back down.
|
|
if ((reqs['MaxNops']) and
|
|
(reqs['MaxNops'] < self.nop_sled_size))
|
|
self.nop_sled_size = reqs['MaxNops']
|
|
end
|
|
|
|
# Check for the DisableNops setting
|
|
self.nop_sled_size = 0 if reqs['DisableNops']
|
|
|
|
# Check for the PadNops setting
|
|
self.nop_sled_size = (pad_nops - self.encoded.length) if reqs['PadNops']
|
|
|
|
# Now construct the actual sled
|
|
if (self.nop_sled_size > 0)
|
|
nops = pinst.compatible_nops
|
|
|
|
# If the caller had a preferred nop, try to find it and prefix it
|
|
if ((reqs['Nop']) and
|
|
(preferred = framework.nops[reqs['Nop']]))
|
|
nops.unshift([reqs['Nop'], preferred ])
|
|
elsif (reqs['Nop'])
|
|
wlog("#{pinst.refname}: Failed to find preferred nop #{reqs['Nop']}")
|
|
end
|
|
|
|
nops.each { |nopname, nopmod|
|
|
# Create an instance of the nop module
|
|
self.nop = nopmod.new
|
|
|
|
# Propagate options from the payload and possibly exploit
|
|
self.nop.share_datastore(pinst.datastore)
|
|
|
|
# The list of save registers
|
|
save_regs = (reqs['SaveRegisters'] || []) + (pinst.save_registers || [])
|
|
|
|
if (save_regs.empty? == true)
|
|
save_regs = nil
|
|
end
|
|
|
|
begin
|
|
nop.copy_ui(pinst)
|
|
self.nop_sled = nop.generate_sled(self.nop_sled_size,
|
|
'BadChars' => reqs['BadChars'],
|
|
'SaveRegisters' => save_regs)
|
|
|
|
if nop_sled && nop_sled.length == nop_sled_size
|
|
break
|
|
else
|
|
dlog("#{pinst.refname}: Nop generator #{nop.refname} failed to generate sled for payload", 'core', LEV_1)
|
|
end
|
|
rescue
|
|
dlog("#{pinst.refname}: Nop generator #{nop.refname} failed to generate sled for payload: #{$!}",
|
|
'core', LEV_1)
|
|
|
|
self.nop = nil
|
|
end
|
|
}
|
|
|
|
if (self.nop_sled == nil)
|
|
raise NoNopsSucceededError,
|
|
"#{pinst.refname}: All NOP generators failed to construct sled for.",
|
|
caller
|
|
end
|
|
else
|
|
self.nop_sled = ''
|
|
end
|
|
|
|
return self.nop_sled
|
|
end
|
|
|
|
|
|
#
|
|
# Convert the payload to an executable appropriate for its arch and
|
|
# platform.
|
|
#
|
|
# +opts+ are passed directly to +Msf::Util::EXE.to_executable+
|
|
#
|
|
# see +Msf::Exploit::EXE+
|
|
#
|
|
def encoded_exe(opts={})
|
|
# Ensure arch and platform are in the format that to_executable expects
|
|
if opts[:arch] and not opts[:arch].kind_of? Array
|
|
opts[:arch] = [ opts[:arch] ]
|
|
end
|
|
if (opts[:platform].kind_of? Msf::Module::PlatformList)
|
|
opts[:platform] = opts[:platform].platforms
|
|
end
|
|
|
|
emod = pinst.assoc_exploit if pinst.respond_to? :assoc_exploit
|
|
|
|
if emod
|
|
if (emod.datastore["EXE::Custom"] and emod.respond_to? :get_custom_exe)
|
|
return emod.get_custom_exe
|
|
end
|
|
# This is a little ghetto, grabbing datastore options from the
|
|
# associated exploit, but it doesn't really make sense for the
|
|
# payload to have exe options if the exploit doesn't need an exe.
|
|
# Msf::Util::EXE chooses reasonable defaults if these aren't given,
|
|
# so it's not that big of an issue.
|
|
opts.merge!({
|
|
:template_path => emod.datastore['EXE::Path'],
|
|
:template => emod.datastore['EXE::Template'],
|
|
:inject => emod.datastore['EXE::Inject'],
|
|
:fallback => emod.datastore['EXE::FallBack'],
|
|
:sub_method => emod.datastore['EXE::OldMethod']
|
|
})
|
|
# Prefer the target's platform/architecture information, but use
|
|
# the exploit module's if no target specific information exists.
|
|
opts[:platform] ||= emod.target_platform if emod.respond_to? :target_platform
|
|
opts[:platform] ||= emod.platform if emod.respond_to? :platform
|
|
opts[:arch] ||= emod.target_arch if emod.respond_to? :target_arch
|
|
opts[:arch] ||= emod.arch if emod.respond_to? :arch
|
|
end
|
|
# Lastly, try the payload's. This always happens if we don't have an
|
|
# associated exploit module.
|
|
opts[:platform] ||= pinst.platform if pinst.respond_to? :platform
|
|
opts[:arch] ||= pinst.arch if pinst.respond_to? :arch
|
|
|
|
Msf::Util::EXE.to_executable(framework, opts[:arch], opts[:platform], encoded, opts)
|
|
end
|
|
|
|
#
|
|
# Generate a jar file containing the encoded payload.
|
|
#
|
|
# Uses the payload's +generate_jar+ method if it is implemented (Java
|
|
# payloads should all have it). Otherwise, converts the payload to an
|
|
# executable and uses Msf::Util::EXE.to_jar to create a jar file that dumps
|
|
# the exe out to a random file name in the system's temporary directory and
|
|
# executes it.
|
|
#
|
|
def encoded_jar(opts={})
|
|
return pinst.generate_jar(opts) if pinst.respond_to? :generate_jar
|
|
|
|
opts[:spawn] ||= pinst.datastore["Spawn"]
|
|
|
|
Msf::Util::EXE.to_jar(encoded_exe(opts), opts)
|
|
end
|
|
|
|
#
|
|
# Similar to +encoded_jar+ but builds a web archive for use in servlet
|
|
# containers such as Tomcat.
|
|
#
|
|
def encoded_war(opts={})
|
|
return pinst.generate_war(opts) if pinst.respond_to? :generate_war
|
|
|
|
Msf::Util::EXE.to_jsp_war(encoded_exe(opts), opts)
|
|
end
|
|
|
|
#
|
|
# An array containing the architecture(s) that this payload was made to run on
|
|
#
|
|
def arch
|
|
if pinst
|
|
pinst.arch
|
|
end
|
|
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
|
|
#
|
|
# The encoder that was used
|
|
#
|
|
attr_reader :encoder
|
|
#
|
|
# The NOP generator that was used
|
|
#
|
|
attr_reader :nop
|
|
#
|
|
# The number of encoding iterations used
|
|
#
|
|
attr_reader :iterations
|
|
#
|
|
# The maximum number of bytes acceptable for the encoded payload
|
|
#
|
|
attr_reader :space
|
|
protected
|
|
|
|
attr_writer :raw # :nodoc:
|
|
attr_writer :encoded # :nodoc:
|
|
attr_writer :nop_sled_size # :nodoc:
|
|
attr_writer :nop_sled # :nodoc:
|
|
attr_writer :payload # :nodoc:
|
|
attr_writer :encoder # :nodoc:
|
|
attr_writer :nop # :nodoc:
|
|
attr_writer :iterations # :nodoc:
|
|
attr_writer :space # :nodoc
|
|
|
|
#
|
|
# The payload instance used to generate the payload
|
|
#
|
|
attr_accessor :pinst
|
|
#
|
|
# The requirements used for generation
|
|
#
|
|
attr_accessor :reqs
|
|
|
|
def has_chars?(chars)
|
|
return false if chars.blank? || self.raw.blank?
|
|
|
|
chars.each_byte do |bad|
|
|
return true if self.raw.index(bad.chr(Encoding::ASCII_8BIT))
|
|
end
|
|
|
|
false
|
|
end
|
|
end
|
|
|
|
end
|