From 03ca3b8b70f54ee634fcaff33dd27c7f3ad0a647 Mon Sep 17 00:00:00 2001 From: ltyack Date: Wed, 18 Mar 2026 17:12:29 +0100 Subject: [PATCH] Add TPM+PIN decryption support (-t/--tpm-datum option) Allow decrypting BitLocker volumes protected with TPM+PIN by providing a TPM datum file (the AES-CCM blob manually extracted from the TPM) along with the user PIN. Usage: dislocker -t vmk_datum.bin -u PIN -V /dev/sdX The implementation iterates all VMK datums in the volume metadata, derives an intermediate key from the PIN and the salt found in each VMK's stretch key datum, decrypts the TPM blob to obtain an intermediate key, then uses it to decrypt the actual VMK. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../dislocker/accesses/user_pass/user_pass.h | 2 + include/dislocker/config.h | 2 + include/dislocker/config.priv.h | 5 +- src/accesses/accesses.c | 23 ++ src/accesses/user_pass/user_pass.c | 233 ++++++++++++++++++ src/config.c | 49 +++- 6 files changed, 311 insertions(+), 3 deletions(-) diff --git a/include/dislocker/accesses/user_pass/user_pass.h b/include/dislocker/accesses/user_pass/user_pass.h index f98f6d88..522dfb86 100644 --- a/include/dislocker/accesses/user_pass/user_pass.h +++ b/include/dislocker/accesses/user_pass/user_pass.h @@ -39,6 +39,8 @@ int get_vmk_from_user_pass(dis_metadata_t dis_meta, dis_config_t* cfg, void** vmk_datum); int get_vmk_from_user_pass2(dis_metadata_t dis_meta, uint8_t** user_password, void** vmk_datum); +int get_vmk_from_tpm_pin(dis_metadata_t dis_meta, dis_config_t* cfg, void** vmk_datum); + int user_key(const uint8_t *user_password, const uint8_t *salt, uint8_t *result_key); int prompt_up(uint8_t** up); diff --git a/include/dislocker/config.h b/include/dislocker/config.h index d5ef91d4..e5fc4cce 100644 --- a/include/dislocker/config.h +++ b/include/dislocker/config.h @@ -44,6 +44,8 @@ typedef enum { DIS_OPT_SET_FVEK_FILE_PATH, DIS_OPT_USE_VMK_FILE, DIS_OPT_SET_VMK_FILE_PATH, + DIS_OPT_USE_TPM_PIN, + DIS_OPT_SET_TPM_DATUM_FILE_PATH, DIS_OPT_VERBOSITY, DIS_OPT_LOG_FILE_PATH, DIS_OPT_FORCE_BLOCK, diff --git a/include/dislocker/config.priv.h b/include/dislocker/config.priv.h index 8b9eaa07..01fb11bc 100644 --- a/include/dislocker/config.priv.h +++ b/include/dislocker/config.priv.h @@ -35,11 +35,12 @@ typedef enum { DIS_USE_RECOVERY_PASSWORD = (1 << 2), DIS_USE_BEKFILE = (1 << 3), DIS_USE_FVEKFILE = (1 << 4), + DIS_USE_TPM_PIN = (1 << 5), DIS_USE_VMKFILE = (1 << 8) } DIS_DECRYPT_MEAN; /* Don't use this as a decryption mean, but as the last one */ -#define LAST_MEAN (1 << 5) +#define LAST_MEAN (1 << 6) /** @@ -80,6 +81,8 @@ typedef struct _dis_cfg { char* fvek_file; /* Use directly the VMK file DECRYPT_MEAN */ char* vmk_file; + /* Path to the TPM datum file for TPM+PIN DECRYPT_MEAN */ + char* tpm_datum_file; /* Output verbosity */ DIS_LOGS verbosity; diff --git a/src/accesses/accesses.c b/src/accesses/accesses.c index 1e2cacc5..24f09059 100644 --- a/src/accesses/accesses.c +++ b/src/accesses/accesses.c @@ -60,6 +60,29 @@ int dis_get_access(dis_context_t dis_ctx) break; } } + else if(dis_ctx->cfg.decryption_mean & DIS_USE_TPM_PIN) + { + if(!get_vmk_from_tpm_pin(dis_ctx->metadata, &dis_ctx->cfg, &vmk_datum)) + { + dis_ctx->cfg.decryption_mean &= (unsigned) ~DIS_USE_TPM_PIN; + } + else + { + dis_printf(L_INFO, "Used TPM+PIN decryption method\n"); + dis_ctx->cfg.decryption_mean = DIS_USE_TPM_PIN; + + /* We don't need the user password anymore */ + if(dis_ctx->cfg.user_password) + { + memclean( + (char*) dis_ctx->cfg.user_password, + strlen((char*) dis_ctx->cfg.user_password) + ); + dis_ctx->cfg.user_password = NULL; + } + break; + } + } else if(dis_ctx->cfg.decryption_mean & DIS_USE_USER_PASSWORD) { if(!get_vmk_from_user_pass(dis_ctx->metadata, &dis_ctx->cfg, &vmk_datum)) diff --git a/src/accesses/user_pass/user_pass.c b/src/accesses/user_pass/user_pass.c index 21c45bf0..3d2d5e44 100644 --- a/src/accesses/user_pass/user_pass.c +++ b/src/accesses/user_pass/user_pass.c @@ -30,6 +30,9 @@ #include #include #include +#include +#include +#include /** @@ -178,6 +181,236 @@ int get_vmk_from_user_pass2(dis_metadata_t dis_meta, } +/** + * Get the VMK datum using a TPM datum file and a user PIN + * + * The TPM datum file contains a raw AES-CCM datum (as obtained from the TPM). + * The PIN/user password is used together with the salt from the volume metadata + * to derive an intermediate key that decrypts the TPM datum. The result is then + * used to decrypt the actual VMK from the volume metadata. + * + * @param dis_meta The metadata structure + * @param cfg The configuration structure (provides tpm_datum_file and user_password) + * @param vmk_datum The datum_key_t found, containing the unencrypted VMK + * @return TRUE if result can be trusted, FALSE otherwise + */ +int get_vmk_from_tpm_pin(dis_metadata_t dis_meta, + dis_config_t* cfg, + void** vmk_datum) +{ + if(!dis_meta || !cfg || !cfg->tpm_datum_file) + return FALSE; + + uint8_t user_hash[32] = {0,}; + uint8_t salt[16] = {0,}; + void* vmk_key = NULL; + size_t vmk_key_size = 0; + int file_fd = -1; + off_t file_size; + ssize_t rs; + void* tpm_aesccm = NULL; + + /* If the user password/PIN wasn't provided, ask for it */ + if(!cfg->user_password) + if(!prompt_up(&cfg->user_password)) + { + dis_printf(L_ERROR, "Cannot get valid user PIN. Abort.\n"); + return FALSE; + } + + dis_printf( + L_DEBUG, + "Using TPM+PIN method with datum file '%s'.\n", + cfg->tpm_datum_file + ); + + /* Read the TPM datum file */ + file_fd = dis_open(cfg->tpm_datum_file, O_RDONLY); + if(file_fd == -1) + { + dis_printf(L_ERROR, "Cannot open TPM datum file (%s)\n", cfg->tpm_datum_file); + return FALSE; + } + + file_size = dis_lseek(file_fd, 0, SEEK_END); + if(file_size < (off_t)sizeof(datum_aes_ccm_t) || file_size > 65536) + { + dis_printf( + L_ERROR, + "Invalid TPM datum file size: %d (expected at least %lu bytes)\n", + (int)file_size, + (unsigned long)sizeof(datum_aes_ccm_t) + ); + dis_close(file_fd); + return FALSE; + } + + tpm_aesccm = dis_malloc((size_t)file_size); + dis_lseek(file_fd, 0, SEEK_SET); + rs = dis_read(file_fd, tpm_aesccm, (size_t)file_size); + dis_close(file_fd); + + if(rs != file_size) + { + dis_printf(L_ERROR, "Cannot read TPM datum file completely\n"); + dis_free(tpm_aesccm); + return FALSE; + } + + dis_printf(L_DEBUG, "TPM datum file read (%d bytes):\n", (int)file_size); + hexdump(L_DEBUG, tpm_aesccm, (size_t)file_size); + + /* + * Iterate over all VMK datums in the metadata, looking for one that + * has both a STRETCH_KEY and an AES_CCM nested datum. For each candidate, + * try to decrypt the TPM blob with the PIN-derived key, then use the + * result to decrypt the actual VMK. + */ + void* current_vmk = NULL; + + while(get_next_datum( + dis_meta, + DATUMS_ENTRY_VMK, + DATUMS_VALUE_VMK, + current_vmk, + ¤t_vmk + )) + { + /* Look for a STRETCH_KEY nested datum (contains the salt) */ + void* stretch_datum = NULL; + if(!get_nested_datumvaluetype( + current_vmk, + DATUMS_VALUE_STRETCH_KEY, + &stretch_datum + ) || + !stretch_datum) + { + continue; + } + + memcpy(salt, ((datum_stretch_key_t*) stretch_datum)->salt, 16); + + dis_printf(L_DEBUG, "Found VMK with stretch key, salt:\n"); + hexdump(L_DEBUG, salt, 16); + + /* + * Derive the intermediate key from the user PIN and the salt + */ + if(!user_key(cfg->user_password, salt, user_hash)) + { + dis_printf(L_DEBUG, "Cannot stretch user PIN with this salt, trying next VMK.\n"); + continue; + } + + dis_printf(L_DEBUG, "Derived user hash:\n"); + hexdump(L_DEBUG, user_hash, 32); + + /* + * Step 1: Decrypt the TPM datum with the PIN-derived key. + * This yields an intermediate VMK key. + */ + /* + * Initialize to tpm_aesccm so get_vmk's debug print has a + * valid datum to display before decryption overwrites it. + */ + datum_key_t* intermediate_key = (datum_key_t*) tpm_aesccm; + if(!get_vmk( + (datum_aes_ccm_t*) tpm_aesccm, + user_hash, + 32, + &intermediate_key + )) + { + dis_printf(L_DEBUG, "Cannot decrypt TPM datum with this VMK's salt, trying next.\n"); + continue; + } + + /* Extract the raw key bytes from the intermediate key datum */ + if(!get_payload_safe(intermediate_key, &vmk_key, &vmk_key_size)) + { + dis_printf( + L_DEBUG, + "Cannot extract payload from intermediate key, trying next.\n" + ); + dis_free(intermediate_key); + continue; + } + + dis_printf(L_DEBUG, "Intermediate VMK key (%lu bytes):\n", + (unsigned long)vmk_key_size); + hexdump(L_DEBUG, vmk_key, vmk_key_size); + + /* + * Step 2: Get the AES_CCM nested datum from the metadata VMK + * and decrypt it with the intermediate key to get the real VMK. + */ + void* aesccm_vmk_datum = NULL; + if(!get_nested_datumvaluetype( + current_vmk, + DATUMS_VALUE_AES_CCM, + &aesccm_vmk_datum + ) || + !aesccm_vmk_datum) + { + dis_printf( + L_DEBUG, + "No AES_CCM datum in this VMK entry, trying next.\n" + ); + dis_free(intermediate_key); + dis_free(vmk_key); + vmk_key = NULL; + continue; + } + + /* Make a copy since get_vmk may modify the pointer */ + size_t aesccm_size = ((datum_aes_ccm_t*)aesccm_vmk_datum)->header.datum_size; + void* aesccm_copy = dis_malloc(aesccm_size); + memcpy(aesccm_copy, aesccm_vmk_datum, aesccm_size); + + dis_printf(L_DEBUG, "AES_CCM VMK datum (%lu bytes):\n", + (unsigned long)aesccm_size); + hexdump(L_DEBUG, aesccm_copy, aesccm_size); + + datum_key_t* final_vmk = (datum_key_t*) aesccm_copy; + if(!get_vmk( + (datum_aes_ccm_t*) aesccm_copy, + vmk_key, + (vmk_key_size > 32) ? 32 : vmk_key_size, + &final_vmk + )) + { + dis_printf( + L_DEBUG, + "Cannot decrypt VMK with intermediate key, trying next.\n" + ); + dis_free(intermediate_key); + dis_free(vmk_key); + dis_free(aesccm_copy); + vmk_key = NULL; + continue; + } + + /* Success! */ + dis_printf(L_INFO, "Successfully decrypted VMK using TPM+PIN method.\n"); + *vmk_datum = final_vmk; + + dis_free(intermediate_key); + dis_free(vmk_key); + dis_free(aesccm_copy); + dis_free(tpm_aesccm); + return TRUE; + } + + dis_printf( + L_ERROR, + "Failed to decrypt VMK using TPM+PIN method. " + "No matching VMK datum found or wrong PIN.\n" + ); + dis_free(tpm_aesccm); + return FALSE; +} + + /** * Get the user's pass without displaying it. * diff --git a/src/config.c b/src/config.c index 9577799a..344b092c 100644 --- a/src/config.c +++ b/src/config.c @@ -86,6 +86,12 @@ static void setvmk(dis_context_t dis_ctx, char* optarg) dis_setopt(dis_ctx, DIS_OPT_USE_VMK_FILE, &trueval); dis_setopt(dis_ctx, DIS_OPT_SET_VMK_FILE_PATH, optarg); } +static void settpmdatum(dis_context_t dis_ctx, char* optarg) +{ + int trueval = TRUE; + dis_setopt(dis_ctx, DIS_OPT_USE_TPM_PIN, &trueval); + dis_setopt(dis_ctx, DIS_OPT_SET_TPM_DATUM_FILE_PATH, optarg); +} static void setlogfile(dis_context_t dis_ctx, char* optarg) { dis_setopt(dis_ctx, DIS_OPT_LOG_FILE_PATH, optarg); @@ -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 }, + { {"tpm-datum", required_argument, NULL, 't'}, settpmdatum }, { {"user-password", optional_argument, NULL, 'u'}, setuserpassword }, { {"verbosity", no_argument, NULL, 'v'}, setverbosity }, { {"volume", required_argument, NULL, 'V'}, NULL } @@ -173,7 +180,7 @@ PROGNAME " by " AUTHOR ", v" VERSION " (compiled for " __OS "/" __ARCH ")\n" #endif "\n" "Usage: " PROGNAME " [-hqrsv] [-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" +" with DECRYPTMETHOD = -p[RECOVERY_PASSWORD]|-f BEK_FILE|-u[USER_PASSWORD]|-k FVEK_FILE|-K VMK_FILE|-t TPM_DATUM_FILE [-u[PIN]]|-c\n" "\n" "Options:\n" " -c, --clearkey decrypt volume using a clear key (default)\n" @@ -191,6 +198,8 @@ PROGNAME " by " AUTHOR ", v" VERSION " (compiled for " __OS "/" __ARCH ")\n" " -q, --quiet do NOT display anything\n" " -r, --readonly do not allow one to write on the BitLocker volume\n" " -s, --stateok do not check the volume's state, assume it's ok to mount it\n" +" -t, --tpm-datum TPM_DATUM_FILE\n" +" decrypt volume using the TPM+PIN method (use with -u for PIN)\n" " -u, --user-password=[USER_PASSWORD]\n" " decrypt volume using the user password method\n" " -v, --verbosity increase verbosity (CRITICAL errors are displayed by default)\n" @@ -259,7 +268,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::qrst:u::vV:"; struct option* long_opts; if(!dis_ctx || !argv) @@ -361,6 +370,12 @@ int dis_getopts(dis_context_t dis_ctx, int argc, char** argv) dis_setopt(dis_ctx, DIS_OPT_DONT_CHECK_VOLUME_STATE, &trueval); break; } + case 't': + { + dis_setopt(dis_ctx, DIS_OPT_USE_TPM_PIN, &trueval); + dis_setopt(dis_ctx, DIS_OPT_SET_TPM_DATUM_FILE_PATH, optarg); + break; + } case 'u': { dis_setopt(dis_ctx, DIS_OPT_USE_USER_PASSWORD, &trueval); @@ -484,6 +499,15 @@ int dis_getopt(dis_context_t dis_ctx, dis_opt_e opt_name, void** opt_value) case DIS_OPT_SET_VMK_FILE_PATH: *opt_value = cfg->vmk_file; break; + case DIS_OPT_USE_TPM_PIN: + if(cfg->decryption_mean & DIS_USE_TPM_PIN) + *opt_value = (void*) TRUE; + else + *opt_value = (void*) FALSE; + break; + case DIS_OPT_SET_TPM_DATUM_FILE_PATH: + *opt_value = cfg->tpm_datum_file; + break; case DIS_OPT_VERBOSITY: *opt_value = (void*) cfg->verbosity; break; @@ -633,6 +657,20 @@ int dis_setopt(dis_context_t dis_ctx, dis_opt_e opt_name, const void* opt_value) else cfg->vmk_file = strdup((const char*) opt_value); break; + case DIS_OPT_USE_TPM_PIN: + if(opt_value == NULL) + cfg->decryption_mean &= (unsigned) ~DIS_USE_TPM_PIN; + else + set_decryption_mean(cfg, *(int*) opt_value, DIS_USE_TPM_PIN); + break; + case DIS_OPT_SET_TPM_DATUM_FILE_PATH: + if(cfg->tpm_datum_file != NULL) + free(cfg->tpm_datum_file); + if(opt_value == NULL) + cfg->tpm_datum_file = NULL; + else + cfg->tpm_datum_file = strdup((const char*) opt_value); + break; case DIS_OPT_VERBOSITY: if(opt_value == NULL) cfg->verbosity = 0; @@ -739,6 +777,9 @@ void dis_free_args(dis_context_t dis_ctx) if(cfg->vmk_file) memclean(cfg->vmk_file, strlen(cfg->vmk_file) + sizeof(char)); + if(cfg->tpm_datum_file) + memclean(cfg->tpm_datum_file, strlen(cfg->tpm_datum_file) + sizeof(char)); + if(cfg->volume_path) dis_free(cfg->volume_path); @@ -783,6 +824,10 @@ void dis_print_args(dis_context_t dis_ctx) { dis_printf(L_DEBUG, " \tusing the FVEK file at '%s'\n", cfg->fvek_file); } + else if(cfg->decryption_mean & DIS_USE_TPM_PIN) + { + dis_printf(L_DEBUG, " \tusing the TPM+PIN method with datum file '%s'\n", cfg->tpm_datum_file); + } else if(cfg->decryption_mean & DIS_USE_VMKFILE) { dis_printf(L_DEBUG, " \tusing the VMK file at '%s'\n", cfg->vmk_file);