Skip to content

Commit 889d5fa

Browse files
cwerlingclaude
andcommitted
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 <noreply@anthropic.com>
1 parent 95c0b2a commit 889d5fa

6 files changed

Lines changed: 223 additions & 5 deletions

File tree

include/dislocker/accesses/rp/recovery_password.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,5 +45,7 @@ int prompt_rp(uint8_t** rp);
4545

4646
void print_intermediate_key(uint8_t *result_key);
4747

48+
int extract_recovery_password_from_vmk(dis_metadata_t dis_meta, uint8_t* vmk, char* password);
49+
4850

4951
#endif // RECOVERY_PASSWORD_H

include/dislocker/config.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ typedef enum {
5050
DIS_OPT_VOLUME_OFFSET,
5151
DIS_OPT_READ_ONLY,
5252
DIS_OPT_DONT_CHECK_VOLUME_STATE,
53+
DIS_OPT_SHOW_RECOVERY_PASSWORD,
5354

5455
/* Below are options for users of the library (i.e: developers) */
5556
DIS_OPT_INITIALIZE_STATE

include/dislocker/config.priv.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ typedef enum {
5353
* if mounted using fuse
5454
*/
5555
DIS_FLAG_DONT_CHECK_VOLUME_STATE = (1 << 1),
56+
/* Show recovery password after successful VMK decryption */
57+
DIS_FLAG_SHOW_RECOVERY_PASSWORD = (1 << 2),
5658
} dis_flags_e;
5759

5860

src/accesses/accesses.c

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030

3131
#include "dislocker/metadata/vmk.h"
3232
#include "dislocker/metadata/fvek.h"
33+
#include "dislocker/metadata/datums.h"
3334

3435
#include "dislocker/return_values.h"
3536

@@ -171,11 +172,34 @@ int dis_get_access(dis_context_t dis_ctx)
171172
* NOTE -- We could here validate the information buffer in a more precise
172173
* way using the VMK and the validations structure (the one after the
173174
* information one, see bitlocker_validations_t in metadata/metadata.h)
174-
*
175-
* NOTE -- We could here get all of the other key a user could use
176-
* using the VMK and the reverse encrypted data
177175
*/
178176

177+
/*
178+
* If requested, extract and display the recovery password using the VMK
179+
*/
180+
if(dis_ctx->cfg.flags & DIS_FLAG_SHOW_RECOVERY_PASSWORD)
181+
{
182+
char recovery_password[56]; /* 8*6 + 7 + 1 */
183+
uint8_t* vmk_key = (uint8_t*)vmk_datum + sizeof(datum_key_t);
184+
185+
if(extract_recovery_password_from_vmk(dis_ctx->metadata, vmk_key, recovery_password))
186+
{
187+
/* Use L_CRITICAL to ensure output is always visible when -R is used */
188+
dis_printf(L_CRITICAL, "\n");
189+
dis_printf(L_CRITICAL, "============================================================\n");
190+
dis_printf(L_CRITICAL, "BitLocker Recovery Password:\n");
191+
dis_printf(L_CRITICAL, "\n");
192+
dis_printf(L_CRITICAL, " %s\n", recovery_password);
193+
dis_printf(L_CRITICAL, "\n");
194+
dis_printf(L_CRITICAL, "============================================================\n");
195+
dis_printf(L_CRITICAL, "\n");
196+
}
197+
else
198+
{
199+
dis_printf(L_WARNING, "Could not extract recovery password from VMK\n");
200+
}
201+
}
202+
179203

180204
/*
181205
* And then, use the VMK to decrypt the FVEK

src/accesses/rp/recovery_password.c

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@
2626

2727
#include "dislocker/accesses/rp/recovery_password.h"
2828
#include "dislocker/metadata/vmk.h"
29+
#include "dislocker/metadata/datums.h"
30+
#include "dislocker/encryption/decrypt.h"
2931
#include "dislocker/xstd/xsys_select.h"
3032

3133

@@ -594,3 +596,159 @@ void print_intermediate_key(uint8_t *result_key)
594596

595597
dis_printf(L_INFO, "Intermediate recovery key:\n\t%s\n", s);
596598
}
599+
600+
601+
#define VMK_SIZE 32
602+
#define RECOVERY_KEY_SIZE 16
603+
604+
/**
605+
* Convert 16-byte recovery key material to recovery password string
606+
*
607+
* @param key_material 16 bytes of recovery key material
608+
* @param password Output buffer (must be at least 56 bytes: 8*6 digits + 7 hyphens + null)
609+
* @return TRUE on success, FALSE on failure
610+
*/
611+
static int recovery_key_to_password(const uint8_t* key_material, char* password)
612+
{
613+
int i;
614+
char* p = password;
615+
616+
for (i = 0; i < NB_RP_BLOCS; i++)
617+
{
618+
/* Extract 16-bit little-endian value */
619+
uint16_t value = (uint16_t)(key_material[i * 2] | (key_material[i * 2 + 1] << 8));
620+
621+
/* Multiply by 11 to get the 6-digit recovery password group */
622+
uint32_t digit_group = (uint32_t)value * 11;
623+
624+
/* Format as 6-digit group */
625+
int written = snprintf(p, 7, "%06u", digit_group);
626+
if (written != 6)
627+
{
628+
dis_printf(L_ERROR, "Error formatting recovery password block %d\n", i + 1);
629+
return FALSE;
630+
}
631+
p += 6;
632+
633+
/* Add hyphen separator (except after last block) */
634+
if (i < NB_RP_BLOCS - 1)
635+
{
636+
*p++ = '-';
637+
}
638+
}
639+
640+
*p = '\0';
641+
return TRUE;
642+
}
643+
644+
645+
/**
646+
* Extract recovery password from VMK
647+
*
648+
* Given a decrypted VMK, this function finds the recovery password protector
649+
* datum, decrypts it using the VMK, and converts the result to the standard
650+
* 8x6-digit recovery password format.
651+
*
652+
* @param dis_meta The metadata structure
653+
* @param vmk The 32-byte Volume Master Key
654+
* @param password Output buffer for recovery password (at least 56 bytes)
655+
* @return TRUE on success, FALSE on failure
656+
*/
657+
int extract_recovery_password_from_vmk(dis_metadata_t dis_meta, uint8_t* vmk, char* password)
658+
{
659+
void* vmk_datum = NULL;
660+
void* stretch_datum = NULL;
661+
void* aesccm_datum = NULL;
662+
datum_aes_ccm_t* aesccm = NULL;
663+
void* decrypted = NULL;
664+
uint8_t* key_material = NULL;
665+
unsigned int header_size;
666+
unsigned int payload_size;
667+
668+
if (!dis_meta || !vmk || !password)
669+
return FALSE;
670+
671+
/*
672+
* Find VMK datum for recovery password protector
673+
* Recovery password protectors have priority range 0x800-0xfff
674+
*/
675+
if (!get_vmk_datum_from_range(dis_meta, 0x800, 0xfff, &vmk_datum, NULL))
676+
{
677+
dis_printf(L_DEBUG, "No recovery password protector found in metadata\n");
678+
return FALSE;
679+
}
680+
681+
dis_printf(L_DEBUG, "Found VMK datum for recovery password protector\n");
682+
683+
/*
684+
* Get the nested STRETCH_KEY datum
685+
* This contains the salt and nested AES-CCM data
686+
*/
687+
if (!get_nested_datumvaluetype(vmk_datum, DATUMS_VALUE_STRETCH_KEY, &stretch_datum) ||
688+
!stretch_datum)
689+
{
690+
dis_printf(L_DEBUG, "Cannot find STRETCH_KEY datum in VMK datum\n");
691+
return FALSE;
692+
}
693+
694+
/*
695+
* Get the nested AES-CCM datum inside the STRETCH_KEY
696+
* This contains the recovery key material encrypted by the VMK
697+
*/
698+
if (!get_nested_datumvaluetype(stretch_datum, DATUMS_VALUE_AES_CCM, &aesccm_datum) ||
699+
!aesccm_datum)
700+
{
701+
dis_printf(L_DEBUG, "Cannot find AES-CCM datum in STRETCH_KEY datum\n");
702+
return FALSE;
703+
}
704+
705+
aesccm = (datum_aes_ccm_t*)aesccm_datum;
706+
707+
/* Calculate payload size */
708+
header_size = datum_value_types_prop[aesccm->header.value_type].size_header;
709+
payload_size = aesccm->header.datum_size - header_size;
710+
711+
dis_printf(L_DEBUG, "AES-CCM payload size: %u bytes\n", payload_size);
712+
713+
/* Decrypt the recovery key material using the VMK */
714+
if (!decrypt_key(
715+
(unsigned char*)aesccm_datum + header_size,
716+
payload_size,
717+
aesccm->mac,
718+
aesccm->nonce,
719+
vmk,
720+
VMK_SIZE * 8, /* key size in bits */
721+
&decrypted))
722+
{
723+
dis_printf(L_DEBUG, "Failed to decrypt recovery key material\n");
724+
return FALSE;
725+
}
726+
727+
/*
728+
* The decrypted data has the following structure:
729+
* - 4 bytes: size
730+
* - 4 bytes: type
731+
* - 4 bytes: algorithm
732+
* - 16 bytes: recovery key material
733+
*/
734+
if (payload_size < 12 + RECOVERY_KEY_SIZE)
735+
{
736+
dis_printf(L_DEBUG, "Decrypted data too small (%u bytes)\n", payload_size);
737+
dis_free(decrypted);
738+
return FALSE;
739+
}
740+
741+
/* Skip the 12-byte header to get to the recovery key material */
742+
key_material = (uint8_t*)decrypted + 12;
743+
744+
/* Convert to recovery password format */
745+
if (!recovery_key_to_password(key_material, password))
746+
{
747+
dis_printf(L_ERROR, "Failed to convert recovery key to password format\n");
748+
dis_free(decrypted);
749+
return FALSE;
750+
}
751+
752+
dis_free(decrypted);
753+
return TRUE;
754+
}

src/config.c

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,12 @@ static void setstateok(dis_context_t dis_ctx, char* optarg)
120120
int trueval = TRUE;
121121
dis_setopt(dis_ctx, DIS_OPT_DONT_CHECK_VOLUME_STATE, &trueval);
122122
}
123+
static void setshowrecovery(dis_context_t dis_ctx, char* optarg)
124+
{
125+
(void) optarg;
126+
int trueval = TRUE;
127+
dis_setopt(dis_ctx, DIS_OPT_SHOW_RECOVERY_PASSWORD, &trueval);
128+
}
123129
static void setuserpassword(dis_context_t dis_ctx, char* optarg)
124130
{
125131
int trueval = TRUE;
@@ -154,6 +160,7 @@ static struct _dis_options dis_opt[] = {
154160
{ {"readonly", no_argument, NULL, 'r'}, setro },
155161
{ {"ro", no_argument, NULL, 'r'}, setro },
156162
{ {"stateok", no_argument, NULL, 's'}, setstateok },
163+
{ {"show-recovery", no_argument, NULL, 'R'}, setshowrecovery },
157164
{ {"user-password", optional_argument, NULL, 'u'}, setuserpassword },
158165
{ {"verbosity", no_argument, NULL, 'v'}, setverbosity },
159166
{ {"volume", required_argument, NULL, 'V'}, NULL }
@@ -172,7 +179,7 @@ PROGNAME " by " AUTHOR ", v" VERSION " (compiled for " __OS "/" __ARCH ")\n"
172179
"Compiled version: " VERSION_DBG "\n"
173180
#endif
174181
"\n"
175-
"Usage: " PROGNAME " [-hqrsv] [-l LOG_FILE] [-O OFFSET] [-V VOLUME DECRYPTMETHOD -F[N]] [-- ARGS...]\n"
182+
"Usage: " PROGNAME " [-hqrRsv] [-l LOG_FILE] [-O OFFSET] [-V VOLUME DECRYPTMETHOD -F[N]] [-- ARGS...]\n"
176183
" with DECRYPTMETHOD = -p[RECOVERY_PASSWORD]|-f BEK_FILE|-u[USER_PASSWORD]|-k FVEK_FILE|-K VMK_FILE|-c\n"
177184
"\n"
178185
"Options:\n"
@@ -190,6 +197,7 @@ PROGNAME " by " AUTHOR ", v" VERSION " (compiled for " __OS "/" __ARCH ")\n"
190197
" decrypt volume using the recovery password method\n"
191198
" -q, --quiet do NOT display anything\n"
192199
" -r, --readonly do not allow one to write on the BitLocker volume\n"
200+
" -R, --show-recovery show the recovery password after successful decryption\n"
193201
" -s, --stateok do not check the volume's state, assume it's ok to mount it\n"
194202
" -u, --user-password=[USER_PASSWORD]\n"
195203
" decrypt volume using the user password method\n"
@@ -259,7 +267,7 @@ int dis_getopts(dis_context_t dis_ctx, int argc, char** argv)
259267

260268

261269
/* Options which could be passed as argument */
262-
const char short_opts[] = "cf:F::hk:K:l:O:o:p::qrsu::vV:";
270+
const char short_opts[] = "cf:F::hk:K:l:O:o:p::qrRsu::vV:";
263271
struct option* long_opts;
264272

265273
if(!dis_ctx || !argv)
@@ -356,6 +364,11 @@ int dis_getopts(dis_context_t dis_ctx, int argc, char** argv)
356364
dis_setopt(dis_ctx, DIS_OPT_READ_ONLY, &trueval);
357365
break;
358366
}
367+
case 'R':
368+
{
369+
dis_setopt(dis_ctx, DIS_OPT_SHOW_RECOVERY_PASSWORD, &trueval);
370+
break;
371+
}
359372
case 's':
360373
{
361374
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)
508521
else
509522
*opt_value = (void*) FALSE;
510523
break;
524+
case DIS_OPT_SHOW_RECOVERY_PASSWORD:
525+
if(cfg->flags & DIS_FLAG_SHOW_RECOVERY_PASSWORD)
526+
*opt_value = (void*) TRUE;
527+
else
528+
*opt_value = (void*) FALSE;
529+
break;
511530
case DIS_OPT_INITIALIZE_STATE:
512531
*opt_value = (void*) cfg->init_stop_at;
513532
break;
@@ -695,6 +714,18 @@ int dis_setopt(dis_context_t dis_ctx, dis_opt_e opt_name, const void* opt_value)
695714
cfg->flags &= (unsigned) ~DIS_FLAG_DONT_CHECK_VOLUME_STATE;
696715
}
697716
break;
717+
case DIS_OPT_SHOW_RECOVERY_PASSWORD:
718+
if(opt_value == NULL)
719+
cfg->flags &= (unsigned) ~DIS_FLAG_SHOW_RECOVERY_PASSWORD;
720+
else
721+
{
722+
int flag = *(int*) opt_value;
723+
if(flag == TRUE)
724+
cfg->flags |= DIS_FLAG_SHOW_RECOVERY_PASSWORD;
725+
else
726+
cfg->flags &= (unsigned) ~DIS_FLAG_SHOW_RECOVERY_PASSWORD;
727+
}
728+
break;
698729
case DIS_OPT_INITIALIZE_STATE:
699730
if(opt_value == NULL)
700731
cfg->init_stop_at = DIS_STATE_COMPLETE_EVERYTHING;

0 commit comments

Comments
 (0)