hacks/dump-imessages/iphone-dataprotection/python_scripts/backups/backup4.py

175 lines
5.6 KiB
Python

from Crypto.Cipher import AES
from hashlib import sha1
from struct import unpack
import os
MBDB_SIGNATURE = 'mbdb\x05\x00'
MASK_SYMBOLIC_LINK = 0xa000
MASK_REGULAR_FILE = 0x8000
MASK_DIRECTORY = 0x4000
def warn(msg):
print "WARNING: %s" % msg
class MBFileRecord(object):
def __init__(self, mbdb):
self.domain = self._decode_string(mbdb)
if self.domain is None:
warn("Domain name missing from record")
self.path = self._decode_string(mbdb)
if self.path is None:
warn("Relative path missing from record")
self.target= self._decode_string(mbdb) # for symbolic links
self.digest = self._decode_string(mbdb)
self.encryption_key = self._decode_data(mbdb)
data = mbdb.read(40) # metadata, fixed size
self.mode, = unpack('>H', data[0:2])
if not(self.is_regular_file() or self.is_symbolic_link() or self.is_directory()):
print self.mode
warn("File type mising from record mode")
if self.is_symbolic_link() and self.target is None:
warn("Target required for symblolic links")
self.inode_number = unpack('>Q', data[2:10])
self.user_id, = unpack('>I', data[10:14])
self.group_id = unpack('>I', data[14:18])
self.last_modification_time, = unpack('>i', data[18:22])
self.last_status_change_time, = unpack('>i', data[22:26])
self.birth_time, = unpack('>i', data[26:30])
self.size, = unpack('>q', data[30:38])
if self.size != 0 and not self.is_regular_file():
warn("Non-zero size for a record which is not a regular file")
self.protection_class = ord(data[38])
num_attributes = ord(data[39])
if num_attributes == 0:
self.extended_attributes = None
else:
self.extended_attributes = {}
for i in xrange(num_attributes):
k = self._decode_string(mbdb)
v = self._decode_data(mbdb)
self.extended_attributes[k] = v
def _decode_string(self, s):
s_len, = unpack('>H', s.read(2))
if s_len == 0xffff:
return None
return s.read(s_len)
def _decode_data(self, s):
return self._decode_string(s)
def type(self):
return self.mode & 0xf000
def is_symbolic_link(self):
return self.type() == MASK_SYMBOLIC_LINK
def is_regular_file(self):
return self.type() == MASK_REGULAR_FILE
def is_directory(self):
return self.type() == MASK_DIRECTORY
class MBDB(object):
def __init__(self, path):
self.files = {}
self.backup_path = path
self.keybag = None
# open the database
mbdb = file(path + '/Manifest.mbdb', 'rb')
# skip signature
signature = mbdb.read(len(MBDB_SIGNATURE))
if signature != MBDB_SIGNATURE:
raise Exception("Bad mbdb signature")
try:
while True:
rec = MBFileRecord(mbdb)
fn = rec.domain + "-" + rec.path
sb = sha1(fn).digest().encode('hex')
if len(sb) % 2 == 1:
sb = '0'+sb
self.files[sb] = rec
except:
mbdb.close()
def get_file_by_name(self, filename):
for (k, v) in self.files.iteritems():
if v.path == filename:
return (k, v)
return None
def extract_backup(self, output_path):
for record in self.files.values():
# create directories if they do not exist
# makedirs throw an exception, my code is ugly =)
if record.is_directory():
try:
os.makedirs(os.path.join(output_path, record.domain, record.path))
except:
pass
for (filename, record) in self.files.items():
# skip directories
if record.is_directory():
continue
self.extract_file(filename, record, output_path)
def extract_file(self, filename, record, output_path):
# adjust output file name
if record.is_symbolic_link():
out_file = record.target
else:
out_file = record.path
# read backup file
try:
f1 = file(os.path.join(self.backup_path, filename), 'rb')
except(IOError):
warn("File %s (%s) has not been found" % (filename, record.path))
return
# write output file
output_path = os.path.join(output_path, record.domain, out_file)
print("Writing %s" % output_path)
f2 = file(output_path, 'wb')
aes = None
if record.encryption_key is not None and self.keybag: # file is encrypted!
key = self.keybag.unwrapKeyForClass(record.protection_class, record.encryption_key[4:])
if not key:
warn("Cannot unwrap key")
return
aes = AES.new(key, AES.MODE_CBC, "\x00"*16)
while True:
data = f1.read(8192)
if not data:
break
if aes:
data2 = data = aes.decrypt(data)
f2.write(data)
f1.close()
if aes:
c = data2[-1]
i = ord(c)
if i < 17 and data2.endswith(c*i):
f2.truncate(f2.tell() - i)
else:
warn("Bad padding, last byte = 0x%x !" % i)
f2.close()