1
mirror of https://github.com/hashcat/hashcat synced 2024-11-20 23:27:31 +01:00
hashcat/tools/test_modules/m22100.pm
2019-12-31 22:04:31 +01:00

303 lines
6.7 KiB
Perl

#!/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;
sub module_constraints { [[4, 256], [16, 16], [-1, -1], [-1, -1], [-1, -1]] }
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;