Package rekall :: Package plugins :: Package windows :: Module mimikatz
[frames] | no frames]

Source Code for Module rekall.plugins.windows.mimikatz

  1  # Rekall Memory Forensics 
  2  # Copyright 2015 Google Inc. All Rights Reserved. 
  3  # 
  4  # This file is part of Rekall Memory Forensics. 
  5  # 
  6  # Rekall Memory Forensics is free software; you can redistribute it and/or 
  7  # modify it under the terms of the GNU General Public License Version 2 as 
  8  # published by the Free Software Foundation.  You may not use, modify or 
  9  # distribute this program under any other version of the GNU General Public 
 10  # License. 
 11  # 
 12  # Rekall Memory Forensics is distributed in the hope that it will be useful, 
 13  # but WITHOUT ANY WARRANTY; without even the implied warranty of 
 14  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
 15  # GNU General Public License for more details. 
 16  # 
 17  # You should have received a copy of the GNU General Public License along with 
 18  # Rekall Memory Forensics.  If not, see <http://www.gnu.org/licenses/>. 
 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  # pylint: disable=protected-access 
 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 """
95 - def __unicode__(self):
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 # TODO: should be special cases (1or2) addressed? 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
136 - def Initialize(cls, profile):
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
216 - def init_crypto(self):
217 if self.session.profile.metadata('version') < 6.0: 218 self.decryption_enabled = self.init_crypto_nt5() 219 else: 220 self.decryption_enabled = self.init_crypto_nt6() 221 if not self.decryption_enabled: 222 logging.warning('AES: {}'.format(self.aes_key.encode('hex'))) 223 logging.warning('IV: {}'.format(self.iv.encode('hex'))) 224 logging.warning('DES key: {}'.format(self.des_key.encode('hex'))) 225 logging.error('Unable to initialize decryption keys!')
226
227 - def decrypt(self, encrypted):
228 if self.session.profile.metadata('version') < 6.0: 229 return self.decrypt_nt5(encrypted) 230 else: 231 return self.decrypt_nt6(encrypted)
232
233 - def init_crypto_nt6(self):
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
260 - def decrypt_nt6(self, encrypted):
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
275 - def init_crypto_nt5(self):
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
304 - def decrypt_nt5(self, encrypted):
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
320 - def get_lsass_logons(self):
321 logons = {} 322 lsass_logons = self.get_constant_object( 323 'LogonSessionList', target='_LIST_ENTRY') 324 for entry in lsass_logons.list_of_type('MSV1_0_LIST', 'List'): 325 logons[entry.LocallyUniqueIdentifier.Text] = entry 326 return logons
327
328 - def _msv_primary_credentials(self, data):
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 # TODO: check NULL Pointer dereference with this VM. 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
373 - def master_keys(self):
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
394 - def Initialize(cls, profile):
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
425 - def logons(self):
426 # TODO: if the symbols are wrong? Add a check for the LIST validity. 427 logons = self.get_constant_object( 428 'l_LogSessList', target='_LIST_ENTRY') 429 for entry in logons.list_of_type('_KIWI_WDIGEST_LIST_ENTRY', 'List'): 430 yield entry
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
442 - def Initialize(cls, profile):
443 super(cls, Livessp).Initialize(profile) 444 445 arch = profile.session.profile.metadata('arch') 446 mimikatz_profile = profile.session.LoadProfile('mimikatz/%s' % arch) 447 448 kwargs = {} 449 for name in cls.mimikatz_vtypes: 450 kwargs[name] = mimikatz_profile.vtypes[name] 451 452 profile.add_types(kwargs) 453 454 profile.add_overlay(mimikatz_common_overlays) 455 456 profile.add_overlay({ 457 '_KIWI_LIVESSP_LIST_ENTRY': [None, { 458 'List': [0, ['_LIST_ENTRY']], 459 }] 460 })
461
462 - def logons(self):
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
492 - def __init__(self, *args, **kwargs):
493 super(Mimikatz, self).__init__(*args, **kwargs) 494 495 # Track the following modules. If we do not have them in the profile 496 # repository then try to get them directly from Microsoft. 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
506 - def collect(self):
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