Skip to content

Commit

Permalink
Add decryption for iOS 10.2 Manifest.DB (fixes #14)
Browse files Browse the repository at this point in the history
  • Loading branch information
mbiette committed May 29, 2017
1 parent 866f834 commit d356489
Show file tree
Hide file tree
Showing 3 changed files with 88 additions and 9 deletions.
14 changes: 12 additions & 2 deletions python_scripts/backup_tool.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import os
import sys
import plistlib
import struct

showinfo = ["Device Name", "Display Name", "Last Backup Date", "IMEI",
"Serial Number", "Product Type", "Product Version", "iTunes Version"]
Expand Down Expand Up @@ -51,15 +52,24 @@ def extract_backup(backup_path, output_path, password=""):
print "python keychain_tool.py -d \"%s\" \"%s\"" % (output_path + "/KeychainDomain/keychain-backup.plist", output_path + "/Manifest.plist")

elif os.path.exists(backup_path + "/Manifest.db"):
manifset_db = ManifestDB(backup_path)
if 'ManifestKey' in manifest:
kb = Keybag.createWithBackupManifest(manifest, password, ios102=True)
else:
kb = Keybag.createWithBackupManifest(manifest, password)

kb = Keybag.createWithBackupManifest(manifest, password)
if not kb:
return
manifest["password"] = password
makedirs(output_path)
plistlib.writePlist(manifest, output_path + "/Manifest.plist")

manifest_key = None
if 'ManifestKey' in manifest:
clas = struct.unpack('<L', manifest['ManifestKey'].data[:4])[0]
wkey = manifest['ManifestKey'].data[4:]
manifest_key = kb.unwrapKeyForClass(clas, wkey)

manifset_db = ManifestDB(backup_path, key=manifest_key)
manifset_db.keybag = kb
manifset_db.extract_backup(output_path)

Expand Down
44 changes: 42 additions & 2 deletions python_scripts/backups/backup10.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,12 +80,20 @@ def is_directory(self):


class ManifestDB(object):
def __init__ (self, path):
def __init__ (self, path, key=None):
self.files = {}
self.backup_path = path
self.keybag = None

conn = sqlite3.connect(os.path.join(path,'Manifest.db'))
mdb_path = os.path.join(path,'Manifest.db')

#If a key is provided, try to decrypt the DB
if key:
mdb_path_encrypted = mdb_path
mdb_path = os.path.join(path,'Manifest.db-decypted')
self.decrypt_manifest_db(mdb_path_encrypted, mdb_path, key)

conn = sqlite3.connect(mdb_path)

try:
conn.row_factory = sqlite3.Row
Expand All @@ -105,6 +113,38 @@ def __init__ (self, path):
finally:
conn.close()

@staticmethod
def decrypt_manifest_db(path_in, path_out, key):
"""
Will decrypt the Manifest.db file using the provided key.
TODO: merge with method _extract_file
"""
aes = AES.new(key, AES.MODE_CBC, "\x00"*16)

f_in = file(path_in, 'rb')
f_out = file(path_out, 'wb')

while True:
data = f_in.read(8192)
if not data:
break

data2 = data = aes.decrypt(data)
f_out.write(data)

f_in.close()

c = data2[-1]
i = ord(c)

if i < 17 and data2.endswith(c*i):
f_out.truncate(f_out.tell() - i)

else:
print "Bad padding, last byte = 0x%x !" % i

f_out.close()

def extract_backup(self, output_path):
for mbfile in self.files.itervalues():
Expand Down
39 changes: 34 additions & 5 deletions python_scripts/keystore/keybag.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from crypto.aeswrap import AESUnwrap
from crypto.aeswrap import AESwrap
from crypto.curve25519 import curve25519
from Crypto.Hash import SHA256
from hashlib import sha256, sha1
from util.bplist import BPlistReader
from util.tlv import loopTLVBlocks, tlvToDict
Expand Down Expand Up @@ -127,10 +128,10 @@ def createWithDataSignBlob(blob, deviceKey=None):
return kb

@staticmethod
def createWithBackupManifest(manifest, password, deviceKey=None):
def createWithBackupManifest(manifest, password, deviceKey=None, ios102=False):
kb = Keybag(manifest["BackupKeyBag"].data)
kb.deviceKey = deviceKey
if not kb.unlockBackupKeybagWithPasscode(password):
if not kb.unlockBackupKeybagWithPasscode(password, ios102=ios102):
print "Cannot decrypt backup keybag. Wrong password ?"
return
return kb
Expand Down Expand Up @@ -163,18 +164,46 @@ def parseBinaryBlob(self, data):
if currentClassKey:
self.classKeys[currentClassKey["CLAS"] & 0xF] = currentClassKey

def getPasscodekeyFromPasscode(self, passcode):
def getPasscodekeyFromPasscode(self, passcode, ios102=False):
"""
Generate a passcode key from the raw passcode provided using the keybag data.
Parameters
----------
passcode : str
The passcode provided to decrypt the keybag
ios102 : bool
Will trigger the use of the iOS 10.2 method, which added an additional step.
Returns
-------
str
Passcode key in a binary format.
"""
if self.type == BACKUP_KEYBAG or self.type == OTA_KEYBAG:
if ios102:
""" Additional step introduce by iOS 10.2
Following this post:
https://github.com/horrorho/InflatableDonkey/issues/41#issuecomment-261927890
"""
print "Generating the password key for iOS 10.2+. May take a while..."
passcode = PBKDF2(passcode,
self.attrs["DPSL"],
iterations=self.attrs["DPIC"],
digestmodule=SHA256
).read(32)
return PBKDF2(passcode, self.attrs["SALT"], iterations=self.attrs["ITER"]).read(32)
else:
#Warning, need to run derivation on device with this result
return PBKDF2(passcode, self.attrs["SALT"], iterations=1).read(32)

def unlockBackupKeybagWithPasscode(self, passcode):
def unlockBackupKeybagWithPasscode(self, passcode, ios102=False):
if self.type != BACKUP_KEYBAG and self.type != OTA_KEYBAG:
print "unlockBackupKeybagWithPasscode: not a backup keybag"
return False
return self.unlockWithPasscodeKey(self.getPasscodekeyFromPasscode(passcode))
return self.unlockWithPasscodeKey(self.getPasscodekeyFromPasscode(passcode, ios102=ios102))

def unlockAlwaysAccessible(self):
for classkey in self.classKeys.values():
Expand Down

0 comments on commit d356489

Please sign in to comment.