From 1a9a1ccd10eddc209a338ef29e2ed40bf08b0005 Mon Sep 17 00:00:00 2001 From: m1stadev Date: Thu, 6 Jan 2022 22:36:07 -0600 Subject: [PATCH 01/56] [api] Rewrite - Actually use exceptions - Type-hinting - Change return of some functions --- utils/api.py | 86 ++++++++++++++++++++++++++++++---------------------- 1 file changed, 49 insertions(+), 37 deletions(-) diff --git a/utils/api.py b/utils/api.py index fb88e65..61563cb 100644 --- a/utils/api.py +++ b/utils/api.py @@ -1,59 +1,71 @@ +from pathlib import Path +from typing import Optional +from utils import errors + import remotezip import requests -import sys class API: - def check_device(self, identifier): - api = requests.get('https://api.ipsw.me/v4/devices').json() - - if identifier not in [device['identifier'] for device in api]: - sys.exit(f"[ERROR] '{identifier}' does not exist. Exiting.") - + def __init__(self, identifier: str) -> None: self.device = identifier + self.api = self.fetch_api() + self.board = self.get_board() - def is_signed(self, version): - return any(firm['signed'] == True for firm in self.api['firmwares'] if firm['version'] == version) + def is_signed(self, version: str) -> bool: return any(firm['signed'] == True for firm in self.api['firmwares'] if firm['version'] == version) - def check_version(self, version): + def check_version(self, version) -> Optional[bool]: if not any(firm['version'] == version for firm in self.api['firmwares']): - sys.exit(f"[ERROR] '{version}' does not exist. Exiting.") + raise errors.NotFoundError(f"Version does not exist: {version}.") - def fetch_api(self, identifier=None): - self.api = requests.get(f'https://api.ipsw.me/v4/device/{identifier if identifier else self.device}?type=ipsw').json() + return True - def fetch_sha1(self, buildid): - return next(firm['sha1sum'] for firm in self.api['firmwares'] if firm['buildid'] == buildid) + def fetch_api(self) -> Optional[dict]: + try: + return requests.get(f'https://api.ipsw.me/v4/device/{self.device}').json() + except requests.exceptions.JSONDecodeError: + raise errors.NotFoundError(f"Device does not exist: {self.device}.") - def get_board(self): - boards = [board['boardconfig'] for board in self.api['boards']] + def fetch_board(self) -> Optional[str]: + boards = [board['boardconfig'].lower() for board in self.api['boards'] if board['boardconfig'].lower().endswith('ap')] if len(boards) == 1: - self.board = boards[0] - return - - print('There are multiple boardconfigs for your device! Please choose the correct boardconfig for your device:') - for board in range(len(boards)): - print(f" {board + 1}: {boards[board]}") - - board = input('Choice: ') - try: - board = int(board) - 1 - except: - sys.exit('[ERROR] Invalid input given. Exiting.') + return boards[0] + else: - if board not in range(len(boards)): - sys.exit('[ERROR] Invalid input given. Exiting.') + print('There are multiple board configs for your device! Please choose the correct boardc onfig for your device:') + for b in range(len(boards)): + print(f" {b + 1}: {boards[b]}") - self.board = boards[board] + board = input('Choice: ') + try: + board = int(board) - 1 + except: + raise TypeError(f'Invalid choice given: {board}.') + else: + if board not in range(len(boards)): + raise ValueError(f'Incorrect choice given: {board}.') - def partialzip_extract(self, buildid, component, path): + return boards[board] + + def fetch_sha1(self, buildid: str) -> str: + return next(firm['sha1sum'] for firm in self.api['firmwares'] if firm['buildid'] == buildid) + + def partialzip_extract(self, buildid: str, component: str, path: Path) -> Path: firm = next(firm for firm in self.api['firmwares'] if firm['buildid'] == buildid) + with remotezip.RemoteZip(firm['url']) as ipsw: ipsw.extract(component, path) - return firm['version'] + return path / Path(component) + + def partialzip_read(self, buildid: str, component: str) -> bytes: + try: + firm = next(firm for firm in self.api['firmwares'] if firm['buildid'] == buildid) + except StopIteration: + raise errors.NotFoundError(f'Buildid does not exist: {buildid}.') - def partialzip_read(self, buildid, component): - firm = next(firm for firm in self.api['firmwares'] if firm['buildid'] == buildid) with remotezip.RemoteZip(firm['url']) as ipsw: - return ipsw.read(component) + try: + return ipsw.read(component) + except KeyError: + raise errors.NotFoundError(f'Component does not exist: {component}.') From 3413a73b7c709bbe36a3555057e9770adf675067 Mon Sep 17 00:00:00 2001 From: m1stadev Date: Thu, 6 Jan 2022 22:40:48 -0600 Subject: [PATCH 02/56] [errors] Add errors --- utils/errors.py | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 utils/errors.py diff --git a/utils/errors.py b/utils/errors.py new file mode 100644 index 0000000..88d3be0 --- /dev/null +++ b/utils/errors.py @@ -0,0 +1,11 @@ +class InferiusError(Exception): + pass + +class NotFoundError(InferiusError): + pass + +class InvalidChoiceError(ValueError): + pass + +class InvalidTypeError(TypeError): + pass \ No newline at end of file From 66195f201645995f53e6f70c68b954ced324db06 Mon Sep 17 00:00:00 2001 From: m1stadev Date: Thu, 6 Jan 2022 22:41:51 -0600 Subject: [PATCH 03/56] [Manifest] Some changes - Manifest.version is now a tuple - fetch_buildid, fetch_supported_devices, and fetch_version are removed --- utils/manifest.py | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/utils/manifest.py b/utils/manifest.py index 7242dc8..de899bf 100644 --- a/utils/manifest.py +++ b/utils/manifest.py @@ -4,19 +4,13 @@ class Manifest: def __init__(self, manifest): self.manifest = plistlib.loads(manifest) - self.version = self.fetch_version() - self.buildid = self.fetch_buildid() - self.supported_devices = self.fetch_supported_devices() + self.version = (int(_) for _ in self.manifest['ProductVersion'].split('.')) + self.buildid = self.manifest['ProductBuildVersion'] + self.supported_devices = self.manifest['SupportedProductTypes'] - def fetch_buildid(self): return self.manifest['ProductBuildVersion'] - - def fetch_component_path(self, boardconfig, component): + def fetch_component_path(self, boardconfig: str, component: str) -> str: return next(identity['Manifest'][component]['Info']['Path'] for identity in self.manifest['BuildIdentities'] if identity['Info']['DeviceClass'].lower() == boardconfig.lower()) - def fetch_supported_devices(self): return self.manifest['SupportedProductTypes'] - - def fetch_version(self): return self.manifest['ProductVersion'] - class RestoreManifest: def __init__(self, manifest, boardconfig): self.platform = self.fetch_platform(boardconfig, plistlib.loads(manifest)) From 8d6356cc5286a01ebce831b6febdd445ca90e0f7 Mon Sep 17 00:00:00 2001 From: m1stadev Date: Thu, 6 Jan 2022 22:46:51 -0600 Subject: [PATCH 04/56] [API] partialzip_Read: Update type-hinting --- utils/api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/api.py b/utils/api.py index 61563cb..21dacaa 100644 --- a/utils/api.py +++ b/utils/api.py @@ -58,7 +58,7 @@ def partialzip_extract(self, buildid: str, component: str, path: Path) -> Path: return path / Path(component) - def partialzip_read(self, buildid: str, component: str) -> bytes: + def partialzip_read(self, buildid: str, component: str) -> Optional[bytes]: try: firm = next(firm for firm in self.api['firmwares'] if firm['buildid'] == buildid) except StopIteration: From 2a8c335fa017132dc51e121c2cf270fb2bdcb3c7 Mon Sep 17 00:00:00 2001 From: m1stadev Date: Thu, 6 Jan 2022 22:47:10 -0600 Subject: [PATCH 05/56] [API] partialzip_extract: Update - Update type-hinting - Handle more errors --- utils/api.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/utils/api.py b/utils/api.py index 21dacaa..f261c6c 100644 --- a/utils/api.py +++ b/utils/api.py @@ -50,11 +50,19 @@ def fetch_board(self) -> Optional[str]: def fetch_sha1(self, buildid: str) -> str: return next(firm['sha1sum'] for firm in self.api['firmwares'] if firm['buildid'] == buildid) - def partialzip_extract(self, buildid: str, component: str, path: Path) -> Path: - firm = next(firm for firm in self.api['firmwares'] if firm['buildid'] == buildid) + def partialzip_extract(self, buildid: str, component: str, path: Path) -> Optional[Path]: + try: + firm = next(firm for firm in self.api['firmwares'] if firm['buildid'] == buildid) + except StopIteration: + raise errors.NotFoundError(f'Buildid does not exist: {buildid}.') with remotezip.RemoteZip(firm['url']) as ipsw: - ipsw.extract(component, path) + try: + ipsw.extract(component, path) + except KeyError: + raise errors.NotFoundError(f'Component does not exist: {component}.') + except OSError: + raise OSError(f'Failed to partialzip component: {component}.') return path / Path(component) From 71a12e4f4d1eb5bdb0c4d4086baa81fff83e2d39 Mon Sep 17 00:00:00 2001 From: m1stadev Date: Thu, 6 Jan 2022 22:53:37 -0600 Subject: [PATCH 06/56] [Checks] Update check_bin - Type-hinting - Updated errors --- utils/dependencies.py | 11 ++++++----- utils/errors.py | 3 +++ 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/utils/dependencies.py b/utils/dependencies.py index 3c3b253..680c91f 100644 --- a/utils/dependencies.py +++ b/utils/dependencies.py @@ -1,6 +1,7 @@ +from utils import errors + import shutil import subprocess -import sys class Checks: @@ -10,17 +11,17 @@ def __init__(self): self.check_bin('irecovery') self.check_bin('img4tool') - def check_bin(self, binary): + def check_bin(self, binary: str) -> None: if shutil.which(binary) is None: - sys.exit(f"[ERROR] '{binary}' is not installed on your system. Exiting.") + raise errors.DependencyError(f'Binary not found on your PC: {binary}.') if binary == 'futurerestore': fr_ver = subprocess.run((binary), stdout=subprocess.PIPE, universal_newlines=True).stdout if '-m1sta' not in fr_ver.splitlines()[1]: - sys.exit(f"[ERROR] This futurerestore build cannot be used with Inferius. Exiting.") + raise errors.DependencyError('This FutureRestore build cannot be used with Inferius.') elif binary == 'irecovery': try: subprocess.check_call((binary, '-V'), stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) except subprocess.CalledProcessError: - sys.exit(f"[ERROR] Your irecovery version is too old. Exiting.") + raise errors.DependencyError('iRecovery build is too old to be used with Inferius.') diff --git a/utils/errors.py b/utils/errors.py index 88d3be0..8980d19 100644 --- a/utils/errors.py +++ b/utils/errors.py @@ -1,6 +1,9 @@ class InferiusError(Exception): pass +class DependencyError(InferiusError): + pass + class NotFoundError(InferiusError): pass From be217cd5af117f5c700ffbe7d5b7367ed8099762 Mon Sep 17 00:00:00 2001 From: m1stadev Date: Thu, 6 Jan 2022 23:06:18 -0600 Subject: [PATCH 07/56] [manifest] Add extra line between classes --- utils/manifest.py | 1 + 1 file changed, 1 insertion(+) diff --git a/utils/manifest.py b/utils/manifest.py index de899bf..03e61b0 100644 --- a/utils/manifest.py +++ b/utils/manifest.py @@ -11,6 +11,7 @@ def __init__(self, manifest): def fetch_component_path(self, boardconfig: str, component: str) -> str: return next(identity['Manifest'][component]['Info']['Path'] for identity in self.manifest['BuildIdentities'] if identity['Info']['DeviceClass'].lower() == boardconfig.lower()) + class RestoreManifest: def __init__(self, manifest, boardconfig): self.platform = self.fetch_platform(boardconfig, plistlib.loads(manifest)) From 7918458a44c5b0582c5bd1c8a770596c3e9f5691 Mon Sep 17 00:00:00 2001 From: m1stadev Date: Fri, 7 Jan 2022 15:30:36 -0600 Subject: [PATCH 08/56] [API] Import RemoteZip class from remotezip --- utils/api.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/utils/api.py b/utils/api.py index f261c6c..71aed66 100644 --- a/utils/api.py +++ b/utils/api.py @@ -1,8 +1,8 @@ from pathlib import Path +from remotezip import RemoteZip from typing import Optional from utils import errors -import remotezip import requests @@ -56,7 +56,7 @@ def partialzip_extract(self, buildid: str, component: str, path: Path) -> Option except StopIteration: raise errors.NotFoundError(f'Buildid does not exist: {buildid}.') - with remotezip.RemoteZip(firm['url']) as ipsw: + with RemoteZip(firm['url']) as ipsw: try: ipsw.extract(component, path) except KeyError: @@ -72,7 +72,7 @@ def partialzip_read(self, buildid: str, component: str) -> Optional[bytes]: except StopIteration: raise errors.NotFoundError(f'Buildid does not exist: {buildid}.') - with remotezip.RemoteZip(firm['url']) as ipsw: + with RemoteZip(firm['url']) as ipsw: try: return ipsw.read(component) except KeyError: From 575a3e66ec76296f7c67bb0ebcb3b8588168ffb6 Mon Sep 17 00:00:00 2001 From: m1stadev Date: Fri, 7 Jan 2022 15:37:35 -0600 Subject: [PATCH 09/56] [Bundle] Rewrite - Change return values of some functions - Type-hinting - Pathlib implementation - Code cleanup - Use custom errors --- utils/bundle.py | 89 +++++++++++++++++++++++++++---------------------- 1 file changed, 49 insertions(+), 40 deletions(-) diff --git a/utils/bundle.py b/utils/bundle.py index 052921a..084940e 100644 --- a/utils/bundle.py +++ b/utils/bundle.py @@ -1,15 +1,18 @@ +from pathlib import Path +from remotezip import RemoteZip, RemoteIOError +from typing import Optional +from utils.api import API +from utils import errors + import bsdiff4 -import io import json -import os import requests -import sys import zipfile class Bundle: - def apply_patches(self, ipsw): - with open(f'{self.bundle}/Info.json', 'r') as f: + def apply_patches(self, ipsw: Path) -> None: + with (self.bundle / Path('Info.json')).open('r') as f: bundle_data = json.load(f) for patches in bundle_data['patches']: @@ -19,64 +22,70 @@ def apply_patches(self, ipsw): continue for patch in bundle_data['patches'][patches]: - bsdiff4.file_patch_inplace(f"{ipsw}/{patch['file']}", f"{self.bundle}/{patch['patch']}") + bsdiff4.file_patch_inplace(ipsw / Path(patch['file']), self.bundle / Path(patch['patch'])) def check_update_support(self): - with open(f'{self.bundle}/Info.json', 'r') as f: + with (self.bundle / Path('Info.json')).open('r') as f: bundle_data = json.load(f) return bundle_data['update_support'] - def fetch_bundle(self, device, version, buildid, path): - bundle_name = f'{device}_{version}_{buildid}' - bundle = requests.get(f'https://github.com/m1stadev/inferius-ext/raw/master/bundles/{bundle_name}.bundle') - if bundle.status_code == 404: - sys.exit(f'[ERROR] A Firmware Bundle does not exist for {device}, iOS {version}. Exiting.') + def fetch_bundle(self, device: str, version: tuple, buildid: str, path: Path) -> Optional[Path]: + bundle_name = '_'.join(device, '.'.join(version), buildid) - output = f'{path}/{bundle_name}' - with zipfile.ZipFile(io.BytesIO(bundle.content), 'r') as f: - try: - f.extractall(output) - except OSError: - sys.exit('[ERROR] Ran out of storage while extracting Firmware Bundle. Exiting.') + bundle = path / Path(bundle_name) + bundle.mkdir() - self.bundle = output + try: + with RemoteZip(f'https://github.com/m1stadev/inferius-ext/raw/master/bundles/{bundle_name}.bundle') as rz: + try: + rz.extractall(bundle) + except OSError: + raise OSError(f'Failed to extract Firmware Bundle to: {bundle}.') - def fetch_ota_manifest(self, device, path): - manifest = requests.get(f'https://raw.githubusercontent.com/m1stadev/inferius-ext/master/manifests/BuildManifest_{device}.plist') - if manifest.status_code == 404: - sys.exit(f'[ERROR] An OTA manifest does not exist for {device}. Exiting.') + except RemoteIOError: + raise errors.NotFoundError(f'A bundle does not exist for device: {device}, OS: {version}.') - with open(path, 'wb') as f: + return bundle + + def fetch_ota_manifest(self, device: str, path: Path) -> Optional[Path]: + r = requests.get(f'https://github.com/m1stadev/inferius-ext/raw/master/manifests/BuildManifest_{device}.plist') + if r.status_code == 404: + raise errors.NotFoundError(f'An OTA manifest does not exist for device: {device}.') + + manifest = path / Path('otamanifest.plist') + with manifest.open('wb') as f: try: - f.write(manifest.content) + f.write(r.content) except OSError: - sys.exit('[ERROR] Ran out of storage while writing OTA manifest. Exiting.') + raise OSError(f'Failed to write OTA manifest to: {manifest}.') - def verify_bundle(self, bundle, tmpdir, api, buildid, boardconfig): - if not zipfile.is_zipfile(bundle): - return False + return manifest + + def verify_bundle(self, local_bundle: Path, path: Path, api: API, buildid: str, boardconfig: str) -> Optional[Path]: + if not zipfile.is_zipfile(local_bundle): + return try: - with zipfile.ZipFile(bundle, 'r') as f: + with zipfile.ZipFile(local_bundle, 'r') as f: try: bundle_data = json.loads(f.read('Info.json')) except: - return False + return if not any(firm['buildid'] == buildid for firm in api['firmwares']): - return False + return if not any(board.lower() == boardconfig.lower() for board in bundle_data['boards']): - return False + return except: - return False + return + + bundle = path / local_bundle.stem + bundle.mkdir() - bundle_path = f"{tmpdir}/{bundle.split('/')[-1].rsplit('.', 1)[0]}" - os.mkdir(bundle_path) - with zipfile.ZipFile(bundle) as f: - f.extractall(bundle_path) + with zipfile.ZipFile(local_bundle) as f: + f.extractall(bundle) - self.bundle = bundle_path - return True + return bundle From 1aacb5934b6d8924808e268921a7a49c293446e2 Mon Sep 17 00:00:00 2001 From: m1stadev Date: Mon, 10 Jan 2022 20:17:58 -0600 Subject: [PATCH 10/56] [API] partialzip_extract: Update --- utils/api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/api.py b/utils/api.py index 71aed66..681c604 100644 --- a/utils/api.py +++ b/utils/api.py @@ -64,7 +64,7 @@ def partialzip_extract(self, buildid: str, component: str, path: Path) -> Option except OSError: raise OSError(f'Failed to partialzip component: {component}.') - return path / Path(component) + return path / component def partialzip_read(self, buildid: str, component: str) -> Optional[bytes]: try: From 085cbe55877143aa3a67d3b1c24cb438c4a2d730 Mon Sep 17 00:00:00 2001 From: m1stadev Date: Mon, 10 Jan 2022 20:18:23 -0600 Subject: [PATCH 11/56] [bundle] No need to convert appending strings to paths --- utils/bundle.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/utils/bundle.py b/utils/bundle.py index 084940e..8ca8005 100644 --- a/utils/bundle.py +++ b/utils/bundle.py @@ -12,7 +12,7 @@ class Bundle: def apply_patches(self, ipsw: Path) -> None: - with (self.bundle / Path('Info.json')).open('r') as f: + with (self.bundle / 'Info.json').open('r') as f: bundle_data = json.load(f) for patches in bundle_data['patches']: @@ -22,10 +22,10 @@ def apply_patches(self, ipsw: Path) -> None: continue for patch in bundle_data['patches'][patches]: - bsdiff4.file_patch_inplace(ipsw / Path(patch['file']), self.bundle / Path(patch['patch'])) + bsdiff4.file_patch_inplace(ipsw / patch['file'], self.bundle / patch['patch']) def check_update_support(self): - with (self.bundle / Path('Info.json')).open('r') as f: + with (self.bundle / 'Info.json').open('r') as f: bundle_data = json.load(f) return bundle_data['update_support'] @@ -33,7 +33,7 @@ def check_update_support(self): def fetch_bundle(self, device: str, version: tuple, buildid: str, path: Path) -> Optional[Path]: bundle_name = '_'.join(device, '.'.join(version), buildid) - bundle = path / Path(bundle_name) + bundle = path / bundle_name bundle.mkdir() try: @@ -53,7 +53,7 @@ def fetch_ota_manifest(self, device: str, path: Path) -> Optional[Path]: if r.status_code == 404: raise errors.NotFoundError(f'An OTA manifest does not exist for device: {device}.') - manifest = path / Path('otamanifest.plist') + manifest = path / 'otamanifest.plist' with manifest.open('wb') as f: try: f.write(r.content) From d136561410013d691a3d055751e942a0faa5f2a2 Mon Sep 17 00:00:00 2001 From: m1stadev Date: Mon, 10 Jan 2022 20:18:43 -0600 Subject: [PATCH 12/56] [device] Rename Device.device to Device.identifier --- utils/device.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/utils/device.py b/utils/device.py index d60dfe5..12354c9 100644 --- a/utils/device.py +++ b/utils/device.py @@ -7,7 +7,7 @@ class Device: def __init__(self, identifier): - self.device = identifier + self.identifier = identifier self.baseband = self.check_baseband() self.backend = self.get_backend() self.platform = self.fetch_platform() @@ -16,10 +16,10 @@ def __init__(self, identifier): self.ecid = self.fetch_ecid() def check_baseband(self): - if self.device.startswith('iPhone'): + if self.identifier.startswith('iPhone'): return True - return self.device in ( # All (current) 64-bit cellular iPads vulerable to checkm8. + return self.identifier in ( # All (current) 64-bit cellular iPads vulerable to checkm8. 'iPad4,2', 'iPad4,3', 'iPad4,5', From ce171abb2e328ff8e7ce6ce562297b6e31b0bcfc Mon Sep 17 00:00:00 2001 From: m1stadev Date: Mon, 10 Jan 2022 20:19:26 -0600 Subject: [PATCH 13/56] [errors] Add InferiusError as a subclass to all exceptions --- utils/errors.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/utils/errors.py b/utils/errors.py index 8980d19..1e64282 100644 --- a/utils/errors.py +++ b/utils/errors.py @@ -7,8 +7,8 @@ class DependencyError(InferiusError): class NotFoundError(InferiusError): pass -class InvalidChoiceError(ValueError): +class InvalidChoiceError(InferiusError, ValueError): pass -class InvalidTypeError(TypeError): +class InvalidTypeError(InferiusError, TypeError): pass \ No newline at end of file From e8a05be63dd462a66bc3e301ec78814dad2f0c85 Mon Sep 17 00:00:00 2001 From: m1stadev Date: Mon, 10 Jan 2022 22:20:10 -0600 Subject: [PATCH 14/56] [API] Fix API.board --- utils/api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/api.py b/utils/api.py index 681c604..a6b769c 100644 --- a/utils/api.py +++ b/utils/api.py @@ -10,7 +10,7 @@ class API: def __init__(self, identifier: str) -> None: self.device = identifier self.api = self.fetch_api() - self.board = self.get_board() + self.board = self.fetch_board() def is_signed(self, version: str) -> bool: return any(firm['signed'] == True for firm in self.api['firmwares'] if firm['version'] == version) From 88bf0033289e1aae2e16154369751fc9afb88e88 Mon Sep 17 00:00:00 2001 From: m1stadev Date: Mon, 10 Jan 2022 22:27:13 -0600 Subject: [PATCH 15/56] [API] Cleanup - More error handling - Add errors.IOError - Code cleanup --- utils/api.py | 31 +++++++++++++++---------------- utils/errors.py | 10 +++++++++- 2 files changed, 24 insertions(+), 17 deletions(-) diff --git a/utils/api.py b/utils/api.py index a6b769c..d34fa46 100644 --- a/utils/api.py +++ b/utils/api.py @@ -14,12 +14,6 @@ def __init__(self, identifier: str) -> None: def is_signed(self, version: str) -> bool: return any(firm['signed'] == True for firm in self.api['firmwares'] if firm['version'] == version) - def check_version(self, version) -> Optional[bool]: - if not any(firm['version'] == version for firm in self.api['firmwares']): - raise errors.NotFoundError(f"Version does not exist: {version}.") - - return True - def fetch_api(self) -> Optional[dict]: try: return requests.get(f'https://api.ipsw.me/v4/device/{self.device}').json() @@ -32,7 +26,7 @@ def fetch_board(self) -> Optional[str]: return boards[0] else: - print('There are multiple board configs for your device! Please choose the correct boardc onfig for your device:') + print('There are multiple board configs for your device! Please choose the correct board config for your device:') for b in range(len(boards)): print(f" {b + 1}: {boards[b]}") @@ -48,32 +42,37 @@ def fetch_board(self) -> Optional[str]: return boards[board] def fetch_sha1(self, buildid: str) -> str: - return next(firm['sha1sum'] for firm in self.api['firmwares'] if firm['buildid'] == buildid) + try: + sha1 = next(firm['sha1sum'] for firm in self.api['firmwares'] if firm['buildid'] == buildid) + except StopIteration: + raise errors.NotFoundError(f'Firmware does not exist with buildid: {buildid}.') + + return sha1 def partialzip_extract(self, buildid: str, component: str, path: Path) -> Optional[Path]: try: - firm = next(firm for firm in self.api['firmwares'] if firm['buildid'] == buildid) + url = next(firm['url'] for firm in self.api['firmwares'] if firm['buildid'] == buildid) except StopIteration: - raise errors.NotFoundError(f'Buildid does not exist: {buildid}.') + raise errors.NotFoundError(f'Firmware does not exist with buildid: {buildid}.') - with RemoteZip(firm['url']) as ipsw: + with RemoteZip(url) as ipsw: try: ipsw.extract(component, path) except KeyError: raise errors.NotFoundError(f'Component does not exist: {component}.') except OSError: - raise OSError(f'Failed to partialzip component: {component}.') + raise errors.IOError(f'Failed to partialzip component: {component}.') return path / component def partialzip_read(self, buildid: str, component: str) -> Optional[bytes]: try: - firm = next(firm for firm in self.api['firmwares'] if firm['buildid'] == buildid) + url = next(firm['url'] for firm in self.api['firmwares'] if firm['buildid'] == buildid) except StopIteration: - raise errors.NotFoundError(f'Buildid does not exist: {buildid}.') + raise errors.NotFoundError(f'Firmware does not exist with buildid: {buildid}.') - with RemoteZip(firm['url']) as ipsw: + with RemoteZip(url) as ipsw: try: return ipsw.read(component) except KeyError: - raise errors.NotFoundError(f'Component does not exist: {component}.') + raise errors.NotFoundError(f'File does not exist in IPSW: {component}.') diff --git a/utils/errors.py b/utils/errors.py index 1e64282..9b1f8e3 100644 --- a/utils/errors.py +++ b/utils/errors.py @@ -1,14 +1,22 @@ class InferiusError(Exception): pass + class DependencyError(InferiusError): pass + class NotFoundError(InferiusError): pass + class InvalidChoiceError(InferiusError, ValueError): pass + class InvalidTypeError(InferiusError, TypeError): - pass \ No newline at end of file + pass + + +class IOError(InferiusError, OSError): + pass From 0e82d8d8c6c2bf7ef30bcc1a41a9b85b27ee2f9e Mon Sep 17 00:00:00 2001 From: m1stadev Date: Mon, 10 Jan 2022 22:42:24 -0600 Subject: [PATCH 16/56] [Bundle] Update - Code cleanup - Type-hinting - Use custom errors --- utils/bundle.py | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/utils/bundle.py b/utils/bundle.py index 8ca8005..fa1650d 100644 --- a/utils/bundle.py +++ b/utils/bundle.py @@ -17,14 +17,18 @@ def apply_patches(self, ipsw: Path) -> None: for patches in bundle_data['patches']: if patches != 'required': - apply_patch = input(f"[NOTE] Would you like to apply '{patches}' patch to your custom IPSW? [Y\\N]: ").lower() - if (apply_patch not in ('y', 'n')) or (apply_patch == 'n'): + apply_patch = input(f"[NOTE] Would you like to apply '{patches}' patch to your custom IPSW? [Y/N]: ").lower() + if apply_patch == 'n': + continue + + elif apply_patch not in ('y', 'n'): + print('[WARN] Invalid input, skipping patch...') continue for patch in bundle_data['patches'][patches]: bsdiff4.file_patch_inplace(ipsw / patch['file'], self.bundle / patch['patch']) - def check_update_support(self): + def check_update_support(self) -> bool: with (self.bundle / 'Info.json').open('r') as f: bundle_data = json.load(f) @@ -41,10 +45,10 @@ def fetch_bundle(self, device: str, version: tuple, buildid: str, path: Path) -> try: rz.extractall(bundle) except OSError: - raise OSError(f'Failed to extract Firmware Bundle to: {bundle}.') + raise errors.IOError(f'Failed to download firmware bundle to: {bundle}.') except RemoteIOError: - raise errors.NotFoundError(f'A bundle does not exist for device: {device}, OS: {version}.') + raise errors.NotFoundError(f'A firmware bundle does not exist for device: {device}, OS: {version}.') return bundle @@ -58,26 +62,22 @@ def fetch_ota_manifest(self, device: str, path: Path) -> Optional[Path]: try: f.write(r.content) except OSError: - raise OSError(f'Failed to write OTA manifest to: {manifest}.') + raise errors.IOError(f'Failed to write OTA manifest to: {manifest}.') return manifest - def verify_bundle(self, local_bundle: Path, path: Path, api: API, buildid: str, boardconfig: str) -> Optional[Path]: + def verify_bundle(self, local_bundle: Path, path: Path, api: dict, buildid: str, boardconfig: str) -> Optional[Path]: if not zipfile.is_zipfile(local_bundle): return + if not any(_['buildid'] == buildid for _ in api['firmwares']): + return + try: with zipfile.ZipFile(local_bundle, 'r') as f: - try: - bundle_data = json.loads(f.read('Info.json')) - except: - return - - if not any(firm['buildid'] == buildid for firm in api['firmwares']): - return + bundle_data = json.loads(f.read('Info.json')) - if not any(board.lower() == boardconfig.lower() for board in bundle_data['boards']): - return + next(_.lower() == boardconfig.lower() for _ in bundle_data['boards']) except: return From 80735a77db34d471a29313e4a6f471ffadbdc977 Mon Sep 17 00:00:00 2001 From: m1stadev Date: Mon, 10 Jan 2022 22:43:32 -0600 Subject: [PATCH 17/56] [depos] check_bin: Change error message --- utils/dependencies.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/dependencies.py b/utils/dependencies.py index 680c91f..9740559 100644 --- a/utils/dependencies.py +++ b/utils/dependencies.py @@ -24,4 +24,4 @@ def check_bin(self, binary: str) -> None: try: subprocess.check_call((binary, '-V'), stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) except subprocess.CalledProcessError: - raise errors.DependencyError('iRecovery build is too old to be used with Inferius.') + raise errors.DependencyError('This iRecovery build is too old to be used with Inferius.') From c32d3955b1650f2c9585ef681771bc9d5f611453 Mon Sep 17 00:00:00 2001 From: m1stadev Date: Tue, 11 Jan 2022 23:16:09 -0600 Subject: [PATCH 18/56] [device] Rewrite - Remove irecovery dependency for nonce/boardconfig - Move to pathlib - Use custom errors - Combine functions that retrieve data using pyusb into one function for geting all data --- utils/device.py | 136 +++++++++---------- utils/devices.json | 320 +++++++++++++++++++++++++++++++++++++++++++++ utils/errors.py | 10 +- 3 files changed, 388 insertions(+), 78 deletions(-) create mode 100644 utils/devices.json diff --git a/utils/device.py b/utils/device.py index 12354c9..b575b15 100644 --- a/utils/device.py +++ b/utils/device.py @@ -1,73 +1,52 @@ -import glob -import os -import subprocess -import sys -import usb, usb.backend.libusb1 +from pathlib import Path +from typing import Optional +from utils import errors + +import json +import usb, usb.backend.libusb1, usb.util class Device: def __init__(self, identifier): self.identifier = identifier - self.baseband = self.check_baseband() - self.backend = self.get_backend() - self.platform = self.fetch_platform() self.board = self.fetch_boardconfig() - self.apnonce = self.fetch_apnonce() - self.ecid = self.fetch_ecid() + self.data = self.get_dfu_data() - def check_baseband(self): + @property + def baseband(self) -> bool: if self.identifier.startswith('iPhone'): return True - return self.identifier in ( # All (current) 64-bit cellular iPads vulerable to checkm8. - 'iPad4,2', - 'iPad4,3', - 'iPad4,5', - 'iPad4,6', - 'iPad4,8', - 'iPad4,9', - 'iPad5,2', - 'iPad5,4', - 'iPad6,8', - 'iPad6,4', - 'iPad7,2', - 'iPad7,4', - 'iPad8,3', - 'iPad8,4', - 'iPad8,7', - 'iPad8,8', - 'iPad8,10', - 'iPad8,12', - 'iPad11,2', - 'iPad11,4', - 'iPad13,2', - ) - - def check_pwndfu(self): - device = usb.core.find(idVendor=0x5AC, idProduct=0x1227, backend=self.backend) - if device is None: - sys.exit('[ERROR] Device in DFU mode not found. Exiting.') - - if 'PWND:' not in device.serial_number: - sys.exit('[ERROR] Attempting to restore a device not in Pwned DFU mode. Exiting.') - - def fetch_apnonce(self): - irecv = subprocess.check_output(('irecovery', '-q'), universal_newlines=True) - line = next(l for l in irecv.splitlines() if 'NONC:' in l) - return line.split(' ')[1] - - def get_backend(self): # Attempt to find a libusb 1.0 library to use as pyusb's backend, exit if one isn't found. - directories = ('/usr/lib', '/opt/procursus/lib', '/usr/local/lib') # Common library directories to search + else: + return self.identifier in ( # All (current) 64-bit cellular iPads vulerable to checkm8. + 'iPad4,2', + 'iPad4,3', + 'iPad4,5', + 'iPad4,6', + 'iPad4,8', + 'iPad4,9', + 'iPad5,2', + 'iPad5,4', + 'iPad6,4', + 'iPad6,8', + 'iPad6,12', + 'iPad7,2', + 'iPad7,4', + 'iPad7,6', + 'iPad7,12' + ) + + def get_backend(self) -> Optional[usb.backend.libusb1._LibUSB]: # Attempt to find a libusb 1.0 library to use as pyusb's backend, exit if one isn't found. + directories = ('/usr/local/lib', '/opt/procursus/lib', '/usr/lib') # Common library directories to search libusb1 = None for libdir in directories: - for file in glob.glob(f'{libdir}/**', recursive=True): - if os.path.isdir(file) or (not any(ext in file for ext in ('so', 'dylib'))): + for file in Path(libdir).glob('libusb-1.0.0.*'): + if file.is_dir() or (file.suffix not in ('.so', '.dylib')): continue - if 'libusb-1.0' in file: - libusb1 = file - break + libusb1 = file + break else: continue @@ -75,33 +54,36 @@ def get_backend(self): # Attempt to find a libusb 1.0 library to use as pyusb's break if libusb1 is None: - sys.exit('[ERROR] libusb is not installed. Install libusb. Exiting.') + raise errors.DependencyError('libusb not found on your PC.') - return usb.backend.libusb1.get_backend(find_library=lambda x:libusb1) + return usb.backend.libusb1.get_backend(find_library=lambda _:libusb1) - def fetch_boardconfig(self): - irecv = subprocess.run(('irecovery', '-qv'), stdout=subprocess.DEVNULL, stderr=subprocess.PIPE, universal_newlines=True).stderr - line = next(l for l in irecv.splitlines() if 'Connected to' in l) - return line.split(', ')[1].replace('model ', '') - - def fetch_ecid(self): - device = usb.core.find(idVendor=0x5AC, idProduct=0x1227, backend=self.backend) + def get_dfu_data(self) -> Optional[dict]: + device = usb.core.find(idVendor=0x5AC, idProduct=0x1227, backend=self.get_backend()) if device is None: - sys.exit('[ERROR] Device in DFU mode not found. Exiting.') + raise errors.DeviceError('Device in DFU mode not found.') - ecid = device.serial_number.split(' ')[5].split(':')[1] + device_data = dict() + for item in device.serial_number.split(): + device_data[item.split(':')[0]] = item.split(':')[1] - for x in ecid: - if x == '0': - ecid = ecid[1:] - else: - break + device_data['ECID'] = hex(int(device_data['ECID'], 16)) - return ecid + for i in ('CPID', 'CPRV', 'BDID', 'CPFM', 'SCEP', 'IBFL'): + device_data[i] = int(device_data[i], 16) - def fetch_platform(self): - device = usb.core.find(idVendor=0x5AC, idProduct=0x1227, backend=self.backend) - if device is None: - sys.exit('[ERROR] Device in DFU mode not found. Exiting.') + for item in usb.util.get_string(device, device.bDescriptorType).split(): + device_data[item.split(':')[0]] = item.split(':')[1] + + return device_data + + def fetch_board(self) -> Optional[str]: + device_info = Path('utils/devices.json') + try: + with device_info.open('r') as f: + data = json.load(f) + + except FileNotFoundError: + raise errors.CorruptError(f'File missing from Inferius: {device_info}') - return int(device.serial_number.split(' ')[0].split(':')[1]) + return next(_['boardconfig'] for _ in data if _['identifier'] == self.identifier and _['bdid'] == self.data['BDID'] and _['cpid'] == self.data['CPID']) diff --git a/utils/devices.json b/utils/devices.json new file mode 100644 index 0000000..3a19c43 --- /dev/null +++ b/utils/devices.json @@ -0,0 +1,320 @@ +[ + { + "identifier": "AppleTV5,3", + "boardconfig": "j42dap", + "bdid": 52, + "cpid": 28672 + }, + { + "identifier": "AppleTV6,2", + "boardconfig": "j105aap", + "bdid": 2, + "cpid": 32785 + }, + { + "identifier": "iPad4,1", + "boardconfig": "j71ap", + "bdid": 16, + "cpid": 35168 + }, + { + "identifier": "iPad4,2", + "boardconfig": "j72ap", + "bdid": 18, + "cpid": 35168 + }, + { + "identifier": "iPad4,3", + "boardconfig": "j73ap", + "bdid": 20, + "cpid": 35168 + }, + { + "identifier": "iPad4,4", + "boardconfig": "j85ap", + "bdid": 10, + "cpid": 35168 + }, + { + "identifier": "iPad4,5", + "boardconfig": "j86ap", + "bdid": 12, + "cpid": 35168 + }, + { + "identifier": "iPad4,6", + "boardconfig": "j87ap", + "bdid": 14, + "cpid": 35168 + }, + { + "identifier": "iPad4,7", + "boardconfig": "j85map", + "bdid": 50, + "cpid": 35168 + }, + { + "identifier": "iPad4,8", + "boardconfig": "j86map", + "bdid": 52, + "cpid": 35168 + }, + { + "identifier": "iPad4,9", + "boardconfig": "j87map", + "bdid": 54, + "cpid": 35168 + }, + { + "identifier": "iPad5,1", + "boardconfig": "j96ap", + "bdid": 8, + "cpid": 28672 + }, + { + "identifier": "iPad5,2", + "boardconfig": "j97ap", + "bdid": 10, + "cpid": 28672 + }, + { + "identifier": "iPad5,3", + "boardconfig": "j81ap", + "bdid": 6, + "cpid": 28673 + }, + { + "identifier": "iPad5,4", + "boardconfig": "j82ap", + "bdid": 2, + "cpid": 28673 + }, + { + "identifier": "iPad6,11", + "boardconfig": "j71sap", + "bdid": 16, + "cpid": 32768 + }, + { + "identifier": "iPad6,11", + "boardconfig": "j71tap", + "bdid": 16, + "cpid": 32771 + }, + { + "identifier": "iPad6,12", + "boardconfig": "j72sap", + "bdid": 18, + "cpid": 32768 + }, + { + "identifier": "iPad6,12", + "boardconfig": "j72tap", + "bdid": 18, + "cpid": 32771 + }, + { + "identifier": "iPad6,3", + "boardconfig": "j127ap", + "bdid": 8, + "cpid": 32769 + }, + { + "identifier": "iPad6,4", + "boardconfig": "j128ap", + "bdid": 10, + "cpid": 32769 + }, + { + "identifier": "iPad6,7", + "boardconfig": "j98aap", + "bdid": 16, + "cpid": 32769 + }, + { + "identifier": "iPad6,8", + "boardconfig": "j99aap", + "bdid": 18, + "cpid": 32769 + }, + { + "identifier": "iPad7,1", + "boardconfig": "j120ap", + "bdid": 12, + "cpid": 32785 + }, + { + "identifier": "iPad7,11", + "boardconfig": "j171ap", + "bdid": 28, + "cpid": 32784 + }, + { + "identifier": "iPad7,12", + "boardconfig": "j172ap", + "bdid": 30, + "cpid": 32784 + }, + { + "identifier": "iPad7,2", + "boardconfig": "j121ap", + "bdid": 14, + "cpid": 32785 + }, + { + "identifier": "iPad7,3", + "boardconfig": "j207ap", + "bdid": 4, + "cpid": 32785 + }, + { + "identifier": "iPad7,4", + "boardconfig": "j208ap", + "bdid": 6, + "cpid": 32785 + }, + { + "identifier": "iPad7,5", + "boardconfig": "j71bap", + "bdid": 24, + "cpid": 32784 + }, + { + "identifier": "iPad7,6", + "boardconfig": "j72bap", + "bdid": 26, + "cpid": 32784 + }, + { + "identifier": "iPhone10,1", + "boardconfig": "d20ap", + "bdid": 2, + "cpid": 32789 + }, + { + "identifier": "iPhone10,2", + "boardconfig": "d21ap", + "bdid": 4, + "cpid": 32789 + }, + { + "identifier": "iPhone10,3", + "boardconfig": "d22ap", + "bdid": 6, + "cpid": 32789 + }, + { + "identifier": "iPhone10,4", + "boardconfig": "d201ap", + "bdid": 10, + "cpid": 32789 + }, + { + "identifier": "iPhone10,5", + "boardconfig": "d211ap", + "bdid": 12, + "cpid": 32789 + }, + { + "identifier": "iPhone10,6", + "boardconfig": "d221ap", + "bdid": 14, + "cpid": 32789 + }, + { + "identifier": "iPhone6,1", + "boardconfig": "n51ap", + "bdid": 0, + "cpid": 35168 + }, + { + "identifier": "iPhone6,2", + "boardconfig": "n53ap", + "bdid": 2, + "cpid": 35168 + }, + { + "identifier": "iPhone7,1", + "boardconfig": "n56ap", + "bdid": 4, + "cpid": 28672 + }, + { + "identifier": "iPhone7,2", + "boardconfig": "n61ap", + "bdid": 6, + "cpid": 28672 + }, + { + "identifier": "iPhone8,1", + "boardconfig": "n71ap", + "bdid": 4, + "cpid": 32768 + }, + { + "identifier": "iPhone8,1", + "boardconfig": "n71map", + "bdid": 4, + "cpid": 32771 + }, + { + "identifier": "iPhone8,2", + "boardconfig": "n66ap", + "bdid": 6, + "cpid": 32768 + }, + { + "identifier": "iPhone8,2", + "boardconfig": "n66map", + "bdid": 6, + "cpid": 32771 + }, + { + "identifier": "iPhone8,4", + "boardconfig": "n69ap", + "bdid": 2, + "cpid": 32771 + }, + { + "identifier": "iPhone8,4", + "boardconfig": "n69uap", + "bdid": 2, + "cpid": 32768 + }, + { + "identifier": "iPhone9,1", + "boardconfig": "d10ap", + "bdid": 8, + "cpid": 32784 + }, + { + "identifier": "iPhone9,2", + "boardconfig": "d11ap", + "bdid": 10, + "cpid": 32784 + }, + { + "identifier": "iPhone9,3", + "boardconfig": "d101ap", + "bdid": 12, + "cpid": 32784 + }, + { + "identifier": "iPhone9,4", + "boardconfig": "d111ap", + "bdid": 14, + "cpid": 32784 + }, + { + "identifier": "iPod7,1", + "boardconfig": "n102ap", + "bdid": 16, + "cpid": 28672 + }, + { + "identifier": "iPod9,1", + "boardconfig": "n112ap", + "bdid": 22, + "cpid": 32784 + } +] \ No newline at end of file diff --git a/utils/errors.py b/utils/errors.py index 9b1f8e3..7c6e37a 100644 --- a/utils/errors.py +++ b/utils/errors.py @@ -18,5 +18,13 @@ class InvalidTypeError(InferiusError, TypeError): pass -class IOError(InferiusError, OSError): +class InferiusIOError(InferiusError, OSError): + pass + + +class DeviceError(InferiusError): + pass + + +class CorruptError(InferiusError): pass From ced4d77572427703dcbf6416605cd065d781d23f Mon Sep 17 00:00:00 2001 From: m1stadev Date: Tue, 11 Jan 2022 23:39:03 -0600 Subject: [PATCH 19/56] Update formatting --- utils/api.py | 53 +++++++++++++++++++++++++++-------- utils/bundle.py | 36 ++++++++++++++++++------ utils/dependencies.py | 16 ++++++++--- utils/device.py | 65 ++++++++++++++++++++++++++++--------------- utils/errors.py | 6 ++-- 5 files changed, 126 insertions(+), 50 deletions(-) diff --git a/utils/api.py b/utils/api.py index d34fa46..071ab84 100644 --- a/utils/api.py +++ b/utils/api.py @@ -12,7 +12,12 @@ def __init__(self, identifier: str) -> None: self.api = self.fetch_api() self.board = self.fetch_board() - def is_signed(self, version: str) -> bool: return any(firm['signed'] == True for firm in self.api['firmwares'] if firm['version'] == version) + def is_signed(self, version: str) -> bool: + return any( + firm['signed'] == True + for firm in self.api['firmwares'] + if firm['version'] == version + ) def fetch_api(self) -> Optional[dict]: try: @@ -21,12 +26,18 @@ def fetch_api(self) -> Optional[dict]: raise errors.NotFoundError(f"Device does not exist: {self.device}.") def fetch_board(self) -> Optional[str]: - boards = [board['boardconfig'].lower() for board in self.api['boards'] if board['boardconfig'].lower().endswith('ap')] + boards = [ + board['boardconfig'].lower() + for board in self.api['boards'] + if board['boardconfig'].lower().endswith('ap') + ] if len(boards) == 1: return boards[0] else: - print('There are multiple board configs for your device! Please choose the correct board config for your device:') + print( + 'There are multiple board configs for your device! Please choose the correct board config for your device:' + ) for b in range(len(boards)): print(f" {b + 1}: {boards[b]}") @@ -43,17 +54,31 @@ def fetch_board(self) -> Optional[str]: def fetch_sha1(self, buildid: str) -> str: try: - sha1 = next(firm['sha1sum'] for firm in self.api['firmwares'] if firm['buildid'] == buildid) + sha1 = next( + firm['sha1sum'] + for firm in self.api['firmwares'] + if firm['buildid'] == buildid + ) except StopIteration: - raise errors.NotFoundError(f'Firmware does not exist with buildid: {buildid}.') + raise errors.NotFoundError( + f'Firmware does not exist with buildid: {buildid}.' + ) return sha1 - def partialzip_extract(self, buildid: str, component: str, path: Path) -> Optional[Path]: + def partialzip_extract( + self, buildid: str, component: str, path: Path + ) -> Optional[Path]: try: - url = next(firm['url'] for firm in self.api['firmwares'] if firm['buildid'] == buildid) + url = next( + firm['url'] + for firm in self.api['firmwares'] + if firm['buildid'] == buildid + ) except StopIteration: - raise errors.NotFoundError(f'Firmware does not exist with buildid: {buildid}.') + raise errors.NotFoundError( + f'Firmware does not exist with buildid: {buildid}.' + ) with RemoteZip(url) as ipsw: try: @@ -61,15 +86,21 @@ def partialzip_extract(self, buildid: str, component: str, path: Path) -> Option except KeyError: raise errors.NotFoundError(f'Component does not exist: {component}.') except OSError: - raise errors.IOError(f'Failed to partialzip component: {component}.') + raise IOError(f'Failed to partialzip component: {component}.') return path / component def partialzip_read(self, buildid: str, component: str) -> Optional[bytes]: try: - url = next(firm['url'] for firm in self.api['firmwares'] if firm['buildid'] == buildid) + url = next( + firm['url'] + for firm in self.api['firmwares'] + if firm['buildid'] == buildid + ) except StopIteration: - raise errors.NotFoundError(f'Firmware does not exist with buildid: {buildid}.') + raise errors.NotFoundError( + f'Firmware does not exist with buildid: {buildid}.' + ) with RemoteZip(url) as ipsw: try: diff --git a/utils/bundle.py b/utils/bundle.py index fa1650d..8e02d7c 100644 --- a/utils/bundle.py +++ b/utils/bundle.py @@ -17,7 +17,9 @@ def apply_patches(self, ipsw: Path) -> None: for patches in bundle_data['patches']: if patches != 'required': - apply_patch = input(f"[NOTE] Would you like to apply '{patches}' patch to your custom IPSW? [Y/N]: ").lower() + apply_patch = input( + f"[NOTE] Would you like to apply '{patches}' patch to your custom IPSW? [Y/N]: " + ).lower() if apply_patch == 'n': continue @@ -26,7 +28,9 @@ def apply_patches(self, ipsw: Path) -> None: continue for patch in bundle_data['patches'][patches]: - bsdiff4.file_patch_inplace(ipsw / patch['file'], self.bundle / patch['patch']) + bsdiff4.file_patch_inplace( + ipsw / patch['file'], self.bundle / patch['patch'] + ) def check_update_support(self) -> bool: with (self.bundle / 'Info.json').open('r') as f: @@ -34,28 +38,40 @@ def check_update_support(self) -> bool: return bundle_data['update_support'] - def fetch_bundle(self, device: str, version: tuple, buildid: str, path: Path) -> Optional[Path]: + def fetch_bundle( + self, device: str, version: tuple, buildid: str, path: Path + ) -> Optional[Path]: bundle_name = '_'.join(device, '.'.join(version), buildid) bundle = path / bundle_name bundle.mkdir() try: - with RemoteZip(f'https://github.com/m1stadev/inferius-ext/raw/master/bundles/{bundle_name}.bundle') as rz: + with RemoteZip( + f'https://github.com/m1stadev/inferius-ext/raw/master/bundles/{bundle_name}.bundle' + ) as rz: try: rz.extractall(bundle) except OSError: - raise errors.IOError(f'Failed to download firmware bundle to: {bundle}.') + raise errors.IOError( + f'Failed to download firmware bundle to: {bundle}.' + ) except RemoteIOError: - raise errors.NotFoundError(f'A firmware bundle does not exist for device: {device}, OS: {version}.') + raise errors.NotFoundError( + f'A firmware bundle does not exist for device: {device}, OS: {version}.' + ) return bundle def fetch_ota_manifest(self, device: str, path: Path) -> Optional[Path]: - r = requests.get(f'https://github.com/m1stadev/inferius-ext/raw/master/manifests/BuildManifest_{device}.plist') + r = requests.get( + f'https://github.com/m1stadev/inferius-ext/raw/master/manifests/BuildManifest_{device}.plist' + ) if r.status_code == 404: - raise errors.NotFoundError(f'An OTA manifest does not exist for device: {device}.') + raise errors.NotFoundError( + f'An OTA manifest does not exist for device: {device}.' + ) manifest = path / 'otamanifest.plist' with manifest.open('wb') as f: @@ -66,7 +82,9 @@ def fetch_ota_manifest(self, device: str, path: Path) -> Optional[Path]: return manifest - def verify_bundle(self, local_bundle: Path, path: Path, api: dict, buildid: str, boardconfig: str) -> Optional[Path]: + def verify_bundle( + self, local_bundle: Path, path: Path, api: dict, buildid: str, boardconfig: str + ) -> Optional[Path]: if not zipfile.is_zipfile(local_bundle): return diff --git a/utils/dependencies.py b/utils/dependencies.py index 9740559..6ce7a7f 100644 --- a/utils/dependencies.py +++ b/utils/dependencies.py @@ -16,12 +16,20 @@ def check_bin(self, binary: str) -> None: raise errors.DependencyError(f'Binary not found on your PC: {binary}.') if binary == 'futurerestore': - fr_ver = subprocess.run((binary), stdout=subprocess.PIPE, universal_newlines=True).stdout + fr_ver = subprocess.run( + (binary), stdout=subprocess.PIPE, universal_newlines=True + ).stdout if '-m1sta' not in fr_ver.splitlines()[1]: - raise errors.DependencyError('This FutureRestore build cannot be used with Inferius.') + raise errors.DependencyError( + 'This FutureRestore build cannot be used with Inferius.' + ) elif binary == 'irecovery': try: - subprocess.check_call((binary, '-V'), stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) + subprocess.check_call( + (binary, '-V'), stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL + ) except subprocess.CalledProcessError: - raise errors.DependencyError('This iRecovery build is too old to be used with Inferius.') + raise errors.DependencyError( + 'This iRecovery build is too old to be used with Inferius.' + ) diff --git a/utils/device.py b/utils/device.py index b575b15..8dac8ba 100644 --- a/utils/device.py +++ b/utils/device.py @@ -18,26 +18,37 @@ def baseband(self) -> bool: return True else: - return self.identifier in ( # All (current) 64-bit cellular iPads vulerable to checkm8. - 'iPad4,2', - 'iPad4,3', - 'iPad4,5', - 'iPad4,6', - 'iPad4,8', - 'iPad4,9', - 'iPad5,2', - 'iPad5,4', - 'iPad6,4', - 'iPad6,8', - 'iPad6,12', - 'iPad7,2', - 'iPad7,4', - 'iPad7,6', - 'iPad7,12' + return ( + self.identifier + in ( # All (current) 64-bit cellular iPads vulerable to checkm8. + 'iPad4,2', + 'iPad4,3', + 'iPad4,5', + 'iPad4,6', + 'iPad4,8', + 'iPad4,9', + 'iPad5,2', + 'iPad5,4', + 'iPad6,4', + 'iPad6,8', + 'iPad6,12', + 'iPad7,2', + 'iPad7,4', + 'iPad7,6', + 'iPad7,12', ) - - def get_backend(self) -> Optional[usb.backend.libusb1._LibUSB]: # Attempt to find a libusb 1.0 library to use as pyusb's backend, exit if one isn't found. - directories = ('/usr/local/lib', '/opt/procursus/lib', '/usr/lib') # Common library directories to search + ) + + def get_backend( + self, + ) -> Optional[ + usb.backend.libusb1._LibUSB + ]: # Attempt to find a libusb 1.0 library to use as pyusb's backend, exit if one isn't found. + directories = ( + '/usr/local/lib', + '/opt/procursus/lib', + '/usr/lib', + ) # Common library directories to search libusb1 = None for libdir in directories: @@ -56,10 +67,12 @@ def get_backend(self) -> Optional[usb.backend.libusb1._LibUSB]: # Attempt to fin if libusb1 is None: raise errors.DependencyError('libusb not found on your PC.') - return usb.backend.libusb1.get_backend(find_library=lambda _:libusb1) + return usb.backend.libusb1.get_backend(find_library=lambda _: libusb1) def get_dfu_data(self) -> Optional[dict]: - device = usb.core.find(idVendor=0x5AC, idProduct=0x1227, backend=self.get_backend()) + device = usb.core.find( + idVendor=0x5AC, idProduct=0x1227, backend=self.get_backend() + ) if device is None: raise errors.DeviceError('Device in DFU mode not found.') @@ -82,8 +95,14 @@ def fetch_board(self) -> Optional[str]: try: with device_info.open('r') as f: data = json.load(f) - + except FileNotFoundError: raise errors.CorruptError(f'File missing from Inferius: {device_info}') - return next(_['boardconfig'] for _ in data if _['identifier'] == self.identifier and _['bdid'] == self.data['BDID'] and _['cpid'] == self.data['CPID']) + return next( + _['boardconfig'] + for _ in data + if _['identifier'] == self.identifier + and _['bdid'] == self.data['BDID'] + and _['cpid'] == self.data['CPID'] + ) diff --git a/utils/errors.py b/utils/errors.py index 7c6e37a..4c287e9 100644 --- a/utils/errors.py +++ b/utils/errors.py @@ -18,13 +18,13 @@ class InvalidTypeError(InferiusError, TypeError): pass -class InferiusIOError(InferiusError, OSError): +class DeviceError(InferiusError): pass -class DeviceError(InferiusError): +class CorruptError(InferiusError): pass -class CorruptError(InferiusError): +class BadIPSWError(CorruptError): pass From 7f11def32dc5146247a4510a087beae2340e2a21 Mon Sep 17 00:00:00 2001 From: m1stadev Date: Tue, 11 Jan 2022 23:45:12 -0600 Subject: [PATCH 20/56] Update error handling --- utils/api.py | 32 ++++++++++++++++++-------------- utils/bundle.py | 14 +++++++------- utils/dependencies.py | 4 ++-- utils/device.py | 6 ++++-- 4 files changed, 31 insertions(+), 25 deletions(-) diff --git a/utils/api.py b/utils/api.py index 071ab84..350f57c 100644 --- a/utils/api.py +++ b/utils/api.py @@ -22,8 +22,8 @@ def is_signed(self, version: str) -> bool: def fetch_api(self) -> Optional[dict]: try: return requests.get(f'https://api.ipsw.me/v4/device/{self.device}').json() - except requests.exceptions.JSONDecodeError: - raise errors.NotFoundError(f"Device does not exist: {self.device}.") + except requests.exceptions.JSONDecodeError as e: + raise errors.NotFoundError(f"Device does not exist: {self.device}.") from e def fetch_board(self) -> Optional[str]: boards = [ @@ -59,10 +59,10 @@ def fetch_sha1(self, buildid: str) -> str: for firm in self.api['firmwares'] if firm['buildid'] == buildid ) - except StopIteration: + except StopIteration as e: raise errors.NotFoundError( f'Firmware does not exist with buildid: {buildid}.' - ) + ) from e return sha1 @@ -75,18 +75,20 @@ def partialzip_extract( for firm in self.api['firmwares'] if firm['buildid'] == buildid ) - except StopIteration: + except StopIteration as e: raise errors.NotFoundError( f'Firmware does not exist with buildid: {buildid}.' - ) + ) from e with RemoteZip(url) as ipsw: try: ipsw.extract(component, path) - except KeyError: - raise errors.NotFoundError(f'Component does not exist: {component}.') - except OSError: - raise IOError(f'Failed to partialzip component: {component}.') + except KeyError as e: + raise errors.NotFoundError( + f'Component does not exist: {component}.' + ) from e + except OSError as e: + raise IOError(f'Failed to partialzip component: {component}.') from e return path / component @@ -97,13 +99,15 @@ def partialzip_read(self, buildid: str, component: str) -> Optional[bytes]: for firm in self.api['firmwares'] if firm['buildid'] == buildid ) - except StopIteration: + except StopIteration as e: raise errors.NotFoundError( f'Firmware does not exist with buildid: {buildid}.' - ) + ) from e with RemoteZip(url) as ipsw: try: return ipsw.read(component) - except KeyError: - raise errors.NotFoundError(f'File does not exist in IPSW: {component}.') + except KeyError as e: + raise errors.NotFoundError( + f'File does not exist in IPSW: {component}.' + ) from e diff --git a/utils/bundle.py b/utils/bundle.py index 8e02d7c..7b79f5d 100644 --- a/utils/bundle.py +++ b/utils/bundle.py @@ -52,15 +52,15 @@ def fetch_bundle( ) as rz: try: rz.extractall(bundle) - except OSError: - raise errors.IOError( + except OSError as e: + raise IOError( f'Failed to download firmware bundle to: {bundle}.' - ) + ) from e - except RemoteIOError: + except RemoteIOError as e: raise errors.NotFoundError( f'A firmware bundle does not exist for device: {device}, OS: {version}.' - ) + ) from e return bundle @@ -77,8 +77,8 @@ def fetch_ota_manifest(self, device: str, path: Path) -> Optional[Path]: with manifest.open('wb') as f: try: f.write(r.content) - except OSError: - raise errors.IOError(f'Failed to write OTA manifest to: {manifest}.') + except OSError as e: + raise IOError(f'Failed to write OTA manifest to: {manifest}.') from e return manifest diff --git a/utils/dependencies.py b/utils/dependencies.py index 6ce7a7f..d9d5bad 100644 --- a/utils/dependencies.py +++ b/utils/dependencies.py @@ -29,7 +29,7 @@ def check_bin(self, binary: str) -> None: subprocess.check_call( (binary, '-V'), stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL ) - except subprocess.CalledProcessError: + except subprocess.CalledProcessError as e: raise errors.DependencyError( 'This iRecovery build is too old to be used with Inferius.' - ) + ) from e diff --git a/utils/device.py b/utils/device.py index 8dac8ba..9346f8f 100644 --- a/utils/device.py +++ b/utils/device.py @@ -96,8 +96,10 @@ def fetch_board(self) -> Optional[str]: with device_info.open('r') as f: data = json.load(f) - except FileNotFoundError: - raise errors.CorruptError(f'File missing from Inferius: {device_info}') + except FileNotFoundError as e: + raise errors.CorruptError( + f'File missing from Inferius: {device_info}' + ) from e return next( _['boardconfig'] From a3c7f6131b98a06e5f02b40b589db13709a454eb Mon Sep 17 00:00:00 2001 From: m1stadev Date: Tue, 11 Jan 2022 23:51:51 -0600 Subject: [PATCH 21/56] [IPSW] Rewrite - Move to pathlib - Type-hinting - Formatting - Code cleanup - Custom errors --- utils/ipsw.py | 105 ++++++++++++++++++++++++++++---------------------- 1 file changed, 58 insertions(+), 47 deletions(-) diff --git a/utils/ipsw.py b/utils/ipsw.py index d0b2d53..1e40b53 100644 --- a/utils/ipsw.py +++ b/utils/ipsw.py @@ -1,96 +1,107 @@ +from pathlib import Path +from typing import Optional from utils.api import API +from utils import errors + import hashlib import json -import os import shutil -import sys import zipfile class IPSW: - def __init__(self, ipsw): + def __init__(self, ipsw: Path): self.ipsw = ipsw - def create_ipsw(self, path, output, update, bootloader): - os.makedirs('IPSW', exist_ok=True) + def __str__(self) -> str: + return str(self.ipsw) + + def create_ipsw( + self, path: Path, filename: str, update: bool, bootloader: str + ) -> Optional[Path]: + ipsw = Path(f'IPSW/{filename}') + ipsw.parent.mkdir(exist_ok=True) - info = { - 'update_support': update, - 'bootloader': bootloader - } + info = {'update_support': update, 'bootloader': bootloader} - with open(f'{path}/.Inferius', 'w') as f: + with (Path / '.Inferius').open('w') as f: json.dump(info, f) - custom_ipsw = f'IPSW/{output}' try: - shutil.make_archive(custom_ipsw, 'zip', path) + shutil.make_archive(ipsw, 'zip', path) except: - sys.exit('[ERROR] Failed to create custom IPSW. Exiting.') + raise OSError(f'Failed to create custom IPSW at path: {ipsw}.') - os.rename(f'{custom_ipsw}.zip', custom_ipsw) - return custom_ipsw + return ipsw.rename(ipsw.with_suffix('.ipsw')) - def extract_file(self, file, output): + def extract_file(self, file: str, output: Path) -> Path: try: - with zipfile.ZipFile(self.ipsw, 'r') as ipsw, open(output, 'wb') as f: + with zipfile.ZipFile(self.ipsw, 'r') as ipsw, (output / file).open( + 'wb' + ) as f: f.write(ipsw.read(file)) - except: - sys.exit(f"[ERROR] Failed to extract '{file}' from IPSW. Exiting.") - def extract_ipsw(self, path): + except KeyError as e: + raise errors.NotFoundError(f'File not in IPSW: {file}.') from e + + except OSError as e: + raise IOError(f'Failed to extract file from IPSW: {file}.') + + def extract_ipsw(self, path: Path) -> None: with zipfile.ZipFile(self.ipsw, 'r') as ipsw: try: ipsw.extractall(path) - except: - sys.exit(f"[ERROR] Failed to extract '{self.ipsw}'. Exiting.") + except OSError as e: + raise OSError(f'Failed to extract IPSW: {self.ipsw}.') from e - def read_file(self, file): + def read_file(self, file: str) -> Optional[bytes]: try: with zipfile.ZipFile(self.ipsw, 'r') as ipsw: return ipsw.read(file) - except: - sys.exit(f"[ERROR] Failed to read '{file}' from IPSW. Exiting.") + except KeyError as e: + raise errors.NotFoundError(f'File not in IPSW: {file}.') from e - def verify_ipsw(self, ipsw_sha1): - if not os.path.isfile(self.ipsw): - sys.exit(f"[ERROR] '{self.ipsw}' does not exist. Exiting.") + def verify_ipsw(self, sha1: str) -> None: + if not self.ipsw.is_file(): + raise errors.NotFoundError(f'IPSW does not exist: {self.ipsw}.') if not zipfile.is_zipfile(self.ipsw): - sys.exit(f"[ERROR] '{self.ipsw}' is not a valid IPSW. Exiting.") + raise errors.BadIPSWError(f'IPSW is corrupt: {self.ipsw}.') with zipfile.ZipFile(self.ipsw, 'r') as ipsw: if '.Inferius' in ipsw.namelist(): - sys.exit(f"[ERROR] '{self.ipsw}' is not a stock IPSW. Exiting.") + raise errors.BadIPSWError(f'IPSW has been modified: {self.ipsw}.') - sha1 = hashlib.sha1() - with open(self.ipsw, 'rb') as ipsw: - fbuf = ipsw.read(8192) - while len(fbuf) != 0: - sha1.update(fbuf) - fbuf = ipsw.read(8192) + hash = hashlib.sha1() + with self.ipsw.open('rb') as ipsw: + fbuf = ipsw.read(65536) + while len(fbuf) > 0: + hash.update(fbuf) + fbuf = ipsw.read(65536) - if ipsw_sha1 != sha1.hexdigest(): - sys.exit(f"[ERROR] '{self.ipsw}' is not a valid IPSW. Exiting.") + if sha1 != hash.hexdigest(): + raise errors.BadIPSWError(f'IPSW is corrupt: {self.ipsw}.') - def verify_custom_ipsw(self, device, update): - if not os.path.isfile(self.ipsw): - sys.exit(f"[ERROR] '{self.ipsw}' does not exist. Exiting.") + def verify_custom_ipsw(self, api: API, update: bool) -> None: + if not self.ipsw.is_file(): + raise errors.NotFoundError(f'IPSW does not exist: {self.ipsw}.') if not zipfile.is_zipfile(self.ipsw): - sys.exit(f"[ERROR] '{self.ipsw}' is not a valid IPSW. Exiting.") + raise errors.BadIPSWError(f'IPSW is corrupt: {self.ipsw}.') with zipfile.ZipFile(self.ipsw, 'r') as ipsw: if '.Inferius' not in ipsw.namelist(): - sys.exit(f"[ERROR] '{self.ipsw}' is not a custom IPSW. Exiting.") + raise errors.BadIPSWError(f'IPSW is not custom: {self.ipsw}.') info = json.loads(ipsw.read('.Inferius')) if (info['update_support'] == False) and (update == True): - sys.exit('[ERROR] This IPSW does not have support for update restores. Exiting.') + raise errors.BadIPSWError( + f'IPSW does not support update restores: {self.ipsw}.' + ) - api = API() - api.fetch_api(device) if api.is_signed(info['bootloader']) == False: - sys.exit('[ERROR] This IPSW is too old to be used with Inferius. Create a new custom IPSW. Exiting.') + raise errors.BadIPSWError( + f'IPSW is too old to be used with Inferius: {self.ipsw}. A new custom IPSW must be created.' + ) From c9413d602084c73177a22f777d0b535b6c222993 Mon Sep 17 00:00:00 2001 From: m1stadev Date: Tue, 11 Jan 2022 23:51:58 -0600 Subject: [PATCH 22/56] [errors] Remove unused errors --- utils/errors.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/utils/errors.py b/utils/errors.py index 4c287e9..15349c0 100644 --- a/utils/errors.py +++ b/utils/errors.py @@ -10,14 +10,6 @@ class NotFoundError(InferiusError): pass -class InvalidChoiceError(InferiusError, ValueError): - pass - - -class InvalidTypeError(InferiusError, TypeError): - pass - - class DeviceError(InferiusError): pass From 03b60aa100c8c5b07a80911157449346cafa4002 Mon Sep 17 00:00:00 2001 From: m1stadev Date: Tue, 11 Jan 2022 23:55:55 -0600 Subject: [PATCH 23/56] [manifest] Cleanup - Type-hinting - RestoreManifest.platform is now a property function - Formatting --- utils/manifest.py | 32 ++++++++++++++++++++------------ 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/utils/manifest.py b/utils/manifest.py index 03e61b0..62eceb0 100644 --- a/utils/manifest.py +++ b/utils/manifest.py @@ -1,24 +1,32 @@ +from typing import Optional + import plistlib class Manifest: - def __init__(self, manifest): - self.manifest = plistlib.loads(manifest) - self.version = (int(_) for _ in self.manifest['ProductVersion'].split('.')) - self.buildid = self.manifest['ProductBuildVersion'] - self.supported_devices = self.manifest['SupportedProductTypes'] + def __init__(self, manifest: bytes): + self._manifest = plistlib.loads(manifest) + self.version = (int(_) for _ in self._manifest['ProductVersion'].split('.')) + self.buildid = self._manifest['ProductBuildVersion'] + self.supported_devices = self._manifest['SupportedProductTypes'] def fetch_component_path(self, boardconfig: str, component: str) -> str: - return next(identity['Manifest'][component]['Info']['Path'] for identity in self.manifest['BuildIdentities'] if identity['Info']['DeviceClass'].lower() == boardconfig.lower()) + return next( + identity['Manifest'][component]['Info']['Path'] + for identity in self.manifest['BuildIdentities'] + if identity['Info']['DeviceClass'].lower() == boardconfig.lower() + ) class RestoreManifest: - def __init__(self, manifest, boardconfig): - self.platform = self.fetch_platform(boardconfig, plistlib.loads(manifest)) - - def fetch_platform(self, boardconfig, manifest): - for device in manifest['DeviceMap']: - if device['BoardConfig'].lower() != boardconfig.lower(): + def __init__(self, manifest: bytes, boardconfig: str): + self._manifest = plistlib.loads(manifest) + self.boardconfig = boardconfig + + @property + def platform(self) -> Optional[int]: + for device in self._manifest['DeviceMap']: + if device['BoardConfig'].lower() != self.boardconfig.lower(): continue if device['Platform'].startswith('s5l89'): From 135dedf6c3b87e2a2c2e81da2f609a8f707a6756 Mon Sep 17 00:00:00 2001 From: m1stadev Date: Wed, 12 Jan 2022 20:04:26 -0600 Subject: [PATCH 24/56] Update license --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index 5d89b54..f0a053b 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright 2021 m1sta +Copyright 2022 m1sta Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: From de6c672d3f1c48decf6261340346fddfb490d967 Mon Sep 17 00:00:00 2001 From: m1stadev Date: Sat, 15 Jan 2022 01:06:06 -0600 Subject: [PATCH 25/56] [device] get_dfu_data: Changes - Prefix function name with underscore - Type-hinting for device variable - Run usb.util.dispose_resources on device after we're done --- utils/device.py | 46 ++++++++++++++++++++++++---------------------- 1 file changed, 24 insertions(+), 22 deletions(-) diff --git a/utils/device.py b/utils/device.py index 9346f8f..58fe6de 100644 --- a/utils/device.py +++ b/utils/device.py @@ -10,7 +10,30 @@ class Device: def __init__(self, identifier): self.identifier = identifier self.board = self.fetch_boardconfig() - self.data = self.get_dfu_data() + self.data = self._get_dfu_data() + + def _get_dfu_data(self) -> Optional[dict]: + device: usb.core.Device = usb.core.find( + idVendor=0x5AC, idProduct=0x1227, backend=self.get_backend() + ) + if device is None: + raise errors.DeviceError('Device in DFU mode not found.') + + device_data = dict() + for item in device.serial_number.split(): + device_data[item.split(':')[0]] = item.split(':')[1] + + device_data['ECID'] = hex(int(device_data['ECID'], 16)) + + for i in ('CPID', 'CPRV', 'BDID', 'CPFM', 'SCEP', 'IBFL'): + device_data[i] = int(device_data[i], 16) + + for item in usb.util.get_string(device, device.bDescriptorType).split(): + device_data[item.split(':')[0]] = item.split(':')[1] + + usb.util.dispose_resources(device) + + return device_data @property def baseband(self) -> bool: @@ -69,27 +92,6 @@ def get_backend( return usb.backend.libusb1.get_backend(find_library=lambda _: libusb1) - def get_dfu_data(self) -> Optional[dict]: - device = usb.core.find( - idVendor=0x5AC, idProduct=0x1227, backend=self.get_backend() - ) - if device is None: - raise errors.DeviceError('Device in DFU mode not found.') - - device_data = dict() - for item in device.serial_number.split(): - device_data[item.split(':')[0]] = item.split(':')[1] - - device_data['ECID'] = hex(int(device_data['ECID'], 16)) - - for i in ('CPID', 'CPRV', 'BDID', 'CPFM', 'SCEP', 'IBFL'): - device_data[i] = int(device_data[i], 16) - - for item in usb.util.get_string(device, device.bDescriptorType).split(): - device_data[item.split(':')[0]] = item.split(':')[1] - - return device_data - def fetch_board(self) -> Optional[str]: device_info = Path('utils/devices.json') try: From 003023d0e41bd99e7b84b0c0761d98dee0f6c90d Mon Sep 17 00:00:00 2001 From: m1stadev Date: Sat, 15 Jan 2022 01:50:49 -0600 Subject: [PATCH 26/56] [device] Export all pyusb code to new USB module --- utils/device.py | 44 ++++------------------------------------ utils/usb.py | 54 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+), 40 deletions(-) create mode 100644 utils/usb.py diff --git a/utils/device.py b/utils/device.py index 58fe6de..799ba6f 100644 --- a/utils/device.py +++ b/utils/device.py @@ -1,9 +1,8 @@ from pathlib import Path from typing import Optional -from utils import errors +from utils import errors, usb import json -import usb, usb.backend.libusb1, usb.util class Device: @@ -13,11 +12,7 @@ def __init__(self, identifier): self.data = self._get_dfu_data() def _get_dfu_data(self) -> Optional[dict]: - device: usb.core.Device = usb.core.find( - idVendor=0x5AC, idProduct=0x1227, backend=self.get_backend() - ) - if device is None: - raise errors.DeviceError('Device in DFU mode not found.') + device = usb.get_device() device_data = dict() for item in device.serial_number.split(): @@ -28,11 +23,10 @@ def _get_dfu_data(self) -> Optional[dict]: for i in ('CPID', 'CPRV', 'BDID', 'CPFM', 'SCEP', 'IBFL'): device_data[i] = int(device_data[i], 16) - for item in usb.util.get_string(device, device.bDescriptorType).split(): + for item in usb.get_string(device, device.bDescriptorType).split(): device_data[item.split(':')[0]] = item.split(':')[1] - usb.util.dispose_resources(device) - + usb.release_device(device) return device_data @property @@ -62,36 +56,6 @@ def baseband(self) -> bool: ) ) - def get_backend( - self, - ) -> Optional[ - usb.backend.libusb1._LibUSB - ]: # Attempt to find a libusb 1.0 library to use as pyusb's backend, exit if one isn't found. - directories = ( - '/usr/local/lib', - '/opt/procursus/lib', - '/usr/lib', - ) # Common library directories to search - - libusb1 = None - for libdir in directories: - for file in Path(libdir).glob('libusb-1.0.0.*'): - if file.is_dir() or (file.suffix not in ('.so', '.dylib')): - continue - - libusb1 = file - break - - else: - continue - - break - - if libusb1 is None: - raise errors.DependencyError('libusb not found on your PC.') - - return usb.backend.libusb1.get_backend(find_library=lambda _: libusb1) - def fetch_board(self) -> Optional[str]: device_info = Path('utils/devices.json') try: diff --git a/utils/usb.py b/utils/usb.py new file mode 100644 index 0000000..bd1dcf2 --- /dev/null +++ b/utils/usb.py @@ -0,0 +1,54 @@ +from pathlib import Path +from typing import Optional +from utils import errors + +import usb, usb.backend.libusb1, usb.util + + +def _get_backend( + self, +) -> Optional[ + usb.backend.libusb1._LibUSB +]: # Attempt to find a libusb 1.0 library to use as pyusb's backend, exit if one isn't found. + directories = ( + '/usr/local/lib', + '/opt/procursus/lib', + '/usr/lib', + ) # Common library directories to search + + libusb1 = None + for libdir in directories: + for file in Path(libdir).glob('libusb-1.0.0.*'): + if file.is_dir() or (file.suffix not in ('.so', '.dylib')): + continue + + libusb1 = file + break + + else: + continue + + break + + if libusb1 is None: + raise errors.DependencyError('libusb not found on your PC.') + + return usb.backend.libusb1.get_backend(find_library=lambda _: libusb1) + +def get_device(self) -> Optional[usb.core.Device]: + device: usb.core.Device = usb.core.find( + idVendor=0x5AC, idProduct=0x1227, backend=_get_backend() + ) + if device is None: + raise errors.DeviceError('Device in DFU mode not found.') + + return + +def get_string(self, device: usb.core.Device, index: int) -> Optional[str]: + return usb.util.get_string(device, index) + +def release_device(self, device: usb.core.Device) -> None: + usb.util.dispose_resources(device) + +def reset_device(self, device: usb.core.Device) -> None: + device.reset() From 2986c2d37654c97dd7bfb4189f6ee82731671f9d Mon Sep 17 00:00:00 2001 From: m1stadev Date: Sat, 15 Jan 2022 01:52:13 -0600 Subject: [PATCH 27/56] [restore] Rewrite - Use pathlib - Type-hinting - Use utils.usb module - Failed restore log now stored in restore.log - irecovery is no longer used to reset a device's usb connection --- utils/errors.py | 4 ++ utils/restore.py | 153 +++++++++++++++++++++++++++-------------------- 2 files changed, 91 insertions(+), 66 deletions(-) diff --git a/utils/errors.py b/utils/errors.py index 15349c0..4edaeba 100644 --- a/utils/errors.py +++ b/utils/errors.py @@ -20,3 +20,7 @@ class CorruptError(InferiusError): class BadIPSWError(CorruptError): pass + + +class RestoreError(InferiusError): + pass diff --git a/utils/restore.py b/utils/restore.py index 400fb3a..891f517 100644 --- a/utils/restore.py +++ b/utils/restore.py @@ -1,108 +1,129 @@ -import glob +from pathlib import Path +from utils.device import Device +from utils import errors, usb + import shutil import subprocess -import os -import sys import time class Restore: - def __init__(self, identifier, platform): - self.device = identifier - self.platform = platform + def __init__(self, device: Device): + self.device = device - def restore(self, ipsw, cellular, update): - args = [ - 'futurerestore', - '-t', - self.blob, - '--latest-sep' - ] + def _irecv_send_file(self, file: Path) -> None: + try: + subprocess.check_call( + ('irecovery', '-f', file), stdout=subprocess.DEVNULL + ) + except: + raise errors.RestoreError(f"Failed to send file to device: {file}.") - if update: - args.append('-u') + def _irecv_send_cmd(self, cmd: str) -> None: + try: + subprocess.check_call( + ('irecovery', '-c', cmd), stdout=subprocess.DEVNULL + ) + except: + raise errors.RestoreError(f"Failed to send command to device: '{cmd}'.") + + def restore(self, ipsw: Path, cellular: bool, update: bool, *, sep: Path=None, manifest: Path=None) -> None: + args = ['futurerestore', '-t', self.blob] + + if sep and manifest: + args.append('-s') + args.append(sep) + + args.append('-m') + args.append(manifest) + + else: + args.append('--latest-sep') if cellular: args.append('--latest-baseband') else: args.append('--no-baseband') + if update: + args.append('-u') + args.append(ipsw) - with open('futurerestore_error.log', 'w') as f: - f.write(f"{' '.join(args)}\n\n") - with open('futurerestore_error.log', 'a') as f: - futurerestore = subprocess.run(args, stderr=subprocess.DEVNULL, stdout=f, universal_newlines=True) + Path('restore.log').unlink(missing_ok=True) + try: + futurerestore = subprocess.check_output( + args, stderr=subprocess.STDOUT, universal_newlines=True + ) + + except subprocess.CalledProcessError as process: + with open('restore.log', 'w') as f: + f.write(f"{' '.join(args)}\n\n") + f.write(process.stdout) - if os.path.isdir(ipsw.rsplit('.', 1)[0]): - shutil.rmtree(ipsw.rsplit('.', 1)[0]) + if Path(ipsw.stem).is_dir(): + shutil.rmtree(ipsw.stem) if 'Done: restoring succeeded!' not in futurerestore.stdout: - sys.exit("[ERROR] Restore failed. Log written to 'futurerestore_error.log'. Exiting.") - - os.remove('futurerestore_error.log') + with open('restore.log', 'w') as f: + f.write(f"{' '.join(args)}\n\n") + f.write(process.stdout) + + raise errors.RestoreError( + "[ERROR] Restore failed. Log written to 'restore.log'. Exiting." + ) - def save_blobs(self, ecid, boardconfig, path, apnonce=None): + def save_blobs(self, ecid: str, boardconfig: str, path: Path, *, manifest: Path=None, apnonce: str=None) -> None: args = [ 'tsschecker', - '-d', - self.device, - '-B', - boardconfig, - '-e', - f'0x{ecid}', - '-l', + '-d', self.device.identifier, + '-B', boardconfig, + '-e', ecid, + '--save-path', path, '-s', - '--save-path', - path, - '--nocache' ] + if manifest: + args.append('-m') + args.append(manifest) + else: + args.append('-l') + args.append('--nocache') + if apnonce: args.append('--apnonce') args.append(apnonce) tsschecker = subprocess.check_output(args, universal_newlines=True) if 'Saved shsh blobs!' not in tsschecker: - sys.exit('[ERROR] Failed to save blobs. Exiting.') + raise errors.RestoreError('Failed to save SHSH blobs.') if apnonce: - for blob in glob.glob(f'{path}/*.shsh*'): + for blob in path.glob('*.shsh*'): if blob != self.signing_blob: self.blob = blob break else: - self.signing_blob = glob.glob(f'{path}/*.shsh*')[0] - - def send_component(self, file, component): - if component == 'iBSS' and self.platform in (8960, 8015): #TODO: Reset device via pyusb rather than call an external binary. - irecovery_reset = subprocess.run(('irecovery', '-f', file), stdout=subprocess.DEVNULL) - if irecovery_reset.returncode != 0: - sys.exit('[ERROR] Failed to reset device. Exiting.') + self.signing_blob = path.glob('*.shsh*')[0] - irecovery = subprocess.run(('irecovery', '-f', file), stdout=subprocess.DEVNULL) - if irecovery.returncode != 0: - sys.exit(f"[ERROR] Failed to send '{component}'. Exiting.") + def send_bootchain(self, ibss: Path, ibec: Path) -> None: + if self.device.platform in ( + 0x8960, + 0x8015, + ): # Reset device + usb.reset_device(usb.get_device()) - if component == 'iBEC': - if 8010 <= self.platform <= 8015: - irecovery_jump = subprocess.run(('irecovery', '-c', 'go'), stdout=subprocess.DEVNULL) - if irecovery_jump.returncode != 0: - sys.exit(f"[ERROR] Failed to boot '{component}'. Exiting.") + self._irecv_send_file(ibss) + self._irecv_send_file(ibec) + if 8010 <= self.device.data['CPID'] <= 0x8015: + self._irecv_send_cmd('go') time.sleep(3) - def sign_component(self, file, output): - args = ( - 'img4tool', - '-c', - output, - '-p', - file, - '-s', - self.signing_blob - ) - - img4tool = subprocess.run(args, stdout=subprocess.DEVNULL) - if img4tool.returncode != 0: - sys.exit(f"[ERROR] Failed to sign '{file.split('/')[-1]}'. Exiting.") + def sign_component(self, file: Path, output: Path) -> None: + args = ('img4tool', '-c', output, '-p', file, '-s', self.signing_blob) + + try: + subprocess.check_call(args, stdout=subprocess.DEVNULL) + except subprocess.CalledProcessError: + raise errors.RestoreError(f"Failed to sign component: {file}.") From e2d785dffa1cc2ad0c81112508a0afffc586401b Mon Sep 17 00:00:00 2001 From: m1stadev Date: Sat, 15 Jan 2022 01:58:37 -0600 Subject: [PATCH 28/56] [bundle] Move fetch_ota_manifest to API --- utils/api.py | 18 ++++++++++++++++++ utils/bundle.py | 18 ------------------ 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/utils/api.py b/utils/api.py index 350f57c..1488f38 100644 --- a/utils/api.py +++ b/utils/api.py @@ -52,6 +52,24 @@ def fetch_board(self) -> Optional[str]: return boards[board] + def fetch_ota_manifest(self, device: str, path: Path) -> Optional[Path]: + r = requests.get( + f'https://github.com/m1stadev/inferius-ext/raw/master/manifests/BuildManifest_{device}.plist' + ) + if r.status_code == 404: + raise errors.NotFoundError( + f'An OTA manifest does not exist for device: {device}.' + ) + + manifest = path / 'otamanifest.plist' + with manifest.open('wb') as f: + try: + f.write(r.content) + except OSError as e: + raise IOError(f'Failed to write OTA manifest to: {manifest}.') from e + + return manifest + def fetch_sha1(self, buildid: str) -> str: try: sha1 = next( diff --git a/utils/bundle.py b/utils/bundle.py index 7b79f5d..d420673 100644 --- a/utils/bundle.py +++ b/utils/bundle.py @@ -64,24 +64,6 @@ def fetch_bundle( return bundle - def fetch_ota_manifest(self, device: str, path: Path) -> Optional[Path]: - r = requests.get( - f'https://github.com/m1stadev/inferius-ext/raw/master/manifests/BuildManifest_{device}.plist' - ) - if r.status_code == 404: - raise errors.NotFoundError( - f'An OTA manifest does not exist for device: {device}.' - ) - - manifest = path / 'otamanifest.plist' - with manifest.open('wb') as f: - try: - f.write(r.content) - except OSError as e: - raise IOError(f'Failed to write OTA manifest to: {manifest}.') from e - - return manifest - def verify_bundle( self, local_bundle: Path, path: Path, api: dict, buildid: str, boardconfig: str ) -> Optional[Path]: From 5e8614afc3567b572f152daa35d43f7a747238c6 Mon Sep 17 00:00:00 2001 From: m1stadev Date: Sat, 15 Jan 2022 15:43:47 -0600 Subject: [PATCH 29/56] [USB] Remove self from function args --- utils/usb.py | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/utils/usb.py b/utils/usb.py index bd1dcf2..bce1bc5 100644 --- a/utils/usb.py +++ b/utils/usb.py @@ -5,9 +5,7 @@ import usb, usb.backend.libusb1, usb.util -def _get_backend( - self, -) -> Optional[ +def _get_backend() -> Optional[ usb.backend.libusb1._LibUSB ]: # Attempt to find a libusb 1.0 library to use as pyusb's backend, exit if one isn't found. directories = ( @@ -19,7 +17,7 @@ def _get_backend( libusb1 = None for libdir in directories: for file in Path(libdir).glob('libusb-1.0.0.*'): - if file.is_dir() or (file.suffix not in ('.so', '.dylib')): + if not file.is_file() or (file.suffix not in ('.so', '.dylib')): continue libusb1 = file @@ -35,20 +33,20 @@ def _get_backend( return usb.backend.libusb1.get_backend(find_library=lambda _: libusb1) -def get_device(self) -> Optional[usb.core.Device]: +def get_device() -> Optional[usb.core.Device]: device: usb.core.Device = usb.core.find( idVendor=0x5AC, idProduct=0x1227, backend=_get_backend() ) if device is None: raise errors.DeviceError('Device in DFU mode not found.') - return + return device -def get_string(self, device: usb.core.Device, index: int) -> Optional[str]: +def get_string(device: usb.core.Device, index: int) -> Optional[str]: return usb.util.get_string(device, index) -def release_device(self, device: usb.core.Device) -> None: +def release_device(device: usb.core.Device) -> None: usb.util.dispose_resources(device) -def reset_device(self, device: usb.core.Device) -> None: +def reset_device(device: usb.core.Device) -> None: device.reset() From ea5b22c3ab37b0e75610a5bd6417470b4786a4a4 Mon Sep 17 00:00:00 2001 From: m1stadev Date: Sat, 15 Jan 2022 15:44:41 -0600 Subject: [PATCH 30/56] [Restore] send_bootchain: - Reset device, no matter the SoC - Fix typo --- utils/restore.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/utils/restore.py b/utils/restore.py index 891f517..ddbd9ab 100644 --- a/utils/restore.py +++ b/utils/restore.py @@ -107,16 +107,15 @@ def save_blobs(self, ecid: str, boardconfig: str, path: Path, *, manifest: Path= self.signing_blob = path.glob('*.shsh*')[0] def send_bootchain(self, ibss: Path, ibec: Path) -> None: - if self.device.platform in ( - 0x8960, - 0x8015, - ): # Reset device - usb.reset_device(usb.get_device()) + # Reset device + device = usb.get_device() + usb.reset_device(device) + usb.release_device(device) self._irecv_send_file(ibss) self._irecv_send_file(ibec) - if 8010 <= self.device.data['CPID'] <= 0x8015: + if 0x8010 <= self.device.data['CPID'] <= 0x8015: self._irecv_send_cmd('go') time.sleep(3) From f4a025ee31b4d7336943e9e9d831c92eaee9a7b6 Mon Sep 17 00:00:00 2001 From: m1stadev Date: Sat, 15 Jan 2022 15:45:52 -0600 Subject: [PATCH 31/56] [restore] Fix some code - Pass Path objects as strings to command args - Add debug flag to futurerestore --- utils/restore.py | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/utils/restore.py b/utils/restore.py index ddbd9ab..2ff0e2a 100644 --- a/utils/restore.py +++ b/utils/restore.py @@ -14,7 +14,7 @@ def __init__(self, device: Device): def _irecv_send_file(self, file: Path) -> None: try: subprocess.check_call( - ('irecovery', '-f', file), stdout=subprocess.DEVNULL + ('irecovery', '-f', str(file)), stdout=subprocess.DEVNULL ) except: raise errors.RestoreError(f"Failed to send file to device: {file}.") @@ -28,14 +28,14 @@ def _irecv_send_cmd(self, cmd: str) -> None: raise errors.RestoreError(f"Failed to send command to device: '{cmd}'.") def restore(self, ipsw: Path, cellular: bool, update: bool, *, sep: Path=None, manifest: Path=None) -> None: - args = ['futurerestore', '-t', self.blob] + args = ['futurerestore', '-d', '-t', str(self.blob)] if sep and manifest: args.append('-s') - args.append(sep) + args.append(str(sep)) args.append('-m') - args.append(manifest) + args.append(str(manifest)) else: args.append('--latest-sep') @@ -48,7 +48,7 @@ def restore(self, ipsw: Path, cellular: bool, update: bool, *, sep: Path=None, m if update: args.append('-u') - args.append(ipsw) + args.append(str(ipsw)) Path('restore.log').unlink(missing_ok=True) try: @@ -58,16 +58,20 @@ def restore(self, ipsw: Path, cellular: bool, update: bool, *, sep: Path=None, m except subprocess.CalledProcessError as process: with open('restore.log', 'w') as f: - f.write(f"{' '.join(args)}\n\n") + f.write(f"{' '.join([str(_) for _ in args])}\n\n") f.write(process.stdout) + raise errors.RestoreError( + "[ERROR] Restore failed. Log written to 'restore.log'. Exiting." + ) from process + if Path(ipsw.stem).is_dir(): shutil.rmtree(ipsw.stem) - if 'Done: restoring succeeded!' not in futurerestore.stdout: + if 'Done: restoring succeeded!' not in futurerestore: with open('restore.log', 'w') as f: - f.write(f"{' '.join(args)}\n\n") - f.write(process.stdout) + f.write(f"{' '.join([str(_) for _ in args])}\n\n") + f.write(futurerestore) raise errors.RestoreError( "[ERROR] Restore failed. Log written to 'restore.log'. Exiting." @@ -79,13 +83,13 @@ def save_blobs(self, ecid: str, boardconfig: str, path: Path, *, manifest: Path= '-d', self.device.identifier, '-B', boardconfig, '-e', ecid, - '--save-path', path, + '--save-path', str(path), '-s', ] if manifest: args.append('-m') - args.append(manifest) + args.append(str(manifest)) else: args.append('-l') args.append('--nocache') @@ -104,7 +108,7 @@ def save_blobs(self, ecid: str, boardconfig: str, path: Path, *, manifest: Path= self.blob = blob break else: - self.signing_blob = path.glob('*.shsh*')[0] + self.signing_blob = tuple(path.glob('*.shsh*'))[0] def send_bootchain(self, ibss: Path, ibec: Path) -> None: # Reset device From 965409cfdfb5e5d31bcebb0c13628aafc367e459 Mon Sep 17 00:00:00 2001 From: m1stadev Date: Sat, 15 Jan 2022 15:46:29 -0600 Subject: [PATCH 32/56] [manifest] Manifest: changes - fetch_component_path -> get_path - Manifest.version is now a tuple rather than a generator --- utils/manifest.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/utils/manifest.py b/utils/manifest.py index 62eceb0..39c7921 100644 --- a/utils/manifest.py +++ b/utils/manifest.py @@ -6,14 +6,14 @@ class Manifest: def __init__(self, manifest: bytes): self._manifest = plistlib.loads(manifest) - self.version = (int(_) for _ in self._manifest['ProductVersion'].split('.')) + self.version = tuple(int(_) for _ in self._manifest['ProductVersion'].split('.')) self.buildid = self._manifest['ProductBuildVersion'] self.supported_devices = self._manifest['SupportedProductTypes'] - def fetch_component_path(self, boardconfig: str, component: str) -> str: + def get_path(self, boardconfig: str, component: str) -> str: return next( identity['Manifest'][component]['Info']['Path'] - for identity in self.manifest['BuildIdentities'] + for identity in self._manifest['BuildIdentities'] if identity['Info']['DeviceClass'].lower() == boardconfig.lower() ) From 90d65d084e17db522dbaf6c1149be393ba1d6ea0 Mon Sep 17 00:00:00 2001 From: m1stadev Date: Sat, 15 Jan 2022 15:47:05 -0600 Subject: [PATCH 33/56] [IPSW] create_ipsw: Fix errors --- utils/ipsw.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/utils/ipsw.py b/utils/ipsw.py index 1e40b53..95a9a66 100644 --- a/utils/ipsw.py +++ b/utils/ipsw.py @@ -24,7 +24,7 @@ def create_ipsw( info = {'update_support': update, 'bootloader': bootloader} - with (Path / '.Inferius').open('w') as f: + with (path / '.Inferius').open('w') as f: json.dump(info, f) try: @@ -32,7 +32,7 @@ def create_ipsw( except: raise OSError(f'Failed to create custom IPSW at path: {ipsw}.') - return ipsw.rename(ipsw.with_suffix('.ipsw')) + return ipsw.with_suffix(ipsw.suffix + '.zip').rename(ipsw.with_suffix('.ipsw')) def extract_file(self, file: str, output: Path) -> Path: try: From 023bbc3e9958ac2fe97717d3afcb6a9dcef5fba5 Mon Sep 17 00:00:00 2001 From: m1stadev Date: Sat, 15 Jan 2022 15:47:58 -0600 Subject: [PATCH 34/56] [IPSW] extract_file: Fix error --- utils/ipsw.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/ipsw.py b/utils/ipsw.py index 95a9a66..d59cb81 100644 --- a/utils/ipsw.py +++ b/utils/ipsw.py @@ -36,7 +36,7 @@ def create_ipsw( def extract_file(self, file: str, output: Path) -> Path: try: - with zipfile.ZipFile(self.ipsw, 'r') as ipsw, (output / file).open( + with zipfile.ZipFile(self.ipsw, 'r') as ipsw, output.open( 'wb' ) as f: f.write(ipsw.read(file)) From 94723ceea43368f70af62cb5d50f20445f3297f2 Mon Sep 17 00:00:00 2001 From: m1stadev Date: Sat, 15 Jan 2022 15:48:38 -0600 Subject: [PATCH 35/56] [bundle] Changes - Store bundle path in Bundle.bundle --- utils/bundle.py | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/utils/bundle.py b/utils/bundle.py index d420673..a4b4a46 100644 --- a/utils/bundle.py +++ b/utils/bundle.py @@ -6,11 +6,13 @@ import bsdiff4 import json -import requests import zipfile class Bundle: + def __init__(self, bundle: Optional[Path]=None): + self.bundle = bundle + def apply_patches(self, ipsw: Path) -> None: with (self.bundle / 'Info.json').open('r') as f: bundle_data = json.load(f) @@ -40,8 +42,8 @@ def check_update_support(self) -> bool: def fetch_bundle( self, device: str, version: tuple, buildid: str, path: Path - ) -> Optional[Path]: - bundle_name = '_'.join(device, '.'.join(version), buildid) + ) -> None: + bundle_name = '_'.join([device, '.'.join([str(_) for _ in version]), buildid]) bundle = path / bundle_name bundle.mkdir() @@ -62,30 +64,33 @@ def fetch_bundle( f'A firmware bundle does not exist for device: {device}, OS: {version}.' ) from e - return bundle + self.bundle = bundle def verify_bundle( - self, local_bundle: Path, path: Path, api: dict, buildid: str, boardconfig: str - ) -> Optional[Path]: - if not zipfile.is_zipfile(local_bundle): - return + self, path: Path, api: dict, buildid: str, boardconfig: str + ) -> None: + if not self.bundle.exists(): + raise errors.NotFoundError(f'Firmware bundle does not exist: {self.bundle}.') + + if not zipfile.is_zipfile(self.bundle): + raise errors.CorruptError(f'Firmware bundle is corrupt: {self.bundle}.') if not any(_['buildid'] == buildid for _ in api['firmwares']): return try: - with zipfile.ZipFile(local_bundle, 'r') as f: + with zipfile.ZipFile(self.bundle, 'r') as f: bundle_data = json.loads(f.read('Info.json')) next(_.lower() == boardconfig.lower() for _ in bundle_data['boards']) - except: + except StopIteration: return - bundle = path / local_bundle.stem + bundle = path / self.bundle.stem bundle.mkdir() - with zipfile.ZipFile(local_bundle) as f: + with zipfile.ZipFile(self.bundle) as f: f.extractall(bundle) - return bundle + self.bundle = bundle From 0f41e5becca503a55c82da93ce1aa74a3632b331 Mon Sep 17 00:00:00 2001 From: m1stadev Date: Sat, 15 Jan 2022 15:49:46 -0600 Subject: [PATCH 36/56] [device] FIx Device.board --- utils/device.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/device.py b/utils/device.py index 799ba6f..301f0d2 100644 --- a/utils/device.py +++ b/utils/device.py @@ -8,8 +8,8 @@ class Device: def __init__(self, identifier): self.identifier = identifier - self.board = self.fetch_boardconfig() self.data = self._get_dfu_data() + self.board = self.fetch_board() def _get_dfu_data(self) -> Optional[dict]: device = usb.get_device() From bc7e1b751aa7a1c314f131ee3228f89513c3ef20 Mon Sep 17 00:00:00 2001 From: m1stadev Date: Sat, 15 Jan 2022 15:53:11 -0600 Subject: [PATCH 37/56] [main] Rewrite - Use Path objects - (Initial) iOS 10 support - Disable iOS 14 support - Formatting --- inferius | 209 +++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 143 insertions(+), 66 deletions(-) diff --git a/inferius b/inferius index f8cabea..bd29d94 100755 --- a/inferius +++ b/inferius @@ -1,5 +1,7 @@ #!/usr/bin/env python3 +from pathlib import Path + from utils.api import API from utils.bundle import Bundle from utils.dependencies import Checks @@ -7,34 +9,38 @@ from utils.device import Device from utils.ipsw import IPSW from utils.manifest import Manifest, RestoreManifest from utils.restore import Restore + import argparse import platform -import os import sys import tempfile -def create_ipsw(api, buildmanifest, ipsw_path, tmpdir, bundle_path): +def create_ipsw( + api: API, buildmanifest: Manifest, bundle: Bundle, ipsw: IPSW, tmpdir: Path +): print('Creating custom IPSW') - bundle = Bundle() - ipsw = IPSW(ipsw_path) - print('[1] Grabbing Firmware Bundle...') - if bundle_path is not None: + if bundle.bundle is not None: print('Note: Using user provided Firmware Bundle.') - if bundle.verify_bundle(bundle_path, tmpdir, api.api, buildmanifest.buildid, api.board) == False: - sys.exit(f"[ERROR] Bundle '{bundle_path}' is invalid. Exiting.") + try: + bundle.verify_bundle(tmpdir, api.api, buildmanifest.buildid, api.board) + except: + sys.exit(f"[ERROR] Bundle is invalid: {bundle.bundle}. Exiting.") else: - bundle.fetch_bundle(api.device, buildmanifest.version, buildmanifest.buildid, tmpdir) + bundle.fetch_bundle( + api.device, buildmanifest.version, buildmanifest.buildid, tmpdir + ) print('[2] Verifying IPSW...') ipsw.verify_ipsw(api.fetch_sha1(buildmanifest.buildid)) print('[3] Extracting IPSW...') - extracted_ipsw = f'{tmpdir}/ipsw' - os.mkdir(extracted_ipsw) + extracted_ipsw = tmpdir / 'ipsw' + extracted_ipsw.mkdir() + ipsw.extract_ipsw(extracted_ipsw) print('[4] Patching components...') @@ -42,105 +48,176 @@ def create_ipsw(api, buildmanifest, ipsw_path, tmpdir, bundle_path): buildid = api.api['firmwares'][0]['buildid'] latest_manifest = Manifest(api.partialzip_read(buildid, 'BuildManifest.plist')) - api.partialzip_extract(buildid, latest_manifest.fetch_component_path(api.board, 'LLB'), extracted_ipsw) - bootloader_ver = api.partialzip_extract(buildid, latest_manifest.fetch_component_path(api.board, 'iBoot'), extracted_ipsw) + api.partialzip_extract( + buildid, latest_manifest.get_path(api.board, 'LLB'), extracted_ipsw + ) + api.partialzip_extract( + buildid, latest_manifest.get_path(api.board, 'iBoot'), extracted_ipsw + ) print('[5] Repacking IPSW...') - custom_ipsw = ipsw.create_ipsw(extracted_ipsw, f"{ipsw_path.split('/')[-1].rsplit('.', 1)[0]}_custom.ipsw", bundle.check_update_support(), bootloader_ver) - print(f"Finished creating custom IPSW: '{custom_ipsw}'.") + ipsw.ipsw = ipsw.create_ipsw( + extracted_ipsw, + f'{ipsw.ipsw.stem}_custom.ipsw', + bundle.check_update_support(), + api.api['firmwares'][0]['version'], + ) + print(f"Finished creating custom IPSW: '{ipsw.ipsw}'.") - return custom_ipsw + return ipsw -def restore_ipsw(api, buildmanifest, ipsw_path, updating, tmpdir): +def restore_ipsw(api: API, buildmanifest: Manifest, ipsw: IPSW, updating: bool, tmpdir: Path): print('Restoring custom IPSW') Checks() device = Device(api.device) - ipsw = IPSW(ipsw_path) - restore = Restore(api.device, device.platform) + restore = Restore(device) print('[1] Verifying custom IPSW...') - ipsw.verify_custom_ipsw(api.device, updating) + ipsw.verify_custom_ipsw(api, updating) print('[2] Checking for device in pwned DFU...') - device.check_pwndfu() + if 'PWND' not in device.data.keys(): + sys.exit( + '[ERROR] Attempting to restore a device not in Pwned DFU mode. Exiting.' + ) print('[3] Extracting bootchain...') - ibss = buildmanifest.fetch_component_path(device.board, 'iBSS') - ipsw.extract_file(ibss, f'{tmpdir}/ibss.im4p') - ibec = buildmanifest.fetch_component_path(device.board, 'iBEC') - ipsw.extract_file(ibec, f'{tmpdir}/ibec.im4p') + ibss = buildmanifest.get_path(device.board, 'iBSS') + ipsw.extract_file(ibss, tmpdir / 'ibss.im4p') + + ibec = buildmanifest.get_path(device.board, 'iBEC') + ipsw.extract_file(ibec, tmpdir / 'ibec.im4p') print('[4] Signing bootchain...') - restore.save_blobs(device.ecid, device.board, tmpdir) - restore.sign_component(f'{tmpdir}/ibss.im4p', f'{tmpdir}/ibss.img4') - restore.sign_component(f'{tmpdir}/ibec.im4p', f'{tmpdir}/ibec.img4') + if buildmanifest.version[0] == 10: + otamanifest = api.fetch_ota_manifest(api.device, tmpdir) + restore.save_blobs(device.data['ECID'], device.board, tmpdir, manifest=otamanifest) + else: + restore.save_blobs(device.data['ECID'], device.board, tmpdir) + + restore.sign_component(tmpdir / 'ibss.im4p', tmpdir / 'ibss.img4') + restore.sign_component(tmpdir / 'ibec.im4p', tmpdir / 'ibec.img4') print('[5] Sending bootchain...') - restore.send_component(f'{tmpdir}/ibss.img4', 'iBSS') - restore.send_component(f'{tmpdir}/ibec.img4', 'iBEC') + restore.send_bootchain(tmpdir / 'ibss.img4', tmpdir / 'ibec.img4') print('[6] Saving SHSH blobs...') - restore.save_blobs(device.ecid, device.board, tmpdir, device.fetch_apnonce()) + if buildmanifest.version[0] == 10: + restore.save_blobs( + device.data['ECID'], + device.board, + tmpdir, + manifest=otamanifest, + apnonce=device.data['NONC'], + ) + else: + restore.save_blobs( + device.data['ECID'], device.board, tmpdir, apnonce=device.data['NONC'] + ) print('[7] Restoring...') - restore.restore(ipsw_path, device.baseband, updating) - print(f'Finished restoring pwned iOS {buildmanifest.version} IPSW to your device. Please boot your iOS device using one of the tools listed in the README.') + if buildmanifest.version[0] == 10: + manifest = Manifest(api.partialzip_read('14G60', 'BuildManifest.plist')) + + sep = api.partialzip_extract( + '14G60', manifest.get_path(api.board, 'RestoreSEP'), tmpdir + ) -def main(): - parser = argparse.ArgumentParser(description='Inferius - Create & Restore 64-bit custom IPSWs', usage="inferius -d 'identifier' -f 'IPSW' [-c/-r] [-b 'BUNDLE']") + restore.restore( + ipsw.ipsw, device.baseband, updating, sep=sep, manifest=otamanifest + ) + + else: + restore.restore(ipsw.ipsw, device.baseband, updating) + + print( + f'Finished restoring custom IPSW to your device. Please boot your device using one of the tools listed in the README.' + ) + + +def main() -> None: + parser = argparse.ArgumentParser( + description='Inferius - Create & Restore 64-bit custom IPSWs', + usage="inferius -d 'identifier' -f 'IPSW' [-c/-r] [-b 'BUNDLE']", + ) parser.add_argument('-d', '--device', help='Device identifier', nargs=1) - parser.add_argument('-f', '--ipsw', help='Path to IPSW', nargs=1) - parser.add_argument('-c', '--create', help='Create custom IPSW', action='store_true') - parser.add_argument('-r', '--restore', help='Restore custom IPSW', action='store_true') - parser.add_argument('-b', '--bundle', help='(Optional) Path to local Firmware Bundle', nargs=1) - parser.add_argument('-u', '--update', help='Keep data while restoring custom IPSW', action='store_true') + parser.add_argument('-f', '--ipsw', help='Path to IPSW', nargs=1, type=Path) + parser.add_argument( + '-c', '--create', help='Create custom IPSW', action='store_true' + ) + parser.add_argument( + '-r', '--restore', help='Restore custom IPSW', action='store_true' + ) + parser.add_argument( + '-b', + '--bundle', + help='(Optional) Path to local Firmware Bundle', + nargs=1, + type=Path, + ) + parser.add_argument( + '-u', + '--update', + help='Keep data while restoring custom IPSW', + action='store_true', + ) args = parser.parse_args() - if (not args.device or not args.ipsw) or \ - (not args.create and not args.restore) or \ - (args.update and not args.restore) or \ - (args.bundle and not args.create): + if ( + (not args.device or not args.ipsw) + or (not args.create and not args.restore) + or (args.update and not args.restore) + or (args.bundle and not args.create) + ): sys.exit(parser.print_help(sys.stderr)) if platform.system() == 'Windows': sys.exit('[ERROR] Inferius does not support Windows. Exiting.') - identifier = args.device[0] - ipsw_path = args.ipsw[0] - - api = API() - api.check_device(identifier) + api = API(args.device[0]) api.fetch_api() - api.get_board() + api.fetch_board() - ipsw = IPSW(ipsw_path) + ipsw = IPSW(args.ipsw[0]) restoremanifest = RestoreManifest(ipsw.read_file('Restore.plist'), api.board) - if restoremanifest.platform not in (0x8960, 0x7000, 0x7001, 0x8000, 0x8001, 0x8003, 0x8010, 0x8011, 0x8015): - sys.exit(f"[ERROR] '{identifier}' is not supported by Inferius. Exiting.") + if restoremanifest.platform not in ( + 0x8960, + 0x7000, + 0x7001, + 0x8000, + 0x8001, + 0x8003, + 0x8010, + 0x8011, + 0x8015, + ): + sys.exit(f"[ERROR] '{api.device}' is not supported by Inferius. Exiting.") buildmanifest = Manifest(ipsw.read_file('BuildManifest.plist')) - ver_major = int(buildmanifest.version.split('.')[0]) - if ver_major == 10 and restoremanifest.platform != 0x8960: - sys.exit(f'[ERROR] iOS 10 downgrades are only supported on A7 devices. Exiting.') + if not 10 <= buildmanifest.version[0] <= 13: + sys.exit( + f'[ERROR] iOS {buildmanifest.version} is not supported by Inferius. Exiting.' + ) - elif not 11 <= ver_major <= 14: - sys.exit(f'[ERROR] iOS {buildmanifest.version} is not supported by Inferius. Exiting.') + if buildmanifest.version[0] == 10 and restoremanifest.platform != 0x8960: + sys.exit( + f'[ERROR] iOS 10 downgrades are only supported on A7 devices. Exiting.' + ) - if identifier not in buildmanifest.supported_devices: - sys.exit(f"[ERROR] IPSW '{ipsw_path}' does not support {identifier}. Exiting.") + if args.device[0] not in buildmanifest.supported_devices: + sys.exit(f"[ERROR] IPSW: {ipsw} does not support {api.device}. Exiting.") - if args.bundle: - bundle = args.bundle[0] - else: - bundle = None + bundle = Bundle(args.bundle[0] if args.bundle else None) with tempfile.TemporaryDirectory() as tmpdir: + tmpdir = Path(tmpdir) + if args.create: - custom_ipsw = create_ipsw(api, buildmanifest, ipsw_path, tmpdir, bundle) + custom_ipsw = create_ipsw(api, buildmanifest, bundle, ipsw, tmpdir) else: - custom_ipsw = ipsw_path + custom_ipsw = ipsw if args.restore: restore_ipsw(api, buildmanifest, custom_ipsw, args.update, tmpdir) From bcf5e65b085d5df4731523e2f333d2a1baa21c79 Mon Sep 17 00:00:00 2001 From: m1stadev Date: Sat, 15 Jan 2022 17:03:58 -0600 Subject: [PATCH 38/56] [bundle] fetch_bundle: Update error message --- utils/bundle.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/bundle.py b/utils/bundle.py index a4b4a46..227c945 100644 --- a/utils/bundle.py +++ b/utils/bundle.py @@ -61,7 +61,7 @@ def fetch_bundle( except RemoteIOError as e: raise errors.NotFoundError( - f'A firmware bundle does not exist for device: {device}, OS: {version}.' + f"A firmware bundle does not exist for device: {device}, OS: {'.'.join([str(_) for _ in version])}." ) from e self.bundle = bundle From 92c45ef467616316762407e248ed39427aa64cad Mon Sep 17 00:00:00 2001 From: m1stadev Date: Sat, 15 Jan 2022 17:04:30 -0600 Subject: [PATCH 39/56] Update README with new links + dependencies and update gitignore --- .gitignore | 2 +- README.md | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index abb7f2a..9d59a96 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,4 @@ FirmwareBundles/ IPSW/ .DS_Store -futurerestore_error.log +restore.log diff --git a/README.md b/README.md index b19dcce..0a67663 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![License](https://img.shields.io/github/license/m1stadev/Inferius)](https://github.com/m1stadev/Inferius) [![Stars](https://img.shields.io/github/stars/m1stadev/Inferius)]((https://github.com/m1stadev/Inferius)) -Inferius is an [xpwn](https://github.com/m1stadev/xpwn)-like tool to create & restore custom IPSWs to 64-bit devices. +Inferius is an [xpwn](https://github.com/OothecaPickle/xpwn)-like tool to create & restore custom IPSWs to 64-bit devices. Its current purpose is to downgrade devices (vulnerable to [checkm8](https://github.com/axi0mX/ipwndfu)) to previous iOS versions. However, there are other possible uses for this tool as well. @@ -44,6 +44,7 @@ By default, firmware bundles are automatically downloaded from an [external repo - [libusb](https://libusb.info/) - [futurerestore](https://github.com/m1stadev/futurerestore) - futurerestore must be compiled with [my fork of img4tool](https://github.com/m1stadev/img4tool), or else it can't be used with Inferius. +- [img4tool](https://github.com/tihmstar/img4tool) - [libirecovery](https://github.com/libimobiledevice/libirecovery) - [tsschecker](https://github.com/1Conan/tsschecker) - Python dependencies: From fbc86174c3f68be8170b92a2b7797f4cc2a030f9 Mon Sep 17 00:00:00 2001 From: m1stadev Date: Sat, 5 Feb 2022 12:31:46 -0600 Subject: [PATCH 40/56] [usb] Updates - get_device: Update to allow getting either a DFU or Recovery mode device - send_cmd: Add function for sending commands to a recovery mode device --- utils/device.py | 2 +- utils/restore.py | 7 +++++-- utils/usb.py | 27 ++++++++++++++++++++++++--- 3 files changed, 30 insertions(+), 6 deletions(-) diff --git a/utils/device.py b/utils/device.py index 301f0d2..1ec5873 100644 --- a/utils/device.py +++ b/utils/device.py @@ -12,7 +12,7 @@ def __init__(self, identifier): self.board = self.fetch_board() def _get_dfu_data(self) -> Optional[dict]: - device = usb.get_device() + device = usb.get_device(usb.DFU) device_data = dict() for item in device.serial_number.split(): diff --git a/utils/restore.py b/utils/restore.py index 2ff0e2a..418aaf3 100644 --- a/utils/restore.py +++ b/utils/restore.py @@ -112,7 +112,7 @@ def save_blobs(self, ecid: str, boardconfig: str, path: Path, *, manifest: Path= def send_bootchain(self, ibss: Path, ibec: Path) -> None: # Reset device - device = usb.get_device() + device = usb.get_device(usb.DFU) usb.reset_device(device) usb.release_device(device) @@ -120,7 +120,10 @@ def send_bootchain(self, ibss: Path, ibec: Path) -> None: self._irecv_send_file(ibec) if 0x8010 <= self.device.data['CPID'] <= 0x8015: - self._irecv_send_cmd('go') + device = usb.get_device(usb.RECOVERY) + usb.send_cmd('go') + usb.release_device(device) + time.sleep(3) def sign_component(self, file: Path, output: Path) -> None: diff --git a/utils/usb.py b/utils/usb.py index bce1bc5..c776bd6 100644 --- a/utils/usb.py +++ b/utils/usb.py @@ -5,6 +5,10 @@ import usb, usb.backend.libusb1, usb.util +RECOVERY = 0x1281 +DFU = 0x1227 + + def _get_backend() -> Optional[ usb.backend.libusb1._LibUSB ]: # Attempt to find a libusb 1.0 library to use as pyusb's backend, exit if one isn't found. @@ -33,20 +37,37 @@ def _get_backend() -> Optional[ return usb.backend.libusb1.get_backend(find_library=lambda _: libusb1) -def get_device() -> Optional[usb.core.Device]: + +def get_device(mode: int) -> Optional[usb.core.Device]: + if mode not in (DFU, RECOVERY): + raise errors.DeviceError(f'Invalid mode specified: {mode}.') + device: usb.core.Device = usb.core.find( - idVendor=0x5AC, idProduct=0x1227, backend=_get_backend() + idVendor=0x5AC, idProduct=mode, backend=_get_backend() ) + if device is None: - raise errors.DeviceError('Device in DFU mode not found.') + raise errors.DeviceError(f'Device not found.') return device + def get_string(device: usb.core.Device, index: int) -> Optional[str]: return usb.util.get_string(device, index) + +def send_cmd(device: usb.core.Device, cmd: str) -> None: + if device.idProduct != RECOVERY: + raise errors.DeviceError( + 'Commands can only be send to a device in Recovery mode.' + ) + + device.ctrl_transfer(0x40, 1, 0, 0, cmd) + + def release_device(device: usb.core.Device) -> None: usb.util.dispose_resources(device) + def reset_device(device: usb.core.Device) -> None: device.reset() From ae5fe93e842513be26d9b70e622a96afbce3b2de Mon Sep 17 00:00:00 2001 From: m1stadev Date: Sat, 5 Feb 2022 13:26:55 -0600 Subject: [PATCH 41/56] [manifest] Update - Store boardconfig as instance attribute - Add dump function for returning manifest as bytes - get_path: Update with error handling --- inferius | 21 +++++++++------------ utils/manifest.py | 38 +++++++++++++++++++++++++++++++------- 2 files changed, 40 insertions(+), 19 deletions(-) diff --git a/inferius b/inferius index bd29d94..043e47a 100755 --- a/inferius +++ b/inferius @@ -48,12 +48,8 @@ def create_ipsw( buildid = api.api['firmwares'][0]['buildid'] latest_manifest = Manifest(api.partialzip_read(buildid, 'BuildManifest.plist')) - api.partialzip_extract( - buildid, latest_manifest.get_path(api.board, 'LLB'), extracted_ipsw - ) - api.partialzip_extract( - buildid, latest_manifest.get_path(api.board, 'iBoot'), extracted_ipsw - ) + api.partialzip_extract(buildid, latest_manifest.get_path('LLB'), extracted_ipsw) + api.partialzip_extract(buildid, latest_manifest.get_path('iBoot'), extracted_ipsw) print('[5] Repacking IPSW...') ipsw.ipsw = ipsw.create_ipsw( @@ -73,6 +69,9 @@ def restore_ipsw(api: API, buildmanifest: Manifest, ipsw: IPSW, updating: bool, device = Device(api.device) restore = Restore(device) + # Make new instance of buildmanifest + buildmanifest = Manifest(buildmanifest.dump(), device.board) + print('[1] Verifying custom IPSW...') ipsw.verify_custom_ipsw(api, updating) @@ -83,10 +82,10 @@ def restore_ipsw(api: API, buildmanifest: Manifest, ipsw: IPSW, updating: bool, ) print('[3] Extracting bootchain...') - ibss = buildmanifest.get_path(device.board, 'iBSS') + ibss = buildmanifest.get_path('iBSS') ipsw.extract_file(ibss, tmpdir / 'ibss.im4p') - ibec = buildmanifest.get_path(device.board, 'iBEC') + ibec = buildmanifest.get_path('iBEC') ipsw.extract_file(ibec, tmpdir / 'ibec.im4p') print('[4] Signing bootchain...') @@ -120,9 +119,7 @@ def restore_ipsw(api: API, buildmanifest: Manifest, ipsw: IPSW, updating: bool, if buildmanifest.version[0] == 10: manifest = Manifest(api.partialzip_read('14G60', 'BuildManifest.plist')) - sep = api.partialzip_extract( - '14G60', manifest.get_path(api.board, 'RestoreSEP'), tmpdir - ) + sep = api.partialzip_extract('14G60', manifest.get_path('RestoreSEP'), tmpdir) restore.restore( ipsw.ipsw, device.baseband, updating, sep=sep, manifest=otamanifest @@ -194,7 +191,7 @@ def main() -> None: ): sys.exit(f"[ERROR] '{api.device}' is not supported by Inferius. Exiting.") - buildmanifest = Manifest(ipsw.read_file('BuildManifest.plist')) + buildmanifest = Manifest(ipsw.read_file('BuildManifest.plist'), api.board) if not 10 <= buildmanifest.version[0] <= 13: sys.exit( diff --git a/utils/manifest.py b/utils/manifest.py index 39c7921..abafbc9 100644 --- a/utils/manifest.py +++ b/utils/manifest.py @@ -1,22 +1,46 @@ from typing import Optional +from utils import errors import plistlib class Manifest: - def __init__(self, manifest: bytes): + def __init__(self, manifest: bytes, board: str): self._manifest = plistlib.loads(manifest) - self.version = tuple(int(_) for _ in self._manifest['ProductVersion'].split('.')) + + self.version = tuple( + int(_) for _ in self._manifest['ProductVersion'].split('.') + ) self.buildid = self._manifest['ProductBuildVersion'] self.supported_devices = self._manifest['SupportedProductTypes'] - def get_path(self, boardconfig: str, component: str) -> str: - return next( - identity['Manifest'][component]['Info']['Path'] - for identity in self._manifest['BuildIdentities'] - if identity['Info']['DeviceClass'].lower() == boardconfig.lower() + # Get proper capitalization for board + self.board = next( + _['Info']['DeviceClass'] + for _ in self._manifest['BuildIdentities'] + if _['Info']['DeviceClass'].lower() == board.lower() + ) + + self.id = next( + _ + for _ in range(len(self._manifest['BuildIdentities'])) + if self._manifest['BuildIdentities'][_]['Info']['DeviceClass'] == self.board ) + def get_path(self, component: str) -> str: + if ( + component + not in self._manifest['BuildIdentities'][self.id]['Manifest'].keys() + ): + raise errors.NotFoundError(f'Component not found in manifest: {component}.') + + return self._manifest['BuildIdentities'][self.id]['Manifest'][component][ + 'Info' + ]['Path'] + + def dump(self) -> bytes: + return plistlib.dumps(self._manifest) + class RestoreManifest: def __init__(self, manifest: bytes, boardconfig: str): From 6b62ca9fbe1b6c22a0a75346df772a5b10dda1a1 Mon Sep 17 00:00:00 2001 From: m1stadev Date: Sat, 5 Feb 2022 13:29:07 -0600 Subject: [PATCH 42/56] Cleaner error handling --- inferius | 3 +++ utils/api.py | 38 ++++++++++++++++++++------------------ utils/bundle.py | 15 ++++++++------- utils/dependencies.py | 4 ++-- utils/device.py | 4 ++-- utils/ipsw.py | 22 ++++++++++------------ utils/restore.py | 8 +++++--- 7 files changed, 50 insertions(+), 44 deletions(-) diff --git a/inferius b/inferius index 043e47a..3ccd4f2 100755 --- a/inferius +++ b/inferius @@ -16,6 +16,9 @@ import sys import tempfile +sys.tracebacklimit = 0 + + def create_ipsw( api: API, buildmanifest: Manifest, bundle: Bundle, ipsw: IPSW, tmpdir: Path ): diff --git a/utils/api.py b/utils/api.py index 1488f38..299a110 100644 --- a/utils/api.py +++ b/utils/api.py @@ -22,8 +22,10 @@ def is_signed(self, version: str) -> bool: def fetch_api(self) -> Optional[dict]: try: return requests.get(f'https://api.ipsw.me/v4/device/{self.device}').json() - except requests.exceptions.JSONDecodeError as e: - raise errors.NotFoundError(f"Device does not exist: {self.device}.") from e + except requests.exceptions.JSONDecodeError: + raise errors.NotFoundError( + f"Device does not exist: {self.device}." + ) from None def fetch_board(self) -> Optional[str]: boards = [ @@ -45,10 +47,10 @@ def fetch_board(self) -> Optional[str]: try: board = int(board) - 1 except: - raise TypeError(f'Invalid choice given: {board}.') + raise TypeError(f'Invalid choice given: {board}.') from None else: if board not in range(len(boards)): - raise ValueError(f'Incorrect choice given: {board}.') + raise ValueError(f'Incorrect choice given: {board}.') from None return boards[board] @@ -65,8 +67,8 @@ def fetch_ota_manifest(self, device: str, path: Path) -> Optional[Path]: with manifest.open('wb') as f: try: f.write(r.content) - except OSError as e: - raise IOError(f'Failed to write OTA manifest to: {manifest}.') from e + except OSError: + raise IOError(f'Failed to write OTA manifest to: {manifest}.') from None return manifest @@ -77,10 +79,10 @@ def fetch_sha1(self, buildid: str) -> str: for firm in self.api['firmwares'] if firm['buildid'] == buildid ) - except StopIteration as e: + except StopIteration: raise errors.NotFoundError( f'Firmware does not exist with buildid: {buildid}.' - ) from e + ) from None return sha1 @@ -93,20 +95,20 @@ def partialzip_extract( for firm in self.api['firmwares'] if firm['buildid'] == buildid ) - except StopIteration as e: + except StopIteration: raise errors.NotFoundError( f'Firmware does not exist with buildid: {buildid}.' - ) from e + ) from None with RemoteZip(url) as ipsw: try: ipsw.extract(component, path) - except KeyError as e: + except KeyError: raise errors.NotFoundError( f'Component does not exist: {component}.' - ) from e - except OSError as e: - raise IOError(f'Failed to partialzip component: {component}.') from e + ) from None + except OSError: + raise IOError(f'Failed to partialzip component: {component}.') from None return path / component @@ -117,15 +119,15 @@ def partialzip_read(self, buildid: str, component: str) -> Optional[bytes]: for firm in self.api['firmwares'] if firm['buildid'] == buildid ) - except StopIteration as e: + except StopIteration: raise errors.NotFoundError( f'Firmware does not exist with buildid: {buildid}.' - ) from e + ) from None with RemoteZip(url) as ipsw: try: return ipsw.read(component) - except KeyError as e: + except KeyError: raise errors.NotFoundError( f'File does not exist in IPSW: {component}.' - ) from e + ) from None diff --git a/utils/bundle.py b/utils/bundle.py index 227c945..e5f3f4d 100644 --- a/utils/bundle.py +++ b/utils/bundle.py @@ -1,7 +1,6 @@ from pathlib import Path from remotezip import RemoteZip, RemoteIOError from typing import Optional -from utils.api import API from utils import errors import bsdiff4 @@ -10,7 +9,7 @@ class Bundle: - def __init__(self, bundle: Optional[Path]=None): + def __init__(self, bundle: Optional[Path] = None): self.bundle = bundle def apply_patches(self, ipsw: Path) -> None: @@ -54,15 +53,15 @@ def fetch_bundle( ) as rz: try: rz.extractall(bundle) - except OSError as e: + except OSError: raise IOError( f'Failed to download firmware bundle to: {bundle}.' - ) from e + ) from None - except RemoteIOError as e: + except RemoteIOError: raise errors.NotFoundError( f"A firmware bundle does not exist for device: {device}, OS: {'.'.join([str(_) for _ in version])}." - ) from e + ) from None self.bundle = bundle @@ -70,7 +69,9 @@ def verify_bundle( self, path: Path, api: dict, buildid: str, boardconfig: str ) -> None: if not self.bundle.exists(): - raise errors.NotFoundError(f'Firmware bundle does not exist: {self.bundle}.') + raise errors.NotFoundError( + f'Firmware bundle does not exist: {self.bundle}.' + ) if not zipfile.is_zipfile(self.bundle): raise errors.CorruptError(f'Firmware bundle is corrupt: {self.bundle}.') diff --git a/utils/dependencies.py b/utils/dependencies.py index d9d5bad..3018604 100644 --- a/utils/dependencies.py +++ b/utils/dependencies.py @@ -29,7 +29,7 @@ def check_bin(self, binary: str) -> None: subprocess.check_call( (binary, '-V'), stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL ) - except subprocess.CalledProcessError as e: + except subprocess.CalledProcessError: raise errors.DependencyError( 'This iRecovery build is too old to be used with Inferius.' - ) from e + ) from None diff --git a/utils/device.py b/utils/device.py index 1ec5873..d9be2c6 100644 --- a/utils/device.py +++ b/utils/device.py @@ -62,10 +62,10 @@ def fetch_board(self) -> Optional[str]: with device_info.open('r') as f: data = json.load(f) - except FileNotFoundError as e: + except FileNotFoundError: raise errors.CorruptError( f'File missing from Inferius: {device_info}' - ) from e + ) from None return next( _['boardconfig'] diff --git a/utils/ipsw.py b/utils/ipsw.py index d59cb81..345de09 100644 --- a/utils/ipsw.py +++ b/utils/ipsw.py @@ -30,37 +30,35 @@ def create_ipsw( try: shutil.make_archive(ipsw, 'zip', path) except: - raise OSError(f'Failed to create custom IPSW at path: {ipsw}.') + raise OSError(f'Failed to create custom IPSW at path: {ipsw}.') from None return ipsw.with_suffix(ipsw.suffix + '.zip').rename(ipsw.with_suffix('.ipsw')) def extract_file(self, file: str, output: Path) -> Path: try: - with zipfile.ZipFile(self.ipsw, 'r') as ipsw, output.open( - 'wb' - ) as f: + with zipfile.ZipFile(self.ipsw, 'r') as ipsw, output.open('wb') as f: f.write(ipsw.read(file)) - except KeyError as e: - raise errors.NotFoundError(f'File not in IPSW: {file}.') from e + except KeyError: + raise errors.NotFoundError(f'File not in IPSW: {file}.') from None - except OSError as e: - raise IOError(f'Failed to extract file from IPSW: {file}.') + except OSError: + raise IOError(f'Failed to extract file from IPSW: {file}.') from None def extract_ipsw(self, path: Path) -> None: with zipfile.ZipFile(self.ipsw, 'r') as ipsw: try: ipsw.extractall(path) - except OSError as e: - raise OSError(f'Failed to extract IPSW: {self.ipsw}.') from e + except OSError: + raise OSError(f'Failed to extract IPSW: {self.ipsw}.') from None def read_file(self, file: str) -> Optional[bytes]: try: with zipfile.ZipFile(self.ipsw, 'r') as ipsw: return ipsw.read(file) - except KeyError as e: - raise errors.NotFoundError(f'File not in IPSW: {file}.') from e + except KeyError: + raise errors.NotFoundError(f'File not in IPSW: {file}.') from None def verify_ipsw(self, sha1: str) -> None: if not self.ipsw.is_file(): diff --git a/utils/restore.py b/utils/restore.py index 418aaf3..3a625bc 100644 --- a/utils/restore.py +++ b/utils/restore.py @@ -17,7 +17,9 @@ def _irecv_send_file(self, file: Path) -> None: ('irecovery', '-f', str(file)), stdout=subprocess.DEVNULL ) except: - raise errors.RestoreError(f"Failed to send file to device: {file}.") + raise errors.RestoreError( + f"Failed to send file to device: {file}." + ) from None def _irecv_send_cmd(self, cmd: str) -> None: try: @@ -63,7 +65,7 @@ def restore(self, ipsw: Path, cellular: bool, update: bool, *, sep: Path=None, m raise errors.RestoreError( "[ERROR] Restore failed. Log written to 'restore.log'. Exiting." - ) from process + ) from None if Path(ipsw.stem).is_dir(): shutil.rmtree(ipsw.stem) @@ -132,4 +134,4 @@ def sign_component(self, file: Path, output: Path) -> None: try: subprocess.check_call(args, stdout=subprocess.DEVNULL) except subprocess.CalledProcessError: - raise errors.RestoreError(f"Failed to sign component: {file}.") + raise errors.RestoreError(f'Failed to sign image: {image}.') from None From c90cc4cabd4368e094ad78a52466a6ff298f244a Mon Sep 17 00:00:00 2001 From: m1stadev Date: Sat, 5 Feb 2022 22:39:25 -0600 Subject: [PATCH 43/56] [bundlegen] create_im4p: Remove stderr output --- bundlegen | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bundlegen b/bundlegen index 8dffee1..56bbe50 100755 --- a/bundlegen +++ b/bundlegen @@ -38,7 +38,7 @@ def create_im4p(file, output, tag=None, patch=None): args.append('-P') args.append(patch) - img4 = subprocess.run(args, stdout=subprocess.DEVNULL) + img4 = subprocess.run(args, stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT) if img4.returncode != 0: sys.exit(f"[ERROR] Packing '{file}' into im4p container failed. Exiting.") From 4c70873bef2993f5fe0c123591adc47d3101bcd2 Mon Sep 17 00:00:00 2001 From: m1stadev Date: Sat, 5 Feb 2022 22:39:43 -0600 Subject: [PATCH 44/56] [device] _get_dfu_data -> _get_data --- utils/device.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/utils/device.py b/utils/device.py index d9be2c6..7c6c295 100644 --- a/utils/device.py +++ b/utils/device.py @@ -8,10 +8,10 @@ class Device: def __init__(self, identifier): self.identifier = identifier - self.data = self._get_dfu_data() + self.data = self._get_data() self.board = self.fetch_board() - def _get_dfu_data(self) -> Optional[dict]: + def _get_data(self) -> Optional[dict]: device = usb.get_device(usb.DFU) device_data = dict() From bb0626e67442f2f64127fe9819c70289a4b217bb Mon Sep 17 00:00:00 2001 From: m1stadev Date: Sat, 5 Feb 2022 22:40:18 -0600 Subject: [PATCH 45/56] [restore] send_bootchain: Change A10-A11 check to A10+ --- utils/restore.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/restore.py b/utils/restore.py index 3a625bc..cc7eed7 100644 --- a/utils/restore.py +++ b/utils/restore.py @@ -121,7 +121,7 @@ def send_bootchain(self, ibss: Path, ibec: Path) -> None: self._irecv_send_file(ibss) self._irecv_send_file(ibec) - if 0x8010 <= self.device.data['CPID'] <= 0x8015: + if 0x8010 <= self.device.data['CPID']: device = usb.get_device(usb.RECOVERY) usb.send_cmd('go') usb.release_device(device) From 676a86f3efa16f6192a11d3eda8dd69c86669dc1 Mon Sep 17 00:00:00 2001 From: m1stadev Date: Sun, 6 Feb 2022 00:03:58 -0600 Subject: [PATCH 46/56] [main] create_ipsw: Update - Update print statements - Update Manifest call --- inferius | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/inferius b/inferius index 3ccd4f2..67010d7 100755 --- a/inferius +++ b/inferius @@ -22,7 +22,7 @@ sys.tracebacklimit = 0 def create_ipsw( api: API, buildmanifest: Manifest, bundle: Bundle, ipsw: IPSW, tmpdir: Path ): - print('Creating custom IPSW') + print('\nCreating custom IPSW\n') print('[1] Grabbing Firmware Bundle...') if bundle.bundle is not None: @@ -50,7 +50,10 @@ def create_ipsw( bundle.apply_patches(extracted_ipsw) buildid = api.api['firmwares'][0]['buildid'] - latest_manifest = Manifest(api.partialzip_read(buildid, 'BuildManifest.plist')) + latest_manifest = Manifest( + api.partialzip_read(buildid, 'BuildManifest.plist'), api.board + ) + api.partialzip_extract(buildid, latest_manifest.get_path('LLB'), extracted_ipsw) api.partialzip_extract(buildid, latest_manifest.get_path('iBoot'), extracted_ipsw) @@ -61,7 +64,7 @@ def create_ipsw( bundle.check_update_support(), api.api['firmwares'][0]['version'], ) - print(f"Finished creating custom IPSW: '{ipsw.ipsw}'.") + print(f"Finished creating custom IPSW: '{ipsw.ipsw}'.\n") return ipsw From 0df6df13ca61cfbc9d748857a1f86af4d27fb9c7 Mon Sep 17 00:00:00 2001 From: m1stadev Date: Sun, 6 Feb 2022 00:07:33 -0600 Subject: [PATCH 47/56] [main] Update arguments --- inferius | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/inferius b/inferius index 67010d7..853e046 100755 --- a/inferius +++ b/inferius @@ -144,8 +144,12 @@ def main() -> None: description='Inferius - Create & Restore 64-bit custom IPSWs', usage="inferius -d 'identifier' -f 'IPSW' [-c/-r] [-b 'BUNDLE']", ) - parser.add_argument('-d', '--device', help='Device identifier', nargs=1) - parser.add_argument('-f', '--ipsw', help='Path to IPSW', nargs=1, type=Path) + parser.add_argument( + '-d', '--device', help='Device identifier', nargs=1, required=True + ) + parser.add_argument( + '-f', '--ipsw', help='Path to IPSW', nargs=1, type=Path, required=True + ) parser.add_argument( '-c', '--create', help='Create custom IPSW', action='store_true' ) @@ -162,7 +166,7 @@ def main() -> None: parser.add_argument( '-u', '--update', - help='Keep data while restoring custom IPSW', + help='(Optional) Keep data while restoring custom IPSW', action='store_true', ) args = parser.parse_args() From b953d9fb0c7a6699d85a9e115432fc0949c14d2d Mon Sep 17 00:00:00 2001 From: m1stadev Date: Sun, 6 Feb 2022 00:08:39 -0600 Subject: [PATCH 48/56] [main] Update version check --- inferius | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/inferius b/inferius index 853e046..f5d2eca 100755 --- a/inferius +++ b/inferius @@ -203,9 +203,9 @@ def main() -> None: buildmanifest = Manifest(ipsw.read_file('BuildManifest.plist'), api.board) - if not 10 <= buildmanifest.version[0] <= 13: + if not 10 <= buildmanifest.version[0] <= 14: sys.exit( - f'[ERROR] iOS {buildmanifest.version} is not supported by Inferius. Exiting.' + f"[ERROR] iOS {'.'.join(str(_) for _ in buildmanifest.version)} is not supported by Inferius. Exiting." ) if buildmanifest.version[0] == 10 and restoremanifest.platform != 0x8960: From 37af301403d8f18cc3fad861095cdf8f6525f02c Mon Sep 17 00:00:00 2001 From: m1stadev Date: Sun, 6 Feb 2022 00:09:06 -0600 Subject: [PATCH 49/56] [main] create_ipsw: Use single quotes --- inferius | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/inferius b/inferius index f5d2eca..eeb98f6 100755 --- a/inferius +++ b/inferius @@ -30,7 +30,7 @@ def create_ipsw( try: bundle.verify_bundle(tmpdir, api.api, buildmanifest.buildid, api.board) except: - sys.exit(f"[ERROR] Bundle is invalid: {bundle.bundle}. Exiting.") + sys.exit(f'[ERROR] Bundle is invalid: {bundle.bundle}. Exiting.') else: bundle.fetch_bundle( From dcb8af19ea5a1d3a69a9be537c8c9e11e26011db Mon Sep 17 00:00:00 2001 From: m1stadev Date: Sun, 6 Feb 2022 00:30:01 -0600 Subject: [PATCH 50/56] [restore] Update - Removed send_bootchain, _irecv_send_file, and _irecv_send_cmd - sign_component -> sign_image - sign_image: Add support for setting tag - restore: Now require ramdisk + kernelcache images to be passed - restore: Use path object for logfile - restore: Now use futurerestore to enter pwnrecovery - save_blobs: Better error handling --- utils/restore.py | 129 +++++++++++++++++++++++++++-------------------- 1 file changed, 75 insertions(+), 54 deletions(-) diff --git a/utils/restore.py b/utils/restore.py index cc7eed7..b19db90 100644 --- a/utils/restore.py +++ b/utils/restore.py @@ -1,36 +1,38 @@ from pathlib import Path from utils.device import Device -from utils import errors, usb +from utils import errors import shutil import subprocess -import time class Restore: def __init__(self, device: Device): self.device = device - def _irecv_send_file(self, file: Path) -> None: - try: - subprocess.check_call( - ('irecovery', '-f', str(file)), stdout=subprocess.DEVNULL - ) - except: - raise errors.RestoreError( - f"Failed to send file to device: {file}." - ) from None - - def _irecv_send_cmd(self, cmd: str) -> None: - try: - subprocess.check_call( - ('irecovery', '-c', cmd), stdout=subprocess.DEVNULL - ) - except: - raise errors.RestoreError(f"Failed to send command to device: '{cmd}'.") - - def restore(self, ipsw: Path, cellular: bool, update: bool, *, sep: Path=None, manifest: Path=None) -> None: - args = ['futurerestore', '-d', '-t', str(self.blob)] + def restore( + self, + ipsw: Path, + rdsk: Path, + kern: Path, + cellular: bool, + update: bool, + *, + sep: Path = None, + manifest: Path = None, + ) -> None: + args = [ + 'futurerestore', + '-t', + str(self.signing_blob), + '--use-pwndfu', + '--no-cache', + '--skip-blob', + '--rdsk', + str(rdsk), + '--rkrn', + str(kern), + ] if sep and manifest: args.append('-s') @@ -52,40 +54,56 @@ def restore(self, ipsw: Path, cellular: bool, update: bool, *, sep: Path=None, m args.append(str(ipsw)) - Path('restore.log').unlink(missing_ok=True) + log = Path('restore.log') + log.unlink(missing_ok=True) + try: futurerestore = subprocess.check_output( - args, stderr=subprocess.STDOUT, universal_newlines=True + args, + stderr=subprocess.STDOUT, + universal_newlines=True, ) except subprocess.CalledProcessError as process: - with open('restore.log', 'w') as f: - f.write(f"{' '.join([str(_) for _ in args])}\n\n") + with log.open('w') as f: + f.write(f"{' '.join([_ for _ in args])}\n\n") f.write(process.stdout) raise errors.RestoreError( - "[ERROR] Restore failed. Log written to 'restore.log'. Exiting." + f"[ERROR] Restore failed. Log written to '{log.stem}'. Exiting." ) from None if Path(ipsw.stem).is_dir(): shutil.rmtree(ipsw.stem) if 'Done: restoring succeeded!' not in futurerestore: - with open('restore.log', 'w') as f: - f.write(f"{' '.join([str(_) for _ in args])}\n\n") + with log.open('w') as f: + f.write(f"{' '.join([_ for _ in args])}\n\n") f.write(futurerestore) raise errors.RestoreError( - "[ERROR] Restore failed. Log written to 'restore.log'. Exiting." + f"[ERROR] Restore failed. Log written to '{log.stem}'. Exiting." ) - def save_blobs(self, ecid: str, boardconfig: str, path: Path, *, manifest: Path=None, apnonce: str=None) -> None: + def save_blobs( + self, + ecid: str, + boardconfig: str, + path: Path, + *, + manifest: Path = None, + apnonce: str = None, + ) -> None: args = [ 'tsschecker', - '-d', self.device.identifier, - '-B', boardconfig, - '-e', ecid, - '--save-path', str(path), + '-d', + self.device.identifier, + '-B', + boardconfig, + '-e', + ecid, + '--save-path', + str(path), '-s', ] @@ -100,9 +118,13 @@ def save_blobs(self, ecid: str, boardconfig: str, path: Path, *, manifest: Path= args.append('--apnonce') args.append(apnonce) - tsschecker = subprocess.check_output(args, universal_newlines=True) - if 'Saved shsh blobs!' not in tsschecker: - raise errors.RestoreError('Failed to save SHSH blobs.') + try: + if 'Saved shsh blobs!' not in subprocess.check_output( + args, stderr=subprocess.STDOUT, universal_newlines=True + ): + raise errors.RestoreError('Failed to save SHSH blobs.') + except subprocess.CalledProcessError: + raise errors.RestoreError('Failed to save SHSH blobs.') from None if apnonce: for blob in path.glob('*.shsh*'): @@ -112,24 +134,23 @@ def save_blobs(self, ecid: str, boardconfig: str, path: Path, *, manifest: Path= else: self.signing_blob = tuple(path.glob('*.shsh*'))[0] - def send_bootchain(self, ibss: Path, ibec: Path) -> None: - # Reset device - device = usb.get_device(usb.DFU) - usb.reset_device(device) - usb.release_device(device) - - self._irecv_send_file(ibss) - self._irecv_send_file(ibec) - - if 0x8010 <= self.device.data['CPID']: - device = usb.get_device(usb.RECOVERY) - usb.send_cmd('go') - usb.release_device(device) + def sign_image(self, image: Path, output: Path, tag: str = None) -> None: + args = [ + 'img4tool', + '-c', + str(output), + '-p', + str(image), + '-s', + str(self.signing_blob), + ] - time.sleep(3) + if tag: + if len(tag) != 4: + raise errors.RestoreError(f'Invalid IMG4 tag: {tag}.') - def sign_component(self, file: Path, output: Path) -> None: - args = ('img4tool', '-c', output, '-p', file, '-s', self.signing_blob) + args.append('-t') + args.append(tag.lower()) try: subprocess.check_call(args, stdout=subprocess.DEVNULL) From bd8acdca6dfc39deff42f8eda86f21a5b49f858e Mon Sep 17 00:00:00 2001 From: m1stadev Date: Sun, 6 Feb 2022 00:30:23 -0600 Subject: [PATCH 51/56] [deps] check_bin: Require futurerestore 2.0.0 build 266 --- utils/dependencies.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/dependencies.py b/utils/dependencies.py index 3018604..8e9c067 100644 --- a/utils/dependencies.py +++ b/utils/dependencies.py @@ -19,7 +19,7 @@ def check_bin(self, binary: str) -> None: fr_ver = subprocess.run( (binary), stdout=subprocess.PIPE, universal_newlines=True ).stdout - if '-m1sta' not in fr_ver.splitlines()[1]: + if '266' not in fr_ver.splitlines()[0]: # TODO: Make this check less ass raise errors.DependencyError( 'This FutureRestore build cannot be used with Inferius.' ) From 7ea6afc26ab54acf3cb3fb9d22d4ed4646ad98e0 Mon Sep 17 00:00:00 2001 From: m1stadev Date: Sun, 6 Feb 2022 00:32:17 -0600 Subject: [PATCH 52/56] [bundle] apply_patches: Now (kinda horribly) skip patching iBSS/iBEC --- utils/bundle.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/utils/bundle.py b/utils/bundle.py index e5f3f4d..29f581d 100644 --- a/utils/bundle.py +++ b/utils/bundle.py @@ -29,6 +29,9 @@ def apply_patches(self, ipsw: Path) -> None: continue for patch in bundle_data['patches'][patches]: + if any(_ in patch['file'] for _ in ('iBSS', 'iBEC')): + continue + bsdiff4.file_patch_inplace( ipsw / patch['file'], self.bundle / patch['patch'] ) From 37b5f74d867d3cd0ab9a4ef0677f138f7cade460 Mon Sep 17 00:00:00 2001 From: m1stadev Date: Sun, 6 Feb 2022 00:33:42 -0600 Subject: [PATCH 53/56] [main] restore_ipsw: Update - No longer extract, sign & send iBSS/iBEC - Extract ramdisk + kernel & sign - Cleanup iOS 10-specific code --- inferius | 53 +++++++++++++++++++++++++++++------------------------ 1 file changed, 29 insertions(+), 24 deletions(-) diff --git a/inferius b/inferius index eeb98f6..022d385 100755 --- a/inferius +++ b/inferius @@ -68,8 +68,11 @@ def create_ipsw( return ipsw -def restore_ipsw(api: API, buildmanifest: Manifest, ipsw: IPSW, updating: bool, tmpdir: Path): - print('Restoring custom IPSW') + +def restore_ipsw( + api: API, buildmanifest: Manifest, ipsw: IPSW, updating: bool, tmpdir: Path +): + print('\nRestoring custom IPSW\n') Checks() device = Device(api.device) @@ -88,26 +91,18 @@ def restore_ipsw(api: API, buildmanifest: Manifest, ipsw: IPSW, updating: bool, ) print('[3] Extracting bootchain...') - ibss = buildmanifest.get_path('iBSS') - ipsw.extract_file(ibss, tmpdir / 'ibss.im4p') + ramdisk = tmpdir / 'rdsk.dmg' + ipsw.extract_file(buildmanifest.get_path('RestoreRamDisk'), tmpdir / 'rdsk.dmg') - ibec = buildmanifest.get_path('iBEC') - ipsw.extract_file(ibec, tmpdir / 'ibec.im4p') + kernel = tmpdir / 'krnl.im4p' + ipsw.extract_file(buildmanifest.get_path('KernelCache'), tmpdir / 'krnl.im4p') - print('[4] Signing bootchain...') + print('[4] Saving SHSH blobs...') if buildmanifest.version[0] == 10: otamanifest = api.fetch_ota_manifest(api.device, tmpdir) - restore.save_blobs(device.data['ECID'], device.board, tmpdir, manifest=otamanifest) else: - restore.save_blobs(device.data['ECID'], device.board, tmpdir) - - restore.sign_component(tmpdir / 'ibss.im4p', tmpdir / 'ibss.img4') - restore.sign_component(tmpdir / 'ibec.im4p', tmpdir / 'ibec.img4') + otamanifest = None - print('[5] Sending bootchain...') - restore.send_bootchain(tmpdir / 'ibss.img4', tmpdir / 'ibec.img4') - - print('[6] Saving SHSH blobs...') if buildmanifest.version[0] == 10: restore.save_blobs( device.data['ECID'], @@ -121,18 +116,28 @@ def restore_ipsw(api: API, buildmanifest: Manifest, ipsw: IPSW, updating: bool, device.data['ECID'], device.board, tmpdir, apnonce=device.data['NONC'] ) - print('[7] Restoring...') + print('[5] Signing bootchain...') + rdsk_img = tmpdir / 'rdsk.dmg.img4' + kern_img = tmpdir / 'rkrn.img4' + restore.sign_image(ramdisk, rdsk_img) + restore.sign_image(kernel, kern_img, tag='rkrn') + + print('[6] Restoring...') if buildmanifest.version[0] == 10: manifest = Manifest(api.partialzip_read('14G60', 'BuildManifest.plist')) - sep = api.partialzip_extract('14G60', manifest.get_path('RestoreSEP'), tmpdir) - - restore.restore( - ipsw.ipsw, device.baseband, updating, sep=sep, manifest=otamanifest - ) - else: - restore.restore(ipsw.ipsw, device.baseband, updating) + sep = None + + restore.restore( + ipsw.ipsw, + rdsk_img, + kern_img, + device.baseband, + updating, + sep=sep, + manifest=otamanifest, + ) print( f'Finished restoring custom IPSW to your device. Please boot your device using one of the tools listed in the README.' From 0d9ae97c3f6eb76d86fdceba862a4f7ffdd4c0c8 Mon Sep 17 00:00:00 2001 From: m1stadev Date: Sun, 6 Feb 2022 00:52:00 -0600 Subject: [PATCH 54/56] [restore] Update - Fix some bugs - save_blobs: No longer have optional 'apnonce' arg - restore: Update error message - No longer use Restore.signing_blob attribute --- utils/restore.py | 28 ++++++---------------------- 1 file changed, 6 insertions(+), 22 deletions(-) diff --git a/utils/restore.py b/utils/restore.py index b19db90..13c55e2 100644 --- a/utils/restore.py +++ b/utils/restore.py @@ -24,7 +24,7 @@ def restore( args = [ 'futurerestore', '-t', - str(self.signing_blob), + str(self.blob), '--use-pwndfu', '--no-cache', '--skip-blob', @@ -70,7 +70,7 @@ def restore( f.write(process.stdout) raise errors.RestoreError( - f"[ERROR] Restore failed. Log written to '{log.stem}'. Exiting." + f"[ERROR] Restore failed. Log written to '{log.name}'. Exiting." ) from None if Path(ipsw.stem).is_dir(): @@ -82,17 +82,11 @@ def restore( f.write(futurerestore) raise errors.RestoreError( - f"[ERROR] Restore failed. Log written to '{log.stem}'. Exiting." + f"[ERROR] Restore failed. Log written to '{log.name}'. Exiting." ) def save_blobs( - self, - ecid: str, - boardconfig: str, - path: Path, - *, - manifest: Path = None, - apnonce: str = None, + self, ecid: str, boardconfig: str, path: Path, *, manifest: Path = None ) -> None: args = [ 'tsschecker', @@ -114,10 +108,6 @@ def save_blobs( args.append('-l') args.append('--nocache') - if apnonce: - args.append('--apnonce') - args.append(apnonce) - try: if 'Saved shsh blobs!' not in subprocess.check_output( args, stderr=subprocess.STDOUT, universal_newlines=True @@ -126,13 +116,7 @@ def save_blobs( except subprocess.CalledProcessError: raise errors.RestoreError('Failed to save SHSH blobs.') from None - if apnonce: - for blob in path.glob('*.shsh*'): - if blob != self.signing_blob: - self.blob = blob - break - else: - self.signing_blob = tuple(path.glob('*.shsh*'))[0] + self.blob = tuple(path.glob('*.shsh*'))[0] def sign_image(self, image: Path, output: Path, tag: str = None) -> None: args = [ @@ -142,7 +126,7 @@ def sign_image(self, image: Path, output: Path, tag: str = None) -> None: '-p', str(image), '-s', - str(self.signing_blob), + str(self.blob), ] if tag: From 2c9f0083d223bb426803b3a8fe5bbe23eed14c89 Mon Sep 17 00:00:00 2001 From: m1stadev Date: Sun, 6 Feb 2022 01:04:17 -0600 Subject: [PATCH 55/56] [main] Update - Update logs - restore_ipsw: Save SHSH blob inside of a separate folder in tmpdir - restore_ipsw: Don't pass apnonce to save_blobs --- inferius | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/inferius b/inferius index 022d385..b6ef214 100755 --- a/inferius +++ b/inferius @@ -22,7 +22,7 @@ sys.tracebacklimit = 0 def create_ipsw( api: API, buildmanifest: Manifest, bundle: Bundle, ipsw: IPSW, tmpdir: Path ): - print('\nCreating custom IPSW\n') + print('Creating custom IPSW') print('[1] Grabbing Firmware Bundle...') if bundle.bundle is not None: @@ -72,7 +72,7 @@ def create_ipsw( def restore_ipsw( api: API, buildmanifest: Manifest, ipsw: IPSW, updating: bool, tmpdir: Path ): - print('\nRestoring custom IPSW\n') + print('Restoring custom IPSW') Checks() device = Device(api.device) @@ -103,18 +103,14 @@ def restore_ipsw( else: otamanifest = None + shsh_path = tmpdir / 'shsh' + shsh_path.mkdir() if buildmanifest.version[0] == 10: restore.save_blobs( - device.data['ECID'], - device.board, - tmpdir, - manifest=otamanifest, - apnonce=device.data['NONC'], + device.data['ECID'], device.board, shsh_path, manifest=otamanifest ) else: - restore.save_blobs( - device.data['ECID'], device.board, tmpdir, apnonce=device.data['NONC'] - ) + restore.save_blobs(device.data['ECID'], device.board, shsh_path) print('[5] Signing bootchain...') rdsk_img = tmpdir / 'rdsk.dmg.img4' @@ -140,7 +136,7 @@ def restore_ipsw( ) print( - f'Finished restoring custom IPSW to your device. Please boot your device using one of the tools listed in the README.' + f'Finished restoring custom IPSW to your device. Please boot your device using one of the tools listed in the README.\n' ) @@ -187,6 +183,8 @@ def main() -> None: if platform.system() == 'Windows': sys.exit('[ERROR] Inferius does not support Windows. Exiting.') + print('Inferius - Create & Restore 64-bit custom IPSWs\n') + api = API(args.device[0]) api.fetch_api() api.fetch_board() From b5cf43fca1ff3542943eb1f933e0d1e843c3e507 Mon Sep 17 00:00:00 2001 From: m1stadev Date: Tue, 7 Jun 2022 12:07:57 -0500 Subject: [PATCH 56/56] Update - Move to Poetry for packaging - Update some code --- inferius => inferius/__main__.py | 32 ++- {utils => inferius}/api.py | 0 {utils => inferius}/bundle.py | 0 inferius/dependencies.py | 38 ++++ inferius/device.py | 50 +++++ {utils => inferius}/devices.json | 0 {utils => inferius}/errors.py | 0 {utils => inferius}/ipsw.py | 0 {utils => inferius}/manifest.py | 0 {utils => inferius}/restore.py | 0 {utils => inferius}/usb.py | 0 poetry.lock | 355 +++++++++++++++++++++++++++++++ pyproject.toml | 45 ++++ requirements.txt | 1 + utils/dependencies.py | 35 --- utils/device.py | 76 ------- 16 files changed, 501 insertions(+), 131 deletions(-) rename inferius => inferius/__main__.py (88%) rename {utils => inferius}/api.py (100%) rename {utils => inferius}/bundle.py (100%) create mode 100644 inferius/dependencies.py create mode 100644 inferius/device.py rename {utils => inferius}/devices.json (100%) rename {utils => inferius}/errors.py (100%) rename {utils => inferius}/ipsw.py (100%) rename {utils => inferius}/manifest.py (100%) rename {utils => inferius}/restore.py (100%) rename {utils => inferius}/usb.py (100%) create mode 100644 poetry.lock create mode 100644 pyproject.toml delete mode 100644 utils/dependencies.py delete mode 100644 utils/device.py diff --git a/inferius b/inferius/__main__.py similarity index 88% rename from inferius rename to inferius/__main__.py index b6ef214..591bfcc 100755 --- a/inferius +++ b/inferius/__main__.py @@ -1,12 +1,15 @@ #!/usr/bin/env python3 from pathlib import Path +from pymobiledevice3.restore.device import Device +from pymobiledevice3.restore.ipsw.ipsw import IPSW +from pymobiledevice3.irecv import IRecv from utils.api import API from utils.bundle import Bundle -from utils.dependencies import Checks -from utils.device import Device -from utils.ipsw import IPSW + +# from utils.device import Device +# from utils.ipsw import IPSW from utils.manifest import Manifest, RestoreManifest from utils.restore import Restore @@ -70,32 +73,21 @@ def create_ipsw( def restore_ipsw( - api: API, buildmanifest: Manifest, ipsw: IPSW, updating: bool, tmpdir: Path + api: API, buildmanifest: Manifest, ipsw: Path, updating: bool, tmpdir: Path ): print('Restoring custom IPSW') - Checks() - device = Device(api.device) - restore = Restore(device) - - # Make new instance of buildmanifest - buildmanifest = Manifest(buildmanifest.dump(), device.board) - print('[1] Verifying custom IPSW...') - ipsw.verify_custom_ipsw(api, updating) + # ipsw.verify_custom_ipsw(api, updating) print('[2] Checking for device in pwned DFU...') - if 'PWND' not in device.data.keys(): + device = Device(irecv=IRecv()) + if 'PWND' not in device.irecv._device_info.keys(): sys.exit( '[ERROR] Attempting to restore a device not in Pwned DFU mode. Exiting.' ) - print('[3] Extracting bootchain...') - ramdisk = tmpdir / 'rdsk.dmg' - ipsw.extract_file(buildmanifest.get_path('RestoreRamDisk'), tmpdir / 'rdsk.dmg') - - kernel = tmpdir / 'krnl.im4p' - ipsw.extract_file(buildmanifest.get_path('KernelCache'), tmpdir / 'krnl.im4p') + sys.exit('im out!') print('[4] Saving SHSH blobs...') if buildmanifest.version[0] == 10: @@ -230,7 +222,7 @@ def main() -> None: custom_ipsw = ipsw if args.restore: - restore_ipsw(api, buildmanifest, custom_ipsw, args.update, tmpdir) + restore_ipsw(api, buildmanifest, args.ipsw[0], args.update, tmpdir) if __name__ == '__main__': diff --git a/utils/api.py b/inferius/api.py similarity index 100% rename from utils/api.py rename to inferius/api.py diff --git a/utils/bundle.py b/inferius/bundle.py similarity index 100% rename from utils/bundle.py rename to inferius/bundle.py diff --git a/inferius/dependencies.py b/inferius/dependencies.py new file mode 100644 index 0000000..521cf94 --- /dev/null +++ b/inferius/dependencies.py @@ -0,0 +1,38 @@ +from utils import errors + +import shutil +import subprocess + + +class Checks: + def __init__(self): + self.check_bin("futurerestore") + self.check_bin("tsschecker") + self.check_bin("irecovery") + self.check_bin("img4tool") + + def check_bin(self, binary: str) -> None: + if shutil.which(binary) is None: + raise errors.DependencyError(f"Binary not found on your PC: {binary}.") + + if binary == "futurerestore": + fr_help = subprocess.check_output( + (binary, "--help"), stderr=subprocess.DEVNULL, universal_newlines=True + ) + + if ( + "--skip-blob" not in fr_help + ): # Inferius relies on the '--skip-blob' option + raise errors.DependencyError( + "This FutureRestore build is too old be used with Inferius." + ) + + elif binary == "irecovery": + try: + subprocess.check_call( + (binary, "-V"), stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL + ) + except subprocess.CalledProcessError: + raise errors.DependencyError( + "This iRecovery build is too old to be used with Inferius." + ) from None diff --git a/inferius/device.py b/inferius/device.py new file mode 100644 index 0000000..677b149 --- /dev/null +++ b/inferius/device.py @@ -0,0 +1,50 @@ +from pathlib import Path +from typing import Optional +from utils import errors, usb + +import json + + +class Device: + def __init__(self, identifier): + self.identifier = identifier + self.data = self._get_data() + self.board = self.fetch_board() + + def _get_data(self) -> Optional[dict]: + device = usb.get_device(usb.DFU) + + device_data = dict() + for item in ( + *device.serial_number.split(), + *usb.get_string(device, device.bDescriptorType).split(), + ): + name, value = item.split(':') + if name in ('NONC', 'SNON'): + device_data[name] = bytes.fromhex(value) + elif value.startswith('[') and value.endswith(']'): + device_data[name] = value + else: + device_data[name] = int(value, 16) + + usb.release_device(device) + return device_data + + def fetch_board(self) -> Optional[str]: + device_info = Path('utils/devices.json') + try: + with device_info.open('r') as f: + data = json.load(f) + + except FileNotFoundError: + raise errors.CorruptError( + f'File missing from Inferius: {device_info}' + ) from None + + return next( + _['boardconfig'] + for _ in data + if _['identifier'] == self.identifier + and _['bdid'] == self.data['BDID'] + and _['cpid'] == self.data['CPID'] + ) diff --git a/utils/devices.json b/inferius/devices.json similarity index 100% rename from utils/devices.json rename to inferius/devices.json diff --git a/utils/errors.py b/inferius/errors.py similarity index 100% rename from utils/errors.py rename to inferius/errors.py diff --git a/utils/ipsw.py b/inferius/ipsw.py similarity index 100% rename from utils/ipsw.py rename to inferius/ipsw.py diff --git a/utils/manifest.py b/inferius/manifest.py similarity index 100% rename from utils/manifest.py rename to inferius/manifest.py diff --git a/utils/restore.py b/inferius/restore.py similarity index 100% rename from utils/restore.py rename to inferius/restore.py diff --git a/utils/usb.py b/inferius/usb.py similarity index 100% rename from utils/usb.py rename to inferius/usb.py diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 0000000..a444c92 --- /dev/null +++ b/poetry.lock @@ -0,0 +1,355 @@ +[[package]] +name = "black" +version = "22.3.0" +description = "The uncompromising code formatter." +category = "dev" +optional = false +python-versions = ">=3.6.2" + +[package.dependencies] +click = ">=8.0.0" +mypy-extensions = ">=0.4.3" +pathspec = ">=0.9.0" +platformdirs = ">=2" +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} +typed-ast = {version = ">=1.4.2", markers = "python_version < \"3.8\" and implementation_name == \"cpython\""} +typing-extensions = {version = ">=3.10.0.0", markers = "python_version < \"3.10\""} + +[package.extras] +colorama = ["colorama (>=0.4.3)"] +d = ["aiohttp (>=3.7.4)"] +jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] +uvloop = ["uvloop (>=0.15.2)"] + +[[package]] +name = "certifi" +version = "2022.5.18.1" +description = "Python package for providing Mozilla's CA Bundle." +category = "main" +optional = false +python-versions = ">=3.6" + +[[package]] +name = "charset-normalizer" +version = "2.0.12" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +category = "main" +optional = false +python-versions = ">=3.5.0" + +[package.extras] +unicode_backport = ["unicodedata2"] + +[[package]] +name = "click" +version = "8.1.3" +description = "Composable command line interface toolkit" +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} +importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} + +[[package]] +name = "colorama" +version = "0.4.4" +description = "Cross-platform colored terminal text." +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" + +[[package]] +name = "idna" +version = "3.3" +description = "Internationalized Domain Names in Applications (IDNA)" +category = "main" +optional = false +python-versions = ">=3.5" + +[[package]] +name = "importlib-metadata" +version = "4.11.4" +description = "Read metadata from Python packages" +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""} +zipp = ">=0.5" + +[package.extras] +docs = ["sphinx", "jaraco.packaging (>=9)", "rst.linker (>=1.9)"] +perf = ["ipython"] +testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "packaging", "pyfakefs", "flufl.flake8", "pytest-perf (>=0.9.2)", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)", "importlib-resources (>=1.3)"] + +[[package]] +name = "isort" +version = "5.10.1" +description = "A Python utility / library to sort Python imports." +category = "dev" +optional = false +python-versions = ">=3.6.1,<4.0" + +[package.extras] +pipfile_deprecated_finder = ["pipreqs", "requirementslib"] +requirements_deprecated_finder = ["pipreqs", "pip-api"] +colors = ["colorama (>=0.4.3,<0.5.0)"] +plugins = ["setuptools"] + +[[package]] +name = "mypy-extensions" +version = "0.4.3" +description = "Experimental type system extensions for programs checked with the mypy typechecker." +category = "dev" +optional = false +python-versions = "*" + +[[package]] +name = "pathspec" +version = "0.9.0" +description = "Utility library for gitignore style pattern matching of file paths." +category = "dev" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" + +[[package]] +name = "platformdirs" +version = "2.5.2" +description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.extras] +docs = ["furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx-autodoc-typehints (>=1.12)", "sphinx (>=4)"] +test = ["appdirs (==1.4.4)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)", "pytest (>=6)"] + +[[package]] +name = "pyusb" +version = "1.2.1" +description = "Python USB access module" +category = "main" +optional = false +python-versions = ">=3.6.0" + +[[package]] +name = "remotezip" +version = "0.9.4" +description = "Access zip file content hosted remotely without downloading the full file." +category = "main" +optional = false +python-versions = "*" + +[package.dependencies] +requests = "*" +tabulate = "*" + +[[package]] +name = "requests" +version = "2.27.1" +description = "Python HTTP for Humans." +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" + +[package.dependencies] +certifi = ">=2017.4.17" +charset-normalizer = {version = ">=2.0.0,<2.1.0", markers = "python_version >= \"3\""} +idna = {version = ">=2.5,<4", markers = "python_version >= \"3\""} +urllib3 = ">=1.21.1,<1.27" + +[package.extras] +socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"] +use_chardet_on_py3 = ["chardet (>=3.0.2,<5)"] + +[[package]] +name = "tabulate" +version = "0.8.9" +description = "Pretty-print tabular data" +category = "main" +optional = false +python-versions = "*" + +[package.extras] +widechars = ["wcwidth"] + +[[package]] +name = "tomli" +version = "2.0.1" +description = "A lil' TOML parser" +category = "dev" +optional = false +python-versions = ">=3.7" + +[[package]] +name = "typed-ast" +version = "1.5.4" +description = "a fork of Python 2 and 3 ast modules with type comment support" +category = "dev" +optional = false +python-versions = ">=3.6" + +[[package]] +name = "typing-extensions" +version = "4.2.0" +description = "Backported and Experimental Type Hints for Python 3.7+" +category = "dev" +optional = false +python-versions = ">=3.7" + +[[package]] +name = "urllib3" +version = "1.26.9" +description = "HTTP library with thread-safe connection pooling, file post, and more." +category = "main" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" + +[package.extras] +brotli = ["brotlicffi (>=0.8.0)", "brotli (>=1.0.9)", "brotlipy (>=0.6.0)"] +secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"] +socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] + +[[package]] +name = "zipp" +version = "3.8.0" +description = "Backport of pathlib-compatible object wrapper for zip files" +category = "dev" +optional = false +python-versions = ">=3.7" + +[package.extras] +docs = ["sphinx", "jaraco.packaging (>=9)", "rst.linker (>=1.9)"] +testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy (>=0.9.1)"] + +[metadata] +lock-version = "1.1" +python-versions = "^3.7" +content-hash = "a373b182efd8cc1de3557e6a005a9378ab741e1e7a9b6931da8f7941babafac1" + +[metadata.files] +black = [ + {file = "black-22.3.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:2497f9c2386572e28921fa8bec7be3e51de6801f7459dffd6e62492531c47e09"}, + {file = "black-22.3.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5795a0375eb87bfe902e80e0c8cfaedf8af4d49694d69161e5bd3206c18618bb"}, + {file = "black-22.3.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e3556168e2e5c49629f7b0f377070240bd5511e45e25a4497bb0073d9dda776a"}, + {file = "black-22.3.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:67c8301ec94e3bcc8906740fe071391bce40a862b7be0b86fb5382beefecd968"}, + {file = "black-22.3.0-cp310-cp310-win_amd64.whl", hash = "sha256:fd57160949179ec517d32ac2ac898b5f20d68ed1a9c977346efbac9c2f1e779d"}, + {file = "black-22.3.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:cc1e1de68c8e5444e8f94c3670bb48a2beef0e91dddfd4fcc29595ebd90bb9ce"}, + {file = "black-22.3.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d2fc92002d44746d3e7db7cf9313cf4452f43e9ea77a2c939defce3b10b5c82"}, + {file = "black-22.3.0-cp36-cp36m-win_amd64.whl", hash = "sha256:a6342964b43a99dbc72f72812bf88cad8f0217ae9acb47c0d4f141a6416d2d7b"}, + {file = "black-22.3.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:328efc0cc70ccb23429d6be184a15ce613f676bdfc85e5fe8ea2a9354b4e9015"}, + {file = "black-22.3.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:06f9d8846f2340dfac80ceb20200ea5d1b3f181dd0556b47af4e8e0b24fa0a6b"}, + {file = "black-22.3.0-cp37-cp37m-win_amd64.whl", hash = "sha256:ad4efa5fad66b903b4a5f96d91461d90b9507a812b3c5de657d544215bb7877a"}, + {file = "black-22.3.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:e8477ec6bbfe0312c128e74644ac8a02ca06bcdb8982d4ee06f209be28cdf163"}, + {file = "black-22.3.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:637a4014c63fbf42a692d22b55d8ad6968a946b4a6ebc385c5505d9625b6a464"}, + {file = "black-22.3.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:863714200ada56cbc366dc9ae5291ceb936573155f8bf8e9de92aef51f3ad0f0"}, + {file = "black-22.3.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:10dbe6e6d2988049b4655b2b739f98785a884d4d6b85bc35133a8fb9a2233176"}, + {file = "black-22.3.0-cp38-cp38-win_amd64.whl", hash = "sha256:cee3e11161dde1b2a33a904b850b0899e0424cc331b7295f2a9698e79f9a69a0"}, + {file = "black-22.3.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:5891ef8abc06576985de8fa88e95ab70641de6c1fca97e2a15820a9b69e51b20"}, + {file = "black-22.3.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:30d78ba6bf080eeaf0b7b875d924b15cd46fec5fd044ddfbad38c8ea9171043a"}, + {file = "black-22.3.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ee8f1f7228cce7dffc2b464f07ce769f478968bfb3dd1254a4c2eeed84928aad"}, + {file = "black-22.3.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6ee227b696ca60dd1c507be80a6bc849a5a6ab57ac7352aad1ffec9e8b805f21"}, + {file = "black-22.3.0-cp39-cp39-win_amd64.whl", hash = "sha256:9b542ced1ec0ceeff5b37d69838106a6348e60db7b8fdd245294dc1d26136265"}, + {file = "black-22.3.0-py3-none-any.whl", hash = "sha256:bc58025940a896d7e5356952228b68f793cf5fcb342be703c3a2669a1488cb72"}, + {file = "black-22.3.0.tar.gz", hash = "sha256:35020b8886c022ced9282b51b5a875b6d1ab0c387b31a065b84db7c33085ca79"}, +] +certifi = [ + {file = "certifi-2022.5.18.1-py3-none-any.whl", hash = "sha256:f1d53542ee8cbedbe2118b5686372fb33c297fcd6379b050cca0ef13a597382a"}, + {file = "certifi-2022.5.18.1.tar.gz", hash = "sha256:9c5705e395cd70084351dd8ad5c41e65655e08ce46f2ec9cf6c2c08390f71eb7"}, +] +charset-normalizer = [ + {file = "charset-normalizer-2.0.12.tar.gz", hash = "sha256:2857e29ff0d34db842cd7ca3230549d1a697f96ee6d3fb071cfa6c7393832597"}, + {file = "charset_normalizer-2.0.12-py3-none-any.whl", hash = "sha256:6881edbebdb17b39b4eaaa821b438bf6eddffb4468cf344f09f89def34a8b1df"}, +] +click = [ + {file = "click-8.1.3-py3-none-any.whl", hash = "sha256:bb4d8133cb15a609f44e8213d9b391b0809795062913b383c62be0ee95b1db48"}, + {file = "click-8.1.3.tar.gz", hash = "sha256:7682dc8afb30297001674575ea00d1814d808d6a36af415a82bd481d37ba7b8e"}, +] +colorama = [ + {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, + {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, +] +idna = [ + {file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"}, + {file = "idna-3.3.tar.gz", hash = "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"}, +] +importlib-metadata = [ + {file = "importlib_metadata-4.11.4-py3-none-any.whl", hash = "sha256:c58c8eb8a762858f49e18436ff552e83914778e50e9d2f1660535ffb364552ec"}, + {file = "importlib_metadata-4.11.4.tar.gz", hash = "sha256:5d26852efe48c0a32b0509ffbc583fda1a2266545a78d104a6f4aff3db17d700"}, +] +isort = [ + {file = "isort-5.10.1-py3-none-any.whl", hash = "sha256:6f62d78e2f89b4500b080fe3a81690850cd254227f27f75c3a0c491a1f351ba7"}, + {file = "isort-5.10.1.tar.gz", hash = "sha256:e8443a5e7a020e9d7f97f1d7d9cd17c88bcb3bc7e218bf9cf5095fe550be2951"}, +] +mypy-extensions = [ + {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, + {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, +] +pathspec = [ + {file = "pathspec-0.9.0-py2.py3-none-any.whl", hash = "sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a"}, + {file = "pathspec-0.9.0.tar.gz", hash = "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1"}, +] +platformdirs = [ + {file = "platformdirs-2.5.2-py3-none-any.whl", hash = "sha256:027d8e83a2d7de06bbac4e5ef7e023c02b863d7ea5d079477e722bb41ab25788"}, + {file = "platformdirs-2.5.2.tar.gz", hash = "sha256:58c8abb07dcb441e6ee4b11d8df0ac856038f944ab98b7be6b27b2a3c7feef19"}, +] +pyusb = [ + {file = "pyusb-1.2.1-py3-none-any.whl", hash = "sha256:2b4c7cb86dbadf044dfb9d3a4ff69fd217013dbe78a792177a3feb172449ea36"}, + {file = "pyusb-1.2.1.tar.gz", hash = "sha256:a4cc7404a203144754164b8b40994e2849fde1cfff06b08492f12fff9d9de7b9"}, +] +remotezip = [ + {file = "remotezip-0.9.4.tar.gz", hash = "sha256:8bed7d1fd3f096c15e480d05492d84537ac401b473ba109e0b30611452ac8e57"}, +] +requests = [ + {file = "requests-2.27.1-py2.py3-none-any.whl", hash = "sha256:f22fa1e554c9ddfd16e6e41ac79759e17be9e492b3587efa038054674760e72d"}, + {file = "requests-2.27.1.tar.gz", hash = "sha256:68d7c56fd5a8999887728ef304a6d12edc7be74f1cfa47714fc8b414525c9a61"}, +] +tabulate = [ + {file = "tabulate-0.8.9-py3-none-any.whl", hash = "sha256:d7c013fe7abbc5e491394e10fa845f8f32fe54f8dc60c6622c6cf482d25d47e4"}, + {file = "tabulate-0.8.9.tar.gz", hash = "sha256:eb1d13f25760052e8931f2ef80aaf6045a6cceb47514db8beab24cded16f13a7"}, +] +tomli = [ + {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, + {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, +] +typed-ast = [ + {file = "typed_ast-1.5.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:669dd0c4167f6f2cd9f57041e03c3c2ebf9063d0757dc89f79ba1daa2bfca9d4"}, + {file = "typed_ast-1.5.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:211260621ab1cd7324e0798d6be953d00b74e0428382991adfddb352252f1d62"}, + {file = "typed_ast-1.5.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:267e3f78697a6c00c689c03db4876dd1efdfea2f251a5ad6555e82a26847b4ac"}, + {file = "typed_ast-1.5.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:c542eeda69212fa10a7ada75e668876fdec5f856cd3d06829e6aa64ad17c8dfe"}, + {file = "typed_ast-1.5.4-cp310-cp310-win_amd64.whl", hash = "sha256:a9916d2bb8865f973824fb47436fa45e1ebf2efd920f2b9f99342cb7fab93f72"}, + {file = "typed_ast-1.5.4-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:79b1e0869db7c830ba6a981d58711c88b6677506e648496b1f64ac7d15633aec"}, + {file = "typed_ast-1.5.4-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a94d55d142c9265f4ea46fab70977a1944ecae359ae867397757d836ea5a3f47"}, + {file = "typed_ast-1.5.4-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:183afdf0ec5b1b211724dfef3d2cad2d767cbefac291f24d69b00546c1837fb6"}, + {file = "typed_ast-1.5.4-cp36-cp36m-win_amd64.whl", hash = "sha256:639c5f0b21776605dd6c9dbe592d5228f021404dafd377e2b7ac046b0349b1a1"}, + {file = "typed_ast-1.5.4-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:cf4afcfac006ece570e32d6fa90ab74a17245b83dfd6655a6f68568098345ff6"}, + {file = "typed_ast-1.5.4-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ed855bbe3eb3715fca349c80174cfcfd699c2f9de574d40527b8429acae23a66"}, + {file = "typed_ast-1.5.4-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:6778e1b2f81dfc7bc58e4b259363b83d2e509a65198e85d5700dfae4c6c8ff1c"}, + {file = "typed_ast-1.5.4-cp37-cp37m-win_amd64.whl", hash = "sha256:0261195c2062caf107831e92a76764c81227dae162c4f75192c0d489faf751a2"}, + {file = "typed_ast-1.5.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2efae9db7a8c05ad5547d522e7dbe62c83d838d3906a3716d1478b6c1d61388d"}, + {file = "typed_ast-1.5.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7d5d014b7daa8b0bf2eaef684295acae12b036d79f54178b92a2b6a56f92278f"}, + {file = "typed_ast-1.5.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:370788a63915e82fd6f212865a596a0fefcbb7d408bbbb13dea723d971ed8bdc"}, + {file = "typed_ast-1.5.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:4e964b4ff86550a7a7d56345c7864b18f403f5bd7380edf44a3c1fb4ee7ac6c6"}, + {file = "typed_ast-1.5.4-cp38-cp38-win_amd64.whl", hash = "sha256:683407d92dc953c8a7347119596f0b0e6c55eb98ebebd9b23437501b28dcbb8e"}, + {file = "typed_ast-1.5.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:4879da6c9b73443f97e731b617184a596ac1235fe91f98d279a7af36c796da35"}, + {file = "typed_ast-1.5.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:3e123d878ba170397916557d31c8f589951e353cc95fb7f24f6bb69adc1a8a97"}, + {file = "typed_ast-1.5.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ebd9d7f80ccf7a82ac5f88c521115cc55d84e35bf8b446fcd7836eb6b98929a3"}, + {file = "typed_ast-1.5.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:98f80dee3c03455e92796b58b98ff6ca0b2a6f652120c263efdba4d6c5e58f72"}, + {file = "typed_ast-1.5.4-cp39-cp39-win_amd64.whl", hash = "sha256:0fdbcf2fef0ca421a3f5912555804296f0b0960f0418c440f5d6d3abb549f3e1"}, + {file = "typed_ast-1.5.4.tar.gz", hash = "sha256:39e21ceb7388e4bb37f4c679d72707ed46c2fbf2a5609b8b8ebc4b067d977df2"}, +] +typing-extensions = [ + {file = "typing_extensions-4.2.0-py3-none-any.whl", hash = "sha256:6657594ee297170d19f67d55c05852a874e7eb634f4f753dbd667855e07c1708"}, + {file = "typing_extensions-4.2.0.tar.gz", hash = "sha256:f1c24655a0da0d1b67f07e17a5e6b2a105894e6824b92096378bb3668ef02376"}, +] +urllib3 = [ + {file = "urllib3-1.26.9-py2.py3-none-any.whl", hash = "sha256:44ece4d53fb1706f667c9bd1c648f5469a2ec925fcf3a776667042d645472c14"}, + {file = "urllib3-1.26.9.tar.gz", hash = "sha256:aabaf16477806a5e1dd19aa41f8c2b7950dd3c746362d7e3223dbe6de6ac448e"}, +] +zipp = [ + {file = "zipp-3.8.0-py3-none-any.whl", hash = "sha256:c4f6e5bbf48e74f7a38e7cc5b0480ff42b0ae5178957d564d18932525d5cf099"}, + {file = "zipp-3.8.0.tar.gz", hash = "sha256:56bf8aadb83c24db6c4b577e13de374ccfb67da2078beba1d037c17980bf43ad"}, +] diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..94e953b --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,45 @@ +[tool.poetry] +name = "inferius" +version = "0.1.0" +description = "A Python CLI tool for creating & restoring custom firmwares to an iOS device." +authors = ["m1stadev "] +license = "MIT" +readme = "README.md" +repository = "https://github.com/m1stadev/Inferius" +keywords = ["restore", "pwndfu", "ios", "checkm8"] +classifiers = [ + "Development Status :: 4 - Beta", + "Environment :: Console", + "Programming Language :: Python :: 3.7", + "Topic :: Utilities" +] +packages = [ + { include = "inferius" } +] + +[tool.poetry.scripts] +pyimg4 = "inferius.__main__:main" + +[tool.poetry.dependencies] +python = "^3.7" +pyusb = "^1.2.1" +remotezip = "^0.9.4" +requests = "^2.27.1" + +[tool.poetry.dev-dependencies] +black = {version = "^22.1.0", python = "^3.7"} +isort = {version = "^5.10.1", python = "^3.7"} + +[tool.black] +skip-string-normalization = true + +[tool.isort] +profile = "black" +src_paths = ["inferius"] + +[tool.poetry.urls] +"Bug Tracker" = "https://github.com/m1stadev/Inferius/issues" + +[build-system] +requires = ["poetry-core>=1.0.0"] +build-backend = "poetry.core.masonry.api" diff --git a/requirements.txt b/requirements.txt index 0d223d7..c1ab475 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,4 +2,5 @@ bsdiff4 mwclient remotezip requests +pymobiledevice3 pyusb \ No newline at end of file diff --git a/utils/dependencies.py b/utils/dependencies.py deleted file mode 100644 index 8e9c067..0000000 --- a/utils/dependencies.py +++ /dev/null @@ -1,35 +0,0 @@ -from utils import errors - -import shutil -import subprocess - - -class Checks: - def __init__(self): - self.check_bin('futurerestore') - self.check_bin('tsschecker') - self.check_bin('irecovery') - self.check_bin('img4tool') - - def check_bin(self, binary: str) -> None: - if shutil.which(binary) is None: - raise errors.DependencyError(f'Binary not found on your PC: {binary}.') - - if binary == 'futurerestore': - fr_ver = subprocess.run( - (binary), stdout=subprocess.PIPE, universal_newlines=True - ).stdout - if '266' not in fr_ver.splitlines()[0]: # TODO: Make this check less ass - raise errors.DependencyError( - 'This FutureRestore build cannot be used with Inferius.' - ) - - elif binary == 'irecovery': - try: - subprocess.check_call( - (binary, '-V'), stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL - ) - except subprocess.CalledProcessError: - raise errors.DependencyError( - 'This iRecovery build is too old to be used with Inferius.' - ) from None diff --git a/utils/device.py b/utils/device.py deleted file mode 100644 index 7c6c295..0000000 --- a/utils/device.py +++ /dev/null @@ -1,76 +0,0 @@ -from pathlib import Path -from typing import Optional -from utils import errors, usb - -import json - - -class Device: - def __init__(self, identifier): - self.identifier = identifier - self.data = self._get_data() - self.board = self.fetch_board() - - def _get_data(self) -> Optional[dict]: - device = usb.get_device(usb.DFU) - - device_data = dict() - for item in device.serial_number.split(): - device_data[item.split(':')[0]] = item.split(':')[1] - - device_data['ECID'] = hex(int(device_data['ECID'], 16)) - - for i in ('CPID', 'CPRV', 'BDID', 'CPFM', 'SCEP', 'IBFL'): - device_data[i] = int(device_data[i], 16) - - for item in usb.get_string(device, device.bDescriptorType).split(): - device_data[item.split(':')[0]] = item.split(':')[1] - - usb.release_device(device) - return device_data - - @property - def baseband(self) -> bool: - if self.identifier.startswith('iPhone'): - return True - - else: - return ( - self.identifier - in ( # All (current) 64-bit cellular iPads vulerable to checkm8. - 'iPad4,2', - 'iPad4,3', - 'iPad4,5', - 'iPad4,6', - 'iPad4,8', - 'iPad4,9', - 'iPad5,2', - 'iPad5,4', - 'iPad6,4', - 'iPad6,8', - 'iPad6,12', - 'iPad7,2', - 'iPad7,4', - 'iPad7,6', - 'iPad7,12', - ) - ) - - def fetch_board(self) -> Optional[str]: - device_info = Path('utils/devices.json') - try: - with device_info.open('r') as f: - data = json.load(f) - - except FileNotFoundError: - raise errors.CorruptError( - f'File missing from Inferius: {device_info}' - ) from None - - return next( - _['boardconfig'] - for _ in data - if _['identifier'] == self.identifier - and _['bdid'] == self.data['BDID'] - and _['cpid'] == self.data['CPID'] - )