github.com/Coalfire-Research/Slackor@v0.0.0-20191010164036-aa32a7f9250b/impacket/examples/dpapi.py (about)

     1  #!/usr/bin/env python
     2  # SECUREAUTH LABS. Copyright 2018 SecureAuth Corporation. All rights reserved.
     3  #
     4  # This software is provided under under a slightly modified version
     5  # of the Apache Software License. See the accompanying LICENSE file
     6  # for more information.
     7  #
     8  # Author:
     9  #  Alberto Solino (@agsolino)
    10  #
    11  # Description:
    12  #       Example for using the DPAPI/Vault structures to unlock Windows Secrets.
    13  #
    14  # Examples:
    15  #
    16  #   You can unlock masterkeys, credentials and vaults. For the three, you will specify the file name (using -file for
    17  #   masterkeys and credentials, and -vpol and -vcrd for vaults).
    18  #   If no other parameter is sent, the contents of these resource will be shown, with their encrypted data as well.
    19  #   If you specify a -key blob (in the form of '0xabcdef...') that key will be used to decrypt the contents.
    20  #   In the case of vaults, you might need to also provide the user's sid (and the user password will be asked).
    21  #   For system secrets, instead of a password you will need to specify the system and security hives.
    22  #
    23  # References: All of the work done by these guys. I just adapted their work to my needs.
    24  #       https://www.passcape.com/index.php?section=docsys&cmd=details&id=28
    25  #       https://github.com/jordanbtucker/dpapick
    26  #       https://github.com/gentilkiwi/mimikatz/wiki/howto-~-credential-manager-saved-credentials (and everything else Ben did )
    27  #       http://blog.digital-forensics.it/2016/01/windows-revaulting.html
    28  #       https://www.passcape.com/windows_password_recovery_vault_explorer
    29  #       https://www.passcape.com/windows_password_recovery_dpapi_master_key
    30  #
    31  from __future__ import division
    32  from __future__ import print_function
    33  
    34  import struct
    35  import argparse
    36  import logging
    37  import sys
    38  import re
    39  from binascii import unhexlify, hexlify
    40  from hashlib import pbkdf2_hmac
    41  
    42  from Cryptodome.Cipher import AES, PKCS1_v1_5
    43  from Cryptodome.Hash import HMAC, SHA1, MD4
    44  from impacket.uuid import bin_to_string
    45  from impacket import crypto
    46  from impacket.smbconnection import SMBConnection
    47  from impacket.dcerpc.v5 import transport
    48  from impacket.dcerpc.v5 import lsad
    49  from impacket import version
    50  from impacket.examples import logger
    51  from impacket.examples.secretsdump import LocalOperations, LSASecrets
    52  from impacket.structure import hexdump
    53  from impacket.dpapi import MasterKeyFile, MasterKey, CredHist, DomainKey, CredentialFile, DPAPI_BLOB, \
    54      CREDENTIAL_BLOB, VAULT_VCRD, VAULT_VPOL, VAULT_KNOWN_SCHEMAS, VAULT_VPOL_KEYS, P_BACKUP_KEY, PREFERRED_BACKUP_KEY, \
    55      PVK_FILE_HDR, PRIVATE_KEY_BLOB, privatekeyblob_to_pkcs1, DPAPI_DOMAIN_RSA_MASTER_KEY
    56  
    57  class DPAPI:
    58      def __init__(self, options):
    59          self.options = options
    60          self.dpapiSystem = None
    61          pass
    62  
    63      def getDPAPI_SYSTEM(self,secretType, secret):
    64          if secret.startswith("dpapi_machinekey:"):
    65              machineKey, userKey = secret.split('\n')
    66              machineKey = machineKey.split(':')[1]
    67              userKey = userKey.split(':')[1]
    68              self.dpapiSystem = {}
    69              self.dpapiSystem['MachineKey'] = unhexlify(machineKey[2:])
    70              self.dpapiSystem['UserKey'] = unhexlify(userKey[2:])
    71  
    72      def getLSA(self):
    73          localOperations = LocalOperations(self.options.system)
    74          bootKey = localOperations.getBootKey()
    75  
    76          lsaSecrets = LSASecrets(self.options.security, bootKey, None, isRemote=False, history=False, perSecretCallback = self.getDPAPI_SYSTEM)
    77  
    78          lsaSecrets.dumpSecrets()
    79  
    80      def deriveKeysFromUser(self, sid, password):
    81          # Will generate two keys, one with SHA1 and another with MD4
    82          key1 = HMAC.new(SHA1.new(password.encode('utf-16le')).digest(), (sid + '\0').encode('utf-16le'), SHA1).digest()
    83          key2 = HMAC.new(MD4.new(password.encode('utf-16le')).digest(), (sid + '\0').encode('utf-16le'), SHA1).digest()
    84          # For Protected users
    85          tmpKey = pbkdf2_hmac('sha256', MD4.new(password.encode('utf-16le')).digest(), sid.encode('utf-16le'), 10000)
    86          tmpKey2 = pbkdf2_hmac('sha256', tmpKey, sid.encode('utf-16le'), 1)[:16]
    87          key3 = HMAC.new(tmpKey2, (sid + '\0').encode('utf-16le'), SHA1).digest()[:20]
    88  
    89          return key1, key2, key3
    90  
    91      def run(self):
    92          if self.options.action.upper() == 'MASTERKEY':
    93              fp = open(options.file, 'rb')
    94              data = fp.read()
    95              mkf= MasterKeyFile(data)
    96              mkf.dump()
    97              data = data[len(mkf):]
    98  
    99              if mkf['MasterKeyLen'] > 0:
   100                  mk = MasterKey(data[:mkf['MasterKeyLen']])
   101                  data = data[len(mk):]
   102  
   103              if mkf['BackupKeyLen'] > 0:
   104                  bkmk = MasterKey(data[:mkf['BackupKeyLen']])
   105                  data = data[len(bkmk):]
   106  
   107              if mkf['CredHistLen'] > 0:
   108                  ch = CredHist(data[:mkf['CredHistLen']])
   109                  data = data[len(ch):]
   110  
   111              if mkf['DomainKeyLen'] > 0:
   112                  dk = DomainKey(data[:mkf['DomainKeyLen']])
   113                  data = data[len(dk):]
   114  
   115              if self.options.system and self.options.security:
   116                  # We have hives, let's try to decrypt with them
   117                  self.getLSA()
   118                  decryptedKey = mk.decrypt(self.dpapiSystem['UserKey'])
   119                  if decryptedKey:
   120                      print('Decrypted key with UserKey')
   121                      print('Decrypted key: 0x%s' % hexlify(decryptedKey).decode('latin-1'))
   122                      return
   123                  decryptedKey = mk.decrypt(self.dpapiSystem['MachineKey'])
   124                  if decryptedKey:
   125                      print('Decrypted key with MachineKey')
   126                      print('Decrypted key: 0x%s' % hexlify(decryptedKey).decode('latin-1'))
   127                      return
   128                  decryptedKey = bkmk.decrypt(self.dpapiSystem['UserKey'])
   129                  if decryptedKey:
   130                      print('Decrypted Backup key with UserKey')
   131                      print('Decrypted key: 0x%s' % hexlify(decryptedKey).decode('latin-1'))
   132                      return
   133                  decryptedKey = bkmk.decrypt(self.dpapiSystem['MachineKey'])
   134                  if decryptedKey:
   135                      print('Decrypted Backup key with MachineKey')
   136                      print('Decrypted key: 0x%s' % hexlify(decryptedKey).decode('latin-1'))
   137                      return
   138              elif self.options.key:
   139                  key = unhexlify(self.options.key[2:])
   140                  decryptedKey = mk.decrypt(key)
   141                  if decryptedKey:
   142                      print('Decrypted key with key provided')
   143                      print('Decrypted key: 0x%s' % hexlify(decryptedKey).decode('latin-1'))
   144                      return
   145  
   146              elif self.options.pvk and dk:
   147                  pvkfile = open(self.options.pvk, 'rb').read()
   148                  key = PRIVATE_KEY_BLOB(pvkfile[len(PVK_FILE_HDR()):])
   149                  private = privatekeyblob_to_pkcs1(key)
   150                  cipher = PKCS1_v1_5.new(private)
   151  
   152                  decryptedKey = cipher.decrypt(dk['SecretData'][::-1], None)
   153                  if decryptedKey:
   154                      domain_master_key = DPAPI_DOMAIN_RSA_MASTER_KEY(decryptedKey)
   155                      key = domain_master_key['buffer'][:domain_master_key['cbMasterKey']]
   156                      print('Decrypted key with domain backup key provided')
   157                      print('Decrypted key: 0x%s' % hexlify(key).decode('latin-1'))
   158                  return
   159  
   160              elif self.options.sid and self.options.key is None:
   161                  # Do we have a password?
   162                  if self.options.password is None:
   163                      # Nope let's ask it
   164                      from getpass import getpass
   165                      password = getpass("Password:")
   166                  else:
   167                      password = options.password
   168                  key1, key2, key3 = self.deriveKeysFromUser(self.options.sid, password)
   169  
   170                  # if mkf['flags'] & 4 ? SHA1 : MD4
   171                  decryptedKey = mk.decrypt(key3)
   172                  if decryptedKey:
   173                      print('Decrypted key with User Key (MD4 protected)')
   174                      print('Decrypted key: 0x%s' % hexlify(decryptedKey).decode('latin-1'))
   175                      return
   176  
   177                  decryptedKey = mk.decrypt(key2)
   178                  if decryptedKey:
   179                      print('Decrypted key with User Key (MD4)')
   180                      print('Decrypted key: 0x%s' % hexlify(decryptedKey).decode('latin-1'))
   181                      return
   182  
   183                  decryptedKey = mk.decrypt(key1)
   184                  if decryptedKey:
   185                      print('Decrypted key with User Key (SHA1)')
   186                      print('Decrypted key: 0x%s' % hexlify(decryptedKey).decode('latin-1'))
   187                      return
   188  
   189                  decryptedKey = bkmk.decrypt(key3)
   190                  if decryptedKey:
   191                      print('Decrypted Backup key with User Key (MD4 protected)')
   192                      print('Decrypted key: 0x%s' % hexlify(decryptedKey).decode('latin-1'))
   193                      return
   194  
   195                  decryptedKey = bkmk.decrypt(key2)
   196                  if decryptedKey:
   197                      print('Decrypted Backup key with User Key (MD4)')
   198                      print('Decrypted key: 0x%s' % hexlify(decryptedKey).decode('latin-1'))
   199                      return
   200  
   201                  decryptedKey = bkmk.decrypt(key1)
   202                  if decryptedKey:
   203                      print('Decrypted Backup key with User Key (SHA1)')
   204                      print('Decrypted key: 0x%s' % hexlify(decryptedKey).decode('latin-1'))
   205                      return
   206  
   207              else:
   208                  # Just print key's data
   209                  if mkf['MasterKeyLen'] > 0:
   210                      mk.dump()
   211  
   212                  if mkf['BackupKeyLen'] > 0:
   213                      bkmk.dump()
   214  
   215                  if mkf['CredHistLen'] > 0:
   216                      ch.dump()
   217  
   218                  if mkf['DomainKeyLen'] > 0:
   219                      dk.dump()
   220  
   221          # credit to @gentilkiwi
   222          elif self.options.action.upper() == 'BACKUPKEYS':
   223              domain, username, password, address = re.compile('(?:(?:([^/@:]*)/)?([^@:]*)(?::([^@]*))?@)?(.*)').match(
   224                  self.options.target).groups('')
   225              if password == '' and username != '':
   226                  from getpass import getpass
   227                  password = getpass ("Password:")
   228              connection = SMBConnection(address, address)
   229              if self.options.k:
   230                  connection.kerberosLogin(username, password, domain)
   231              else:
   232                  connection.login(username, password, domain)
   233  
   234              rpctransport = transport.DCERPCTransportFactory(r'ncacn_np:445[\pipe\lsarpc]')
   235              rpctransport.set_smb_connection(connection)
   236              dce = rpctransport.get_dce_rpc()
   237              try:
   238                  dce.connect()
   239                  dce.bind(lsad.MSRPC_UUID_LSAD)
   240              except transport.DCERPCException as e:
   241                  raise e
   242  
   243              resp = lsad.hLsarOpenPolicy2(dce, lsad.POLICY_GET_PRIVATE_INFORMATION)
   244              for keyname in ("G$BCKUPKEY_PREFERRED", "G$BCKUPKEY_P"):
   245                  buffer = crypto.decryptSecret(connection.getSessionKey(), lsad.hLsarRetrievePrivateData(dce,
   246                                                resp['PolicyHandle'], keyname))
   247                  guid = bin_to_string(buffer)
   248                  name = "G$BCKUPKEY_{}".format(guid)
   249                  secret = crypto.decryptSecret(connection.getSessionKey(), lsad.hLsarRetrievePrivateData(dce,
   250                                                resp['PolicyHandle'], name))
   251                  keyVersion = struct.unpack('<L', secret[:4])[0]
   252                  if keyVersion == 1:  # legacy key
   253                      backup_key = P_BACKUP_KEY(secret)
   254                      backupkey = backup_key['Data']
   255                      if self.options.export:
   256                          logging.debug("Exporting key to file {}".format(name + ".key"))
   257                          open(name + ".key", 'wb').write(backupkey)
   258                      else:
   259                          print("Legacy key:")
   260                          print("0x%s" % hexlify(backupkey).decode('latin-1'))
   261                          print("\n")
   262  
   263                  elif keyVersion == 2:  # preferred key
   264                      backup_key = PREFERRED_BACKUP_KEY(secret)
   265                      pvk = backup_key['Data'][:backup_key['KeyLength']]
   266                      cert = backup_key['Data'][backup_key['KeyLength']:backup_key['KeyLength'] + backup_key['CertificateLength']]
   267  
   268                      # build pvk header (PVK_MAGIC, PVK_FILE_VERSION_0, KeySpec, PVK_NO_ENCRYPT, 0, cbPvk)
   269                      header = PVK_FILE_HDR()
   270                      header['dwMagic'] = 0xb0b5f11e
   271                      header['dwVersion'] = 0
   272                      header['dwKeySpec'] = 1
   273                      header['dwEncryptType'] = 0
   274                      header['cbEncryptData'] = 0
   275                      header['cbPvk'] = backup_key['KeyLength']
   276                      backupkey_pvk = header.getData() + pvk  # pvk blob
   277  
   278                      backupkey = backupkey_pvk
   279                      if self.options.export:
   280                          logging.debug("Exporting certificate to file {}".format(name + ".der"))
   281                          open(name + ".der", 'wb').write(cert)
   282                          logging.debug("Exporting private key to file {}".format(name + ".pvk"))
   283                          open(name + ".pvk", 'wb').write(backupkey)
   284                      else:
   285                          print("Preferred key:")
   286                          header.dump()
   287                          print("PRIVATEKEYBLOB:{%s}" % (hexlify(backupkey).decode('latin-1')))
   288                          print("\n")
   289              return
   290  
   291  
   292          elif self.options.action.upper() == 'CREDENTIAL':
   293              fp = open(options.file, 'rb')
   294              data = fp.read()
   295              cred = CredentialFile(data)
   296              blob = DPAPI_BLOB(cred['Data'])
   297  
   298              if self.options.key is not None:
   299                  key = unhexlify(self.options.key[2:])
   300                  decrypted = blob.decrypt(key)
   301                  if decrypted is not None:
   302                      creds = CREDENTIAL_BLOB(decrypted)
   303                      creds.dump()
   304                      return
   305              else:
   306                  # Just print the data
   307                  blob.dump()
   308  
   309          elif self.options.action.upper() == 'VAULT':
   310              if options.vcrd is None and options.vpol is None:
   311                  print('You must specify either -vcrd or -vpol parameter. Type --help for more info')
   312                  return
   313              if options.vcrd is not None:
   314                  fp = open(options.vcrd, 'rb')
   315                  data = fp.read()
   316                  blob = VAULT_VCRD(data)
   317  
   318                  if self.options.key is not None:
   319                      key = unhexlify(self.options.key[2:])
   320  
   321                      cleartext = None
   322                      for i, entry in enumerate(blob.attributesLen):
   323                          if entry > 28:
   324                              attribute = blob.attributes[i]
   325                              if 'IV' in attribute.fields and len(attribute['IV']) == 16:
   326                                  cipher = AES.new(key, AES.MODE_CBC, iv=attribute['IV'])
   327                              else:
   328                                  cipher = AES.new(key, AES.MODE_CBC)
   329                              cleartext = cipher.decrypt(attribute['Data'])
   330  
   331                      if cleartext is not None:
   332                          # Lookup schema Friendly Name and print if we find one
   333                          if blob['FriendlyName'].decode('utf-16le')[:-1] in VAULT_KNOWN_SCHEMAS:
   334                              # Found one. Cast it and print
   335                              vault = VAULT_KNOWN_SCHEMAS[blob['FriendlyName'].decode('utf-16le')[:-1]](cleartext)
   336                              vault.dump()
   337                          else:
   338                              # otherwise
   339                              hexdump(cleartext)
   340                          return
   341                  else:
   342                      blob.dump()
   343  
   344              elif options.vpol is not None:
   345                  fp = open(options.vpol, 'rb')
   346                  data = fp.read()
   347                  vpol = VAULT_VPOL(data)
   348                  vpol.dump()
   349  
   350                  if self.options.key is not None:
   351                      key = unhexlify(self.options.key[2:])
   352                      blob = vpol['Blob']
   353                      data = blob.decrypt(key)
   354                      if data is not None:
   355                          keys = VAULT_VPOL_KEYS(data)
   356                          keys.dump()
   357                          return
   358  
   359          print('Cannot decrypt (specify -key or -sid whenever applicable) ')
   360  
   361  
   362  if __name__ == '__main__':
   363      # Init the example's logger theme
   364      logger.init()
   365      print(version.BANNER)
   366  
   367      parser = argparse.ArgumentParser(add_help=True, description="Nose")
   368      parser.add_argument('-debug', action='store_true', help='Turn DEBUG output ON')
   369      subparsers = parser.add_subparsers(help='actions', dest='action')
   370  
   371      # A domain backup key command
   372      backupkeys = subparsers.add_parser('backupkeys', help='domain backup key related functions')
   373      backupkeys.add_argument('-t', '--target', action='store', required=True, help='[[domain/]username[:password]@]<targetName or address>')
   374      backupkeys.add_argument('-k', action='store_true', required=False, help='use kerberos')
   375      backupkeys.add_argument('--export', action='store_true', required=False, help='export keys to file')
   376  
   377      # A masterkey command
   378      masterkey = subparsers.add_parser('masterkey', help='masterkey related functions')
   379      masterkey.add_argument('-file', action='store', required=True, help='Master Key File to parse')
   380      masterkey.add_argument('-sid', action='store', help='SID of the user')
   381      masterkey.add_argument('-pvk', action='store', help='Domain backup privatekey to use for decryption')
   382      masterkey.add_argument('-key', action='store', help='Specific key to use for decryption')
   383      masterkey.add_argument('-password', action='store', help='User\'s password. If you specified the SID and not the password it will be prompted')
   384      masterkey.add_argument('-system', action='store', help='SYSTEM hive to parse')
   385      masterkey.add_argument('-security', action='store', help='SECURITY hive to parse')
   386  
   387      # A credential command
   388      credential = subparsers.add_parser('credential', help='credential related functions')
   389      credential.add_argument('-file', action='store', required=True, help='Credential file')
   390      credential.add_argument('-key', action='store', required=False, help='Key used for decryption')
   391  
   392      # A vault command
   393      vault = subparsers.add_parser('vault', help='vault credential related functions')
   394      vault.add_argument('-vcrd', action='store', required=False, help='Vault Credential file')
   395      vault.add_argument('-vpol', action='store', required=False, help='Vault Policy file')
   396      vault.add_argument('-key', action='store', required=False, help='Master key used for decryption')
   397  
   398      options = parser.parse_args()
   399  
   400      if len(sys.argv)==1:
   401          parser.print_help()
   402          sys.exit(1)
   403  
   404      if options.debug is True:
   405          logging.getLogger().setLevel(logging.DEBUG)
   406      else:
   407          logging.getLogger().setLevel(logging.INFO)
   408  
   409  
   410      try:
   411          executer = DPAPI(options)
   412          executer.run()
   413      except Exception as e:
   414          if logging.getLogger().level == logging.DEBUG:
   415              import traceback
   416              traceback.print_exc()
   417          print(str(e))