1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 """Partial emulation of the Mimikatz tool.
22
23 This code replicates the algorithm first implemented in the mimikatz tool,
24 which can be found here:
25
26 https://github.com/gentilkiwi/mimikatz
27 """
28
29
30
31 __author__ = ("Michael Cohen <scudette@google.com> and "
32 "Francesco Picasso <francesco.picasso@gmail.com>")
33
34 import logging
35
36 from Crypto.Cipher import AES
37 from Crypto.Cipher import DES3
38 from Crypto.Cipher import ARC4
39
40 from rekall import addrspace
41 from rekall import obj
42
43 from rekall.plugins.overlays.windows import pe_vtypes
44 from rekall.plugins.windows import common
45 from rekall.plugins.windows import lsadecryptxp
46
47
48 mimikatz_common_overlays = {
49 '_LSA_UNICODE_STRING': [None, {
50 'Value': lambda x: x.Buffer.dereference_as(
51 'UnicodeString', target_args=dict(length=x.Length)),
52 'Raw': lambda x: x.Buffer.dereference_as(
53 'String', target_args=dict(length=x.Length, term=None)).v(),
54 'RawMax': lambda x: x.Buffer.dereference_as(
55 'String', target_args=dict(length=x.MaximumLength, term=None)).v(),
56 }],
57 '_LSA_STRING': [None, {
58 'Value': lambda x: x.Buffer.dereference_as(
59 'String', target_args=dict(length=x.Length)),
60 'Raw': lambda x: x.Buffer.dereference_as(
61 'String', target_args=dict(length=x.Length, term=None)).v(),
62 'RawMax': lambda x: x.Buffer.dereference_as(
63 'String', target_args=dict(length=x.MaximumLength, term=None)).v(),
64 }],
65 '_LUID': [None, {
66 'Text': lambda x: '{:08x}:{:08x}'.format(x.HighPart, x.LowPart)
67 }],
68 '_SID': [None, {
69 'IdentifierAuthority': [None, ['Enumeration', dict(
70 choices={
71 '\x00\x00\x00\x00\x00\x00': 'Null Authority',
72 '\x00\x00\x00\x00\x00\x01': 'World Authority',
73 '\x00\x00\x00\x00\x00\x02': 'Local Authority',
74 '\x00\x00\x00\x00\x00\x03': 'Creator Authority',
75 '\x00\x00\x00\x00\x00\x04': 'NonUnique Authority',
76 '\x00\x00\x00\x00\x00\x05': 'NT Authority',
77 },
78 target='String',
79 target_args=dict(length=6, term=None)
80 )]],
81 'NumericIdentifier': [0x4, ['unsigned be int']],
82 'SubAuthority': [None, ['Array', dict(
83 target='unsigned long',
84 count=lambda x: x.SubAuthorityCount)]],
85 }],
86 }
87
88
89 -class _SID(obj.Struct):
90 """A Pretty printing implementation of sids.
91
92 Reference:
93 http://www.sekchek.com/downloads/white-papers/windows-about-sids.pdf
94 """
96 """Format the Sid using SDDL Notation."""
97 components = [self.Revision, self.NumericIdentifier]
98 components.extend(self.SubAuthority)
99
100 return u"S-" + u"-".join([str(x) for x in components])
101
102
103 -class Lsasrv(pe_vtypes.BasicPEProfile):
104 """A profile for lsasrv.dll"""
105
106 mimikatz_vtypes = [
107 '_LIST_ENTRY', '_LSA_UNICODE_STRING', '_LUID',
108 '_LSA_STRING', '_MSV1_0_PRIMARY_CREDENTIAL',
109 '_KIWI_BCRYPT_HANDLE_KEY', '_KIWI_HARD_KEY',
110 '_KIWI_MSV1_0_CREDENTIALS', '_KIWI_MSV1_0_PRIMARY_CREDENTIALS',
111 '_KIWI_GENERIC_PRIMARY_CREDENTIAL',
112 '_KIWI_MASTERKEY_CACHE_ENTRY', '_FILETIME']
113
114 windows_vtypes = ['_SID', '_SID_IDENTIFIER_AUTHORITY', '_GUID']
115
116
117 mimikatz_msv_versioned = {
118 5.1: '_KIWI_MSV1_0_LIST_51',
119 5.2: '_KIWI_MSV1_0_LIST_52',
120 6.0: '_KIWI_MSV1_0_LIST_60',
121 6.1: '_KIWI_MSV1_0_LIST_61_ANTI_MIMIKATZ',
122 6.2: '_KIWI_MSV1_0_LIST_62',
123 6.3: '_KIWI_MSV1_0_LIST_63',
124 }
125
126 mimikatz_key_versioned = {
127 5.1: '_KIWI_BCRYPT_KEY',
128 5.2: '_KIWI_BCRYPT_KEY',
129 6.0: '_KIWI_BCRYPT_KEY',
130 6.1: '_KIWI_BCRYPT_KEY',
131 6.2: '_KIWI_BCRYPT_KEY8',
132 6.3: '_KIWI_BCRYPT_KEY81',
133 }
134
135 @classmethod
137 super(cls, Lsasrv).Initialize(profile)
138
139 arch = profile.session.profile.metadata('arch')
140
141 mimikatz_profile = profile.session.LoadProfile('mimikatz/%s' % arch)
142 if not mimikatz_profile:
143 raise IOError('Unable to load mimikatz profile from repository!')
144
145 kwargs = {}
146 for name in cls.mimikatz_vtypes:
147 kwargs[name] = mimikatz_profile.vtypes[name]
148
149 for name in cls.windows_vtypes:
150 kwargs[name] = profile.session.profile.vtypes[name]
151
152 profile.add_types(kwargs)
153
154 profile.add_types({
155 'SIZED_DATA': [lambda x: x.size + 4, {
156 'size': [0, ['unsigned long', {}]],
157 'data': [4, ['String', dict(length=lambda x: x.size)]],
158 }]
159 })
160
161 version = profile.session.profile.metadata('version')
162 if version not in cls.mimikatz_msv_versioned:
163 raise IOError('OS version not supported.')
164
165 profile.add_types({
166 'MSV1_0_LIST': mimikatz_profile.vtypes[
167 cls.mimikatz_msv_versioned[version]]
168 })
169
170 profile.add_types({
171 '_KIWI_BCRYPT_KEY': mimikatz_profile.vtypes[
172 cls.mimikatz_key_versioned[version]]
173 })
174
175 profile.add_classes(_SID=_SID)
176
177 profile.add_overlay(mimikatz_common_overlays)
178
179 profile.add_overlay({
180 '_KIWI_HARD_KEY': [None, {
181 'data': lambda x: x.m('data').cast(
182 'String', term=None, length=x.cbSecret)
183 }],
184 'MSV1_0_LIST': [None, {
185 'List': [0, ['_LIST_ENTRY']],
186 'pSid': [None, ['Pointer', dict(target='_SID')]],
187 'LogonType': [None, ['Enumeration', dict(
188 target='unsigned int',
189 choices={
190 2: 'Interactive',
191 3: 'Network',
192 4: 'Batch',
193 5: 'Service',
194 6: 'Proxy',
195 7: 'Unlock',
196 8: 'NetworkCleartext',
197 9: 'NewCredentials',
198 10: 'RemoteInteractive',
199 11: 'CachedInteractive',
200 12: 'CachedRemoteInteractive',
201 13: 'CachedUnlock',
202 },
203 )]],
204 }],
205 '_MSV1_0_PRIMARY_CREDENTIAL': [None, {
206 'NtOwfPassword': [None, ['String', dict(length=16)]],
207 'LmOwfPassword': [None, ['String', dict(length=16)]],
208 'ShaOwPassword': [None, ['String', dict(length=20)]],
209 }],
210 '_KIWI_MASTERKEY_CACHE_ENTRY': [None, {
211 'List': [0, ['_LIST_ENTRY']],
212 'key': [None, ['String', dict(length=lambda x: x.keySize)]],
213 }],
214 })
215
226
232
234 self.iv = self.get_constant_object(
235 'InitializationVector', 'String', length=16, term=None).v()
236
237 aes_handle = self.get_constant_object(
238 'hAesKey', target='Pointer',
239 target_args=dict(target='_KIWI_BCRYPT_HANDLE_KEY'))
240
241 self.aes_key = aes_handle.key.hardkey.data.v()
242
243 des_handle = self.get_constant_object(
244 'h3DesKey', target='Pointer',
245 target_args=dict(target='_KIWI_BCRYPT_HANDLE_KEY'))
246
247 self.des_key = des_handle.key.hardkey.data.v()
248
249 try:
250 cipher = AES.new(self.aes_key, AES.MODE_CFB, self.iv)
251 cipher = DES3.new(self.des_key, DES3.MODE_CBC, self.iv[:8])
252 cipher = None
253 decryption_enabled = True
254 except ValueError as e_ve:
255 decryption_enabled = False
256 logging.warning('init_crypto_nt6 exception {}'.format(e_ve))
257 finally:
258 return decryption_enabled
259
261 if not self.decryption_enabled:
262 return obj.NoneObject()
263
264 cipher = None
265 if self.iv:
266 if len(encrypted) % 8:
267 cipher = AES.new(self.aes_key, AES.MODE_CFB, self.iv)
268 else:
269 if self.des_key:
270 cipher = DES3.new(self.des_key, DES3.MODE_CBC, self.iv[:8])
271 if cipher and encrypted:
272 return cipher.decrypt(encrypted)
273 return obj.NoneObject()
274
276 rc4_key_len = self.get_constant_object(
277 'g_cbRandomKey', 'unsigned long').v()
278
279 rc4_key_ptr = self.get_constant_object(
280 'g_pRandomKey', target='Pointer')
281
282 self.rc4_key = rc4_key_ptr.dereference_as(
283 'String', target_args=dict(length=rc4_key_len, term=None)).v()
284
285 desx_key_ptr = self.get_constant_object(
286 'g_pDESXKey', target='Pointer')
287
288 self.desx_key = desx_key_ptr.dereference_as(
289 'String', target_args=dict(length=144, term=None)).v()
290
291 self.feedback = self.get_constant_object(
292 'g_Feedback', target='String',
293 target_args=dict(length=8)).v()
294
295 try:
296 cipher = ARC4.new(self.rc4_key)
297 decryption_enabled = True
298 except ValueError as e_ve:
299 decryption_enabled = False
300 logging.warning('init_crypto_nt5 exception {}'.format(e_ve))
301 finally:
302 return decryption_enabled
303
305 if not self.decryption_enabled:
306 return obj.NoneObject()
307
308 cipher = None
309 if len(encrypted) % 8:
310 if self.rc4_key:
311 cipher = ARC4.new(self.rc4_key)
312 else:
313 if self.desx_key and self.feedback:
314 cipher = lsadecryptxp.XP_LsaDecryptMemory(
315 self.desx_key, self.feedback)
316 if cipher and encrypted:
317 return cipher.decrypt(encrypted)
318 return obj.NoneObject()
319
327
329 vm = addrspace.BufferAddressSpace(data=data, session=self.session)
330 cred_obj = self.Object('_MSV1_0_PRIMARY_CREDENTIAL',
331 profile=self, vm=vm)
332
333
334 domain = ''
335 if cred_obj.LogonDomainName.Buffer.is_valid():
336 domain = cred_obj.LogonDomainName.Value
337
338 user_name = ''
339 if cred_obj.UserName.Buffer.is_valid():
340 user_name = cred_obj.UserName.Value
341
342 if cred_obj.isLmOwfPassword.v() == 1:
343 yield (domain, user_name, 'LM',
344 cred_obj.LmOwfPassword.v().encode('hex'))
345
346 if cred_obj.isNtOwfPassword.v() == 1:
347 yield (domain, user_name, 'NTLM',
348 cred_obj.NtOwfPassword.v().encode('hex'))
349
350 if cred_obj.isShaOwPassword.v() == 1:
351 yield (domain, user_name, 'SHA1',
352 cred_obj.ShaOwPassword.v().encode('hex'))
353
354 - def logons(self, lsass_logons):
355 for luid, lsass_logon in lsass_logons.iteritems():
356 for cred in lsass_logon.Credentials.walk_list('next'):
357 for primary_cred in cred.PrimaryCredentials.walk_list('next'):
358
359 dec_cred = self.decrypt(primary_cred.Credentials.Raw)
360 if not dec_cred:
361 continue
362
363 cur_cred_type = primary_cred.Primary.Value
364
365 if cur_cred_type == u'Primary':
366 for (domain, user_name, secret_type,
367 secret) in self._msv_primary_credentials(dec_cred):
368 yield (luid, cur_cred_type, domain, user_name,
369 secret_type, secret)
370 else:
371 pass
372
374 keys = self.get_constant_object(
375 'g_MasterKeyCacheList', target='_LIST_ENTRY')
376 for entry in keys.list_of_type('_KIWI_MASTERKEY_CACHE_ENTRY', 'List'):
377 logonId = entry.LogonId
378 if logonId.HighPart.v() == 0 and logonId.LowPart.v() == 0:
379 continue
380 yield (logonId.Text, '',
381 '', '', 'masterkey',
382 self.decrypt(entry.key.v()).encode('hex'))
383
384
385 -class Wdigest(pe_vtypes.BasicPEProfile):
386 """A profile for wdigest.dll"""
387
388 mimikatz_vtypes = [
389 '_LIST_ENTRY', '_LSA_UNICODE_STRING', '_LUID',
390 '_KIWI_WDIGEST_LIST_ENTRY', '_KIWI_GENERIC_PRIMARY_CREDENTIAL',
391 '_KIWI_HARD_KEY']
392
393 @classmethod
395 super(cls, Wdigest).Initialize(profile)
396
397 arch = profile.session.profile.metadata('arch')
398 mimikatz_profile = profile.session.LoadProfile('mimikatz/%s' % arch)
399
400 kwargs = {}
401 for name in cls.mimikatz_vtypes:
402 kwargs[name] = mimikatz_profile.vtypes[name]
403
404 profile.add_types(kwargs)
405
406 profile.add_overlay(mimikatz_common_overlays)
407
408 kiwi_cred_offset = 8
409 if profile.session.profile.metadata('version') < 6.0:
410 kiwi_cred_offset = 12
411
412 profile.add_overlay({
413 '_KIWI_WDIGEST_LIST_ENTRY': [None, {
414 'List': [0, ['_LIST_ENTRY']],
415 'Cred': [lambda x: (x.LocallyUniqueIdentifier.obj_end +
416 kiwi_cred_offset),
417 ['_KIWI_GENERIC_PRIMARY_CREDENTIAL']]
418 }],
419 '_KIWI_HARD_KEY': [None, {
420 'data': lambda x: x.m('data').cast(
421 'String', term=None, length=x.cbSecret)
422 }],
423 })
424
431
432
433 -class Livessp(pe_vtypes.BasicPEProfile):
434 """A profile for livessp.dll"""
435
436 mimikatz_vtypes = [
437 '_LIST_ENTRY', '_LSA_UNICODE_STRING', '_LUID',
438 '_KIWI_LIVESSP_LIST_ENTRY', '_KIWI_LIVESSP_PRIMARY_CREDENTIAL',
439 '_KIWI_GENERIC_PRIMARY_CREDENTIAL']
440
441 @classmethod
461
463 logons = self.get_constant_object(
464 'LiveGlobalLogonSessionList', target='_LIST_ENTRY')
465 for entry in logons.list_of_type('_KIWI_LIVESSP_LIST_ENTRY', 'List'):
466 yield (entry.LocallyUniqueIdentifier.Text,
467 '',
468 entry.suppCreds.dereference().credentials.Domaine.Value,
469 entry.suppCreds.dereference().credentials.UserName.Value,
470 'password',
471 entry.suppCreds.dereference().credentials.Password)
472
473
474 -class Mimikatz(common.WindowsCommandPlugin):
475 """Extract and decrypt passwords from the LSA Security Service."""
476
477 name = 'mimikatz'
478
479 table_header = [
480 dict(name='LUID', width=20),
481 dict(name='Type', width=16),
482 dict(name='Sess', width=2),
483 dict(name='SID', width=20),
484 dict(name='Module', width=7),
485 dict(name='Info', width=7),
486 dict(name='Domain', width=16),
487 dict(name='User', width=16),
488 dict(name='SType', width=9),
489 dict(name='Secret', width=32)
490 ]
491
493 super(Mimikatz, self).__init__(*args, **kwargs)
494
495
496
497 tracked = self.session.GetParameter(
498 'autodetect_build_local_tracked') or []
499
500 needed = set(['lsasrv', 'wdigest', 'livessp'])
501 if not needed.issubset(tracked):
502 needed.update(tracked)
503 with self.session as session:
504 session.SetParameter('autodetect_build_local_tracked', needed)
505
507 cc = self.session.plugins.cc()
508 for task in self.session.plugins.pslist(
509 proc_regex='lsass.exe').filter_processes():
510 cc.SwitchProcessContext(task)
511
512 lsasrv = None
513 lsasrv_module = self.session.address_resolver.GetModuleByName(
514 'lsasrv')
515
516 if lsasrv_module:
517 lsasrv = lsasrv_module.profile
518 if not isinstance(lsasrv, Lsasrv):
519 logging.warning('Unable to properly initialize lsasrv!')
520 lsasrv = None
521
522 if lsasrv:
523 lsasrv.init_crypto()
524 lsass_logons = lsasrv.get_lsass_logons()
525
526 for (luid, info, domain, user_name, secret_type,
527 secret) in lsasrv.logons(lsass_logons):
528
529 lsass_entry = lsass_logons.get(luid, obj.NoneObject())
530 yield (luid,
531 lsass_entry.LogonType,
532 lsass_entry.Session,
533 lsass_entry.pSid.deref(),
534 'msv',
535 info,
536 domain,
537 user_name,
538 secret_type,
539 secret)
540
541 wdigest = None
542 wdigest_module = self.session.address_resolver.GetModuleByName(
543 'wdigest')
544
545 if wdigest_module:
546 wdigest = wdigest_module.profile
547 if not isinstance(wdigest, Wdigest):
548 logging.warning('Unable to properly initialize wdigest.')
549 wdigest = None
550 else:
551 if not wdigest.get_constant('l_LogSessList'):
552 logging.warning('wdigest not initialized, KO.')
553 wdigest = None
554
555 if wdigest:
556 for entry in wdigest.logons():
557 luid = entry.LocallyUniqueIdentifier.Text
558 lsass_entry = lsass_logons.get(luid, obj.NoneObject())
559
560 yield (luid,
561 lsass_entry.LogonType,
562 lsass_entry.Session,
563 lsass_entry.pSid.deref(),
564 'wdigest',
565 '',
566 entry.Cred.Domaine.Value,
567 entry.Cred.UserName.Value,
568 'password',
569 lsasrv.decrypt(entry.Cred.Password.RawMax))
570
571 livessp = None
572 livessp_module = self.session.address_resolver.GetModuleByName(
573 'livessp')
574
575 if livessp_module:
576 livessp = livessp_module.profile
577 if not isinstance(livessp, Livessp):
578 logging.warning('Unable to properly initialize livessp.')
579 livessp = None
580 else:
581 if not livessp.get_constant('LiveGlobalLogonSessionList'):
582 logging.warning('livessp not initialized, KO.')
583 livessp = None
584
585 if livessp:
586 for (luid, info, domain, user_name, secret_type,
587 enc_secret) in livessp.logons():
588 lsass_entry = lsass_logons.get(luid, obj.NoneObject())
589
590 yield (luid,
591 lsass_entry.LogonType,
592 lsass_entry.Session,
593 lsass_entry.pSid.deref(),
594 'livessp',
595 info,
596 domain,
597 user_name,
598 secret_type,
599 lsasrv.decrypt(enc_secret))
600
601 if lsasrv:
602 for (luid, info, domain, user_name, secret_type,
603 secret) in lsasrv.master_keys():
604 lsass_entry = lsass_logons.get(luid, obj.NoneObject())
605
606 yield (luid,
607 lsass_entry.LogonType,
608 lsass_entry.Session,
609 lsass_entry.pSid.deref(),
610 'lsasrv',
611 info,
612 domain,
613 user_name,
614 secret_type,
615 secret)
616