Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

cli: add device-support subcommand #22

Merged
merged 1 commit into from
Jul 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading