From c0dd24276d0887ec5c7347af9e4e2652d9fd4d5c Mon Sep 17 00:00:00 2001 From: Nikolay Elenkov Date: Thu, 3 Jul 2014 21:53:17 +0900 Subject: [PATCH 1/6] display L KDF version --- .../android_bruteforce_stdcrypto/bruteforce_stdcrypto.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/tools/android/android_bruteforce_stdcrypto/bruteforce_stdcrypto.py b/tools/android/android_bruteforce_stdcrypto/bruteforce_stdcrypto.py index 8fb0b76..d63c12f 100755 --- a/tools/android/android_bruteforce_stdcrypto/bruteforce_stdcrypto.py +++ b/tools/android/android_bruteforce_stdcrypto/bruteforce_stdcrypto.py @@ -82,7 +82,12 @@ def dump(self): print 'Encrypted Key :', "0x" + self.cryptoKey.encode("hex").upper() print 'Salt :', "0x" + self.cryptoSalt.encode("hex").upper() if self.minorVersion >= SCRYPT_ADDED_MINOR: - print 'KDF :', "PBKDF2" if self.kdf == KDF_PBKDF else "scrypt" + if self.kdf == KDF_PBKDF: + print 'KDF :', "PBKDF2" + elif self.kdf == KDF_SCRYPT: + print 'KDF :', "scrypt" + else: + print 'KDF :', ("unknown (%d)" % self.kdf) print 'N_factor :', "%u (N=%u)" % (self.N_factor, self.N) print 'r_factor :', "%u (r=%u)" % (self.r_factor, self.r) print 'p_factor :', "%u (p=%u)" % (self.p_factor, self.p) @@ -166,7 +171,7 @@ def bruteforcePIN(headerData, cryptoFooter, maxdigits): elif cryptoFooter.kdf == KDF_SCRYPT: decKey = decryptDecodeScryptKey(cryptoFooter, passwdTry) else: - raise "Unknown KDF: " + cf.kdf + raise Exception("Unknown KDF: " + str(cryptoFooter.kdf)) # try to decrypt the frist 32 bytes of the header data (we don't need the iv) decData = decryptData(decKey,"",headerData) From a519a66853f716c5176024b2512904f02ccce93f Mon Sep 17 00:00:00 2001 From: Nikolay Elenkov Date: Thu, 6 Nov 2014 15:26:44 +0900 Subject: [PATCH 2/6] parse Lollipop footer --- .../bruteforce_stdcrypto.py | 106 ++++++++++++++---- 1 file changed, 86 insertions(+), 20 deletions(-) diff --git a/tools/android/android_bruteforce_stdcrypto/bruteforce_stdcrypto.py b/tools/android/android_bruteforce_stdcrypto/bruteforce_stdcrypto.py index d63c12f..1ab7d5a 100755 --- a/tools/android/android_bruteforce_stdcrypto/bruteforce_stdcrypto.py +++ b/tools/android/android_bruteforce_stdcrypto/bruteforce_stdcrypto.py @@ -36,6 +36,43 @@ SCRYPT_ADDED_MINOR = 2 KDF_PBKDF = 1 KDF_SCRYPT = 2 +KDF_SCRYPT_KEYMASTER_UNPADDED = 3 +KDF_SCRYPT_KEYMASTER_BADLY_PADDED = 4 +KDF_SCRYPT_KEYMASTER = 5 +KDF_NAMES = dict() +KDF_NAMES[KDF_PBKDF] = "PBKDF2" +KDF_NAMES[KDF_SCRYPT] = "scrypt" +KDF_NAMES[KDF_SCRYPT_KEYMASTER_UNPADDED] = "scrypt+keymaster (padded)" +KDF_NAMES[KDF_SCRYPT_KEYMASTER_BADLY_PADDED] = "scrypt+keymaster (badly padded)" +KDF_NAMES[KDF_SCRYPT_KEYMASTER] = "scrypt+keymaster" + +CRYPT_TYPES = ('password', 'default', 'pattern', 'PIN') + +class QcomKmKeyBlob: + def parse(self,data): + s = Struct('<'+'L L 512s L 512s L 16s 512s L 32s') + (self.magic_num, self.version_num, + self.modulus, self.modulus_size, + self.pub_exp, self.pub_exp_size, + self.iv, + self.enc_priv_exp, self.enc_priv_exp_size, + self.hmac) = s.unpack_from(data) + + self.modulus = self.modulus[0:self.modulus_size] + self.pub_exp = self.pub_exp[0:self.pub_exp_size] + self.enc_priv_exp = self.enc_priv_exp[0:self.enc_priv_exp_size] + + def dump(self): + print "QCOM key blob" + print '-------------------------' + print "magic num : 0x%0.4X" % self.magic_num + print "version num : %u" % self.version_num + print "modulus : %s... [%d]" % (self.modulus.encode("hex").upper()[0:32], self.modulus_size) + print "pub exp : %s" % self.pub_exp.encode("hex").upper() + print "IV : %s" % self.iv.encode("hex").upper() + print "encr priv exp: %s...[%d]" % (self.enc_priv_exp.encode("hex").upper()[0:32], self.enc_priv_exp_size) + print "HMAC : %s" % self.hmac.encode("hex").upper() + class CryptoFooter: @@ -54,7 +91,7 @@ def unpack(self, data): self.spare2, self.cryptoKey, self.cryptoSalt) = s.unpack_from(data) self.cryptoKey = self.cryptoKey[0:self.keySize] - else: + elif minorVersion == SCRYPT_ADDED_MINOR: s = Struct('<'+'L H H L L L L L L L 64s L 48s 16s 2Q L B B B B') (self.ftrMagic, self.majorVersion, self.minorVersion, self.ftrSize, self.flags, self.keySize, self.spare1, self.fsSize1, self.fsSize2, @@ -67,31 +104,60 @@ def unpack(self, data): self.N = 1 << self.N_factor self.r = 1 << self.r_factor self.p = 1 << self.p_factor + else: + s = Struct('<'+'L H H L L L L Q L 64s L 48s 16s 2Q L B B B B Q 32s 2048s L 32s') + (self.ftrMagic, self.majorVersion, self.minorVersion, self.ftrSize, + self.flags, self.keySize, self.crypt_type, self.fsSize, + self.failedDecrypt, self.cryptoType, self.spare2, self.cryptoKey, + self.cryptoSalt, self.persistDataOffset1, self.persistDataOffset2, + self.persistDataSize, self.kdf, self.N_factor, self.r_factor, + self.p_factor, + self.encrypted_upto, + self.hash_first_block, + self.km_blob, self.km_blob_size, + self.scrypted_intermediate_key) = s.unpack_from(data) + + self.cryptoKey = self.cryptoKey[0:self.keySize] + self.N = 1 << self.N_factor + self.r = 1 << self.r_factor + self.p = 1 << self.p_factor + self.km_blob = self.km_blob[0:self.km_blob_size] def dump(self): print "Android FDE crypto footer" print '-------------------------' - print 'Magic :', "0x%0.8X" % self.ftrMagic - print 'Major Version :', self.majorVersion - print 'Minor Version :', self.minorVersion - print 'Footer Size :', self.ftrSize, "bytes" - print 'Flags :', "0x%0.8X" % self.flags - print 'Key Size :', self.keySize * 8, "bits" - print 'Failed Decrypts:', self.failedDecrypt - print 'Crypto Type :', self.cryptoType.rstrip("\0") - print 'Encrypted Key :', "0x" + self.cryptoKey.encode("hex").upper() - print 'Salt :', "0x" + self.cryptoSalt.encode("hex").upper() + print 'Magic :', "0x%0.8X" % self.ftrMagic + print 'Major Version :', self.majorVersion + print 'Minor Version :', self.minorVersion + print 'Footer Size :', self.ftrSize, "bytes" + print 'Flags :', "0x%0.8X" % self.flags + print 'Key Size :', self.keySize * 8, "bits" + print 'Failed Decrypts :', self.failedDecrypt + print 'Crypto Type :', self.cryptoType.rstrip("\0") + print 'Encrypted Key :', "0x" + self.cryptoKey.encode("hex").upper() + print 'Salt :', "0x" + self.cryptoSalt.encode("hex").upper() if self.minorVersion >= SCRYPT_ADDED_MINOR: - if self.kdf == KDF_PBKDF: - print 'KDF :', "PBKDF2" - elif self.kdf == KDF_SCRYPT: - print 'KDF :', "scrypt" + if self.kdf in KDF_NAMES.keys(): + print 'KDF : %s' % KDF_NAMES[self.kdf] else: - print 'KDF :', ("unknown (%d)" % self.kdf) - print 'N_factor :', "%u (N=%u)" % (self.N_factor, self.N) - print 'r_factor :', "%u (r=%u)" % (self.r_factor, self.r) - print 'p_factor :', "%u (p=%u)" % (self.p_factor, self.p) + print 'KDF :', ("unknown (%d)" % self.kdf) + print 'N_factor :', "%u (N=%u)" % (self.N_factor, self.N) + print 'r_factor :', "%u (r=%u)" % (self.r_factor, self.r) + print 'p_factor :', "%u (p=%u)" % (self.p_factor, self.p) + if self.minorVersion >= KDF_SCRYPT_KEYMASTER_UNPADDED: + print 'crypt type : %s' % CRYPT_TYPES[self.crypt_type] + print 'FS size : %u' % self.fsSize + print 'encrypted upto : %u' % self.encrypted_upto + print 'hash first block : %s' % self.hash_first_block.encode("hex").upper() + print 'keymaster blob : %s...[%d]' % (self.km_blob.encode("hex").upper()[0:32], self.km_blob_size) + print 'scrypted IK : %s' % self.scrypted_intermediate_key.encode("hex").upper() + + print "\n" + qb = QcomKmKeyBlob() + qb.parse(self.km_blob) + qb.dump() print '-------------------------' + def main(args): @@ -171,7 +237,7 @@ def bruteforcePIN(headerData, cryptoFooter, maxdigits): elif cryptoFooter.kdf == KDF_SCRYPT: decKey = decryptDecodeScryptKey(cryptoFooter, passwdTry) else: - raise Exception("Unknown KDF: " + str(cryptoFooter.kdf)) + raise Exception("Unknown or unsupporeted KDF: " + str(cryptoFooter.kdf)) # try to decrypt the frist 32 bytes of the header data (we don't need the iv) decData = decryptData(decKey,"",headerData) From e61c02496e20b57717dab399de88582e22deae47 Mon Sep 17 00:00:00 2001 From: Oliver Kunz Date: Mon, 29 Jun 2015 11:50:57 +0200 Subject: [PATCH 3/6] Implemented ext4 Magic comparison --- .../bruteforce_stdcrypto.py | 33 ++++++++++++++++--- 1 file changed, 28 insertions(+), 5 deletions(-) diff --git a/tools/android/android_bruteforce_stdcrypto/bruteforce_stdcrypto.py b/tools/android/android_bruteforce_stdcrypto/bruteforce_stdcrypto.py index 1ab7d5a..7329de7 100755 --- a/tools/android/android_bruteforce_stdcrypto/bruteforce_stdcrypto.py +++ b/tools/android/android_bruteforce_stdcrypto/bruteforce_stdcrypto.py @@ -17,6 +17,8 @@ # Speed improvements # ------------ # 2014/5/14 Added 4.4 support (scrypt, etc.) [Nikolay Elenkov] +# ------------ +# 2015/6/29 Implemented ext4 Magic comparison [Oliver Kunz] # -- from os import path @@ -48,6 +50,9 @@ CRYPT_TYPES = ('password', 'default', 'pattern', 'PIN') +EXT4_MAGIC = "53ef" +parseMagic = False + class QcomKmKeyBlob: def parse(self,data): s = Struct('<'+'L L 512s L 512s L 16s 512s L 32s') @@ -161,6 +166,7 @@ def dump(self): def main(args): + global parseMagic # default value maxpin_digits = 4 @@ -200,12 +206,23 @@ def main(args): fileSize = path.getsize(footerFile) assert (fileSize >= 16384), "Input file '%s' must be at least 16384 bytes" % footerFile + # check headerFile + fileSize = path.getsize(headerFile) + # for the NULL padding check, we need at least 32 bytes + assert(fileSize > 32), "Header file '%s' must be at least 32 bytes" % headerFile + + if fileSize >= 1088: + parseMagic = True; + # load the header data for testing the password + headerData = open(headerFile, 'rb').read(1088) + else: + parseMagic = False; + # load the header data for testing the password + headerData = open(headerFile, 'rb').read(32) + # retrive the key and salt from the footer file cf = getCryptoData(footerFile) - # load the header data for testing the password - headerData = open(headerFile, 'rb').read(32) - for n in xrange(4, maxpin_digits+1): result = bruteforcePIN(headerData, cf, n) if result: @@ -243,8 +260,14 @@ def bruteforcePIN(headerData, cryptoFooter, maxdigits): decData = decryptData(decKey,"",headerData) # has the test worked? - if decData[16:32] == "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0": - return passwdTry + if parseMagic: + # ext4 superblock MAGIC (0xef53, little endian) present + if decData[1080:1082].encode("hex") == EXT4_MAGIC: + return passwdTry + else: + # headerFile not large enough, only check for NULL padding of ext4 superblock + if decData[16:32] == "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0": + return passwdTry return None From 565c073149f41f86490fcf261ab0791e58567f8c Mon Sep 17 00:00:00 2001 From: mlet Date: Thu, 2 Jul 2015 08:35:17 +0200 Subject: [PATCH 4/6] Update bruteforce_stdcrypto.py Changed parseMagic from global variable to passed by reference --- .../android_bruteforce_stdcrypto/bruteforce_stdcrypto.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/tools/android/android_bruteforce_stdcrypto/bruteforce_stdcrypto.py b/tools/android/android_bruteforce_stdcrypto/bruteforce_stdcrypto.py index 7329de7..8bdefda 100755 --- a/tools/android/android_bruteforce_stdcrypto/bruteforce_stdcrypto.py +++ b/tools/android/android_bruteforce_stdcrypto/bruteforce_stdcrypto.py @@ -51,7 +51,6 @@ CRYPT_TYPES = ('password', 'default', 'pattern', 'PIN') EXT4_MAGIC = "53ef" -parseMagic = False class QcomKmKeyBlob: def parse(self,data): @@ -166,9 +165,9 @@ def dump(self): def main(args): - global parseMagic # default value maxpin_digits = 4 + parseMagic = False if len(args) < 3: print 'Usage: python bruteforce_stdcrypto.py [header file] [footer file] (max PIN digits)' @@ -224,12 +223,12 @@ def main(args): cf = getCryptoData(footerFile) for n in xrange(4, maxpin_digits+1): - result = bruteforcePIN(headerData, cf, n) + result = bruteforcePIN(headerData, cf, n, parseMagic) if result: print 'Found PIN!: ' + result break -def bruteforcePIN(headerData, cryptoFooter, maxdigits): +def bruteforcePIN(headerData, cryptoFooter, maxdigits, parseMagic): print 'Trying to Bruteforce Password... please wait' # try all possible 4 to maxdigits digit PINs, returns value immediately when found From 33695e4795f6542affecaed54bc3e13a04e10f3c Mon Sep 17 00:00:00 2001 From: Oliver Kunz Date: Thu, 9 Jul 2015 17:32:25 +0200 Subject: [PATCH 5/6] changed header file offsets, added more decrypt tests --- .../bruteforce_stdcrypto.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/tools/android/android_bruteforce_stdcrypto/bruteforce_stdcrypto.py b/tools/android/android_bruteforce_stdcrypto/bruteforce_stdcrypto.py index 8bdefda..4edbd83 100755 --- a/tools/android/android_bruteforce_stdcrypto/bruteforce_stdcrypto.py +++ b/tools/android/android_bruteforce_stdcrypto/bruteforce_stdcrypto.py @@ -208,9 +208,9 @@ def main(args): # check headerFile fileSize = path.getsize(headerFile) # for the NULL padding check, we need at least 32 bytes - assert(fileSize > 32), "Header file '%s' must be at least 32 bytes" % headerFile + assert(fileSize > 31), "Header file '%s' must be at least 32 bytes" % headerFile - if fileSize >= 1088: + if fileSize >= 1087: parseMagic = True; # load the header data for testing the password headerData = open(headerFile, 'rb').read(1088) @@ -260,10 +260,20 @@ def bruteforcePIN(headerData, cryptoFooter, maxdigits, parseMagic): # has the test worked? if parseMagic: + # ext4 superblock MAGIC and NULL padding present + if decData[1080:1082].encode("hex") == EXT4_MAGIC and decData[16:32] == "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0": + print "DBG: parseMagic" + return passwdTry # ext4 superblock MAGIC (0xef53, little endian) present if decData[1080:1082].encode("hex") == EXT4_MAGIC: + print "DBG: parseMagic" + return passwdTry + # NULL padding, after first block, present + elif decData[16:32] == "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0": + print "DBG: parseMagic" return passwdTry else: + print "DBG: no parseMagic" # headerFile not large enough, only check for NULL padding of ext4 superblock if decData[16:32] == "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0": return passwdTry From 77db1b4fe0332d0dc8a3abb65e54b7eae3dd4eea Mon Sep 17 00:00:00 2001 From: Oliver Kunz Date: Thu, 9 Jul 2015 17:36:10 +0200 Subject: [PATCH 6/6] updated scrpt header --- .../android_bruteforce_stdcrypto/bruteforce_stdcrypto.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/tools/android/android_bruteforce_stdcrypto/bruteforce_stdcrypto.py b/tools/android/android_bruteforce_stdcrypto/bruteforce_stdcrypto.py index 4edbd83..9ed9642 100755 --- a/tools/android/android_bruteforce_stdcrypto/bruteforce_stdcrypto.py +++ b/tools/android/android_bruteforce_stdcrypto/bruteforce_stdcrypto.py @@ -11,14 +11,17 @@ # Footer is located in file userdata_footer on the efs partition # # -- -# Revision 0.3 (shipped with Santoku Alpha 0.3) +# Revision 0.5 (Revision 0.3 shipped with Santoku Alpha 0.3) # ------------ # Added support for more than 4-digit PINs # Speed improvements # ------------ -# 2014/5/14 Added 4.4 support (scrypt, etc.) [Nikolay Elenkov] +# 2014/05/14 Added 4.4 support (scrypt, etc.) [Nikolay Elenkov] # ------------ -# 2015/6/29 Implemented ext4 Magic comparison [Oliver Kunz] +# 2015/06/29 Implemented ext4 Magic comparison [Oliver Kunz] +# ------------ +# 2015/07/09 Changed length check for header file [Oliver Kunz] +# Added more tests for decryption # -- from os import path