From d0478e44816e9c5ae67a3a5b18aac23296de4073 Mon Sep 17 00:00:00 2001 From: "R. Yushaev" <44146334+Naufragous@users.noreply.github.com> Date: Thu, 20 Dec 2018 17:08:11 +0100 Subject: [PATCH 1/3] Rename test.pl to legacy_test.pl --- tools/{test.pl => legacy_test.pl} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tools/{test.pl => legacy_test.pl} (100%) diff --git a/tools/test.pl b/tools/legacy_test.pl similarity index 100% rename from tools/test.pl rename to tools/legacy_test.pl From a92ab33ad54e8b4dc6daf64c44fd77bf0b2bd8fe Mon Sep 17 00:00:00 2001 From: "R. Yushaev" <44146334+Naufragous@users.noreply.github.com> Date: Thu, 20 Dec 2018 18:03:48 +0100 Subject: [PATCH 2/3] Add modularized test.pl --- tools/test.pl | 211 +++++++++++++++++++++++++++++++++++ tools/test_modules/README.md | 9 ++ 2 files changed, 220 insertions(+) create mode 100755 tools/test.pl create mode 100644 tools/test_modules/README.md diff --git a/tools/test.pl b/tools/test.pl new file mode 100755 index 000000000..548dde66e --- /dev/null +++ b/tools/test.pl @@ -0,0 +1,211 @@ +#!/usr/bin/env perl + +## +## Author......: See docs/credits.txt +## License.....: MIT +## + +use strict; +use warnings; + +use Data::Types qw (is_count is_whole); +use File::Basename; +use FindBin; + +# allows require by filename +use lib "$FindBin::Bin/test_modules"; + +my $TYPES = [ 'single', 'passthrough', 'verify' ]; + +my $TYPE = shift @ARGV; +my $MODE = shift @ARGV; + +is_in_array ($TYPE, $TYPES) or usage_exit (); + +is_whole ($MODE) or die "Mode must be a number\n"; + +eval { require "m$MODE.pm" } or die "Could not load test module:\n$@"; + +if ($TYPE eq 'single') +{ + single (@ARGV); +} +elsif ($TYPE eq 'passthrough') +{ + passthrough (); +} +elsif ($TYPE eq "verify") +{ + usage_exit () if scalar @ARGV != 3; + + verify (@ARGV); +} +else +{ + usage_exit (); +} + +sub single +{ + exists &{module_generate_hash} or die "Module function not found\n"; + + my $len = shift; + + undef $len unless is_count ($len); + + my $format = "echo -n %-32s | ./hashcat \${OPTS} -a0 -m%d '%s'\n"; + + for (my $i = 1; $i <= 32; $i++) + { + my $cur_len = $len // $i; + + my $word = random_numeric_string ($cur_len); + my $hash = module_generate_hash ($word); + + next unless defined $hash; + + print sprintf ($format, $word, $MODE, $hash); + } +} + +sub passthrough +{ + exists &{module_generate_hash} or die "Module function not found\n"; + + while (<>) + { + chomp $_; + + next if length $_ > 256; + + my $hash = module_generate_hash ($_); + + print "$hash\n"; + } +} + +sub verify +{ + exists &{module_verify_hash} or die "Module function not found\n"; + + my $hashes_file = shift; + my $cracks_file = shift; + my $out_file = shift; + + open (IN, '<', $hashes_file) or die "$hashes_file: $!\n"; + + my $hashlist; + + while () + { + s/[\n\r]*$//; + + push (@{$hashlist}, $_); + } + + close IN; + + open (IN, '<', $cracks_file) or die "$cracks_file: $!\n"; + open (OUT, '>', $out_file ) or die "$out_file: $!\n"; + + while () + { + s/[\n\r]*$//; + + my $hash = module_verify_hash ($_); + + next unless defined $hash; + + next unless is_in_array ($hash, $hashlist); + + print OUT "$_\n"; + } + + close IN; + close OUT; +} + +sub is_in_array +{ + my $value = shift; + my $array = shift; + + return unless defined $value; + return unless defined $array; + + return grep { $_ eq $value } @{$array}; +} + +sub pack_if_HEX_notation +{ + my $string = shift; + + return unless defined $string; + + if ($string =~ m/^\$HEX\[[0-9a-fA-F]*\]$/) + { + return pack ("H*", substr ($string, 5, -1)); + } + + return $string; +} + +sub random_bytes +{ + my $count = shift; + + return pack ("H*", random_hex_string (2 * $count)); +} + +sub random_hex_string +{ + my $count = shift; + + return if ! is_count ($count); + + my $string; + + $string .= sprintf("%x", rand 16) for (1 .. $count); + + return $string; +} + +sub random_numeric_string +{ + my $count = shift; + + return if ! is_count ($count); + + my $string; + + $string .= sprintf("%d", rand 10) for (1 .. $count); + + return $string; +} + +sub usage_exit +{ + my $f = basename ($0); + + print "\n" + . "Usage:\n" + . " $f single [length]\n" + . " $f passthrough \n" + . " $f verify \n" + . "\n" + . "Single:\n" + . " Generates up to 32 hashes of random numbers of incrementing length, or up to 32\n" + . " hashes of random numbers of exact [length]. Writes shell commands to stdout that\n" + . " can be processed by the test.sh script.\n" + . "\n" + . "Passthrough:\n" + . " Generates hashes for strings entered via stdin and prints them to stdout.\n" + . "\n" + . "Verify:\n" + . " Reads a list of hashes from and a list of hash:password pairs from\n" + . " . Hashes every password and compares the hash to the corresponding\n" + . " entry in the . If the hashes match and the hash is present in the\n" + . " list from , it will be written to the .\n"; + + exit 1; +} diff --git a/tools/test_modules/README.md b/tools/test_modules/README.md new file mode 100644 index 000000000..3732b42d6 --- /dev/null +++ b/tools/test_modules/README.md @@ -0,0 +1,9 @@ +### 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. + +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. + +**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. From 444d11a74b54efdb3c7ad75ec8860decbafb6662 Mon Sep 17 00:00:00 2001 From: "R. Yushaev" <44146334+Naufragous@users.noreply.github.com> Date: Thu, 20 Dec 2018 19:18:06 +0100 Subject: [PATCH 3/3] Add test modules Add tests for modes 0, 100, 110, 120, 18400, 18600. Update readme. --- tools/test_modules/README.md | 6 ++ tools/test_modules/m0.pm | 39 ++++++++++ tools/test_modules/m100.pm | 39 ++++++++++ tools/test_modules/m110.pm | 41 +++++++++++ tools/test_modules/m120.pm | 41 +++++++++++ tools/test_modules/m18400.pm | 109 ++++++++++++++++++++++++++++ tools/test_modules/m18600.pm | 135 +++++++++++++++++++++++++++++++++++ 7 files changed, 410 insertions(+) create mode 100644 tools/test_modules/m0.pm create mode 100644 tools/test_modules/m100.pm create mode 100644 tools/test_modules/m110.pm create mode 100644 tools/test_modules/m120.pm create mode 100644 tools/test_modules/m18400.pm create mode 100644 tools/test_modules/m18600.pm diff --git a/tools/test_modules/README.md b/tools/test_modules/README.md index 3732b42d6..35a951d77 100644 --- a/tools/test_modules/README.md +++ b/tools/test_modules/README.md @@ -7,3 +7,9 @@ During `single` and `passthrough` tests the `module_generate_hash` function must 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. **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 some sligthly more complex modules with PBKDF2 and encryption, see [m18400.pm](m18400.pm) and [m18600.pm](m18600.pm) diff --git a/tools/test_modules/m0.pm b/tools/test_modules/m0.pm new file mode 100644 index 000000000..da25bf5c1 --- /dev/null +++ b/tools/test_modules/m0.pm @@ -0,0 +1,39 @@ +#!/usr/bin/env perl + +## +## Author......: See docs/credits.txt +## License.....: MIT +## + +use strict; + +use Digest::MD5 qw (md5_hex); + +sub module_generate_hash +{ + my $word = shift; + + my $hash = md5_hex ($word); + + return $hash; +} + +sub module_verify_hash +{ + my $line = shift; + + my ($hash, $word) = split ":", $line; + + return unless defined $hash; + return unless defined $word; + + $word = pack_if_HEX_notation ($word); + + my $new_hash = module_generate_hash ($word); + + return unless $new_hash eq $hash; + + return $new_hash; +} + +1; diff --git a/tools/test_modules/m100.pm b/tools/test_modules/m100.pm new file mode 100644 index 000000000..7f87d334f --- /dev/null +++ b/tools/test_modules/m100.pm @@ -0,0 +1,39 @@ +#!/usr/bin/env perl + +## +## Author......: See docs/credits.txt +## License.....: MIT +## + +use strict; + +use Digest::SHA qw (sha1_hex); + +sub module_generate_hash +{ + my $word = shift; + + my $hash = sha1_hex ($word); + + return $hash; +} + +sub module_verify_hash +{ + my $line = shift; + + my ($hash, $word) = split (":", $line); + + return unless defined $hash; + return unless defined $word; + + $word = pack_if_HEX_notation ($word); + + my $new_hash = module_generate_hash ($word); + + return unless $new_hash eq $hash; + + return $new_hash; +} + +1; diff --git a/tools/test_modules/m110.pm b/tools/test_modules/m110.pm new file mode 100644 index 000000000..a4883124c --- /dev/null +++ b/tools/test_modules/m110.pm @@ -0,0 +1,41 @@ +#!/usr/bin/env perl + +## +## Author......: See docs/credits.txt +## License.....: MIT +## + +use strict; + +use Digest::SHA qw (sha1_hex); + +sub module_generate_hash +{ + my $word = shift; + my $salt = shift // random_numeric_string (int (rand 16)); + + my $hash = sha1_hex ($word . $salt) . ":$salt"; + + return $hash; +} + +sub module_verify_hash +{ + my $line = shift; + + my ($hash, $salt, $word) = split (":", $line); + + return unless defined $hash; + return unless defined $salt; + return unless defined $word; + + $word = pack_if_HEX_notation ($word); + + my $new_hash = module_generate_hash ($word, $salt); + + return unless $new_hash eq "$hash:$salt"; + + return $new_hash; +} + +1; diff --git a/tools/test_modules/m120.pm b/tools/test_modules/m120.pm new file mode 100644 index 000000000..765f959e6 --- /dev/null +++ b/tools/test_modules/m120.pm @@ -0,0 +1,41 @@ +#!/usr/bin/env perl + +## +## Author......: See docs/credits.txt +## License.....: MIT +## + +use strict; + +use Digest::SHA qw (sha1_hex); + +sub module_generate_hash +{ + my $word = shift; + my $salt = shift // random_numeric_string (int (rand 16)); + + my $hash = sha1_hex ($salt . $word) . ":$salt"; + + return $hash; +} + +sub module_verify_hash +{ + my $line = shift; + + my ($hash, $salt, $word) = split (":", $line); + + return unless defined $hash; + return unless defined $salt; + return unless defined $word; + + $word = pack_if_HEX_notation ($word); + + my $new_hash = module_generate_hash ($word, $salt); + + return unless $new_hash eq "$hash:$salt"; + + return $new_hash; +} + +1; diff --git a/tools/test_modules/m18400.pm b/tools/test_modules/m18400.pm new file mode 100644 index 000000000..0c7ed42c8 --- /dev/null +++ b/tools/test_modules/m18400.pm @@ -0,0 +1,109 @@ +#!/usr/bin/env perl + +## +## Author......: See docs/credits.txt +## License.....: MIT +## + +use strict; + +use Crypt::Mode::CBC; +use Crypt::PBKDF2; +use Digest::SHA qw (sha256 sha256_hex); + +sub module_generate_hash +{ + my $word = shift; + my $salt = shift // random_hex_string (2*16); + my $iter = shift // 100000; + 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 $kdf = Crypt::PBKDF2->new + ( + hash_class => 'HMACSHA1', + iterations => $iter, + output_len => 32 + ); + + my $pass_hash = sha256 ($word); + 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 $checksum = sha256_hex ($b_plain); + + my $hash = '$odf$'."*1*1*$iter*32*$checksum*16*$iv*16*$salt*0*$cipher"; + + return $hash; +} + +sub module_verify_hash +{ + my $line = shift; + + my ($hash, $word) = split (":", $line); + + return unless defined $hash; + return unless defined $word; + + $word = pack_if_HEX_notation ($word); + + # tokenize + my @data = split ('\*', $hash); + + next unless scalar @data == 12; + + my $signature = shift @data; + my $cipher_type = shift @data; + my $cs_type = shift @data; + my $iter = shift @data; + my $cs_len = shift @data; + my $cs = shift @data; + my $iv_len = shift @data; + my $iv = shift @data; + my $salt_len = shift @data; + my $salt = shift @data; + my $unused = shift @data; + my $cipher = shift @data; + + # validate + return unless $signature eq '$odf$'; + return unless $cipher_type eq '1'; + return unless $cs_type eq '1'; + return unless $cs_len eq '32'; + return unless $iv_len eq '16'; + return unless $salt_len eq '16'; + return unless $unused eq '0'; + return unless defined $cipher; + + # decrypt + my $b_iv = pack ("H*", $iv); + my $b_salt = pack ("H*", $salt); + my $b_cipher = pack ("H*", $cipher); + + my $kdf = Crypt::PBKDF2->new + ( + hash_class => 'HMACSHA1', + iterations => $iter, + output_len => 32 + ); + + my $pass_hash = sha256 ($word); + 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 $new_hash = module_generate_hash ($word, $salt, $iter, $iv, $plain); + + return unless $new_hash eq $hash; + + return $new_hash; +} + +1; diff --git a/tools/test_modules/m18600.pm b/tools/test_modules/m18600.pm new file mode 100644 index 000000000..e18fb2bd5 --- /dev/null +++ b/tools/test_modules/m18600.pm @@ -0,0 +1,135 @@ +#!/usr/bin/env perl + +## +## Author......: See docs/credits.txt +## License.....: MIT +## + +use strict; + +use Crypt::GCrypt; +use Crypt::PBKDF2; +use Digest::SHA qw (sha1 sha1_hex); + +sub module_generate_hash +{ + my $word = shift; + my $salt = shift // random_hex_string (2*16); + my $iter = shift // 100000; + 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 $kdf = Crypt::PBKDF2->new + ( + hash_class => 'HMACSHA1', + iterations => $iter, + output_len => 16 + ); + + my $pass_hash = sha1 ($word); + my $key = $kdf->PBKDF2 ($b_salt, $pass_hash); + + my $cfb = Crypt::GCrypt->new( + type => 'cipher', + algorithm => 'blowfish', + mode => 'cfb' + ); + + $cfb->start ('encrypting'); + $cfb->setkey ($key); + $cfb->setiv ($b_iv); + + my $b_cipher = $cfb->encrypt ($b_plain); + + $cfb->finish (); + + 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"; + + return $hash; +} + +sub module_verify_hash +{ + my $line = shift; + + my ($hash, $word) = split (":", $line); + + return unless defined $hash; + return unless defined $word; + + $word = pack_if_HEX_notation ($word); + + # tokenize + my @data = split ('\*', $hash); + + next unless scalar @data == 12; + + my $signature = shift @data; + my $cipher_type = shift @data; + my $cs_type = shift @data; + my $iter = shift @data; + my $cs_len = shift @data; + my $cs = shift @data; + my $iv_len = shift @data; + my $iv = shift @data; + my $salt_len = shift @data; + my $salt = shift @data; + my $unused = shift @data; + my $cipher = shift @data; + + # validate + return unless $signature eq '$odf$'; + return unless $cipher_type eq '0'; + return unless $cs_type eq '0'; + return unless $cs_len eq '16'; + return unless $iv_len eq '8'; + return unless $salt_len eq '16'; + return unless $unused eq '0'; + return unless defined $cipher; + + # decrypt + my $b_iv = pack ("H*", $iv); + my $b_salt = pack ("H*", $salt); + my $b_cipher = pack ("H*", $cipher); + + my $kdf = Crypt::PBKDF2->new + ( + hash_class => 'HMACSHA1', + iterations => $iter, + output_len => 16 + ); + + my $pass_hash = sha1 ($word); + my $key = $kdf->PBKDF2 ($b_salt, $pass_hash); + + my $cfb = Crypt::GCrypt->new( + type => 'cipher', + algorithm => 'blowfish', + mode => 'cfb' + ); + + $cfb->start ('decrypting'); + $cfb->setkey ($key); + $cfb->setiv ($b_iv); + + my $b_plain = $cfb->decrypt ($b_cipher); + + $cfb->finish (); + + my $plain = unpack ("H*", $b_plain); + + my $new_hash = module_generate_hash ($word, $salt, $iter, $iv, $plain); + + return unless $new_hash eq $hash; + + return $new_hash; +} + +1;