) that after the $key = pbkdf2_hmac_sha1 ($pass, $salt, $iter) call the WPKY key needs to be "unwrapped" (see AESUnwrap ($key, $wpky) in the code snippet from the stackoverflow post) to verify if the first 8 bytes are 0xa6a6a6a6a6a6a6a6.
If this is successfull the $pass (and therefore the $key + $wpky combination) are correct.
Therefore this algorithm is not just -m 12000 = PBKDF2-HMAC-SHA1 and is also not yet supported by hashcat.
Here you have a POC (proof of concept) to show that this is exactly how you possibly can attack this:
Code:
#!/usr/bin/env perl
# Author: philsmd
# License: public domain
# Date: January 2017
use strict;
use warnings;
use Crypt::PBKDF2;
use Crypt::Mode::ECB;
#
# Constants
#
my $CHECK_SIG = 12008468691120727718; # 0xa6a6a6a6a6a6a6a6
my $MAX_PLIST_SEARCH_DISTANCE = 256;
#
# Helper functions
#
sub to_64bit_num
{
my $str = shift;
my $res = 0;
for (my $i = 0; $i < 8; $i++)
{
$res += ord (substr ($str, $i, 1)) << (8 * (7 - $i));
}
return $res ;
}
sub from_64bit_num
{
my $num = shift;
my $res = "";
for (my $i = 0; $i < 8; $i++)
{
$res = chr ($num & 0xff) . $res;
$num >>= 8;
}
return $res ;
}
sub aes_unwrap
{
my $key = shift;
my $WPKY = shift;
# init
my @C;
for (my $i = 0; $i < length ($WPKY) / 8; $i++)
{
$C[$i] = unpack ("Q>", substr ($WPKY, $i * 8, 8)); # $C[$i] = to_64bit_num (substr ($WPKY, $i * 8, 8));
}
my $n = scalar (@C) - 1;
my @R = (0) x ($n + 1);
my $A = $C[0];
for (my $i = 1; $i < $n + 1; $i++)
{
$R[$i] = $C[$i];
}
# AES mode ECB
my $m = Crypt::Mode::ECB->new ('AES', 0);
# main unwrap loop
for (my $j = 5; $j >= 0; $j--)
{
for (my $i = $n; $i > 0; $i--)
{
my $todec;
$todec = pack ("Q>", $A ^ ($n * $j + $i)); # $todec = from_64bit_num ($A ^ ($n * $j + $i));
$todec .= pack ("Q>", $R[$i]);
my $B = $m->decrypt ($todec, $key);
$A = unpack ("Q>", substr ($B, 0, 8));
$R[$i] = unpack ("Q>", substr ($B, 8, 8));
}
}
return $A;
}
sub parse_manifest_file
{
my $fp = shift;
my $wpky = undef;
my $salt = undef;
my $iter = undef;
my $manifest_buffer = do
{
local $/ = undef;
<$fp>;
};
my $manifest_buffer_len = length ($manifest_buffer);
return (undef, undef, undef) if (length ($manifest_buffer) < 4 + 4 + 4 + 4 + 4 + 4);
my @salt_matches = ($manifest_buffer =~ /SALT..../g);
# okay, I admit this is some strange parsing, but it seems to work all the times (for me)
# for instance, it assumes that the order is always like this: 1. salt, 2. iter, 3. wpky
my $idx_glob = 0;
for (my $i = 0; $i < scalar (@salt_matches); $i++)
{
my $idx_salt = index ($manifest_buffer, "SALT", $idx_glob + 0);
last if ($idx_salt == -1);
my $idx_iter = index ($manifest_buffer, "ITER", $idx_salt + 1);
last if ($idx_iter == -1);
my $idx_wpky = index ($manifest_buffer, "WPKY", $idx_iter + 1);
last if ($idx_wpky == -1);
# special case:
last if ($manifest_buffer_len - $idx_wpky < 8); # too close to the EOF
if ($idx_wpky - $idx_salt < $MAX_PLIST_SEARCH_DISTANCE) # some sane distance between the items
{
my $salt_len = substr ($manifest_buffer, $idx_salt + 4, 4);
my $iter_len = substr ($manifest_buffer, $idx_iter + 4, 4);
my $wpky_len = substr ($manifest_buffer, $idx_wpky + 4, 4);
$idx_salt += 8;
$idx_iter += 8;
$idx_wpky += 8;
$salt = substr ($manifest_buffer, $idx_salt, unpack ("L>", $salt_len));
$iter = substr ($manifest_buffer, $idx_iter, unpack ("L>", $iter_len));
$wpky = substr ($manifest_buffer, $idx_wpky, unpack ("L>", $wpky_len));
# iter is a special case, needs to be converted to a number
$iter = unpack ("L>", $iter);
last;
}
$idx_glob = $idx_wpky + 1;
}
return ($wpky, $salt, $iter);
}
#
# MAIN
#
# init:
if (scalar (@ARGV) != 1)
{
print "Usage: $0 <Manifest.plist file>\n";
exit (1);
}
my $manifest_file = $ARGV[0];
my $FH_MANIFEST;
if (! open ($FH_MANIFEST, "<$manifest_file"))
{
print "ERROR: Could not open '$manifest_file'\n";
exit (1);
}
binmode ($FH_MANIFEST);
my ($WPKY, $SALT, $ITER) = parse_manifest_file ($FH_MANIFEST);
close ($FH_MANIFEST);
if (! defined ($WPKY))
{
print "ERROR: WPKY could not be found in '$manifest_file'\n";
exit (1);
}
if (! defined ($SALT))
{
print "ERROR: SALT could not be found in '$manifest_file'\n";
exit (1);
}
if (! defined ($ITER))
{
print "ERROR: ITER could not be found in '$manifest_file'\n";
exit (1);
}
# crypto init
my $pbkdf2 = Crypt::PBKDF2->new
(
hasher => Crypt::PBKDF2->hasher_from_algorithm ('HMACSHA1'),
iterations => $ITER,
output_len => 32
);
#
# Actual START
#
while (my $pass = <STDIN>)
{
chomp ($pass);
my $key = $pbkdf2->PBKDF2 ($SALT, $pass);
if (aes_unwrap ($key, $WPKY) == $CHECK_SIG)
{
print "Password found: $pass\n";
exit (0);
}
}
exit (1);
Note: I put this code together pretty quickly so please don't blame me for it. Feel free to use it (pubic domain license).
This is an example on how you use it (dictionary/password candidates come from stdin, Manifest file as first command line argument):