Skip to content

Instantly share code, notes, and snippets.

@marhensa
Last active May 12, 2025 08:29
Show Gist options
  • Save marhensa/5296c317b629d7778468c9d988c779df to your computer and use it in GitHub Desktop.
Save marhensa/5296c317b629d7778468c9d988c779df to your computer and use it in GitHub Desktop.
Mount and Unmount BitLocker Drive from Linux (Debian/Ubuntu Based)
# Disable Fast Startup in Windows (CRITICAL for read-write access & NTFS health)
# In Windows: Control Panel -> Power Options -> "Choose what the power buttons do"
# -> "Change settings that are currently unavailable" -> (UN)check "Turn on fast startup".
#
# If not doing that (i.e., if Fast Startup is LEFT ON), please restart Windows (not just shutdown),
# because in contrary, Windows shutdown with Fast Startup ON is not "clean"
# and possibly dangerous to use partition in this state on another machine.
# TL;DR: if Fast Startup is ON, then before accessing drives in Linux for read-write, use Restart from Windows.
# Restarting from Windows to Linux is a safe alternative to get a clean filesystem state.
# If Fast Startup is OFF, then a normal "Shut down" is also fine.
sudo apt update
sudo apt install gnupg ntfs-3g dislocker
##########
# For some reason, the apt repository hasn't been updated to the latest changes on Windows 11 24H2 about BitLocker
# (not being able to decrypt the OSV / Windows partition):
# "Error looking for the VIRTUALIZATION datum type 15 (VIRTUALIZATION INFO)"
# https://github.com/Aorimn/dislocker/issues/334
# If this happens, use this instead:
sudo apt remove dislocker
sudo apt install git gcc cmake make libfuse-dev libmbedtls-dev ruby-dev
cd ~
mkdir -p Labs && cd Labs
git clone https://github.com/Aorimn/dislocker.git && cd dislocker
mkdir build && cd build
cmake ..
make
sudo make install
which dislocker-fuse
dislocker-fuse -h
##########
nano ~/mount.sh
# Edit the # Drive definitions section, accordingly
## use `sudo blkid`
## and `lsblk -o NAME,FSTYPE,LABEL,PARTLABEL,PARTUUID,MOUNTPOINT`
## to determine the device and partition path in Linux
## "CUSTOMLABEL|DEVICE_PATH|RAW_MOUNT_POINT|FINAL_MOUNT_POINT"
## "c_system|/dev/nvme0n1p3|/mnt/raw-c_system|/mnt/c_system"
## "d_labs|/dev/nvme0n1p7|/mnt/raw-d_labs|/mnt/d_labs"
## ... so on
# Choose as Read-Only or Read/Write,
# on # Mount Options Configuration, the default set to Read-Only.
# MOUNT_MODE="ro"
# the option is: "rw_ntfs3" (modern), "rw_ntfs-3g", or "ro" (Read-Only)
# Save
chmod +x ~/mount.sh
nano ~/unmount.sh
# Edit the # Configuration section
# Should match `mount.sh` for its labels and mount points
# Save
chmod +x ~/unmount.sh
nano ~/.config/bitlocker_keys.conf
# Put your BitLocker keys for each partition here, each line for each partition
## For example:
## c_system:111111-111111-111111-111111-111111-111111-111111-111111
## d_labs:222222-222222-222222-222222-222222-222222-222222-222222
## ... so on
# Encrypt your `bitlocker_keys.conf`
gpg -c ~/.config/bitlocker_keys.conf
# Enter STRONG passphrase, this is your master key now.
# Remember it (and backup the .gpg file & passphrase securely)!
# Check the decryption of your key is valid or not:
gpg-connect-agent reloadagent /bye
gpg -d ~/.config/bitlocker_keys.conf.gpg
# Enter your passphrase
# Remove securely the plain-text (not encrypted) `bitlocker_keys.conf`:
shred -u ~/.config/bitlocker_keys.conf
# Main usage: Decrypt the BitLocker partition (using dislocker-fuse) and then mount the decrypted view
sudo ./mount.sh d_labs
# or
sudo ./mount.sh all
# It will be mounted on /mnt/(decrypted-bitlocker-partition)
# Ending session: Unmount the decrypted partition and raw BitLocker partition
sudo ./unmount.sh d_labs
# or
sudo ./unmount.sh all
# It will be not visible again on /mnt/ (but the mounting folder exist, ignore this)
#!/bin/bash
# Script to unlock and mount BitLocker drives using GPG-encrypted keys
# --- Determine Real User's Home Directory ---
if [ -n "$SUDO_USER" ]; then
# Ensure SUDO_USER is not root itself if script is run as "sudo su - root" then "./script"
# In that case, SUDO_USER might not be set, or might be the previous user.
# A more robust way for "sudo ./script"
if [ "$(id -u)" -eq 0 ] && [ -n "$SUDO_USER" ] && [ "$SUDO_USER" != "root" ]; then
REAL_HOME=$(getent passwd "$SUDO_USER" | cut -d: -f6)
else
# If run as root directly, or SUDO_USER is root, default to root's home for key file
# For this use case, if truly run as root, maybe /root/.config is intended.
REAL_HOME="$HOME" # This will be /root if script is "sudo ./script" and SUDO_USER wasn't set right
fi
else
REAL_HOME="$HOME" # Running as normal user
fi
# More direct check for "sudo ./script.sh" scenario:
INVOKING_USER_HOME="$HOME" # Home of the user whose sudo session it is, likely /root
if [ -n "$SUDO_USER" ] && [ "$SUDO_USER" != "root" ]; then
CANDIDATE_HOME=$(getent passwd "$SUDO_USER" | cut -d: -f6)
if [ -d "$CANDIDATE_HOME" ]; then # Check if the home directory actually exists
INVOKING_USER_HOME="$CANDIDATE_HOME"
fi
fi
# Use INVOKING_USER_HOME as it's more likely what's intended for user-specific config
# when script is run with "sudo ./script.sh"
# --- Configuration ---
ENCRYPTED_KEY_FILE="$INVOKING_USER_HOME/.config/bitlocker_keys.conf.gpg" # Path to your GPG encrypted key file
# Drive definitions: "LABEL|DEVICE_PATH|RAW_MOUNT_POINT|FINAL_MOUNT_POINT"
DRIVES=(
"c_system|/dev/nvme0n1p3|/mnt/raw-c_system|/mnt/c_system"
"d_labs|/dev/nvme0n1p7|/mnt/raw-d_labs|/mnt/d_labs"
"e_labsgames|/dev/nvme1n1p2|/mnt/raw-e_labsgames|/mnt/e_labsgames"
"f_games|/dev/sdb1|/mnt/raw-f_games|/mnt/f_games"
"g_data|/dev/sdc1|/mnt/raw-g_data|/mnt/g_data"
"h_dataactive|/dev/sda1|/mnt/raw-h_dataactive|/mnt/h_dataactive"
"i_dataarchive|/dev/sda2|/mnt/raw-i_dataarchive|/mnt/i_dataarchive"
)
# Determine target UID/GID for mount options
TARGET_UID=$(id -u) # Default to current user (root if script is sudo'd)
TARGET_GID=$(id -g)
if [ -n "$SUDO_UID" ]; then # If SUDO_UID is set (script run with sudo by a normal user)
TARGET_UID="$SUDO_UID"
fi
if [ -n "$SUDO_GID" ]; then # If SUDO_GID is set
TARGET_GID="$SUDO_GID"
fi
# --- Mount Options Configuration ---
# Choose your desired mount mode,
# the option is: "rw_ntfs3", "rw_ntfs-3g", or "ro" (Read-Only)
MOUNT_MODE="ro"
if [ "$MOUNT_MODE" == "rw_ntfs3" ]; then
MOUNT_TYPE="ntfs3"
MOUNT_OPTS="loop,rw,uid=$TARGET_UID,gid=$TARGET_GID,fmask=0022,dmask=0022,iocharset=utf8"
elif [ "$MOUNT_MODE" == "rw_ntfs-3g" ]; then
MOUNT_TYPE="ntfs-3g"
MOUNT_OPTS="loop,rw,uid=$TARGET_UID,gid=$TARGET_GID,fmask=0022,dmask=0022,allow_other,big_writes"
elif [ "$MOUNT_MODE" == "ro" ]; then
MOUNT_TYPE="" # Auto-detect, likely uses ntfs3 if available for loop ro
MOUNT_OPTS="ro,loop,uid=$TARGET_UID,gid=$TARGET_GID,allow_other"
else
echo "ERROR: Invalid MOUNT_MODE set. Defaulting to Read-Only."
MOUNT_TYPE=""
MOUNT_OPTS="ro,loop,uid=$TARGET_UID,gid=$TARGET_GID,allow_other"
fi
# --- End Mount Options Configuration ---
# Temporary storage for decrypted keys
DECRYPTED_KEYS_CONTENT=""
# --- Helper Functions ---
decrypt_key_file() {
if [ ! -f "$ENCRYPTED_KEY_FILE" ]; then
echo "ERROR: Encrypted key file not found: $ENCRYPTED_KEY_FILE"
echo "If you ran with sudo, \$HOME might be /root."
echo "Ensure the path is correct for the user who owns the key file."
return 1
fi
echo "Attempting to decrypt key file: $ENCRYPTED_KEY_FILE"
local gpg_user_opts=""
if [ -n "$SUDO_USER" ] && [ "$SUDO_USER" != "root" ]; then
gpg_user_opts="sudo -u $SUDO_USER"
fi
read -s -r -p "Enter GPG passphrase for $ENCRYPTED_KEY_FILE (for user ${SUDO_USER:-$(whoami)}): " gpg_passphrase
echo
DECRYPTED_KEYS_CONTENT=$($gpg_user_opts gpg --decrypt --quiet --batch --pinentry-mode loopback --passphrase-fd 0 "$ENCRYPTED_KEY_FILE" <<< "$gpg_passphrase" 2>/dev/null)
for i in $(seq 1 ${#gpg_passphrase}); do gpg_passphrase="${gpg_passphrase%?}#"; done
gpg_passphrase=""
if [ -z "$DECRYPTED_KEYS_CONTENT" ]; then
echo "ERROR: Failed to decrypt key file or file is empty. Check GPG passphrase or file permissions."
echo "Attempted to read: $ENCRYPTED_KEY_FILE"
return 1
fi
echo "Key file decrypted successfully (in memory)."
return 0
}
get_key_for_label_from_decrypted() {
local label="$1"
if [ -n "$DECRYPTED_KEYS_CONTENT" ]; then
echo "$DECRYPTED_KEYS_CONTENT" | grep "^${label}:" | cut -d':' -f2-
fi
}
unlock_and_mount() {
local label="$1"
local device_path="$2"
local raw_mp="$3"
local final_mp="$4"
local recovery_key=""
echo "-----------------------------------------------------"
echo "Processing: $label ($device_path)"
if mountpoint -q "$final_mp"; then
echo "$final_mp is already mounted. Skipping."
return 0
fi
mkdir -p "$raw_mp" "$final_mp"
if [ $? -ne 0 ]; then
echo "ERROR: Could not create mount points for $label. (run with sudo?)"
return 1
fi
if mountpoint -q "$raw_mp"; then
echo "$raw_mp is already a mountpoint. Attempting to use it."
else
recovery_key=$(get_key_for_label_from_decrypted "$label")
if [ -n "$recovery_key" ]; then
echo "Using decrypted key for $label."
dislocker-fuse -v -V "$device_path" -p"$recovery_key" -- "$raw_mp"
else
echo "Key for $label not found in decrypted content or decryption failed."
echo "This script expects the key file '$ENCRYPTED_KEY_FILE' to be GPG encrypted and accessible."
return 1
fi
if [ $? -ne 0 ]; then
echo "ERROR: dislocker-fuse failed for $label ($device_path)."
rmdir "$raw_mp" "$final_mp" 2>/dev/null
return 1
fi
echo "Dislocker layer mounted at $raw_mp"
fi
echo "Mounting decrypted filesystem to $final_mp..."
if [ -n "$MOUNT_TYPE" ]; then
mount -t "$MOUNT_TYPE" -o "$MOUNT_OPTS" "$raw_mp/dislocker-file" "$final_mp"
else
mount -o "$MOUNT_OPTS" "$raw_mp/dislocker-file" "$final_mp"
fi
if [ $? -ne 0 ]; then
echo "ERROR: Failed to mount $raw_mp/dislocker-file to $final_mp."
echo "Unmounting dislocker layer at $raw_mp..."
umount "$raw_mp"
return 1
else
echo "$label ($device_path) successfully unlocked and mounted to $final_mp."
fi
echo "-----------------------------------------------------"
}
# --- Main Logic ---
if [ "$(id -u)" -ne 0 ]; then
echo "This script must be run with sudo or as root." >&2
exit 1
fi
if [ -z "$1" ]; then
echo "Usage: $(basename "$0") <drive_label | all>"
echo "Available labels:"
for drive_info in "${DRIVES[@]}"; do
IFS='|' read -r label _ _ _ <<< "$drive_info"
echo " $label"
done
exit 1
fi
if ! decrypt_key_file; then
DECRYPTED_KEYS_CONTENT=""
exit 1
fi
if [ "$1" == "all" ]; then
for drive_info in "${DRIVES[@]}"; do
IFS='|' read -r label device raw final <<< "$drive_info"
unlock_and_mount "$label" "$device" "$raw" "$final"
done
else
TARGET_LABEL="$1"
FOUND=0
for drive_info in "${DRIVES[@]}"; do
IFS='|' read -r label device raw final <<< "$drive_info"
if [ "$label" == "$TARGET_LABEL" ]; then
unlock_and_mount "$label" "$device" "$raw" "$final"
FOUND=1
break
fi
done
if [ "$FOUND" -eq 0 ]; then
echo "Error: Drive label '$TARGET_LABEL' not found in configuration."
DECRYPTED_KEYS_CONTENT=""
exit 1
fi
fi
DECRYPTED_KEYS_CONTENT=""
for i in $(seq 1 ${#DECRYPTED_KEYS_CONTENT}); do DECRYPTED_KEYS_CONTENT="${DECRYPTED_KEYS_CONTENT%?}#"; done
DECRYPTED_KEYS_CONTENT=""
echo "Mounting process complete. Decrypted keys cleared from script memory."
#!/bin/bash
# Script to unmount BitLocker drives with colored output
# Define colors
NC='\033[0m' # No Color
GREEN='\033[0;32m'
YELLOW='\033[0;33m' # For "not mounted" or warnings
RED='\033[0;31m'
BLUE='\033[0;34m' # For informational messages
# --- Configuration (should match unlock_bitlocker.sh for labels and mount points) ---
DRIVES=(
"c_system|/dev/nvme0n1p3|/mnt/raw-c_system|/mnt/c_system"
"d_labs|/dev/nvme0n1p7|/mnt/raw-d_labs|/mnt/d_labs"
"e_labsgames|/dev/nvme1n1p2|/mnt/raw-e_labsgames|/mnt/e_labsgames"
"f_games|/dev/sdb1|/mnt/raw-f_games|/mnt/f_games"
"g_data|/dev/sdc1|/mnt/raw-g_data|/mnt/g_data"
"h_dataactive|/dev/sda1|/mnt/raw-h_dataactive|/mnt/h_dataactive"
"i_dataarchive|/dev/sda2|/mnt/raw-i_dataarchive|/mnt/i_dataarchive"
)
# --- Helper Function ---
unmount_and_lock() {
local label="$1"
# local device_path="$2" # Not strictly needed for unmount
local raw_mp="$3"
local final_mp="$4"
local final_mp_unmounted=0
local raw_mp_unmounted=0
local error_occurred=0
echo -e "${BLUE}-----------------------------------------------------${NC}"
echo -e "${BLUE}Unmounting: ${label}${NC}"
# Unmount the final user-accessible mount point
if mountpoint -q "$final_mp"; then
echo "Unmounting $final_mp..."
sudo umount "$final_mp"
if [ $? -ne 0 ]; then
echo -e "${RED}ERROR: Failed to unmount $final_mp. It might be busy.${NC}"
error_occurred=1
else
echo -e "${GREEN}$final_mp unmounted.${NC}"
final_mp_unmounted=1
fi
else
echo -e "${YELLOW}$final_mp is not mounted.${NC}"
fi
# Unmount the dislocker raw FUSE mount point (this stops dislocker-fuse daemon)
if mountpoint -q "$raw_mp"; then
echo "Unmounting dislocker layer $raw_mp..."
sudo umount "$raw_mp"
if [ $? -ne 0 ]; then
echo -e "${RED}ERROR: Failed to unmount dislocker layer $raw_mp.${NC}"
error_occurred=1
else
echo -e "${GREEN}Dislocker layer $raw_mp unmounted.${NC}"
raw_mp_unmounted=1
fi
else
echo -e "${YELLOW}Dislocker layer $raw_mp is not mounted.${NC}"
fi
if [ "$error_occurred" -eq 1 ]; then
echo -e "${RED}${label} processed for unmounting with errors.${NC}"
elif [ "$final_mp_unmounted" -eq 1 ] || [ "$raw_mp_unmounted" -eq 1 ]; then
echo -e "${GREEN}${label} successfully processed for unmounting.${NC}"
else
echo -e "${YELLOW}${label} was not mounted, no action taken.${NC}"
fi
echo -e "${BLUE}-----------------------------------------------------${NC}"
}
# --- Main Logic ---
if [ "$(id -u)" -ne 0 ]; then
echo -e "${RED}This script must be run with sudo or as root.${NC}" >&2
exit 1
fi
if [ -z "$1" ]; then
echo "Usage: $(basename "$0") <drive_label | all>"
echo "Available labels:"
for drive_info in "${DRIVES[@]}"; do
IFS='|' read -r label _ _ _ <<< "$drive_info"
echo " $label"
done
exit 1
fi
if [ "$1" == "all" ]; then
for drive_info in "${DRIVES[@]}"; do
IFS='|' read -r label device raw final <<< "$drive_info"
unmount_and_lock "$label" "$device" "$raw" "$final"
done
else
TARGET_LABEL="$1"
FOUND=0
for drive_info in "${DRIVES[@]}"; do
IFS='|' read -r label device raw final <<< "$drive_info"
if [ "$label" == "$TARGET_LABEL" ]; then
unmount_and_lock "$label" "$device" "$raw" "$final"
FOUND=1
break
fi
done
if [ "$FOUND" -eq 0 ]; then
echo -e "${RED}Error: Drive label '$TARGET_LABEL' not found in configuration.${NC}"
exit 1
fi
fi
echo -e "${GREEN}Unmounting process complete.${NC}"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment