openwall / john

John the Ripper jumbo - advanced offline password cracker, which supports hundreds of hash and cipher types, and runs on many operating systems, CPUs, GPUs, and even some FPGAs
https://www.openwall.com/john/
Other
10.33k stars 2.1k forks source link

BestCrypt Volume Encryption Format #4242

Open trounce1 opened 4 years ago

trounce1 commented 4 years ago

Hi guys,

i know that the Jetico bestcrypt container format is supported, but would it be possible to add the volume encryption format. It doesnt seem to be supported by any other tools.

I have tried to find some source code to the format and not managed to, i have also done some work on the header but not got anywhere.

Thanks

kholia commented 4 years ago

https://dspace.cvut.cz/bitstream/handle/10467/69601/F8-BP-2017-Vojtesek-Jan-thesis.pdf could be helpful.

Update: Related source-code can be found at the following URL,

https://github.com/MotherOfAllVoids/bachelor_thesis.

trounce1 commented 4 years ago

I saw this the other day, I compiled it and read through source. It is a program to parse an optional recovery file and the virtually mount and decrypt the volume. I couldn’t find anywhere about the header of an FDE disk.

trounce1 commented 4 years ago
Screenshot 2020-04-07 at 18 24 24

This is what the header looks like.

the first 32 bytes appear to also be present in the s-table for blowfish

trounce1 commented 4 years ago

I have written a proof of concept based on the above paper.

from binascii import hexlify
import hashlib
from Crypto.Cipher import AES
from sys import argv

from CryptoPlus.Cipher import python_Rijndael

buffsize = 0x10000
buff = b''
password = b'password'

bc_file = open(argv[1], 'rb').read(0x588300)

salt = bc_file[488:496]
enc_data = bc_file[38:134]
encrypted_first_sector = bc_file[0x588100:0x588300]

while len(buff) < buffsize:
    buff += salt
    buff += password
dk = hashlib.sha256(buff).digest()

cipher = AES.new(dk, AES.MODE_CBC, b'\0' * 16)

plain = cipher.decrypt(enc_data)

hash_of_keys = hashlib.sha256(plain[:0x40]).digest()

primary_key = plain[:0x20]
tweak = plain[0x20:0x40]
hashval = plain[0x40:]

print('primary key            : ', hexlify(primary_key))
print('tweak key              : ', hexlify(tweak))
print('hash value             : ', hexlify(hashval))
print('hash of keys           : ', hexlify(hash_of_keys))

if hash_of_keys == hashval:
    key = (primary_key, tweak)
    cipher2 = python_Rijndael.new(key, python_Rijndael.MODE_XTS, blocksize=16)
    decrypted_first_sector = cipher2.decrypt(encrypted_first_sector)
    print('Encrypted First Sector : ', hexlify(encrypted_first_sector[:0x40]))
    print('Decrypted First Sector : ', hexlify(decrypted_first_sector[:0x40]))

this works on the old versions of bestcrypt full disk. it does not work on modern versions v4+

attached is the partial image that works : partial_image.bin.zip

Update : Added decryption of first block

magnumripper commented 4 years ago

@kholia!!! 🎉

I missed you

kholia commented 4 years ago

@trounce1 Here is a plan:

@MotherOfAllVoids Thanks for your awesome thesis work on reversing BCVE. Do you remember which files you reverse engineered and the general process you used? Thanks!

trounce1 commented 4 years ago

looks like it is now scrypt as see in url below https://www.jetico.com/file-downloads/web_help/bcve4/?url=html/02_standards/01_security_characteristics.htm

ive tried a few things with scrypt and camellia but to no avail.

i thought about reversing the 16 bit recovery binary that comes with the recovery disk as it is small ~500kb. i cant seem to find anything that will decompile 16bit x86

kholia commented 4 years ago

I cant seem to find anything that will decompile 16bit x86.

GHIDRA seems to be working fine for recovery.exe. Give it a try.

https://ghidra-sre.org/

kholia commented 4 years ago

GHIDRA (+ https://github.com/d3v1l401/FindCrypt-Ghidra) in action:

GHIDRA_FTW
kholia commented 4 years ago

It seems recovery.exe doesn't support v4.x volumes?

old-code
trounce1 commented 4 years ago

good spot, It now looks like they are using scrypt with a 5 digit iteration count after talking to their support team. i will need to confirm the settings.

janvojtesek commented 4 years ago

Hey guys,

the thesis linked above only deals with BCVE v3 (since v4 wasn't out yet) and I didn't really look into v4 since release. However, the weak password hashing algorithm (cracking the password in v3 basically boils down to trying single iterations of SHA256) was one of the things that I reported to Jetico and they told me that they were already planning to improve it, most likely by using scrypt.

If you are looking for the headers as they are on the raw disk, they are described in another thesis at my university done by Juraj Hornak (https://dspace.cvut.cz/handle/10467/65108?show=full). Unfortunately, it's in Slovak, but I hope that it still can help... The table on page 29 describes exactly what you have in the hex dump above. The first 32 bytes, are just magic bytes, they are derived from digits of Pi, same as blowfish sboxes, so it's pretty much just coincidence :). What you are probably interested in are the 0x60 bytes from offset 0x26 to offset 0x86.

I worked by reverse engineering the main usermode binary and attaching a debugger to it a lot. Since you already found the implementation of SHA256, I think it'll be easiest to just go through all of its uses and you should find the password hashing routine, but yeah, it'll probably not be easy :).

trounce1 commented 4 years ago

@janvojtesek good info. thanks for the paper. i have translated F8-DP-2016-Hornak-Juraj-thesis.cs.en.pdf

Update: My code a few comments above uses those offsets you talk about.

Was there any way to distinguish the version number of the BCVE?

kholia commented 4 years ago

Was there any way to distinguish the version number of the BCVE?

I can help with this I think.

Update: Probably not :|

trounce1 commented 4 years ago

good spot, It now looks like they are using scrypt with a 5 digit iteration count after talking to their support team. i will need to confirm the settings.

the scrypt parameters are p=1, r=16, N=32768 or 2^15

it differs now from my script as apparently the encryption algorithm to derive to volume keys is the same one used to decrypt the volume.

e.g. you encrypt your disk with aes-xts and also decrypt the 0x60 bytes from offset 0x26 to offset 0x86 with aes-xts.

kholia commented 4 years ago

GHIDRA seems to be working fine for recovery.exe.

Surprisingly IDA's dissembler output is easier to read than GHIDRA's decompiler output. IDA handles string references pretty well.

trounce1 commented 4 years ago

GHIDRA seems to be working fine for recovery.exe.

Surprisingly IDA's dissembler output is easier to read than GHIDRA's decompiler output. IDA handles string references pretty well.

I noticed the same thing. did you find any reference to 2^15?

kholia commented 4 years ago

did you find any reference to 2^15?

call-to-scrypt

Perhaps the called function (at 004da2f0) is the scrypt function or leads to it?

https://github.com/technion/libscrypt/blob/master/crypto_scrypt-nosse.c <-- The next step would be to find code patterns (in the binary to be reversed) similar to this .c file?

Also,

tango-1

Seems like we are on the right track (?).

I entered xyzabc as the password.

trounce1 commented 4 years ago

did you find any reference to 2^15?

call-to-scrypt

Perhaps the called function (at 004da2f0) is the scrypt function or leads to it?

https://github.com/technion/libscrypt/blob/master/crypto_scrypt-nosse.c <-- The next step would be to find code patterns (in the binary to be reversed) similar to this .c file?

Also,

tango-1

Seems like we are on the right track (?).

I entered xyzabc as the password.

Thats great progress. im pretty sure this is the implementation for V4 format, something is missing though.

from binascii import hexlify
from sys import argv
import scrypt
from CryptoPlus.Cipher import python_Rijndael

password = b'password'

bc_file = open(argv[1], 'rb').read(0x588300)

salt = bc_file[0x1e8:0x1f0]
enc_data = bc_file[0x26:0x86]
encrypted_first_sector = bc_file[0x588100:0x588300]

p = 1
r = 16
N = pow(2, 15)

derived_first_key = scrypt.hash(password, salt, N, r, p)

first_aes_primary_key = derived_first_key[:0x20]
first_aes_tweak_key = derived_first_key[0x20:]
key = (first_aes_primary_key, first_aes_tweak_key)

cipher = python_Rijndael.new(key, python_Rijndael.MODE_XTS, blocksize=16)
decrypted_data = cipher.decrypt(enc_data)

hash_of_keys = scrypt.hash(decrypted_data[:0x40], b'', N, r, p, 0x20)
hashval = decrypted_data[0x40:]

print('first scrypt      : ', hexlify(derived_first_key))
print('first primary key       : ', hexlify(first_aes_primary_key))
print('first tweak key         : ', hexlify(first_aes_tweak_key))
print('hash value        : ', hexlify(hashval))
print('hash of keys      : ', hexlify(hash_of_keys))

if hash_of_keys == hashval:
    print('correct_password')
kholia commented 4 years ago

I am sharing a copy of an encrypted v4 disk. Might be helpful in debugging and in comparing notes.

https://github.com/kholia/bcve_otfe/blob/master/src/bcve_otfe/encrypted.raw.zip

Password is "openwall".

kholia commented 4 years ago

For this image:

In [9]: f = open("encrypted.raw", "rb").read(512000)                                                                                                                                

In [10]: f.rfind(b"\x4b\x9e\x5d\x53")                                                                                                                                               
Out[10]: 32744

In [11]: binascii.hexlify(f[32744:32744+32])                                                                                                                                        
Out[11]: b'4b9e5d53ba3dbb6a08100000000000000000000000000000f05292fcc3154625'

In [12]: binascii.hexlify(f[32744:32744+8])                                                                                                                                         
Out[12]: b'4b9e5d53ba3dbb6a'

I suspect that the v4 program is using a bigger salt somehow? Look at the hexdump (pointing to the salt buffer) in my debugger screenshot.

Also,

In [16]: f.find(b"\x1a\x0c\x40\xc9\xf1\x85")                                                                                                                                        
Out[16]: 32614

In [17]: binascii.hexlify(f[32614:32614+32])                                                                                                                                        
Out[17]: b'1a0c40c9f185c9aef9fc63f4561c96a310100000000000000404000000000000

This is the "salt continuation" sub-string we saw in the debugger screenshot.

kholia commented 4 years ago

I suspect that the v4 program is using a bigger salt somehow?

Confirmed.

$ cat code.py
# Demo

from binascii import hexlify
from sys import argv
import scrypt
#from CryptoPlus.Cipher import python_Rijndael
import binascii

password = b'xyzabc'

#bc_file = open(argv[1], 'rb').read(0x588300)

#salt = bc_file[0x1e8:0x1f0]
#salt = bc_file[0x1e8:0x1f0]
salt = "4b9e5d53ba3dbb6a" + "1a0c40c9f185c9aef9fc63f4561c96a3"  # latter is "salt continuation"
salt = binascii.unhexlify(salt)
#enc_data = bc_file[0x26:0x86]
#encrypted_first_sector = bc_file[0x588100:0x588300]

p = 1
r = 16
N = pow(2, 15)

derived_first_key = scrypt.hash(password, salt, N, r, p)
print(binascii.hexlify(derived_first_key))

This programs outputs b'3b43545d86b4e24b0870.... This is exactly the "key" that is also calculated by the target program (see Dump 2 in the screenshot below).

VirtualBox_XP_11_04_2020_13_24_15

trounce1 commented 4 years ago

I suspect that the v4 program is using a bigger salt somehow?

Confirmed.

$ cat code.py
# Demo

from binascii import hexlify
from sys import argv
import scrypt
#from CryptoPlus.Cipher import python_Rijndael
import binascii

password = b'xyzabc'

#bc_file = open(argv[1], 'rb').read(0x588300)

#salt = bc_file[0x1e8:0x1f0]
#salt = bc_file[0x1e8:0x1f0]
salt = "4b9e5d53ba3dbb6a" + "1a0c40c9f185c9aef9fc63f4561c96a3"  # latter is "salt continuation"
salt = binascii.unhexlify(salt)
#enc_data = bc_file[0x26:0x86]
#encrypted_first_sector = bc_file[0x588100:0x588300]

p = 1
r = 16
N = pow(2, 15)

derived_first_key = scrypt.hash(password, salt, N, r, p)
print(binascii.hexlify(derived_first_key))

This programs outputs b'3b43545d86b4e24b0870.... This is exactly the "key" that is also calculated by the target program (see Dump 2 in the screenshot below).

VirtualBox_XP_11_04_2020_13_24_15

amazin, so just need to work out how it calculates the salt and we are there

kholia commented 4 years ago

I can extract the full salt from the encrypted disks - not a problem.

The following code needs to be validated.

enc_data = bc_file[0x26:0x86]
...
first_aes_primary_key = derived_first_key[:0x20]
first_aes_tweak_key = derived_first_key[0x20:]
key = (first_aes_primary_key, first_aes_tweak_key)

cipher = python_Rijndael.new(key, python_Rijndael.MODE_XTS, blocksize=16)
decrypted_data = cipher.decrypt(enc_data)

hash_of_keys = scrypt.hash(decrypted_data[:0x40], b'', N, r, p, 0x20)
hashval = decrypted_data[0x40:]

print('first scrypt      : ', hexlify(derived_first_key))
print('first primary key       : ', hexlify(first_aes_primary_key))
print('first tweak key         : ', hexlify(first_aes_tweak_key))
print('hash value        : ', hexlify(hashval))
print('hash of keys      : ', hexlify(hash_of_keys))

if hash_of_keys == hashval:
    print('correct_password')

I am having trouble with this code, right from the enc_data extraction part.

kholia commented 4 years ago

Quick notes:

I am now trying to locate the higher level AES decryption code and XTS code. The idea is to see what data it is trying to decrypt.

trounce1 commented 4 years ago

Quick notes:

  • AES is located at 0x4CDBF0. <-- This could be "AES Key Expansion".

I am now trying to locate the higher level AES decryption code and XTS code. The idea is to see what data it is trying to decrypt.

and what the keys / tweak keys might be being used

kholia commented 4 years ago

The idea is to see what data it is trying to decrypt.

Found the data + it's offset.

In [45]: f.find(b"\xab\xf1\xcc\x52")                                                                                                                                                
Out[45]: 32294

Data-Offset

This screenshot shows the call to the decryption function and the disk data it is decrypting.

trounce1 commented 4 years ago

The idea is to see what data it is trying to decrypt.

Found the data + it's offset.

In [45]: f.find(b"\xab\xf1\xcc\x52")                                                                                                                                                
Out[45]: 32294

Data-Offset

This screenshot shows the call to the decryption function and the disk data it is decrypting.

yeah thats the same. you are looking at the full disk rather than the partition. your offset is 0x7e00 + the script offsets. ill write a quick patch on my script to account for this

trounce1 commented 4 years ago

The idea is to see what data it is trying to decrypt.

Found the data + it's offset.

In [45]: f.find(b"\xab\xf1\xcc\x52")                                                                                                                                                
Out[45]: 32294

Data-Offset This screenshot shows the call to the decryption function and the disk data it is decrypting.

yeah thats the same. you are looking at the full disk rather than the partition. your offset is 0x7e00 + the script offsets. ill write a quick patch on my script to account for this

password = b'xyzabc'

bc_file = open(argv[1], 'rb').read(0x588300)

offset = 0

if bc_file[0x1fe:0x200] == b'\x55\xaa':
    offset = 0x200 * struct.unpack('<i', bc_file[0x1c6:0x1ca])[0]

salt = bc_file[offset + 0x1e8:offset + 0x1f0] + bc_file[offset + 0x166:offset + 0x176]
enc_data = bc_file[offset + 0x26:offset + 0x86]
encrypted_first_sector = bc_file[offset + 0x588100:offset + 0x588300]

add that to your code, your first sector may vary

kholia commented 4 years ago

Thanks.

I am trying to trace this function now:

signed int __cdecl sub_4D2560(_DWORD *key, int n_8, int input_known, int output_likely)
{
  char *v4; // ecx
  _DWORD *v5; // ebx
  unsigned int v6; // edx
  bool v7; // cf
  int v9; // [esp+8h] [ebp-34h]
  int v10; // [esp+Ch] [ebp-30h]
  int v11; // [esp+10h] [ebp-2Ch]
  int v12; // [esp+14h] [ebp-28h]
  char v13; // [esp+18h] [ebp-24h]

  v9 = 0;
  v10 = 0;
  v11 = 0;
  v12 = 0;
  setup_keys_init(n_8, key, &ctx);
  is_this_xts_decrypt(n_8, input_known, output_likely, 96u, (int)&v9, 0, (int)&ctx, 0);
  simple_sha256_or_something_else(output_likely, 64u, (int)&v13);
  v4 = &v13;
  v5 = (_DWORD *)(output_likely + 64);
  v6 = 28;
  while ( *v5 == *(_DWORD *)v4 )
  {
    ++v5;
    v4 += 4;
    v7 = v6 < 4;
    v6 -= 4;
    if ( v7 )
      return 1;
  }
  return 0;
}

The input_known has the data (enc_data) from above screenshot. The scrypt key is getting calculated fine and is being passed to this function.

The next problem is that the decrypted data (for enc_data encrypted input) doesn't match between this target program and our Python code below.

derived_first_key = scrypt.hash(password, salt, N, r, p)
print(binascii.hexlify(derived_first_key))
print(len(derived_first_key))

first_aes_primary_key = derived_first_key[:0x20]
first_aes_tweak_key = derived_first_key[0x20:]
key = (first_aes_primary_key, first_aes_tweak_key)
#print(len(first_aes_tweak_key))
#print(len(first_aes_primary_key))

cipher = python_Rijndael.new(key, python_Rijndael.MODE_XTS, blocksize=16)
decrypted_data = cipher.decrypt(enc_data)
print(decrypted_data)
print(hexlify(decrypted_data))

Not sure what is_this_xts_decrypt is doing with enc_data. I know that AES Key Expansion is getting called twice which happens in XTS mode.

      case 8:                                   // TANGO! Setup two keys for XTS?
        aes_key_expansion(key_, v3);

        sub_4CD0E0(key_, v3 + 61);

        aes_key_expansion(key_ + 8, v3 + 122);

        result = 1;
        break;
kholia commented 4 years ago

@trounce1 Can you please share your full encrypted image? I will attach it to this debugging VM and step through the target program.

trounce1 commented 4 years ago

@trounce1 Can you please share your full encrypted image? I will attach it to this debugging VM and step through the target program.

trounce.zip

This also has my rescue file in it which should also contain the same 0x60 bytes The password is password

kholia commented 4 years ago

I know that AES Key Expansion is getting called twice which happens in XTS mode.

With better comments,

case 8:    // TANGO! Setup two keys for XTS?
           // https://github.com/magnumripper/JohnTheRipper/blob/bleeding-jumbo/src/xts_plug.c#L25

    AES_key_expansion_likely(key_, mystery_struct);

    AES_encrypt(key_, (_DWORD *)mystery_struct + 61);

    AES_key_expansion_likely(key_ + 32, (_DWORD *)mystery_struct + 122);

    result = 1;
    break;

Note: This code is at 004D3C8B location.

This still doesn't explain why our XTS decryption stuff is not working well. Hmmm.

kholia commented 4 years ago

Current Code:

# Demo Code (BCVE v4)

from binascii import hexlify
from sys import argv
import scrypt
import binascii
import hashlib
import struct

from CryptoPlus.Cipher import python_Rijndael
from Crypto.Cipher import AES

password = b'openwall'

bc_file = open(argv[1], 'rb').read(0x588300)

if bc_file[0x1fe:0x200] == b'\x55\xaa':
    offset = 0x200 * struct.unpack('<i', bc_file[0x1c6:0x1ca])[0]
print(offset)

# old kdf - program still tries it!
"""
salt = bc_file[offset + 0x1e8:offset + 0x1f0]
buffsize = 0x10000
buff = b''
while len(buff) < buffsize:
    buff += salt
    buff += password
dk = hashlib.sha256(buff).digest()
print(hexlify(dk))  # EB FA E9... the target exe calculates this too!
print(hexlify(dk[15:]))  # EB FA E9... the target exe calculates this too!
"""

salt = bc_file[offset + 0x1e8:offset + 0x1f0] + bc_file[offset + 0x166:offset + 0x176]
enc_data = bc_file[offset + 0x26:offset + 0x86]
encrypted_first_sector = bc_file[offset + 0x588100:offset + 0x588300]
print(hexlify(enc_data))

p = 1
r = 16
N = pow(2, 15)

derived_first_key = scrypt.hash(password, salt, N, r, p)
print(binascii.hexlify(derived_first_key))

first_aes_primary_key = derived_first_key[:0x20]
first_aes_tweak_key = derived_first_key[0x20:]
key = (first_aes_primary_key, first_aes_tweak_key)

cipher = python_Rijndael.new(key, python_Rijndael.MODE_XTS, blocksize=16)
decrypted_data = cipher.decrypt(enc_data)
print(decrypted_data)
print(hexlify(decrypted_data))

# hash_of_keys = scrypt.hash(decrypted_data[:0x40], b'', N, r, p, 0x20)
hash_of_keys = hashlib.sha256(decrypted_data[:64]).digest()
hashval = decrypted_data[64:96]

print('first scrypt      : ', hexlify(derived_first_key))
print('first primary key : ', hexlify(first_aes_primary_key))
print('first tweak key   : ', hexlify(first_aes_tweak_key))
print('hash value        : ', hexlify(hashval))
print('hash of keys      : ', hexlify(hash_of_keys))

if hash_of_keys == hashval:
    print('correct_password')

We just need to debug the data decryption part. Taking a break for now :)

trounce1 commented 4 years ago

Current Code:

# Demo Code (BCVE v4)

from binascii import hexlify
from sys import argv
import scrypt
import binascii
import hashlib
import struct

from CryptoPlus.Cipher import python_Rijndael
from Crypto.Cipher import AES

password = b'openwall'

bc_file = open(argv[1], 'rb').read(0x588300)

if bc_file[0x1fe:0x200] == b'\x55\xaa':
    offset = 0x200 * struct.unpack('<i', bc_file[0x1c6:0x1ca])[0]
print(offset)

# old kdf - program still tries it!
"""
salt = bc_file[offset + 0x1e8:offset + 0x1f0]
buffsize = 0x10000
buff = b''
while len(buff) < buffsize:
    buff += salt
    buff += password
dk = hashlib.sha256(buff).digest()
print(hexlify(dk))  # EB FA E9... the target exe calculates this too!
print(hexlify(dk[15:]))  # EB FA E9... the target exe calculates this too!
"""

salt = bc_file[offset + 0x1e8:offset + 0x1f0] + bc_file[offset + 0x166:offset + 0x176]
enc_data = bc_file[offset + 0x26:offset + 0x86]
encrypted_first_sector = bc_file[offset + 0x588100:offset + 0x588300]
print(hexlify(enc_data))

p = 1
r = 16
N = pow(2, 15)

derived_first_key = scrypt.hash(password, salt, N, r, p)
print(binascii.hexlify(derived_first_key))
print(len(derived_first_key))

first_aes_primary_key = derived_first_key[:0x20]
first_aes_tweak_key = derived_first_key[0x20:]
key = (first_aes_primary_key, first_aes_tweak_key)
#print(len(first_aes_tweak_key))
#print(len(first_aes_primary_key))

cipher = python_Rijndael.new(key, python_Rijndael.MODE_XTS, blocksize=16)
decrypted_data = cipher.decrypt(enc_data)
print(decrypted_data)
print(hexlify(decrypted_data))

# hash_of_keys = scrypt.hash(decrypted_data[:0x40], b'', N, r, p, 0x20)
hash_of_keys = hashlib.sha256(decrypted_data[:64]).digest()
hashval = decrypted_data[64:96]

print('first scrypt      : ', hexlify(derived_first_key))
print('first primary key : ', hexlify(first_aes_primary_key))
print('first tweak key   : ', hexlify(first_aes_tweak_key))
print('hash value        : ', hexlify(hashval))
print('hash of keys      : ', hexlify(hash_of_keys))

if hash_of_keys == hashval:
    print('correct_password')

We just need to debug the data decryption part. Taking a break for now :)

you think they are using sha256 at the end?

trounce1 commented 4 years ago

i have solved it !!!

from binascii import hexlify
from sys import argv
import scrypt
import struct
from hashlib import sha256
from Crypto.Cipher import AES

password = b'password'

bc_file = open(argv[1], 'rb').read(0x588300)

offset = 0

if bc_file[0x1fe:0x200] == b'\x55\xaa':
    offset = 0x200 * struct.unpack('<i', bc_file[0x1c6:0x1ca])[0]

salt = bc_file[offset + 0x1e8:offset + 0x1f0] + bc_file[offset + 0x166:offset + 0x176]
enc_data = bc_file[offset + 0x26:offset + 0x86]
encrypted_first_sector = bc_file[offset + 0x588100:offset + 0x588300]

p = 1
r = 16
N = pow(2, 15)

derived_first_key = scrypt.hash(password, salt, N, r, p)

first_aes_primary_key = derived_first_key[:0x20]

cipher = AES.new(first_aes_primary_key, AES.MODE_CBC, b'\0' * 16)
decrypted_data = cipher.decrypt(enc_data)

hash_of_keys = sha256(decrypted_data[:0x40]).digest()
hashval = decrypted_data[0x40:]

print('first scrypt      : ', hexlify(derived_first_key))
print('first primary key : ', hexlify(first_aes_primary_key))
print('hash value        : ', hexlify(hashval))
print('hash of keys      : ', hexlify(hash_of_keys))
kholia commented 4 years ago

Awesome! :) :) :)

sha256-likely

Confirmation that SHA256 is being used.

kholia commented 4 years ago

How did you figure out the cipher = AES.new(first_aes_primary_key, AES.MODE_CBC, b'\0' * 16) part?

The code was somehow indicating XTS mode usage (maybe it was already decrypting the first sector in the debugger and not just the initial verification header!?).

trounce1 commented 4 years ago

How did you figure out the cipher = AES.new(first_aes_primary_key, AES.MODE_CBC, b'\0' * 16) part?

The code was somehow indicating XTS mode usage (maybe it was already decrypting the first sector in the debugger and not just the initial verification header!?).

i just gave it a go and assumed that it hadnt changed. A kernel can now be written. ill do some research into the other 2 crypto algorithms that can be used and see if the behaviour is the same.

kholia commented 4 years ago

I did notice 16 NULL bytes in the debugger near AES code. Should have guessed a Zero IV. Doh! :)

trounce1 commented 4 years ago

I did notice 16 NULL bytes in the debugger near AES code. Should have guessed a Zero IV. Doh! :)

haha, at least this wasn't too hard to crack. the scryot settings are quite high. im not sure how cards will handle such high N values.

kholia commented 4 years ago

A kernel can now be written.

Yep!

To make bcve2john.py robust, can we scan for those blowfish sbox values to locate the BCVE header? Or is there a better way to do this?

trounce1 commented 4 years ago

A kernel can now be written.

Yep!

To make bcve2john.py robust, can we scan for those blowfish sbox values to locate the BCVE header? Or is there a better way to do this?

I think that is the best way. maybe also checking the MBR/GPT for multiple partitions and following the disk offsets.

trounce1 commented 4 years ago
Screenshot 2020-04-11 at 13 30 41

just confirmed that it is working and decrypting properly

kholia commented 4 years ago

If possible, let's not rely on MBR/GPT structures to find partitions. I wonder how slow a linear brute force for finding those Blowfish table values will be... probably too slow for bigger disks?

trounce1 commented 4 years ago

If possible, let's not rely on MBR/GPT structures to find partitions. I wonder how slow a linear brute force for finding those Blowfish table values will be... probably too slow for bigger disks?

True, if they are always block aligned, you would only need to check the first say 4 bytes of each 512 block. might be faster then

trounce1 commented 4 years ago

i have noticed that the first 16 bytes of the blowfish table appears a lot of times

kholia commented 4 years ago

BCVE must be writing backups of its header? Are they same? Can any of them be used for cracking purposes?

trounce1 commented 4 years ago

no they are not the same. and there are hundreds on a 10mb disk

trounce1 commented 4 years ago

you can tell v3 and v4 partitions apart by presence of the secondary salt at 0x166:0x176