Skip to content

Commit

Permalink
Merge pull request #22 from doronz88/feature/device-support
Browse files Browse the repository at this point in the history
cli: add `device-support` subcommand
  • Loading branch information
doronz88 authored Jul 25, 2024
2 parents 9f8b235 + 3687823 commit 66ea12e
Show file tree
Hide file tree
Showing 4 changed files with 75 additions and 24 deletions.
9 changes: 6 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,15 @@ the split DSC.
# Usage

```
Usage: python -m ipsw_parser [OPTIONS] COMMAND [ARGS]...
Usage: ipsw-parser [OPTIONS] COMMAND [ARGS]...
CLI utility for extracting info from IPSW files
Options:
--help Show this message and exit.
Commands:
extract extract .ipsw into filesystem layout
info parse given .ipsw basic info
device-support Create DeviceSupport directory
extract Extract .ipsw into filesystem layout
info Parse given .ipsw basic info
```
36 changes: 31 additions & 5 deletions ipsw_parser/__main__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#!/usr/bin/env python3
import logging
import plistlib
from datetime import datetime
from pathlib import Path
from typing import IO
from zipfile import ZipFile
Expand All @@ -23,14 +25,15 @@


@click.group()
def cli():
def cli() -> None:
""" CLI utility for extracting info from IPSW files """
pass


@cli.command('info')
@click.argument('file', type=click.Path(exists=True, file_okay=True, dir_okay=False))
def info(file):
""" parse given .ipsw basic info """
def info(file) -> None:
""" Parse given .ipsw basic info """
ipsw = IPSW(ZipFile(file))
print(f'SupportedProductTypes: {ipsw.build_manifest.supported_product_types}')
print(f'ProductVersion: {ipsw.build_manifest.product_version}')
Expand All @@ -46,8 +49,8 @@ def info(file):
@cli.command('extract')
@click.argument('file', type=click.Path(exists=True, file_okay=True, dir_okay=False))
@click.argument('output', type=click.Path(exists=False))
def extract(file: IO, output: str):
""" extract .ipsw into filesystem layout """
def extract(file: IO, output: str) -> None:
""" Extract .ipsw into filesystem layout """
output = Path(output)

if not output.exists():
Expand All @@ -59,5 +62,28 @@ def extract(file: IO, output: str):
path=output, members=[f for f in ipsw.archive.filelist if f.filename.startswith('Firmware')])


@cli.command('device-support')
@click.argument('file', type=click.Path(exists=True, file_okay=True, dir_okay=False))
def device_support(file: IO) -> None:
""" Create DeviceSupport directory """
device_support_path = Path('~/Library/Developer/Xcode/iOS DeviceSupport').expanduser()
ipsw = IPSW(ZipFile(file))
device_support_path /= (f'{ipsw.build_manifest.supported_product_types[0]} '
f'{ipsw.build_manifest.product_version} ({ipsw.build_manifest.product_build_version})')
build_identity = ipsw.build_manifest.build_identities[0]
symbols_path = device_support_path / 'Symbols'
build_identity.extract_dsc(symbols_path)
for file in (symbols_path / 'private/preboot/Cryptexes/OS/System/Library/Caches/com.apple.dyld').iterdir():
file.unlink()
(device_support_path / 'Info.plist').write_bytes(plistlib.dumps({
'DSC Extractor Version': '1228.0.0.0.0',
'DateCollected': datetime.now(),
'Version': '16.0',
}))
(device_support_path / '.finalized').write_bytes(plistlib.dumps({}))
(device_support_path / '.processed_dyld_shared_cache_arm64e').touch()
(device_support_path / '.processing_lock').touch()


if __name__ == '__main__':
cli()
51 changes: 36 additions & 15 deletions ipsw_parser/build_identity.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from collections import UserDict
from pathlib import Path
from tempfile import TemporaryDirectory
from typing import List, Mapping
from typing import List, Mapping, Optional

from cached_property import cached_property
from plumbum import local
Expand All @@ -13,7 +13,7 @@
logger = logging.getLogger(__name__)


def _extract_dmg(buf: bytes, output: Path) -> None:
def _extract_dmg(buf: bytes, output: Path, sub_path: Optional[Path] = None) -> None:
ipsw = local['ipsw']
hdiutil = local['hdiutil']
# darwin system statistically have problems cleaning up after detaching the mountpoint
Expand All @@ -34,14 +34,34 @@ def _extract_dmg(buf: bytes, output: Path) -> None:
hdiutil('attach', '-mountpoint', mnt, dmg)

try:
shutil.copytree(mnt, output, symlinks=True, dirs_exist_ok=True)
if sub_path is None:
src = mnt
else:
src = mnt / sub_path
shutil.copytree(src, output, symlinks=True, dirs_exist_ok=True)
except shutil.Error:
# when overwriting the same files, some of them don't contain write permissions
pass

hdiutil('detach', '-force', mnt)


def _split_dsc(root: Path) -> None:
ipsw = local['ipsw']
dsc_paths = [
root / 'System/Library/Caches/com.apple.dyld/dyld_shared_cache_arm64',
root / 'System/Library/Caches/com.apple.dyld/dyld_shared_cache_arm64e',
root / 'private/preboot/Cryptexes/OS/System/Library/Caches/com.apple.dyld/dyld_shared_cache_arm64',
root / 'private/preboot/Cryptexes/OS/System/Library/Caches/com.apple.dyld/dyld_shared_cache_arm64e']

for dsc in dsc_paths:
if not dsc.exists():
continue

logger.info(f'splitting DSC: {dsc}')
ipsw('dyld', 'split', dsc, '-o', root)


class BuildIdentity(UserDict):
def __init__(self, build_manifest, data):
super().__init__(data)
Expand Down Expand Up @@ -116,8 +136,19 @@ def populate_tss_request_parameters(self, parameters: Mapping, additional_keys:
if requires_uid_mode is not None:
parameters['RequiresUIDMode'] = requires_uid_mode

def extract_dsc(self, output: Path) -> None:
build_identity = self.build_manifest.build_identities[0]
if not build_identity.has_component('Cryptex1,SystemOS'):
return

device_support_symbols_path = output / 'private/preboot/Cryptexes/OS/System'
device_support_symbols_path.mkdir(parents=True, exist_ok=True)

_extract_dmg(build_identity.get_component('Cryptex1,SystemOS').data, device_support_symbols_path,
sub_path=Path('System'))
_split_dsc(output)

def extract(self, output: Path) -> None:
ipsw = local['ipsw']
logger.info(f'extracting into: {output}')

build_identity = self.build_manifest.build_identities[0]
Expand Down Expand Up @@ -147,14 +178,4 @@ def extract(self, output: Path) -> None:
logger.info(f'extracting {name} into: {cryptex_path}')
_extract_dmg(build_identity.get_component(name).data, cryptex_path)

dsc_paths = [output / 'System/Library/Caches/com.apple.dyld/dyld_shared_cache_arm64',
output / 'System/Library/Caches/com.apple.dyld/dyld_shared_cache_arm64e',
output / 'System/Cryptexes/OS/System/Library/Caches/com.apple.dyld/dyld_shared_cache_arm64',
output / 'System/Cryptexes/OS/System/Library/Caches/com.apple.dyld/dyld_shared_cache_arm64e']

for dsc in dsc_paths:
if not dsc.exists():
continue

logger.info(f'splitting DSC: {dsc}')
ipsw('dyld', 'split', dsc, '-o', output)
_split_dsc(output)
3 changes: 2 additions & 1 deletion ipsw_parser/ipsw.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ class IPSW:
def __init__(self, archive: zipfile.ZipFile):
self.archive = archive
self._logger = logging.getLogger(__file__)
self.build_manifest = BuildManifest(self, self.archive.read(next(f for f in self.archive.namelist() if f.startswith('BuildManifest') and f.endswith('.plist'))))
self.build_manifest = BuildManifest(self, self.archive.read(
next(f for f in self.archive.namelist() if f.startswith('BuildManifest') and f.endswith('.plist'))))

@cached_property
def restore_version(self) -> bytes:
Expand Down

0 comments on commit 66ea12e

Please sign in to comment.