Skip to content

Instantly share code, notes, and snippets.

@philsmd
Created January 20, 2017 14:07
Show Gist options
  • Save philsmd/6288a3c68e245034d459ba2be2d58328 to your computer and use it in GitHub Desktop.
Save philsmd/6288a3c68e245034d459ba2be2d58328 to your computer and use it in GitHub Desktop.
#!/usr/bin/env perl
# Author: philsmd (for hashcat)
# 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;
}
# optional also search for DPIC and DPSL (iOS 10.2+ ?)
my $dpic = undef; # iteration count
my $dpsl = undef; # salt
my @dpsl_matches = ($manifest_buffer =~ /DPSL..../g);
$idx_glob = 0;
for (my $i = 0; $i < scalar (@dpsl_matches); $i++)
{
my $idx_dpic = index ($manifest_buffer, "DPIC", $idx_glob + 0);
last if ($idx_dpic == -1);
my $idx_dpsl = index ($manifest_buffer, "DPSL", $idx_dpic + 1);
last if ($idx_dpsl == -1);
if ($idx_dpsl - $idx_dpic < $MAX_PLIST_SEARCH_DISTANCE)
{
my $dpic_len = substr ($manifest_buffer, $idx_dpic + 4, 4);
my $dpsl_len = substr ($manifest_buffer, $idx_dpsl + 4, 4);
$idx_dpic += 8;
$idx_dpsl += 8;
$dpic = substr ($manifest_buffer, $idx_dpic, unpack ("L>", $dpic_len));
$dpsl = substr ($manifest_buffer, $idx_dpsl, unpack ("L>", $dpsl_len));
$dpic = unpack ("L>", $dpic);
last;
}
$idx_glob = $idx_dpsl + 1;
}
return ($wpky, $salt, $iter, $dpic, $dpsl);
}
#
# 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, $DPIC, $DPSL) = 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);
}
# either none or both should be set!
$DPSL = undef if (! defined ($DPIC));
$DPIC = undef if (! defined ($DPSL));
# crypto init
#
# Actual START
#
my $pbkdf2 = Crypt::PBKDF2->new
(
hasher => Crypt::PBKDF2->hasher_from_algorithm ('HMACSHA1'),
iterations => $ITER,
output_len => 32
);
my $pbkdf2x;
if (defined ($DPSL))
{
$pbkdf2x = Crypt::PBKDF2->new
(
hasher => Crypt::PBKDF2->hasher_from_algorithm ('HMACSHA2'),
iterations => $DPIC,
output_len => 32
);
}
while (my $pass = <STDIN>)
{
chomp ($pass);
my $key;
if (defined ($DPSL))
{
my $key_dpsl = $pbkdf2x->PBKDF2 ($DPSL, $pass);
$key = $pbkdf2->PBKDF2 ($SALT, $key_dpsl);
}
else
{
$key = $pbkdf2->PBKDF2 ($SALT, $pass);
}
if (aes_unwrap ($key, $WPKY) == $CHECK_SIG)
{
print "Password found: $pass\n";
exit (0);
}
}
exit (1);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment