Last active
June 17, 2022 14:38
-
-
Save NTICompass/980161a50f5053ce0e9c2b48425b5340 to your computer and use it in GitHub Desktop.
BEK File Decoder
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/env python3 | |
""" | |
PyLocker: A python program to do stuff with BitLocker drives & files. | |
bek_file.py: Prints info about BEK files (startup keys) | |
""" | |
import struct, binascii | |
# filestimes.py from http://reliablybroken.com/b/2009/09/working-with-active-directory-filetime-values-in-python/ | |
from filetimes import filetime_to_dt | |
class StartupKey: | |
def __init__(self, file): | |
# Open the BEK file in binary mode and give me a binary string | |
with open(file, 'rb') as data: | |
self.bek = data.read() | |
""" | |
Thanks to https://github.com/libyal/libbde for info on BEK files | |
""" | |
def decode(self): | |
bek_file = { | |
'header': None, | |
'data': None | |
} | |
index = 0 | |
# BEK File header: | |
# https://github.com/libyal/libbde/blob/master/documentation/BitLocker%20Drive%20Encryption%20(BDE)%20format.asciidoc#61-bek-file-header-version-1 | |
# The 1st 4 bytes are the size of the BEK file itself | |
# Next comes the "version", currently it's always 1 | |
# Then it's the "header" size, this is always 48 | |
# After, for some reason, we have a *copy* of the file size | |
fileSize, version, headerSize, fileSizeCopy = struct.unpack('<IIII', self.bek[index:index+16]) | |
index += 16 | |
# Before continuing, let's do a simple sanity check | |
if not (fileSize == fileSizeCopy and version == 1 and headerSize == 48): | |
raise ValueError("Header value mismatch!") | |
# Volume Identifier, which is used as the (Recovery Key) GUID | |
# Formatted as: XXXXXXXX-YYYY-YYYY-YYYY-ZZZZZZZZZZZZ | |
# (That's 8-4-4-4-12) | |
# The first 3 segments are stored as little-endian, | |
# and the rest are big-endian. | |
volumeID = self.bek[index:index+16] | |
index += 16 | |
# little-endian | |
# unsigned int, unsigned short, unsigned short | |
segments = list(struct.unpack('<IHH', volumeID[0:8])) | |
# big-endian unsigned short, unsigned long long | |
# Last segment requires 8 bytes, but we only have 6 | |
segments.extend(struct.unpack('>HQ', volumeID[8:10]+bytes(2)+volumeID[10:])) | |
# Combine the segments together | |
guid = '-'.join(format(segment, 'X') for segment in segments) | |
# Nonce Counter followed by Encryption Method | |
# Encryption Method Should be 0x0000 since this is an "external key" | |
nonceCounter, encMethod = struct.unpack('<II', self.bek[index:index+8]) | |
index += 8 | |
if encMethod != 0: | |
raise ValueError("File does not contain external key!") | |
# The creation type is stored as a `FILETIME` | |
# https://msdn.microsoft.com/en-us/library/windows/desktop/ms724284(v=vs.85).aspx | |
creationFileTime, = struct.unpack('<Q', self.bek[index:index+8]) | |
index += 8 | |
creationTime = filetime_to_dt(creationFileTime) | |
# We're at 48 bytes! That's the header. | |
bek_file['header'] = { | |
'metadataSize': fileSize, | |
'version': version, | |
'metadataHeaderSize': headerSize, | |
'guid': guid, | |
'nonceCounter': nonceCounter, | |
'encMethod': encMethod, | |
'creationTime': creationTime | |
} | |
# Next is the data! | |
# It's formatted like "FVE metadata" | |
# https://github.com/libyal/libbde/blob/master/documentation/BitLocker%20Drive%20Encryption%20(BDE)%20format.asciidoc#53-fve-metadata-entry-version-1 | |
# The data is a "multi-part" data file. By that I mean it's multiple "metadata" entries | |
# First there's an entry with the GUID (again) and the last modification time of the key | |
# And then a 2nd entry with the actual key data | |
# Let's start with entry number 1 | |
# entryType should be 0x0006, which means "Startup Key" | |
# valueType should be 0x0009, which means "External Key" | |
entrySize, entryType, valueType, version = struct.unpack('<HHHH', self.bek[index:index+8]) | |
index += 8 | |
if not(entryType == 6 and valueType == 9): | |
raise ValueError("Data does not contain startup/external key!") | |
# Okay, I was little wrong in the above comments | |
# How this works is that "external key" entry *contains* | |
# The GUID and modification time | |
# as well as an array of FVE metadata entries | |
# https://github.com/libyal/libbde/blob/master/documentation/BitLocker%20Drive%20Encryption%20(BDE)%20format.asciidoc#510-fve-external-key | |
# Let's get the GUID, again | |
volumeID = self.bek[index:index+16] | |
index += 16 | |
segments = list(struct.unpack('<IHH', volumeID[0:8])) | |
segments.extend(struct.unpack('>HQ', volumeID[8:10]+bytes(2)+volumeID[10:])) | |
guid = '-'.join(format(segment, 'X') for segment in segments) | |
# And the modification time | |
modificationFileTime, = struct.unpack('<Q', self.bek[index:index+8]) | |
index += 8 | |
modificationTime = filetime_to_dt(modificationFileTime) | |
# Now the rest of the bytes (from `entrySize`) are the key data | |
# Maybe I should pull out all "entrySize" bytes and then parse that, | |
# but it seems it's just the rest of the file, so let's just decode | |
# the "next" entry | |
# Now we have an array of "properties", which means that | |
# entryType will be 0x0000 | |
# and valueType will tell us what the data is and what to do with it | |
# In my case, it seems we only have one property, | |
# and that's the key data (valueType = 0x0001) | |
entrySize, entryType, valueType, version = struct.unpack('<HHHH', self.bek[index:index+8]) | |
index += 8 | |
if not(entryType == 0 and valueType == 1): | |
raise ValueError("Data does not contain key property!") | |
# Since it seems like there is no more data/properties, the rest of the | |
# file is the key. We do have `entrySize`, so I can check that. | |
# The key data is the encryption method followed by the key itself | |
# https://github.com/libyal/libbde/blob/master/documentation/BitLocker%20Drive%20Encryption%20(BDE)%20format.asciidoc#54-fve-key | |
# The encryption methods are listed here: | |
# https://github.com/libyal/libbde/blob/master/documentation/BitLocker%20Drive%20Encryption%20(BDE)%20format.asciidoc#521-encryption-methods | |
encryptionMethod, = struct.unpack('<I', self.bek[index:index+4]) | |
index += 4 | |
keyData = self.bek[index:] | |
bek_file['data'] = { | |
'guid': guid, | |
'modificationTime': modificationTime, | |
'encryptionMethod': hex(encryptionMethod), | |
'key': binascii.hexlify(keyData).decode('ascii') | |
} | |
return bek_file | |
if __name__ == '__main__': | |
key = StartupKey('./StartupKey.bek') | |
try: | |
data = key.decode() | |
except ValueError as e: | |
print("Error:", e) | |
else: | |
print(data) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment