rapid7/metasploit-framework

View on GitHub
lib/msf/core/payload.rb

Summary

Maintainability
F
3 days
Test Coverage
# -*- coding: binary -*-
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 from this Payload as a base class.
#
###
class Payload < Msf::Module


  # Platform specific includes
  require 'metasploit/framework/compiler/mingw'

  # Universal payload includes

  ##
  #
  # 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)

    Adapter = (1 << 3)
  end

  #
  # Creates an instance of a payload module using the supplied information.
  #
  def initialize(info = {})
    super

    #
    # Gets the Dependencies if the payload requires external help
    # to work
    #
    self.module_info['Dependencies'] = self.module_info['Dependencies'] || []

    # If this is an adapted or 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::Adapter) || self.class.include?(Msf::Payload::Single)) and self.class.include?(Msf::Payload::Stager)

      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'] || {}
      elsif !self.module_info['Stage']
        self.module_info['Stage'] = {}
        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::Adapter
        return "adapter"
      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.
  # @param opts [Hash]
  def generate(opts = {})
    internal_generate(opts)
  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

  # Select a reasonable default payload and minimally configure it
  # @param [Msf::Module] mod
  def self.choose_payload(mod)
    compatible_payloads = mod.compatible_payloads(
      excluded_platforms: ['Multi'] # We don't want to select a multi payload
    ).map(&:first)

    # XXX: Determine LHOST based on global LHOST, RHOST or an arbitrary internet address
    lhost = mod.datastore['LHOST'] || Rex::Socket.source_address(mod.datastore['RHOST'] || '50.50.50.50')

    configure_payload = lambda do |payload|
      if mod.datastore.is_a?(Msf::DataStoreWithFallbacks)
        payload_defaults = { 'PAYLOAD' => payload }

        # Set LHOST if this is a reverse payload
        if payload.index('reverse')
          payload_defaults['LHOST'] = lhost
        end
        mod.datastore.import_defaults_from_hash(payload_defaults, imported_by: 'choose_payload')
      else
        mod.datastore['PAYLOAD'] = payload
        # Set LHOST if this is a reverse payload
        if payload.index('reverse')
          mod.datastore['LHOST'] = lhost
        end
      end

      payload
    end

    # If there is only one compatible payload, return it immediately
    if compatible_payloads.length == 1
      return configure_payload.call(compatible_payloads.first)
    end

    # XXX: This approach is subpar, and payloads should really be ranked!
    preferred_payloads = [
      # These payloads are generally reliable and common enough in practice
      '/meterpreter/reverse_tcp',
      '/shell/reverse_tcp',
      'cmd/unix/reverse_bash',
      'cmd/unix/reverse_netcat',
      'cmd/windows/powershell_reverse_tcp',
      # Fall back on a generic payload to autoselect a specific payload
      'generic/shell_reverse_tcp',
      'generic/shell_bind_tcp'
    ]

    # XXX: This is not efficient in the slightest
    preferred_payloads.each do |type|
      payload = compatible_payloads.find { |name| name.end_with?(type) }

      next unless payload

      return configure_payload.call(payload)
    end

    nil
  end

  #
  # A placeholder stub, to be overridden 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 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
  # @param opts [Hash]
  # @return [String] The final, assembled payload
  # @raise ArgumentError if +asm+ is blank
  def build(asm, off={}, opts = {})
    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 = opts[:arch] || 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(opts = {})
    # 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, opts)
    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