From f0a0ebc329db595af1b8a949eb18c06f8b39ff68 Mon Sep 17 00:00:00 2001 From: James Sharkey Date: Sat, 10 Feb 2024 11:40:57 +0000 Subject: [PATCH] Use standard library plistlib over biplist Since Python 3.4, the standard library has supported the binary plist format found in iPhone backups, and in 3.8 the support for UID values was added: https://docs.python.org/3.8/library/plistlib.html This is all that is required by this library, so the additional biplist dependency is now unnecesary. --- pyproject.toml | 1 - src/iphone_backup_decrypt/iphone_backup.py | 11 +++++------ 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index d00b886..bb807c1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -15,7 +15,6 @@ classifiers = [ keywords = ["iPhone", "backup", "forensics", "iOS", "WhatsApp", "decryption", "iOS backup", "iTunes Backup"] dependencies = [ - "biplist>=1.0.3", "pycryptodome>=3.20.0" ] diff --git a/src/iphone_backup_decrypt/iphone_backup.py b/src/iphone_backup_decrypt/iphone_backup.py index 4cd8533..22187b5 100644 --- a/src/iphone_backup_decrypt/iphone_backup.py +++ b/src/iphone_backup_decrypt/iphone_backup.py @@ -1,11 +1,10 @@ import os.path +import plistlib import shutil import sqlite3 import struct import tempfile -import biplist - from . import google_iphone_dataprotection __all__ = ["EncryptedBackup", "RelativePath", "RelativePathsLike", "DomainLike", "MatchFiles"] @@ -138,7 +137,7 @@ def _read_and_unlock_keybag(self): return self._unlocked # Open the Manifest.plist file to access the Keybag: with open(self._manifest_plist_path, 'rb') as infile: - self._manifest_plist = biplist.readPlist(infile) + self._manifest_plist = plistlib.load(infile) self._keybag = google_iphone_dataprotection.Keybag(self._manifest_plist['BackupKeyBag']) # Attempt to unlock the Keybag: self._unlocked = self._keybag.unlockWithPassphrase(self._passphrase) @@ -188,8 +187,8 @@ def _decrypt_inner_file(self, *, file_id, file_bplist, if_modified_since=None): # Ensure we've already unlocked the Keybag: self._read_and_unlock_keybag() # Read the plist data and extract file metadata: - plist = biplist.readPlistFromString(file_bplist) - file_data = plist['$objects'][plist['$top']['root'].integer] + plist = plistlib.loads(file_bplist) + file_data = plist['$objects'][plist['$top']['root'].data] file_mtime = file_data.get("LastModified") # Was the file modified since the time requested? if if_modified_since and file_mtime <= if_modified_since: @@ -199,7 +198,7 @@ def _decrypt_inner_file(self, *, file_id, file_bplist, if_modified_since=None): protection_class = file_data['ProtectionClass'] if "EncryptionKey" not in file_data: raise ValueError("Path is not an encrypted file.") # File is not encrypted; either a directory or empty. - encryption_key = plist['$objects'][file_data['EncryptionKey'].integer]['NS.data'][4:] + encryption_key = plist['$objects'][file_data['EncryptionKey'].data]['NS.data'][4:] inner_key = self._keybag.unwrapKeyForClass(protection_class, encryption_key) # Find the encrypted version of the file on disk and decrypt it: filename_in_backup = os.path.join(self._backup_directory, file_id[:2], file_id)