diff --git a/tools/test.pl b/tools/test.pl index d3985bc55..6536d60ce 100755 --- a/tools/test.pl +++ b/tools/test.pl @@ -8,7 +8,7 @@ use strict; use warnings; -use Data::Types qw (is_count is_whole); +use Data::Types qw (is_count is_int is_whole); use File::Basename; use FindBin; @@ -25,8 +25,9 @@ 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; } or die "Could not load test module: $module\n$@"; +eval { require $MODULE_FILE } or die "Could not load test module: $MODULE_FILE\n$@"; exists &{module_generate_hash} or die "Module function 'module_generate_hash' not found\n"; exists &{module_verify_hash} or die "Module function 'module_verify_hash' not found\n"; @@ -54,18 +55,21 @@ sub single { my $len = shift; + # fallback to incrementing length undef $len unless is_count ($len); my $format = "echo -n %-32s | ./hashcat \${OPTS} -a 0 -m %d '%s'\n"; for (my $i = 1; $i <= 32; $i++) { + # requested or incrementing length my $cur_len = $len // $i; my $word = random_numeric_string ($cur_len); my $hash = module_generate_hash ($word); + # possible if the requested length is not supported by algorithm next unless defined $hash; print sprintf ($format, $word, $MODE, $hash); @@ -116,8 +120,10 @@ sub verify my $hash = module_verify_hash ($line); + # possible if the hash:password pair does not match next unless defined $hash; + # possible if the hash is in cracksfile, but not in hashfile next unless is_in_array ($hash, $hashlist); print OUT "$line\n"; @@ -138,6 +144,7 @@ sub is_in_array return grep { $_ eq $value } @{$array}; } +# detect hashcat $HEX[...] notation and pack as binary sub pack_if_HEX_notation { my $string = shift; @@ -152,8 +159,33 @@ sub pack_if_HEX_notation return $string; } +# random_count (max) +# returns integer from 1 to max +sub random_count +{ + my $max = shift; + + return unless is_count $max; + + return int ((rand ($max - 1)) + 1); +} + +# random_number (min, max) +sub random_number +{ + my $min = shift; + my $max = shift; + + return unless is_int ($min); + return unless is_int ($max); + return unless $min lt $max; + + return int ((rand ($max - $min)) + $min); +} + sub random_bytes { + # length in bytes my $count = shift; return pack ("H*", random_hex_string (2 * $count)); @@ -161,6 +193,7 @@ sub random_bytes sub random_hex_string { + # length in characters my $count = shift; return if ! is_count ($count); @@ -172,6 +205,51 @@ sub random_hex_string return $string; } +sub random_lowercase_string +{ + my $count = shift; + + return if ! is_count ($count); + + my @chars = ('a'..'z'); + + my $string; + + $string .= $chars[rand @chars] for (1 .. $count); + + return $string; +} + +sub random_uppercase_string +{ + my $count = shift; + + return if ! is_count ($count); + + my @chars = ('A'..'Z'); + + my $string; + + $string .= $chars[rand @chars] for (1 .. $count); + + return $string; +} + +sub random_mixedcase_string +{ + my $count = shift; + + return if ! is_count ($count); + + my @chars = ('A'..'Z', 'a'..'z'); + + my $string; + + $string .= $chars[rand @chars] for (1 .. $count); + + return $string; +} + sub random_numeric_string { my $count = shift; @@ -185,6 +263,21 @@ sub random_numeric_string return $string; } +sub random_string +{ + my $count = shift; + + return if ! is_count ($count); + + my @chars = ('A'..'Z', 'a'..'z', '0'..'9'); + + my $string; + + $string .= $chars[rand @chars] for (1 .. $count); + + return $string; +} + sub usage_exit { my $f = basename ($0); diff --git a/tools/test_modules/README.md b/tools/test_modules/README.md index 35a951d77..810b6af25 100644 --- a/tools/test_modules/README.md +++ b/tools/test_modules/README.md @@ -1,15 +1,16 @@ ### Hashcat test modules ### -Each module provides the two functions `module_generate_hash` and `module_verify_hash`. The first parameter to `module_generate_hash` is the password, which can be either in ASCII or binary (packed) form. The `module_verify_hash` function accepts a line from the cracks file, without the newline characters. +Each module provides the functions `module_generate_hash` and `module_verify_hash`. The first parameter to `module_generate_hash` is the password, which can be either in ASCII or binary (packed) form. The `module_verify_hash` function accepts a line from the cracks file, without the newline characters. During `single` and `passthrough` tests the `module_generate_hash` function must provide random values (e.g. salt) for hash generation if necessary. The test.pl script offers a few handy functions like `random_hex_string`, `random_numeric_string` and `random_bytes`. You can implement your own salt generation functions, if your mode has specific requirements. -During `verify` tests the `module_verify_hash` function must parse the hash:password line and to calculate a hash by passing all necessary data to `module_generate_hash`. How you pass it is up to you, as long as the first parameter is the password. +During `verify` tests the `module_verify_hash` function must parse the hash:password line and calculate a hash by passing all necessary data to `module_generate_hash`. How you pass it is up to you, as long as the first parameter is the password. **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. #### Examples #### -* For the most basic test modules, see [m0.pm](m0.pm) and [m100.pm](m100.pm) -* For the basic salted hash tests, see [m110.pm](m110.pm) and [m120.pm](m120.pm) +* 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) diff --git a/tools/test_modules/m00000.pm b/tools/test_modules/m00000.pm index 8574ee74a..c9e8bddae 100644 --- a/tools/test_modules/m00000.pm +++ b/tools/test_modules/m00000.pm @@ -23,7 +23,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/m00100.pm b/tools/test_modules/m00100.pm index 7c750f937..100d08d0d 100644 --- a/tools/test_modules/m00100.pm +++ b/tools/test_modules/m00100.pm @@ -23,7 +23,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/m00110.pm b/tools/test_modules/m00110.pm index a1f1138ef..c6ccb9586 100644 --- a/tools/test_modules/m00110.pm +++ b/tools/test_modules/m00110.pm @@ -13,7 +13,7 @@ use Digest::SHA qw (sha1_hex); sub module_generate_hash { my $word = shift; - my $salt = shift // random_numeric_string (int (rand 16)); + my $salt = shift // random_numeric_string (random_count (15)); my $hash = sha1_hex ($word . $salt) . ":$salt"; @@ -24,7 +24,7 @@ sub module_verify_hash { my $line = shift; - my ($hash, $salt, $word) = split (":", $line); + my ($hash, $salt, $word) = split (':', $line); return unless defined $hash; return unless defined $salt; diff --git a/tools/test_modules/m00120.pm b/tools/test_modules/m00120.pm index 2f536f1f4..f302dea43 100644 --- a/tools/test_modules/m00120.pm +++ b/tools/test_modules/m00120.pm @@ -13,7 +13,7 @@ use Digest::SHA qw (sha1_hex); sub module_generate_hash { my $word = shift; - my $salt = shift // random_numeric_string (int (rand 16)); + my $salt = shift // random_numeric_string (random_count (15)); my $hash = sha1_hex ($salt . $word) . ":$salt"; @@ -24,7 +24,7 @@ sub module_verify_hash { my $line = shift; - my ($hash, $salt, $word) = split (":", $line); + my ($hash, $salt, $word) = split (':', $line); return unless defined $hash; return unless defined $salt; diff --git a/tools/test_modules/m05600.pm b/tools/test_modules/m05600.pm new file mode 100644 index 000000000..082520c24 --- /dev/null +++ b/tools/test_modules/m05600.pm @@ -0,0 +1,89 @@ +#!/usr/bin/env perl + +## +## Author......: See docs/credits.txt +## License.....: MIT +## + +use strict; + +use Authen::Passphrase::NTHash; +use Digest::HMAC qw (hmac hmac_hex); +use Digest::MD5 qw (md5); +use Encode qw (encode); + +sub module_generate_hash +{ + my $word = shift; + + my $user_len = random_number (0, 27); + my $domain_len = 27 - $user_len; + + my $user = shift // random_string ($user_len); + my $domain = shift // random_string ($domain_len); + 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 $nthash = Authen::Passphrase::NTHash->new (passphrase => $word)->hash; + 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); + + return $hash; +} + +sub module_verify_hash +{ + my $line = shift; + + my $user; + my $domain; + my $srv_ch; + my $cli_ch; + my $word; + + my $hash; + + 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; + return if $index3 eq -1; + + $hash = substr ($line, 0, $index3); + + $user = substr ($line, 0, $index1); + $domain = substr ($line, $index1 + 2, $index2 - $index1 - 2); + $srv_ch = substr ($line, $index2 + 1, 16); + $cli_ch = substr ($line, $index2 + 3 + 16 + 32, $index3 - $index2 - 3 - 16 - 32); + $word = substr ($line, $index3 + 1); + + $word = pack_if_HEX_notation ($word); + + my $new_hash = module_generate_hash ($word, $user, $domain, $srv_ch, $cli_ch); + + return unless lc $new_hash eq lc $hash; + + return $new_hash; +} + +sub random_client_challenge +{ + my $ch; + + $ch .= '0101000000000000'; + $ch .= random_hex_string (2*16); + $ch .= '00000000'; + $ch .= random_hex_string(2 * random_count (20)); + $ch .= '00'; + + return $ch; +} + +1; diff --git a/tools/test_modules/m18400.pm b/tools/test_modules/m18400.pm index 0391866b8..bc5efcbef 100644 --- a/tools/test_modules/m18400.pm +++ b/tools/test_modules/m18400.pm @@ -47,7 +47,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/m18600.pm b/tools/test_modules/m18600.pm index 2b3e307db..d9ee97b79 100644 --- a/tools/test_modules/m18600.pm +++ b/tools/test_modules/m18600.pm @@ -60,7 +60,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;