Last active
September 3, 2024 15:43
-
-
Save m1stadev/f1130b066fb1334dccc8bcb596c5b336 to your computer and use it in GitHub Desktop.
iOS/tvOS nonce setter
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
from pathlib import Path | |
from remotezip import RemoteZip | |
import argparse | |
import plistlib | |
import requests | |
import subprocess | |
import tempfile | |
import time | |
import shutil | |
import sys | |
def check_device() -> dict: | |
args = ( | |
'irecovery', | |
'-q', | |
) | |
try: | |
irecv = subprocess.check_output( | |
args, | |
stderr=subprocess.DEVNULL, | |
universal_newlines=True, | |
) | |
except subprocess.CalledProcessError: | |
sys.exit('[ERROR] Failed to find a device in Pwned DFU mode. Exiting.') | |
device_data = dict() | |
for l in irecv.splitlines(): | |
x, y = l.split(': ') | |
device_data[x.lower()] = y | |
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) | |
if ('pwnd' not in device_data.keys()) or (device_data['mode'] != 'DFU'): | |
sys.exit('[ERROR] Device is not in Pwned DFU mode. Exiting.') | |
api = requests.get('https://api.ipsw.me/v4/devices').json() | |
for d in api: | |
for board in d['boards']: | |
if (board['cpid'] == device_data['cpid']) and ( | |
board['bdid'] == device_data['bdid'] | |
): | |
device_data['identifier'] = d['identifier'] | |
device_data['boardconfig'] = board['boardconfig'] | |
return device_data | |
sys.exit('[ERROR] Device does not exist?!?!??!?!') | |
def unpack(img: Path, iv: str, key: str) -> Path: | |
args = ( | |
'img4tool', | |
'-e', | |
'--iv', | |
iv, | |
'--key', | |
key, | |
'-o', | |
str(img.with_suffix('.raw')), | |
str(img), | |
) | |
try: | |
subprocess.check_call(args, stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT) | |
except subprocess.CalledProcessError: | |
sys.exit(f'[ERROR] Failed to unpack image: {img}.') | |
return img.with_suffix('.raw') | |
def patch(patcher: Path, img: Path) -> Path: | |
args = (str(patcher), str(img), str(img.with_suffix('.pwn')), '-n') | |
try: | |
subprocess.check_call(args, stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT) | |
except subprocess.CalledProcessError: | |
sys.exit(f'[ERROR] Failed to patch image: {img}. Exiting.') | |
return img.with_suffix('.pwn') | |
def verify_shsh(shsh: Path) -> str: | |
if (not shsh.exists()) or (not shsh.is_file()): | |
sys.exit(f'[ERROR] SHSH blob does not exist: {shsh}. Exiting.') | |
with shsh.open('rb') as f: | |
try: | |
im4m = plistlib.loads(f.read()) | |
except plistlib.InvalidFileException: | |
sys.exit(f'[ERROR] SHSH Blob is invalid: {shsh}. Exiting.') | |
if not any(_ in im4m.keys() for _ in ('ApImg4Ticket', 'generator')): | |
sys.exit(f'[ERROR] SHSH Blob is invalid: {shsh}. Exiting.') | |
if (not im4m['generator'].startswith('0x')) or (len(im4m['generator']) != 18): | |
sys.exit( | |
f"[ERROR] Generator inside SHSH blob is invalid: {im4m['generator']}. Exiting." | |
) | |
try: | |
int(im4m['generator'], 16) | |
except: | |
sys.exit( | |
f"[ERROR] Generator inside SHSH blob is invalid: {im4m['generator']}. Exiting." | |
) | |
return im4m['generator'] | |
def repack(img: Path, tag: str, shsh: Path) -> Path: | |
args = ( | |
'img4tool', | |
'-c', | |
str(img.with_suffix('.im4p')), | |
'-t', | |
tag, | |
str(img), | |
) | |
try: | |
subprocess.check_call(args, stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT) | |
except subprocess.CalledProcessError: | |
sys.exit(f'[ERROR] Failed to repack image: {img}. Exiting.') | |
args = ( | |
'img4tool', | |
'-c', | |
str(img.with_suffix('.img4')), | |
'-p', | |
str(img.with_suffix('.im4p')), | |
'-s', | |
str(shsh), | |
) | |
try: | |
subprocess.check_call(args, stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT) | |
except subprocess.CalledProcessError: | |
sys.exit(f"[ERROR] Failed to sign image: {img.with_suffix('.im4p')}. Exiting.") | |
return img.with_suffix('.img4') | |
def send_bootchain(ibss: Path, ibec: Path, cpid: int) -> None: | |
args = ( | |
'irecovery', | |
'-r', | |
) | |
try: | |
subprocess.check_call(args, stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT) | |
except subprocess.CalledProcessError: | |
sys.exit('[ERROR] Failed to reset device connection. Exiting.') | |
args = ('irecovery', '-f', str(ibss)) | |
try: | |
subprocess.check_call(args, stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT) | |
except subprocess.CalledProcessError: | |
sys.exit(f'[ERROR] Failed to send image: {ibss}. Exiting.') | |
time.sleep(2) | |
args = ('irecovery', '-f', str(ibec)) | |
try: | |
subprocess.check_call(args, stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT) | |
except subprocess.CalledProcessError: | |
sys.exit(f'[ERROR] Failed to send image: {ibec}. Exiting.') | |
time.sleep(2) | |
if 0x8010 <= cpid: | |
irecv_cmd('go') | |
time.sleep(3) | |
args = ('irecovery', '-m') | |
try: | |
mode = subprocess.check_output( | |
args, | |
stderr=subprocess.STDOUT, | |
universal_newlines=True, | |
) | |
except subprocess.CalledProcessError: | |
sys.exit(f'[ERROR] Device failed to load bootchain. Exiting.') | |
if 'Recovery Mode' not in mode: | |
sys.exit('[ERROR] Device failed to load bootchain. Exiting.') | |
def irecv_cmd(cmd: str) -> None: | |
args = ('irecovery', '-c', cmd) | |
try: | |
subprocess.check_call(args, stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT) | |
except subprocess.CalledProcessError: | |
sys.exit(f'[ERROR] Failed to send command to device: {cmd}. Exiting.') | |
def main() -> None: | |
print('iOS/tvOS nonce setter\n') | |
parser = argparse.ArgumentParser( | |
description='iOS/tvOS nonce setter', | |
usage=f'{sys.argv[0]} -s SHSH blob', | |
) | |
parser.add_argument( | |
'-s', '--shsh', help='Path to SHSH blob', nargs=1, required=True, type=Path | |
) | |
args = parser.parse_args() | |
shsh = args.shsh[0] | |
for x in ('img4tool', 'irecovery'): | |
if shutil.which(x) is None: | |
sys.exit(f'[ERROR] Required tool is not installed: {x}. Exiting.') | |
for x in ('iBoot64Patcher', 'Kairos'): | |
if shutil.which(x): | |
patcher = Path(x) | |
break | |
else: | |
sys.exit(f'[ERROR] Neither Kairos nor iBoot64Patcher are installed. Exiting.') | |
print('Verifying SHSH blob...') | |
generator = verify_shsh(shsh) | |
device_data = check_device() | |
os_type = 'iOS' if 'AppleTV' not in device_data['identifier'] else 'tvOS' | |
api = requests.get( | |
f"https://api.ipsw.me/v4/device/{device_data['identifier']}" | |
).json() | |
try: | |
firm = next( | |
_ | |
for _ in api['firmwares'] | |
if 12 <= int(_['version'].split('.')[0]) <= 15 | |
) | |
except StopIteration: | |
sys.exit( | |
f"[ERROR] {device_data['identifier']} doesn't support any firmwares between {os_type} 10-14. Exiting." | |
) | |
print(f"Grabbing build manifest from {os_type} {firm['version']}...") | |
with RemoteZip(firm['url']) as rz: | |
manifest = plistlib.loads( | |
rz.read(next(f for f in rz.namelist() if 'BuildManifest' in f)) | |
) | |
print(f"Grabbing bootchain from {os_type} {firm['version']}...") | |
with tempfile.TemporaryDirectory() as tmpdir: | |
tmp = Path(tmpdir) | |
ibss_path = next( | |
i['Manifest']['iBSS']['Info']['Path'] | |
for i in manifest['BuildIdentities'] | |
if i['Info']['DeviceClass'].lower() == device_data['boardconfig'].lower() | |
) | |
ibec_path = next( | |
i['Manifest']['iBEC']['Info']['Path'] | |
for i in manifest['BuildIdentities'] | |
if i['Info']['DeviceClass'].lower() == device_data['boardconfig'].lower() | |
) | |
ibss = tmp / 'ibss.im4p' | |
ibec = tmp / 'ibec.im4p' | |
with RemoteZip(firm['url']) as rz: | |
with ibss.open('wb') as f: | |
f.write(rz.read(ibss_path)) | |
with ibec.open('wb') as f: | |
f.write(rz.read(ibec_path)) | |
print('Unpacking bootchain...') | |
if ( | |
len( | |
[ | |
board | |
for board in api['boards'] | |
if board['boardconfig'].lower().endswith('ap') | |
] | |
) | |
== 1 | |
): | |
wiki_url = f"https://api.m1sta.xyz/wikiproxy/{device_data['identifier']}/{firm['buildid']}" | |
else: | |
wiki_url = f"https://api.m1sta.xyz/wikiproxy/{device_data['identifier']}/{device_data['boardconfig']}/{firm['buildid']}" | |
wikiproxy = requests.get(wiki_url).json() | |
ibss_keys = next(_ for _ in wikiproxy['keys'] if _['image'] == 'iBSS') | |
ibss = unpack(ibss, ibss_keys['iv'], ibss_keys['key']) | |
ibec_keys = next(_ for _ in wikiproxy['keys'] if _['image'] == 'iBEC') | |
ibec = unpack(ibec, ibec_keys['iv'], ibec_keys['key']) | |
print('Patching bootchain...') | |
ibss = patch(patcher, ibss) | |
ibec = patch(patcher, ibec) | |
print('Repacking bootchain...') | |
ibss = repack(ibss, 'ibss', shsh) | |
ibec = repack(ibec, 'ibec', shsh) | |
print('Sending bootchain...') | |
send_bootchain(ibss, ibec, device_data['cpid']) | |
print(f'Setting generator to {generator}...') | |
irecv_cmd(f'setenv com.apple.System.boot-nonce {generator}') | |
irecv_cmd('saveenv') | |
print('Disabling auto-boot...') | |
irecv_cmd('setenv auto-boot false') | |
irecv_cmd('saveenv') | |
print('Rebooting device into recovery mode...') | |
irecv_cmd('reboot') | |
print(f"\nDone! Your device's generator is set to {generator}.") | |
if __name__ == '__main__': | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment