From 6365672c347eaef6ebe8d93e3b96afd297d565a5 Mon Sep 17 00:00:00 2001 From: "R. Yushaev" <44146334+Naufragous@users.noreply.github.com> Date: Sat, 22 Dec 2018 19:29:24 +0100 Subject: [PATCH] Add module_preprocess_hashlist hook for tests Some algorithms have ambiguous hashes (e.g. case-insensetive usernames in Net-NTLMv2 hashes). This optional function allows test modules to unify the hashlist before the verification process starts. Also update readme and minor code formatting. --- tools/test.pl | 17 ++++++++++++----- tools/test_modules/README.md | 4 +++- tools/test_modules/m01000.pm | 4 ++-- tools/test_modules/m01500.pm | 2 +- tools/test_modules/m05600.pm | 31 ++++++++++++++++++++++++------- tools/test_modules/m18400.pm | 16 ++++++++-------- tools/test_modules/m18600.pm | 16 ++++++++-------- 7 files changed, 58 insertions(+), 32 deletions(-) diff --git a/tools/test.pl b/tools/test.pl index 6536d60ce..4575d2abb 100755 --- a/tools/test.pl +++ b/tools/test.pl @@ -24,7 +24,6 @@ is_in_array ($TYPE, $TYPES) or usage_exit (); is_whole ($MODE) or die "Mode must be a number\n"; -my $module = sprintf ("m%05d.pm", $MODE); my $MODULE_FILE = sprintf ("m%05d.pm", $MODE); eval { require $MODULE_FILE } or die "Could not load test module: $MODULE_FILE\n$@"; @@ -98,18 +97,24 @@ sub verify open (IN, '<', $hashes_file) or die "$hashes_file: $!\n"; - my $hashlist; + my @hashlist; while (my $line = ) { $line =~ s/\n$//; $line =~ s/\r$//; - push (@{$hashlist}, $line); + push (@hashlist, $line); } close (IN); + # resolve hash ambiguity if necessary + if (exists &{module_preprocess_hashlist}) + { + module_preprocess_hashlist (\@hashlist); + } + open (IN, '<', $cracks_file) or die "$cracks_file: $!\n"; open (OUT, '>', $out_file ) or die "$out_file: $!\n"; @@ -124,7 +129,7 @@ sub verify next unless defined $hash; # possible if the hash is in cracksfile, but not in hashfile - next unless is_in_array ($hash, $hashlist); + next unless is_in_array ($hash, \@hashlist); print OUT "$line\n"; } @@ -256,9 +261,11 @@ sub random_numeric_string return if ! is_count ($count); + my @chars = ('0'..'9'); + my $string; - $string .= sprintf ("%d", rand 10) for (1 .. $count); + $string .= $chars[rand @chars] for (1 .. $count); return $string; } diff --git a/tools/test_modules/README.md b/tools/test_modules/README.md index 810b6af25..35d03fda1 100644 --- a/tools/test_modules/README.md +++ b/tools/test_modules/README.md @@ -8,9 +8,11 @@ During `verify` tests the `module_verify_hash` function must parse the hash:pass **Important**: You have to call `pack_if_HEX_notation` as soon as you have parsed the password, or your tests will fail on passwords in the `$HEX[...]` format. +If the algorithm has ambiguous hashes (e.g. partial case-insensetivity), the test module can provide an optional function `module_preprocess_hashlist`. It recieves a reference to the hashlist array and can unify the hashes in a way that guarantees the match with the output of `module_verify_hash`. + #### Examples #### * For the most basic test modules, see [m00000.pm](m00000.pm) and [m00100.pm](m00100.pm) * For the basic salted hash tests, see [m00110.pm](m00110.pm) and [m00120.pm](m00120.pm) * For some sligthly more complex modules with PBKDF2 and encryption, see [m18400.pm](m18400.pm) and [m18600.pm](m18600.pm) -* For a test module with a custom salt generation algorithm, see [m05600.pm](m05600.pm) +* For a test module with hashlist preprocessing and a custom salt generation algorithm, see [m05600.pm](m05600.pm) diff --git a/tools/test_modules/m01000.pm b/tools/test_modules/m01000.pm index dbfbbe865..45bde92e1 100644 --- a/tools/test_modules/m01000.pm +++ b/tools/test_modules/m01000.pm @@ -17,7 +17,7 @@ sub module_generate_hash return if length $word > 27; - my $hash = md4_hex (encode ("UTF-16LE", $word)); + my $hash = md4_hex (encode ('UTF-16LE', $word)); return $hash; } @@ -26,7 +26,7 @@ sub module_verify_hash { my $line = shift; - my ($hash, $word) = split ":", $line; + my ($hash, $word) = split (':', $line); return unless defined $hash; return unless defined $word; diff --git a/tools/test_modules/m01500.pm b/tools/test_modules/m01500.pm index 13bd8e85b..bf8a64fc0 100644 --- a/tools/test_modules/m01500.pm +++ b/tools/test_modules/m01500.pm @@ -24,7 +24,7 @@ sub module_verify_hash { my $line = shift; - my ($hash, $word) = split ":", $line; + my ($hash, $word) = split (':', $line); return unless defined $hash; return unless defined $word; diff --git a/tools/test_modules/m05600.pm b/tools/test_modules/m05600.pm index 082520c24..72cd2718f 100644 --- a/tools/test_modules/m05600.pm +++ b/tools/test_modules/m05600.pm @@ -24,11 +24,11 @@ sub module_generate_hash my $srv_ch = shift // random_hex_string (2*8); my $cli_ch = shift // random_client_challenge (); - my $b_srv_ch = pack ("H*", $srv_ch); - my $b_cli_ch = pack ("H*", $cli_ch); + my $b_srv_ch = pack ('H*', $srv_ch); + my $b_cli_ch = pack ('H*', $cli_ch); my $nthash = Authen::Passphrase::NTHash->new (passphrase => $word)->hash; - my $identity = encode ("UTF-16LE", uc ($user) . $domain); + my $identity = encode ('UTF-16LE', uc ($user) . $domain); my $hash_buf = hmac_hex ($b_srv_ch . $b_cli_ch, hmac ($identity, $nthash, \&md5, 64), \&md5, 64); my $hash = sprintf ("%s::%s:%s:%s:%s", $user, $domain, $srv_ch, $hash_buf, $cli_ch); @@ -48,9 +48,9 @@ sub module_verify_hash my $hash; - my $index1 = index ($line, "::"); - my $index2 = index ($line, ":", $index1 + 2); - my $index3 = index ($line, ":", $index2 + 3 + 16 + 32); + my $index1 = index ($line, '::'); + my $index2 = index ($line, ':', $index1 + 2); + my $index3 = index ($line, ':', $index2 + 3 + 16 + 32); return if $index1 eq -1; return if $index2 eq -1; @@ -68,11 +68,28 @@ sub module_verify_hash my $new_hash = module_generate_hash ($word, $user, $domain, $srv_ch, $cli_ch); - return unless lc $new_hash eq lc $hash; + # resolve lowercase/uppercase ambiguity in the username + # this will also guarantee a match with the preprocessed hashlist + $hash = lc $hash; + $new_hash = lc $new_hash; + + return unless $new_hash eq $hash; return $new_hash; } +# algorithm is case-insensitive in regard to usernames +# hashcat output always is uppercase, while real world hashes are not +sub module_preprocess_hashlist +{ + my $hashlist = shift; + + for my $hash (@{$hashlist}) + { + $hash = lc $hash; + } +} + sub random_client_challenge { my $ch; diff --git a/tools/test_modules/m18400.pm b/tools/test_modules/m18400.pm index bc5efcbef..539af94fa 100644 --- a/tools/test_modules/m18400.pm +++ b/tools/test_modules/m18400.pm @@ -20,9 +20,9 @@ sub module_generate_hash my $iv = shift // random_hex_string (2*16); my $plain = shift // random_hex_string (2*1024); - my $b_iv = pack ("H*", $iv); - my $b_salt = pack ("H*", $salt); - my $b_plain = pack ("H*", $plain); + my $b_iv = pack ('H*', $iv); + my $b_salt = pack ('H*', $salt); + my $b_plain = pack ('H*', $plain); my $kdf = Crypt::PBKDF2->new ( @@ -35,7 +35,7 @@ sub module_generate_hash my $key = $kdf->PBKDF2 ($b_salt, $pass_hash); my $cbc = Crypt::Mode::CBC->new ('AES', 0); my $b_cipher = $cbc->encrypt ($b_plain, $key, $b_iv); - my $cipher = unpack ("H*", $b_cipher); + my $cipher = unpack ('H*', $b_cipher); my $checksum = sha256_hex ($b_plain); my $hash = '$odf$'."*1*1*$iter*32*$checksum*16*$iv*16*$salt*0*$cipher"; @@ -83,9 +83,9 @@ sub module_verify_hash return unless defined $cipher; # decrypt - my $b_iv = pack ("H*", $iv); - my $b_salt = pack ("H*", $salt); - my $b_cipher = pack ("H*", $cipher); + my $b_iv = pack ('H*', $iv); + my $b_salt = pack ('H*', $salt); + my $b_cipher = pack ('H*', $cipher); my $kdf = Crypt::PBKDF2->new ( @@ -98,7 +98,7 @@ sub module_verify_hash my $key = $kdf->PBKDF2 ($b_salt, $pass_hash); my $cbc = Crypt::Mode::CBC->new ('AES', 0); my $b_plain = $cbc->decrypt ($b_cipher, $key, $b_iv); - my $plain = unpack ("H*", $b_plain); + my $plain = unpack ('H*', $b_plain); my $new_hash = module_generate_hash ($word, $salt, $iter, $iv, $plain); diff --git a/tools/test_modules/m18600.pm b/tools/test_modules/m18600.pm index d9ee97b79..3ec06e2ba 100644 --- a/tools/test_modules/m18600.pm +++ b/tools/test_modules/m18600.pm @@ -20,9 +20,9 @@ sub module_generate_hash my $iv = shift // random_hex_string (2*8); my $plain = shift // random_hex_string (2*1024); - my $b_iv = pack ("H*", $iv); - my $b_salt = pack ("H*", $salt); - my $b_plain = pack ("H*", $plain); + my $b_iv = pack ('H*', $iv); + my $b_salt = pack ('H*', $salt); + my $b_plain = pack ('H*', $plain); my $kdf = Crypt::PBKDF2->new ( @@ -48,7 +48,7 @@ sub module_generate_hash $cfb->finish (); - my $cipher = unpack ("H*", $b_cipher); + my $cipher = unpack ('H*', $b_cipher); my $checksum = sha1_hex ($b_plain); my $hash = '$odf$'."*0*0*$iter*16*$checksum*8*$iv*16*$salt*0*$cipher"; @@ -96,9 +96,9 @@ sub module_verify_hash return unless defined $cipher; # decrypt - my $b_iv = pack ("H*", $iv); - my $b_salt = pack ("H*", $salt); - my $b_cipher = pack ("H*", $cipher); + my $b_iv = pack ('H*', $iv); + my $b_salt = pack ('H*', $salt); + my $b_cipher = pack ('H*', $cipher); my $kdf = Crypt::PBKDF2->new ( @@ -124,7 +124,7 @@ sub module_verify_hash $cfb->finish (); - my $plain = unpack ("H*", $b_plain); + my $plain = unpack ('H*', $b_plain); my $new_hash = module_generate_hash ($word, $salt, $iter, $iv, $plain);