Class: Rex::Parser::BITLOCKER

Inherits:
Object
  • Object
show all
Defined in:
lib/rex/parser/fs/bitlocker.rb

Overview

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

Constant Summary collapse

BLOCK_HEADER_SIZE =
64
METADATA_HEADER_SIZE =
48
ENTRY_TYPE_NONE =
0x0000
ENTRY_TYPE_VMK =
0x0002
ENTRY_TYPE_FVEK =
0x0003
ENTRY_TYPE_STARTUP_KEY =
0x0006
ENTRY_TYPE_DESC =
0x0007
ENTRY_TYPE_HEADER =
0x000f
VALUE_TYPE_ERASED =
0x0000
VALUE_TYPE_KEY =
0x0001
VALUE_TYPE_STRING =
0x0002
VALUE_TYPE_STRETCH_KEY =
0x0003
VALUE_TYPE_ENCRYPTED_KEY =
0x0005
VALUE_TYPE_TPM =
0x0006
VALUE_TYPE_VALIDATION =
0x0007
VALUE_TYPE_VMK =
0x0008
VALUE_TYPE_EXTERNAL_KEY =
0x0009
VALUE_TYPE_UPDATE =
0x000a
VALUE_TYPE_ERROR =
0x000b
PROTECTION_TPM =
0x0100
PROTECTION_CLEAR_KEY =
0x0000
PROTECTION_STARTUP_KEY =
0x0200
PROTECTION_RECOVERY_PASSWORD =
0x0800
PROTECTION_PASSWORD =
0x2000

Instance Method Summary collapse

Constructor Details

#initialize(file_handler) ⇒ BITLOCKER

Returns a new instance of BITLOCKER.



48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
# File 'lib/rex/parser/fs/bitlocker.rb', line 48

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

Instance Method Details

#decrypt_aes_ccm_key(fve_entry, key) ⇒ Object



107
108
109
110
111
112
113
114
# File 'lib/rex/parser/fs/bitlocker.rb', line 107

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) ⇒ Object

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



119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
# File 'lib/rex/parser/fs/bitlocker.rb', line 119

def fve_entries()
  offset_entry = 0
  entry_size = [0, 2].unpack('v')[0]
  result = Hash.new({})
  while entry_size != 0
     = [
                          offset_entry + 2, 2].unpack('v')[0]
     = [
                          offset_entry + 4, 2].unpack('v')[0]
     = [offset_entry + 8, entry_size - 8]
    if result[] == {}
      result[] = {  => [
        ] }
    else
      if result[][].nil?
        result[][] = [
          ]
      else
        result[][] += [
          ]
      end
    end
    offset_entry += entry_size
    if [offset_entry, 2] != ''
      entry_size = [offset_entry, 2].unpack('v')[0]
    else
      entry_size = 0
    end
  end
  result
end

#fvek_entriesObject

Return FVEK entry, encrypted with the VMK



214
215
216
217
# File 'lib/rex/parser/fs/bitlocker.rb', line 214

def fvek_entries
  @fve_metadata_entries[ENTRY_TYPE_FVEK][
    VALUE_TYPE_ENCRYPTED_KEY][ENTRY_TYPE_NONE]
end

#fvek_from_recovery_password(recoverykey) ⇒ Object

Extract FVEK using the provided recovery key



100
101
102
103
104
105
# File 'lib/rex/parser/fs/bitlocker.rb', line 100

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) ⇒ Object

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



72
73
74
75
# File 'lib/rex/parser/fs/bitlocker.rb', line 72

def fvek_from_recovery_password_dislocker(recoverykey)
  [@encryption_methods].pack('v') +
    fvek_from_recovery_password(recoverykey)
end

#recovery_key_transformation(recoverykey) ⇒ Object

stretch all the Recovery key and returns it



159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
# File 'lib/rex/parser/fs/bitlocker.rb', line 159

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) ⇒ Object

Dummy strcpy to use with metasm and string asignement



152
153
154
155
156
# File 'lib/rex/parser/fs/bitlocker.rb', line 152

def strcpy(str_src, str_dst)
  (0..(str_src.length - 1)).each do |cpt|
    str_dst[cpt] = str_src[cpt].ord
  end
end

#vmk_entriesObject

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



221
222
223
224
225
226
227
228
229
230
231
232
# File 'lib/rex/parser/fs/bitlocker.rb', line 221

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) ⇒ Object

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



79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
# File 'lib/rex/parser/fs/bitlocker.rb', line 79

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