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))