CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
rapid7

CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!

GitHub Repository: rapid7/metasploit-framework
Path: blob/master/lib/msf/util/windows_registry/security.rb
Views: 1904
1
require 'ruby_smb'
2
3
module Msf
4
module Util
5
module WindowsRegistry
6
7
#
8
# This module include helpers for the SECURITY hive
9
#
10
module Security
11
12
include Msf::Util::WindowsCryptoHelpers
13
14
# All of these structures were taken from Impacket secretsdump.py
15
class CacheData < BinData::Record
16
mandatory_parameter :user_name_length
17
mandatory_parameter :domain_name_length
18
mandatory_parameter :dns_domain_name_length
19
mandatory_parameter :upn_length
20
mandatory_parameter :effective_name_length
21
mandatory_parameter :full_name_length
22
mandatory_parameter :logon_script_length
23
mandatory_parameter :profile_path_length
24
mandatory_parameter :home_directory_length
25
mandatory_parameter :home_directory_drive_length
26
mandatory_parameter :group_count
27
mandatory_parameter :logon_domain_name_length
28
29
endian :little
30
31
string :enc_hash, length: 16
32
string :unknown, length: 56
33
string16 :username, length: -> { user_name_length }, byte_align: 4
34
string16 :domain_name, length: -> { domain_name_length }, byte_align: 4
35
string16 :dns_domain_name, length: -> { dns_domain_name_length }, byte_align: 4
36
string16 :upn, length: -> { upn_length }, byte_align: 4
37
string16 :effective_name, length: -> { effective_name_length }, byte_align: 4
38
string16 :full_name, length: -> { full_name_length }, byte_align: 4
39
string16 :logon_script, length: -> { logon_script_length }, byte_align: 4
40
string16 :profile_path, length: -> { profile_path_length }, byte_align: 4
41
string16 :home_directory, length: -> { home_directory_length }, byte_align: 4
42
string16 :home_directory_drive, length: -> { home_directory_drive_length }, byte_align: 4
43
array :groups, initial_length: -> { group_count }, byte_align: 4 do
44
uint32 :relative_id
45
uint32 :attributes
46
end
47
string16 :logon_domain_name, length: -> { logon_domain_name_length }, byte_align: 4
48
end
49
50
class CacheEntry < BinData::Record
51
endian :little
52
53
uint16 :user_name_length
54
uint16 :domain_name_length
55
uint16 :effective_name_length
56
uint16 :full_name_length
57
uint16 :logon_script_length
58
uint16 :profile_path_length
59
uint16 :home_directory_length
60
uint16 :home_directory_drive_length
61
uint32 :user_id
62
uint32 :primary_group_id
63
uint32 :group_count
64
uint16 :logon_domain_name_length
65
uint16 :logon_domain_id_length
66
file_time :last_access
67
uint32 :revision
68
uint32 :sid_count
69
uint16 :valid
70
uint16 :iteration_count
71
uint32 :sif_length
72
uint32 :logon_package
73
uint16 :dns_domain_name_length
74
uint16 :upn_length
75
string :iv, length: 16
76
string :ch, length: 16
77
array :enc_data, type: :uint8, read_until: :eof
78
end
79
80
attr_accessor :lsa_vista_style
81
82
def normalize_key(key)
83
@root.blank? ? key : key.delete_prefix(@root)
84
end
85
86
# Retrieve the decrypted LSA secret key from a given BootKey. This also sets
87
# the @lsa_vista_style attributes according to the registry keys found
88
# under `HKLM\SECURITY\Policy`. If set to `true`, the system version is
89
# Windows Vista and above, otherwise it is Windows XP or below.
90
#
91
# @param boot_key [String] The BootKey
92
# @return [String] The decrypted LSA secret key
93
def lsa_secret_key(boot_key)
94
# vprint_status('Getting PolEKList...')
95
_value_type, value_data = get_value(normalize_key('HKLM\\SECURITY\\Policy\\PolEKList'))
96
if value_data
97
# Vista or above system
98
@lsa_vista_style = true
99
100
lsa_key = decrypt_lsa_data(value_data, boot_key)
101
lsa_key = lsa_key[68, 32] unless lsa_key.empty?
102
else
103
# vprint_status('Getting PolSecretEncryptionKey...')
104
_value_type, value_data = get_value(normalize_key('HKLM\\SECURITY\\Policy\\PolSecretEncryptionKey'))
105
# If that didn't work, then we're out of luck
106
return nil if value_data.nil?
107
108
# XP or below system
109
@lsa_vista_style = false
110
111
md5x = Digest::MD5.new
112
md5x << boot_key
113
1000.times do
114
md5x << value_data[60, 16]
115
end
116
117
rc4 = OpenSSL::Cipher.new('rc4')
118
rc4.decrypt
119
rc4.key = md5x.digest
120
lsa_key = rc4.update(value_data[12, 48])
121
lsa_key << rc4.final
122
lsa_key = lsa_key[0x10..0x1F]
123
end
124
125
lsa_key
126
end
127
128
# Returns the decrypted LSA secrets under HKLM\SECURITY\Policy\Secrets. For
129
# this, the LSA secret key must be provided, which can be retrieved with
130
# the #lsa_secret_key method.
131
#
132
# @param lsa_key [String] The LSA secret key
133
# @return [Hash] A hash containing the LSA secrets.
134
def lsa_secrets(lsa_key)
135
keys = enum_key(normalize_key('HKLM\\SECURITY\\Policy\\Secrets'))
136
return unless keys
137
138
keys.delete('NL$Control')
139
140
keys.each_with_object({}) do |key, lsa_secrets|
141
_value_type, value_data = get_value(normalize_key("HKLM\\SECURITY\\Policy\\Secrets\\#{key}\\CurrVal"))
142
encrypted_secret = value_data
143
next unless encrypted_secret
144
145
if @lsa_vista_style
146
decrypted = decrypt_lsa_data(encrypted_secret, lsa_key)
147
secret_size = decrypted[0, 4].unpack('L<').first
148
secret = decrypted[16, secret_size]
149
else
150
encrypted_secret_size = encrypted_secret[0, 4].unpack('L<').first
151
secret = decrypt_secret_data(encrypted_secret[(encrypted_secret.size - encrypted_secret_size)..-1], lsa_key)
152
end
153
lsa_secrets[key] = secret
154
end
155
end
156
157
# Returns the decrypted NLKM secret key from
158
# HKLM\SECURITY\Policy\Secrets\NL$KM\CurrVal. For this, the LSA secret key
159
# must be provided, which can be retrieved with the #lsa_secret_key method.
160
#
161
# @param lsa_key [String] The LSA secret key
162
# @return [String] The NLKM secret key
163
def nlkm_secret_key(lsa_key)
164
_value_type, value_data = get_value(normalize_key('HKLM\\SECURITY\\Policy\\Secrets\\NL$KM\\CurrVal'))
165
return nil unless value_data
166
167
if @lsa_vista_style
168
nlkm_dec = decrypt_lsa_data(value_data, lsa_key)
169
else
170
value_data_size = value_data[0, 4].unpack('L<').first
171
nlkm_dec = decrypt_secret_data(value_data[(value_data.size - value_data_size)..-1], lsa_key)
172
end
173
174
nlkm_dec
175
end
176
177
# This structure consolidates Cache data and information, as retrieved by the #cached_infos method
178
CacheInfo = Struct.new(
179
:name,
180
:iteration_count,
181
:real_iteration_count,
182
:entry, # CacheEntry structure
183
:data, # CacheData structure
184
keyword_init: true
185
)
186
187
# Returns the decrypted Cache data and information from HKLM\Cache. For
188
# this, the NLKM secret key must be provided, which can be retrieved with
189
# the #nlkm_secret_key method.
190
#
191
# @param nlkm_key [String] The NLKM secret key
192
# @return [Array] An array of CacheInfo structures containing the Cache information
193
def cached_infos(nlkm_key)
194
values = enum_values(normalize_key('HKLM\\SECURITY\\Cache'))
195
unless values
196
elog('[Msf::Util::WindowsRegistry::Sam::cached_hashes] No cashed entries')
197
return
198
end
199
200
values.delete('NL$Control')
201
202
iteration_count = nil
203
if values.delete('NL$IterationCount')
204
_value_type, value_data = reg_parser.get_value(normalize_key('HKLM\\SECURITY\\Cache'), 'NL$IterationCount')
205
iteration_count = value_data.to_i
206
end
207
208
values.map do |value|
209
_value_type, value_data = get_value(normalize_key('HKLM\\SECURITY\\Cache'), value)
210
cache = CacheEntry.read(value_data)
211
212
cache_info = CacheInfo.new(name: value, entry: cache)
213
214
next cache_info unless cache.user_name_length > 0
215
216
enc_data = cache.enc_data.map(&:chr).join
217
if @lsa_vista_style
218
dec_data = decrypt_aes(enc_data, nlkm_key[16...32], cache.iv)
219
else
220
dec_data = decrypt_hash(enc_data, nlkm_key, cache.iv)
221
end
222
223
params = cache.snapshot.to_h.select { |key, _v| key.to_s.end_with?('_length') }
224
params[:group_count] = cache.group_count
225
cache_data = CacheData.new(params).read(dec_data)
226
cache_info.data = cache_data
227
228
if @lsa_vista_style
229
cache_info.iteration_count = iteration_count ? iteration_count : cache.iteration_count
230
if (cache_info.iteration_count > 10240)
231
cache_info.real_iteration_count = cache_info.iteration_count & 0xfffffc00
232
else
233
cache_info.real_iteration_count = cache_info.iteration_count * 1024
234
end
235
end
236
237
cache_info
238
end
239
end
240
241
end
242
end
243
end
244
end
245
246
247