Skip to content

Instantly share code, notes, and snippets.

@NTICompass
Last active June 17, 2022 14:38
Show Gist options
  • Save NTICompass/980161a50f5053ce0e9c2b48425b5340 to your computer and use it in GitHub Desktop.
Save NTICompass/980161a50f5053ce0e9c2b48425b5340 to your computer and use it in GitHub Desktop.
BEK File Decoder
#!/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