Files
Didier A a0bc463baf dpapi: fix DPAPI_BLOB decryption when initialized with too much data (#1764)
* dpapi: fix DPAPI_BLOB size
This strips rawData when the bytes array given for initialization is longer than the actual DPAPI Blob,
and fixes toSign extraction which was bogus in this case.

* Add warning in Structure if unpacked structure length does not match provided data

* Update impacket/dpapi.py

Avoid changing status of the object

* Removed warning in Structure added in previous commit.

* Reverted unneeded change

---------

Co-authored-by: DidierA <1620015+didiera@users.noreply.github.com>
Co-authored-by: gabrielg5 <gabriel.gonzalez@fortra.com>
2026-01-16 12:52:56 -03:00

1225 lines
46 KiB
Python

# Impacket - Collection of Python classes for working with network protocols.
#
# Copyright Fortra, LLC and its affiliated companies
#
# All rights reserved.
#
# This software is provided under a slightly modified version
# of the Apache Software License. See the accompanying LICENSE file
# for more information.
#
# Description:
# DPAPI and Windows Vault parsing structures and manipulation
#
# Author:
# Alberto Solino (@agsolino)
#
# References:
# All of the work done by these guys. I just adapted their work to my needs.
# - https://www.passcape.com/index.php?section=docsys&cmd=details&id=28
# - https://github.com/jordanbtucker/dpapick
# - https://github.com/gentilkiwi/mimikatz/wiki/howto-~-credential-manager-saved-credentials (and everything else Ben did)
# - http://blog.digital-forensics.it/2016/01/windows-revaulting.html
# - https://www.passcape.com/windows_password_recovery_vault_explorer
# - https://www.passcape.com/windows_password_recovery_dpapi_master_key
#
from __future__ import division
from __future__ import print_function
import sys
from struct import unpack
from datetime import datetime, timezone
from binascii import unhexlify, hexlify
from struct import pack
from hashlib import pbkdf2_hmac
from Cryptodome.Hash import HMAC, SHA512, SHA1, MD4
from Cryptodome.Cipher import AES, DES3
from Cryptodome.Util.Padding import unpad
from Cryptodome.PublicKey import RSA
from Cryptodome.Util.number import bytes_to_long
from six import PY3
from impacket.ese import getUnixTime
from impacket.structure import Structure, hexdump
from impacket.uuid import bin_to_string
from impacket.dcerpc.v5.enum import Enum
from impacket.dcerpc.v5.dtypes import RPC_SID
# Algorithm classes
ALG_CLASS_ANY = (0)
ALG_CLASS_SIGNATURE = (1 << 13)
ALG_CLASS_MSG_ENCRYPT = (2 << 13)
ALG_CLASS_DATA_ENCRYPT = (3 << 13)
ALG_CLASS_HASH = (4 << 13)
ALG_CLASS_KEY_EXCHANGE = (5 << 13)
ALG_CLASS_ALL = (7 << 13)
# Algorithm types
ALG_TYPE_ANY = (0)
ALG_TYPE_DSS = (1 << 9)
ALG_TYPE_RSA = (2 << 9)
ALG_TYPE_BLOCK = (3 << 9)
ALG_TYPE_STREAM = (4 << 9)
ALG_TYPE_DH = (5 << 9)
ALG_TYPE_SECURECHANNEL = (6 << 9)
ALG_SID_ANY = (0)
ALG_SID_RSA_ANY = 0
ALG_SID_RSA_PKCS = 1
ALG_SID_RSA_MSATWORK = 2
ALG_SID_RSA_ENTRUST = 3
ALG_SID_RSA_PGP = 4
ALG_SID_DSS_ANY = 0
ALG_SID_DSS_PKCS = 1
ALG_SID_DSS_DMS = 2
ALG_SID_ECDSA = 3
# Block cipher sub ids
ALG_SID_DES = 1
ALG_SID_3DES = 3
ALG_SID_DESX = 4
ALG_SID_IDEA = 5
ALG_SID_CAST = 6
ALG_SID_SAFERSK64 = 7
ALG_SID_SAFERSK128 = 8
ALG_SID_3DES_112 = 9
ALG_SID_CYLINK_MEK = 12
ALG_SID_RC5 = 13
ALG_SID_AES_128 = 14
ALG_SID_AES_192 = 15
ALG_SID_AES_256 = 16
ALG_SID_AES = 17
ALG_SID_SKIPJACK = 10
ALG_SID_TEK = 11
CRYPT_MODE_CBCI = 6 # ANSI CBC Interleaved
CRYPT_MODE_CFBP = 7 # ANSI CFB Pipelined
CRYPT_MODE_OFBP = 8 # ANSI OFB Pipelined
CRYPT_MODE_CBCOFM = 9 # ANSI CBC + OF Masking
CRYPT_MODE_CBCOFMI = 10 # ANSI CBC + OFM Interleaved
ALG_SID_RC2 = 2
ALG_SID_RC4 = 1
ALG_SID_SEAL = 2
# Diffie - Hellman sub - ids
ALG_SID_DH_SANDF = 1
ALG_SID_DH_EPHEM = 2
ALG_SID_AGREED_KEY_ANY = 3
ALG_SID_KEA = 4
ALG_SID_ECDH = 5
# Hash sub ids
ALG_SID_MD2 = 1
ALG_SID_MD4 = 2
ALG_SID_MD5 = 3
ALG_SID_SHA = 4
ALG_SID_SHA1 = 4
ALG_SID_MAC = 5
ALG_SID_RIPEMD = 6
ALG_SID_RIPEMD160 = 7
ALG_SID_SSL3SHAMD5 = 8
ALG_SID_HMAC = 9
ALG_SID_TLS1PRF = 10
ALG_SID_HASH_REPLACE_OWF = 11
ALG_SID_SHA_256 = 12
ALG_SID_SHA_384 = 13
ALG_SID_SHA_512 = 14
# secure channel sub ids
ALG_SID_SSL3_MASTER = 1
ALG_SID_SCHANNEL_MASTER_HASH = 2
ALG_SID_SCHANNEL_MAC_KEY = 3
ALG_SID_PCT1_MASTER = 4
ALG_SID_SSL2_MASTER = 5
ALG_SID_TLS1_MASTER = 6
ALG_SID_SCHANNEL_ENC_KEY = 7
ALG_SID_ECMQV = 1
def getFlags(myenum, flags):
return '|'.join([name for name, member in myenum.__members__.items() if member.value & flags])
class FLAGS(Enum):
CRYPTPROTECT_UI_FORBIDDEN = 0x1
CRYPTPROTECT_LOCAL_MACHINE = 0x4
CRYPTPROTECT_CRED_SYNC = 0x8
CRYPTPROTECT_AUDIT = 0x10
CRYPTPROTECT_VERIFY_PROTECTION = 0x40
CRYPTPROTECT_CRED_REGENERATE = 0x80
CRYPTPROTECT_SYSTEM = 0x20000000
# algorithm identifier definitions
class ALGORITHMS(Enum):
CALG_MD2 = (ALG_CLASS_HASH | ALG_TYPE_ANY | ALG_SID_MD2)
CALG_MD4 = (ALG_CLASS_HASH | ALG_TYPE_ANY | ALG_SID_MD4)
CALG_MD5 = (ALG_CLASS_HASH | ALG_TYPE_ANY | ALG_SID_MD5)
CALG_SHA = (ALG_CLASS_HASH | ALG_TYPE_ANY | ALG_SID_SHA)
CALG_SHA1 = (ALG_CLASS_HASH | ALG_TYPE_ANY | ALG_SID_SHA1)
CALG_RSA_SIGN = (ALG_CLASS_SIGNATURE | ALG_TYPE_RSA | ALG_SID_RSA_ANY)
CALG_DSS_SIGN = (ALG_CLASS_SIGNATURE | ALG_TYPE_DSS | ALG_SID_DSS_ANY)
CALG_NO_SIGN = (ALG_CLASS_SIGNATURE | ALG_TYPE_ANY | ALG_SID_ANY)
CALG_RSA_KEYX = (ALG_CLASS_KEY_EXCHANGE|ALG_TYPE_RSA|ALG_SID_RSA_ANY)
CALG_DES = (ALG_CLASS_DATA_ENCRYPT|ALG_TYPE_BLOCK|ALG_SID_DES)
CALG_3DES_112 = (ALG_CLASS_DATA_ENCRYPT|ALG_TYPE_BLOCK|ALG_SID_3DES_112)
CALG_3DES = (ALG_CLASS_DATA_ENCRYPT|ALG_TYPE_BLOCK|ALG_SID_3DES)
CALG_DESX = (ALG_CLASS_DATA_ENCRYPT|ALG_TYPE_BLOCK|ALG_SID_DESX)
CALG_RC2 = (ALG_CLASS_DATA_ENCRYPT|ALG_TYPE_BLOCK|ALG_SID_RC2)
CALG_RC4 = (ALG_CLASS_DATA_ENCRYPT|ALG_TYPE_STREAM|ALG_SID_RC4)
CALG_SEAL = (ALG_CLASS_DATA_ENCRYPT|ALG_TYPE_STREAM|ALG_SID_SEAL)
CALG_DH_SF = (ALG_CLASS_KEY_EXCHANGE|ALG_TYPE_DH|ALG_SID_DH_SANDF)
CALG_DH_EPHEM = (ALG_CLASS_KEY_EXCHANGE|ALG_TYPE_DH|ALG_SID_DH_EPHEM)
CALG_AGREEDKEY_ANY = (ALG_CLASS_KEY_EXCHANGE|ALG_TYPE_DH|ALG_SID_AGREED_KEY_ANY)
CALG_KEA_KEYX = (ALG_CLASS_KEY_EXCHANGE|ALG_TYPE_DH|ALG_SID_KEA)
CALG_HUGHES_MD5 = (ALG_CLASS_KEY_EXCHANGE|ALG_TYPE_ANY|ALG_SID_MD5)
CALG_SKIPJACK = (ALG_CLASS_DATA_ENCRYPT|ALG_TYPE_BLOCK|ALG_SID_SKIPJACK)
CALG_TEK = (ALG_CLASS_DATA_ENCRYPT|ALG_TYPE_BLOCK|ALG_SID_TEK)
CALG_SSL3_SHAMD5 = (ALG_CLASS_HASH | ALG_TYPE_ANY | ALG_SID_SSL3SHAMD5)
CALG_SSL3_MASTER = (ALG_CLASS_MSG_ENCRYPT|ALG_TYPE_SECURECHANNEL|ALG_SID_SSL3_MASTER)
CALG_SCHANNEL_MASTER_HASH = (ALG_CLASS_MSG_ENCRYPT|ALG_TYPE_SECURECHANNEL|ALG_SID_SCHANNEL_MASTER_HASH)
CALG_SCHANNEL_MAC_KEY = (ALG_CLASS_MSG_ENCRYPT|ALG_TYPE_SECURECHANNEL|ALG_SID_SCHANNEL_MAC_KEY)
CALG_SCHANNEL_ENC_KEY = (ALG_CLASS_MSG_ENCRYPT|ALG_TYPE_SECURECHANNEL|ALG_SID_SCHANNEL_ENC_KEY)
CALG_PCT1_MASTER = (ALG_CLASS_MSG_ENCRYPT|ALG_TYPE_SECURECHANNEL|ALG_SID_PCT1_MASTER)
CALG_SSL2_MASTER = (ALG_CLASS_MSG_ENCRYPT|ALG_TYPE_SECURECHANNEL|ALG_SID_SSL2_MASTER)
CALG_TLS1_MASTER = (ALG_CLASS_MSG_ENCRYPT|ALG_TYPE_SECURECHANNEL|ALG_SID_TLS1_MASTER)
CALG_RC5 = (ALG_CLASS_DATA_ENCRYPT|ALG_TYPE_BLOCK|ALG_SID_RC5)
CALG_HMAC = (ALG_CLASS_HASH | ALG_TYPE_ANY | ALG_SID_HMAC)
CALG_TLS1PRF = (ALG_CLASS_HASH | ALG_TYPE_ANY | ALG_SID_TLS1PRF)
CALG_HASH_REPLACE_OWF = (ALG_CLASS_HASH | ALG_TYPE_ANY | ALG_SID_HASH_REPLACE_OWF)
CALG_AES_128 = (ALG_CLASS_DATA_ENCRYPT|ALG_TYPE_BLOCK|ALG_SID_AES_128)
CALG_AES_192 = (ALG_CLASS_DATA_ENCRYPT|ALG_TYPE_BLOCK|ALG_SID_AES_192)
CALG_AES_256 = (ALG_CLASS_DATA_ENCRYPT|ALG_TYPE_BLOCK|ALG_SID_AES_256)
CALG_AES = (ALG_CLASS_DATA_ENCRYPT|ALG_TYPE_BLOCK|ALG_SID_AES)
CALG_SHA_256 = (ALG_CLASS_HASH | ALG_TYPE_ANY | ALG_SID_SHA_256)
CALG_SHA_384 = (ALG_CLASS_HASH | ALG_TYPE_ANY | ALG_SID_SHA_384)
CALG_SHA_512 = (ALG_CLASS_HASH | ALG_TYPE_ANY | ALG_SID_SHA_512)
CALG_ECDH = (ALG_CLASS_KEY_EXCHANGE | ALG_TYPE_DH | ALG_SID_ECDH)
CALG_ECMQV = (ALG_CLASS_KEY_EXCHANGE | ALG_TYPE_ANY | ALG_SID_ECMQV)
CALG_ECDSA = (ALG_CLASS_SIGNATURE | ALG_TYPE_DSS | ALG_SID_ECDSA)
class CREDENTIAL_FLAGS(Enum):
CRED_FLAGS_PASSWORD_FOR_CERT = 0x1
CRED_FLAGS_PROMPT_NOW = 0x2
CRED_FLAGS_USERNAME_TARGET = 0x4
CRED_FLAGS_OWF_CRED_BLOB = 0x8
CRED_FLAGS_REQUIRE_CONFIRMATION = 0x10
CRED_FLAGS_WILDCARD_MATCH = 0x20
CRED_FLAGS_VSM_PROTECTED = 0x40
CRED_FLAGS_NGC_CERT = 0x80
class CREDENTIAL_TYPE(Enum):
CRED_TYPE_GENERIC = 0x1
CRED_TYPE_DOMAIN_PASSWORD = 0x2
CRED_TYPE_DOMAIN_CERTIFICATE = 0x3
CRED_TYPE_DOMAIN_VISIBLE_PASSWORD = 0x4
CRED_TYPE_GENERIC_CERTIFICATE = 0x5
CRED_TYPE_DOMAIN_EXTENDED = 0x6
CRED_TYPE_MAXIMUM = 0x7
CRED_TYPE_MAXIMUM_EX = 0x8
class CREDENTIAL_PERSIST(Enum):
CRED_PERSIST_NONE = 0x0
CRED_PERSIST_SESSION = 0x1
CRED_PERSIST_LOCAL_MACHINE = 0x2
CRED_PERSIST_ENTERPRISE = 0x3
ALGORITHMS_DATA = {
# Algorithm: key/SaltLen, CryptHashModule, Mode, IVLen, BlockSize
ALGORITHMS.CALG_SHA.value: (160//8, SHA1, None, None, 512//8),
ALGORITHMS.CALG_HMAC.value: (160//8, SHA512, None, None, 512//8),
ALGORITHMS.CALG_3DES.value: (192//8, DES3, DES3.MODE_CBC, 64//8),
ALGORITHMS.CALG_SHA_512.value: (128//8, SHA512, None, None, 1024//8),
ALGORITHMS.CALG_AES_256.value: (256//8, AES, AES.MODE_CBC, 128//8),
}
class MasterKeyFile(Structure):
structure = (
('Version', '<L=0'),
('unk1', '<L=0'),
('unk2', '<L=0'),
('Guid', "72s=b''"),
('Unkown', '<L=0'),
('Policy', '<L=0'),
('Flags', '<L=0'),
('MasterKeyLen', '<Q=0'),
('BackupKeyLen', '<Q=0'),
('CredHistLen', '<Q=0'),
('DomainKeyLen', '<Q=0'),
)
def dump(self):
print("[MASTERKEYFILE]")
print("Version : %8x (%d)" % (self['Version'], self['Version']))
print("Guid : %s" % self['Guid'].decode('utf-16le'))
print("Flags : %8x (%d)" % (self['Flags'], self['Flags']))
print("Policy : %8x (%d)" % (self['Policy'], self['Policy']))
print("MasterKeyLen: %.8x (%d)" % (self['MasterKeyLen'], self['MasterKeyLen']))
print("BackupKeyLen: %.8x (%d)" % (self['BackupKeyLen'], self['BackupKeyLen']))
print("CredHistLen : %.8x (%d)" % (self['CredHistLen'], self['CredHistLen']))
print("DomainKeyLen: %.8x (%d)" % (self['DomainKeyLen'], self['DomainKeyLen']))
print()
class MasterKey(Structure):
structure = (
('Version', '<L=0'),
('Salt', '16s=b""'),
('MasterKeyIterationCount', '<L=0'),
('HashAlgo', "<L=0"),
('CryptAlgo', '<L=0'),
('data', ':'),
)
def __init__(self, data = None, alignment = 0):
Structure.__init__(self, data, alignment)
self.decryptedKey = None
def dump(self):
print("[MASTERKEY]")
print("Version : %8x (%d)" % (self['Version'], self['Version']))
print("Salt : %s" % hexlify(self['Salt']))
print("Rounds : %8x (%d)" % (self['MasterKeyIterationCount'], self['MasterKeyIterationCount']))
print("HashAlgo : %.8x (%d) (%s)" % (self['HashAlgo'], self['HashAlgo'], ALGORITHMS(self['HashAlgo']).name))
print("CryptAlgo : %.8x (%d) (%s)" % (self['CryptAlgo'], self['CryptAlgo'], ALGORITHMS(self['CryptAlgo']).name))
print("data : %s" % (hexlify(self['data'])))
print()
def deriveKey(self, passphrase, salt, keylen, count, hashFunction):
keyMaterial = b""
i = 1
while len(keyMaterial) < keylen:
U = salt + pack("!L", i)
i += 1
derived = bytearray(hashFunction(passphrase, U))
for r in range(count - 1):
actual = bytearray(hashFunction(passphrase, derived))
if PY3:
derived = (int.from_bytes(derived, sys.byteorder) ^ int.from_bytes(actual, sys.byteorder)).to_bytes(len(actual), sys.byteorder)
else:
derived = bytearray([ chr((a) ^ (b)) for (a,b) in zip(derived, actual) ])
keyMaterial += derived
return keyMaterial[:keylen]
def decrypt(self, key):
if self['HashAlgo'] == ALGORITHMS.CALG_HMAC.value:
hashModule = SHA1
else:
hashModule = ALGORITHMS_DATA[self['HashAlgo']][1]
prf = lambda p, s: HMAC.new(p, s, hashModule).digest()
derivedBlob = self.deriveKey(key, self['Salt'],
ALGORITHMS_DATA[self['CryptAlgo']][0] + ALGORITHMS_DATA[self['CryptAlgo']][3],
count=self['MasterKeyIterationCount'], hashFunction=prf)
cryptKey = derivedBlob[:ALGORITHMS_DATA[self['CryptAlgo']][0]]
iv = derivedBlob[ALGORITHMS_DATA[self['CryptAlgo']][0]:][:ALGORITHMS_DATA[self['CryptAlgo']][3]]
cipher = ALGORITHMS_DATA[self['CryptAlgo']][1].new(cryptKey, mode = ALGORITHMS_DATA[self['CryptAlgo']][2], iv = iv)
cleartext = cipher.decrypt(self['data'])
decryptedKey = cleartext[-64:]
hmacSalt = cleartext[:16]
hmac = cleartext[16:][:ALGORITHMS_DATA[self['HashAlgo']][0]]
hmacKey = HMAC.new(key, hmacSalt, hashModule).digest()
hmacCalculated = HMAC.new(hmacKey, decryptedKey, hashModule ).digest()
if hmacCalculated[:ALGORITHMS_DATA[self['HashAlgo']][0]] == hmac:
self.decryptedKey = decryptedKey
return decryptedKey
else:
return None
class CredHist(Structure):
structure = (
('Version', '<L=0'),
('Guid', "16s=b''"),
)
def dump(self):
print("[CREDHIST]")
print("Version : %8x (%d)" % (self['Version'], self['Version']))
print("Guid : %s" % bin_to_string(self['Guid']))
print()
class CREDHIST_ENTRY(Structure):
structure = (
('Version', '<L=0'),
('HashAlgo', '<L=0'),
('Rounds', '<L=0'),
('SidLen', '<L=0'),
('_Sid', '_-Sid','self["SidLen"]'),
('CryptAlgo', '<L=0'),
('shaHashLen', '<L=0'),
('ntHashLen', '<L=0'),
('Salt', '16s=b'),
('Sid', ':'),
('_data','_-data','(self["shaHashLen"]+self["ntHashLen"]) + (-(self["shaHashLen"]+self["ntHashLen"])) % 16'),
('data', ':'),
('Version2', '<L=0'),
('Guid', '16s=b'),
)
def __init__(self, data = None, alignment = 0):
Structure.__init__(self, data, alignment)
self.sid = RPC_SID(b'\x05\x00\x00\x00'+self['Sid']).formatCanonical()
self.pwdhash = None
self.nthash = None
def deriveKey(self, passphrase, salt, keylen, count, hashFunction):
keyMaterial = b""
i = 1
while len(keyMaterial) < keylen:
U = salt + pack("!L", i)
i += 1
derived = bytearray(hashFunction(passphrase, U))
for r in range(count - 1):
actual = bytearray(hashFunction(passphrase, derived))
if PY3:
derived = (int.from_bytes(derived, sys.byteorder) ^ int.from_bytes(actual, sys.byteorder)).to_bytes(len(actual), sys.byteorder)
else:
derived = bytearray([ chr((a) ^ (b)) for (a,b) in zip(derived, actual) ])
keyMaterial += derived
return keyMaterial[:keylen]
def decrypt(self, key):
if self['HashAlgo'] == ALGORITHMS.CALG_HMAC.value:
hashModule = SHA1
else:
hashModule = ALGORITHMS_DATA[self['HashAlgo']][1]
prf = lambda p, s: HMAC.new(p, s, hashModule).digest()
derivedBlob = self.deriveKey(key, self['Salt'], ALGORITHMS_DATA[self['CryptAlgo']][0] + ALGORITHMS_DATA[self['CryptAlgo']][3], count=self['Rounds'], hashFunction=prf)
cryptKey = derivedBlob[:ALGORITHMS_DATA[self['CryptAlgo']][0]]
iv = derivedBlob[ALGORITHMS_DATA[self['CryptAlgo']][0]:][:ALGORITHMS_DATA[self['CryptAlgo']][3]]
cipher = ALGORITHMS_DATA[self['CryptAlgo']][1].new(cryptKey, mode = ALGORITHMS_DATA[self['CryptAlgo']][2], iv = iv)
cleartext = cipher.decrypt(self['data'])
ntHashSize = 16
self.pwdhash = cleartext[:self['shaHashLen']]
self.nthash = cleartext[self['shaHashLen']:self['shaHashLen'] + ntHashSize]
if cleartext[self['shaHashLen'] + ntHashSize:] != (len(self['data']) - self['shaHashLen'] - ntHashSize) * b'\x00':
self.pwdhash = None
self.nthash = None
def dump(self):
print("[CREDHIST ENTRY]")
print("Version : 0x%.8x (%d)" % (self['Version'], self['Version']))
print("HashAlgo : 0x%.8x (%d) (%s)" % (self['HashAlgo'], self['HashAlgo'], ALGORITHMS(self['HashAlgo']).name))
print("Rounds : %d" % (self['Rounds']))
print("CryptAlgo : 0x%.8x (%d) (%s)" % (self['CryptAlgo'], self['CryptAlgo'], ALGORITHMS(self['CryptAlgo']).name))
print("shaHashLen : 0x%.8x (%d)" % (self['shaHashLen'], self['shaHashLen']))
print("ntHashLen : 0x%.8x (%d)" % (self['ntHashLen'], self['ntHashLen']))
print("Salt : %s" % (hexlify(self['Salt']).decode()))
print('SID : %s' % self.sid)
print("Version2 : 0x%.8x (%d)" % (self['Version2'], self['Version2']))
print("Guid : %s" % bin_to_string(self['Guid']))
if self.pwdhash is not None and self.nthash is not None:
print("pwdHash : %s" % hexlify(self.pwdhash).decode())
print("ntHash : %s" % hexlify(self.nthash).decode())
else:
print("Data : %s" % (hexlify(self['data'])).decode())
print()
def summarize(self):
print("[CREDHIST ENTRY]")
print("Guid : %s" % bin_to_string(self['Guid']))
if self.pwdhash is not None and self.nthash is not None:
print("pwdHash : %s" % hexlify(self.pwdhash).decode())
print("ntHash : %s" % hexlify(self.nthash).decode())
else:
print("Data : %s" % (hexlify(self['data'])).decode())
print()
class CREDHIST_FILE:
def __init__(self, raw):
self.credhist_entries = {}
self.credhist_entries_list = []
self.version = unpack('<L', raw[:4])[0]
self.current_guid = unpack('16s', raw[4:20])[0]
i = 0
next_len = unpack('<L', raw[-i-4:])[0]
i += 4
while next_len != 0:
ch_entry = CREDHIST_ENTRY(data=raw[-(i + next_len - 4):-i])
i += next_len - 4
self.credhist_entries[bin_to_string(ch_entry['Guid'])] = ch_entry
self.credhist_entries_list.append(ch_entry)
next_len = unpack('<L', raw[-i-4:-i])[0]
i += 4
def decrypt_entry_by_index(self, entry_index, key):
self.credhist_entries_list[entry_index].decrypt(key)
def decrypt_entry_by_guid(self, guid, key):
self.credhist_entries[guid].decrypt(key)
def decrypt(self, key):
keys = [key]
for i, e in enumerate(self.credhist_entries_list):
# Try all keys until success
for k in keys:
e.decrypt(k)
if e.pwdhash is not None:
break
if e.pwdhash is None:
print('Error decrypting entry #%d' % i)
return
keys = deriveKeysFromUserkey(e.sid, e.pwdhash)
def dump(self):
print('[CREDHIST FILE]')
print("Version : 0x%.8x (%d)" % (self.version, self.version))
print("Current Guid : %s" % bin_to_string(self.current_guid))
print()
for i, e in enumerate(self.credhist_entries_list):
print('[Entry #%d]' % i)
e.dump()
def summarize(self):
print('[CREDHIST FILE]')
print("Version : 0x%.8x (%d)" % (self.version, self.version))
print("Current Guid : %s" % bin_to_string(self.current_guid))
print()
for i, e in enumerate(self.credhist_entries_list):
print('[Entry #%d]' % i)
e.summarize()
class DomainKey(Structure):
structure = (
('Version', '<L=0'),
('SecretLen', '<L=0'),
('AccessCheckLen', '<L=0'),
('Guid', "16s=b"""),
('_SecretData', '_-SecretData', 'self["SecretLen"]'),
('SecretData', ':'),
('_AccessCheck', '_-AccessCheck', 'self["AccessCheckLen"]'),
('AccessCheck', ':'),
)
def dump(self):
print("[DOMAINKEY]")
print("Version : %8x (%d)" % (self['Version'], self['Version']))
print("Guid : %s" % bin_to_string(self['Guid']))
print("SecretLen : %8x (%d)" % (self['SecretLen'], self['SecretLen']))
print("AccessCheckLen: %.8x (%d)" % (self['AccessCheckLen'], self['AccessCheckLen']))
print("SecretData : %s" % (hexlify(self['SecretData'])))
print("AccessCheck : %s" % (hexlify(self['AccessCheck'])))
print()
class DPAPI_SYSTEM(Structure):
structure = (
('Version', '<L=0'),
('MachineKey', '20s=b""'),
('UserKey', '20s=b""'),
)
def dump(self):
print("[DPAPI_SYSTEM]")
print("Version : %8x (%d)" % (self['Version'], self['Version']))
print("MachineKey : 0x%s" % hexlify(self['MachineKey']).decode('latin-1'))
print("UserKey : 0x%s" % hexlify(self['UserKey']).decode('latin-1'))
print()
class CredentialFile(Structure):
structure = (
('Version', '<L=0'),
('Size', '<L=0'),
('Unknown', '<L=0'),
('_Data', '_-Data', 'self["Size"]'),
('Data', ':'),
)
#def dump(self):
# print("[CREDENTIAL FILE]")
# print("Version : %8x (%d)" % (self['Version'], self['Version']))
# print("MachineKey : %s" % hexlify(self['MachineKey']))
# print("UserKey : %s" % hexlify(self['UserKey']))
# print("CryptAlgo : %.8x (%d) (%s)" % (self['CryptAlgo'], self['CryptAlgo'], ALGORITHMS(self['CryptAlgo']).name))
# print()
class DPAPI_BLOB(Structure):
structure = (
('Version', '<L=0'),
('GuidCredential', "16s=b"""),
('MasterKeyVersion', '<L=0'),
('GuidMasterKey', "16s=b"""),
('Flags', '<L=0'),
('DescriptionLen', '<L=0'),
('_Description', '_-Description', 'self["DescriptionLen"]'),
('Description', ':'),
('CryptAlgo', '<L=0'),
('CryptAlgoLen', '<L=0'),
('SaltLen', '<L=0'),
('_Salt', '_-Salt', 'self["SaltLen"]'),
('Salt', ':'),
('HMacKeyLen', '<L=0'),
('_HMacKey', '_-HMacKey', 'self["HMacKeyLen"]'),
('HMacKey', ':'),
('HashAlgo', '<L=0'),
('HashAlgoLen', '<L=0'),
('HMac', '<L=0'),
('_HMac', '_-HMac', 'self["HMac"]'),
('HMac', ':'),
('DataLen', '<L=0'),
('_Data', '_-Data', 'self["DataLen"]'),
('Data', ':'),
('SignLen', '<L=0'),
('_Sign', '_-Sign', 'self["SignLen"]'),
('Sign', ':'),
)
def dump(self):
print("[BLOB]")
print("Version : %8x (%d)" % (self['Version'], self['Version']))
print("Guid Credential : %s" % bin_to_string(self['GuidCredential']))
print("MasterKeyVersion : %8x (%d)" % (self['MasterKeyVersion'], self['MasterKeyVersion']))
print("Guid MasterKey : %s" % bin_to_string(self['GuidMasterKey']))
print("Flags : %8x (%s)" % (self['Flags'], getFlags(FLAGS, self['Flags'])))
print("Description : %s" % (self['Description'].decode('utf-16le')))
print("CryptAlgo : %.8x (%d) (%s)" % (self['CryptAlgo'], self['CryptAlgo'], ALGORITHMS(self['CryptAlgo']).name))
print("Salt : %s" % (hexlify(self['Salt'])))
print("HMacKey : %s" % (hexlify(self['HMacKey'])))
print("HashAlgo : %.8x (%d) (%s)" % (self['HashAlgo'], self['HashAlgo'], ALGORITHMS(self['HashAlgo']).name))
print("HMac : %s" % (hexlify(self['HMac'])))
print("Data : %s" % (hexlify(self['Data'])))
print("Sign : %s" % (hexlify(self['Sign'])))
print()
def deriveKey(self, sessionKey):
def fixparity(deskey):
from six import indexbytes, b
temp = b''
for i in range(len(deskey)):
t = (bin(indexbytes(deskey,i))[2:]).rjust(8,'0')
if t[:7].count('1') %2 == 0:
temp+= b(chr(int(t[:7]+'1',2)))
else:
temp+= b(chr(int(t[:7]+'0',2)))
return temp
if len(sessionKey) > ALGORITHMS_DATA[self['HashAlgo']][4]:
derivedKey = HMAC.new(sessionKey, digestmod = ALGORITHMS_DATA[self['HashAlgo']][1]).digest()
else:
derivedKey = sessionKey
if len(derivedKey) < ALGORITHMS_DATA[self['CryptAlgo']][0]:
# Extend the key
derivedKey += b'\x00'*ALGORITHMS_DATA[self['HashAlgo']][4]
ipad = bytearray([ i ^ 0x36 for i in bytearray(derivedKey)][:ALGORITHMS_DATA[self['HashAlgo']][4]])
opad = bytearray([ i ^ 0x5c for i in bytearray(derivedKey)][:ALGORITHMS_DATA[self['HashAlgo']][4]])
derivedKey = ALGORITHMS_DATA[self['HashAlgo']][1].new(ipad).digest() + \
ALGORITHMS_DATA[self['HashAlgo']][1].new(opad).digest()
derivedKey = fixparity(derivedKey)
return derivedKey
def decrypt(self, key, entropy = None):
keyHash = SHA1.new(key).digest()
sessionKey = HMAC.new(keyHash, self['Salt'], ALGORITHMS_DATA[self['HashAlgo']][1])
if entropy is not None:
sessionKey.update(entropy)
sessionKey = sessionKey.digest()
# Derive the key
derivedKey = self.deriveKey(sessionKey)
cipher = ALGORITHMS_DATA[self['CryptAlgo']][1].new(derivedKey[:ALGORITHMS_DATA[self['CryptAlgo']][0]],
mode=ALGORITHMS_DATA[self['CryptAlgo']][2], iv=b'\x00'*ALGORITHMS_DATA[self['CryptAlgo']][3])
cleartext = unpad(cipher.decrypt(self['Data']), ALGORITHMS_DATA[self['CryptAlgo']][1].block_size)
# Now check the signature
# ToDo Fix this, it's just ugly, more testing so we can remove one
toSign = (self.rawData[20:len(self)-len(self['Sign'])-4])
# Calculate the different HMACKeys
keyHash2 = keyHash + b"\x00"*ALGORITHMS_DATA[self['HashAlgo']][1].block_size
ipad = bytearray([i ^ 0x36 for i in bytearray(keyHash2)][:ALGORITHMS_DATA[self['HashAlgo']][1].block_size])
opad = bytearray([i ^ 0x5c for i in bytearray(keyHash2)][:ALGORITHMS_DATA[self['HashAlgo']][1].block_size])
a = ALGORITHMS_DATA[self['HashAlgo']][1].new(ipad)
a.update(self['HMac'])
hmacCalculated1 = ALGORITHMS_DATA[self['HashAlgo']][1].new(opad)
hmacCalculated1.update(a.digest())
if entropy is not None:
hmacCalculated1.update(entropy)
hmacCalculated1.update(toSign)
hmacCalculated3 = HMAC.new(keyHash, self['HMac'], ALGORITHMS_DATA[self['HashAlgo']][1])
if entropy is not None:
hmacCalculated3.update(entropy)
hmacCalculated3.update(toSign)
if hmacCalculated1.digest() == self['Sign'] or hmacCalculated3.digest() == self['Sign']:
return cleartext
else:
return None
class VAULT_ATTRIBUTE(Structure):
structure = (
('Id', '<L=0'),
('Unknown1', '<L=0'),
('Unknown2', '<L=0'),
('Unknown3', '<L=0'),
)
padding = (
('Pad', '6s=b""'),
)
id100 = (
('Unknown5', '<L=0'),
)
extended = (
('Size','<L=0'),
('IVPresent', '<B=?&IVSize'),
('IVSize', '<L=0'),
('_IV', '_-IV', 'self["IVSize"] if self["IVSize"] is not None else 0'),
('IV', ':'),
('_Data','_-Data', 'self["Size"]-self["IVSize"]-5 if self["IVPresent"] else self["Size"]-1'),
('Data',':'),
)
def __init__(self, data = None, alignment = 0):
if len(data) > 20:
if data[16:][:6] == b'\x00'*6:
self.structure += self.padding
if unpack('<L',data[:4])[0] >= 100:
self.structure += self.id100
if len(data[16:]) >= 9:
self.structure += self.extended
Structure.__init__(self, data, alignment)
def dump(self):
print("[ATTRIBUTE %d]" % self['Id'])
if len(self.rawData) > 28:
print("Size : 0x%x" % self['Size'])
if self['IVPresent'] > 0:
print("IVSize : 0x%x" % self['IVSize'])
print("IV : %s" % hexlify(self['IV']))
print("Data : %s" % hexlify(self['Data']))
class VAULT_ATTRIBUTE_MAP_ENTRY(Structure):
structure = (
('Id', '<L=0'),
('Offset', '<L=0'),
('Unknown1', '<L=0'),
)
def dump(self):
print("[MAP ENTRY %d @ 0x%.8x]" % (self['Id'], self['Offset']))
class VAULT_VCRD(Structure):
structure = (
('SchemaGuid', "16s=b"""),
('Unknown0', '<L=0'),
('LastWritten', '<Q=0'),
('Unknown1', '<L=0'),
('Unknown2', '<L=0'),
('FriendlyNameLen', '<L=0'),
('FriendlyNameL', '_-FriendlyName', 'self["FriendlyNameLen"]'),
('FriendlyName', ':'),
('AttributesMapsSize', '<L=0'),
('AttributeL', '_-AttributeMaps', 'self["AttributesMapsSize"]'),
('AttributeMaps', ':'),
('Data', ':'),
)
def __init__(self, data = None, alignment = 0):
Structure.__init__(self, data, alignment)
if data is not None:
# Process the MAP entries
self.mapEntries = list()
data = self['AttributeMaps']
for i in range(self['AttributesMapsSize']//len(VAULT_ATTRIBUTE_MAP_ENTRY())):
entry = VAULT_ATTRIBUTE_MAP_ENTRY(data)
self.mapEntries.append(entry)
data = data[len(VAULT_ATTRIBUTE_MAP_ENTRY()):]
self.attributesLen = list()
for i in range(len(self.mapEntries)):
if i > 0:
self.attributesLen.append(self.mapEntries[i]['Offset']-self.mapEntries[i-1]['Offset'])
self.attributesLen.append(len(self.rawData) - self.mapEntries[i]['Offset'] )
self.attributes = list()
for i, entry in enumerate(self.mapEntries):
attribute = VAULT_ATTRIBUTE(self.rawData[entry['Offset']:][:self.attributesLen[i]])
self.attributes.append(attribute)
# Do we have remaining data?
self['Data'] = self.rawData[self.mapEntries[-1]['Offset']+len(self.attributes[-1].getData()):]
def dump(self):
print("[VCRD]")
print("SchemaGuid : %s" % bin_to_string(self['SchemaGuid']))
print("LastWritten : %s" % (datetime.fromtimestamp(getUnixTime(self['LastWritten']), tz=timezone.utc)))
print("FriendlyName: %s" % (self['FriendlyName'].decode('utf-16le')))
print()
for i,entry in enumerate(self.mapEntries):
entry.dump()
self.attributes[i].dump()
print()
print("Remaining : %s" % (hexlify(self['Data'])))
print()
class VAULT_VPOL(Structure):
structure = (
('Version', '<L=0'),
('Guid', "16s=b"""),
('DescriptionLen', '<L=0'),
('_Description', '_-Description', 'self["DescriptionLen"]'),
('Description', ':'),
('Unknown', '12s=b""'),
('Size', '<L=0'),
('Guid2', "16s=b"""),
('Guid3', "16s=b"""),
('KeySize','<L=0'),
('_Blob', '_-Blob', 'self["KeySize"]'),
('Blob', ':', DPAPI_BLOB),
)
def dump(self):
print("[VAULT_VPOL]")
print("Version : %8x (%d)" % (self['Version'], self['Version']))
print("Guid : %s" % bin_to_string(self['Guid']))
print("Description : %s" % (self['Description'].decode('utf-16le')))
print("Size : 0x%.8x (%d)" % (self['Size'], self['Size']))
print("Guid2 : %s" % bin_to_string(self['Guid2']))
print("Guid3 : %s" % bin_to_string(self['Guid3']))
print("KeySize : 0x%.8x (%d)" % (self['KeySize'], self['KeySize']))
self['Blob'].dump()
print()
# from bcrypt.h
class BCRYPT_KEY_DATA_BLOB_HEADER(Structure):
structure = (
('dwMagic','<L=0'),
('dwVersion','<L=0'),
('cbKeyData','<L=0'),
('_bKey','_-bKey', 'self["cbKeyData"]'),
('bKey',':'),
)
# from https://media.defcon.org/DEF%20CON%2024/DEF%20CON%2024%20presentations/DEFCON-24-Jkambic-Cunning-With-Cng-Soliciting-Secrets-From-Schannel-WP.pdf
class BCRYPT_KSSM_DATA_BLOB_HEADER(Structure):
structure = (
('cbLength','<L=0'),
('dwKeyMagic','<L=0'),
('dwUnknown2','<L=0'),
('dwUnknown3','<L=0'),
('dwKeyBitLen','<L=0'),
('cbKeyLength','<L=0'),
#('_bKey','_-bKey', 'self["cbKeyData"]'),
#('AesKey','32s=""'),
#('dwUnknown4','<L=0'),
#('KeySchedule','448s=""'),
#('dwUnknown5','<L=0'),
#('cbScheduleLen','<L=0'),
#('Unknown6','16s=""'),
)
class BCRYPT_KEY_WRAP(Structure):
structureKDBM = (
('Size','<L=0'),
('Version','<L=0'),
('Unknown2','<L=0'),
('_bKeyBlob','_-bKeyBlob', 'self["Size"]'),
('bKeyBlob',':', BCRYPT_KEY_DATA_BLOB_HEADER),
)
structureKSSM = (
('Size','<L=0'),
('Version','<L=0'),
('Unknown2','<L=0'),
('_bKeyBlob','_-bKeyBlob', 'self["Size"]-8'),
('bKeyBlob',':'),
)
def __init__(self, data = None, alignment = 0):
if len(data) >=16:
if data[0:1] == b'\x24' or data[0:1] == b'\x34':
self.structure = self.structureKDBM
else:
self.structure = self.structureKSSM
Structure.__init__(self, data, alignment)
class VAULT_VPOL_KEYS(Structure):
structure = (
('Key1',':', BCRYPT_KEY_WRAP),
('Key2',':', BCRYPT_KEY_WRAP),
)
def dump(self):
print("[VAULT_VPOL_KEYS]")
if self['Key1']['Size'] > 0x24:
print('Key1:')
hexdump(self['Key1']['bKeyBlob'])
print('Key2:')
hexdump(self['Key2']['bKeyBlob'])
else:
print('Key1: 0x%s' % hexlify(self['Key1']['bKeyBlob']['bKey']).decode('latin-1'))
print('Key2: 0x%s' % hexlify(self['Key2']['bKeyBlob']['bKey']).decode('latin-1'))
print()
class VAULT_INTERNET_EXPLORER(Structure):
structure = (
('Version','<L=0'),
('Count','<L=0'),
('Unknown','<L=0'),
('Id1', '<L=0'),
('UsernameLen', '<L=0'),
('_Username', '_-Username','self["UsernameLen"]'),
('Username', ':'),
('Id2', '<L=0'),
('ResourceLen', '<L=0'),
('_Resource', '_-Resource', 'self["ResourceLen"]'),
('Resource', ':'),
('Id3', '<L=0'),
('PasswordLen', '<L=0'),
('_Password', '_-Password', 'self["PasswordLen"]'),
('Password', ':'),
)
def fromString(self, data):
Structure.fromString(self, data)
def dump(self):
print("[Internet Explorer]")
print('Username : %s' % self['Username'].decode('utf-16le'))
print('Resource : %s' % self['Resource'].decode('utf-16le'))
print('Password : %s' % (hexlify(self['Password'])))
print()
class VAULT_WIN_BIO_KEY(Structure):
structure = (
('Version','<L=0'),
('Count','<L=0'),
('Unknown','<L=0'),
('Id1', '<L=0'),
('SidLen', '<L=0'),
('_Sid', '_-Sid','self["SidLen"]'),
('Sid', ':'),
('Id2', '<L=0'),
('NameLen', '<L=0'),
('_Name', '_-Name', 'self["NameLen"]'),
('Name', ':'),
('Id3', '<L=0'),
('BioKeyLen', '<L=0'),
('_BioKey', '_-BioKey', 'self["BioKeyLen"]'),
('BioKey', ':'),
)
def fromString(self, data):
Structure.fromString(self, data)
if data is not None:
bioKey = BCRYPT_KEY_DATA_BLOB_HEADER(unhexlify(self['BioKey'].decode('utf-16le')[:-1]))
self['BioKey'] = bioKey
def dump(self):
print("[WINDOWS BIOMETRIC KEY]")
print('Sid : %s' % RPC_SID(b'\x05\x00\x00\x00'+self['Sid']).formatCanonical())
print('Friendly Name: %s' % self['Name'].decode('utf-16le'))
print('Biometric Key: 0x%s' % (hexlify(self['BioKey']['bKey'])).decode('latin-1'))
print()
class NGC_LOCAL_ACCOOUNT(Structure):
structure = (
('Version','<L=0'),
('UnlockKeySize','<L=0'),
('IVSize','<L=0'),
('CipherTextSize','<L=0'),
('MustBeZeroTest','<L=0'),
('_UnlockKey', '_-UnlockKey', 'self["UnlockKeySize"]'),
('UnlockKey', ':'),
('_IV', '_-IV', 'self["IVSize"]'),
('IV', ':'),
('_CipherText', '_-CipherText', 'self["CipherTextSize"]'),
('CipherText', ':'),
)
# def __init__(self, data=None, alignment = 0):
# hexdump(data)
def dump(self):
print("[NGC LOCAL ACCOOUNT]")
print('UnlockKey : %s' % hexlify(self["UnlockKey"]))
print('IV : %s' % hexlify(self["IV"]))
print('CipherText : %s' % hexlify(self["CipherText"]))
class VAULT_NGC_ACCOOUNT(Structure):
structure = (
('Version','<L=0'),
('Count','<L=0'),
('Unknown','<L=0'),
('Id1', '<L=0'),
('SidLen', '<L=0'),
('_Sid', '_-Sid','self["SidLen"]'),
('Sid', ':'),
('Id2', '<L=0'),
('NameLen', '<L=0'),
('_Name', '_-Name', 'self["NameLen"]'),
('Name', ':'),
('Id3', '<L=0'),
('BlobLen', '<L=0'),
('Blob', '_-Blob', 'self["BlobLen"]'),
('Blob', ':', NGC_LOCAL_ACCOOUNT),
)
def dump(self):
print("[NGC VAULT]")
print('Sid : %s' % RPC_SID(b'\x05\x00\x00\x00'+self['Sid']).formatCanonical())
print('Friendly Name: %s' % self['Name'].decode('utf-16le'))
self['Blob'].dump()
print()
VAULT_KNOWN_SCHEMAS = {
'WinBio Key': VAULT_WIN_BIO_KEY,
'NGC Local Accoount Logon Vault Credential': VAULT_NGC_ACCOOUNT,
'Internet Explorer': VAULT_INTERNET_EXPLORER,
}
class CREDENTIAL_ATTRIBUTE(Structure):
# some info here https://docs.microsoft.com/en-us/windows/desktop/api/wincred/ns-wincred-_credential_attributea
structure = (
('Flags','<L=0'),
('KeyWordSize', '<L=0'),
('_KeyWord', '_-KeyWord', 'self["KeyWordSize"]'),
('KeyWord', ':'),
('DataSize', '<L=0'),
('_Data', '_-Data', 'self["DataSize"]'),
('Data', ':'),
)
def dump(self):
print("KeyWord : %s" % (self['KeyWord'].decode('utf-16le')))
#print("Flags : %8x (%s)" % (self['Flags'], getFlags(CREDENTIAL_FLAGS, self['Flags'])))
print("Data : ")
hexdump(self['Data'])
class CREDENTIAL_BLOB(Structure):
# some info here https://docs.microsoft.com/en-us/windows/desktop/api/wincred/ns-wincred-_credentiala
structure = (
('Flags','<L=0'),
('Size','<L=0'),
('Unknown0','<L=0'),
('Type','<L=0'),
('Flags2','<L=0'),
('LastWritten','<Q=0'),
('Unknown2','<L=0'),
('Persist','<L=0'),
('AttrCount','<L=0'),
('Unknown3','<Q=0'),
('TargetSize','<L=0'),
('_Target','_-Target','self["TargetSize"]'),
('Target',':'),
('TargetAliasSize', '<L=0'),
('_TargetAliast', '_-TargetAlias', 'self["TargetAliasSize"]'),
('TargetAlias', ':'),
('DescriptionSize', '<L=0'),
('_Description', '_-Description', 'self["DescriptionSize"]'),
('Description', ':'),
('UnknownSize', '<L=0'),
('_Unknown', '_-Unknown', 'self["UnknownSize"]'),
('Unknown', ':'),
('UsernameSize', '<L=0'),
('_Username', '_-Username', 'self["UsernameSize"]'),
('Username', ':'),
('Unknown3Size', '<L=0'),
('_Unknown3', '_-Unknown3', 'self["Unknown3Size"]'),
('Unknown3', ':'),
('Remaining', ':'),
)
def __init__(self, data = None, alignment = 0):
Structure.__init__(self, data, alignment)
self.attributes = 0
if data is not None:
# Unpack the attributes
remaining = self['Remaining']
self.attributes = list()
for i in range(self['AttrCount']):
attr = CREDENTIAL_ATTRIBUTE(remaining)
self.attributes.append(attr)
remaining = remaining[len(attr):]
def dump(self):
print("[CREDENTIAL]")
print("LastWritten : %s" % (datetime.fromtimestamp(getUnixTime(self['LastWritten']), tz=timezone.utc)))
print("Flags : 0x%.8x (%s)" % (self['Flags'], getFlags(CREDENTIAL_FLAGS, self['Flags'])))
print("Persist : 0x%.8x (%s)" % (self['Persist'], CREDENTIAL_PERSIST(self['Persist']).name))
print("Type : 0x%.8x (%s)" % (self['Type'], CREDENTIAL_TYPE(self['Type']).name))
print("Target : %s" % (self['Target'].decode('utf-16le')))
print("Description : %s" % (self['Description'].decode('utf-16le')))
print("Unknown : %s" % (self['Unknown'].decode('utf-16le')))
print("Username : %s" % (self['Username'].decode('utf-16le')))
try:
print("Unknown : %s" % (self['Unknown3'].decode('utf-16le')))
except UnicodeDecodeError:
print("Unknown : %s" % (self['Unknown3'].decode('latin-1')))
print()
for entry in self.attributes:
entry.dump()
ALG_ID = '<L=0'
class P_BACKUP_KEY(Structure):
structure = (
('Version', '<L=0'),
('Data', ':'),
)
class PREFERRED_BACKUP_KEY(Structure):
structure = (
('Version', '<L=0'),
('KeyLength', '<L=0'),
('CertificateLength', '<L=0'),
('Data', ':'),
)
class PVK_FILE_HDR(Structure):
structure = (
('dwMagic', '<L=0'),
('dwVersion', '<L=0'),
('dwKeySpec', '<L=0'),
('dwEncryptType', '<L=0'),
('cbEncryptData', '<L=0'),
('cbPvk', '<L=0'),
)
class PUBLICKEYSTRUC(Structure):
structure = (
('bType', '<B=0'),
('bVersion', '<B=0'),
('reserved', '<H=0'),
('aiKeyAlg', ALG_ID),
)
class RSAPUBKEY(Structure):
structure = (
('magic', '<L=0'),
('bitlen', '<L=0'),
('pubexp', '<L=0'),
)
class PUBLIC_KEY_BLOB(Structure):
structure = (
('publickeystruc', ':', PUBLICKEYSTRUC),
('rsapubkey', ':', RSAPUBKEY),
('_modulus', '_-modulus', 'self["rsapubkey"]["bitlen"] // 8'),
)
class PRIVATE_KEY_BLOB(Structure):
structure = (
('publickeystruc', ':', PUBLICKEYSTRUC),
('rsapubkey', ':', RSAPUBKEY),
('_modulus', '_-modulus', 'self["rsapubkey"]["bitlen"] // 8'),
('modulus', ':'),
('_prime1', '_-prime1', 'self["rsapubkey"]["bitlen"] // 16'),
('prime1', ':'),
('_prime2', '_-prime2', 'self["rsapubkey"]["bitlen"] // 16'),
('prime2', ':'),
('_exponent1', '_-exponent1', 'self["rsapubkey"]["bitlen"] // 16'),
('exponent1', ':'),
('_exponent2', '_-exponent2', 'self["rsapubkey"]["bitlen"] // 16'),
('exponent2', ':'),
('_coefficient', '_-coefficient', 'self["rsapubkey"]["bitlen"] // 16'),
('coefficient', ':'),
('_privateExponent', '_-privateExponent', 'self["rsapubkey"]["bitlen"] // 8'),
('privateExponent', ':'),
)
class SIMPLE_KEY_BLOB(Structure):
structure = (
('publickeystruc', ':', PUBLICKEYSTRUC),
('algid', ALG_ID),
('encryptedkey', ':'),
)
class DPAPI_DOMAIN_RSA_MASTER_KEY(Structure):
structure = (
('cbMasterKey', '<L=0'),
('cbSuppKey', '<L=0'),
('buffer', ':'),
)
def privatekeyblob_to_pkcs1(key):
'''
parse private key into pkcs#1 format
:param key:
:return:
'''
modulus = bytes_to_long(key['modulus'][::-1]) # n
prime1 = bytes_to_long(key['prime1'][::-1]) # p
prime2 = bytes_to_long(key['prime2'][::-1]) # q
exp1 = bytes_to_long(key['exponent1'][::-1])
exp2 = bytes_to_long(key['exponent2'][::-1])
coefficient = bytes_to_long(key['coefficient'][::-1])
privateExp = bytes_to_long(key['privateExponent'][::-1]) # d
if PY3:
long = int
pubExp = long(key['rsapubkey']['pubexp']) # e
# RSA.Integer(prime2).inverse(prime1) # u
r = RSA.construct((modulus, pubExp, privateExp, prime1, prime2))
return r
def deriveKeysFromUser(sid, password):
# Will generate two keys, one with SHA1 and another with MD4
key1 = HMAC.new(SHA1.new(password.encode('utf-16le')).digest(), (sid + '\0').encode('utf-16le'), SHA1).digest()
key2 = HMAC.new(MD4.new(password.encode('utf-16le')).digest(), (sid + '\0').encode('utf-16le'), SHA1).digest()
# For Protected users
tmpKey = pbkdf2_hmac('sha256', MD4.new(password.encode('utf-16le')).digest(), sid.encode('utf-16le'), 10000)
tmpKey2 = pbkdf2_hmac('sha256', tmpKey, sid.encode('utf-16le'), 1)[:16]
key3 = HMAC.new(tmpKey2, (sid + '\0').encode('utf-16le'), SHA1).digest()[:20]
return [key1, key2, key3]
def deriveKeysFromUserkey(sid, pwdhash):
if len(pwdhash) == 20:
# SHA1
key1 = HMAC.new(pwdhash, (sid + '\0').encode('utf-16le'), SHA1).digest()
return [key1]
# Assume MD4
key1 = HMAC.new(pwdhash, (sid + '\0').encode('utf-16le'), SHA1).digest()
# For Protected users
tmpKey = pbkdf2_hmac('sha256', pwdhash, sid.encode('utf-16le'), 10000)
tmpKey2 = pbkdf2_hmac('sha256', tmpKey, sid.encode('utf-16le'), 1)[:16]
key2 = HMAC.new(tmpKey2, (sid + '\0').encode('utf-16le'), SHA1).digest()[:20]
return [key1, key2]