Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Path: blob/master/lib/rex/parser/fs/bitlocker.rb
Views: 11783
# -*- coding: binary -*-12require 'openssl/ccm'3require 'metasm'45##6# This module requires Metasploit: https://metasploit.com/download7# Current source: https://github.com/rapid7/metasploit-framework8##910module Rex11module Parser12###13#14# This class parses the content of a Bitlocker partition file.15# Author : Danil Bazin <danil.bazin[at]hsc.fr> @danilbaz16#17###18class BITLOCKER19BLOCK_HEADER_SIZE = 6420METADATA_HEADER_SIZE = 482122ENTRY_TYPE_NONE = 0x000023ENTRY_TYPE_VMK = 0x000224ENTRY_TYPE_FVEK = 0x000325ENTRY_TYPE_STARTUP_KEY = 0x000626ENTRY_TYPE_DESC = 0x000727ENTRY_TYPE_HEADER = 0x000f2829VALUE_TYPE_ERASED = 0x000030VALUE_TYPE_KEY = 0x000131VALUE_TYPE_STRING = 0x000232VALUE_TYPE_STRETCH_KEY = 0x000333VALUE_TYPE_ENCRYPTED_KEY = 0x000534VALUE_TYPE_TPM = 0x000635VALUE_TYPE_VALIDATION = 0x000736VALUE_TYPE_VMK = 0x000837VALUE_TYPE_EXTERNAL_KEY = 0x000938VALUE_TYPE_UPDATE = 0x000a39VALUE_TYPE_ERROR = 0x000b4041PROTECTION_TPM = 0x010042PROTECTION_CLEAR_KEY = 0x000043PROTECTION_STARTUP_KEY = 0x020044PROTECTION_RECOVERY_PASSWORD = 0x080045PROTECTION_PASSWORD = 0x20004647def initialize(file_handler)48@file_handler = file_handler49volume_header = @file_handler.read(512)50@fs_sign = volume_header[3, 8]51unless @fs_sign == '-FVE-FS-'52fail ArgumentError, 'File system signature does not match Bitlocker :53#@fs_sign}, bitlocker not used', caller54end55@fve_offset = volume_header[176, 8].unpack('Q')[0]5657@file_handler.seek(@fve_offset)58@fve_raw = @file_handler.read(4096)59@encryption_methods = @fve_raw[BLOCK_HEADER_SIZE + 36, 4].unpack('V')[0]60size = @fve_raw[BLOCK_HEADER_SIZE, 4].unpack('V')[0] -61METADATA_HEADER_SIZE62@metadata_entries = @fve_raw[BLOCK_HEADER_SIZE + METADATA_HEADER_SIZE,63size]64@version = @fve_raw[BLOCK_HEADER_SIZE + 4]65@fve_metadata_entries = fve_entries(@metadata_entries)66@vmk_entries_hash = vmk_entries67end6869# Extract FVEK and prefix it with the encryption methods integer on70# 2 bytes71def fvek_from_recovery_password_dislocker(recoverykey)72[@encryption_methods].pack('v') +73fvek_from_recovery_password(recoverykey)74end7576# stretch recovery key with all stretch key and try to decrypt all VMK77# encrypted with a recovery key78def vmk_from_recovery_password(recoverykey)79recovery_keys_stretched = recovery_key_transformation(recoverykey)80vmk_encrypted_in_recovery_password_list = @vmk_entries_hash[81PROTECTION_RECOVERY_PASSWORD]82vmk_recovery_password = ''83vmk_encrypted_in_recovery_password_list.each do |vmk|84vmk_encrypted = vmk[ENTRY_TYPE_NONE][VALUE_TYPE_ENCRYPTED_KEY][0]85recovery_keys_stretched.each do |recovery_key|86vmk_recovery_password = decrypt_aes_ccm_key(87vmk_encrypted, recovery_key)88break if vmk_recovery_password != ''89end90break if vmk_recovery_password != ''91end92if vmk_recovery_password == ''93fail ArgumentError, 'Wrong decryption, bad recovery key?'94end95vmk_recovery_password96end9798# Extract FVEK using the provided recovery key99def fvek_from_recovery_password(recoverykey)100vmk_recovery_password = vmk_from_recovery_password(recoverykey)101fvek_encrypted = fvek_entries102fvek = decrypt_aes_ccm_key(fvek_encrypted, vmk_recovery_password)103fvek104end105106def decrypt_aes_ccm_key(fve_entry, key)107nonce = fve_entry[0, 12]108mac = fve_entry[12, 16]109encrypted_data = fve_entry[28..-1]110ccm = OpenSSL::CCM.new('AES', key, 16)111decrypted_data = ccm.decrypt(encrypted_data + mac, nonce)112decrypted_data[12..-1]113end114115# Parse the metadata_entries and return a hashmap using the116# following format:117# metadata_entry_type => metadata_value_type => [fve_entry,...]118def fve_entries(metadata_entries)119offset_entry = 0120entry_size = metadata_entries[0, 2].unpack('v')[0]121result = Hash.new({})122while entry_size != 0123metadata_entry_type = metadata_entries[124offset_entry + 2, 2].unpack('v')[0]125metadata_value_type = metadata_entries[126offset_entry + 4, 2].unpack('v')[0]127metadata_entry = metadata_entries[offset_entry + 8, entry_size - 8]128if result[metadata_entry_type] == {}129result[metadata_entry_type] = { metadata_value_type => [130metadata_entry] }131else132if result[metadata_entry_type][metadata_value_type].nil?133result[metadata_entry_type][metadata_value_type] = [134metadata_entry]135else136result[metadata_entry_type][metadata_value_type] += [137metadata_entry]138end139end140offset_entry += entry_size141if metadata_entries[offset_entry, 2] != ''142entry_size = metadata_entries[offset_entry, 2].unpack('v')[0]143else144entry_size = 0145end146end147result148end149150# Dummy strcpy to use with metasm and string asignement151def strcpy(str_src, str_dst)152(0..(str_src.length - 1)).each do |cpt|153str_dst[cpt] = str_src[cpt].ord154end155end156157# stretch all the Recovery key and returns it158def recovery_key_transformation(recoverykey)159# recovery key stretching phase 1160recovery_intermediate = recoverykey.split('-').map(&:to_i)161recovery_intermediate.each do |n|162n % 11 != 0 && (fail ArgumentError, 'Invalid recovery key')163end164recovery_intermediate =165recovery_intermediate.map { |a| (a / 11) }.pack('v*')166167# recovery key stretching phase 2168recovery_keys = []169cpu = Metasm.const_get('Ia32').new170exe = Metasm.const_get('Shellcode').new(cpu)171cp = Metasm::C::Parser.new(exe)172bitlocker_struct_src = <<-EOS173typedef struct {174unsigned char updated_hash[32];175unsigned char password_hash[32];176unsigned char salt[16];177unsigned long long int hash_count;178} bitlocker_chain_hash_t;179EOS180cp.parse bitlocker_struct_src181btl_struct = Metasm::C::AllocCStruct.new(cp, cp.find_c_struct(182'bitlocker_chain_hash_t'))183vmk_protected_by_recovery_key = @vmk_entries_hash[184PROTECTION_RECOVERY_PASSWORD]185if vmk_protected_by_recovery_key.nil?186fail ArgumentError, 'No recovery key on disk'187end188vmk_protected_by_recovery_key.each do |vmk_encrypted|189vmk_encrypted_raw = vmk_encrypted[ENTRY_TYPE_NONE][190VALUE_TYPE_STRETCH_KEY][0]191stretch_key_salt = vmk_encrypted_raw[4, 16]192strcpy(Digest::SHA256.digest(recovery_intermediate),193btl_struct.password_hash)194strcpy(stretch_key_salt, btl_struct.salt)195btl_struct.hash_count = 0196sha256 = Digest::SHA256.new197btl_struct_raw = btl_struct.str198btl_struct_hash_count_offset = btl_struct.struct.fldoffset[199'hash_count']200(1..0x100000).each do |c|201updated_hash = sha256.digest(btl_struct_raw)202btl_struct_raw = updated_hash + btl_struct_raw[btl_struct.updated_hash.sizeof..(203btl_struct_hash_count_offset - 1)] + [c].pack('Q')204sha256.reset205end206recovery_keys += [btl_struct_raw[btl_struct.updated_hash.stroff,207btl_struct.updated_hash.sizeof]]208end209recovery_keys210end211212# Return FVEK entry, encrypted with the VMK213def fvek_entries214@fve_metadata_entries[ENTRY_TYPE_FVEK][215VALUE_TYPE_ENCRYPTED_KEY][ENTRY_TYPE_NONE]216end217218# Produce a hash map using the following format:219# PROTECTION_TYPE => [fve_entry, fve_entry...]220def vmk_entries221res = {}222(@fve_metadata_entries[ENTRY_TYPE_VMK][VALUE_TYPE_VMK]).each do |vmk|223protection_type = vmk[26, 2].unpack('v')[0]224if res[protection_type].nil?225res[protection_type] = [fve_entries(vmk[28..-1])]226else227res[protection_type] += [fve_entries(vmk[28..-1])]228end229end230res231end232end233end234end235236237