243 lines
8.3 KiB
Ruby
243 lines
8.3 KiB
Ruby
require 'ruby_smb'
|
|
|
|
module Msf
|
|
module Util
|
|
module WindowsRegistry
|
|
|
|
#
|
|
# This module include helpers for the SECURITY hive
|
|
#
|
|
module Security
|
|
|
|
include Msf::Util::WindowsCryptoHelpers
|
|
|
|
# All of these structures were taken from Impacket secretsdump.py
|
|
class CacheData < BinData::Record
|
|
mandatory_parameter :user_name_length
|
|
mandatory_parameter :domain_name_length
|
|
mandatory_parameter :dns_domain_name_length
|
|
mandatory_parameter :upn_length
|
|
mandatory_parameter :effective_name_length
|
|
mandatory_parameter :full_name_length
|
|
mandatory_parameter :logon_script_length
|
|
mandatory_parameter :profile_path_length
|
|
mandatory_parameter :home_directory_length
|
|
mandatory_parameter :home_directory_drive_length
|
|
mandatory_parameter :group_count
|
|
mandatory_parameter :logon_domain_name_length
|
|
|
|
endian :little
|
|
|
|
string :enc_hash, length: 16
|
|
string :unknown, length: 56
|
|
string16 :username, length: -> { user_name_length }, byte_align: 4
|
|
string16 :domain_name, length: -> { domain_name_length }, byte_align: 4
|
|
string16 :dns_domain_name, length: -> { dns_domain_name_length }, byte_align: 4
|
|
string16 :upn, length: -> { upn_length }, byte_align: 4
|
|
string16 :effective_name, length: -> { effective_name_length }, byte_align: 4
|
|
string16 :full_name, length: -> { full_name_length }, byte_align: 4
|
|
string16 :logon_script, length: -> { logon_script_length }, byte_align: 4
|
|
string16 :profile_path, length: -> { profile_path_length }, byte_align: 4
|
|
string16 :home_directory, length: -> { home_directory_length }, byte_align: 4
|
|
string16 :home_directory_drive, length: -> { home_directory_drive_length }, byte_align: 4
|
|
array :groups, initial_length: -> { group_count }, byte_align: 4 do
|
|
uint32 :relative_id
|
|
uint32 :attributes
|
|
end
|
|
string16 :logon_domain_name, length: -> { logon_domain_name_length }, byte_align: 4
|
|
end
|
|
|
|
class CacheEntry < BinData::Record
|
|
endian :little
|
|
|
|
uint16 :user_name_length
|
|
uint16 :domain_name_length
|
|
uint16 :effective_name_length
|
|
uint16 :full_name_length
|
|
uint16 :logon_script_length
|
|
uint16 :profile_path_length
|
|
uint16 :home_directory_length
|
|
uint16 :home_directory_drive_length
|
|
uint32 :user_id
|
|
uint32 :primary_group_id
|
|
uint32 :group_count
|
|
uint16 :logon_domain_name_length
|
|
uint16 :logon_domain_id_length
|
|
file_time :last_access
|
|
uint32 :revision
|
|
uint32 :sid_count
|
|
uint16 :valid
|
|
uint16 :iteration_count
|
|
uint32 :sif_length
|
|
uint32 :logon_package
|
|
uint16 :dns_domain_name_length
|
|
uint16 :upn_length
|
|
string :iv, length: 16
|
|
string :ch, length: 16
|
|
array :enc_data, type: :uint8, read_until: :eof
|
|
end
|
|
|
|
attr_accessor :lsa_vista_style
|
|
|
|
# Retrieve the decrypted LSA secret key from a given BootKey. This also sets
|
|
# the @lsa_vista_style attributes according to the registry keys found
|
|
# under `HKLM\SECURITY\Policy`. If set to `true`, the system version is
|
|
# Windows Vista and above, otherwise it is Windows XP or below.
|
|
#
|
|
# @param boot_key [String] The BootKey
|
|
# @return [String] The decrypted LSA secret key
|
|
def lsa_secret_key(boot_key)
|
|
# vprint_status('Getting PolEKList...')
|
|
_value_type, value_data = get_value('\\Policy\\PolEKList')
|
|
if value_data
|
|
# Vista or above system
|
|
@lsa_vista_style = true
|
|
|
|
lsa_key = decrypt_lsa_data(value_data, boot_key)
|
|
lsa_key = lsa_key[68, 32] unless lsa_key.empty?
|
|
else
|
|
# vprint_status('Getting PolSecretEncryptionKey...')
|
|
_value_type, value_data = get_value('\\Policy\\PolSecretEncryptionKey')
|
|
# If that didn't work, then we're out of luck
|
|
return nil if value_data.nil?
|
|
|
|
# XP or below system
|
|
@lsa_vista_style = false
|
|
|
|
md5x = Digest::MD5.new
|
|
md5x << boot_key
|
|
1000.times do
|
|
md5x << value_data[60, 16]
|
|
end
|
|
|
|
rc4 = OpenSSL::Cipher.new('rc4')
|
|
rc4.decrypt
|
|
rc4.key = md5x.digest
|
|
lsa_key = rc4.update(value_data[12, 48])
|
|
lsa_key << rc4.final
|
|
lsa_key = lsa_key[0x10..0x1F]
|
|
end
|
|
|
|
lsa_key
|
|
end
|
|
|
|
# Returns the decrypted LSA secrets under HKLM\SECURITY\Policy\Secrets. For
|
|
# this, the LSA secret key must be provided, which can be retrieved with
|
|
# the #lsa_secret_key method.
|
|
#
|
|
# @param lsa_key [String] The LSA secret key
|
|
# @return [Hash] A hash containing the LSA secrets.
|
|
def lsa_secrets(lsa_key)
|
|
keys = enum_key('\\Policy\\Secrets')
|
|
return unless keys
|
|
|
|
keys.delete('NL$Control')
|
|
|
|
keys.each_with_object({}) do |key, lsa_secrets|
|
|
# vprint_status("Looking into #{key}")
|
|
_value_type, value_data = get_value("\\Policy\\Secrets\\#{key}\\CurrVal")
|
|
encrypted_secret = value_data
|
|
next unless encrypted_secret
|
|
|
|
if @lsa_vista_style
|
|
decrypted = decrypt_lsa_data(encrypted_secret, lsa_key)
|
|
secret_size = decrypted[0, 4].unpack('L<').first
|
|
secret = decrypted[16, secret_size]
|
|
else
|
|
encrypted_secret_size = encrypted_secret[0, 4].unpack('L<').first
|
|
secret = decrypt_secret_data(encrypted_secret[(encrypted_secret.size - encrypted_secret_size)..-1], lsa_key)
|
|
end
|
|
lsa_secrets[key] = secret
|
|
end
|
|
end
|
|
|
|
# Returns the decrypted NLKM secret key from
|
|
# HKLM\SECURITY\Policy\Secrets\NL$KM\CurrVal. For this, the LSA secret key
|
|
# must be provided, which can be retrieved with the #lsa_secret_key method.
|
|
#
|
|
# @param lsa_key [String] The LSA secret key
|
|
# @return [String] The NLKM secret key
|
|
def nlkm_secret_key(lsa_key)
|
|
_value_type, value_data = get_value('\\Policy\\Secrets\\NL$KM\\CurrVal')
|
|
return nil unless value_data
|
|
|
|
if @lsa_vista_style
|
|
nlkm_dec = decrypt_lsa_data(value_data, lsa_key)
|
|
else
|
|
value_data_size = value_data[0, 4].unpack('L<').first
|
|
nlkm_dec = decrypt_secret_data(value_data[(value_data.size - value_data_size)..-1], lsa_key)
|
|
end
|
|
|
|
nlkm_dec
|
|
end
|
|
|
|
# This structure consolidates Cache data and information, as retrieved by the #cached_infos method
|
|
CacheInfo = Struct.new(
|
|
:name,
|
|
:iteration_count,
|
|
:real_iteration_count,
|
|
:entry, # CacheEntry structure
|
|
:data, # CacheData structure
|
|
keyword_init: true
|
|
)
|
|
|
|
# Returns the decrypted Cache data and information from HKLM\Cache. For
|
|
# this, the NLKM secret key must be provided, which can be retrieved with
|
|
# the #nlkm_secret_key method.
|
|
#
|
|
# @param nlkm_key [String] The NLKM secret key
|
|
# @return [Array] An array of CacheInfo structures containing the Cache information
|
|
def cached_infos(nlkm_key)
|
|
values = enum_values('\\Cache')
|
|
unless values
|
|
elog('[Msf::Util::WindowsRegistry::Sam::cached_hashes] No cashed entries')
|
|
return
|
|
end
|
|
|
|
values.delete('NL$Control')
|
|
|
|
iteration_count = nil
|
|
if values.delete('NL$IterationCount')
|
|
_value_type, value_data = reg_parser.get_value('\\Cache', 'NL$IterationCount')
|
|
iteration_count = value_data.to_i
|
|
end
|
|
|
|
values.map do |value|
|
|
_value_type, value_data = get_value('\\Cache', value)
|
|
cache = CacheEntry.read(value_data)
|
|
|
|
cache_info = CacheInfo.new(name: value, entry: cache)
|
|
|
|
next cache_info unless cache.user_name_length > 0
|
|
|
|
enc_data = cache.enc_data.map(&:chr).join
|
|
if @lsa_vista_style
|
|
dec_data = decrypt_aes(enc_data, nlkm_key[16...32], cache.iv)
|
|
else
|
|
dec_data = decrypt_hash(enc_data, nlkm_key, cache.iv)
|
|
end
|
|
|
|
params = cache.snapshot.to_h.select { |key, _v| key.to_s.end_with?('_length') }
|
|
params[:group_count] = cache.group_count
|
|
cache_data = CacheData.new(params).read(dec_data)
|
|
cache_info.data = cache_data
|
|
|
|
if @lsa_vista_style
|
|
cache_info.iteration_count = iteration_count ? iteration_count : cache.iteration_count
|
|
if (cache_info.iteration_count > 10240)
|
|
cache_info.real_iteration_count = cache_info.iteration_count & 0xfffffc00
|
|
else
|
|
cache_info.real_iteration_count = cache_info.iteration_count * 1024
|
|
end
|
|
end
|
|
|
|
cache_info
|
|
end
|
|
end
|
|
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|