2019-01-19 19:09:32 +00:00
|
|
|
#!/usr/bin/env perl
|
|
|
|
|
|
|
|
##
|
|
|
|
## Author......: See docs/credits.txt
|
|
|
|
## License.....: MIT
|
|
|
|
##
|
|
|
|
|
|
|
|
use strict;
|
|
|
|
use warnings;
|
|
|
|
|
|
|
|
use Crypt::PBKDF2;
|
|
|
|
use Digest::CMAC;
|
|
|
|
use Digest::MD5 qw (md5);
|
|
|
|
use Digest::SHA qw (sha1 sha256);
|
|
|
|
use Digest::HMAC qw (hmac);
|
|
|
|
use MIME::Base64 qw (encode_base64);
|
|
|
|
|
2019-01-20 09:41:16 +00:00
|
|
|
sub module_constraints { [[8, 63], [0, 32], [8, 63], [0, 32], [-1, -1]] }
|
2019-01-19 19:09:32 +00:00
|
|
|
|
|
|
|
sub module_generate_hash
|
|
|
|
{
|
|
|
|
my $word = shift;
|
|
|
|
my $salt = shift;
|
|
|
|
|
|
|
|
my $bssid = random_bytes (6);
|
|
|
|
my $stmac = random_bytes (6);
|
|
|
|
my $snonce = random_bytes (32);
|
|
|
|
my $anonce = random_bytes (32);
|
|
|
|
|
|
|
|
my $keyver = random_number (1, 4); # 1, 2 or 3
|
|
|
|
|
|
|
|
# eapol:
|
|
|
|
# should be "validly" generated, but in theory could be anything for us also:
|
|
|
|
# $eapol = "\x00" x 121; # works too, but let's generate it correctly
|
|
|
|
|
|
|
|
my $eapol = gen_random_wpa_eapol ($keyver, $snonce);
|
|
|
|
|
|
|
|
my $eapol_len = length ($eapol);
|
|
|
|
|
|
|
|
# constants
|
|
|
|
|
|
|
|
my $iterations = 4096;
|
|
|
|
|
|
|
|
#
|
|
|
|
# START
|
|
|
|
#
|
|
|
|
|
|
|
|
# generate the Pairwise Master Key (PMK)
|
|
|
|
|
|
|
|
my $pbkdf2 = Crypt::PBKDF2->new
|
|
|
|
(
|
|
|
|
hash_class => 'HMACSHA1',
|
|
|
|
iterations => $iterations,
|
|
|
|
output_len => 32,
|
|
|
|
);
|
|
|
|
|
|
|
|
my $pmk = $pbkdf2->PBKDF2 ($salt, $word);
|
|
|
|
|
|
|
|
# Pairwise Transient Key (PTK) transformation
|
|
|
|
|
|
|
|
my $ptk = wpa_prf_512 ($keyver, $pmk, $stmac, $bssid, $snonce, $anonce);
|
|
|
|
|
|
|
|
# generate the Message Integrity Code (MIC)
|
|
|
|
|
|
|
|
my $mic = "";
|
|
|
|
|
|
|
|
if ($keyver == 1) # WPA1 => MD5
|
|
|
|
{
|
|
|
|
$mic = hmac ($eapol, $ptk, \&md5);
|
|
|
|
}
|
|
|
|
elsif ($keyver == 2) # WPA2 => SHA1
|
|
|
|
{
|
|
|
|
$mic = hmac ($eapol, $ptk, \&sha1);
|
|
|
|
}
|
|
|
|
elsif ($keyver == 3) # WPA2 => SHA256 + AES-CMAC
|
|
|
|
{
|
|
|
|
my $omac1 = Digest::CMAC->new ($ptk, 'Crypt::Rijndael');
|
|
|
|
|
|
|
|
$omac1->add ($eapol);
|
|
|
|
|
|
|
|
$mic = $omac1->digest;
|
|
|
|
}
|
|
|
|
|
|
|
|
$mic = substr ($mic, 0, 16);
|
|
|
|
|
|
|
|
#
|
|
|
|
# format the binary output
|
|
|
|
#
|
|
|
|
|
|
|
|
my $HCCAPX_VERSION = 4;
|
|
|
|
|
|
|
|
# signature
|
|
|
|
my $hash_buf = "HCPX";
|
|
|
|
|
|
|
|
# format version
|
|
|
|
$hash_buf .= pack ("L<", $HCCAPX_VERSION);
|
|
|
|
|
|
|
|
# authenticated
|
|
|
|
$hash_buf .= pack ("C", 0);
|
|
|
|
|
|
|
|
# essid length
|
|
|
|
my $essid_len = length ($salt);
|
|
|
|
$hash_buf .= pack ("C", $essid_len);
|
|
|
|
|
|
|
|
# essid (NULL-padded up to the first 32 bytes)
|
|
|
|
$hash_buf .= $salt;
|
|
|
|
$hash_buf .= "\x00" x (32 - $essid_len);
|
|
|
|
|
|
|
|
# key version
|
|
|
|
$hash_buf .= pack ("C", $keyver);
|
|
|
|
|
|
|
|
# key mic
|
|
|
|
$hash_buf .= $mic;
|
|
|
|
|
|
|
|
# access point MAC
|
|
|
|
$hash_buf .= $bssid;
|
|
|
|
|
|
|
|
# access point nonce
|
|
|
|
$hash_buf .= $snonce;
|
|
|
|
|
|
|
|
# client MAC
|
|
|
|
$hash_buf .= $stmac;
|
|
|
|
|
|
|
|
# client nonce
|
|
|
|
$hash_buf .= $anonce;
|
|
|
|
|
|
|
|
# eapol length
|
|
|
|
$hash_buf .= pack ("S<", $eapol_len);
|
|
|
|
|
|
|
|
# eapol
|
|
|
|
$hash_buf .= $eapol;
|
|
|
|
$hash_buf .= "\x00" x (256 - $eapol_len);
|
|
|
|
|
|
|
|
# base64 encode the output
|
|
|
|
my $hash = encode_base64 ($hash_buf, "");
|
|
|
|
|
|
|
|
return $hash;
|
|
|
|
}
|
|
|
|
|
|
|
|
sub module_verify_hash
|
|
|
|
{
|
|
|
|
print "ERROR: verify currently not supported for WPA-EAPOL-PBKDF2 (because of hashcat's output format)\n";
|
|
|
|
|
|
|
|
exit (1);
|
|
|
|
}
|
|
|
|
|
|
|
|
sub gen_random_wpa_eapol
|
|
|
|
{
|
|
|
|
my $keyver = shift;
|
|
|
|
my $snonce = shift;
|
|
|
|
|
|
|
|
my $ret = "";
|
|
|
|
|
|
|
|
# version
|
|
|
|
|
|
|
|
my $version = 1; # 802.1X-2001
|
|
|
|
|
|
|
|
$ret .= pack ("C*", $version);
|
|
|
|
|
|
|
|
my $type = 3; # means that this EAPOL frame is used to transfer key information
|
|
|
|
|
|
|
|
$ret .= pack ("C*", $type);
|
|
|
|
|
|
|
|
my $length; # length of remaining data
|
|
|
|
|
|
|
|
if ($keyver == 1)
|
|
|
|
{
|
|
|
|
$length = 119;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
$length = 117;
|
|
|
|
}
|
|
|
|
|
|
|
|
$ret .= pack ("n*", $length);
|
|
|
|
|
|
|
|
my $descriptor_type;
|
|
|
|
|
|
|
|
if ($keyver == 1)
|
|
|
|
{
|
|
|
|
$descriptor_type = 254; # EAPOL WPA key
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
$descriptor_type = 1; # EAPOL RSN key
|
|
|
|
}
|
|
|
|
|
|
|
|
$ret .= pack ("C*", $descriptor_type);
|
|
|
|
|
|
|
|
# key_info is a bit vector:
|
|
|
|
# generated from these 13 bits: encrypted key data, request, error, secure, key mic, key ack, install, key index (2), key type, key descriptor (3)
|
|
|
|
|
|
|
|
my $key_info = 0;
|
|
|
|
|
|
|
|
$key_info |= 1 << 8; # set key MIC
|
|
|
|
$key_info |= 1 << 3; # set if it is a pairwise key
|
|
|
|
|
|
|
|
if ($keyver == 1)
|
|
|
|
{
|
|
|
|
$key_info |= 1 << 0; # RC4 Cipher, HMAC-MD5 MIC
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
$key_info |= 1 << 1; # AES Cipher, HMAC-SHA1 MIC
|
|
|
|
}
|
|
|
|
|
|
|
|
$ret .= pack ("n*", $key_info);
|
|
|
|
|
|
|
|
my $key_length;
|
|
|
|
|
|
|
|
if ($keyver == 1)
|
|
|
|
{
|
|
|
|
$key_length = 32;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
$key_length = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
$ret .= pack ("n*", $key_length);
|
|
|
|
|
|
|
|
my $replay_counter = 1;
|
|
|
|
|
|
|
|
$ret .= pack ("Q>*", $replay_counter);
|
|
|
|
|
|
|
|
$ret .= $snonce;
|
|
|
|
|
|
|
|
my $key_iv = "\x00" x 16;
|
|
|
|
|
|
|
|
$ret .= $key_iv;
|
|
|
|
|
|
|
|
my $key_rsc = "\x00" x 8;
|
|
|
|
|
|
|
|
$ret .= $key_rsc;
|
|
|
|
|
|
|
|
my $key_id = "\x00" x 8;
|
|
|
|
|
|
|
|
$ret .= $key_id;
|
|
|
|
|
|
|
|
my $key_mic = "\x00" x 16;
|
|
|
|
|
|
|
|
$ret .= $key_mic;
|
|
|
|
|
|
|
|
my $key_data_len;
|
|
|
|
|
|
|
|
if ($keyver == 1)
|
|
|
|
{
|
|
|
|
$key_data_len = 24; # length of the key_data (== WPA info)
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
$key_data_len = 22; # length of the key_data (== RSN info)
|
|
|
|
}
|
|
|
|
|
|
|
|
$ret .= pack ("n*", $key_data_len);
|
|
|
|
|
|
|
|
my $key_data = "";
|
|
|
|
|
|
|
|
if ($keyver == 1)
|
|
|
|
{
|
|
|
|
# wpa info
|
|
|
|
|
|
|
|
my $wpa_info = "";
|
|
|
|
|
|
|
|
my $vendor_specific_data = "";
|
|
|
|
|
|
|
|
my $tag_number = 221; # means it is a vendor specific tag
|
|
|
|
|
|
|
|
$vendor_specific_data .= pack ("C*", $tag_number);
|
|
|
|
|
|
|
|
my $tag_len = 22; # length of the remaining "tag data"
|
|
|
|
|
|
|
|
$vendor_specific_data .= pack ("C*", $tag_len);
|
|
|
|
|
|
|
|
my $vendor_specific_oui = pack ("H*", "0050f2"); # microsoft
|
|
|
|
|
|
|
|
$vendor_specific_data .= $vendor_specific_oui;
|
|
|
|
|
|
|
|
my $vendor_specific_oui_type = 1; # WPA Information Element
|
|
|
|
|
|
|
|
$vendor_specific_data .= pack ("C*", $vendor_specific_oui_type);
|
|
|
|
|
|
|
|
my $vendor_specific_wpa_version = 1;
|
|
|
|
|
|
|
|
$vendor_specific_data .= pack ("v*", $vendor_specific_wpa_version);
|
|
|
|
|
|
|
|
# multicast
|
|
|
|
|
|
|
|
my $vendor_specific_multicast_oui = pack ("H*", "0050f2");
|
|
|
|
|
|
|
|
$vendor_specific_data .= $vendor_specific_multicast_oui;
|
|
|
|
|
|
|
|
my $vendor_specific_multicast_type = 2; # TKIP
|
|
|
|
|
|
|
|
$vendor_specific_data .= pack ("C*", $vendor_specific_multicast_type);
|
|
|
|
|
|
|
|
# unicast
|
|
|
|
|
|
|
|
my $vendor_specific_unicast_count = 1;
|
|
|
|
|
|
|
|
$vendor_specific_data .= pack ("v*", $vendor_specific_unicast_count);
|
|
|
|
|
|
|
|
my $vendor_specific_unicast_oui = pack ("H*", "0050f2");
|
|
|
|
|
|
|
|
$vendor_specific_data .= $vendor_specific_unicast_oui;
|
|
|
|
|
|
|
|
my $vendor_specific_unicast_type = 2; # TKIP
|
|
|
|
|
|
|
|
$vendor_specific_data .= pack ("C*", $vendor_specific_unicast_type);
|
|
|
|
|
|
|
|
# Auth Key Management (AKM)
|
|
|
|
|
|
|
|
my $auth_key_management_count = 1;
|
|
|
|
|
|
|
|
$vendor_specific_data .= pack ("v*", $auth_key_management_count);
|
|
|
|
|
|
|
|
my $auth_key_management_oui = pack ("H*", "0050f2");
|
|
|
|
|
|
|
|
$vendor_specific_data .= $auth_key_management_oui;
|
|
|
|
|
|
|
|
my $auth_key_management_type = 2; # Pre-Shared Key (PSK)
|
|
|
|
|
|
|
|
$vendor_specific_data .= pack ("C*", $auth_key_management_type);
|
|
|
|
|
|
|
|
$wpa_info = $vendor_specific_data;
|
|
|
|
|
|
|
|
$key_data = $wpa_info;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
# rsn info
|
|
|
|
|
|
|
|
my $rsn_info = "";
|
|
|
|
|
|
|
|
my $tag_number = 48; # RSN info
|
|
|
|
|
|
|
|
$rsn_info .= pack ("C*", $tag_number);
|
|
|
|
|
|
|
|
my $tag_len = 20; # length of the remaining "tag_data"
|
|
|
|
|
|
|
|
$rsn_info .= pack ("C*", $tag_len);
|
|
|
|
|
|
|
|
my $rsn_version = 1;
|
|
|
|
|
|
|
|
$rsn_info .= pack ("v*", $rsn_version);
|
|
|
|
|
|
|
|
# group cipher suite
|
|
|
|
|
|
|
|
my $group_cipher_suite_oui = pack ("H*", "000fac"); # Ieee8021
|
|
|
|
|
|
|
|
$rsn_info .= $group_cipher_suite_oui;
|
|
|
|
|
|
|
|
my $group_cipher_suite_type = 4; # AES (CCM)
|
|
|
|
|
|
|
|
$rsn_info .= pack ("C*", $group_cipher_suite_type);
|
|
|
|
|
|
|
|
# pairwise cipher suite
|
|
|
|
|
|
|
|
my $pairwise_cipher_suite_count = 1;
|
|
|
|
|
|
|
|
$rsn_info .= pack ("v*", $pairwise_cipher_suite_count);
|
|
|
|
|
|
|
|
my $pairwise_cipher_suite_oui = pack ("H*", "000fac"); # Ieee8021
|
|
|
|
|
|
|
|
$rsn_info .= $pairwise_cipher_suite_oui;
|
|
|
|
|
|
|
|
my $pairwise_cipher_suite_type = 4; # AES (CCM)
|
|
|
|
|
|
|
|
$rsn_info .= pack ("C*", $pairwise_cipher_suite_type);
|
|
|
|
|
|
|
|
# Auth Key Management (AKM)
|
|
|
|
|
|
|
|
my $auth_key_management_count = 1;
|
|
|
|
|
|
|
|
$rsn_info .= pack ("v*", $auth_key_management_count);
|
|
|
|
|
|
|
|
my $auth_key_management_oui = pack ("H*", "000fac"); # Ieee8021
|
|
|
|
|
|
|
|
$rsn_info .= $auth_key_management_oui;
|
|
|
|
|
|
|
|
my $auth_key_management_type = 2; # Pre-Shared Key (PSK)
|
|
|
|
|
|
|
|
$rsn_info .= pack ("C*", $auth_key_management_type);
|
|
|
|
|
|
|
|
# RSN Capabilities
|
|
|
|
|
|
|
|
# bit vector of these 9 bits: peerkey enabled, management frame protection (MFP) capable, MFP required,
|
|
|
|
# RSN GTKSA Capabilities (2), RSN PTKSA Capabilities (2), no pairwise Capabilities, Pre-Auth Capabilities
|
|
|
|
|
|
|
|
my $rsn_capabilities = pack ("H*", "0000");
|
|
|
|
|
|
|
|
$rsn_info .= $rsn_capabilities;
|
|
|
|
|
|
|
|
$key_data = $rsn_info;
|
|
|
|
}
|
|
|
|
|
|
|
|
$ret .= $key_data;
|
|
|
|
|
|
|
|
return $ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
sub wpa_prf_512
|
|
|
|
{
|
|
|
|
my $keyver = shift;
|
|
|
|
my $pmk = shift;
|
|
|
|
my $stmac = shift;
|
|
|
|
my $bssid = shift;
|
|
|
|
my $snonce = shift;
|
|
|
|
my $anonce = shift;
|
|
|
|
|
|
|
|
my $data = "Pairwise key expansion";
|
|
|
|
|
|
|
|
if (($keyver == 1) || ($keyver == 2))
|
|
|
|
{
|
|
|
|
$data .= "\x00";
|
|
|
|
}
|
|
|
|
|
|
|
|
#
|
|
|
|
# Min(AA, SPA) || Max(AA, SPA)
|
|
|
|
#
|
|
|
|
|
|
|
|
# compare if greater: Min()/Max() on the MACs (6 bytes)
|
|
|
|
|
|
|
|
if (memcmp ($stmac, $bssid, 6) < 0)
|
|
|
|
{
|
|
|
|
$data .= $stmac;
|
|
|
|
$data .= $bssid;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
$data .= $bssid;
|
|
|
|
$data .= $stmac;
|
|
|
|
}
|
|
|
|
|
|
|
|
#
|
|
|
|
# Min(ANonce,SNonce) || Max(ANonce,SNonce)
|
|
|
|
#
|
|
|
|
|
|
|
|
# compare if greater: Min()/Max() on the nonces (32 bytes)
|
|
|
|
|
|
|
|
if (memcmp ($snonce, $anonce, 32) < 0)
|
|
|
|
{
|
|
|
|
$data .= $snonce;
|
|
|
|
$data .= $anonce;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
$data .= $anonce;
|
|
|
|
$data .= $snonce;
|
|
|
|
}
|
|
|
|
|
|
|
|
my $prf_buf;
|
|
|
|
|
|
|
|
if (($keyver == 1) || ($keyver == 2))
|
|
|
|
{
|
|
|
|
$data .= "\x00";
|
|
|
|
|
|
|
|
$prf_buf = hmac ($data, $pmk, \&sha1);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
my $data3 = "\x01\x00" . $data . "\x80\x01";
|
|
|
|
|
|
|
|
$prf_buf = hmac ($data3, $pmk, \&sha256);
|
|
|
|
}
|
|
|
|
|
|
|
|
$prf_buf = substr ($prf_buf, 0, 16);
|
|
|
|
|
|
|
|
return $prf_buf;
|
|
|
|
}
|
|
|
|
|
|
|
|
sub memcmp
|
|
|
|
{
|
|
|
|
my $str1 = shift;
|
|
|
|
my $str2 = shift;
|
|
|
|
my $len = shift;
|
|
|
|
|
|
|
|
my $len_str1 = length ($str1);
|
|
|
|
my $len_str2 = length ($str2);
|
|
|
|
|
|
|
|
if (($len > $len_str1) || ($len > $len_str2))
|
|
|
|
{
|
|
|
|
print "ERROR: memcmp () lengths wrong";
|
|
|
|
|
|
|
|
exit (1);
|
|
|
|
}
|
|
|
|
|
|
|
|
for (my $i = 0; $i < $len; $i++)
|
|
|
|
{
|
|
|
|
my $c_1 = ord (substr ($str1, $i, 1));
|
|
|
|
my $c_2 = ord (substr ($str2, $i, 1));
|
|
|
|
|
|
|
|
return -1 if ($c_1 < $c_2);
|
|
|
|
return 1 if ($c_1 > $c_2);
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
1;
|