Exemple #1
0
    def __init__(self, ncch_fp: BinaryIO, g_stat: os.stat_result, decompress_code: bool = True, dev: bool = False,
                 seeddb: str = None):
        self.crypto = CryptoEngine(dev=dev)

        self.decompress_code = decompress_code
        self.seeddb = seeddb
        self.files: Dict[str, Dict] = {}

        # get status change, modify, and file access times
        self._g_stat = g_stat
        self.g_stat = {'st_ctime': int(g_stat.st_ctime), 'st_mtime': int(g_stat.st_mtime),
                       'st_atime': int(g_stat.st_atime)}

        ncch_header = ncch_fp.read(0x200)
        self.reader = NCCHReader.from_header(ncch_header)

        self.f = ncch_fp

        if not self.reader.flags.no_crypto:
            # I should figure out what happens if fixed-key crypto is
            #   used along with seed. even though this will never
            #   happen in practice, I would still like to see what
            #   happens if it happens.
            if self.reader.flags.fixed_crypto_key:
                normal_key = FIXED_SYSTEM_KEY if self.reader.program_id & (0x10 << 32) else 0x0
                self.crypto.set_normal_key(0x2C, normal_key.to_bytes(0x10, 'big'))
            else:
                if self.reader.flags.uses_seed:
                    self.reader.load_seed_from_seeddb()

                self.crypto.set_keyslot('y', 0x2C, readbe(self.reader.get_key_y(original=True)))
                self.crypto.set_keyslot('y', self.reader.extra_keyslot,
                                        readbe(self.reader.get_key_y()))
Exemple #2
0
    def __init__(
        self,
        fp: "Union[PathLike, str, bytes, BinaryIO]",
        *,
        closefd: bool = True,
        _load_icon: bool = True,
    ):

        super().__init__(fp, closefd=closefd)

        # Threading lock to prevent two operations on one class instance from interfering with eachother.
        self._lock = Lock()

        header = self._file.read(0x180)
        unitcode = readbe(header[0x12:0x13])

        self.apptitle = header[0x0:0x12]
        self.regionlocks = ""
        self.shortDesc = ""
        self.longDesc = ""
        if unitcode & 0x01:
            extheader = self._file.read(0xE80)
            region_lockout = readle(extheader[0x34:0x35])
            if region_lockout == "0x0":
                self.regionlocks = "Normal"
            elif region_lockout == "0x80":
                self.regionlocks = "China"
            elif region_lockout == "0x40":
                self.regionlocks = "Korea"
            else:
                self.regionlocks = str(region_lockout)

        self._icon_offset = readle(header[0x068:0x68 + 4])
        self._load_icon()
Exemple #3
0
    def __init__(self,
                 sd_dir: str,
                 movable: str,
                 dev: bool = False,
                 readonly: bool = False):
        self.crypto = CryptoEngine(dev=dev)

        with open(movable, 'rb') as mv:
            mv.seek(0x110)
            key_y = mv.read(0x10)
        key_hash = sha256(key_y).digest()
        hash_parts = unpack('<IIII', key_hash[0:16])
        self.root_dir = f'{hash_parts[0]:08x}{hash_parts[1]:08x}{hash_parts[2]:08x}{hash_parts[3]:08x}'

        if not os.path.isdir(sd_dir + '/' + self.root_dir):
            exit(f'Failed to find {self.root_dir} in the SD dir.')

        self.fds: Dict[int, BinaryIO] = {}

        print('Root dir: ' + self.root_dir)

        self.crypto.set_keyslot('y', 0x34, readbe(key_y))
        print('Key:      ' + self.crypto.key_normal[0x34].hex())

        self.root = os.path.realpath(sd_dir + '/' + self.root_dir)
        self.root_len = len(self.root)

        self.readonly = readonly
Exemple #4
0
        def generate_ctr():
            print(
                'Attempting to generate Counter for CTR/TWL areas. If errors occur, provide the CID manually.'
            )

            # -------------------------------------------------- #
            # attempt to generate CTR Counter
            nand_fp.seek(0xB9301D0)
            # these blocks are assumed to be entirely 00, so no need to xor anything
            ctrn_block_0x1d = nand_fp.read(0x10)
            ctrn_block_0x1e = nand_fp.read(0x10)
            for ks in (Keyslot.CTRNANDOld, Keyslot.CTRNANDNew):
                ctr_counter_offs = self.crypto.create_ecb_cipher(ks).decrypt(
                    ctrn_block_0x1d)
                ctr_counter = int.from_bytes(ctr_counter_offs,
                                             'big') - 0xB9301D

                # try the counter
                out = self.crypto.create_ctr_cipher(
                    ks, ctr_counter + 0xB9301E).decrypt(ctrn_block_0x1e)
                if out == b'\0' * 16:
                    print('Counter for CTR area automatically generated.')
                    self.ctr = ctr_counter
                    break
            else:
                print(
                    'Counter could not be generated for CTR area. Related virtual files will not appear.'
                )
                self.ctr = None

            # -------------------------------------------------- #
            # attempt to generate TWL Counter
            nand_fp.seek(0x1C0)
            twln_block_0x1c = readbe(nand_fp.read(0x10))
            twl_blk_xored = twln_block_0x1c ^ 0x18000601A03F97000000A97D04000004
            twl_counter_offs = self.crypto.create_ecb_cipher(0x03).decrypt(
                twl_blk_xored.to_bytes(0x10, 'little'))
            twl_counter = int.from_bytes(twl_counter_offs, 'big') - 0x1C

            # try the counter
            twln_block_0x1d = nand_fp.read(0x10)
            out = self.crypto.create_ctr_cipher(0x03, twl_counter +
                                                0x1D).decrypt(twln_block_0x1d)
            if out == b'\x8e@\x06\x01\xa0\xc3\x8d\x80\x04\x00\xb3\x05\x01\x00\x00\x00':
                print('Counter for TWL area automatically generated.')
                self.ctr_twl = twl_counter
            else:
                print(
                    'Counter could not be generated for TWL area. Related virtual files will not appear.'
                )
                self.ctr_twl = None
Exemple #5
0
    def __init__(
        self,
        fp: "Union[PathLike, str, bytes, BinaryIO]",
        *,
        closefd: bool = True,
        _load_icon: bool = True,
    ):

        super().__init__(fp, closefd=closefd)

        # Threading lock to prevent two operations on one class instance from interfering with eachother.
        self._lock = Lock()

        header = self._file.read(0x180)
        unitcode = readbe(header[0x12:0x13])

        self.apptitle = header[0x0:0x12]
        self.regionlocks = ""
        self.shortDesc = ""
        self.longDesc = ""
        if unitcode & 0x01:
            extheader = self._file.read(0xE80)
            region_lockout = readle(extheader[0x34:0x35])
            if region_lockout == "0x0":
                self.regionlocks = "Normal"
            elif region_lockout == "0x80":
                self.regionlocks = "China"
            elif region_lockout == "0x40":
                self.regionlocks = "Korea"
            else:
                self.regionlocks = str(region_lockout)
            # if region_lockout == 0x7FFFFFFF:
            #    self.regionlocks = "ALL"
            # else:
            #    for i in range(7):
            #        bit = region_lockout & (1 << i)
            #        if bit != 0:
            #            regionlist.append(_region_lock_names[i])
            #    regionlist = set(regionlist)
            #    self.regionlocks = ",".join(regionlist)

        self._icon_offset = readle(header[0x068:0x68 + 4])
        # file.seek(icon_offset)
        # self.icon = SRLIcon.load(file)
        self._load_icon()
Exemple #6
0
    def __init__(self,
                 nand_fp: BinaryIO,
                 g_stat: dict,
                 dev: bool = False,
                 readonly: bool = False,
                 otp: bytes = None,
                 cid: AnyStr = None,
                 boot9: str = None):
        self.crypto = CryptoEngine(boot9=boot9, dev=dev)

        self.g_stat = g_stat

        nand_fp.seek(0x100)  # screw the signature
        ncsd_header = nand_fp.read(0x100)
        if ncsd_header[0:4] != b'NCSD':
            exit(
                'NCSD magic not found, is this a real Nintendo 3DS NAND image?'
            )
        media_id = ncsd_header[0x8:0x10]
        if media_id != b'\0' * 8:
            exit(
                'Media ID not all-zero, is this a real Nintendo 3DS NAND image?'
            )

        # check for essential.exefs
        nand_fp.seek(0x200)
        try:
            exefs = ExeFSReader(nand_fp)
        except InvalidExeFSError:
            exefs = None

        otp_data = None
        if otp:
            try:
                with open(otp, 'rb') as f:
                    otp_data = f.read(0x200)
            except Exception:
                print(f'Failed to open and read given OTP ({otp}).\n')
                print_exc()
                exit(1)

        else:
            if exefs is None:
                exit(
                    'OTP not found, provide with --otp or embed essentials backup with GodMode9'
                )
            else:
                try:
                    with exefs.open('otp') as otp:
                        otp_data = otp.read(0x200)
                except ExeFSFileNotFoundError:
                    exit(
                        '"otp" not found in essentials backup, update with GodMode9 or provide with --otp'
                    )

        self.crypto.setup_keys_from_otp(otp_data)

        def generate_ctr():
            print(
                'Attempting to generate Counter for CTR/TWL areas. If errors occur, provide the CID manually.'
            )

            # -------------------------------------------------- #
            # attempt to generate CTR Counter
            nand_fp.seek(0xB9301D0)
            # these blocks are assumed to be entirely 00, so no need to xor anything
            ctrn_block_0x1d = nand_fp.read(0x10)
            ctrn_block_0x1e = nand_fp.read(0x10)
            for ks in (Keyslot.CTRNANDOld, Keyslot.CTRNANDNew):
                ctr_counter_offs = self.crypto.create_ecb_cipher(ks).decrypt(
                    ctrn_block_0x1d)
                ctr_counter = int.from_bytes(ctr_counter_offs,
                                             'big') - 0xB9301D

                # try the counter
                out = self.crypto.create_ctr_cipher(
                    ks, ctr_counter + 0xB9301E).decrypt(ctrn_block_0x1e)
                if out == b'\0' * 16:
                    print('Counter for CTR area automatically generated.')
                    self.ctr = ctr_counter
                    break
            else:
                print(
                    'Counter could not be generated for CTR area. Related virtual files will not appear.'
                )
                self.ctr = None

            # -------------------------------------------------- #
            # attempt to generate TWL Counter
            nand_fp.seek(0x1C0)
            twln_block_0x1c = readbe(nand_fp.read(0x10))
            twl_blk_xored = twln_block_0x1c ^ 0x18000601A03F97000000A97D04000004
            twl_counter_offs = self.crypto.create_ecb_cipher(0x03).decrypt(
                twl_blk_xored.to_bytes(0x10, 'little'))
            twl_counter = int.from_bytes(twl_counter_offs, 'big') - 0x1C

            # try the counter
            twln_block_0x1d = nand_fp.read(0x10)
            out = self.crypto.create_ctr_cipher(0x03, twl_counter +
                                                0x1D).decrypt(twln_block_0x1d)
            if out == b'\x8e@\x06\x01\xa0\xc3\x8d\x80\x04\x00\xb3\x05\x01\x00\x00\x00':
                print('Counter for TWL area automatically generated.')
                self.ctr_twl = twl_counter
            else:
                print(
                    'Counter could not be generated for TWL area. Related virtual files will not appear.'
                )
                self.ctr_twl = None

        cid_data = None
        if cid:
            try:
                with open(cid, 'rb') as f:
                    cid_data = f.read(0x200)
            except Exception:
                print(f'Failed to open and read given CID ({cid}).')
                print(
                    'If you want to attempt Counter generation, do not provide a CID path.\n'
                )
                print_exc()
                exit(1)

        else:
            if exefs is None:
                generate_ctr()
            else:
                try:
                    with exefs.open('nand_cid') as cid:
                        cid_data = cid.read(0x10)
                except ExeFSFileNotFoundError:
                    print(
                        '"nand_cid" not found in essentials backup, update with GodMode9 or provide with --cid'
                    )
                    generate_ctr()

        if cid_data:
            self.ctr = readbe(sha256(cid_data).digest()[0:16])
            self.ctr_twl = readle(sha1(cid_data).digest()[0:16])

        if not (self.ctr or self.ctr_twl):
            exit("Couldn't generate Counter for both CTR/TWL. "
                 "Make sure the OTP is correct, or provide the CID manually.")

        nand_fp.seek(0, 2)
        raw_nand_size = nand_fp.tell()

        self.real_nand_size = nand_size[readle(ncsd_header[4:8])]

        self.files = {
            '/nand_hdr.bin': {
                'size': 0x200,
                'offset': 0,
                'keyslot': 0xFF,
                'type': 'raw'
            },
            '/nand.bin': {
                'size': raw_nand_size,
                'offset': 0,
                'keyslot': 0xFF,
                'type': 'raw'
            },
            '/nand_minsize.bin': {
                'size': self.real_nand_size,
                'offset': 0,
                'keyslot': 0xFF,
                'type': 'raw'
            }
        }

        nand_fp.seek(0x12C00)
        keysect_enc = nand_fp.read(0x200)
        if len(set(keysect_enc)) != 1:
            keysect_dec = self.crypto.create_ecb_cipher(0x11).decrypt(
                keysect_enc)
            # i'm cheating here by putting the decrypted version in memory and
            #   not reading from the image every time. but it's not AES-CTR so
            #   f**k that.
            self.files['/sector0x96.bin'] = {
                'size': 0x200,
                'offset': 0x12C00,
                'keyslot': 0x11,
                'type': 'keysect',
                'content': keysect_dec
            }

        ncsd_part_fstype = ncsd_header[0x10:0x18]
        ncsd_part_crypttype = ncsd_header[0x18:0x20]
        ncsd_part_raw = ncsd_header[0x20:0x60]
        ncsd_partitions = [[
            readle(ncsd_part_raw[i:i + 4]) * 0x200,
            readle(ncsd_part_raw[i + 4:i + 8]) * 0x200
        ] for i in range(0, 0x40, 0x8)]

        # including padding for crypto
        if self.ctr_twl:
            twl_mbr = self.crypto.create_ctr_cipher(
                Keyslot.TWLNAND,
                self.ctr_twl + 0x1B).decrypt(ncsd_header[0xB0:0x100])[0xE:0x50]
            if twl_mbr[0x40:0x42] == b'\x55\xaa':
                twl_partitions = [[
                    readle(twl_mbr[i + 8:i + 12]) * 0x200,
                    readle(twl_mbr[i + 12:i + 16]) * 0x200
                ] for i in range(0, 0x40, 0x10)]
            else:
                twl_partitions = None

            self.files['/twlmbr.bin'] = {
                'size': 0x42,
                'offset': 0x1BE,
                'keyslot': Keyslot.TWLNAND,
                'type': 'twlmbr',
                'content': twl_mbr
            }
        else:
            twl_partitions = None

        # then actually parse the partitions to create files
        firm_idx = 0
        for idx, part in enumerate(ncsd_partitions):
            if ncsd_part_fstype[idx] == 0:
                continue
            print(
                f'ncsd idx:{idx} fstype:{ncsd_part_fstype[idx]} crypttype:{ncsd_part_crypttype[idx]} '
                f'offset:{part[0]:08x} size:{part[1]:08x} ',
                end='')
            if idx == 0:
                if self.ctr_twl:
                    self.files['/twl_full.img'] = {
                        'size': part[1],
                        'offset': part[0],
                        'keyslot': Keyslot.TWLNAND,
                        'type': 'enc'
                    }
                    print('/twl_full.img')
                    if twl_partitions:
                        twl_part_fstype = 0
                        for t_idx, t_part in enumerate(twl_partitions):
                            if t_part[0] != 0:
                                print(
                                    f'twl  idx:{t_idx}                      '
                                    f'offset:{t_part[0]:08x} size:{t_part[1]:08x} ',
                                    end='')
                                if twl_part_fstype == 0:
                                    self.files['/twln.img'] = {
                                        'size': t_part[1],
                                        'offset': t_part[0],
                                        'keyslot': Keyslot.TWLNAND,
                                        'type': 'enc'
                                    }
                                    print('/twln.img')
                                    twl_part_fstype += 1
                                elif twl_part_fstype == 1:
                                    self.files['/twlp.img'] = {
                                        'size': t_part[1],
                                        'offset': t_part[0],
                                        'keyslot': Keyslot.TWLNAND,
                                        'type': 'enc'
                                    }
                                    print('/twlp.img')
                                    twl_part_fstype += 1
                                else:
                                    self.files[
                                        f'/twl_unk{twl_part_fstype}.img'] = {
                                            'size': t_part[1],
                                            'offset': t_part[0],
                                            'keyslot': Keyslot.TWLNAND,
                                            'type': 'enc'
                                        }
                                    print(f'/twl_unk{twl_part_fstype}.img')
                                    twl_part_fstype += 1
                else:
                    print('<ctr_twl not set>')

            elif self.ctr:
                if ncsd_part_fstype[idx] == 3:
                    # boot9 hardcoded this keyslot, i'll do this properly later
                    self.files[f'/firm{firm_idx}.bin'] = {
                        'size': part[1],
                        'offset': part[0],
                        'keyslot': Keyslot.FIRM,
                        'type': 'enc'
                    }
                    print(f'/firm{firm_idx}.bin')
                    firm_idx += 1

                elif ncsd_part_fstype[
                        idx] == 1 and ncsd_part_crypttype[idx] >= 2:
                    ctrnand_keyslot = Keyslot.CTRNANDOld if ncsd_part_crypttype[
                        idx] == 2 else Keyslot.CTRNANDNew
                    self.files['/ctrnand_full.img'] = {
                        'size': part[1],
                        'offset': part[0],
                        'keyslot': ctrnand_keyslot,
                        'type': 'enc'
                    }
                    print('/ctrnand_full.img')
                    nand_fp.seek(part[0])
                    iv = self.ctr + (part[0] >> 4)
                    ctr_mbr = self.crypto.create_ctr_cipher(
                        ctrnand_keyslot,
                        iv).decrypt(nand_fp.read(0x200))[0x1BE:0x200]
                    if ctr_mbr[0x40:0x42] == b'\x55\xaa':
                        ctr_partitions = [[
                            readle(ctr_mbr[i + 8:i + 12]) * 0x200,
                            readle(ctr_mbr[i + 12:i + 16]) * 0x200
                        ] for i in range(0, 0x40, 0x10)]
                        ctr_part_fstype = 0
                        for c_idx, c_part in enumerate(ctr_partitions):
                            if c_part[0] != 0:
                                print(
                                    f'ctr  idx:{c_idx}                      offset:{part[0] + c_part[0]:08x} '
                                    f'size:{c_part[1]:08x} ',
                                    end='')
                                if ctr_part_fstype == 0:
                                    self.files['/ctrnand_fat.img'] = {
                                        'size': c_part[1],
                                        'offset': part[0] + c_part[0],
                                        'keyslot': ctrnand_keyslot,
                                        'type': 'enc'
                                    }
                                    print('/ctrnand_fat.img')
                                    ctr_part_fstype += 1
                                else:
                                    self.files[
                                        f'/ctr_unk{ctr_part_fstype}.img'] = {
                                            'size': c_part[1],
                                            'offset': part[0] + c_part[0],
                                            'keyslot': ctrnand_keyslot,
                                            'type': 'enc'
                                        }
                                    print(f'/ctr_unk{ctr_part_fstype}.img')
                                    ctr_part_fstype += 1

                elif ncsd_part_fstype[idx] == 4:
                    self.files['/agbsave.bin'] = {
                        'size': part[1],
                        'offset': part[0],
                        'keyslot': Keyslot.AGB,
                        'type': 'enc'
                    }
                    print('/agbsave.bin')

            else:
                print('<ctr not set>')

        self.readonly = readonly

        # GM9 bonus drive
        if raw_nand_size != self.real_nand_size:
            nand_fp.seek(self.real_nand_size)
            bonus_drive_header = nand_fp.read(0x200)
            if bonus_drive_header[0x1FE:0x200] == b'\x55\xAA':
                self.files['/bonus.img'] = {
                    'size': raw_nand_size - self.real_nand_size,
                    'offset': self.real_nand_size,
                    'keyslot': 0xFF,
                    'type': 'raw'
                }

        self.f = nand_fp

        if exefs is not None:
            exefs_size = sum(
                roundup(x.size, 0x200) for x in exefs.entries.values()) + 0x200
            self.files['/essential.exefs'] = {
                'size': exefs_size,
                'offset': 0x200,
                'keyslot': 0xFF,
                'type': 'raw'
            }
            try:
                self.exefs_fuse = ExeFSMount(exefs, g_stat=g_stat)
                self.exefs_fuse.init('/')
                self._essentials_mounted = True
            except Exception as e:
                print(
                    f'Failed to mount essential.exefs: {type(e).__name__}: {e}'
                )
Exemple #7
0
    def __init__(self,
                 nand_fp: BinaryIO,
                 g_stat: os.stat,
                 consoleid: str,
                 cid: str = None,
                 readonly: bool = False):
        self.crypto = CryptoEngine(setup_b9_keys=False)
        self.readonly = readonly

        self.g_stat = {
            'st_ctime': int(g_stat.st_ctime),
            'st_mtime': int(g_stat.st_mtime),
            'st_atime': int(g_stat.st_atime)
        }

        self.files = {}

        res = nand_fp.seek(0, 2)
        if res == 0xF000200:
            self.files['/nocash_blk.bin'] = {
                'offset': 0xF000000,
                'size': 0x200,
                'type': 'dec'
            }
        elif res != 0xF000000:
            exit(
                f'Unknown NAND size (expected 0xF000000 or 0xF000200, got {res:#09X}'
            )

        nand_fp.seek(0)

        try:
            consoleid = bytes.fromhex(consoleid)
        except (ValueError, TypeError):
            try:
                with open(consoleid, 'rb') as f:
                    consoleid = f.read(0x10)
            except (FileNotFoundError, TypeError):
                exit(
                    'Failed to convert Console ID to bytes, or file did not exist.'
                )

        twl_consoleid_list = (readbe(consoleid[4:8]), readbe(consoleid[0:4]))

        key_x_list = [
            twl_consoleid_list[0], twl_consoleid_list[0] ^ 0x24EE6906,
            twl_consoleid_list[1] ^ 0xE65B601D, twl_consoleid_list[1]
        ]

        self.crypto.set_keyslot('x', 0x03, pack('<4I', *key_x_list))

        header_enc = nand_fp.read(0x200)

        if cid:
            try:
                cid = bytes.fromhex(cid)
            except ValueError:
                try:
                    with open(cid, 'rb') as f:
                        cid = f.read(0x10)
                except FileNotFoundError:
                    exit(
                        'Failed to convert CID to bytes, or file did not exist.'
                    )
            self.ctr = readle(sha1(cid).digest()[0:16])

        else:
            # attempt to generate counter
            block_0x1c = readbe(header_enc[0x1C0:0x1D0])
            blk_xored = block_0x1c ^ 0x1804060FE03B77080000896F06000002
            ctr_offs = self.crypto.create_ecb_cipher(0x03).decrypt(
                blk_xored.to_bytes(0x10, 'little'))
            self.ctr = int.from_bytes(ctr_offs, 'big') - 0x1C

            # try the counter
            block_0x1d = header_enc[0x1D0:0x1E0]
            out = self.crypto.create_ctr_cipher(0x03, self.ctr +
                                                0x1D).decrypt(block_0x1d)
            if out != b'\xce<\x06\x0f\xe0\xbeMx\x06\x00\xb3\x05\x01\x00\x00\x02':
                exit(
                    'Counter could not be automatically generated. Please provide the CID, '
                    'or ensure the provided Console ID is correct..')
            print('Counter automatically generated.')

        self.files['/stage2_infoblk1.bin'] = {
            'offset': 0x200,
            'size': 0x200,
            'type': 'dec'
        }
        self.files['/stage2_infoblk2.bin'] = {
            'offset': 0x400,
            'size': 0x200,
            'type': 'dec'
        }
        self.files['/stage2_infoblk3.bin'] = {
            'offset': 0x600,
            'size': 0x200,
            'type': 'dec'
        }
        self.files['/stage2_bootldr.bin'] = {
            'offset': 0x800,
            'size': 0x4DC00,
            'type': 'dec'
        }
        self.files['/stage2_footer.bin'] = {
            'offset': 0x4E400,
            'size': 0x400,
            'type': 'dec'
        }
        self.files['/diag_area.bin'] = {
            'offset': 0xFFA00,
            'size': 0x400,
            'type': 'dec'
        }

        header = self.crypto.create_ctr_cipher(0x03,
                                               self.ctr).decrypt(header_enc)
        mbr = header[0x1BE:0x200]
        if mbr[0x40:0x42] != b'\x55\xaa':
            exit(
                'MBR signature not found. Make sure the provided Console ID and CID are correct.'
            )
        partitions = [[
            readle(mbr[i + 8:i + 12]) * 0x200,
            readle(mbr[i + 12:i + 16]) * 0x200
        ] for i in range(0, 0x40, 0x10)]

        for idx, part in enumerate(partitions):
            if part[0]:
                ptype = 'enc' if idx < 2 else 'dec'
                pname = ('twl_main', 'twl_photo', 'twl_unk1', 'twk_unk2')[idx]
                self.files[f'/{pname}.img'] = {
                    'offset': part[0],
                    'size': part[1],
                    'type': ptype
                }

        self.f = nand_fp
Exemple #8
0
 def path_to_iv(self, path):
     path_hash = sha256(path[self.root_len + 33:].encode('utf-16le') +
                        b'\0\0').digest()
     hash_p1 = readbe(path_hash[0:16])
     hash_p2 = readbe(path_hash[16:32])
     return hash_p1 ^ hash_p2
Exemple #9
0
    def __init__(self, nand_fp: BinaryIO, g_stat: dict, consoleid: str = None, cid: str = None,
                 readonly: bool = False):
        self.crypto = CryptoEngine(setup_b9_keys=False)
        self.readonly = readonly

        self.g_stat = g_stat

        self.files = {}

        self.f = nand_fp

        nand_size = nand_fp.seek(0, 2)
        if nand_size < 0xF000000:
            exit(f'NAND is too small (expected >= 0xF000000, got {nand_size:#X}')
        if nand_size & 0x40 == 0x40:
            self.files['/nocash_blk.bin'] = {'offset': nand_size - 0x40, 'size': 0x40, 'type': 'dec'}

        nand_fp.seek(0)

        try:
            consoleid = bytes.fromhex(consoleid)
        except (ValueError, TypeError):
            try:
                with open(consoleid, 'rb') as f:
                    consoleid = f.read(0x10)
            except (FileNotFoundError, TypeError):
                # read Console ID and CID from footer
                try:
                    nocash_blk: bytes = self.read('/nocash_blk.bin', 0x40, 0, 0)
                except KeyError:
                    if consoleid is None:
                        exit('Nocash block not found, and Console ID not provided.')
                    else:
                        exit('Failed to convert Console ID to bytes, or file did not exist.')
                else:
                    if len(nocash_blk) != 0x40:
                        exit('Failed to read 0x40 of footer (this should never happen)')
                    if nocash_blk[0:0x10] != b'DSi eMMC CID/CPU':
                        exit('Failed to find footer magic "DSi eMMC CID/CPU"')
                    if len(set(nocash_blk[0x10:0x40])) == 1:
                        exit('Nocash block is entirely empty. Maybe re-dump NAND with another exploit, or manually '
                             'get Console ID with some other method.')
                    cid = nocash_blk[0x10:0x20]
                    consoleid = nocash_blk[0x20:0x28][::-1]
                    print('Console ID and CID read from nocash block.')

        twl_consoleid_list = (readbe(consoleid[4:8]), readbe(consoleid[0:4]))

        key_x_list = [twl_consoleid_list[0],
                      twl_consoleid_list[0] ^ 0x24EE6906,
                      twl_consoleid_list[1] ^ 0xE65B601D,
                      twl_consoleid_list[1]]

        self.crypto.set_keyslot('x', Keyslot.TWLNAND, pack('<4I', *key_x_list))

        nand_fp.seek(0)
        header_enc = nand_fp.read(0x200)

        if cid:
            if not isinstance(cid, bytes):  # if cid was already read above
                try:
                    cid = bytes.fromhex(cid)
                except ValueError:
                    try:
                        with open(cid, 'rb') as f:
                            cid = f.read(0x10)
                    except FileNotFoundError:
                        exit('Failed to convert CID to bytes, or file did not exist.')
            self.ctr = readle(sha1(cid).digest()[0:16])

        else:
            # attempt to generate counter
            block_0x1c = readbe(header_enc[0x1C0:0x1D0])
            blk_xored = block_0x1c ^ 0x1804060FE03B77080000896F06000002
            ctr_offs = self.crypto.create_ecb_cipher(Keyslot.TWLNAND).decrypt(blk_xored.to_bytes(0x10, 'little'))
            self.ctr = int.from_bytes(ctr_offs, 'big') - 0x1C

            # try the counter
            block_0x1d = header_enc[0x1D0:0x1E0]
            out = self.crypto.create_ctr_cipher(Keyslot.TWLNAND, self.ctr + 0x1D).decrypt(block_0x1d)
            if out != b'\xce<\x06\x0f\xe0\xbeMx\x06\x00\xb3\x05\x01\x00\x00\x02':
                exit('Counter could not be automatically generated. Please provide the CID, '
                     'or ensure the provided Console ID is correct.')
            print('Counter automatically generated.')

        self.files['/stage2_infoblk1.bin'] = {'offset': 0x200, 'size': 0x200, 'type': 'dec'}
        self.files['/stage2_infoblk2.bin'] = {'offset': 0x400, 'size': 0x200, 'type': 'dec'}
        self.files['/stage2_infoblk3.bin'] = {'offset': 0x600, 'size': 0x200, 'type': 'dec'}
        self.files['/stage2_bootldr.bin'] = {'offset': 0x800, 'size': 0x4DC00, 'type': 'dec'}
        self.files['/stage2_footer.bin'] = {'offset': 0x4E400, 'size': 0x400, 'type': 'dec'}
        self.files['/diag_area.bin'] = {'offset': 0xFFA00, 'size': 0x400, 'type': 'dec'}

        header = self.crypto.create_ctr_cipher(Keyslot.TWLNAND, self.ctr).decrypt(header_enc)
        mbr = header[0x1BE:0x200]
        mbr_sig = mbr[0x40:0x42]
        if mbr_sig != b'\x55\xaa':
            exit(f'MBR signature not found (expected "55aa", got "{mbr_sig.hex()}"). '
                 f'Make sure the provided Console ID and CID are correct.')
        partitions = [[readle(mbr[i + 8:i + 12]) * 0x200,
                       readle(mbr[i + 12:i + 16]) * 0x200] for i in range(0, 0x40, 0x10)]

        for idx, part in enumerate(partitions):
            if part[0]:
                ptype = 'enc' if idx < 2 else 'dec'
                pname = ('twl_main', 'twl_photo', 'twl_unk1', 'twk_unk2')[idx]
                self.files[f'/{pname}.img'] = {'offset': part[0], 'size': part[1], 'type': ptype}