Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 41 additions & 5 deletions iOSbackup/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -976,6 +976,10 @@ def getFileDecryptedCopy(self, relativePath=None, manifestEntry=None, targetName

decryptor = AES.new(key, AES.MODE_CBC, b'\x00'*16)

chunkIndex=0
bytesWritten=0
decryptedChunk=None
finalByteWritten=None

# {BACKUP_ROOT}/{UDID}/ae/ae2c3d4e5f6...
with open(os.path.join(self.backupRoot, self.udid, fileNameHash[:2], fileNameHash), 'rb') as inFile:
Expand All @@ -985,18 +989,50 @@ def getFileDecryptedCopy(self, relativePath=None, manifestEntry=None, targetName
mappedInFile = mmap.mmap(inFile.fileno(), length=0, prot=mmap.PROT_READ)

with open(targetFileName, 'wb') as outFile:

chunkIndex=0
while True:
chunk = mappedInFile[chunkIndex*chunkSize:(chunkIndex+1)*chunkSize]

if len(chunk) == 0:
break

outFile.write(decryptor.decrypt(chunk))
decryptedChunk = decryptor.decrypt(chunk)
outFile.write(decryptedChunk)

chunkIndex+=1
bytesWritten+=len(decryptedChunk)
finalByteWritten=decryptedChunk[-1]

def hasAesPadding(expectedPaddingSize):
"""Checks the content of the last N bytes, which should be filled with 'N' repeating"""
if finalByteWritten != expectedPaddingSize:
return False

# Checks each byte by counting occurrences
potentialPadding = decryptedChunk[-finalByteWritten:]
return potentialPadding.count(finalByteWritten) == finalByteWritten

# Compare file sizes across 1) Manifest.db record, 2) original file, and 3) decrypted output
# (decrypted output sometimes adds RFC 1423 padding, aligning data on a 16-byte boundary)
originalSize=os.path.getsize(os.path.join(self.backupRoot, self.udid, fileNameHash[:2], fileNameHash))
if bytesWritten - info['size'] > 16:
# Check if we have a final encryption pass of 16 bytes appended, for some reason
if hasAesPadding(16):
outFile.truncate(bytesWritten - finalByteWritten)

if bytesWritten - info['size'] == 16:
if hasAesPadding(16):
outFile.truncate(bytesWritten - 16)

if bytesWritten - info['size'] < 16 and bytesWritten - info['size'] > 0:
# This is the "normal" case, where we added a few bytes of extra padding
if hasAesPadding(bytesWritten - info['size']):
outFile.truncate(bytesWritten - finalByteWritten)

if bytesWritten - info['size'] < 0:
# For an over-reported size, do nothing because we can't conjure data from nowhere
# Still, check if the last 16 bytes looks like padding
if hasAesPadding(16):
outFile.truncate(bytesWritten - finalByteWritten)

outFile.truncate(info['size'])
elif info['isFolder']:
# Plain folder
Path(targetFileName).mkdir(parents=True, exist_ok=True)
Expand Down