├── .gitignore ├── README.md ├── asr-fetcher.py ├── compare-kernels.py ├── extract-nonce.py ├── redeb.py ├── requirements.txt ├── resources └── bin │ ├── img4tool_linux │ └── img4tool_macos ├── restore-rootfs.py └── wiki-proxy.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.tmp 2 | ASR_Binaries/ 3 | .tmp/ -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # iOS Tools 2 | A repository with scripts that can be helpful for jailbreaking. 3 | 4 | ## Requirements 5 | - Python 3 6 | - A macOS or Linux computer (although not all scripts support Linux) 7 | - Required libraries: 8 | - `pip3 install -r requirements.txt` 9 | 10 | ## [`extract-nonce.py`](https://github.com/marijuanARM/ios-tools/blob/master/extract-nonce.py) 11 | - A script to extract an apnonce and sepnonce from an SHSH blob. 12 | 13 | | Option (short) | Option (long) | Description | 14 | |----------------|---------------|-------------| 15 | | `-h` | `--help` | Shows all options avaiable | 16 | | `-s` | `--shsh SHSH` | Path to SHSH | 17 | 18 | ## [`asr-fetcher.py`](https://github.com/marijuanARM/ios-tools/blob/master/asr-fetcher.py) 19 | - A script (macOS only) to extract ASR binaries from each iOS version's IPSW for an iOS device. 20 | 21 | | Option (short) | Option (long) | Description | 22 | |----------------|---------------|-------------| 23 | | `-h` | `--help` | Shows all options avaiable | 24 | | `-d` | `--device DEVICE` | Device identifier (ex. iPhone9,3) | 25 | | `-i` | `--version VERSION` | Fetch ASR binaries from a single iOS version's IPSW (ex. 13.5) | 26 | 27 | ## [`redeb.py`](https://github.com/marijuanARM/ios-tools/blob/master/redeb.py) 28 | - A script to package installed debian packages back into a DEB. Works on both iOS and Debian-based Linux distributions. 29 | 30 | | Option (short) | Option (long) | Description | 31 | |----------------|---------------|-------------| 32 | | `-h` | `--help` | Shows all options avaiable | 33 | | `-p` | `--package PACKAGE` | Path to installed package | 34 | 35 | ## [`wiki-proxy.py`](https://github.com/marijuanARM/ios-tools/blob/master/wiki-proxy.py) 36 | - A rewrite of [tihmstar](https://twitter.com/tihmstar)'s [wikiproxy.py](https://github.com/tihmstar/libipatcher/blob/master/wikiproxy.py) that utilizes MediaWiki browsing libraries instead of parsing raw HTML documents from The iPhone Wiki with firmware keys. -------------------------------------------------------------------------------- /asr-fetcher.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import atexit 4 | import argparse 5 | import os 6 | import platform 7 | import remotezip 8 | import requests 9 | import shutil 10 | import subprocess 11 | import sys 12 | 13 | if not platform.system() == 'Darwin': 14 | sys.exit('[ERROR] Unsupported OS. Exiting...') 15 | 16 | def cleanup(): 17 | if os.path.isdir('.tmp/asr-fetcher/ramdisk'): 18 | subprocess.run(('hdiutil', 'detach', '.tmp/asr-fetcher/ramdisk'), stdout=subprocess.DEVNULL) 19 | elif os.path.isdir('.tmp'): 20 | shutil.rmtree('.tmp') 21 | 22 | def device_check(device): 23 | api = requests.get('https://api.ipsw.me/v2.1/firmwares.json/condensed') 24 | data = api.json() 25 | if device in data['devices']: 26 | return True 27 | 28 | return False 29 | 30 | atexit.register(cleanup) 31 | 32 | def main(): 33 | parser = argparse.ArgumentParser(description='ASR Fetcher', usage="./asr_fetcher.py -d 'device' [-i 'version']") 34 | parser.add_argument('-d', '--device', help='Device identifier (ex. iPhone9,3)', nargs=1) 35 | parser.add_argument('-i', '--version', help="Fetch ASR binaries from a single iOS version's IPSW (ex. 13.5)", nargs=1) 36 | args = parser.parse_args() 37 | 38 | if not args.device: 39 | sys.exit(parser.print_help(sys.stderr)) 40 | 41 | if not device_check(args.device[0]): 42 | sys.exit(f'[ERROR] Device {args.device[0]} does not exist. Exiting...') 43 | 44 | hdiutil_check = subprocess.run(('which', 'hdiutil'), stdout=subprocess.DEVNULL) 45 | if hdiutil_check.returncode != 0: 46 | sys.exit("[ERROR] hdiutil binary not found. Something is very wrong (unless you're running this on an iOS device). Exiting...") 47 | 48 | img4lib_check = subprocess.run(('which', 'img4'), stdout=subprocess.DEVNULL) 49 | if img4lib_check.returncode == 0: 50 | img4lib = True 51 | else: 52 | img4lib = False 53 | 54 | img4tool_check = subprocess.run(('which', 'img4tool'), stdout=subprocess.DEVNULL) 55 | if img4tool_check.returncode == 0: 56 | img4tool = True 57 | else: 58 | img4tool = False 59 | 60 | cleanup() 61 | 62 | os.makedirs('.tmp/asr-fetcher') 63 | os.chdir('.tmp/asr-fetcher') 64 | 65 | api = requests.get(f'https://api.ipsw.me/v4/device/{args.device[0]}?type=ipsw') 66 | data = api.json() 67 | device_identifier = data['identifier'] 68 | 69 | for x in range(0, len(data['firmwares'])): 70 | ipsw_download = data['firmwares'][x]['url'] 71 | dmg_sizes = [] 72 | ramdisk_path = f'ramdisk_{device_identifier}_{data["firmwares"][x]["version"]}_{data["firmwares"][x]["buildid"]}.dmg' 73 | if args.version: 74 | if not data['firmwares'][x]['version'] == args.version[0]: 75 | continue 76 | 77 | try: 78 | with remotezip.RemoteZip(ipsw_download) as f: 79 | for i in f.infolist(): 80 | if i.filename.endswith('.dmg') and not i.filename.startswith('._'): 81 | dmg_sizes.append(i.file_size) 82 | 83 | dmg_sizes.sort() 84 | for i in f.infolist(): 85 | if not i.file_size == dmg_sizes[0]: 86 | continue 87 | 88 | print(f"Extracting ASR from iOS {data['firmwares'][x]['version']}'s IPSW") 89 | 90 | f.extract(i.filename) 91 | 92 | if img4lib: 93 | subprocess.run(('img4', '-i', i.filename, '-o', ramdisk_path), stdout=subprocess.DEVNULL) 94 | elif img4tool: 95 | subprocess.run(('img4tool', '-e', '-o', ramdisk_path, i.filename), stdout=subprocess.DEVNULL) 96 | else: 97 | sys.exit('[ERROR] Neither img4 or img4tool were found. Exiting...') 98 | 99 | os.remove(i.filename) 100 | break 101 | 102 | except remotezip.RemoteIOError: 103 | print(f"[ERROR] Unable to extract ASR from iOS {data['firmwares'][x]['version']}'s IPSW, continuing...") 104 | continue 105 | 106 | attach_dmg = subprocess.run(('hdiutil', 'attach', ramdisk_path, '-mountpoint', 'ramdisk'), stdout=subprocess.DEVNULL) 107 | if attach_dmg.returncode != 0: 108 | sys.exit(f'[ERROR] Mounting DMG failed.') 109 | 110 | os.makedirs(f'../../ASR_Binaries/{device_identifier}/{data["firmwares"][x]["version"]}/{data["firmwares"][x]["buildid"]}') 111 | 112 | shutil.move('ramdisk/usr/sbin/asr', f'../../ASR_Binaries/{device_identifier}/{data["firmwares"][x]["version"]}/{data["firmwares"][x]["buildid"]}/asr') 113 | 114 | subprocess.run(('hdiutil', 'detach', 'ramdisk'), stdout=subprocess.DEVNULL) 115 | os.remove(f'ramdisk_{device_identifier}_{data["firmwares"][x]["version"]}_{data["firmwares"][x]["buildid"]}.dmg') 116 | 117 | if args.version: 118 | break 119 | 120 | print('Done!') 121 | 122 | if __name__ == '__main__': 123 | main() -------------------------------------------------------------------------------- /compare-kernels.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # Copyright (c) 2020, @mcg29_ 4 | # Code modified by marijuanARM 5 | 6 | import argparse 7 | import os 8 | import sys 9 | 10 | 11 | def main(): 12 | parser = argparse.ArgumentParser(description="compare-kernels.py - Create an img4lib diff file between 2 raw kernelcaches.") 13 | parser.add_argument('-i', '--input', help='Input kernelcache', nargs=1) 14 | parser.add_argument('-p', '--patched', help='Patched kernelcache', nargs=1) 15 | parser.add_argument('-d', '--diff', help='Diff file to write to', nargs=1) 16 | args = parser.parse_args() 17 | 18 | if not args.input or not args.patched or not args.diff: 19 | sys.exit(parser.print_help(sys.stderr)) 20 | 21 | if not os.path.isfile(args.input[0]): 22 | sys.exit(f"[ERROR] Input kernel {args.input[0]} does not exist. Exiting...") 23 | 24 | if not os.path.isfile(args.patched[0]): 25 | sys.exit(f"[ERROR] Patched kernel {args.patched[0]} does not exist. Exiting...") 26 | 27 | with open(args.patched[0], 'rb') as f: 28 | patched = f.read() 29 | 30 | with open(args.input[0], 'rb') as f: 31 | original = f.read() 32 | 33 | if len(patched) != len(original): 34 | sys.exit(f"[ERROR] Input kernel {args.input[0]} and patched kernel {args.patched[0]} are not the same size. Exiting...") 35 | 36 | diff = [] 37 | for i in range(len(original)): 38 | originalByte = original[i] 39 | patchedByte = patched[i] 40 | 41 | if originalByte != patchedByte: 42 | diff.append([hex(i), hex(originalByte), hex(patchedByte)]) 43 | 44 | with open(args.diff[0], 'w+') as f: 45 | f.write('#AMFI\n\n') 46 | for x in diff: 47 | data = f'{str(x[0])} {(str(x[1]))} {(str(x[2]))}' 48 | f.write(f'{data}\n') 49 | 50 | print(f'Diff file written to: {args.diff[0]}. Exiting...') 51 | 52 | 53 | if __name__ == "__main__": 54 | main() 55 | -------------------------------------------------------------------------------- /extract-nonce.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import atexit 4 | import argparse 5 | import binascii 6 | import os 7 | import platform 8 | import shutil 9 | import subprocess 10 | import sys 11 | 12 | if platform.system() == 'Linux': 13 | img4tool_binary = './resources/bin/img4tool_linux' 14 | elif platform.system() == 'Darwin': 15 | img4tool_binary = './resources/bin/img4tool_macos' 16 | else: 17 | sys.exit('[ERROR] Unsupported OS. Exiting...') 18 | 19 | def cleanup(): 20 | if os.path.isdir('.tmp'): 21 | shutil.rmtree('.tmp') 22 | 23 | atexit.register(cleanup) 24 | 25 | def main(): 26 | parser = argparse.ArgumentParser(description='Extract ApNonce & SepNonce from SHSH') 27 | parser.add_argument('-s', '--shsh', help='Path to SHSH', nargs=1) 28 | args = parser.parse_args() 29 | 30 | if not args.shsh: 31 | sys.exit(parser.print_help(sys.stderr)) 32 | 33 | if not os.path.isfile(args.shsh[0]): 34 | sys.exit(f'[ERROR] SHSH not found at given path: {args.shsh[0]}. Exiting...') 35 | 36 | cleanup() 37 | 38 | os.makedirs('.tmp') 39 | 40 | img4tool = subprocess.run((img4tool_binary, '-e', '-s', args.shsh[0], '-m', '.tmp/IM4M'), stdout=subprocess.PIPE, universal_newlines=True) 41 | if not 'Saved IM4M to .tmp/IM4M' in img4tool.stdout: 42 | sys.exit('[ERROR] Failed to extract IM4M from SHSH. Exiting...') 43 | 44 | with open('.tmp/IM4M', 'rb') as f: 45 | IM4M = binascii.hexlify(f.read()) 46 | 47 | ApNonce = IM4M[160:224].decode('utf-8') 48 | SepNonce = IM4M[526:566].decode('utf-8') 49 | 50 | print(f'ApNonce: {ApNonce}') 51 | print(f'SepNonce: {SepNonce}') 52 | 53 | shutil.rmtree('.tmp') 54 | 55 | if __name__ == '__main__': 56 | main() -------------------------------------------------------------------------------- /redeb.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import atexit 4 | import argparse 5 | import os 6 | import platform 7 | import shutil 8 | import subprocess 9 | import sys 10 | 11 | if platform.system() == 'Darwin' and platform.processor() == 'arm64' or platform.processor() == 'arm': 12 | dpkg_admindir = '/Library/dpkg' 13 | 14 | elif platform.system() == 'Linux': 15 | dpkg_admindir = '/var/lib/dpkg' 16 | 17 | def cleanup(): 18 | if os.path.isdir('.tmp'): # Cleanup files from previous run if they exist 19 | shutil.rmtree('.tmp') 20 | 21 | if os.geteuid() != 0: 22 | sys.exit('[ERROR] This script must be ran as root. Exiting...') 23 | 24 | atexit.register(cleanup) 25 | 26 | def main(): 27 | parser = argparse.ArgumentParser(description='ReDEB', usage="./redeb.py -p 'package'") 28 | parser.add_argument('-p', '--package', help='Name of installed package', nargs=1) 29 | args = parser.parse_args() 30 | 31 | if not args.package: 32 | sys.exit(parser.print_help(sys.stderr)) 33 | 34 | package = args.package[0] 35 | 36 | dpkg_check = subprocess.run(('which', 'dpkg-deb'), stdout=subprocess.DEVNULL) 37 | 38 | if dpkg_check.returncode != 0: 39 | sys.exit('[ERROR] dpkg is not on this system. Exiting...') 40 | 41 | if not os.path.isfile(f'{dpkg_admindir}/info/{package}.list'): 42 | sys.exit(f'[ERROR] Package {package} is not installed or does not exist. Exiting...') 43 | 44 | if os.path.isfile(f'{package}.deb'): 45 | os.remove(f'{package}.deb') 46 | 47 | os.makedirs(f'.tmp/dpkg/{package}/DEBIAN') 48 | 49 | with open(f'{dpkg_admindir}/info/{package}.list', 'r+') as f: 50 | package_files = f.read() 51 | 52 | package_files = package_files.split('\n') 53 | 54 | for x in package_files: # Copy files in package from filesystem to work directory 55 | if x == '': # If there's an empty line, we've hit the end of the file 56 | break 57 | elif x == '/.' or os.path.isdir(x): 58 | continue 59 | 60 | if not os.path.isfile(x): 61 | print(f"[NOTE] '{x}' is included with {package}, but file not found. Continuing...") 62 | 63 | path = '' 64 | for y in range(1, len(x.split('/')) - 1): 65 | path += '/{}'.format(x.split('/')[y]) 66 | 67 | 68 | if not os.path.isdir(f'.tmp/dpkg/{package}/{path[1:]}'): 69 | os.makedirs(f'.tmp/dpkg/{package}/{path[1:]}') 70 | 71 | shutil.copyfile(x, f'.tmp/dpkg/{package}/{x[1:]}') 72 | 73 | package_scripts = ['preinst', 'postinst', 'prerm', 'postrm'] 74 | 75 | for x in package_scripts: # Copy any package maintainer scripts 76 | if not os.path.isfile(f'{dpkg_admindir}/info/{package}.{x}'): 77 | continue 78 | 79 | shutil.copyfile(f'{dpkg_admindir}/info/{package}.{x}', f'.tmp/dpkg/{package}/DEBIAN/{x}') 80 | 81 | os.chmod(f'.tmp/dpkg/{package}/DEBIAN/{x}', 0o755) 82 | 83 | with open(f'{dpkg_admindir}/status', 'r+') as f: # Get control file of package from dpkg's status file 84 | status = f.read() 85 | 86 | status = status.split('\n') 87 | 88 | package_status = status.index(f'Package: {package}') 89 | status = status[package_status:] 90 | 91 | package_control = '' 92 | 93 | for x in status: 94 | if x.startswith('Status:'): # Don't include installed status in control 95 | continue 96 | elif x == '': 97 | break 98 | 99 | package_control += f'{x}\n' 100 | 101 | with open(f'.tmp/dpkg/{package}/DEBIAN/control', 'w+') as f: 102 | f.write(package_control) 103 | 104 | 105 | build_deb = subprocess.run(('dpkg-deb', '-b', f'.tmp/dpkg/{package}', f'{package}.deb'), stdout=subprocess.DEVNULL) 106 | 107 | if build_deb.returncode != 0: 108 | sys.exit('[ERROR] Failed to build package back into deb. Exiting...') 109 | 110 | print(f'Package successfully rebuilt to deb: {package}.deb') 111 | 112 | shutil.rmtree(f'.tmp') 113 | 114 | if __name__ == '__main__': 115 | main() -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | argparse 2 | flask 3 | mwclient 4 | remotezip 5 | requests 6 | wikitextparser 7 | -------------------------------------------------------------------------------- /resources/bin/img4tool_linux: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cryptiiiic/ios-tools/1fd62d13c5c5dce585296ae4d4e7a2681144298a/resources/bin/img4tool_linux -------------------------------------------------------------------------------- /resources/bin/img4tool_macos: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Cryptiiiic/ios-tools/1fd62d13c5c5dce585296ae4d4e7a2681144298a/resources/bin/img4tool_macos -------------------------------------------------------------------------------- /restore-rootfs.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import atexit 4 | import os 5 | import pathlib 6 | import platform 7 | import shutil 8 | import subprocess 9 | import sys 10 | 11 | if platform.system() == 'Darwin' and platform.processor() == 'arm64' or platform.processor() == 'arm': 12 | dpkg_admindir = '/Library/dpkg' 13 | 14 | def cleanup(): 15 | if os.path.isfile('.tmp/restore-rootfs/.procursus_strapped'): 16 | subprocess.run(('umount', '.tmp/restore-rootfs'), stdout=subprocess.DEVNULL) 17 | 18 | if os.path.isdir('.tmp'): # Cleanup files from previous run if they exist 19 | shutil.rmtree('.tmp') 20 | 21 | if os.geteuid() != 0: 22 | sys.exit('[ERROR] This script must be ran as root. Exiting...') 23 | 24 | atexit.register(cleanup) 25 | 26 | def main(): 27 | if not os.path.isfile('/.procursus_strapped'): 28 | sys.exit('[ERROR] This script requires your device to be bootstrapped with the Procursus bootstrap (used in Odyssey & Odysseyra1n). Exiting...') 29 | 30 | snaputil_check = subprocess.run(('which', 'snaputil'), stdout=subprocess.DEVNULL) 31 | if snaputil_check.returncode != 0: 32 | sys.exit('[ERROR] snaputil is not installed on this device. Exiting...') 33 | 34 | uicache_check = subprocess.run(('which', 'uicache'), stdout=subprocess.DEVNULL) 35 | if uicache_check.returncode != 0: 36 | sys.exit('[ERROR] uikittools is not installed on this device. Exiting...') 37 | 38 | print('Mounting pre-jailbreak snapshot...') 39 | 40 | os.makedirs(f'.tmp/restore-rootfs', exist_ok=True) 41 | 42 | snaputil = subprocess.run(('snaputil', '-s', 'orig-fs', '/', '.tmp/restore-rootfs'), stdout=subprocess.DEVNULL) 43 | 44 | if snaputil.returncode != 0: 45 | sys.exit('[ERROR] Failed to mount pre-jailbreak snapshot. Exiting...') 46 | 47 | if not os.path.isdir('.tmp/restore-rootfs/Applications'): 48 | sys.exit('[ERROR] /Applications does not exist on pre-jailbreak snapshot. Something must be very wrong. Exiting...') 49 | 50 | 51 | snapshot_application_dir = os.listdir('.tmp/restore-rootfs/Applications') 52 | for x in os.listdir('/Applications'): 53 | if os.path.isfile(f'/Applications/{x}'): 54 | continue 55 | 56 | if x not in snapshot_application_dir: 57 | shutil.rmtree(f'/Applications/{x}') 58 | 59 | unmount = subprocess.run(('umount', '.tmp/restore-rootfs'), stdout=subprocess.DEVNULL) 60 | if unmount.returncode != 0: 61 | sys.exit('[ERROR] Failed to unmount pre-jailbreak snapshot. Exiting...') 62 | 63 | 64 | print('Getting rid of any leftover jailbreak files in /var...') 65 | 66 | jailbreak_files = ['/var/lib', '/var/cache', '/var/checkra1n.dmg', '/var/dropbear_rsa_host_key', '/var/mobile/.bash_history', '/var/mobile/.forward', '/var/mobile/.ssh', '/var/root/.ssh', '/var/mobile/Downloads', '/var/binpack'] # add any jailbreak related files in /var that are left to this list 67 | for x in jailbreak_files: 68 | if os.path.isfile(x): 69 | os.remove(x) 70 | 71 | elif os.path.isdir(x): 72 | shutil.rmtree(x) 73 | 74 | print('Running uicache...') 75 | 76 | uicache = subprocess.run(('uicache', '-a'), stdout=subprocess.DEVNULL) 77 | if uicache.returncode != 0: 78 | sys.exit('[ERROR] Failed to run uicache (wtf?). Exiting...') 79 | 80 | print('Reverting to pre-jailbreak snapshot...') 81 | 82 | restore_rootfs = subprocess.run(('snaputil', '-r', 'orig-fs', '/'), stdout=subprocess.DEVNULL) 83 | if restore_rootfs.returncode != 0: 84 | sys.exit('[ERROR] Failed to revert to pre-jailbreak snapshot.') 85 | 86 | x = input('Successfully restored root-fs. Would you like to reboot now? [Y/N]: ') 87 | 88 | if x.lower() == 'y': 89 | print('Rebooting.') 90 | subprocess.run(('reboot'), stdout=subprocess.DEVNULL) 91 | 92 | elif x.lower() == 'n': 93 | sys.exit('Exiting...') 94 | 95 | else: 96 | sys.exit('[ERROR] Invalid answer given. Exiting...') 97 | 98 | shutil.rmtree(f'.tmp') 99 | 100 | if __name__ == '__main__': 101 | main() -------------------------------------------------------------------------------- /wiki-proxy.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | from datetime import datetime 4 | from flask import Flask, Response, redirect, url_for 5 | from mwclient import Site as WikiSite 6 | from wikitextparser import parse as wikiparse 7 | import sys, traceback 8 | 9 | import json 10 | 11 | 12 | class Wiki: 13 | def __init__(self, device: str, buildid: str, *, boardconfig: str = None) -> None: 14 | self.site = WikiSite('theapplewiki.com', path='/') 15 | 16 | self.device = device 17 | self.buildid = buildid 18 | self.board = boardconfig 19 | 20 | def get_firm_page(self) -> str: 21 | results = list(self.site.search(f'intitle:{self.device}+{self.buildid}', namespace=2304)) 22 | if len(results) == 0: 23 | raise ValueError(f'No Firmware Keys page for device: {self.device}, buildid: {self.buildid}.') 24 | # print(results) 25 | idxs = [i for i, entry in enumerate(results) if "Keys:" in entry.get("title", "")] 26 | if len(idxs) == 0: 27 | return "" 28 | idx = idxs[0] 29 | return self.site.pages[results[idx]['title']].text() 30 | 31 | def parse_page(self, page: str) -> dict: 32 | print("parse_page 1") 33 | print(page) 34 | page = ' '.join([x for x in page.split(' ') if x != '']).replace('{{', '{| class="wikitable"').replace('}}', 35 | '|}') # Have to coerce wikitextparser into recognizing it as a table for easy parsing 36 | page_data = dict() 37 | for entry in wikiparse(page).tables[0].data()[0]: 38 | key, item = entry.split(' = ') 39 | page_data[key] = item 40 | 41 | if self.board is not None: 42 | if ('Model' not in page_data.keys()) and ('Model2' not in page_data.keys()): 43 | return page_data 44 | 45 | if self.board.lower() not in [x.lower() for x in page_data.values()]: 46 | raise ValueError(f'Boardconfig: {self.board} for device: {self.device} is not valid!') 47 | 48 | if page_data['Model2'].lower() == self.board.lower(): 49 | for key in page_data.keys(): 50 | if '2' in key: 51 | page_data[key.replace('2', '')] = page_data[key] 52 | 53 | for key in list(page_data.keys()): 54 | if '2' in key: 55 | del page_data[key] 56 | 57 | return page_data 58 | 59 | def get_keys(self, page: str) -> str: 60 | page_data = self.parse_page(page) 61 | response = { 62 | 'identifier': page_data['Device'], 63 | 'buildid': page_data['Build'], 64 | 'codename': page_data['Codename'], 65 | 'restoreramdiskexists': 'RestoreRamdisk' in page_data.keys(), 66 | 'updateramdiskexists': 'UpdateRamdisk' in page_data.keys(), 67 | 'keys': list() 68 | } 69 | 70 | for component in page_data.keys(): 71 | if any(x == component for x in 72 | ('Version', 'Build', 'Device', 'Model', 'Codename', 'Baseband', 'DownloadURL')): 73 | continue 74 | 75 | if any(component.endswith(x) for x in ('Key', 'IV', 'KBAG')): 76 | continue 77 | 78 | image = { 79 | 'image': component, 80 | 'filename': page_data[component], 81 | 'date': datetime.now().isoformat() 82 | } 83 | 84 | if any(component == x for x in ('RootFS', 'RestoreRamdisk', 'UpdateRamdisk')): 85 | image['filename'] += '.dmg' 86 | 87 | for key in ('IV', 'Key') if component != 'RootFS' else ('Key',): 88 | if component + key not in page_data.keys(): 89 | continue 90 | 91 | if any(x in page_data[component + key] for x in ('Unknown', 'Not Encrypted')): 92 | continue 93 | 94 | image[key.lower()] = page_data[component + key] 95 | 96 | if ('iv' not in image.keys()) and ('key' not in image.keys()): 97 | if not image['filename'].endswith('.dmg'): 98 | continue 99 | 100 | if ('iv' in image.keys()) and ('key' in image.keys()): 101 | image['kbag'] = image['iv'] + image['key'] 102 | 103 | response['keys'].append(image) 104 | 105 | return json.dumps(response) 106 | 107 | 108 | app = Flask('Wikiproxy API') 109 | 110 | 111 | @app.route('/firmware//', methods=['GET']) 112 | def keys(device: str, buildid: str) -> Response: 113 | print(f'Getting firmware keys for device: {device}, buildid: {buildid}') 114 | iphonewiki = Wiki(device, buildid) 115 | try: 116 | page = iphonewiki.get_firm_page() 117 | keys = iphonewiki.get_keys(page) 118 | return app.response_class(response=keys, mimetype='application/json') 119 | except Exception as e: 120 | print(str(e)) 121 | print(traceback.format_exc()) 122 | return app.response_class(status=404) 123 | 124 | 125 | @app.route('/firmware///', methods=['GET']) 126 | def keys_a9(device: str, boardconfig: str, buildid: str) -> Response: 127 | if device == boardconfig: 128 | return redirect(url_for('keys', device=device, buildid=buildid)) 129 | print(f'Getting firmware keys for device: {device} (boardconfig: {boardconfig}), buildid: {buildid}') 130 | iphonewiki = Wiki(device, buildid, boardconfig=boardconfig) 131 | try: 132 | page = iphonewiki.get_firm_page() 133 | keys = iphonewiki.get_keys(page) 134 | return app.response_class(response=keys, mimetype='application/json') 135 | except Exception as e: 136 | print(str(e)) 137 | print(traceback.format_exc()) 138 | return app.response_class(status=404) 139 | 140 | 141 | if __name__ == '__main__': 142 | app.run(port=8888) 143 | --------------------------------------------------------------------------------