Skip to content

Handle cases where Manifest.db's reported size doesn't match #26

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
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