summaryrefslogtreecommitdiff
path: root/foreign/client_handling/lazagne/config/DPAPI/vault.py
diff options
context:
space:
mode:
Diffstat (limited to 'foreign/client_handling/lazagne/config/DPAPI/vault.py')
-rw-r--r--foreign/client_handling/lazagne/config/DPAPI/vault.py489
1 files changed, 489 insertions, 0 deletions
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