Created
January 20, 2017 14:07
-
-
Save philsmd/6288a3c68e245034d459ba2be2d58328 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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