Introduction

BitLocker can simplify the boot process with the usage TPM along Secure Boot: TPM can store and protect the complex key to unlock BitLocker volume.

Different configurations that involves the TPM exist:

  • key protected by TPM
  • key protected by TPM and a PIN

Several publication deal with the case of key protected by TPM only. Above some of them:

The goal of this article is to exploit a default of discrete TPM (also called dTPM) to recover the VMK to decrypt a BitLocker volume. The exploited default of dTPM is the exposure of its communication bus (e.g., SPI, LPC or i2c) that allows intercepting of traffic between TPM and the system.

Different scripts are issued to help the VMK recovery process and they can be found in the following repository: https://github.com/post-cyberlabs/VMK-extractor-for-bitlocker-with-tpm-and-pin

TPM data capture

Pinout for logic analyzer

The test was performed on HP EliteBook 840 G6 with Windows 10. The disk is BitLocker locked by the TPM and PIN protector.

In order to locate the TPM, a visual inspection can be done on the motherboard to locate the TPM chipset.

Another way can be the usage of motherboard schematics. This method has several advantages:

  • Locate TPM using keyword
  • Find alternative location to connect probe. Depending on the TPM component, the connectors can be very small that require more precision to obtain a good capture and avoid vibration to avoid disconnection.

The motherboard schematics can be obtained by searching it in repair forums such as Badcaps

Once the schematics was obtained, the software OpenBoardView can be used.

In our case, the TPM module is located near the M.2 SSD connector.

OpenBoardView

Laptop motherboard

The visual inspection gives the module reference. The used TPM module is an infineon SLB 9670VQ2.0.

The datasheet permits to obtain the chip characterics and pinout.

Pinout of infineon SLB 9670VQ2.0

The chip can use SPI bus to communicate. A SPI bus can be shared between chip. The wanted ship is activated using Chip Select (aka CS). Each chip has its own CS that is not shared with other chips.

As the pin of TPM are very small, alternative locations found for the following pins using OpenBoardView to see direct connection:

  • MISO
  • MOSI
  • SCLK
  • GND

Looking for alternative ping for probe

The final pinout for the logic analyzer will the following. As the Chip Select is not shared by another chip due to its nature, the probe will be connected directly on the CS pin of TPM.

Pinout for logic analyzer

Data capture

During the first attempts, the logic analyzer, Saleae Logic 8, was used but unfortunately the captured data is not exploitable. According to the TPM datasheet, the clock frequency can reach up to 43 MHz. On the other hand, the Saleae Logic support up to 25 MHz according to its datasheet.

To address this situation, another logic analyzer is used: DSLogic U3Pro16. It cost about 300$ compared to the Saleae Logic Pro 8 that costs around 1000$ and support the higher TPM frequency.

Connection of logic analyzer on laptop motherboard

Once the logic analyzer is connected to the laptop’s motherboard, a capture of TPM traffic was done during a normal boot process.

Capture of TPM traffic in DSView

The capture with DreamSourceLab analyzer confirms that the clock frequency (about 30 MHz) is greater than the limit of 25 MHz of our Saleae analyzer.

The software DSView has a native decoder for TPM transaction over SPI bus.

The interesting TPM traffic is the exchange of TPM objects in TPM_DATA_FIFO_0. A script was written to extract these exchanged objects and decoded with the help of tpmstream library. The lib authors make available a web application tpmstream web that decodes TPM objects using the library.

The interesting object is the response to the unseal command.

Decoding response to unseal command

The VMK should be in the following format according to the different article about TPM sniffing such as TPM 2.0: Extracting Bitlocker keys through SPI:

  • 2C000[0-6]000[1-9]000[0-1]000[0-5]200000(\w{64})

Unfortunately, the collected sensitive data is not in the expected format.

BitLocker internal for TPMAndPIN protector

A reverse engineering of the bootloader was performed to understand the mechanism in-place and the role of the extracted data. The analysis approach will be static with the analysis of EFI binary and dynamic with the usage of the debugger attached to the bootloader.

Identification of EFI binary

According to a response to Stackoverflow, the boot loader should be a file called bootmgfw.efi.

After investigation, this file is located in the boot partition. To obtain this file, a solution can mount the boot partition and grab the wanted file.

As the boot partition is special partition, the partition is not mounted automatically. But it is still possible to mount it manually using the following instructions:

  1. launch CMD as administrator
  2. execute diskpart to enter in diskpart’s shell
  3. identify the disk where the boot partition is located with the command list disk
  4. enter select disk 0
  5. list partition with the command list partition
  6. choose the partition with the command select partition <NUMBER>
  7. Mount the boot partition as drive with the command assign letter=B

The bootloader binary is located at B:\EFI\Microsoft\Boot\bootmgfw.efi.

The result, obtained during the analysis, is based on the bootmgfw.efi with the following SHA256 checksum: 0E7B03DA8730958DEBEB85F5D7A0BDAF0A7B9AE76FB51B252664E0F3AFA91F27

During the analysis, the binary has changed several times that impact the address of breakpoints. Hence, the auto-update was disabled to avoid this inconvenience.

Fortunately, the PDB was provided by Microsoft that permits to simplify the analysis.

Open the binary in the disassembler Binary Ninja

The function FveDatumGetVmk() seems to be a good starting point to understand the unlock process for BitLocker.

Attach a debugger on bootloader

To help us during the reverse engineering process, the debugging of bootloader can be done using QEMU with a specific configuration to allow the remote debugging of bootloader.

This article contains interesting information to configure our QEMU hypervisor: Setting up gdb to work with qemu-kvm via libvirt

The domain XML of machines with BitLocker was modified to enable remote debugging.

<domain xmlns:qemu="http://libvirt.org/schemas/domain/qemu/1.0" type="kvm">
  <name>win11-bitlocker</name>
  [***REDACTED***]
  <qemu:commandline>
    <qemu:arg value="-s"/>
  </qemu:commandline>
</domain>
  1. The schema was modified with xmlns:qemu="http://libvirt.org/schemas/domain/qemu/1.0"
  2. The QEMU command was modified using the section qemu:commandline

The usage of the schema http://libvirt.org/schemas/domain/qemu/1.0 is required to customize the QEMU command using qemu:commandline section.

To setup the different breakpoint, a GDB script was written and completed with other elements during the analysis:

Here the initial GDB script:

set pagination off

b * 0x100e143c
commands
printf "call FveDatumGetVmk\n"
printf "- Return address: %p\n",  *((void**)($rsp))
i r rcx rdx r8 r9
end

b * 0x100e45dc
commands
printf "call FveVmkInfoProcess\n"
printf "- Return address: %p\n",  *((void**)($rsp))
i r rcx rdx r8 r9
c
end

target remote localhost:1234
c

To load the GDB script, the following command is used:

gdb -x gdb_script_tpm.txt

From this analysis experience, the best moment to attach the debugger is during the BitLocker prompt that is asking to enter the PIN code. Otherwise, the different breakpoint may not be triggered.

Moment when the debugger was attached

Once the debugger is attached, the breakpoint of FveDatumGetVmk function was triggered:

GDB attached remotely to QEMU VM

Exploiting TPM data and BitLocker metadata to extract VMK

Static and dynamic tools are ready to start the analysis. But first, some background on BitLocker is required before further investigation.

BitLocker object format

BitLocker is a Microsoft solution to have a full-encryption disk. The key architecture permits to have multiple method, called key protector to unlock the disk:

  • Password
  • TPM only
  • USB key
  • TPM + USB key
  • TPM + PIN + USB key

Each Key Protector permits to unlock a Volume Master Key. This VMK in turn permits to unlock Full Volume Encryption Key (FVEK).

Several projects have already done through documentation or implementation of BitLocker data structure:

In addition, some articles speak about the storage of BitLocker metadata on disk:

The header of BitLocker volume contains the disk identifier and the address of 3 metadata blocks. These blocks are identical and seem to be created for redundancy in case of a sector fault.

The GitHub project of libbde gives us a good document to find and parse the metadata blocks.

A BitLocker volume contains 3 FVE metadata blocks. Each FVE metadata block consists of:
  * a block header
  * a metadata header
  * an array of metadata entries
  * padding (0-byte values) (seen in Windows 8)

The main metadata entry list contains:

  • The volume GUID
  • Its name
  • The encrypted Full Volume Encryption Key (FVEK)
  • Different protectors (Recovery key, TPM, TPM and PIN, etc.)

The protector entry contains:

  • Key GUID
  • Protection type
  • Properties that help to unlock the VMK

Among the different properties, two interesting ones:

  • The key
  • The AES-CCM encrypted key

The key format can be presented as follows:

Schema of key entry

This format is the format of VMK, KP or FVEK.

The AES-CCM encrypted can be presented as follows:

Schema of entry of AES-CCM encrypted key

The TPM data appears to be an AES-CCM encrypted key.

Overview of the process to obtain VMK from TPM data

Using the debugger and the disassembler, a global schema about the obtaining of VMK can be done. This analysis was started from the usage of the function FveDatumAesCcmEncUnbox() as the data from TPM is a key encrypted by AES CCM algorithm. The value type is 0x0005 (FVE_DATUM_AESCCM_ENC).

Process of VMK decrypt

This overview permits to trace the path from TPM data to VMK.

There are several to obtain VMK:

  1. Extract TPM data. The TPM data is encrypted Key Protector (aka KP).
  2. Generate the decryption key of KP
  3. Decrypt KP
  4. Extracting encrypted VMK
  5. Decrypt VMK using KP

Generate decryption key for KP

The decryption key for KP is a SHA256 hash on initial data and the PIN code with 0x100000 iterations.

The initial data are composed of:

  • 32 zeroed bytes
  • hashed PIN code of 32 bytes: the PIN encoded in UTF-16LE is hashed twice with SHA256
  • Salt of 16 bytes: extract of a stretch key
  • 8 bytes iteration counter initialized at 0.

During our analysis, there are two stretch keys. By comparing with the value obtained with the VM debugging, the correct value can be selected based on the size of stretch key properties.

Extract of FVE metadata using the script info

One has a size of 0x6C bytes and the other one 0xAC. The wanted size is 0x6C bytes.

Initial data for key generation

For each iteration, the first 32 bytes (in red) is updated with the SHA256 hash result and the number in little-endian on the last 8 bytes (in yellow) is increased by one. The other parts remain fixed.

The following python codes permits to generate the key:

def get_salt_for_key1(self) -> bytes:
    result = None
    vmk = self.get_vmk_for_TPM_with_PIN()
    for property in vmk.properties:
        if property.entry_type is EntryType.PROPERTY and property.value_type is EntryValueType.STRETCH_KEY and property.size == 0X6C:
            return property.data[4:20]
    raise ValueError("Unable to find salt for key1")

def generate_key1(self, pin: str):
    """Generate KEY 1 
    """
    data = bytearray(b'\x00' * 0x20)

    data += bytearray(sha256(sha256(pin.encode('UTF-16LE')).digest()).digest())
    data += bytearray(self.get_salt_for_key1())

    data += bytearray(b'\x00' * 0x8)

    assert len(data) == 0x58

    for idx in range(0x100000):
        data = bytearray(sha256(bytes(data)).digest()) \
            + data[0x20:0x50] + pack("<Q", idx + 1)

    return bytes(data[:0x20])

Once the key is generated, the encrypted key unsealed by the TPM will be decrypted.

Decrypt KP

As the value type of the TPM data is AES-CCM encrypted key, we got the encryption algorithm, AES, with its CCM mode. The CCM mode has a fix block size of 128 bits

The missing argument is the nonce. The analysis of FveAesCcmDecrypt() permits to get the role of function parameters.

Loading nonce

The register r14 contains the address of AES-CCM encrypted key that is sent to FveDatumAesCcmEncUnbox() function as the first argument.

Value of r14

The instruction at 0x100e4532 address permits to save the nonce value at the 2nd parameter (rcx) of the FveAesCcmDecrypt() call.

To conclude, its data is 12 bytes size from the 8th position of the entry of AES-CCM encrypted key. This value corresponds to the concatenation of the timestamp and the counter.

Finally, the python library, PyCryptodome is used to decrypt the KP.

def decode_key_protector_container(self, key, encoded_KP_from_tpm):
    encoded_data = FveEntry.load_from_data(encoded_KP_from_tpm)
    cipher = AES.new(key, AES.MODE_CCM, nonce=encoded_data.loaded_data.nonce)
    return cipher.decrypt(encoded_data.loaded_data.encrypted_data)

Extract encrypted VMK

Once the KP is decrypted, it can be used to decrypt the VMK. First, the encrypted VMK must be retrieved.

Using the VM debugging, the encrypted VMK is identified. The encrypted VMK is in an AES-CCM encrypted property. Same as previously, there are two properties. A way to get the correct property is finding the property of the value type 0x13 and the next property is the encrypted VMK.

Extract encrypted VMK

def get_encrypted_VMK(self) -> bytes:
    encrypted_data : FveEntry = None
    vmk = self.get_vmk_for_TPM_with_PIN()
    for idx in range(len(vmk.properties)):
        property = vmk.properties[idx]
        if property.entry_type is EntryType.PROPERTY and property.value_type is EntryValueType.UNK13:
            encrypted_data = vmk.properties[idx+1]
    return encrypted_data.raw

Decrypt VMK

Like the encrypted KP, the encrypted VMK is protected by AES-CCM. The KP is the key to decrypt the VMK.

Using the script decode_tpm_data.py to decrypt VMK

Once the VMK obtained, the BitLocker can be mounted using dislocker.

First, the key can be saved in a file:

echo -n XXXXXXXXXXXXXXXX | xxd -r -p > vmk.bin

Then, the following command can be used to mount the volume:

sudo mkdir /mnt/dislocker_tmp
sudo mkdir /mnt/dislocker_tmp2
sudo dislocker -K vmk.bin /dev/vdc3 /mnt/dislocker_tmp
sudo mount -t ntfs-3g -o remove_hiberfile,recover /mnt/dislocker_tmp/dislocker-file /mnt/dislocker_tmp2

Remediation

Use Firmware TPM (also called fTPM) included in modern CPU of several brands:

  • For Intel processor: Intel PTT
  • For AMD processor: AMD fTPM

To select the fTPM, please refer to your vendor manual. For example, in the case of Dell machine, this change can be made in BIOS menu.

The change of TPM will break your TPM and PIN protector. Please backup the recovery key for the next boot after the change.

fTPM is harder to tap than dTPM via its bus such as SPI or i2c. But keep in mind that the fTPM is not a bulletproof solution: attacks are still possible but harder to realize such faulTPM.

Conclusion

As the PIN code is required for the attack, the more critical and likely impact can be the Local Privilege Escalation performed by an insider.

By the way, the solution, offered by dTPM and PIN, still giving a good protection against the opportunistic scenario of a stolen laptop.