|
3 | 3 | from rfc8452 import AES_GCM_SIV
|
4 | 4 | from Crypto.Hash import SHA512
|
5 | 5 | import binascii
|
| 6 | +import bcrypt |
| 7 | +from cryptography.hazmat.primitives.keywrap import aes_key_unwrap_with_padding |
| 8 | +from cryptography.hazmat.primitives import hashes |
| 9 | +from cryptography.hazmat.primitives.kdf.hkdf import HKDF |
6 | 10 |
|
7 | 11 | import logging
|
8 | 12 |
|
@@ -744,3 +748,111 @@ def decode_fscb(img, keys, FSCB_LEN_PAGES=2, dna=DNA):
|
744 | 748 | spaceupdate_count += 1
|
745 | 749 | logging.info("Total SpaceUpdate entries found: {}".format(spaceupdate_count))
|
746 | 750 | return fscb
|
| 751 | + |
| 752 | + |
| 753 | +# # # code to extract system basis given a keybox |
| 754 | +def get_key(index, keyrom, length): |
| 755 | + ret = [] |
| 756 | + for offset in range(length // 4): |
| 757 | + word = int.from_bytes(keyrom[(index + offset) * 4: (index + offset) * 4 + 4], 'big') |
| 758 | + ret += list(word.to_bytes(4, 'little')) |
| 759 | + return ret |
| 760 | + |
| 761 | +# Arguments: |
| 762 | +# - keyrom is the binary image of the key box, in plaintext |
| 763 | +# - pddb is the PDDB binary image, starting from the beginning of the PDDB itself (no backup headers, etc.) |
| 764 | +# - boot_pw is the boot pin |
| 765 | +# - basis_credentials is a list of the secret Bases |
| 766 | +# Returns: |
| 767 | +# - A dictionary of keys, by Basis name |
| 768 | +def extract_keys(keyrom, pddb, boot_pw, basis_credentials=None): |
| 769 | + user_key_enc = get_key(40, keyrom, 32) |
| 770 | + pepper = get_key(248, keyrom, 16) |
| 771 | + pepper[0] = pepper[0] ^ 1 # encodes the "boot" password type into the pepper |
| 772 | + |
| 773 | + # acquire and massage the password so that we can decrypt the encrypted user key |
| 774 | + boot_pw_array = [0] * 73 |
| 775 | + pw_len = 0 |
| 776 | + for b in bytes(boot_pw.encode('utf-8')): |
| 777 | + boot_pw_array[pw_len] = b |
| 778 | + pw_len += 1 |
| 779 | + pw_len += 1 # null terminate, so even the null password is one character long |
| 780 | + bcrypter = bcrypt.BCrypt() |
| 781 | + # logging.debug("{}".format(boot_pw_array[:pw_len])) |
| 782 | + logging.debug("user_key_enc: {}".format(list(user_key_enc))) |
| 783 | + logging.debug("private_key_enc: {}".format(list(get_key(8, keyrom, 32)))) |
| 784 | + logging.debug("salt: {}".format(list(pepper))) |
| 785 | + hashed_pw = bcrypter.crypt_raw(boot_pw_array[:pw_len], pepper, 7) |
| 786 | + logging.debug("hashed_pw: {}".format(list(hashed_pw))) |
| 787 | + hasher = SHA512.new(truncate="256") |
| 788 | + hasher.update(hashed_pw) |
| 789 | + user_pw = hasher.digest() |
| 790 | + |
| 791 | + user_key = [] |
| 792 | + for (a, b) in zip(user_key_enc, user_pw): |
| 793 | + user_key += [a ^ b] |
| 794 | + logging.debug("user_key: {}".format(user_key)) |
| 795 | + |
| 796 | + rollback_limit = 255 - int.from_bytes(keyrom[254 * 4 : 254 * 4 + 4], 'little') |
| 797 | + logging.info("rollback limit: {}".format(rollback_limit)) |
| 798 | + for i in range(rollback_limit): |
| 799 | + hasher = SHA512.new(truncate="256") |
| 800 | + hasher.update(bytes(user_key)) |
| 801 | + user_key = hasher.digest() |
| 802 | + |
| 803 | + logging.debug("hashed_key: {}".format(list(user_key))) |
| 804 | + |
| 805 | + pddb_len = len(pddb) |
| 806 | + pddb_size_pages = pddb_len // PAGE_SIZE |
| 807 | + logging.info("Database size: 0x{:x}".format(pddb_len)) |
| 808 | + |
| 809 | + pt_len = pddb_size_pages * 16 |
| 810 | + static_crypto_data = pddb[pt_len:pt_len + 0x1000] |
| 811 | + scd_ver = int.from_bytes(static_crypto_data[:4], 'little') |
| 812 | + if scd_ver != 2: |
| 813 | + logging.error("Static crypto data has wrong version {}", 2) |
| 814 | + exit(1) |
| 815 | + |
| 816 | + wrapped_key_pt = static_crypto_data[4:4+40] |
| 817 | + wrapped_key_data = static_crypto_data[4+40:4+40+40] |
| 818 | + pddb_salt = static_crypto_data[4+40+40:] |
| 819 | + |
| 820 | + # extract the .System key |
| 821 | + key_pt = aes_key_unwrap_with_padding(bytes(user_key), bytes(wrapped_key_pt)) |
| 822 | + key_data = aes_key_unwrap_with_padding(bytes(user_key), bytes(wrapped_key_data)) |
| 823 | + |
| 824 | + logging.debug("key_pt {}".format(key_pt)) |
| 825 | + logging.debug("key_data {}".format(key_data)) |
| 826 | + keys = {} |
| 827 | + keys[SYSTEM_BASIS] = [key_pt, key_data] |
| 828 | + |
| 829 | + # extract the secret basis keys |
| 830 | + for name, pw in basis_credentials.items(): |
| 831 | + bname_copy = [0]*64 |
| 832 | + plaintext_pw = [0]*73 |
| 833 | + i = 0 |
| 834 | + for c in list(name.encode('utf-8')): |
| 835 | + bname_copy[i] = c |
| 836 | + i += 1 |
| 837 | + pw_len = 0 |
| 838 | + for c in list(pw.encode('utf-8')): |
| 839 | + plaintext_pw[pw_len] = c |
| 840 | + pw_len += 1 |
| 841 | + pw_len += 1 # null terminate |
| 842 | + # print(bname_copy) |
| 843 | + # print(plaintext_pw) |
| 844 | + hasher = SHA512.new(truncate="256") |
| 845 | + hasher.update(pddb_salt[32:]) |
| 846 | + hasher.update(bytes(bname_copy)) |
| 847 | + hasher.update(bytes(plaintext_pw)) |
| 848 | + derived_salt = hasher.digest() |
| 849 | + |
| 850 | + bcrypter = bcrypt.BCrypt() |
| 851 | + hashed_pw = bcrypter.crypt_raw(plaintext_pw[:pw_len], derived_salt[:16], 7) |
| 852 | + hkdf = HKDF(algorithm=hashes.SHA256(), length=32, salt=pddb_salt[:32], info=b"pddb page table key") |
| 853 | + pt_key = hkdf.derive(hashed_pw) |
| 854 | + hkdf = HKDF(algorithm=hashes.SHA256(), length=32, salt=pddb_salt[:32], info=b"pddb data key") |
| 855 | + data_key = hkdf.derive(hashed_pw) |
| 856 | + keys[name] = [pt_key, data_key] |
| 857 | + |
| 858 | + return keys |
0 commit comments