Skip to content
Open
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
7 changes: 0 additions & 7 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@

igetnonce
tsschecker
build/SaveMe/xref-SaveMe.html
build/SaveMe/warn-SaveMe.txt
build/SaveMe/PYZ-00.toc
Expand All @@ -12,7 +9,3 @@ build/SaveMe/base_library.zip
build/SaveMe/Analysis-00.toc
__pycache__/SaveMe.cpython-37.pyc
.DS_Store
irecovery
idevice_id
ideviceenterrecovery
ideviceinfo
16 changes: 10 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
# SaveMe
Grab Device ECID, ApNonce, BoardConfig and iPhoneID from device and grab signed SHSH via TSSChecker
-------

Grab Device ECID, ApNonce, BoardConfig and iPhoneID from device and grab signed SHSH via TSSChecker.

UPDATE - Removed igetnonce replaced with ideviceinfo, rewrote the args and use grep for grabbing the values and strip the return values to remove white spaces both leading and trailing.

Now grabs NONC: from recovery mode using irecovery and fixed model names breaking the request (tested with iPad5,3)
Fixed the boardconfig of the iPhone11,6 being cut off
Now grabs ApNonce from Recovery Mode. (ApNone set in NVRAM)

Thanks To Tihmstar & S0uthwest and the creators of iRecovery & Libmobiledevice for
Thanks To Tihmstar & S0uthwest and the creators of iRecovery & [Libmobiledevice](https://github.com/libimobiledevice) for:

- idevice_id (from Libmobiledevice)
- ideviceenterrecovery (from Libmobiledevice)
- [igetnonce](https://github.com/tihmstar/igetnonce)
- [irecovery](https://github.com/libimobiledevice/libirecovery)
- [tsschecker](https://github.com/tihmstar/tsschecker)

- tsschecker
- ideviceinfo
- irecovery
- ideviceenterrecovery
236 changes: 175 additions & 61 deletions SaveMe.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import subprocess
import os
import sys
import time
import argparse
import re
import glob

correct_shsh_hashes = '27325c8258be46e69d9ee57fa9a8fbc28b873df434e5e702a8b27999551138ae', '3a88b7c3802f2f0510abc432104a15ebd8bd7154'

frozen = 'not'
if getattr(sys, 'frozen', False):
Expand All @@ -12,73 +16,183 @@
# we are running in a normal Python environment
bundle_dir = os.path.dirname(os.path.abspath(__file__))

udid = ''
ecid = ''
boardid = ''
deviceid = ''
apnonce = ''
iosversion = 0
parser = argparse.ArgumentParser(description='SaveMe: SHSH saver for macOS')
parser.add_argument('ios_version', help='The version of iOS you want to save blobs for. Examples: 12.4, 12.4.1')
parser.add_argument('--output', '-o', help='Where to save the blobs.', default='~/Desktop')
parser.add_argument('--debug', action='store_true', help='Enable debug output.', default=False)

args = parser.parse_args()

savePath = '~/Desktop'
args.output = os.path.expanduser(args.output)

os.chdir(bundle_dir)
print('\n\n[*] Connect Device To Start..')
os.chdir(os.getcwd() + '/SupportFiles/')

popen = subprocess.Popen('./ideviceinfo | grep UniqueDeviceID: ', shell = True, stdout=subprocess.PIPE,stderr=subprocess.PIPE, encoding='utf8')
communicateRes = popen.communicate()
udid, stdErrValue = communicateRes
udid = udid.strip()
udid = udid[16:]

popen = subprocess.Popen('./ideviceinfo | grep UniqueChipID: ', shell = True, stdout=subprocess.PIPE,stderr=subprocess.PIPE, encoding='utf8')
communicateRes = popen.communicate()
ecid, stdErrValue = communicateRes
ecid = ecid.strip()
ecid = ecid[15:]

popen = subprocess.Popen('./ideviceinfo | grep ProductType: ', shell = True, stdout=subprocess.PIPE,stderr=subprocess.PIPE, encoding='utf8')
communicateRes = popen.communicate()
deviceid, stdErrValue = communicateRes
deviceid = deviceid.strip()
deviceid = deviceid[13:]

popen = subprocess.Popen('./ideviceinfo | grep HardwareModel: ', shell = True, stdout=subprocess.PIPE,stderr=subprocess.PIPE, encoding='utf8')
communicateRes = popen.communicate()
boardid, stdErrValue = communicateRes
boardid = boardid.strip()
boardid = boardid[15:]

popen = subprocess.Popen('./ideviceenterrecovery %s' %udid, shell = True, stdout=subprocess.PIPE,stderr=subprocess.PIPE, encoding='utf8')
communicateRes = popen.communicate()
enterRecovery, stdErrValue = communicateRes
print('[*] ' + enterRecovery)

time.sleep(20) #some devices reboot faster than others

popen = subprocess.Popen('./irecovery -q', shell = True, stdout=subprocess.PIPE,stderr=subprocess.PIPE, encoding='utf8')
communicateRes = popen.communicate()
apNonce, stdErrValue = communicateRes
apNonce = apNonce.split('\n')

for s in apNonce:
if s.startswith('NONC:'):
apnonce = s
apnonce = apnonce.replace('NONC:','')
print('[SETUP]')
print(f'\tSaving blobs for iOS version {args.ios_version}')
print(f'\tWill be writing output to {args.output!r}')

input('\n\nConnect Device To Start. Press ENTER when done...')


def get_device_name(udid):
process = subprocess.run(['./idevice_id', udid], capture_output=True, encoding='utf8')

if process.returncode:
print(f'Could not get device name: {process.stderr!r}', file=sys.stderr)
return None

return process.stdout.strip()


def recognize_device():
print('Trying to recognize device...')

process = subprocess.run(['./idevice_id', '-l'], capture_output=True, encoding='utf8')

if process.returncode:
print(f'Could not recognize device: {process.stderr!r}', file=sys.stderr)
return None

udids = set(process.stdout.splitlines())

if len(udids) == 0:
print("Error identifying your device. Please make sure it's connected and iTunes identifies it.", file=sys.stderr)
return None
elif len(udids) > 1:
print(f"Can only work with 1 device, but got {len(udid)}", file=sys.stderr)
return None

udid, = udids

name = get_device_name(udid)

if name is None:
return None

return udid, name


def run_command_or_exit(arguments, capture=True):
process = subprocess.run(arguments, capture_output=True, encoding='utf8') if capture else subprocess.run(arguments)

if process.returncode:
print(f"Error running {arguments[0]!r}.\nError message:\n{process.stderr}", file=sys.stdout)
sys.exit(-3)

return process

udid_data = recognize_device()
while udid_data is None:
if input("Retry (y/N)? ").lower().strip() != 'y':
print('Failed to find device!', file=sys.stderr)
sys.exit(-1)

udid_data = recognize_device()

udid, name = udid_data

print(f'Identified device as {name!r} with UDID={udid}')

process = run_command_or_exit(['./ideviceenterrecovery', udid], False)
input("Press ENTER once the device has rebooted")

print('Querying device info (ECID, ddevice ID, board ID, ApNonce)...')
process = run_command_or_exit(['./igetnonce'])


info = {
'ECID': None,
'board ID': None,
'device ID': None,
'ApNonce': None
}

for line in process.stdout.lower().splitlines():
if line.startswith('ecid='):
info['ECID'] = line[5:].strip()
elif line.startswith('identified'):
m = re.match(r'identified\s+device\s+as\s+([^,]+)\s*,\s*([^ ]+)\s*in\s+recovery\s+mode\.\.\.\s*', line)
if not m:
print(f'Failed to extract board ID and device ID from line provided by "igetnonce": {line!r}', file=sys.stderr)
sys.exit(3)

info['board ID'], info['device ID'] = m.groups()
elif line.startswith('apnonce='):
info['ApNonce'] = line[8:].strip()

hasError = False
for name, value in info.items():
if value is None:
print(f"Failed to extract {name}", file=sys.stdout)
if name != 'ApNonce':
hasError = True

if hasError:
sys.exit(4)

if info['ApNonce'] is None:
print('Retrying to request ApNonce with a different tool...')
process = run_command_or_exit(['./irecovery', '-q'])

for line in process.stdout.splitlines():
m = regex.match(r'^NONC:\s*(.+)$', line)
if not m:
print(f"Failed to extract ApNonce from line {line}", file=sys.stderr)
sys.exit(5)

info['ApNonce'] = m.group()

if info['ApNonce'] is None:
print("Failed to extract ApNonce. No idea why", file=sys.stderr)
sys.exit(6)

print('[D] Device Model = ' + deviceid)
print('[D] Device UDID = ' + udid)
print('[D] BoardConfig = ' + boardid)
print('[D] ECID = ' + ecid)
print('[D] ApNonce = ' + apnonce)
print('[D] Enter the iOS version to save the ticket for')
print('Device Model: ', info['device ID'])
print('Device UDID: ', udid)
print('BoardConfig: ', info['board ID'])
print('ECID: ', info['ECID'])
print('ApNonce: ', info['ApNonce'])

print(f'\nSaving blobs for iOS version {args.ios_version} to path {args.output}...')

tsscheckerArgs = './tsschecker', '-d', info['device ID'], '-e', info['ECID'], '--boardconfig', info['board ID'], '--ios', args.ios_version,\
'--apnonce', info['ApNonce'], '-s', '--save-path', args.output

if args.debug:
print(f'[DEBUG] tsschecker command: ', tsscheckerArgs)
# tsscheckerArgs = './tsschecker -d %s' %deviceid + ' -e %s' %ecid + ' --boardconfig %s' %boardid + ' --ios %s' %iosversion + ' --apnonce %s' %apnonce + ' -s --save-path ' + savePath

process = subprocess.run(tsscheckerArgs)

iosversion = input()
if process.returncode:
print(f"Error running 'tsschecker'.", file=sys.stdout)
sys.exit(-4)

tsscheckerArgs = './tsschecker -d %s' %deviceid + ' -e %s' %ecid + ' --boardconfig %s' %boardid + ' --ios %s' %iosversion + ' --apnonce %s' %apnonce + ' -s --save-path ' + savePath
print('tsschecker run, but we still need to check if the blobs use the correct generator (0x1111111111111111)!\nExiting recovery mode...')
process = run_command_or_exit(['./irecovery', '-n'])

popen = subprocess.Popen([tsscheckerArgs], shell = True, stdout=subprocess.PIPE,stderr=subprocess.PIPE, encoding='utf8')
popen = subprocess.Popen('./irecovery -n', shell = True, stdout=subprocess.PIPE,stderr=subprocess.PIPE, encoding='utf8')
integer_ECID = int(info["ECID"], 16)
print(f'Device should have exited recovery mode.\nChecking generator for ECID decimal {integer_ECID} (the same as hexadecimal {info["ECID"]})...')

print('[*] File saved at path ' + savePath)
print('[*] Exiting')
SHSH2 = glob.glob(os.path.join(args.output, f'{integer_ECID}_*.shsh2'))
if not SHSH2:
print(f'Could not locate SHSH2 file for ECID', info['ECID'], f'in path {args.output}!', file=sys.stderr)
sys.exit(-6)

hasError = 0
for file_path in SHSH2:
_, file_name = os.path.split(file_path)

name, _ = file_name.rsplit('.', 1)
name, Hash = name.rsplit('_', 1)

if Hash in correct_shsh_hashes:
print(f'[OK] Correct generator for file {file_path!r}')
else:
print(f'[ERROR] Wrong generator for file {file_path!r}', file=sys.stderr)
hasError += 1

if hasError:
print(f'{hasError} blobs were saved with the wrong generator', file=sys.stderr)
else:
print('\tGREAT! All SHSH2 blobs have been saved with the correct generator (0x1111111111111111)')
Binary file added SupportFiles/idevice_id
Binary file not shown.
Binary file added SupportFiles/ideviceenterrecovery
Binary file not shown.
Binary file added SupportFiles/igetnonce
Binary file not shown.
Binary file added SupportFiles/irecovery
Binary file not shown.
Binary file added SupportFiles/tsschecker
Binary file not shown.