/* * BitCracker: BitLocker password cracking tool, Hash Extractor. * Copyright (C) 2013-2017 Elena Ago * Massimo Bernaschi * * This file is part of BitCracker. * * BitCracker is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * BitCracker is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with BitCracker. If not, see . */ #include #include #include #include #include #include #include #include #include #include #define INPUT_SIZE 1024 #define SALT_SIZE 16 #define MAC_SIZE 16 #define IV_SIZE 16 #define PADDING_SIZE 16 #define NONCE_SIZE 12 #define VMK_SIZE 44 #define VMK_ENTRY_SIZE 4 #define SIGNATURE_LEN 9 #define VMK_SALT_JUMP 12 #define VMK_AES_TYPE 0x0005 #define SALT_OFFSETS 2 #define AES_OFFSETS 1 #define MAX_RP 200 #define FILE_OUT_HASH_USER "hash_user_pass.txt" #define FILE_OUT_HASH_RECV "hash_recv_pass.txt" #define FRET_CHECK(ret) \ if(ret < 0) \ { \ fprintf(stderr, "ftell error %s (%d)\n", strerror(errno),errno); \ exit(EXIT_FAILURE); \ } //Fixed unsigned char p_salt[SALT_SIZE], p_nonce[NONCE_SIZE], p_mac[MAC_SIZE], p_vmk[VMK_SIZE]; unsigned char r_salt[MAX_RP][SALT_SIZE], r_nonce[MAX_RP][NONCE_SIZE], r_mac[MAX_RP][MAC_SIZE], r_vmk[MAX_RP][VMK_SIZE]; const char signature[SIGNATURE_LEN] = "-FVE-FS-"; unsigned char vmk_entry[VMK_ENTRY_SIZE] = { 0x02, 0x00, 0x08, 0x00 }; unsigned char key_protection_clear[2] = { 0x00, 0x00 }; unsigned char key_protection_tpm[2] = { 0x00, 0x01 }; unsigned char key_protection_start_key[2] = { 0x00, 0x02 }; unsigned char key_protection_recovery[2] = { 0x00, 0x08 }; unsigned char key_protection_password[2] = { 0x00, 0x20 }; unsigned char value_type[2] = { 0x00, 0x05 }; int userPasswordFound=0, RPfound=0, found_ccm=0; int salt_pos[SALT_OFFSETS] = {12, 32}; int aes_pos[AES_OFFSETS] = {147}; //67 <-- can't prove as valid this second offset long int fp_before_aes=0, fp_before_salt=0; FILE *outFileUser, *outFileRecv, * encryptedImage; static char finalRP[MAX_RP][210]; void * Calloc(size_t len, size_t size) { void * ptr = NULL; if( size <= 0) { fprintf(stderr,"Critical error: memory size is 0\n"); exit(EXIT_FAILURE); } ptr = (void *)calloc(len, size); if( ptr == NULL ) { fprintf(stderr,"Critical error: Memory allocation\n"); exit(EXIT_FAILURE); } return ptr; } static int usage(char *name){ printf("\nUsage: %s -i -o \n\n" "Options:\n\n" " -h" "\t\tShow this help\n" " -i" "\t\tImage path of encrypted memory unit encrypted with BitLocker\n" " -o" "\t\tOutputs path (i.e. /some/path/for/outputs/). Default: current directory\n\n", name); return EXIT_FAILURE; } static void fillBuffer(FILE *fp, unsigned char *buffer, int size) { int k; for (k = 0; k < size; k++) buffer[k] = (unsigned char)fgetc(fp); } static void print_hex(unsigned char *str, int len, FILE *out) { int i; for (i = 0; i < len; ++i) fprintf(out, "%02x", str[i]); } int rp_search_salt_aes() { uint8_t a,b; int ret=0, x, y; for(x=0; x < SALT_OFFSETS; x++) { ret=fseek(encryptedImage, salt_pos[x], SEEK_CUR); FRET_CHECK(ret) fillBuffer(encryptedImage, r_salt[RPfound], SALT_SIZE); fp_before_aes=ftell(encryptedImage); FRET_CHECK(fp_before_aes) fprintf(stderr, "Searching for AES-CCM (0x%lx)...\n", fp_before_aes); for(y=0; y < AES_OFFSETS; y++) { ret=fseek(encryptedImage, aes_pos[y], SEEK_CUR); FRET_CHECK(ret) fprintf(stderr, "\tOffset 0x%lx.... ", ftell(encryptedImage)); a=(uint8_t)fgetc(encryptedImage); b=(uint8_t)fgetc(encryptedImage); if (( a != value_type[0]) || (b != value_type[1])) { fprintf(stderr, "not found :( (0x%x,0x%x)\n", a, b); found_ccm=0; } else { fprintf(stderr, "found! :)\n"); found_ccm=1; ret=fseek(encryptedImage, 3, SEEK_CUR); FRET_CHECK(ret) } if(found_ccm == 1) break; else if(y==0) { ret=fseek(encryptedImage, fp_before_aes, SEEK_SET); FRET_CHECK(ret) } } if(found_ccm == 1) break; else if(x==0) { ret=fseek(encryptedImage, fp_before_salt, SEEK_SET); FRET_CHECK(ret) } } return 0; } int rp_search_dup() { int index=0; if(RPfound > 0) { for(index=0; index < RPfound; index++) { if((memcmp(r_vmk[index], r_vmk[RPfound], VMK_SIZE)) == 0) return 1; } } return 0; } int parse_image(char * encryptedImagePath, char * outHashUser, char * outHashRecovery) { long int fileLen=0, j=0; int version = 0, i = 0, match = 0, ret = 0, outRP = 0, fve_block = 0; unsigned char c,d; encryptedImage = fopen(encryptedImagePath, "r"); if (!encryptedImage || !outHashUser || !outHashRecovery) { fprintf(stderr, "! %s : %s\n", encryptedImagePath, strerror(errno)); return 1; } ret=fseek(encryptedImage, 0, SEEK_END); FRET_CHECK(ret) fileLen = ftell(encryptedImage); FRET_CHECK(fileLen) printf("Encrypted device %s opened, size %7.2f MB\n", encryptedImagePath, (double)((fileLen/1024)/1024)); ret=fseek(encryptedImage, 0, SEEK_SET); FRET_CHECK(ret) for (j = 0; j < fileLen; j++) { c = fgetc(encryptedImage); while (i < (SIGNATURE_LEN-1) && (unsigned char)c == signature[i]) { c = fgetc(encryptedImage); i++; } if (i == (SIGNATURE_LEN-1)) { match = 1; fve_block++; fprintf(stdout, "\n************ Signature #%d found at 0x%lx ************\n", fve_block, (ftell(encryptedImage) - i - 1)); ret=fseek(encryptedImage, 1, SEEK_CUR); version = fgetc(encryptedImage); fprintf(stdout, "Version: %d ", version); if (version == 1) fprintf(stdout, "(Windows Vista)\n"); else if (version == 2) fprintf(stdout, "(Windows 7 or later)\n"); else { fprintf(stdout, "\nInvalid version, looking for a signature with valid version...\n"); match = 0; } } if(!match) { i=0; continue; } i = 0; while (i < VMK_ENTRY_SIZE && (unsigned char)c == vmk_entry[i]) { c = fgetc(encryptedImage); i++; } if (i == VMK_ENTRY_SIZE) { fprintf(stderr, "\n=====> VMK entry found at 0x%lx\n", (ftell(encryptedImage) - i)); ret=fseek(encryptedImage, 27, SEEK_CUR); FRET_CHECK(ret) c = (unsigned char)fgetc(encryptedImage); d = (unsigned char)fgetc(encryptedImage); fp_before_salt = ftell(encryptedImage); FRET_CHECK(fp_before_salt) if ((c == key_protection_clear[0]) && (d == key_protection_clear[1])) fprintf(stdout, "VMK not encrypted.. stored clear! (0x%lx)\n", fp_before_salt); else if ((c == key_protection_tpm[0]) && (d == key_protection_tpm[1])) fprintf(stdout, "VMK encrypted with TPM...not supported! (0x%lx)\n", fp_before_salt); else if ((c == key_protection_start_key[0]) && (d == key_protection_start_key[1])) fprintf(stdout, "VMK encrypted with Startup Key...not supported! (0x%lx)\n", fp_before_salt); else if ((c == key_protection_recovery[0]) && (d == key_protection_recovery[1]) && RPfound < MAX_RP) { fprintf(stdout, "Encrypted with Recovery Password (0x%lx)\n", fp_before_salt); rp_search_salt_aes(); if (found_ccm == 0) { match=0; i=0; continue; } fillBuffer(encryptedImage, r_nonce[RPfound], NONCE_SIZE); fillBuffer(encryptedImage, r_mac[RPfound], MAC_SIZE); fillBuffer(encryptedImage, r_vmk[RPfound], VMK_SIZE); if(rp_search_dup() == 1) { fprintf(stdout, "\nThis VMK has been already stored..."); //Avoid infinite loop n case of huge device memory if(fve_block > 2) { fprintf(stdout, "quitting to avoid infinite loop!\n"); break; } else fprintf(stdout, "\n"); } else { printf("======== RP VMK #%d ========\n", RPfound); printf("RP Salt: "); print_hex(r_salt[RPfound], SALT_SIZE, stdout); fprintf(stdout, "\nRP Nonce: "); print_hex(r_nonce[RPfound], NONCE_SIZE, stdout); fprintf(stdout, "\nRP MAC: "); print_hex(r_mac[RPfound], MAC_SIZE, stdout); fprintf(stdout, "\nRP VMK: "); print_hex(r_vmk[RPfound], VMK_SIZE, stdout); fprintf(stdout, "\n"); fflush(stdout); RPfound++; } } else if ((c == key_protection_password[0]) && (d == key_protection_password[1]) && userPasswordFound == 0) { fprintf(stderr, "Encrypted with User Password (0x%lx)\n", fp_before_salt); ret=fseek(encryptedImage, 12, SEEK_CUR); FRET_CHECK(ret) fillBuffer(encryptedImage, p_salt, SALT_SIZE); ret=fseek(encryptedImage, 83, SEEK_CUR); FRET_CHECK(ret) if (((unsigned char)fgetc(encryptedImage) != value_type[0]) || ((unsigned char)fgetc(encryptedImage) != value_type[1])) { fprintf(stderr, "Error: VMK not encrypted with AES-CCM\n"); match=0; i=0; continue; } else fprintf(stderr, "VMK encrypted with AES-CCM\n"); ret=fseek(encryptedImage, 3, SEEK_CUR); FRET_CHECK(ret) printf("======== UP VMK ========\n"); fprintf(stdout, "UP Salt: "); print_hex(p_salt, SALT_SIZE, stdout); fillBuffer(encryptedImage, p_nonce, NONCE_SIZE); fprintf(stdout, "\nUP Nonce: "); print_hex(p_nonce, NONCE_SIZE, stdout); fillBuffer(encryptedImage, p_mac, MAC_SIZE); fprintf(stdout, "\nUP MAC: "); print_hex(p_mac, MAC_SIZE, stdout); fillBuffer(encryptedImage, p_vmk, VMK_SIZE); fprintf(stdout, "\nUP VMK: "); print_hex(p_vmk, VMK_SIZE, stdout); fprintf(stdout, "\n"); fflush(stdout); userPasswordFound=1; } else fprintf(stdout, "Can't define a key protection method for values (%x,%x)... skipping!\n", c, d); } i = 0; //if(userPasswordFound == 1 || RPfound == 1) break; } fclose(encryptedImage); if (userPasswordFound == 0 && RPfound == 0) { fprintf(stderr, "Error while extracting data: No signature found!\n"); return 1; } else { if(userPasswordFound == 1) { printf("\nUser Password hash:\n$bitlocker$0$%d$", SALT_SIZE); print_hex(p_salt, SALT_SIZE, stdout); printf("$%d$%d$", 0x100000, NONCE_SIZE); // fixed iterations , fixed nonce size print_hex(p_nonce, NONCE_SIZE, stdout); printf("$%d$", VMK_SIZE + MAC_SIZE); print_hex(p_mac, MAC_SIZE, stdout); // hack, this should actually be entire AES-CCM encrypted block (which includes vmk) print_hex(p_vmk, VMK_SIZE, stdout); printf("\n"); outFileUser = fopen(outHashUser, "w"); if (!outFileUser) { fprintf(stderr, "Error creating ./%s : %s\n", outHashUser, strerror(errno)); return 1; } fprintf(outFileUser, "$bitlocker$0$%d$", SALT_SIZE); print_hex(p_salt, SALT_SIZE, outFileUser); fprintf(outFileUser, "$%d$%d$", 0x100000, NONCE_SIZE); // fixed iterations , fixed nonce size print_hex(p_nonce, NONCE_SIZE, outFileUser); fprintf(outFileUser, "$%d$", VMK_SIZE + MAC_SIZE); print_hex(p_mac, MAC_SIZE, outFileUser); print_hex(p_vmk, VMK_SIZE, outFileUser); fprintf(outFileUser, "\n"); fclose(outFileUser); } if(RPfound > 0) { outFileRecv = fopen(outHashRecovery, "w"); if (!outFileRecv) { fprintf(stderr, "Error creating ./%s : %s\n", outHashRecovery, strerror(errno)); return 1; } for(outRP=0; outRP < RPfound; outRP++) { printf("\nRecovery Key hash #%d:\n$bitlocker$2$%d$", outRP, SALT_SIZE); print_hex(r_salt[outRP], SALT_SIZE, stdout); printf("$%d$%d$", 0x100000, NONCE_SIZE); // fixed iterations , fixed nonce size print_hex(r_nonce[outRP], NONCE_SIZE, stdout); printf("$%d$", VMK_SIZE + MAC_SIZE); print_hex(r_mac[outRP], MAC_SIZE, stdout); // hack, this should actually be entire AES-CCM encrypted block (which includes vmk) print_hex(r_vmk[outRP], VMK_SIZE, stdout); printf("\n"); fprintf(outFileRecv, "$bitlocker$2$%d$", SALT_SIZE); print_hex(r_salt[outRP], SALT_SIZE, outFileRecv); fprintf(outFileRecv, "$%d$%d$", 0x100000, NONCE_SIZE); // fixed iterations , fixed nonce size print_hex(r_nonce[outRP], NONCE_SIZE, outFileRecv); fprintf(outFileRecv, "$%d$", VMK_SIZE + MAC_SIZE); print_hex(r_mac[outRP], MAC_SIZE, outFileRecv); print_hex(r_vmk[outRP], VMK_SIZE, outFileRecv); fprintf(outFileRecv, "\n"); } fclose(outFileRecv); } } return 0; } int main(int argc, char **argv) { int opt; char * imagePath=NULL; char * outPath=NULL; char * outHashUser=NULL; char * outHashRecovery=NULL; errno = 0; while (1) { opt = getopt(argc, argv, "hi:o:"); if (opt == -1) break; switch (opt) { case 'h': usage(argv[0]); exit(EXIT_FAILURE); break; case 'i': if(strlen(optarg) >= INPUT_SIZE) { fprintf(stderr, "ERROR: Input string is bigger than %d\n", INPUT_SIZE); exit(EXIT_FAILURE); } imagePath=(char *)Calloc(INPUT_SIZE, sizeof(char)); strncpy(imagePath, optarg, strlen(optarg)+1); break; case 'o': if(strlen(optarg) >= INPUT_SIZE) { fprintf(stderr, "ERROR: Input string is bigger than %d\n", INPUT_SIZE); exit(EXIT_FAILURE); } outHashUser = (char*)Calloc( (strlen(optarg)+strlen(FILE_OUT_HASH_USER)+2), sizeof(char)); memcpy(outHashUser, optarg, strlen(optarg)); outHashUser[strlen(optarg)] = '/'; memcpy(outHashUser+strlen(optarg)+1, FILE_OUT_HASH_USER, strlen(FILE_OUT_HASH_USER)); outHashRecovery = (char*)Calloc( (strlen(optarg)+strlen(FILE_OUT_HASH_RECV)+2), sizeof(char)); memcpy(outHashRecovery, optarg, strlen(optarg)); outHashRecovery[strlen(optarg)] = '/'; memcpy(outHashRecovery+strlen(optarg)+1, FILE_OUT_HASH_RECV, strlen(FILE_OUT_HASH_RECV)); break; default: break; } } if(!imagePath) { usage(argv[0]); exit(EXIT_FAILURE); } if(outHashUser == NULL) //Current directory { outHashUser = (char*)Calloc( (strlen(FILE_OUT_HASH_USER)+1), sizeof(char)); memcpy(outHashUser, FILE_OUT_HASH_USER, strlen(FILE_OUT_HASH_USER)); } if(outHashRecovery == NULL) //Current directory { outHashRecovery = (char*)Calloc( (strlen(FILE_OUT_HASH_RECV)+1), sizeof(char)); memcpy(outHashRecovery, FILE_OUT_HASH_RECV, strlen(FILE_OUT_HASH_RECV)); } printf("\n---------> BitCracker Hash Extractor <---------\n"); if(parse_image(imagePath, outHashUser, outHashRecovery)) fprintf(stderr, "\nError while parsing input device image\n"); else { if(userPasswordFound) printf("\nOutput file for user password attack: \"%s\"\n", outHashUser); if(RPfound) printf("\nOutput file for recovery password attack: \"%s\"\n", outHashRecovery); } free(outHashUser); free(outHashRecovery); return 0; }