From 2eb1d2aa1d0d972cb3d81980d49960913cef20bc Mon Sep 17 00:00:00 2001 From: DoronZ Date: Wed, 27 Nov 2024 00:51:39 +0200 Subject: [PATCH 1/2] docs: fix README linting --- README.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 56ff1e4..23312ed 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,14 @@ +# ipsw-parser + [![Python application](https://github.com/doronz88/ipsw_parser/workflows/Python%20application/badge.svg)](https://github.com/doronz88/ipsw_parser/actions/workflows/python-app.yml "Python application action") [![Pypi version](https://img.shields.io/pypi/v/ipsw_parser.svg)](https://pypi.org/project/ipsw_parser/ "PyPi package") [![Downloads](https://static.pepy.tech/personalized-badge/ipsw_parser?period=total&units=none&left_color=grey&right_color=blue&left_text=Downloads)](https://pepy.tech/project/ipsw_parser) -# Overview +## Overview python3 utility for parsing and extracting data from IPSW. -# Installation +## Installation ```shell python3 -m pip install ipsw-parser @@ -15,7 +17,7 @@ python3 -m pip install ipsw-parser Additionally, if you installed [blacktop/ipsw](https://github.com/blacktop/ipsw), the IPSW extraction will also contain the split DSC. -# Usage +## Usage ``` Usage: ipsw-parser [OPTIONS] COMMAND [ARGS]... From 439b66d25b1f1132137f07ce9f82c1e1a92a5706 Mon Sep 17 00:00:00 2001 From: DoronZ Date: Wed, 27 Nov 2024 00:58:43 +0200 Subject: [PATCH 2/2] add remotezip support together with extract-kernel --- ipsw_parser/__main__.py | 36 +++++++++++++++++++++++++---------- ipsw_parser/build_identity.py | 12 ++++++++++++ requirements.txt | 3 ++- 3 files changed, 40 insertions(+), 11 deletions(-) diff --git a/ipsw_parser/__main__.py b/ipsw_parser/__main__.py index 0e6cf6a..a0a284d 100644 --- a/ipsw_parser/__main__.py +++ b/ipsw_parser/__main__.py @@ -1,11 +1,12 @@ #!/usr/bin/env python3 import logging from pathlib import Path -from typing import IO, Optional +from typing import Optional from zipfile import ZipFile import click import coloredlogs +from remotezip2 import RemoteZip from ipsw_parser.ipsw import IPSW @@ -23,6 +24,14 @@ PEM_DB_ENV_VAR = 'IPSW_PARSER_PEM_DB' + +def handle_ipsw_argument(ctx: click.Context, param: click.Argument, value: str) -> IPSW: + if value.startswith('http://') or value.startswith('https://'): + return IPSW(RemoteZip(value)) + return IPSW(ZipFile(Path(value).expanduser())) + + +ipsw_argument = click.argument('ipsw', callback=handle_ipsw_argument) pem_db_option = click.option('--pem-db', envvar=PEM_DB_ENV_VAR, help='Path DB file url (can be either a filesystem path or an HTTP URL). ' 'Alternatively, use the IPSW_PARSER_PEM_DB envvar.') @@ -35,10 +44,9 @@ def cli() -> None: @cli.command('info') -@click.argument('file', type=click.Path(exists=True, file_okay=True, dir_okay=False)) -def info(file) -> None: +@ipsw_argument +def info(ipsw) -> 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}') print(f'ProductBuildVersion: {ipsw.build_manifest.product_build_version}') @@ -51,28 +59,36 @@ def info(file) -> None: @cli.command('extract') -@click.argument('file', type=click.Path(exists=True, file_okay=True, dir_okay=False)) +@ipsw_argument @click.argument('output', type=click.Path(exists=False)) @pem_db_option -def extract(file: IO, output: str, pem_db: Optional[str]) -> None: +def extract(ipsw: IPSW, output: str, pem_db: Optional[str]) -> None: """ Extract .ipsw into filesystem layout """ output = Path(output) if not output.exists(): output.mkdir(parents=True, exist_ok=True) - ipsw = IPSW(ZipFile(file)) ipsw.build_manifest.build_identities[0].extract(output, pem_db=pem_db) ipsw.archive.extractall( path=output, members=[f for f in ipsw.archive.filelist if f.filename.startswith('Firmware')]) +@cli.command('extract-kernel') +@ipsw_argument +@click.argument('output', type=click.Path(exists=False)) +@click.option('--arch', help='Arch name to extract using lipo') +def extract_kernel(ipsw: IPSW, output: str, arch: Optional[str]) -> None: + """ Extract kernelcache from given .ipsw into given output filename """ + Path(output).write_bytes(ipsw.build_manifest.build_identities[0].get_kernelcache_payload(arch=arch)) + + @cli.command('device-support') -@click.argument('file', type=click.Path(exists=True, file_okay=True, dir_okay=False)) +@ipsw_argument @pem_db_option -def device_support(file: IO, pem_db: Optional[str]) -> None: +def device_support(ipsw: IPSW, pem_db: Optional[str]) -> None: """ Create DeviceSupport directory """ - IPSW(ZipFile(file)).create_device_support(pem_db=pem_db) + ipsw.create_device_support(pem_db=pem_db) if __name__ == '__main__': diff --git a/ipsw_parser/build_identity.py b/ipsw_parser/build_identity.py index 7ff9420..c7488cb 100644 --- a/ipsw_parser/build_identity.py +++ b/ipsw_parser/build_identity.py @@ -158,6 +158,18 @@ def extract_dsc(self, output: Path, pem_db: Optional[str] = None) -> None: sub_path=Path('System'), pem_db=pem_db) _split_dsc(output) + def get_kernelcache_payload(self, arch: Optional[str] = None) -> bytes: + im4p = IM4P(self.build_manifest.build_identities[0].get_component('KernelCache').data) + im4p.payload.decompress() + payload = im4p.payload.output().data + if arch is None: + return payload + + with TemporaryDirectory() as temp_dir: + kernel_output = Path(temp_dir) / 'kernel' + local['ipsw']('macho', 'lipo', '-a', arch, kernel_output) + return Path(next(kernel_output.parent.glob(f'*.{arch}'))).read_bytes() + def extract(self, output: Path, pem_db: Optional[str] = None) -> None: logger.info(f'extracting into: {output}') diff --git a/requirements.txt b/requirements.txt index 2fe8ee8..9d5bf5a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,4 +4,5 @@ coloredlogs cached_property plumbum pyimg4>=0.8.6 -requests \ No newline at end of file +requests +remotezip2 \ No newline at end of file