From 95c0b2a31baa3d3fc6893483a192b99c760cdc78 Mon Sep 17 00:00:00 2001 From: Christian Werling Date: Tue, 3 Feb 2026 20:21:56 +0100 Subject: [PATCH 1/2] Add FindMbedTLS.cmake for mbedTLS 2.x/3.x compatibility Systems like Ubuntu 24.04 ship mbedTLS without CMake config files, causing find_package to fail. This adds a custom find module that locates mbedTLS on common Linux paths and creates imported targets. Co-Authored-By: Claude Opus 4.5 --- cmake/FindMbedTLS.cmake | 87 +++++++++++++++++++++++++++++++++++++++++ src/CMakeLists.txt | 2 +- 2 files changed, 88 insertions(+), 1 deletion(-) create mode 100644 cmake/FindMbedTLS.cmake diff --git a/cmake/FindMbedTLS.cmake b/cmake/FindMbedTLS.cmake new file mode 100644 index 0000000..55c04b5 --- /dev/null +++ b/cmake/FindMbedTLS.cmake @@ -0,0 +1,87 @@ +# FindMbedTLS.cmake +# Find the mbedTLS library (works with mbedTLS 2.x and 3.x) +# +# This module defines: +# MbedTLS_FOUND - True if mbedTLS was found +# MbedTLS::mbedcrypto - Imported target for mbedcrypto library + +include(FindPackageHandleStandardArgs) + +# Find the include directory +find_path(MBEDTLS_INCLUDE_DIR + NAMES mbedtls/ssl.h + PATHS + /usr/include + /usr/local/include +) + +# Find the crypto library +find_library(MBEDCRYPTO_LIBRARY + NAMES mbedcrypto + PATHS + /usr/lib + /usr/lib/aarch64-linux-gnu + /usr/lib/x86_64-linux-gnu + /usr/local/lib +) + +# Find the TLS library +find_library(MBEDTLS_LIBRARY + NAMES mbedtls + PATHS + /usr/lib + /usr/lib/aarch64-linux-gnu + /usr/lib/x86_64-linux-gnu + /usr/local/lib +) + +# Find the X509 library +find_library(MBEDX509_LIBRARY + NAMES mbedx509 + PATHS + /usr/lib + /usr/lib/aarch64-linux-gnu + /usr/lib/x86_64-linux-gnu + /usr/local/lib +) + +# Get version from version.h +if(MBEDTLS_INCLUDE_DIR) + file(STRINGS "${MBEDTLS_INCLUDE_DIR}/mbedtls/version.h" _mbedtls_version_str + REGEX "^#define[ \t]+MBEDTLS_VERSION_STRING[ \t]+\"[^\"]+\"") + if(_mbedtls_version_str) + string(REGEX REPLACE "^#define[ \t]+MBEDTLS_VERSION_STRING[ \t]+\"([^\"]+)\".*" "\\1" + MBEDTLS_VERSION "${_mbedtls_version_str}") + endif() +endif() + +find_package_handle_standard_args(MbedTLS + REQUIRED_VARS MBEDCRYPTO_LIBRARY MBEDTLS_INCLUDE_DIR + VERSION_VAR MBEDTLS_VERSION +) + +if(MbedTLS_FOUND AND NOT TARGET MbedTLS::mbedcrypto) + add_library(MbedTLS::mbedcrypto UNKNOWN IMPORTED) + set_target_properties(MbedTLS::mbedcrypto PROPERTIES + IMPORTED_LOCATION "${MBEDCRYPTO_LIBRARY}" + INTERFACE_INCLUDE_DIRECTORIES "${MBEDTLS_INCLUDE_DIR}" + ) +endif() + +if(MbedTLS_FOUND AND NOT TARGET MbedTLS::mbedtls) + add_library(MbedTLS::mbedtls UNKNOWN IMPORTED) + set_target_properties(MbedTLS::mbedtls PROPERTIES + IMPORTED_LOCATION "${MBEDTLS_LIBRARY}" + INTERFACE_INCLUDE_DIRECTORIES "${MBEDTLS_INCLUDE_DIR}" + ) +endif() + +if(MbedTLS_FOUND AND NOT TARGET MbedTLS::mbedx509) + add_library(MbedTLS::mbedx509 UNKNOWN IMPORTED) + set_target_properties(MbedTLS::mbedx509 PROPERTIES + IMPORTED_LOCATION "${MBEDX509_LIBRARY}" + INTERFACE_INCLUDE_DIRECTORIES "${MBEDTLS_INCLUDE_DIR}" + ) +endif() + +mark_as_advanced(MBEDTLS_INCLUDE_DIR MBEDCRYPTO_LIBRARY MBEDTLS_LIBRARY MBEDX509_LIBRARY) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index d0a8ab4..a43d7f5 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -118,7 +118,7 @@ endif() # Libraries set (CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake) -find_package (MbedTLS 3 REQUIRED) +find_package (MbedTLS REQUIRED) set (LIB ${LIB} MbedTLS::mbedcrypto) # Ruby bindings From 889d5fa3281b71eeeeb6a4b37e8c4caf7c299bf6 Mon Sep 17 00:00:00 2001 From: Christian Werling Date: Wed, 4 Feb 2026 21:50:57 +0100 Subject: [PATCH 2/2] Add --show-recovery option to display recovery password After successful decryption with any method (user password, BEK file, etc.), the -R/--show-recovery flag extracts and displays the BitLocker recovery password from the VMK. This implements the functionality suggested in the NOTE comment at src/accesses/accesses.c - using the VMK to recover other keys. Co-Authored-By: Claude Opus 4.5 --- .../dislocker/accesses/rp/recovery_password.h | 2 + include/dislocker/config.h | 1 + include/dislocker/config.priv.h | 2 + src/accesses/accesses.c | 30 +++- src/accesses/rp/recovery_password.c | 158 ++++++++++++++++++ src/config.c | 35 +++- 6 files changed, 223 insertions(+), 5 deletions(-) diff --git a/include/dislocker/accesses/rp/recovery_password.h b/include/dislocker/accesses/rp/recovery_password.h index 9d46644..b9e6106 100644 --- a/include/dislocker/accesses/rp/recovery_password.h +++ b/include/dislocker/accesses/rp/recovery_password.h @@ -45,5 +45,7 @@ int prompt_rp(uint8_t** rp); void print_intermediate_key(uint8_t *result_key); +int extract_recovery_password_from_vmk(dis_metadata_t dis_meta, uint8_t* vmk, char* password); + #endif // RECOVERY_PASSWORD_H diff --git a/include/dislocker/config.h b/include/dislocker/config.h index d5ef91d..a3d90a8 100644 --- a/include/dislocker/config.h +++ b/include/dislocker/config.h @@ -50,6 +50,7 @@ typedef enum { DIS_OPT_VOLUME_OFFSET, DIS_OPT_READ_ONLY, DIS_OPT_DONT_CHECK_VOLUME_STATE, + DIS_OPT_SHOW_RECOVERY_PASSWORD, /* Below are options for users of the library (i.e: developers) */ DIS_OPT_INITIALIZE_STATE diff --git a/include/dislocker/config.priv.h b/include/dislocker/config.priv.h index 8b9eaa0..e991b4a 100644 --- a/include/dislocker/config.priv.h +++ b/include/dislocker/config.priv.h @@ -53,6 +53,8 @@ typedef enum { * if mounted using fuse */ DIS_FLAG_DONT_CHECK_VOLUME_STATE = (1 << 1), + /* Show recovery password after successful VMK decryption */ + DIS_FLAG_SHOW_RECOVERY_PASSWORD = (1 << 2), } dis_flags_e; diff --git a/src/accesses/accesses.c b/src/accesses/accesses.c index 1e2cacc..e65352b 100644 --- a/src/accesses/accesses.c +++ b/src/accesses/accesses.c @@ -30,6 +30,7 @@ #include "dislocker/metadata/vmk.h" #include "dislocker/metadata/fvek.h" +#include "dislocker/metadata/datums.h" #include "dislocker/return_values.h" @@ -171,11 +172,34 @@ int dis_get_access(dis_context_t dis_ctx) * NOTE -- We could here validate the information buffer in a more precise * way using the VMK and the validations structure (the one after the * information one, see bitlocker_validations_t in metadata/metadata.h) - * - * NOTE -- We could here get all of the other key a user could use - * using the VMK and the reverse encrypted data */ + /* + * If requested, extract and display the recovery password using the VMK + */ + if(dis_ctx->cfg.flags & DIS_FLAG_SHOW_RECOVERY_PASSWORD) + { + char recovery_password[56]; /* 8*6 + 7 + 1 */ + uint8_t* vmk_key = (uint8_t*)vmk_datum + sizeof(datum_key_t); + + if(extract_recovery_password_from_vmk(dis_ctx->metadata, vmk_key, recovery_password)) + { + /* Use L_CRITICAL to ensure output is always visible when -R is used */ + dis_printf(L_CRITICAL, "\n"); + dis_printf(L_CRITICAL, "============================================================\n"); + dis_printf(L_CRITICAL, "BitLocker Recovery Password:\n"); + dis_printf(L_CRITICAL, "\n"); + dis_printf(L_CRITICAL, " %s\n", recovery_password); + dis_printf(L_CRITICAL, "\n"); + dis_printf(L_CRITICAL, "============================================================\n"); + dis_printf(L_CRITICAL, "\n"); + } + else + { + dis_printf(L_WARNING, "Could not extract recovery password from VMK\n"); + } + } + /* * And then, use the VMK to decrypt the FVEK diff --git a/src/accesses/rp/recovery_password.c b/src/accesses/rp/recovery_password.c index 31006a6..46a1889 100644 --- a/src/accesses/rp/recovery_password.c +++ b/src/accesses/rp/recovery_password.c @@ -26,6 +26,8 @@ #include "dislocker/accesses/rp/recovery_password.h" #include "dislocker/metadata/vmk.h" +#include "dislocker/metadata/datums.h" +#include "dislocker/encryption/decrypt.h" #include "dislocker/xstd/xsys_select.h" @@ -594,3 +596,159 @@ void print_intermediate_key(uint8_t *result_key) dis_printf(L_INFO, "Intermediate recovery key:\n\t%s\n", s); } + + +#define VMK_SIZE 32 +#define RECOVERY_KEY_SIZE 16 + +/** + * Convert 16-byte recovery key material to recovery password string + * + * @param key_material 16 bytes of recovery key material + * @param password Output buffer (must be at least 56 bytes: 8*6 digits + 7 hyphens + null) + * @return TRUE on success, FALSE on failure + */ +static int recovery_key_to_password(const uint8_t* key_material, char* password) +{ + int i; + char* p = password; + + for (i = 0; i < NB_RP_BLOCS; i++) + { + /* Extract 16-bit little-endian value */ + uint16_t value = (uint16_t)(key_material[i * 2] | (key_material[i * 2 + 1] << 8)); + + /* Multiply by 11 to get the 6-digit recovery password group */ + uint32_t digit_group = (uint32_t)value * 11; + + /* Format as 6-digit group */ + int written = snprintf(p, 7, "%06u", digit_group); + if (written != 6) + { + dis_printf(L_ERROR, "Error formatting recovery password block %d\n", i + 1); + return FALSE; + } + p += 6; + + /* Add hyphen separator (except after last block) */ + if (i < NB_RP_BLOCS - 1) + { + *p++ = '-'; + } + } + + *p = '\0'; + return TRUE; +} + + +/** + * Extract recovery password from VMK + * + * Given a decrypted VMK, this function finds the recovery password protector + * datum, decrypts it using the VMK, and converts the result to the standard + * 8x6-digit recovery password format. + * + * @param dis_meta The metadata structure + * @param vmk The 32-byte Volume Master Key + * @param password Output buffer for recovery password (at least 56 bytes) + * @return TRUE on success, FALSE on failure + */ +int extract_recovery_password_from_vmk(dis_metadata_t dis_meta, uint8_t* vmk, char* password) +{ + void* vmk_datum = NULL; + void* stretch_datum = NULL; + void* aesccm_datum = NULL; + datum_aes_ccm_t* aesccm = NULL; + void* decrypted = NULL; + uint8_t* key_material = NULL; + unsigned int header_size; + unsigned int payload_size; + + if (!dis_meta || !vmk || !password) + return FALSE; + + /* + * Find VMK datum for recovery password protector + * Recovery password protectors have priority range 0x800-0xfff + */ + if (!get_vmk_datum_from_range(dis_meta, 0x800, 0xfff, &vmk_datum, NULL)) + { + dis_printf(L_DEBUG, "No recovery password protector found in metadata\n"); + return FALSE; + } + + dis_printf(L_DEBUG, "Found VMK datum for recovery password protector\n"); + + /* + * Get the nested STRETCH_KEY datum + * This contains the salt and nested AES-CCM data + */ + if (!get_nested_datumvaluetype(vmk_datum, DATUMS_VALUE_STRETCH_KEY, &stretch_datum) || + !stretch_datum) + { + dis_printf(L_DEBUG, "Cannot find STRETCH_KEY datum in VMK datum\n"); + return FALSE; + } + + /* + * Get the nested AES-CCM datum inside the STRETCH_KEY + * This contains the recovery key material encrypted by the VMK + */ + if (!get_nested_datumvaluetype(stretch_datum, DATUMS_VALUE_AES_CCM, &aesccm_datum) || + !aesccm_datum) + { + dis_printf(L_DEBUG, "Cannot find AES-CCM datum in STRETCH_KEY datum\n"); + return FALSE; + } + + aesccm = (datum_aes_ccm_t*)aesccm_datum; + + /* Calculate payload size */ + header_size = datum_value_types_prop[aesccm->header.value_type].size_header; + payload_size = aesccm->header.datum_size - header_size; + + dis_printf(L_DEBUG, "AES-CCM payload size: %u bytes\n", payload_size); + + /* Decrypt the recovery key material using the VMK */ + if (!decrypt_key( + (unsigned char*)aesccm_datum + header_size, + payload_size, + aesccm->mac, + aesccm->nonce, + vmk, + VMK_SIZE * 8, /* key size in bits */ + &decrypted)) + { + dis_printf(L_DEBUG, "Failed to decrypt recovery key material\n"); + return FALSE; + } + + /* + * The decrypted data has the following structure: + * - 4 bytes: size + * - 4 bytes: type + * - 4 bytes: algorithm + * - 16 bytes: recovery key material + */ + if (payload_size < 12 + RECOVERY_KEY_SIZE) + { + dis_printf(L_DEBUG, "Decrypted data too small (%u bytes)\n", payload_size); + dis_free(decrypted); + return FALSE; + } + + /* Skip the 12-byte header to get to the recovery key material */ + key_material = (uint8_t*)decrypted + 12; + + /* Convert to recovery password format */ + if (!recovery_key_to_password(key_material, password)) + { + dis_printf(L_ERROR, "Failed to convert recovery key to password format\n"); + dis_free(decrypted); + return FALSE; + } + + dis_free(decrypted); + return TRUE; +} diff --git a/src/config.c b/src/config.c index 9577799..9b27ea8 100644 --- a/src/config.c +++ b/src/config.c @@ -120,6 +120,12 @@ static void setstateok(dis_context_t dis_ctx, char* optarg) int trueval = TRUE; dis_setopt(dis_ctx, DIS_OPT_DONT_CHECK_VOLUME_STATE, &trueval); } +static void setshowrecovery(dis_context_t dis_ctx, char* optarg) +{ + (void) optarg; + int trueval = TRUE; + dis_setopt(dis_ctx, DIS_OPT_SHOW_RECOVERY_PASSWORD, &trueval); +} static void setuserpassword(dis_context_t dis_ctx, char* optarg) { int trueval = TRUE; @@ -154,6 +160,7 @@ static struct _dis_options dis_opt[] = { { {"readonly", no_argument, NULL, 'r'}, setro }, { {"ro", no_argument, NULL, 'r'}, setro }, { {"stateok", no_argument, NULL, 's'}, setstateok }, + { {"show-recovery", no_argument, NULL, 'R'}, setshowrecovery }, { {"user-password", optional_argument, NULL, 'u'}, setuserpassword }, { {"verbosity", no_argument, NULL, 'v'}, setverbosity }, { {"volume", required_argument, NULL, 'V'}, NULL } @@ -172,7 +179,7 @@ PROGNAME " by " AUTHOR ", v" VERSION " (compiled for " __OS "/" __ARCH ")\n" "Compiled version: " VERSION_DBG "\n" #endif "\n" -"Usage: " PROGNAME " [-hqrsv] [-l LOG_FILE] [-O OFFSET] [-V VOLUME DECRYPTMETHOD -F[N]] [-- ARGS...]\n" +"Usage: " PROGNAME " [-hqrRsv] [-l LOG_FILE] [-O OFFSET] [-V VOLUME DECRYPTMETHOD -F[N]] [-- ARGS...]\n" " with DECRYPTMETHOD = -p[RECOVERY_PASSWORD]|-f BEK_FILE|-u[USER_PASSWORD]|-k FVEK_FILE|-K VMK_FILE|-c\n" "\n" "Options:\n" @@ -190,6 +197,7 @@ PROGNAME " by " AUTHOR ", v" VERSION " (compiled for " __OS "/" __ARCH ")\n" " decrypt volume using the recovery password method\n" " -q, --quiet do NOT display anything\n" " -r, --readonly do not allow one to write on the BitLocker volume\n" +" -R, --show-recovery show the recovery password after successful decryption\n" " -s, --stateok do not check the volume's state, assume it's ok to mount it\n" " -u, --user-password=[USER_PASSWORD]\n" " decrypt volume using the user password method\n" @@ -259,7 +267,7 @@ int dis_getopts(dis_context_t dis_ctx, int argc, char** argv) /* Options which could be passed as argument */ - const char short_opts[] = "cf:F::hk:K:l:O:o:p::qrsu::vV:"; + const char short_opts[] = "cf:F::hk:K:l:O:o:p::qrRsu::vV:"; struct option* long_opts; if(!dis_ctx || !argv) @@ -356,6 +364,11 @@ int dis_getopts(dis_context_t dis_ctx, int argc, char** argv) dis_setopt(dis_ctx, DIS_OPT_READ_ONLY, &trueval); break; } + case 'R': + { + dis_setopt(dis_ctx, DIS_OPT_SHOW_RECOVERY_PASSWORD, &trueval); + break; + } case 's': { dis_setopt(dis_ctx, DIS_OPT_DONT_CHECK_VOLUME_STATE, &trueval); @@ -508,6 +521,12 @@ int dis_getopt(dis_context_t dis_ctx, dis_opt_e opt_name, void** opt_value) else *opt_value = (void*) FALSE; break; + case DIS_OPT_SHOW_RECOVERY_PASSWORD: + if(cfg->flags & DIS_FLAG_SHOW_RECOVERY_PASSWORD) + *opt_value = (void*) TRUE; + else + *opt_value = (void*) FALSE; + break; case DIS_OPT_INITIALIZE_STATE: *opt_value = (void*) cfg->init_stop_at; break; @@ -695,6 +714,18 @@ int dis_setopt(dis_context_t dis_ctx, dis_opt_e opt_name, const void* opt_value) cfg->flags &= (unsigned) ~DIS_FLAG_DONT_CHECK_VOLUME_STATE; } break; + case DIS_OPT_SHOW_RECOVERY_PASSWORD: + if(opt_value == NULL) + cfg->flags &= (unsigned) ~DIS_FLAG_SHOW_RECOVERY_PASSWORD; + else + { + int flag = *(int*) opt_value; + if(flag == TRUE) + cfg->flags |= DIS_FLAG_SHOW_RECOVERY_PASSWORD; + else + cfg->flags &= (unsigned) ~DIS_FLAG_SHOW_RECOVERY_PASSWORD; + } + break; case DIS_OPT_INITIALIZE_STATE: if(opt_value == NULL) cfg->init_stop_at = DIS_STATE_COMPLETE_EVERYTHING;