class Rex::Parser::BITLOCKER

This class parses the content of a Bitlocker partition file. Author : Danil Bazin <danil.bazinhsc.fr> @danilbaz

Constants

BLOCK_HEADER_SIZE
ENTRY_TYPE_DESC
ENTRY_TYPE_FVEK
ENTRY_TYPE_HEADER
ENTRY_TYPE_NONE
ENTRY_TYPE_STARTUP_KEY
ENTRY_TYPE_VMK
METADATA_HEADER_SIZE
PROTECTION_CLEAR_KEY
PROTECTION_PASSWORD
PROTECTION_RECOVERY_PASSWORD
PROTECTION_STARTUP_KEY
PROTECTION_TPM
VALUE_TYPE_ENCRYPTED_KEY
VALUE_TYPE_ERASED
VALUE_TYPE_ERROR
VALUE_TYPE_EXTERNAL_KEY
VALUE_TYPE_KEY
VALUE_TYPE_STRETCH_KEY
VALUE_TYPE_STRING
VALUE_TYPE_TPM
VALUE_TYPE_UPDATE
VALUE_TYPE_VALIDATION
VALUE_TYPE_VMK

Public Class Methods

new(file_handler) click to toggle source
# File lib/rex/parser/fs/bitlocker.rb, line 44
def initialize(file_handler)
  @file_handler = file_handler
  volume_header = @file_handler.read(512)
  @fs_sign = volume_header[3, 8]
  unless @fs_sign == '-FVE-FS-'
    fail ArgumentError, 'File system signature does not match Bitlocker :
     #@fs_sign}, bitlocker not used', caller
  end
  @fve_offset = volume_header[176, 8].unpack('Q')[0]

  @file_handler.seek(@fve_offset)
  @fve_raw = @file_handler.read(4096)
  @encryption_methods = @fve_raw[BLOCK_HEADER_SIZE + 36, 4].unpack('V')[0]
  size = @fve_raw[BLOCK_HEADER_SIZE, 4].unpack('V')[0] -
         METADATA_HEADER_SIZE
  @metadata_entries = @fve_raw[BLOCK_HEADER_SIZE + METADATA_HEADER_SIZE,
                               size]
  @version = @fve_raw[BLOCK_HEADER_SIZE + 4]
  @fve_metadata_entries = fve_entries(@metadata_entries)
  @vmk_entries_hash = vmk_entries
end

Public Instance Methods

decrypt_aes_ccm_key(fve_entry, key) click to toggle source
# File lib/rex/parser/fs/bitlocker.rb, line 103
def decrypt_aes_ccm_key(fve_entry, key)
  nonce = fve_entry[0, 12]
  mac = fve_entry[12, 16]
  encrypted_data = fve_entry[28..-1]
  ccm = OpenSSL::CCM.new('AES',  key, 16)
  decrypted_data = ccm.decrypt(encrypted_data + mac, nonce)
  decrypted_data[12..-1]
end
fve_entries(metadata_entries) click to toggle source

Parse the metadata_entries and return a hashmap using the following format: {metadata_entry_type => {metadata_value_type => [fve_entry,…]}}

# File lib/rex/parser/fs/bitlocker.rb, line 115
def fve_entries(metadata_entries)
  offset_entry = 0
  entry_size = metadata_entries[0, 2].unpack('v')[0]
  result = Hash.new({})
  while entry_size != 0
    metadata_entry_type = metadata_entries[
                          offset_entry + 2, 2].unpack('v')[0]
    metadata_value_type = metadata_entries[
                          offset_entry + 4, 2].unpack('v')[0]
    metadata_entry = metadata_entries[offset_entry + 8, entry_size - 8]
    if result[metadata_entry_type] == {}
      result[metadata_entry_type] = { metadata_value_type => [
        metadata_entry] }
    else
      if result[metadata_entry_type][metadata_value_type].nil?
        result[metadata_entry_type][metadata_value_type] = [
          metadata_entry]
      else
        result[metadata_entry_type][metadata_value_type] += [
          metadata_entry]
      end
    end
    offset_entry += entry_size
    if metadata_entries[offset_entry, 2] != ''
      entry_size = metadata_entries[offset_entry, 2].unpack('v')[0]
    else
      entry_size = 0
    end
  end
  result
end
fvek_entries() click to toggle source

Return FVEK entry, encrypted with the VMK

# File lib/rex/parser/fs/bitlocker.rb, line 211
def fvek_entries
  @fve_metadata_entries[ENTRY_TYPE_FVEK][
    VALUE_TYPE_ENCRYPTED_KEY][ENTRY_TYPE_NONE]
end
fvek_from_recovery_password(recoverykey) click to toggle source

Extract FVEK using the provided recovery key

# File lib/rex/parser/fs/bitlocker.rb, line 96
def fvek_from_recovery_password(recoverykey)
  vmk_recovery_password = vmk_from_recovery_password(recoverykey)
  fvek_encrypted = fvek_entries
  fvek = decrypt_aes_ccm_key(fvek_encrypted, vmk_recovery_password)
  fvek
end
fvek_from_recovery_password_dislocker(recoverykey) click to toggle source

Extract FVEK and prefix it with the encryption methods integer on 2 bytes

# File lib/rex/parser/fs/bitlocker.rb, line 68
def fvek_from_recovery_password_dislocker(recoverykey)
  [@encryption_methods].pack('v') +
    fvek_from_recovery_password(recoverykey)
end
recovery_key_transformation(recoverykey) click to toggle source

stretch all the Recovery key and returns it

# File lib/rex/parser/fs/bitlocker.rb, line 155
      def recovery_key_transformation(recoverykey)
        # recovery key stretching phase 1
        recovery_intermediate = recoverykey.split('-').map(&:to_i)
        recovery_intermediate.each do |n|
          n % 11 != 0 && (fail ArgumentError, 'Invalid recovery key')
        end
        recovery_intermediate =
                           recovery_intermediate.map { |a| (a / 11) }.pack('v*')

        # recovery key stretching phase 2
        recovery_keys = []
        cpu = Metasm.const_get('Ia32').new
        exe = Metasm.const_get('Shellcode').new(cpu)
        cp = Metasm::C::Parser.new(exe)
        bitlocker_struct_src = <<-EOS
          typedef struct {
          unsigned char updated_hash[32];
          unsigned char password_hash[32];
          unsigned char salt[16];
          unsigned long long int hash_count;
          } bitlocker_chain_hash_t;
        EOS
        cp.parse bitlocker_struct_src
        btl_struct = Metasm::C::AllocCStruct.new(cp, cp.find_c_struct(
                                                     'bitlocker_chain_hash_t'))
        vmk_protected_by_recovery_key = @vmk_entries_hash[
                                        PROTECTION_RECOVERY_PASSWORD]
        if vmk_protected_by_recovery_key.nil?
          fail ArgumentError, 'No recovery key on disk'
        end
        vmk_protected_by_recovery_key.each do |vmk_encrypted|
          vmk_encrypted_raw = vmk_encrypted[ENTRY_TYPE_NONE][
                              VALUE_TYPE_STRETCH_KEY][0]
          stretch_key_salt = vmk_encrypted_raw[4, 16]
          strcpy(Digest::SHA256.digest(recovery_intermediate),
                 btl_struct.password_hash)
          strcpy(stretch_key_salt, btl_struct.salt)
          btl_struct.hash_count = 0
          sha256 = Digest::SHA256.new
          btl_struct_raw = btl_struct.str
          btl_struct_hash_count_offset = btl_struct.struct.fldoffset[
                                         'hash_count']
          (1..0x100000).each do |c|
            updated_hash = sha256.digest(btl_struct_raw)
            btl_struct_raw = updated_hash + btl_struct_raw \
                             [btl_struct.updated_hash.sizeof..(
                             btl_struct_hash_count_offset - 1)] + [c].pack('Q')
            sha256.reset
          end
          recovery_keys += [btl_struct_raw[btl_struct.updated_hash.stroff,
                           btl_struct.updated_hash.sizeof]]
        end
        recovery_keys
      end
strcpy(str_src, str_dst) click to toggle source

Dummy strcpy to use with metasm and string asignement

# File lib/rex/parser/fs/bitlocker.rb, line 148
def strcpy(str_src, str_dst)
  (0..(str_src.length - 1)).each do |cpt|
    str_dst[cpt] = str_src[cpt].ord
  end
end
vmk_entries() click to toggle source

Produce a hash map using the following format: {PROTECTION_TYPE => [fve_entry, fve_entry…]}

# File lib/rex/parser/fs/bitlocker.rb, line 218
def vmk_entries
  res = {}
  (@fve_metadata_entries[ENTRY_TYPE_VMK][VALUE_TYPE_VMK]).each do |vmk|
    protection_type = vmk[26, 2].unpack('v')[0]
    if res[protection_type].nil?
      res[protection_type] = [fve_entries(vmk[28..-1])]
    else
      res[protection_type] += [fve_entries(vmk[28..-1])]
    end
  end
  res
end
vmk_from_recovery_password(recoverykey) click to toggle source

stretch recovery key with all stretch key and try to decrypt all VMK encrypted with a recovery key

# File lib/rex/parser/fs/bitlocker.rb, line 75
def vmk_from_recovery_password(recoverykey)
  recovery_keys_stretched = recovery_key_transformation(recoverykey)
  vmk_encrypted_in_recovery_password_list =  @vmk_entries_hash[
                                             PROTECTION_RECOVERY_PASSWORD]
  vmk_recovery_password = ''
  vmk_encrypted_in_recovery_password_list.each do |vmk|
    vmk_encrypted = vmk[ENTRY_TYPE_NONE][VALUE_TYPE_ENCRYPTED_KEY][0]
    recovery_keys_stretched.each do |recovery_key|
      vmk_recovery_password = decrypt_aes_ccm_key(
      vmk_encrypted, recovery_key)
      break if vmk_recovery_password != ''
    end
    break if vmk_recovery_password != ''
  end
  if vmk_recovery_password == ''
    fail ArgumentError, 'Wrong decryption, bad recovery key?'
  end
  vmk_recovery_password
end