Comment décrypter une sauvegarde iPhone iTunes Apple iTunes cryptée?

Les chercheurs en sécurité Jean-Baptiste Bédrune et Jean Sigwald présenté comment
pour faire ça
à Hack-in-the-Box Amsterdam 2011.

Depuis lors, Apple a publié un livre blanc sur la sécurité iOS
avec plus de détails sur les clés et les algorithmes, et Charlie Miller et al. avoir
publié le manuel iOS Hacker’s Handbook, qui couvre certaines des mêmes
sol dans une mode-comment. Quand iOS 10 est sorti pour la première fois, des changements se sont produits
au format de sauvegarde qu'Apple n'a pas annoncé au début, mais divers
les gens ont inversé les modifications de format.

Les sauvegardes cryptées sont excellentes

L’atout majeur des sauvegardes iPhone cryptées est qu’elles contiennent des éléments
comme les mots de passe WiFi qui ne figurent pas dans les sauvegardes régulières non chiffrées. Comme
discuté dans le livre blanc sur la sécurité iOS, les sauvegardes cryptées
sont considérés comme plus "sûrs", donc Apple considère qu'il est correct d'inclure plus
informations sensibles en eux.

Un avertissement important: évidemment, déchiffrer la sauvegarde de votre appareil iOS
supprime son cryptage. Pour protéger votre vie privée et votre sécurité, vous devriez
N'exécutez ces scripts que sur une machine dotée d'un chiffrement intégral du disque.
Pendant que
est possible pour un expert en sécurité d'écrire un logiciel qui protège les clés
mémoire, par exemple en utilisant des fonctions comme VirtualLock () et
SecureZeroMemory () parmi beaucoup d'autres choses, ces
Les scripts Python stockent vos clés de cryptage et vos mots de passe dans des chaînes pour
être ramassé par Python. Cela signifie vos clés secrètes et mots de passe
vivra dans la RAM pendant un certain temps, d'où ils fuiront dans votre échange
fichier et sur votre disque, où un adversaire peut les récupérer. Ce
défait complètement le point d'avoir une sauvegarde cryptée.

Comment décrypter les sauvegardes: en théorie

Le livre blanc sur la sécurité iOS explique les concepts fondamentaux
des clés par fichier, des classes de protection, des clés de classe de protection et des keybags
mieux que je peux. Si vous ne les connaissez pas déjà, prenez quelques instants.
minutes pour lire les parties pertinentes.

Maintenant, vous savez que chaque fichier sur iOS est chiffré avec sa propre mémoire aléatoire
clé de chiffrement par fichier, appartient à une classe de protection, et la clé par fichier
Les clés de cryptage sont stockées dans les métadonnées du système de fichiers,
clé de classe de protection.

Décrypter:

  1. Décoder le sac à clé stocké dans le BackupKeyBag entrée de
    Manifest.plist. Un aperçu de haut niveau de cette structure est donné dans
    le livre blanc. Le wiki de l'iPhone
    décrit le format binaire: un champ de type chaîne de 4 octets, un champ de 4 octets
    champ de longueur big-endian, puis la valeur elle-même.

    Les valeurs importantes sont le PBKDF2 ITERations et SEL, le double
    sel de protection DPSL et le nombre d'itérations DPIC, puis pour chacun
    protection CLS, la WPKY clé enveloppée.

  2. En utilisant le mot de passe de sauvegarde, dérivez une clé de 32 octets en utilisant le PBKDF2 approprié.
    sel et nombre d'itérations. Commencez par utiliser une cartouche SHA256 avec DPSL et
    DPIC, puis un tour SHA1 avec ITER et SEL.

    Déballer chaque clé emballée selon
    RFC 3394.

  3. Décryptez la base de données de manifestes en extrayant la classe de protection de 4 octets et la clé plus longue ManifestKey dans Manifest.plistet le déballer. Vous avez maintenant un
    Base de données SQLite avec toutes les métadonnées du fichier.

  4. Pour chaque fichier d’intérêt, obtenez le chiffrement par fichier chiffré par classe.
    code de classe de clé et de protection en regardant dans le Files.file base de données
    colonne pour un plist binaire contenant Clé de cryptage et
    Classe de protection les entrées. Dénudez la balise initiale de longueur sur quatre octets de
    Clé de cryptage avant d'utiliser.

    Ensuite, dérivez la clé de déchiffrement finale en la dépliant avec la classe.
    clé qui a été déballée avec le mot de passe de sauvegarde. Puis déchiffrer le fichier
    utiliser AES en mode CBC avec un IV nul.

Comment décrypter des sauvegardes: en pratique

Vous aurez d’abord besoin de dépendances de la bibliothèque. Si vous utilisez un Mac utilisant une version 2.7 ou 3.7 de Python installée chez Homebrew, vous pouvez installer les dépendances avec:

CFLAGS = "- I $ (infusion - préfixe) / opt / openssl / include" 
LDFLAGS = "- L $ (infusion - préfixe) / opt / openssl / lib" 
    pip installer biplist fastpbkdf2 pycrypto

Sous forme de code source exécutable, voici comment décrypter un seul
Fichier de préférences d’une sauvegarde cryptée de l’iPhone:

#! / usr / bin / env python3.7
# codage: UTF-8

depuis __future__ import print_function
de __future__ division d'importation

importer argparse
import getpass
importer os.path
importer pprint
importer au hasard
importation
importer sqlite3
chaîne d'importation
structure d'importation
fichier temporaire d'importation
de binascii import hexlify

importer Crypto.Cipher.AES # https://www.dlitz.net/software/pycrypto/
importer biplist
importer fastpbkdf2
depuis import biplist InvalidPlistException


def main ():
    ## Options d'analyse
    analyseur = argparse.ArgumentParser ()
    parser.add_argument ('- répertoire-sauvegarde', dest = 'répertoire_sauvegarde',
                    default = 'testdata / encrypted')
    parser.add_argument ('- password-pipe', dest = 'password_pipe',
                        help = "" "
Empêche le mot de passe d'être visible dans la liste de processus du système.
Utilisation typique: --password-pipe = <(echo -n foo)
"" ")
    parser.add_argument ('- no-anonymize-output', dest = 'anonymize',
                        action = 'store_false')
    args = parser.parse_args ()
    ANONYMIZE_OUTPUT global
    ANONYMIZE_OUTPUT = args.anonymize
    si ANONYMIZE_OUTPUT:
        print ('Avertissement: toutes les clés de sortie sont fausses pour protéger votre vie privée')

    manifeste_fichier = os.path.join (args.backup_directory, 'Manifest.plist')
    avec open (manifest_file, 'rb') comme infile:
        manifest_plist = biplist.readPlist (infile)
    keybag = Keybag (manifest_plist['BackupKeyBag'])
    # les clés réelles sont inconnues, mais les clés enveloppées sont connues
    keybag.printClassKeys ()

    si args.password_pipe:
        password = readpipe (args.password_pipe)
        si password.endswith (b ' n'):
            mot de passe = mot de passe[:-1]
    autre:
        password = getpass.getpass ('Mot de passe de sauvegarde:') .encode ('utf-8')

    ## Déverrouiller le sac avec mot de passe
    sinon keybag.unlockWithPasscode (mot de passe):
        raise Exception ('Impossible de déverrouiller le sac à clé; mot de passe incorrect?')
    # maintenant les clés sont connues aussi
    keybag.printClassKeys ()

    ## Déchiffrer la base de données de métadonnées
    manifest_key = manifest_plist['ManifestKey'][4:]
    
    
    
    avec open (os.path.join (args.backup_directory, 'Manifest.db'), 'rb') en tant que db:
        encrypted_db = db.read ()

    manifest_class = struct.unpack ('L ", données)[0]
            si tag == b "TYPE":
                self.type = data
                si self.type> 3:
                    print ("FAIL: type de sac à clé> 3:% d"% self.type)
            balise elif == b "UUID" et self.uuid est None:
                self.uuid = data
            balise elif == b "WRAP" et self.wrap est None:
                self.wrap = data
            balise elif == b "UUID":
                si currentClassKey:
                    self.classKeys[currentClassKey[b"CLAS"]]= currentClassKey
                currentClassKey = {b "UUID": données}
            balise elif dans CLASSKEY_TAGS:
                currentClassKey[tag] = données
            autre:
                self.attrs[tag] = données
        si currentClassKey:
            self.classKeys[currentClassKey[b"CLAS"]]= currentClassKey

    def unlockWithPasscode (auto, mot de passe):
        passcode1 = fastpbkdf2.pbkdf2_hmac ('sha256', code d'accès,
                                        self.attrs[b"DPSL"],
                                        self.attrs[b"DPIC"], 32)
        passcode_key = fastpbkdf2.pbkdf2_hmac ('sha1', passcode1,
                                            self.attrs[b"SALT"],
                                            self.attrs[b"ITER"], 32)
        print ('== Code secret')
        print (anonymize (hexlify (passcode_key)))
        pour la clé de classe dans self.classKeys.values ​​():
            si b "WPKY" n'est pas dans la clé de classe:
                continuer
            k = clé de classe[b"WPKY"]
            si classkey[b"WRAP"] & WRAP_PASSCODE:
                k = AESUnwrap (passcode_key, classkey[b"WPKY"])
                sinon k:
                    retourne Faux
                clé de classe[b"KEY"] = k
        retourne True

    def unwrapKeyForClass (self, protection_class, persist_key):
        ck = self.classKeys[protection_class][b"KEY"]
        
        
        
        si len (persistent_key)! = 0x28:
            levée Exception ("Longueur de clé non valide")
        retourne AESUnwrap (ck, persistent_key)

    def printClassKeys (auto):
        print ("== Keybag")
        print ("Type de keybag:% s keybag (% d)"% (KEYBAG_TYPES[self.type], self.type))
        print ("Version Keybag:% d"% self.attrs[b"VERS"])
        print ("Keybag UUID:% s"% anonymize (hexlify (self.uuid)))
        print ("-" * 209)
        print ("". join ((["Class".ljust(53),
                    "WRAP".ljust(5),
                    "Type".ljust(11),
                    "Key".ljust(65),
                    "WPKY".ljust(65),
                    "Public key"]))
        print ("-" * 208)
        pour k, ck dans self.classKeys.items ():
            si k == 6: affiche ("")

            print ("". join ((
                [PROTECTION_CLASSES.get(k).ljust(53),
                str(ck.get(b"WRAP","")).ljust(5),
                KEY_TYPES[ck.get(b"KTYP",0)].ljust (11),
                anonymiser (hexlify (ck.get (b "clé", b "")))). ljust (65),
                anonymiser (hexlify (ck.get (b "WPKY", b "")))). ljust (65),
            ]))
        impression()

def loopTLVBlocks (blob):
    i = 0
    alors que je + 8 <= len(blob):
        tag = blob[i:i+4]
        length = struct.unpack(">L ", blob[i+4:i+8])[0]
        data = blob[i+8:i+8+length]
        rendement (étiquette, données)
        i + = 8 + longueur

def unpack64bit (s):
    retourne struct.unpack ("> Q", s)[0]
def pack64bit (s):
    retourne struct.pack ("> Q", s)

def AESUnwrap (kek, enveloppé):
    C = []
    pour i in range (len (wrapped) // 8):
        C.append (unpack64bit (enveloppé)[i*8:i*8+8]))
    n = len (C) - 1
    R = [0] * (n + 1)
    A = C[0]

    pour i dans la plage (1, n + 1):
        R[i] = C[i]

    pour j en inverse (étendue (0,6)):
        pour i en inverse (plage (1, n + 1)):
            todec = pack64bit (A ^ (n * j + i))
            todec + = pack64bit (R[i])
            B = Crypto.Cipher.AES.new (kek) .decrypt (todec)
            A = unpack64bit (B[:8])
            R[i] = unpack64bit (B[8:])

    si A! = 0xa6a6a6a6a6a6a6a6a6:
        retourner Aucun
    res = b "". join (map (pack64bit, R[1:]))
    retour res

ZEROIV = " x00" * 16
def AESdecryptCBC (données, clé, iv = ZEROIV, padding = False):
    si len (données)% 16:
        print ("AESdecryptCBC: longueur de données non / 16, tronquée")
        data = data[0:(len(data)/16) * 16]
    data = Crypto.Cipher.AES.new (clé, Crypto.Cipher.AES.MODE_CBC, iv) .decrypt (data)
    si rembourrage:
        retourne removePadding (16, données)
    renvoyer des données

##
# voici quelques fonctions d’utilité, dont une pour ne pas laisser fuir mes
# clés secrètes lors de la publication du résultat sur Stack Exchange

anon_random = random.Random (0)
mémo = {}
def anonymiser (s):
    si type (s) == str:
        s = s.encode ('utf-8')
    global anon_random, mémo
    si ANONYMIZE_OUTPUT:
        si en mémo:
            retour mémo[s]
        possible_alphabets = [
            string.digits,
            string.digits + 'abcdef',
            string.ascii_letters,
            "".join(chr(x) for x in range(0, 256)),
        ]
        pour un possible_alphabets:
            si tout ((chr (c) si type (c) == int sinon c) dans a pour c dans s):
                alphabet = a
                Pause
        ret = "" .join ([anon_random.choice(alphabet) for i in range(len(s))])
        note[s] = ret
        retour ret
    autre:
        résultats

def wrap (s, width = 78):
    "Retourne une chaîne de type repr (s) enveloppée dans la largeur sans se briser sur  's"
    s = repr (s)
    citation = s[0]
    s = s[1:-1]
    ret = []
    en tant que:
        i = à la recherche ('\', 0, largeur)
        si je <= largeur - 4: # " x ??" est quatre personnages
            i = largeur
        ret.append (s[:i])
        s = s[i:]
    return ' n'.join ("% s% s% s"% (quote, ligne, quote) pour la ligne dans ret)

def readpipe (path):
    si stat.S_ISFIFO (os.stat (chemin) .st_mode):
        avec open (chemin, 'rb') comme tuyau:
            retourne pipe.read ()
    autre:
        levée Exception ("Pas un tuyau: {! r}". format (chemin))

si __name__ == '__main__':
    principale()

Ce qui imprime ensuite cette sortie:

Avertissement: Toutes les clés de sortie sont FAUX pour protéger votre vie privée
== Keybag
Type de sac: sac de secours (1)
Version Keybag: 3
Clé UUID: dc6486c479e84c94efce4bea7169ef7d
-------------------------------------------------- -------------------------------------------------- -------------------------------------------------- -------------------------------------------------- ---------
Classe Type WRAP Clé WPKY Clé publique
-------------------------------------------------- -------------------------------------------------- -------------------------------------------------- -------------------------------------------------- --------
NSFileProtectionComplete 2 AES 4c80b6da07d35d393fc7158e18b8d8f9979694329a71ceedee86b4cde9f97afec197ad3b13c5d12b
NSFileProtectionCompleteUnlessOpen 2 AES 09e8a0a9965f00f213ce06143a5280fdeb2f0ad54972769845d480b5043f545fa9b66a0353a6
NSFileProtectionCompleteUntilFirstUserAuthentication 2 AES e966b6a0742878ce747cecfa1bf6a53b0d811ad4f1d6147cd28a5d400a8ffe0bbabea5839025cb5
NSFileProtectionNone 2 AES 902f46847302816561e7df57b64beea6fa11b0068779a65f4c651dbe7a1630f323682ff26ae7e577
NSFileProtectionRecovery? 3 AES a3935fed024cd9bc11d0300d522af8e89accfbe389d7c69dca02841df46c0a24d0067dba2f696072

kSecAttrAccessibleWhen 2 AES 09a1856c7e97a51a9c2ecedac8c3c7c7c10e7efa931decb64169ee61cb07a0efb115050fd1e33af1
kSecAttrAccessibleAfterFirstUnlock 2 AES 0509d215f2f574efa2f192efc53c460201168b26a175f066b5347fc48bc76c637e27a730b904ca82
kSecAttrAccessibleToujours 2 AES b7ac3c4f1e04896144ce90c4583e26489a86a6cc45a2b692a5767b5a04b0907e081daba009fdbb3c
kSecAttrAccessibleWhen débloquéCe dispositif Seulement 3 AES 417526e67b82e7c6c633f9063120a299b84e57s8bee97b34020a2caf6e751ec5750053833ab4d45
kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly 3 AES b0e17b0cf7111c6e716cd0272de5684834798431c1b34bab8d1a1b5abad38a3a42c859026f81cc81c
kSecAttrAccessibleAlwaysThisDeviceOnly 3 AES 9b3bdc59ae1d85703aa7f75d49bdc600bf57ba4a458b00a003a10f6e36525fb6648ba70e6602d8b2

== clé de code
ee34f5bb635830d698074b1e3e268059c590973b0f1138f1954a2a4e1069e612

== Keybag
Type de sac: sac de secours (1)
Version Keybag: 3
Clé UUID: dc6486c479e84c94efce4bea7169ef7d
-------------------------------------------------- -------------------------------------------------- -------------------------------------------------- -------------------------------------------------- ---------
Classe Type WRAP Clé WPKY Clé publique
-------------------------------------------------- -------------------------------------------------- -------------------------------------------------- -------------------------------------------------- --------
NSFileProtectionComplets 2
NSFileProtectionCompleteUnlessO 2
NSFileProtectionCompleteUnForfAuthentication
NSFileProtectionNone 2 AES 2e 809a0cd1a73725a788d5d1657d8f1d150b0e360460cb5d105eca9c60c365152
NSFileProtectionRecovery? 3 AES 9a078d710dcd4a1d5f70ea4062822ea3e9f7ea034233e7e290e06cf0d80c19ca a3935fed024cd9bc11d0300d522af8e89accfbe389d7c69dca

kSecAtocleW
kSecAttrAccessibleAfterFirstUnlock 2 AES 6a4b5292661bac882338d5ebb51fd6de585befb4ef5f8ffda209be8ba3af1b96 0509d215f2f574efa2f192efc53c460201168b26a175f066b5347fc48bc76c637e27a730b904ca82
bSaa
kSecAtB
kSecAtAfterFlockCoblique de bricolage de bricolage
bSoCoAbricoDeBoardAujourd'hui

== données décryptées:
' n n nNTDCIMLastDirectoryNumberNT100NTDCIMLastFileNumberNT3 n n n '

== beau plist imprimé
{'DCIMLastDirectoryNumber': 100, 'DCIMLastFileNumber': 3}

Crédit supplémentaire

Le code de protection de données iphone publié par Bédrune et Sigwald peut
décrypter le porte-clés à partir d'une sauvegarde, y compris des choses amusantes comme le wifi enregistré
et mots de passe du site:

$ python iphone-dataprotection / python_scripts / keychain_tool.py ...

-------------------------------------------------- ------------------------------------
| Mots de passe |
-------------------------------------------------- ------------------------------------
| Service | Compte | Données | Groupe d'accès | Classe de protection |
-------------------------------------------------- ------------------------------------
| AirPort | Ed’s Coffee Shop | <3FrenchRoast | apple | AfterFirstUnlock |
...

Ce code ne fonctionne plus sur les sauvegardes de téléphones utilisant la dernière version d'iOS, mais
Si peu de choses ont changé … laissez un commentaire si vous voulez que je
mettre à jour le code ci-dessus pour vider également les mots de passe enregistrés; P

Comment décrypter une sauvegarde iPhone iTunes Apple iTunes cryptée?
4.9 (98%) 43 votes
 
One Comment

Add a Comment

Votre adresse de messagerie ne sera pas publiée.