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