# -*- coding: binary -*-

###
#
# This module exposes a simple method to create an payload in an executable.
#
###

module Msf
module Exploit::EXE

  def initialize(info = {})
    super

    # NOTE: Any new options here should also be dealt with in
    # EncodedPayload#encoded_exe in lib/msf/core/encoded_payload.rb
    register_advanced_options(
      [
        OptBool.new('EXE::EICAR',    [false, 'Generate an EICAR file instead of regular payload exe']),
        OptPath.new('EXE::Custom',   [false, 'Use custom exe instead of automatically generating a payload exe']),
        OptPath.new('EXE::Path',     [false, 'The directory in which to look for the executable template']),
        OptPath.new('EXE::Template', [false, 'The executable template file name.']),
        OptBool.new('EXE::Inject',   [false, 'Set to preserve the original EXE function']),
        OptBool.new('EXE::OldMethod',[false, 'Set to use the substitution EXE generation method.']),
        OptBool.new('EXE::FallBack', [false, 'Use the default template in case the specified one is missing']),
        OptBool.new('MSI::EICAR',    [false, 'Generate an EICAR file instead of regular payload msi']),
        OptPath.new('MSI::Custom',   [false, 'Use custom msi instead of automatically generating a payload msi']),
        OptPath.new('MSI::Path',     [false, 'The directory in which to look for the msi template']),
        OptPath.new('MSI::Template', [false, 'The msi template file name']),
        OptBool.new('MSI::UAC',      [false, 'Create an MSI with a UAC prompt (elevation to SYSTEM if accepted)'])
      ], self.class)
  end

  # Avoid stating the string directly, don't want to get caught by local
  # antivirus!
  def get_eicar_exe
    obfus_eicar = ["x5o!p%@ap[4\\pzx54(p^)7cc)7}$eicar", "standard", "antivirus", "test", "file!$h+h*"]
    obfus_eicar.join("-").upcase
  end

  def get_custom_exe(path = nil)
    path ||= datastore['EXE::Custom']
    print_status("Using custom payload #{path}, no handler will be created!")
    datastore['DisablePayloadHandler'] = true
    exe = nil
    ::File.open(path,'rb') {|f| exe = f.read(f.stat.size)}
    exe
  end


  # Returns an executable.
  #
  # @param opts [Hash]
  # @option opts [String] :code Payload
  # @option opts [Array] :arch Architecture
  # @option opts [Msf::Module::PlatformList] :platform
  # @raise [Msf::NoCompatiblePayloadError] When #genereate_payload_exe fails to generate a payload.
  # @return [String]
  def generate_payload_exe(opts = {})
    return get_custom_exe unless datastore['EXE::Custom'].to_s.strip.empty?
    return get_eicar_exe if datastore['EXE::EICAR']

    exe_init_options(opts)

    pl = opts[:code]
    pl ||= payload.encoded

    # Fall back to x86...
    opts[:arch] = [ARCH_X86] if !opts[:arch] || opts[:arch].length < 1

    # Ensure we have an array
    opts[:arch] = [opts[:arch]] unless opts[:arch].kind_of? Array

    # Transform the PlatformList
    if opts[:platform].kind_of? Msf::Module::PlatformList
      opts[:platform] = opts[:platform].platforms
    end

    exe = Msf::Util::EXE.to_executable(framework, opts[:arch], opts[:platform], pl, opts)

    unless exe
      raise Msf::NoCompatiblePayloadError, "Failed to generate an executable payload due to an invalid platform or arch."
    end

    exe_post_generation(opts)
    exe
  end

  def generate_payload_exe_service(opts = {})
    return get_custom_exe unless datastore['EXE::Custom'].to_s.strip.empty?
    return get_eicar_exe if datastore['EXE::EICAR']

    exe_init_options(opts)

    # NOTE: Only Windows is supported here.
    pl = opts[:code]
    pl ||= payload.encoded

    #Ensure opts[:arch] is an array
    opts[:arch] = [opts[:arch]] unless opts[:arch].kind_of? Array

    if opts[:arch] && opts[:arch].index(ARCH_X64)
      exe = Msf::Util::EXE.to_win64pe_service(framework, pl, opts)
    else
      exe = Msf::Util::EXE.to_win32pe_service(framework, pl, opts)
    end

    exe_post_generation(opts)
    exe
  end

  def generate_payload_dll(opts = {})
    return get_custom_exe unless datastore['EXE::Custom'].to_s.strip.empty?
    return get_eicar_exe if datastore['EXE::EICAR']

    exe_init_options(opts)
    plat = opts[:platform]
    pl = opts[:code]
    pl ||= payload.encoded

    #Ensure opts[:arch] is an array
    opts[:arch] = [opts[:arch]] unless opts[:arch].kind_of? Array

    # NOTE: Only x86_64 linux is supported here.
    if plat.index(Msf::Module::Platform::Linux)
      if opts[:arch] && opts[:arch].index(ARCH_X64)
        dll = Msf::Util::EXE.to_linux_x64_elf_dll(framework, pl,opts)
      elsif opts[:arch] && opts[:arch].index(ARCH_AARCH64)
        dll = Msf::Util::EXE.to_linux_aarch64_elf_dll(framework, pl,opts)
      end
    elsif plat.index(Msf::Module::Platform::Windows)
      if opts[:arch] && opts[:arch].index(ARCH_X64)
        dll = Msf::Util::EXE.to_win64pe_dll(framework, pl, opts)
      else
        dll = Msf::Util::EXE.to_win32pe_dll(framework, pl, opts)
      end
    end

    exe_post_generation(opts)
    dll
  end

  def generate_payload_dccw_gdiplus_dll(opts = {})
    return get_custom_exe unless datastore['EXE::Custom'].to_s.strip.empty?
    return get_eicar_exe if datastore['EXE::EICAR']

    exe_init_options(opts)
    plat = opts[:platform]
    pl = opts[:code]

    pl ||= payload.encoded

    #Ensure opts[:arch] is an array
    opts[:arch] = [opts[:arch]] unless opts[:arch].kind_of? Array
    if opts[:arch] && opts[:arch].index(ARCH_X64)
      dll = Msf::Util::EXE.to_win64pe_dccw_gdiplus_dll(framework, pl, opts)
    else
      dll = Msf::Util::EXE.to_win32pe_dccw_gdiplus_dll(framework, pl, opts)
    end

    exe_post_generation(opts)
    dll
  end

  def generate_payload_msi(opts = {})
    return get_custom_exe(datastore['MSI::Custom']) unless datastore['MSI::Custom'].to_s.strip.empty?
    return get_eicar_exe if datastore['MSI::EICAR']

    exe = generate_payload_exe(opts)

    opts.merge! ({
        :msi_template => datastore['MSI::Template'],
        :msi_template_path => datastore['MSI::Path'],
        :uac => datastore['MSI::UAC']
    })

    Msf::Util::EXE.to_exe_msi(framework, exe, opts)
  end

protected
  def exe_init_options(opts)
    opts.merge!(
      {
        :template_path => datastore['EXE::Path'],
        :template => datastore['EXE::Template'],
        :inject => datastore['EXE::Inject'],
        :fallback => datastore['EXE::FallBack'],
        :sub_method => datastore['EXE::OldMethod']
      })

    # NOTE: If code and platform/arch are supplied, we use those values and skip initialization.
    #
    # This part is kind of tricky so we need to explain the logic behind the following load order.
    # First off, platform can be seen from different sources:
    #
    # 1. From the opts argument. For example: When you are using generate_payload_exe, and you want
    #    to set a specific platform. This is the most explicit. So we check first.
    #
    # 2. From the metadata of a payload module. Normally, a payload module should include the platform
    #    information, with the exception of some generic payloads. For example: generic/shell_reverse_tcp.
    #    This is the most trusted source.
    #
    # 3. From the exploit module's target.
    #
    # 4. From the exploit module's metadata.
    #
    # Architecture shares the same load order.

    unless opts[:code] && opts[:platform]
      if self.respond_to?(:payload_instance) && payload_instance.platform.platforms != [Msf::Module::Platform]
        opts[:platform] = payload_instance.platform
      elsif self.respond_to? :target_platform
        opts[:platform] = target_platform
      elsif self.respond_to? :platform
        opts[:platform] = platform
      end
    end

    unless opts[:code] && opts[:arch]
      if self.respond_to? :payload_instance
        opts[:arch] = payload_instance.arch
      elsif self.respond_to? :target_arch
        opts[:arch] = target_arch
      elsif self.respond_to? :arch
        opts[:arch] = arch
      end
    end
  end

  def exe_post_generation(opts)
    if opts[:fellback]
      print_status("Warning: Falling back to default template: #{opts[:fellback]}")
    end
  end

end
end
