How to decrypt and encrypt iphone backup using python IOS 10.1.1 -
with requirement set apple , mdm force encryption on backups, there way decrypt , encrypt iphone backups?
we need access raw files saved within backup make slight changes few plist files inside, problem need in code doing hand take forever 1000+ phones. have of code written in python , editing plist seems working fine until put on mdm profile resulting in phone requiring encrypted backups point on.
here found far: unable make 10> change file mbdb db file. reading others, seems that might have change how files encrypted. or update on helpful!
#!/usr/bin/env python2.7 # coding: utf-8 # default true avoid leaking secrets anonymize_output = true import pbkdf2 # http://iphone-dataprotection.googlecode.com/hg-history/tip/python_scripts/crypto/pbkdf2.py import bplist # https://github.com/farcaller/bplist-python/raw/master/bplist.py import crypto.cipher.aes # https://www.dlitz.net/software/pycrypto/ import hashlib import os.path import pprint import sys backup_dir = "data/encrypted" def main(): open(os.path.join(backup_dir, 'manifest.plist'), 'rb') infile: manifest_plist = bplist.bplistreader.plistwithstring(infile.read()) keybag = keybag(manifest_plist['backupkeybag']) # actual keys unknown, wrapped keys known keybag.printclasskeys() if not keybag.unlockwithpasscode('test'): raise exception('could not unlock keybag; bad password?') # keys known keybag.printclasskeys() item in process_mbdb_file( os.path.join(backup_dir, 'manifest.mbdb')).values(): filename = item['filename'] if not filename.endswith('calculator.plist'): continue encryption_key = item['unknown1'][4:] protection_class = item['flag'] backup_filename = os.path.join( backup_dir, hashlib.sha1(item['domain'] + '-' + item['filename']).hexdigest()) open(backup_filename, 'rb') infile: data = infile.read() print '== encrypted data:' print wrap(data) print key = keybag.unwrapkeyforclass(protection_class, encryption_key) # truncate actual length, because encryption may introduce padding decrypted_data = aesdecryptcbc(data, key)[:item['filelen']] print '== decrypted data:' print wrap(decrypted_data) print print '== pretty-printed calculator preferences' pprint.pprint(bplist.bplistreader.plistwithstring(decrypted_data)) ## # section copied parts of iphone-dataprotection # http://code.google.com/p/iphone-dataprotection/ import struct classkey_tags = ["clas","wrap","wpky", "ktyp", "pbky"] #uuid keybag_types = ["system", "backup", "escrow", "ota (icloud)"] key_types = ["aes", "curve25519"] protection_classes={ 1:"nsfileprotectioncomplete", 2:"nsfileprotectioncompleteunlessopen", 3:"nsfileprotectioncompleteuntilfirstuserauthentication", 4:"nsfileprotectionnone", 5:"nsfileprotectionrecovery?", 6: "ksecattraccessiblewhenunlocked", 7: "ksecattraccessibleafterfirstunlock", 8: "ksecattraccessiblealways", 9: "ksecattraccessiblewhenunlockedthisdeviceonly", 10: "ksecattraccessibleafterfirstunlockthisdeviceonly", 11: "ksecattraccessiblealwaysthisdeviceonly" } wrap_device = 1 wrap_passcode = 2 class keybag(object): def __init__(self, data): self.type = none self.uuid = none self.wrap = none self.devicekey = none self.attrs = {} self.classkeys = {} self.keybagkeys = none #datasign blob self.parsebinaryblob(data) def parsebinaryblob(self, data): currentclasskey = none tag, data in looptlvblocks(data): if len(data) == 4: data = struct.unpack(">l", data)[0] if tag == "type": self.type = data if self.type > 3: print "fail: keybag type > 3 : %d" % self.type elif tag == "uuid" , self.uuid none: self.uuid = data elif tag == "wrap" , self.wrap none: self.wrap = data elif tag == "uuid": if currentclasskey: self.classkeys[currentclasskey["clas"]] = currentclasskey currentclasskey = {"uuid": data} elif tag in classkey_tags: currentclasskey[tag] = data else: self.attrs[tag] = data if currentclasskey: self.classkeys[currentclasskey["clas"]] = currentclasskey def unlockwithpasscode(self, passcode): passcodekey = pbkdf2.pbkdf2(passcode, self.attrs["salt"], iterations=self.attrs["iter"]).read(32) classkey in self.classkeys.values(): if not classkey.has_key("wpky"): continue k = classkey["wpky"] if classkey["wrap"] & wrap_passcode: k = aesunwrap(passcodekey, classkey["wpky"]) if not k: return false classkey["key"] = k return true def unwrapkeyforclass(self, protection_class, persistent_key): ck = self.classkeys[protection_class]["key"] if len(persistent_key) != 0x28: raise exception("invalid key length") return aesunwrap(ck, persistent_key) def printclasskeys(self): print "== keybag" print "keybag type: %s keybag (%d)" % (keybag_types[self.type], self.type) print "keybag version: %d" % self.attrs["vers"] print "keybag iterations: %d, iv=%s" % ( self.attrs["iter"], anonymize(self.attrs["salt"].encode('hex'))) print "keybag uuid: %s" % anonymize(self.uuid.encode("hex")) print "-"*209 print "".join(["class".ljust(53), "wrap".ljust(5), "type".ljust(11), "key".ljust(65), "wpky".ljust(65), "public key"]) print "-"*208 k, ck in self.classkeys.items(): if k == 6: print "" print "".join( [protection_classes.get(k).ljust(53), str(ck.get("wrap","")).ljust(5), key_types[ck.get("ktyp",0)].ljust(11), anonymize(ck.get("key", "").encode("hex")).ljust(65), anonymize(ck.get("wpky", "").encode("hex")).ljust(65), ck.get("pbky", "").encode("hex")]) print def looptlvblocks(blob): = 0 while + 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] yield (tag,data) += 8 + length def unpack64bit(s): return struct.unpack(">q",s)[0] def pack64bit(s): return struct.pack(">q",s) def aesunwrap(kek, wrapped): c = [] in xrange(len(wrapped)/8): c.append(unpack64bit(wrapped[i*8:i*8+8])) n = len(c) - 1 r = [0] * (n+1) = c[0] in xrange(1,n+1): r[i] = c[i] j in reversed(xrange(0,6)): in reversed(xrange(1,n+1)): todec = pack64bit(a ^ (n*j+i)) todec += pack64bit(r[i]) b = crypto.cipher.aes.new(kek).decrypt(todec) = unpack64bit(b[:8]) r[i] = unpack64bit(b[8:]) if != 0xa6a6a6a6a6a6a6a6: return none res = "".join(map(pack64bit, r[1:])) return res zeroiv = "\x00"*16 def aesdecryptcbc(data, key, iv=zeroiv, padding=false): if len(data) % 16: print "aesdecryptcbc: data length not /16, truncating" data = data[0:(len(data)/16) * 16] data = crypto.cipher.aes.new(key, crypto.cipher.aes.mode_cbc, iv).decrypt(data) if padding: return removepadding(16, data) return data ## # .mbdb-parsing code http://stackoverflow.com/q/3085153/14558: def getint(data, offset, intsize): """retrieve integer (big-endian) , new offset current offset""" value = 0 while intsize > 0: value = (value<<8) + ord(data[offset]) offset = offset + 1 intsize = intsize - 1 return value, offset def getstring(data, offset): """retrieve string , new offset current offset data""" if data[offset] == chr(0xff) , data[offset+1] == chr(0xff): return '', offset+2 # blank string length, offset = getint(data, offset, 2) # 2-byte length value = data[offset:offset+length] return value, (offset + length) def process_mbdb_file(filename): mbdb = {} # map offset of info in file => file info data = open(filename).read() if data[0:4] != "mbdb": raise exception("this not mbdb file") offset = 4 offset = offset + 2 # value x05 x00, not sure while offset < len(data): fileinfo = {} fileinfo['start_offset'] = offset fileinfo['domain'], offset = getstring(data, offset) fileinfo['filename'], offset = getstring(data, offset) fileinfo['linktarget'], offset = getstring(data, offset) fileinfo['datahash'], offset = getstring(data, offset) fileinfo['unknown1'], offset = getstring(data, offset) fileinfo['mode'], offset = getint(data, offset, 2) fileinfo['unknown2'], offset = getint(data, offset, 4) fileinfo['unknown3'], offset = getint(data, offset, 4) fileinfo['userid'], offset = getint(data, offset, 4) fileinfo['groupid'], offset = getint(data, offset, 4) fileinfo['mtime'], offset = getint(data, offset, 4) fileinfo['atime'], offset = getint(data, offset, 4) fileinfo['ctime'], offset = getint(data, offset, 4) fileinfo['filelen'], offset = getint(data, offset, 8) fileinfo['flag'], offset = getint(data, offset, 1) fileinfo['numprops'], offset = getint(data, offset, 1) fileinfo['properties'] = {} ii in range(fileinfo['numprops']): propname, offset = getstring(data, offset) propval, offset = getstring(data, offset) fileinfo['properties'][propname] = propval mbdb[fileinfo['start_offset']] = fileinfo return mbdb ## # , here utility functions, 1 making sure don’t leak # secret keys when posting output on stack exchange if anonymize_output: memo = {} def anonymize(s): global memo if s in memo: return memo[s] import random import string r = random.random(0) possible_alphabets = [ string.digits, string.digits + 'abcdef', "".join(chr(x) x in range(0, 256)), ] in possible_alphabets: if all(c in c in s): alphabet = break ret = "".join([r.choice(alphabet) in range(len(s))]) memo[s] = ret return ret else: def anonymize(s): return s def wrap(s, width=78): "return width-wrapped repr(s)-like string without breaking on \’s" s = repr(s) quote = s[0] s = s[1:-1] ret = [] while len(s): = s.rfind('\\', 0, width) if <= width - 4: # "\x??" 4 characters = width ret.append(s[:i]) s = s[i:] return '\n'.join("%s%s%s" % (quote, line ,quote) line in ret) if __name__ == '__main__': main()
Comments
Post a Comment