2019-12-31 17:42:13 +00:00
|
|
|
#!/usr/bin/env perl
|
|
|
|
|
|
|
|
##
|
|
|
|
## Author......: See docs/credits.txt
|
|
|
|
## License.....: MIT
|
|
|
|
##
|
|
|
|
|
|
|
|
use strict;
|
|
|
|
use warnings;
|
|
|
|
|
|
|
|
use Digest::SHA qw (sha256);
|
|
|
|
use Crypt::Mode::ECB;
|
|
|
|
use Encode;
|
|
|
|
|
2019-12-31 21:04:31 +00:00
|
|
|
sub module_constraints { [[4, 256], [16, 16], [-1, -1], [-1, -1], [-1, -1]] }
|
2019-12-31 17:42:13 +00:00
|
|
|
|
|
|
|
my $ITER = 1048576; # 0x100000
|
|
|
|
my $SALT_LEN = 16;
|
|
|
|
my $IV_LEN = 12;
|
|
|
|
my $MAC_LEN = 16;
|
|
|
|
my $VMK_LEN = 44; # note: MAC_LEN + VMK_LEN = 60
|
|
|
|
|
|
|
|
sub bitlocker_kdf
|
|
|
|
{
|
|
|
|
my $initial_hash = shift;
|
|
|
|
my $salt = shift;
|
|
|
|
|
|
|
|
# password_key_data (88 bytes):
|
|
|
|
# 0-31 (32): last_hash
|
|
|
|
# 32-63 (32): init_hash
|
|
|
|
# 64-79 (16): salt
|
|
|
|
# 80-87 ( 8): iter
|
|
|
|
|
|
|
|
my $password_key_data = "\x00" x (32 + 32 + 16 + 8);
|
|
|
|
|
|
|
|
substr ($password_key_data, 32, 32) = $initial_hash;
|
|
|
|
substr ($password_key_data, 64, 16) = $salt;
|
|
|
|
|
|
|
|
for (my $iter = 0; $iter < 0x100000; $iter++)
|
|
|
|
{
|
|
|
|
substr ($password_key_data, 80, 8) = pack ("Q", $iter);
|
|
|
|
|
|
|
|
substr ($password_key_data, 0, 32) = sha256 ($password_key_data);
|
|
|
|
}
|
|
|
|
|
|
|
|
return substr ($password_key_data, 0, 32); # AES-CCM key
|
|
|
|
}
|
|
|
|
|
|
|
|
# non-standard/variant of AES-CCM (encrypt or decrypt, both => crypt):
|
|
|
|
|
|
|
|
sub bitlocker_crypt_data
|
|
|
|
{
|
|
|
|
my $key = shift;
|
|
|
|
my $data = shift;
|
|
|
|
my $iv = shift;
|
|
|
|
|
|
|
|
my $ret = ""; # return value (output buffer)
|
|
|
|
|
|
|
|
my $iiv = "\x02"; # 15 - length ($iv) - 1 = 14 - length ($iv)
|
|
|
|
|
|
|
|
$iiv = $iiv . $iv . "\x00\x00\x00"; # add "\x00" x (16 - length ($iv))
|
|
|
|
|
|
|
|
# we could do this in a loop (but let's unroll it to make it clear what is going on):
|
|
|
|
# (first and last are special)
|
|
|
|
|
|
|
|
# 0
|
|
|
|
|
|
|
|
# substr ($iiv, 15, 1) = "\x00";
|
|
|
|
|
|
|
|
my $aes = Crypt::Mode::ECB->new ('AES', 0);
|
|
|
|
|
|
|
|
my $block = $aes->encrypt ($iiv, $key);
|
|
|
|
|
|
|
|
for (my $i = 0; $i < 16; $i++)
|
|
|
|
{
|
|
|
|
$ret .= chr (ord (substr ($data, $i, 1)) ^ ord (substr ($block, $i, 1)));
|
|
|
|
}
|
|
|
|
|
|
|
|
# 1
|
|
|
|
|
|
|
|
substr ($iiv, 15, 1) = "\x01";
|
|
|
|
|
|
|
|
$block = $aes->encrypt ($iiv, $key);
|
|
|
|
|
|
|
|
for (my $i = 0; $i < 16; $i++)
|
|
|
|
{
|
|
|
|
$ret .= chr (ord (substr ($data, 16 + $i, 1)) ^ ord (substr ($block, $i, 1)));
|
|
|
|
}
|
|
|
|
|
|
|
|
# 2
|
|
|
|
|
|
|
|
substr ($iiv, 15, 1) = "\x02";
|
|
|
|
|
|
|
|
$block = $aes->encrypt ($iiv, $key);
|
|
|
|
|
|
|
|
for (my $i = 0; $i < 16; $i++)
|
|
|
|
{
|
|
|
|
$ret .= chr (ord (substr ($data, 32 + $i, 1)) ^ ord (substr ($block, $i, 1)));
|
|
|
|
}
|
|
|
|
|
|
|
|
# 3 (final/remaining data: 12 bytes):
|
|
|
|
|
|
|
|
substr ($iiv, 15, 1) = "\x03";
|
|
|
|
|
|
|
|
$block = $aes->encrypt ($iiv, $key);
|
|
|
|
|
|
|
|
for (my $i = 0; $i < 12; $i++)
|
|
|
|
{
|
|
|
|
$ret .= chr (ord (substr ($data, 48 + $i, 1)) ^ ord (substr ($block, $i, 1)));
|
|
|
|
}
|
|
|
|
|
|
|
|
return $ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
sub bitlocker_generate_mac
|
|
|
|
{
|
|
|
|
my $key = shift;
|
|
|
|
my $data = shift;
|
|
|
|
my $iv = shift;
|
|
|
|
|
|
|
|
my $iiv = "\x3a" . $iv . "\x00\x00" . "\x2c";
|
|
|
|
|
|
|
|
# we could do this in a loop (but let's unroll it to make it clear what is going on):
|
|
|
|
# (first and last are special)
|
|
|
|
|
|
|
|
# 0
|
|
|
|
|
|
|
|
my $aes = Crypt::Mode::ECB->new ('AES', 0);
|
|
|
|
|
|
|
|
my $block = $aes->encrypt ($iiv, $key);
|
|
|
|
|
|
|
|
my $res = "";
|
|
|
|
|
|
|
|
for (my $i = 0; $i < 16; $i++)
|
|
|
|
{
|
|
|
|
$res .= chr (ord (substr ($data, $i, 1)) ^ ord (substr ($block, $i, 1)));
|
|
|
|
}
|
|
|
|
|
|
|
|
# 1
|
|
|
|
|
|
|
|
$block = $aes->encrypt ($res, $key);
|
|
|
|
|
|
|
|
$res = "";
|
|
|
|
|
|
|
|
for (my $i = 0; $i < 16; $i++)
|
|
|
|
{
|
|
|
|
$res .= chr (ord (substr ($data, 16 + $i, 1)) ^ ord (substr ($block, $i, 1)));
|
|
|
|
}
|
|
|
|
|
|
|
|
# 2
|
|
|
|
|
|
|
|
$block = $aes->encrypt ($res, $key);
|
|
|
|
|
|
|
|
$res = "";
|
|
|
|
|
|
|
|
for (my $i = 0; $i < 12; $i++)
|
|
|
|
{
|
|
|
|
$res .= chr (ord (substr ($data, 32 + $i, 1)) ^ ord (substr ($block, $i, 1)));
|
|
|
|
}
|
|
|
|
|
|
|
|
# 3
|
|
|
|
|
|
|
|
$block = $aes->encrypt ($res . substr ($block, 12, 4), $key);
|
|
|
|
|
|
|
|
return $block;
|
|
|
|
}
|
|
|
|
|
|
|
|
sub module_generate_hash
|
|
|
|
{
|
|
|
|
my $word = shift;
|
|
|
|
my $salt = shift;
|
|
|
|
my $iv = shift // random_bytes (12);
|
|
|
|
my $data = shift; # if not set, we're going to "generate"/fake it below
|
|
|
|
my $type = shift // random_number (0, 1); # if set to 1: check also the MAC in hashcat
|
|
|
|
|
|
|
|
|
|
|
|
# key generation (KDF):
|
|
|
|
|
|
|
|
my $word_utf16le = encode ("UTF-16LE", $word);
|
|
|
|
|
|
|
|
my $pass_hash = sha256 (sha256 ($word_utf16le));
|
|
|
|
|
|
|
|
my $key = bitlocker_kdf ($pass_hash, $salt);
|
|
|
|
|
|
|
|
|
|
|
|
if (! $data)
|
|
|
|
{
|
|
|
|
$data = pack ("H*", "2c000000"); # actually, only 0x2c00 can be expected for sure
|
|
|
|
$data .= pack ("H*", "01000000"); # actually, only 0x0100 can be expected for sure
|
|
|
|
$data .= chr (random_number (0, 5));
|
|
|
|
$data .= pack ("H*", "200000"); # actually, only 0x20 can be expected for sure
|
|
|
|
|
|
|
|
$data .= random_bytes (44 - 12); # 44 - bytes that we set above
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
# verification:
|
|
|
|
|
|
|
|
my $dec_data = bitlocker_crypt_data ($key, $data, $iv); # decryption
|
|
|
|
|
|
|
|
my $data_size = ord (substr ($dec_data, 16, 1)) | (ord (substr ($dec_data, 17, 1)) << 8);
|
|
|
|
my $version = ord (substr ($dec_data, 20, 1)) | (ord (substr ($dec_data, 21, 1)) << 8);
|
|
|
|
|
|
|
|
my $v1 = ord (substr ($dec_data, 16 + 8, 1)); # Volume Master Key (VMK) + 8
|
|
|
|
my $v2 = ord (substr ($dec_data, 16 + 9, 1)); # Volume Master Key (VMK) + 9
|
|
|
|
|
|
|
|
# early ejects / failed:
|
|
|
|
|
|
|
|
return unless ($data_size == 0x2c);
|
|
|
|
return unless ($version == 0x01);
|
|
|
|
return unless ($v2 == 0x20);
|
|
|
|
return unless ($v1 <= 0x05);
|
|
|
|
|
|
|
|
$data = substr ($dec_data, 16); # skip the MAC such that we get only the raw data (VMK etc)
|
|
|
|
|
|
|
|
# note: we do NOT check the $type value ... we do the MAC verification anyway to be safe
|
|
|
|
# (for "verify" and $type set to 0 - no MAC verification -, we could early exit here already)
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
# MAC (authenticate-then-encrypt, MAC first!):
|
|
|
|
|
|
|
|
my $mac = bitlocker_generate_mac ($key, $data, $iv);
|
|
|
|
|
|
|
|
|
|
|
|
# encrypt (both, MAC + VMK):
|
|
|
|
|
|
|
|
my $mac_vmk = $mac . $data;
|
|
|
|
|
|
|
|
my $enc_data = bitlocker_crypt_data ($key, $mac_vmk, $iv); # encryption
|
|
|
|
|
|
|
|
|
|
|
|
# output:
|
|
|
|
|
|
|
|
my $hash = sprintf ("\$bitlocker\$%i\$%i\$%s\$%i\$%i\$%s\$%i\$%s",
|
|
|
|
$type,
|
|
|
|
$SALT_LEN,
|
|
|
|
unpack ("H*", $salt),
|
|
|
|
$ITER,
|
|
|
|
$IV_LEN,
|
|
|
|
unpack ("H*", $iv),
|
|
|
|
$MAC_LEN + $VMK_LEN,
|
|
|
|
unpack ("H*", $enc_data));
|
|
|
|
|
|
|
|
return $hash;
|
|
|
|
}
|
|
|
|
|
|
|
|
sub module_verify_hash
|
|
|
|
{
|
|
|
|
my $line = shift;
|
|
|
|
|
|
|
|
my $idx = index ($line, ':');
|
|
|
|
|
|
|
|
return if ($idx < 0);
|
|
|
|
|
|
|
|
my $hash = substr ($line, 0, $idx);
|
|
|
|
my $word = substr ($line, $idx + 1);
|
|
|
|
|
|
|
|
return unless defined $hash;
|
|
|
|
return unless defined $word;
|
|
|
|
|
|
|
|
my @data = split ('\$', $hash);
|
|
|
|
|
|
|
|
return unless (scalar (@data) == 10);
|
|
|
|
|
|
|
|
my $signature = $data[1];
|
|
|
|
my $type = $data[2];
|
|
|
|
my $salt_len = $data[3];
|
|
|
|
my $salt = $data[4];
|
|
|
|
my $iter = $data[5];
|
|
|
|
my $iv_len = $data[6];
|
|
|
|
my $iv = $data[7];
|
|
|
|
my $data_len = $data[8];
|
|
|
|
my $data = $data[9];
|
|
|
|
|
|
|
|
# sanity checks:
|
|
|
|
|
|
|
|
return unless ($signature eq "bitlocker");
|
|
|
|
|
|
|
|
return unless ($salt_len == $SALT_LEN);
|
|
|
|
return unless ($iv_len == $IV_LEN);
|
|
|
|
return unless ($data_len == $MAC_LEN + $VMK_LEN);
|
|
|
|
|
|
|
|
# hex to binary conversion:
|
|
|
|
|
|
|
|
$salt = pack ("H*", $salt);
|
|
|
|
$iv = pack ("H*", $iv);
|
|
|
|
$data = pack ("H*", $data);
|
|
|
|
|
|
|
|
return unless (length ($salt) == $SALT_LEN);
|
|
|
|
return unless (length ($iv) == $IV_LEN);
|
|
|
|
return unless (length ($data) == $MAC_LEN + $VMK_LEN);
|
|
|
|
|
|
|
|
|
|
|
|
my $word_packed = pack_if_HEX_notation ($word);
|
|
|
|
|
|
|
|
my $new_hash = module_generate_hash ($word_packed, $salt, $iv, $data, $type);
|
|
|
|
|
|
|
|
return ($new_hash, $word);
|
|
|
|
}
|
|
|
|
|
|
|
|
1;
|