From c1e3dc719f068dc2dbff392716c323e1bc73c543 Mon Sep 17 00:00:00 2001 From: Pablo Curiel Date: Wed, 29 Jun 2022 08:55:35 +0200 Subject: [PATCH] More NCA changes. * Made ncaGenerateEncryptedFsSectionBlock() entirely private. There's no point in keeping it public. * Moved NCA FS section context initialization into its own function, ncaInitializeFsSectionContext(). * Hash data boundaries are now checked while initializing each NCA FS section context, using ncaFsSectionValidateHashDataBoundaries(). Both ncaValidateHierarchicalSha256Offsets() and ncaValidateHierarchicalIntegrityOffsets() have been removed. * Improved hash region access detection in _ncaReadFsSection() by implementing ncaFsSectionCheckHashRegionAccess(). * ncaGetFsSectionHashTargetProperties() is now used in pfs.c, romfs.c and bktr.c to retrieve the properties from the target hash layer. * Updated sanity checks in pfsInitializeContext(), romfsInitializeContext() and bktrInitializeContext(). --- include/core/nca.h | 88 +++--- source/core/bktr.c | 17 +- source/core/nca.c | 731 ++++++++++++++++++++++++++------------------ source/core/pfs.c | 18 +- source/core/romfs.c | 39 +-- source/core/sha3.c | 18 +- 6 files changed, 519 insertions(+), 392 deletions(-) diff --git a/include/core/nca.h b/include/core/nca.h index e0776c6..b8a474b 100644 --- a/include/core/nca.h +++ b/include/core/nca.h @@ -361,31 +361,32 @@ typedef enum { /// Unlike NCA contexts, we don't need to keep a hash for the NCA FS section header in NCA FS section contexts. /// This is because the functions that modify the NCA FS section header also update the NCA FS section header hash stored in the NCA header. typedef struct { - bool enabled; + bool enabled; ///< Set to true if this NCA FS section has passed all validation checks and can be safely used. void *nca_ctx; ///< NcaContext. Used to perform NCA reads. NcaFsHeader header; ///< Plaintext NCA FS section header. NcaFsHeader encrypted_header; ///< Encrypted NCA FS section header. If the plaintext NCA FS section header is modified, this will hold an encrypted copy of it. ///< Otherwise, this holds the unmodified, encrypted NCA FS section header. - u8 section_num; ///< Index within [0 - 3]. - u64 section_offset; ///< Relative to the start of the NCA content file. - u64 section_size; + u8 section_idx; ///< Index within [0 - 3]. + u64 section_offset; ///< Relative to the start of the NCA content file. Placed here for convenience. + u64 section_size; ///< Placed here for convenience. u8 hash_type; ///< NcaHashType. u8 encryption_type; ///< NcaEncryptionType. u8 section_type; ///< NcaFsSectionType. bool skip_hash_layer_crypto; ///< Set to true if hash layer encryption should be skipped while reading section data. - u64 last_layer_offset; ///< Relative to the start of the FS section. - u64 last_layer_size; - u8 ctr[AES_BLOCK_SIZE]; ///< Used to update the AES CTR context IV based on the desired offset. - Aes128CtrContext ctr_ctx; - Aes128XtsContext xts_decrypt_ctx; - Aes128XtsContext xts_encrypt_ctx; + NcaRegion hash_region; /// Holds the properties for the full hash layer region that precedes the actual FS section data. + + ///< Crypto-related fields. + u8 ctr[AES_BLOCK_SIZE]; ///< Used internally by NCA functions to update the AES-128-CTR context IV based on the desired offset. + Aes128CtrContext ctr_ctx; ///< Used internally by NCA functions to perform AES-128-CTR crypto. + Aes128XtsContext xts_decrypt_ctx; ///< Used internally by NCA functions to perform AES-128-XTS decryption. + Aes128XtsContext xts_encrypt_ctx; ///< Used internally by NCA functions to perform AES-128-XTS encryption. ///< SparseInfo-related fields. - bool has_sparse_layer; - u64 sparse_table_offset; ///< header.sparse_info.physical_offset + header.sparse_info.bucket.offset. Placed here for convenience. + bool has_sparse_layer; ///< Set to true if this NCA FS section has a sparse layer. + u64 sparse_table_offset; ///< header.sparse_info.physical_offset + header.sparse_info.bucket.offset. Relative to the start of the NCA content file. Placed here for convenience. u64 sparse_table_size; ///< header.sparse_info.bucket.size. Placed here for convenience. - u8 sparse_ctr[AES_BLOCK_SIZE]; - Aes128CtrContext sparse_ctr_ctx; + u8 sparse_ctr[AES_BLOCK_SIZE]; ///< AES-128-CTR IV used for sparse table decryption. + Aes128CtrContext sparse_ctr_ctx; ///< AES-128-CTR context used for sparse table decryption. ///< NSP-related fields. bool header_written; ///< Set to true after this FS section header has been written to an output dump. @@ -487,14 +488,6 @@ bool ncaReadFsSection(NcaFsSectionContext *ctx, void *out, u64 read_size, u64 of /// Input offset must be relative to the start of the NCA FS section. bool ncaReadAesCtrExStorageFromBktrSection(NcaFsSectionContext *ctx, void *out, u64 read_size, u64 offset, u32 ctr_val); -/// Returns a pointer to a dynamically allocated buffer used to encrypt the input plaintext data, based on the encryption type used by the input NCA FS section, as well as its offset and size. -/// Input offset must be relative to the start of the NCA FS section. -/// Output size and offset are guaranteed to be aligned to the AES sector size used by the encryption type from the FS section. -/// Output offset is relative to the start of the NCA content file, making it easier to use the output encrypted block to seamlessly replace data while dumping a NCA. -/// This function doesn't support Patch RomFS sections, nor sections with Sparse and/or Compressed storage. -/// Used internally by both ncaGenerateHierarchicalSha256Patch() and ncaGenerateHierarchicalIntegrityPatch(). -void *ncaGenerateEncryptedFsSectionBlock(NcaFsSectionContext *ctx, const void *data, u64 data_size, u64 data_offset, u64 *out_block_size, u64 *out_block_offset); - /// Generates HierarchicalSha256 FS section patch data, which can be used to seamlessly replace NCA data. /// Input offset must be relative to the start of the last HierarchicalSha256 hash region (actual underlying FS). /// Bear in mind that this function recalculates both the NcaHashData block master hash and the NCA FS header hash from the NCA header. @@ -551,32 +544,41 @@ NX_INLINE bool ncaIsHeaderDirty(NcaContext *ctx) return (memcmp(tmp_hash, ctx->header_hash, SHA256_HASH_SIZE) != 0); } -NX_INLINE bool ncaValidateHierarchicalSha256Offsets(NcaHierarchicalSha256Data *hierarchical_sha256_data, u64 section_size) +NX_INLINE bool ncaGetFsSectionHashTargetProperties(NcaFsSectionContext *ctx, u64 *out_offset, u64 *out_size) { - if (!hierarchical_sha256_data || !section_size || !hierarchical_sha256_data->hash_block_size || !hierarchical_sha256_data->hash_region_count || \ - hierarchical_sha256_data->hash_region_count > NCA_HIERARCHICAL_SHA256_MAX_REGION_COUNT) return false; + if (!ctx || (!out_offset && !out_size)) return false; - for(u32 i = 0; i < hierarchical_sha256_data->hash_region_count; i++) + bool success = true; + + switch(ctx->hash_type) { - NcaRegion *hash_region = &(hierarchical_sha256_data->hash_region[i]); - if (!hash_region->size || (hash_region->offset + hash_region->size) > section_size) return false; + case NcaHashType_None: + if (out_offset) *out_offset = 0; + if (out_size) *out_size = ctx->section_size; + break; + case NcaHashType_HierarchicalSha256: + case NcaHashType_HierarchicalSha3256: + { + u32 layer_count = ctx->header.hash_data.hierarchical_sha256_data.hash_region_count; + NcaRegion *hash_region = &(ctx->header.hash_data.hierarchical_sha256_data.hash_region[layer_count - 1]); + if (out_offset) *out_offset = hash_region->offset; + if (out_size) *out_size = hash_region->size; + } + break; + case NcaHashType_HierarchicalIntegrity: + case NcaHashType_HierarchicalIntegritySha3: + { + NcaHierarchicalIntegrityVerificationLevelInformation *lvl_info = &(ctx->header.hash_data.integrity_meta_info.info_level_hash.level_information[NCA_IVFC_LEVEL_COUNT - 1]); + if (out_offset) *out_offset = lvl_info->offset; + if (out_size) *out_size = lvl_info->size; + } + break; + default: + success = false; + break; } - return true; -} - -NX_INLINE bool ncaValidateHierarchicalIntegrityOffsets(NcaIntegrityMetaInfo *integrity_meta_info, u64 section_size) -{ - if (!integrity_meta_info || !section_size || __builtin_bswap32(integrity_meta_info->magic) != NCA_IVFC_MAGIC || integrity_meta_info->master_hash_size != SHA256_HASH_SIZE || \ - integrity_meta_info->info_level_hash.max_level_count != NCA_IVFC_MAX_LEVEL_COUNT) return false; - - for(u32 i = 0; i < NCA_IVFC_LEVEL_COUNT; i++) - { - NcaHierarchicalIntegrityVerificationLevelInformation *level_information = &(integrity_meta_info->info_level_hash.level_information[i]); - if (!level_information->size || !level_information->block_order || (level_information->offset + level_information->size) > section_size) return false; - } - - return true; + return success; } NX_INLINE void ncaFreeHierarchicalSha256Patch(NcaHierarchicalSha256Patch *patch) diff --git a/source/core/bktr.c b/source/core/bktr.c index 10ae7f2..9e4e027 100644 --- a/source/core/bktr.c +++ b/source/core/bktr.c @@ -40,8 +40,7 @@ bool bktrInitializeContext(BktrContext *out, NcaFsSectionContext *base_nca_fs_ct if (!out || !base_nca_fs_ctx || !(base_nca_ctx = (NcaContext*)base_nca_fs_ctx->nca_ctx) || \ !update_nca_fs_ctx || !update_nca_fs_ctx->enabled || !(update_nca_ctx = (NcaContext*)update_nca_fs_ctx->nca_ctx) || \ - update_nca_fs_ctx->section_type != NcaFsSectionType_PatchRomFs || (update_nca_fs_ctx->encryption_type != NcaEncryptionType_AesCtrEx && \ - update_nca_fs_ctx->encryption_type != NcaEncryptionType_AesCtrExSkipLayerHash) || base_nca_ctx->header.program_id != update_nca_ctx->header.program_id || \ + update_nca_fs_ctx->section_type != NcaFsSectionType_PatchRomFs || base_nca_ctx->header.program_id != update_nca_ctx->header.program_id || \ base_nca_ctx->header.content_type != update_nca_ctx->header.content_type || \ __builtin_bswap32(update_nca_fs_ctx->header.patch_info.indirect_bucket.header.magic) != NCA_BKTR_MAGIC || \ update_nca_fs_ctx->header.patch_info.indirect_bucket.header.version != NCA_BKTR_VERSION || \ @@ -59,8 +58,7 @@ bool bktrInitializeContext(BktrContext *out, NcaFsSectionContext *base_nca_fs_ct bktrFreeContext(out); /* Update missing base NCA RomFS status. */ - out->missing_base_romfs = (!base_nca_fs_ctx->enabled || base_nca_fs_ctx->section_type != NcaFsSectionType_RomFs || \ - (base_nca_fs_ctx->encryption_type != NcaEncryptionType_AesCtr && base_nca_fs_ctx->encryption_type != NcaEncryptionType_AesCtrSkipLayerHash)); + out->missing_base_romfs = (!base_nca_fs_ctx->enabled || base_nca_fs_ctx->section_type != NcaFsSectionType_RomFs); /* Initialize base NCA RomFS context. */ if (!out->missing_base_romfs && !romfsInitializeContext(&(out->base_romfs_ctx), base_nca_fs_ctx)) @@ -149,8 +147,15 @@ bool bktrInitializeContext(BktrContext *out, NcaFsSectionContext *base_nca_fs_ct /* Initialize update NCA RomFS context. */ /* Don't verify offsets from Patch RomFS sections, because they reflect the full, patched RomFS image. */ out->patch_romfs_ctx.nca_fs_ctx = update_nca_fs_ctx; - out->patch_romfs_ctx.offset = out->offset = update_nca_fs_ctx->header.hash_data.integrity_meta_info.info_level_hash.level_information[NCA_IVFC_LEVEL_COUNT - 1].offset; - out->patch_romfs_ctx.size = out->size = update_nca_fs_ctx->header.hash_data.integrity_meta_info.info_level_hash.level_information[NCA_IVFC_LEVEL_COUNT - 1].size; + + if (!ncaGetFsSectionHashTargetProperties(update_nca_fs_ctx, &(out->offset), &(out->size))) + { + LOG_MSG("Failed to get target hash layer properties!"); + goto end; + } + + out->patch_romfs_ctx.offset = out->offset; + out->patch_romfs_ctx.size = out->size; /* Read update NCA RomFS header. */ if (!bktrPhysicalSectionRead(out, &(out->patch_romfs_ctx.header), sizeof(RomFileSystemHeader), out->patch_romfs_ctx.offset)) diff --git a/source/core/nca.c b/source/core/nca.c index 757211f..78e5b7a 100644 --- a/source/core/nca.c +++ b/source/core/nca.c @@ -57,14 +57,19 @@ NX_INLINE bool ncaIsVersion0KeyAreaEncrypted(NcaContext *ctx); NX_INLINE u8 ncaGetKeyGenerationValue(NcaContext *ctx); NX_INLINE bool ncaCheckRightsIdAvailability(NcaContext *ctx); +static bool ncaInitializeFsSectionContext(NcaContext *nca_ctx, u32 section_idx); +static bool ncaFsSectionValidateHashDataBoundaries(NcaFsSectionContext *ctx); + static bool _ncaReadFsSection(NcaFsSectionContext *ctx, void *out, u64 read_size, u64 offset); +static bool ncaFsSectionCheckHashRegionAccess(NcaFsSectionContext *ctx, u64 offset, u64 size, u64 *out_chunk_size); + static bool _ncaReadAesCtrExStorageFromBktrSection(NcaFsSectionContext *ctx, void *out, u64 read_size, u64 offset, u32 ctr_val); static void ncaCalculateLayerHash(void *dst, const void *src, size_t size, bool use_sha3); static bool ncaGenerateHashDataPatch(NcaFsSectionContext *ctx, const void *data, u64 data_size, u64 data_offset, void *out, bool is_integrity_patch); static bool ncaWritePatchToMemoryBuffer(NcaContext *ctx, const void *patch, u64 patch_size, u64 patch_offset, void *buf, u64 buf_size, u64 buf_offset); -static void *_ncaGenerateEncryptedFsSectionBlock(NcaFsSectionContext *ctx, const void *data, u64 data_size, u64 data_offset, u64 *out_block_size, u64 *out_block_offset); +static void *ncaGenerateEncryptedFsSectionBlock(NcaFsSectionContext *ctx, const void *data, u64 data_size, u64 data_offset, u64 *out_block_size, u64 *out_block_offset); bool ncaAllocateCryptoBuffer(void) { @@ -92,12 +97,11 @@ void ncaFreeCryptoBuffer(void) bool ncaInitializeContext(NcaContext *out, u8 storage_id, u8 hfs_partition_type, const NcmContentInfo *content_info, Ticket *tik) { NcmContentStorage *ncm_storage = NULL; - u8 fs_header_hash_calc[SHA256_HASH_SIZE] = {0}; u8 valid_fs_section_cnt = 0; if (!out || (storage_id != NcmStorageId_GameCard && !(ncm_storage = titleGetNcmStorageByStorageId(storage_id))) || \ (storage_id == NcmStorageId_GameCard && (!hfs_partition_type || hfs_partition_type >= GameCardHashFileSystemPartitionType_Count)) || !content_info || \ - content_info->content_type > NcmContentType_DeltaFragment) + content_info->content_type >= NcmContentType_DeltaFragment) { LOG_MSG("Invalid parameters!"); return false; @@ -166,191 +170,8 @@ bool ncaInitializeContext(NcaContext *out, u8 storage_id, u8 hfs_partition_type, /* Parse NCA FS sections. */ for(u8 i = 0; i < NCA_FS_HEADER_COUNT; i++) { - NcaFsInfo *fs_info = &(out->header.fs_info[i]); - NcaFsSectionContext *fs_ctx = &(out->fs_ctx[i]); - u8 *fs_header_hash = out->header.fs_header_hash[i].hash; - - NcaSparseInfo *sparse_info = &(fs_ctx->header.sparse_info); - NcaBucketInfo *sparse_bucket = &(sparse_info->bucket); - - /* Fill section context. */ - fs_ctx->nca_ctx = out; - fs_ctx->section_num = i; - fs_ctx->section_type = NcaFsSectionType_Invalid; /* Placeholder. */ - fs_ctx->has_sparse_layer = (sparse_info->generation != 0); - - /* Don't proceed if this NCA FS section isn't populated. */ - if (!ncaIsFsInfoEntryValid(fs_info)) continue; - - /* Calculate NCA FS section header hash. */ - sha256CalculateHash(fs_header_hash_calc, &(fs_ctx->header), sizeof(NcaFsHeader)); - - /* Don't proceed if there's a checksum mismatch. */ - if (memcmp(fs_header_hash_calc, fs_header_hash, SHA256_HASH_SIZE) != 0) continue; - - /* Calculate section offset and size. */ - fs_ctx->section_offset = NCA_FS_SECTOR_OFFSET(fs_info->start_sector); - fs_ctx->section_size = (NCA_FS_SECTOR_OFFSET(fs_info->end_sector) - fs_ctx->section_offset); - - /* Check if we're dealing with an invalid start offset or an empty size. */ - if (fs_ctx->section_offset < sizeof(NcaHeader) || !fs_ctx->section_size) continue; - - /* Determine FS section hash type. */ - fs_ctx->hash_type = fs_ctx->header.hash_type; - if (fs_ctx->hash_type == NcaHashType_Auto || fs_ctx->hash_type == NcaHashType_AutoSha3) - { - switch(fs_ctx->section_num) - { - case 0: /* ExeFS Partition FS. */ - case 2: /* Logo Partition FS. */ - fs_ctx->hash_type = (fs_ctx->hash_type == NcaHashType_Auto ? NcaHashType_HierarchicalSha256 : NcaHashType_HierarchicalSha3256); - break; - case 1: /* RomFS. */ - fs_ctx->hash_type = (fs_ctx->hash_type == NcaHashType_Auto ? NcaHashType_HierarchicalIntegrity : NcaHashType_HierarchicalIntegritySha3); - break; - default: - break; - } - } - - if (fs_ctx->hash_type == NcaHashType_Auto || fs_ctx->hash_type == NcaHashType_AutoSha3 || fs_ctx->hash_type > NcaHashType_HierarchicalIntegritySha3) continue; - - /* Determine FS section encryption type. */ - fs_ctx->encryption_type = (out->format_version == NcaVersion_Nca0 ? NcaEncryptionType_AesXts : fs_ctx->header.encryption_type); - if (fs_ctx->encryption_type == NcaEncryptionType_Auto) - { - switch(fs_ctx->section_num) - { - case 0: /* ExeFS Partition FS. */ - case 1: /* RomFS. */ - fs_ctx->encryption_type = NcaEncryptionType_AesCtr; - break; - case 2: /* Logo Partition FS. */ - fs_ctx->encryption_type = NcaEncryptionType_None; - break; - default: - break; - } - } - - if (fs_ctx->encryption_type == NcaEncryptionType_Auto || fs_ctx->encryption_type > NcaEncryptionType_AesCtrExSkipLayerHash) continue; - - /* Determine FS section type. */ - if (fs_ctx->header.fs_type == NcaFsType_PartitionFs && (fs_ctx->hash_type == NcaHashType_HierarchicalSha256 || fs_ctx->hash_type == NcaHashType_HierarchicalSha3256)) - { - fs_ctx->section_type = NcaFsSectionType_PartitionFs; - } else - if (fs_ctx->header.fs_type == NcaFsType_RomFs && (fs_ctx->hash_type == NcaHashType_HierarchicalIntegrity || fs_ctx->hash_type == NcaHashType_HierarchicalIntegritySha3)) - { - fs_ctx->section_type = ((fs_ctx->encryption_type == NcaEncryptionType_AesCtrEx || fs_ctx->encryption_type == NcaEncryptionType_AesCtrExSkipLayerHash) ? \ - NcaFsSectionType_PatchRomFs : NcaFsSectionType_RomFs); - } else - if (fs_ctx->header.fs_type == NcaFsType_RomFs && fs_ctx->hash_type == NcaHashType_HierarchicalSha256 && out->format_version == NcaVersion_Nca0) - { - fs_ctx->section_type = NcaFsSectionType_Nca0RomFs; - } - - if (fs_ctx->section_type >= NcaFsSectionType_Invalid) continue; - - /* Check if we should skip hash layer decryption while reading this FS section. */ - fs_ctx->skip_hash_layer_crypto = (fs_ctx->encryption_type == NcaEncryptionType_AesCtrSkipLayerHash || fs_ctx->encryption_type == NcaEncryptionType_AesCtrExSkipLayerHash); - if (fs_ctx->skip_hash_layer_crypto) - { - u32 layer_count = 0; - - if (fs_ctx->hash_type == NcaHashType_HierarchicalSha256 || fs_ctx->hash_type == NcaHashType_HierarchicalSha3256) - { - layer_count = fs_ctx->header.hash_data.hierarchical_sha256_data.hash_region_count; - if (layer_count <= NCA_HIERARCHICAL_SHA256_MAX_REGION_COUNT) - { - NcaRegion *last_region = &(fs_ctx->header.hash_data.hierarchical_sha256_data.hash_region[layer_count - 1]); - fs_ctx->last_layer_offset = last_region->offset; - fs_ctx->last_layer_size = last_region->size; - } - } else - if (fs_ctx->hash_type == NcaHashType_HierarchicalIntegrity || fs_ctx->hash_type == NcaHashType_HierarchicalIntegritySha3) - { - layer_count = (fs_ctx->header.hash_data.integrity_meta_info.info_level_hash.max_level_count - 1); - if (layer_count == NCA_IVFC_LEVEL_COUNT) - { - NcaHierarchicalIntegrityVerificationLevelInformation *last_level_info = &(fs_ctx->header.hash_data.integrity_meta_info.info_level_hash.level_information[layer_count - 1]); - fs_ctx->last_layer_offset = last_level_info->offset; - fs_ctx->last_layer_size = last_level_info->size; - } - } else { - fs_ctx->skip_hash_layer_crypto = false; - } - } - - /* Check if we're dealing with a sparse storage. */ - if (fs_ctx->has_sparse_layer) - { - /* Check if the sparse bucket is valid. */ - u64 raw_storage_offset = sparse_info->physical_offset; - u64 raw_storage_size = (sparse_bucket->offset + sparse_bucket->size); - - if (__builtin_bswap32(sparse_bucket->header.magic) != NCA_BKTR_MAGIC || sparse_bucket->header.version != NCA_BKTR_VERSION || raw_storage_offset < sizeof(NcaHeader) || \ - ((raw_storage_offset + raw_storage_size) > out->content_size)) continue; - - if (!raw_storage_size || !sparse_bucket->header.entry_count) - { - /* Increase valid FS section count but don't set this FS section as enabled, since we can't use it. */ - valid_fs_section_cnt++; - continue; - } - - /* Set sparse table properties. */ - fs_ctx->sparse_table_offset = (sparse_info->physical_offset + sparse_bucket->offset); - fs_ctx->sparse_table_size = sparse_bucket->size; - } else { - /* Check if we're within boundaries. */ - if ((fs_ctx->section_offset + fs_ctx->section_size) > out->content_size) continue; - } - - /* Initialize crypto data. */ - if ((!out->rights_id_available || (out->rights_id_available && out->titlekey_retrieved)) && fs_ctx->encryption_type > NcaEncryptionType_None && \ - fs_ctx->encryption_type <= NcaEncryptionType_AesCtrExSkipLayerHash) - { - /* Initialize the partial AES counter for this section. */ - aes128CtrInitializePartialCtr(fs_ctx->ctr, fs_ctx->header.aes_ctr_upper_iv.value, fs_ctx->section_offset); - - if (fs_ctx->has_sparse_layer) - { - /* Initialize the partial AES counter for the sparse info bucket table. */ - NcaAesCtrUpperIv sparse_upper_iv = {0}; - memcpy(sparse_upper_iv.value, fs_ctx->header.aes_ctr_upper_iv.value, sizeof(sparse_upper_iv.value)); - sparse_upper_iv.generation = ((u32)(sparse_info->generation) << 16); - - aes128CtrInitializePartialCtr(fs_ctx->sparse_ctr, sparse_upper_iv.value, fs_ctx->sparse_table_offset); - } - - /* Initialize AES context. */ - if (out->rights_id_available) - { - /* AES-128-CTR is always used for FS crypto in NCAs with a rights ID. */ - aes128CtrContextCreate(&(fs_ctx->ctr_ctx), out->titlekey, fs_ctx->ctr); - if (fs_ctx->has_sparse_layer) aes128CtrContextCreate(&(fs_ctx->sparse_ctr_ctx), out->titlekey, fs_ctx->sparse_ctr); - } else { - if (fs_ctx->encryption_type == NcaEncryptionType_AesXts) - { - /* We need to create two different contexts with AES-128-XTS: one for decryption and another one for encryption. */ - aes128XtsContextCreate(&(fs_ctx->xts_decrypt_ctx), out->decrypted_key_area.aes_xts_1, out->decrypted_key_area.aes_xts_2, false); - aes128XtsContextCreate(&(fs_ctx->xts_encrypt_ctx), out->decrypted_key_area.aes_xts_1, out->decrypted_key_area.aes_xts_2, true); - } else - if (fs_ctx->encryption_type >= NcaEncryptionType_AesCtr && fs_ctx->encryption_type <= NcaEncryptionType_AesCtrExSkipLayerHash) - { - /* Patch RomFS sections also use the AES-128-CTR key from the decrypted NCA key area, for some reason. */ - aes128CtrContextCreate(&(fs_ctx->ctr_ctx), out->decrypted_key_area.aes_ctr, fs_ctx->ctr); - if (fs_ctx->has_sparse_layer) aes128CtrContextCreate(&(fs_ctx->sparse_ctr_ctx), out->decrypted_key_area.aes_ctr, fs_ctx->sparse_ctr); - } - } - } - - /* Enable FS context if we got up to this point. */ - fs_ctx->enabled = true; - - /* Increase valid NCA FS section count. */ - valid_fs_section_cnt++; + /* Increase valid NCA FS section count if the FS section is valid. */ + if (ncaInitializeFsSectionContext(out, i)) valid_fs_section_cnt++; } if (!valid_fs_section_cnt) LOG_MSG("Unable to identify any valid FS sections in NCA \"%s\"!", out->content_id_str); @@ -401,13 +222,6 @@ bool ncaReadAesCtrExStorageFromBktrSection(NcaFsSectionContext *ctx, void *out, return ret; } -void *ncaGenerateEncryptedFsSectionBlock(NcaFsSectionContext *ctx, const void *data, u64 data_size, u64 data_offset, u64 *out_block_size, u64 *out_block_offset) -{ - void *ret = NULL; - SCOPED_LOCK(&g_ncaCryptoBufferMutex) ret = _ncaGenerateEncryptedFsSectionBlock(ctx, data, data_size, data_offset, out_block_size, out_block_offset); - return ret; -} - bool ncaGenerateHierarchicalSha256Patch(NcaFsSectionContext *ctx, const void *data, u64 data_size, u64 data_offset, NcaHierarchicalSha256Patch *out) { bool ret = false; @@ -608,7 +422,7 @@ const char *ncaGetFsSectionTypeName(NcaFsSectionContext *ctx) if (!ctx || !ctx->enabled || !(nca_ctx = (NcaContext*)ctx->nca_ctx)) return str; - is_exefs = (nca_ctx->content_type == NcmContentType_Program && ctx->section_num == 0); + is_exefs = (nca_ctx->content_type == NcmContentType_Program && ctx->section_idx == 0); switch(ctx->section_type) { @@ -874,9 +688,363 @@ NX_INLINE bool ncaCheckRightsIdAvailability(NcaContext *ctx) return false; } +static bool ncaInitializeFsSectionContext(NcaContext *nca_ctx, u32 section_idx) +{ + if (!nca_ctx || section_idx >= NCA_FS_HEADER_COUNT) + { + LOG_MSG("Invalid parameters!"); + return false; + } + + NcaFsInfo *fs_info = &(nca_ctx->header.fs_info[section_idx]); + NcaFsSectionContext *fs_ctx = &(nca_ctx->fs_ctx[section_idx]); + + u8 fs_header_hash_calc[SHA256_HASH_SIZE] = {0}; + u8 *fs_header_hash = nca_ctx->header.fs_header_hash[section_idx].hash; + + NcaSparseInfo *sparse_info = &(fs_ctx->header.sparse_info); + NcaBucketInfo *sparse_bucket = &(sparse_info->bucket); + + bool success = false; + + /* Clear FS section context. */ + memset(fs_ctx, 0, sizeof(NcaFsSectionContext)); + + /* Fill section context. */ + fs_ctx->nca_ctx = nca_ctx; + fs_ctx->section_idx = section_idx; + fs_ctx->section_type = NcaFsSectionType_Invalid; /* Placeholder. */ + fs_ctx->has_sparse_layer = (sparse_info->generation != 0); + + /* Don't proceed if this NCA FS section isn't populated. */ + if (!ncaIsFsInfoEntryValid(fs_info)) + { + LOG_MSG("Invalid FsInfo entry for section #%u in \"%s\". Skipping FS section.", section_idx, nca_ctx->content_id_str); + goto end; + } + + /* Calculate NCA FS section header hash. Don't proceed if there's a checksum mismatch. */ + sha256CalculateHash(fs_header_hash_calc, &(fs_ctx->header), sizeof(NcaFsHeader)); + if (memcmp(fs_header_hash_calc, fs_header_hash, SHA256_HASH_SIZE) != 0) + { + LOG_MSG("Checksum mismatch for FS section header #%u in \"%s\". Skipping FS section.", section_idx, nca_ctx->content_id_str); + goto end; + } + + /* Calculate section offset and size. */ + fs_ctx->section_offset = NCA_FS_SECTOR_OFFSET(fs_info->start_sector); + fs_ctx->section_size = (NCA_FS_SECTOR_OFFSET(fs_info->end_sector) - fs_ctx->section_offset); + + /* Check if we're dealing with an invalid start offset or an empty size. */ + if (fs_ctx->section_offset < sizeof(NcaHeader) || !fs_ctx->section_size) + { + LOG_MSG("Invalid offset/size for FS section #%u in \"%s\" (0x%lX, 0x%lX). Skipping FS section.", section_idx, nca_ctx->content_id_str, fs_ctx->section_offset, \ + fs_ctx->section_size); + goto end; + } + + /* Determine FS section hash type. */ + fs_ctx->hash_type = fs_ctx->header.hash_type; + if (fs_ctx->hash_type == NcaHashType_Auto || fs_ctx->hash_type == NcaHashType_AutoSha3) + { + switch(fs_ctx->section_idx) + { + case 0: /* ExeFS Partition FS. */ + case 2: /* Logo Partition FS. */ + fs_ctx->hash_type = (fs_ctx->hash_type == NcaHashType_Auto ? NcaHashType_HierarchicalSha256 : NcaHashType_HierarchicalSha3256); + break; + case 1: /* RomFS. */ + fs_ctx->hash_type = (fs_ctx->hash_type == NcaHashType_Auto ? NcaHashType_HierarchicalIntegrity : NcaHashType_HierarchicalIntegritySha3); + break; + default: + break; + } + } + + if (fs_ctx->hash_type == NcaHashType_Auto || fs_ctx->hash_type == NcaHashType_AutoSha3 || fs_ctx->hash_type > NcaHashType_HierarchicalIntegritySha3) + { + LOG_MSG("Invalid hash type for FS section #%u in \"%s\" (0x%02X). Skipping FS section.", section_idx, nca_ctx->content_id_str, fs_ctx->hash_type); + goto end; + } + + /* Determine FS section encryption type. */ + fs_ctx->encryption_type = (nca_ctx->format_version == NcaVersion_Nca0 ? NcaEncryptionType_AesXts : fs_ctx->header.encryption_type); + if (fs_ctx->encryption_type == NcaEncryptionType_Auto) + { + switch(fs_ctx->section_idx) + { + case 0: /* ExeFS Partition FS. */ + case 1: /* RomFS. */ + fs_ctx->encryption_type = NcaEncryptionType_AesCtr; + break; + case 2: /* Logo Partition FS. */ + fs_ctx->encryption_type = NcaEncryptionType_None; + break; + default: + break; + } + } + + if (fs_ctx->encryption_type == NcaEncryptionType_Auto || fs_ctx->encryption_type > NcaEncryptionType_AesCtrExSkipLayerHash) + { + LOG_MSG("Invalid encryption type for FS section #%u in \"%s\" (0x%02X). Skipping FS section.", section_idx, nca_ctx->content_id_str, fs_ctx->encryption_type); + goto end; + } + + /* Check if we're dealing with a sparse storage. */ + if (fs_ctx->has_sparse_layer) + { + /* Check if the sparse bucket is valid. */ + u64 raw_storage_offset = sparse_info->physical_offset; + u64 raw_storage_size = (sparse_bucket->offset + sparse_bucket->size); + + if (__builtin_bswap32(sparse_bucket->header.magic) != NCA_BKTR_MAGIC || sparse_bucket->header.version != NCA_BKTR_VERSION || raw_storage_offset < sizeof(NcaHeader) || \ + ((raw_storage_offset + raw_storage_size) > nca_ctx->content_size)) + { + LOG_DATA(sparse_info, sizeof(NcaSparseInfo), "Invalid SparseInfo data for FS section #%u in \"%s\" (0x%lX). Skipping FS section. SparseInfo dump:", section_idx, \ + nca_ctx->content_id_str, nca_ctx->content_size); + goto end; + } + + if (!raw_storage_size || !sparse_bucket->header.entry_count) + { + /* Return true but don't set this FS section as enabled, since we can't really use it. */ + LOG_MSG("Empty SparseInfo data detected for FS section #%u in \"%s\". Skipping FS section.", section_idx, nca_ctx->content_id_str); + success = true; + goto end; + } + + /* Set sparse table properties. */ + fs_ctx->sparse_table_offset = (sparse_info->physical_offset + sparse_bucket->offset); + fs_ctx->sparse_table_size = sparse_bucket->size; + + /* Check if we're within boundaries. */ + if ((fs_ctx->sparse_table_offset + fs_ctx->sparse_table_size) > nca_ctx->content_size) + { + LOG_DATA(sparse_info, sizeof(NcaSparseInfo), "SparseInfo table for FS section #%u in \"%s\" is out of NCA boundaries (0x%lX). Skipping FS section. SparseInfo dump:", \ + section_idx, nca_ctx->content_id_str, nca_ctx->content_size); + goto end; + } + + /* Update section size. */ + fs_ctx->section_size = (MIN(fs_ctx->section_offset, raw_storage_offset) + (MAX(fs_ctx->section_offset, raw_storage_offset) - \ + MIN(fs_ctx->section_offset, raw_storage_offset)) + raw_storage_size); + } + + /* Check if we're within boundaries. */ + if ((fs_ctx->section_offset + fs_ctx->section_size) > nca_ctx->content_size) + { + LOG_MSG("FS section #%u in \"%s\" is out of NCA boundaries. Skipping FS section.", section_idx, nca_ctx->content_id_str); + goto end; + } + + /* Validate HashData boundaries. */ + if (!ncaFsSectionValidateHashDataBoundaries(fs_ctx)) goto end; + + /* Get hash layer region size (offset must always be 0). */ + fs_ctx->hash_region.offset = 0; + if (!ncaGetFsSectionHashTargetProperties(fs_ctx, NULL, &(fs_ctx->hash_region.size))) + { + LOG_MSG("Invalid hash type for FS section #%u in \"%s\" (0x%02X). Skipping FS section.", fs_ctx->section_idx, nca_ctx->content_id_str, fs_ctx->hash_type); + goto end; + } + + /* Check if we're within boundaries. */ + if (fs_ctx->hash_region.size > fs_ctx->section_size || (fs_ctx->section_offset + fs_ctx->hash_region.size) > nca_ctx->content_size) + { + LOG_MSG("Hash layer region for FS section #%u in \"%s\" is out of NCA boundaries. Skipping FS section.", section_idx, nca_ctx->content_id_str); + goto end; + } + + /* Determine FS section type. */ + /* TODO: should NcaHashType_None be handled here as well? */ + switch(fs_ctx->header.fs_type) + { + case NcaFsType_PartitionFs: + if ((fs_ctx->hash_type == NcaHashType_HierarchicalSha256 || fs_ctx->hash_type == NcaHashType_HierarchicalSha3256) && \ + (fs_ctx->encryption_type < NcaEncryptionType_AesCtrEx || fs_ctx->encryption_type == NcaEncryptionType_AesCtrSkipLayerHash)) + { + /* Partition FS with None, XTS or CTR encryption. */ + fs_ctx->section_type = NcaFsSectionType_PartitionFs; + } + + break; + case NcaFsType_RomFs: + if (fs_ctx->hash_type == NcaHashType_HierarchicalIntegrity || fs_ctx->hash_type == NcaHashType_HierarchicalIntegritySha3) + { + if ((fs_ctx->header.patch_info.indirect_bucket.size > 0 || fs_ctx->header.patch_info.aes_ctr_ex_bucket.size > 0) && \ + (fs_ctx->encryption_type == NcaEncryptionType_None || fs_ctx->encryption_type == NcaEncryptionType_AesCtrEx || \ + fs_ctx->encryption_type == NcaEncryptionType_AesCtrExSkipLayerHash)) + { + /* Patch RomFS. */ + fs_ctx->section_type = NcaFsSectionType_PatchRomFs; + } else + if (!fs_ctx->header.patch_info.indirect_bucket.size && !fs_ctx->header.patch_info.aes_ctr_ex_bucket.size && \ + ((fs_ctx->encryption_type >= NcaEncryptionType_None && fs_ctx->encryption_type <= NcaEncryptionType_AesCtr) || \ + fs_ctx->encryption_type == NcaEncryptionType_AesCtrSkipLayerHash)) + { + /* Regular RomFS. */ + fs_ctx->section_type = NcaFsSectionType_RomFs; + } + } else + if (nca_ctx->format_version == NcaVersion_Nca0 && fs_ctx->hash_type == NcaHashType_HierarchicalSha256) + { + /* NCA0 RomFS with XTS encryption. */ + fs_ctx->section_type = NcaFsSectionType_Nca0RomFs; + } + + break; + default: + break; + } + + if (fs_ctx->section_type >= NcaFsSectionType_Invalid) + { + LOG_DATA(&(fs_ctx->header), sizeof(NcaFsHeader), "Unable to determine section type for FS section #%u in \"%s\" (0x%02X, 0x%02X). Skipping FS section. FS header dump:", \ + section_idx, nca_ctx->content_id_str, fs_ctx->hash_type, fs_ctx->encryption_type); + goto end; + } + + /* Check if we should skip hash layer decryption while reading this FS section. */ + fs_ctx->skip_hash_layer_crypto = (fs_ctx->encryption_type == NcaEncryptionType_AesCtrSkipLayerHash || fs_ctx->encryption_type == NcaEncryptionType_AesCtrExSkipLayerHash); + if (fs_ctx->skip_hash_layer_crypto && fs_ctx->hash_type == NcaHashType_None) + { + LOG_MSG("NcaHashType_None used with SkipLayerHash crypto for FS section #%u in \"%s\". Skipping FS section.", section_idx, nca_ctx->content_id_str); + goto end; + } + + /* Initialize crypto data. */ + if ((!nca_ctx->rights_id_available || (nca_ctx->rights_id_available && nca_ctx->titlekey_retrieved)) && fs_ctx->encryption_type > NcaEncryptionType_None && \ + fs_ctx->encryption_type <= NcaEncryptionType_AesCtrExSkipLayerHash) + { + /* Initialize the partial AES counter for this section. */ + aes128CtrInitializePartialCtr(fs_ctx->ctr, fs_ctx->header.aes_ctr_upper_iv.value, fs_ctx->section_offset); + + if (fs_ctx->has_sparse_layer) + { + /* Initialize the partial AES counter for the sparse info bucket table. */ + NcaAesCtrUpperIv sparse_upper_iv = {0}; + memcpy(sparse_upper_iv.value, fs_ctx->header.aes_ctr_upper_iv.value, sizeof(sparse_upper_iv.value)); + sparse_upper_iv.generation = ((u32)(sparse_info->generation) << 16); + + aes128CtrInitializePartialCtr(fs_ctx->sparse_ctr, sparse_upper_iv.value, fs_ctx->sparse_table_offset); + } + + /* Initialize AES context. */ + if (nca_ctx->rights_id_available) + { + /* AES-128-CTR is always used for FS crypto in NCAs with a rights ID. */ + aes128CtrContextCreate(&(fs_ctx->ctr_ctx), nca_ctx->titlekey, fs_ctx->ctr); + if (fs_ctx->has_sparse_layer) aes128CtrContextCreate(&(fs_ctx->sparse_ctr_ctx), nca_ctx->titlekey, fs_ctx->sparse_ctr); + } else { + if (fs_ctx->encryption_type == NcaEncryptionType_AesXts) + { + /* We need to create two different contexts with AES-128-XTS: one for decryption and another one for encryption. */ + aes128XtsContextCreate(&(fs_ctx->xts_decrypt_ctx), nca_ctx->decrypted_key_area.aes_xts_1, nca_ctx->decrypted_key_area.aes_xts_2, false); + aes128XtsContextCreate(&(fs_ctx->xts_encrypt_ctx), nca_ctx->decrypted_key_area.aes_xts_1, nca_ctx->decrypted_key_area.aes_xts_2, true); + } else + if (fs_ctx->encryption_type >= NcaEncryptionType_AesCtr && fs_ctx->encryption_type <= NcaEncryptionType_AesCtrExSkipLayerHash) + { + /* Patch RomFS sections also use the AES-128-CTR key from the decrypted NCA key area, for some reason. */ + aes128CtrContextCreate(&(fs_ctx->ctr_ctx), nca_ctx->decrypted_key_area.aes_ctr, fs_ctx->ctr); + if (fs_ctx->has_sparse_layer) aes128CtrContextCreate(&(fs_ctx->sparse_ctr_ctx), nca_ctx->decrypted_key_area.aes_ctr, fs_ctx->sparse_ctr); + } + } + } + + /* Enable FS context if we got up to this point. */ + fs_ctx->enabled = success = true; + +end: + return success; +} + +static bool ncaFsSectionValidateHashDataBoundaries(NcaFsSectionContext *ctx) +{ + NcaContext *nca_ctx = (NcaContext*)ctx->nca_ctx; + bool success = false, valid = true; + u64 accum = 0; + + switch(ctx->hash_type) + { + case NcaHashType_None: + /* Nothing to validate. */ + success = true; + break; + case NcaHashType_HierarchicalSha256: + case NcaHashType_HierarchicalSha3256: + { + NcaHierarchicalSha256Data *hash_data = &(ctx->header.hash_data.hierarchical_sha256_data); + if (!hash_data->hash_block_size || !hash_data->hash_region_count || hash_data->hash_region_count > NCA_HIERARCHICAL_SHA256_MAX_REGION_COUNT) + { + LOG_DATA(hash_data, sizeof(NcaHierarchicalSha256Data), "Invalid HierarchicalSha256 data for FS section #%u in \"%s\". Skipping FS section. Hash data dump:", \ + ctx->section_idx, nca_ctx->content_id_str); + break; + } + + for(u32 i = 0; i < hash_data->hash_region_count; i++) + { + /* Validate all hash regions boundaries. Skip the last one if a sparse layer is used. */ + NcaRegion *hash_region = &(hash_data->hash_region[i]); + if (hash_region->offset != accum || !hash_region->size || \ + ((i < (hash_data->hash_region_count - 1) || !ctx->has_sparse_layer) && (hash_region->offset + hash_region->size) > ctx->section_size)) + { + LOG_MSG("HierarchicalSha256 region #%u for FS section #%u in \"%s\" is out of NCA boundaries. Skipping FS section.", \ + i, ctx->section_idx, nca_ctx->content_id_str); + valid = false; + break; + } + + accum += hash_region->size; + } + + success = valid; + } + + break; + case NcaHashType_HierarchicalIntegrity: + case NcaHashType_HierarchicalIntegritySha3: + { + NcaIntegrityMetaInfo *hash_data = &(ctx->header.hash_data.integrity_meta_info); + if (__builtin_bswap32(hash_data->magic) != NCA_IVFC_MAGIC || hash_data->master_hash_size != SHA256_HASH_SIZE || \ + hash_data->info_level_hash.max_level_count != NCA_IVFC_MAX_LEVEL_COUNT) + { + LOG_DATA(hash_data, sizeof(NcaIntegrityMetaInfo), "Invalid HierarchicalIntegrity data for FS section #%u in \"%s\". Skipping FS section. Hash data dump:", \ + ctx->section_idx, nca_ctx->content_id_str); + break; + } + + for(u32 i = 0; i < NCA_IVFC_LEVEL_COUNT; i++) + { + /* Validate all level informations boundaries. Skip the last one if a sparse layer is used. */ + NcaHierarchicalIntegrityVerificationLevelInformation *level_information = &(hash_data->info_level_hash.level_information[i]); + if (level_information->offset != accum || !level_information->size || !level_information->block_order || \ + ((i < (NCA_IVFC_LEVEL_COUNT - 1) || !ctx->has_sparse_layer) && (level_information->offset + level_information->size) > ctx->section_size)) + { + LOG_MSG("HierarchicalIntegrity level #%u for FS section #%u in \"%s\" is out of NCA boundaries. Skipping FS section.", \ + i, ctx->section_idx, nca_ctx->content_id_str); + valid = false; + break; + } + + accum += level_information->size; + } + + success = valid; + } + + break; + default: + LOG_MSG("Invalid hash type for FS section #%u in \"%s\" (0x%02X). Skipping FS section.", ctx->section_idx, nca_ctx->content_id_str, ctx->hash_type); + break; + } + + return success; +} + static bool _ncaReadFsSection(NcaFsSectionContext *ctx, void *out, u64 read_size, u64 offset) { - if (!g_ncaCryptoBuffer || !ctx || !ctx->enabled || !ctx->nca_ctx || ctx->section_num >= NCA_FS_HEADER_COUNT || ctx->section_offset < sizeof(NcaHeader) || \ + if (!g_ncaCryptoBuffer || !ctx || !ctx->enabled || !ctx->nca_ctx || ctx->section_idx >= NCA_FS_HEADER_COUNT || ctx->section_offset < sizeof(NcaHeader) || \ ctx->section_type >= NcaFsSectionType_Invalid || ctx->encryption_type == NcaEncryptionType_Auto || ctx->encryption_type > NcaEncryptionType_AesCtrExSkipLayerHash || \ !out || !read_size || (offset + read_size) > ctx->section_size) { @@ -895,95 +1063,44 @@ static bool _ncaReadFsSection(NcaFsSectionContext *ctx, void *out, u64 read_size bool ret = false; - if (!*(nca_ctx->content_id_str) || (nca_ctx->storage_id != NcmStorageId_GameCard && !nca_ctx->ncm_storage) || (nca_ctx->storage_id == NcmStorageId_GameCard && !nca_ctx->gamecard_offset) || \ - (nca_ctx->format_version != NcaVersion_Nca0 && nca_ctx->format_version != NcaVersion_Nca2 && nca_ctx->format_version != NcaVersion_Nca3) || (content_offset + read_size) > nca_ctx->content_size) + if (!*(nca_ctx->content_id_str) || (nca_ctx->storage_id != NcmStorageId_GameCard && !nca_ctx->ncm_storage) || \ + (nca_ctx->storage_id == NcmStorageId_GameCard && !nca_ctx->gamecard_offset) || \ + (nca_ctx->format_version != NcaVersion_Nca0 && nca_ctx->format_version != NcaVersion_Nca2 && nca_ctx->format_version != NcaVersion_Nca3) || \ + (content_offset + read_size) > nca_ctx->content_size) { LOG_MSG("Invalid NCA header parameters!"); goto end; } /* Check if we're supposed to read a hash layer without encryption. */ - if (ctx->skip_hash_layer_crypto) + if (ncaFsSectionCheckHashRegionAccess(ctx, offset, read_size, &block_size)) { - if ((offset + read_size) <= ctx->last_layer_offset || (ctx->last_layer_offset + ctx->last_layer_size) <= offset) + /* Read plaintext area. Use NCA-relative offset. */ + if (!ncaReadContentFile(nca_ctx, out, block_size, content_offset)) { - /* Easy route. Just read the plaintext data we need and bail out. */ - ret = ncaReadContentFile(nca_ctx, out, read_size, content_offset); - if (!ret) LOG_MSG("Failed to read 0x%lX bytes data block at offset 0x%lX from NCA \"%s\" FS section #%u! (plaintext hash layer) (#1).", read_size, content_offset, \ - nca_ctx->content_id_str, ctx->section_num); - goto end; - } else - if ((offset < ctx->last_layer_offset && (offset + read_size) > ctx->last_layer_offset && (offset + read_size) <= (ctx->last_layer_offset + ctx->last_layer_size)) || \ - (ctx->last_layer_offset < offset && (ctx->last_layer_offset + ctx->last_layer_size) > offset && (ctx->last_layer_offset + ctx->last_layer_size) < (offset + read_size))) - { - /* Handle reads that span across both plaintext hash layers and encrypted FS area. */ - bool plaintext_first = (offset < ctx->last_layer_offset); - - /* Calculate offsets and block sizes. */ - block_start_offset = content_offset; - block_end_offset = (ctx->section_offset + (plaintext_first ? ctx->last_layer_offset : (ctx->last_layer_offset + ctx->last_layer_size))); - block_size = (block_end_offset - block_start_offset); - - if (plaintext_first) - { - /* Read plaintext area. Use NCA-relative offset. */ - if (!ncaReadContentFile(nca_ctx, out, block_size, block_start_offset)) - { - LOG_MSG("Failed to read 0x%lX bytes data block at offset 0x%lX from NCA \"%s\" FS section #%u! (plaintext hash layer) (#2).", block_size, block_start_offset, \ - nca_ctx->content_id_str, ctx->section_num); - goto end; - } - - /* Read encrypted area. Use FS-section-relative offset. */ - ret = _ncaReadFsSection(ctx, (u8*)out + block_size, read_size - block_size, offset + block_size); - } else { - /* Read encrypted area. Use FS-section-relative offset. */ - if (!_ncaReadFsSection(ctx, out, block_size, offset)) goto end; - - /* Read plaintext area. Use NCA-relative offset. */ - ret = ncaReadContentFile(nca_ctx, (u8*)out + block_size, read_size - block_size, block_end_offset); - if (!ret) LOG_MSG("Failed to read 0x%lX bytes data block at offset 0x%lX from NCA \"%s\" FS section #%u! (plaintext hash layer) (#3).", read_size - block_size, \ - block_end_offset, nca_ctx->content_id_str, ctx->section_num); - } - - goto end; - } else - if (offset < ctx->last_layer_offset && (offset + read_size) > (ctx->last_layer_offset + ctx->last_layer_size)) - { - /* Handle plaintext hash layer + encrypted FS area + plaintext hash layer reads. */ - u8 *out_u8 = (u8*)out; - - /* Read plaintext area. Use NCA-relative offset. */ - block_start_offset = content_offset; - block_end_offset = (ctx->section_offset + ctx->last_layer_offset); - block_size = (block_end_offset - block_start_offset); - - if (!ncaReadContentFile(nca_ctx, out_u8, block_size, block_start_offset)) - { - LOG_MSG("Failed to read 0x%lX bytes data block at offset 0x%lX from NCA \"%s\" FS section #%u! (plaintext hash layer) (#4).", block_size, block_start_offset, \ - nca_ctx->content_id_str, ctx->section_num); - goto end; - } - - /* Read encrypted area. Use FS-section-relative offset. */ - out_u8 += block_size; - block_start_offset = ctx->last_layer_offset; - block_size = ctx->last_layer_size; - - if (!_ncaReadFsSection(ctx, out_u8, block_size, block_start_offset)) goto end; - - /* Read plaintext area. Use NCA-relative offset. */ - out_u8 += block_size; - block_start_offset = (block_end_offset + block_size); - block_end_offset = (content_offset + read_size); - block_size = (block_end_offset - block_start_offset); - - ret = ncaReadContentFile(nca_ctx, out_u8, block_size, block_start_offset); - if (!ret) LOG_MSG("Failed to read 0x%lX bytes data block at offset 0x%lX from NCA \"%s\" FS section #%u! (plaintext hash layer) (#5).", block_size, block_start_offset, \ - nca_ctx->content_id_str, ctx->section_num); - + LOG_MSG("Failed to read 0x%lX bytes data block at offset 0x%lX from NCA \"%s\" FS section #%u! (plaintext hash region) (#1).", block_size, content_offset, \ + nca_ctx->content_id_str, ctx->section_idx); goto end; } + + /* Read remaining encrypted data, if needed. Use FS-section-relative offset. */ + ret = (read_size ? _ncaReadFsSection(ctx, (u8*)out + block_size, read_size - block_size, offset + block_size) : true); + goto end; + } else + if (block_size && block_size < read_size) + { + /* Read encrypted area. Use FS-section-relative offset. */ + if (!_ncaReadFsSection(ctx, out, block_size, offset)) goto end; + + /* Update parameters. */ + read_size -= block_size; + content_offset += block_size; + + /* Read remaining plaintext data. Use NCA-relative offset. */ + ret = ncaReadContentFile(nca_ctx, (u8*)out + block_size, read_size, content_offset); + if (!ret) LOG_MSG("Failed to read 0x%lX bytes data block at offset 0x%lX from NCA \"%s\" FS section #%u! (plaintext hash region) (#2).", read_size, content_offset, \ + nca_ctx->content_id_str, ctx->section_idx); + goto end; } /* Optimization for reads from plaintext FS sections or reads that are aligned to the AES-CTR / AES-XTS sector size. */ @@ -994,7 +1111,7 @@ static bool _ncaReadFsSection(NcaFsSectionContext *ctx, void *out, u64 read_size /* Read data. */ if (!ncaReadContentFile(nca_ctx, out, read_size, content_offset)) { - LOG_MSG("Failed to read 0x%lX bytes data block at offset 0x%lX from NCA \"%s\" FS section #%u! (aligned).", read_size, content_offset, nca_ctx->content_id_str, ctx->section_num); + LOG_MSG("Failed to read 0x%lX bytes data block at offset 0x%lX from NCA \"%s\" FS section #%u! (aligned).", read_size, content_offset, nca_ctx->content_id_str, ctx->section_idx); goto end; } @@ -1014,7 +1131,7 @@ static bool _ncaReadFsSection(NcaFsSectionContext *ctx, void *out, u64 read_size if (crypt_res != read_size) { LOG_MSG("Failed to AES-XTS decrypt 0x%lX bytes data block at offset 0x%lX from NCA \"%s\" FS section #%u! (aligned).", read_size, content_offset, nca_ctx->content_id_str, \ - ctx->section_num); + ctx->section_idx); goto end; } } else @@ -1042,7 +1159,7 @@ static bool _ncaReadFsSection(NcaFsSectionContext *ctx, void *out, u64 read_size if (!ncaReadContentFile(nca_ctx, g_ncaCryptoBuffer, chunk_size, block_start_offset)) { LOG_MSG("Failed to read 0x%lX bytes encrypted data block at offset 0x%lX from NCA \"%s\" FS section #%u! (unaligned).", chunk_size, block_start_offset, nca_ctx->content_id_str, \ - ctx->section_num); + ctx->section_idx); goto end; } @@ -1055,7 +1172,7 @@ static bool _ncaReadFsSection(NcaFsSectionContext *ctx, void *out, u64 read_size if (crypt_res != chunk_size) { LOG_MSG("Failed to AES-XTS decrypt 0x%lX bytes data block at offset 0x%lX from NCA \"%s\" FS section #%u! (unaligned).", chunk_size, block_start_offset, nca_ctx->content_id_str, \ - ctx->section_num); + ctx->section_idx); goto end; } } else @@ -1075,9 +1192,38 @@ end: return ret; } +static bool ncaFsSectionCheckHashRegionAccess(NcaFsSectionContext *ctx, u64 offset, u64 size, u64 *out_chunk_size) +{ + if (!ctx->skip_hash_layer_crypto) return false; + + NcaRegion *hash_region = &(ctx->hash_region); + + /* Check if our region contains the access. */ + if (hash_region->offset <= offset) + { + if (offset < (hash_region->offset + hash_region->size)) + { + if ((hash_region->offset + hash_region->size) <= (offset + size)) + { + *out_chunk_size = ((hash_region->offset + hash_region->size) - offset); + } else { + *out_chunk_size = size; + } + + return true; + } else { + return false; + } + } else { + if (hash_region->offset <= (offset + size)) *out_chunk_size = (hash_region->offset - offset); + + return false; + } +} + static bool _ncaReadAesCtrExStorageFromBktrSection(NcaFsSectionContext *ctx, void *out, u64 read_size, u64 offset, u32 ctr_val) { - if (!g_ncaCryptoBuffer || !ctx || !ctx->enabled || !ctx->nca_ctx || ctx->section_num >= NCA_FS_HEADER_COUNT || ctx->section_offset < sizeof(NcaHeader) || \ + if (!g_ncaCryptoBuffer || !ctx || !ctx->enabled || !ctx->nca_ctx || ctx->section_idx >= NCA_FS_HEADER_COUNT || ctx->section_offset < sizeof(NcaHeader) || \ ctx->section_type != NcaFsSectionType_PatchRomFs || (ctx->encryption_type != NcaEncryptionType_AesCtrEx && ctx->encryption_type != NcaEncryptionType_AesCtrExSkipLayerHash) || \ !out || !read_size || (offset + read_size) > ctx->section_size) { @@ -1106,7 +1252,7 @@ static bool _ncaReadAesCtrExStorageFromBktrSection(NcaFsSectionContext *ctx, voi /* Read data. */ if (!ncaReadContentFile(nca_ctx, out, read_size, content_offset)) { - LOG_MSG("Failed to read 0x%lX bytes data block at offset 0x%lX from NCA \"%s\" FS section #%u! (aligned).", read_size, content_offset, nca_ctx->content_id_str, ctx->section_num); + LOG_MSG("Failed to read 0x%lX bytes data block at offset 0x%lX from NCA \"%s\" FS section #%u! (aligned).", read_size, content_offset, nca_ctx->content_id_str, ctx->section_idx); goto end; } @@ -1132,7 +1278,7 @@ static bool _ncaReadAesCtrExStorageFromBktrSection(NcaFsSectionContext *ctx, voi if (!ncaReadContentFile(nca_ctx, g_ncaCryptoBuffer, chunk_size, block_start_offset)) { LOG_MSG("Failed to read 0x%lX bytes encrypted data block at offset 0x%lX from NCA \"%s\" FS section #%u! (unaligned).", chunk_size, block_start_offset, nca_ctx->content_id_str, \ - ctx->section_num); + ctx->section_idx); goto end; } @@ -1327,7 +1473,7 @@ static bool ncaGenerateHashDataPatch(NcaFsSectionContext *ctx, const void *data, if (!ctx->skip_hash_layer_crypto || i == layer_count) { /* Reencrypt current layer block (if needed). */ - cur_layer_patch->data = _ncaGenerateEncryptedFsSectionBlock(ctx, cur_layer_block + cur_layer_read_patch_offset, cur_data_size, cur_layer_offset + cur_data_offset, \ + cur_layer_patch->data = ncaGenerateEncryptedFsSectionBlock(ctx, cur_layer_block + cur_layer_read_patch_offset, cur_data_size, cur_layer_offset + cur_data_offset, \ &(cur_layer_patch->size), &(cur_layer_patch->offset)); if (!cur_layer_patch->data) { @@ -1366,7 +1512,7 @@ static bool ncaGenerateHashDataPatch(NcaFsSectionContext *ctx, const void *data, } /* Recalculate FS header hash. */ - sha256CalculateHash(nca_ctx->header.fs_header_hash[ctx->section_num].hash, &(ctx->header), sizeof(NcaFsHeader)); + sha256CalculateHash(nca_ctx->header.fs_header_hash[ctx->section_idx].hash, &(ctx->header), sizeof(NcaFsHeader)); /* Copy content ID. */ memcpy(!is_integrity_patch ? &(hierarchical_sha256_patch->content_id) : &(hierarchical_integrity_patch->content_id), &(nca_ctx->content_id), sizeof(NcmContentId)); @@ -1417,12 +1563,17 @@ static bool ncaWritePatchToMemoryBuffer(NcaContext *ctx, const void *patch, u64 return ((patch_block_offset + buf_block_size) == patch_size); } -static void *_ncaGenerateEncryptedFsSectionBlock(NcaFsSectionContext *ctx, const void *data, u64 data_size, u64 data_offset, u64 *out_block_size, u64 *out_block_offset) +/// Returns a pointer to a dynamically allocated buffer used to encrypt the input plaintext data, based on the encryption type used by the input NCA FS section, as well as its offset and size. +/// Input offset must be relative to the start of the NCA FS section. +/// Output size and offset are guaranteed to be aligned to the AES sector size used by the encryption type from the FS section. +/// Output offset is relative to the start of the NCA content file, making it easier to use the output encrypted block to seamlessly replace data while dumping a NCA. +/// This function doesn't support Patch RomFS sections, nor sections with Sparse and/or Compressed storage. +static void *ncaGenerateEncryptedFsSectionBlock(NcaFsSectionContext *ctx, const void *data, u64 data_size, u64 data_offset, u64 *out_block_size, u64 *out_block_offset) { u8 *out = NULL; bool success = false; - if (!g_ncaCryptoBuffer || !ctx || !ctx->enabled || ctx->has_sparse_layer || !ctx->nca_ctx || ctx->section_num >= NCA_FS_HEADER_COUNT || ctx->section_offset < sizeof(NcaHeader) || \ + if (!g_ncaCryptoBuffer || !ctx || !ctx->enabled || ctx->has_sparse_layer || !ctx->nca_ctx || ctx->section_idx >= NCA_FS_HEADER_COUNT || ctx->section_offset < sizeof(NcaHeader) || \ ctx->hash_type <= NcaHashType_None || ctx->hash_type == NcaHashType_AutoSha3 || ctx->hash_type > NcaHashType_HierarchicalIntegritySha3 || \ ctx->encryption_type == NcaEncryptionType_Auto || ctx->encryption_type == NcaEncryptionType_AesCtrEx || ctx->encryption_type >= NcaEncryptionType_AesCtrExSkipLayerHash || \ ctx->section_type >= NcaFsSectionType_Invalid || !data || !data_size || (data_offset + data_size) > ctx->section_size || !out_block_size || !out_block_offset) @@ -1471,7 +1622,7 @@ static void *_ncaGenerateEncryptedFsSectionBlock(NcaFsSectionContext *ctx, const crypt_res = aes128XtsNintendoCrypt(&(ctx->xts_encrypt_ctx), out, out, data_size, sector_num, NCA_AES_XTS_SECTOR_SIZE, true); if (crypt_res != data_size) { - LOG_MSG("Failed to AES-XTS encrypt 0x%lX bytes data block at offset 0x%lX from NCA \"%s\" FS section #%u! (aligned).", data_size, content_offset, nca_ctx->content_id_str, ctx->section_num); + LOG_MSG("Failed to AES-XTS encrypt 0x%lX bytes data block at offset 0x%lX from NCA \"%s\" FS section #%u! (aligned).", data_size, content_offset, nca_ctx->content_id_str, ctx->section_idx); goto end; } } else @@ -1508,7 +1659,7 @@ static void *_ncaGenerateEncryptedFsSectionBlock(NcaFsSectionContext *ctx, const /* Read decrypted data using aligned offset and size. */ if (!_ncaReadFsSection(ctx, out, block_size, block_start_offset)) { - LOG_MSG("Failed to read decrypted NCA \"%s\" FS section #%u data block!", nca_ctx->content_id_str, ctx->section_num); + LOG_MSG("Failed to read decrypted NCA \"%s\" FS section #%u data block!", nca_ctx->content_id_str, ctx->section_idx); goto end; } @@ -1523,7 +1674,7 @@ static void *_ncaGenerateEncryptedFsSectionBlock(NcaFsSectionContext *ctx, const crypt_res = aes128XtsNintendoCrypt(&(ctx->xts_encrypt_ctx), out, out, block_size, sector_num, NCA_AES_XTS_SECTOR_SIZE, true); if (crypt_res != block_size) { - LOG_MSG("Failed to AES-XTS encrypt 0x%lX bytes data block at offset 0x%lX from NCA \"%s\" FS section #%u! (aligned).", block_size, content_offset, nca_ctx->content_id_str, ctx->section_num); + LOG_MSG("Failed to AES-XTS encrypt 0x%lX bytes data block at offset 0x%lX from NCA \"%s\" FS section #%u! (aligned).", block_size, content_offset, nca_ctx->content_id_str, ctx->section_idx); goto end; } } else diff --git a/source/core/pfs.c b/source/core/pfs.c index 66a7c5d..cb4f7ec 100644 --- a/source/core/pfs.c +++ b/source/core/pfs.c @@ -33,13 +33,11 @@ bool pfsInitializeContext(PartitionFileSystemContext *out, NcaFsSectionContext * PartitionFileSystemHeader pfs_header = {0}; PartitionFileSystemEntry *main_npdm_entry = NULL; - u32 hash_region_count = 0; - NcaRegion *hash_region = NULL; - bool success = false, dump_fs_header = false; - if (!out || !nca_fs_ctx || !nca_fs_ctx->enabled || nca_fs_ctx->section_type != NcaFsSectionType_PartitionFs || nca_fs_ctx->header.fs_type != NcaFsType_PartitionFs || \ - nca_fs_ctx->header.hash_type != NcaHashType_HierarchicalSha256 || !(nca_ctx = (NcaContext*)nca_fs_ctx->nca_ctx) || (nca_ctx->rights_id_available && !nca_ctx->titlekey_retrieved)) + if (!out || !nca_fs_ctx || !nca_fs_ctx->enabled || nca_fs_ctx->section_type != NcaFsSectionType_PartitionFs || \ + (nca_fs_ctx->hash_type != NcaHashType_HierarchicalSha256 && nca_fs_ctx->hash_type != NcaHashType_HierarchicalSha3256) || !(nca_ctx = (NcaContext*)nca_fs_ctx->nca_ctx) || \ + (nca_ctx->rights_id_available && !nca_ctx->titlekey_retrieved)) { LOG_MSG("Invalid parameters!"); return false; @@ -51,18 +49,12 @@ bool pfsInitializeContext(PartitionFileSystemContext *out, NcaFsSectionContext * /* Fill context. */ out->nca_fs_ctx = nca_fs_ctx; - if (!nca_fs_ctx->has_sparse_layer && !ncaValidateHierarchicalSha256Offsets(&(nca_fs_ctx->header.hash_data.hierarchical_sha256_data), nca_fs_ctx->section_size)) + if (!ncaGetFsSectionHashTargetProperties(nca_fs_ctx, &(out->offset), &(out->size))) { - LOG_MSG("Invalid HierarchicalSha256 block!"); + LOG_MSG("Failed to get target hash layer properties!"); goto end; } - hash_region_count = nca_fs_ctx->header.hash_data.hierarchical_sha256_data.hash_region_count; - hash_region = &(nca_fs_ctx->header.hash_data.hierarchical_sha256_data.hash_region[hash_region_count - 1]); - - out->offset = hash_region->offset; - out->size = hash_region->size; - /* Read partial Partition FS header. */ if (!ncaReadFsSection(nca_fs_ctx, &pfs_header, sizeof(PartitionFileSystemHeader), out->offset)) { diff --git a/source/core/romfs.c b/source/core/romfs.c index 9b9a166..e99a48e 100644 --- a/source/core/romfs.c +++ b/source/core/romfs.c @@ -33,49 +33,26 @@ bool romfsInitializeContext(RomFileSystemContext *out, NcaFsSectionContext *nca_ u64 dir_table_offset = 0, file_table_offset = 0; bool success = false, dump_fs_header = false; - if (!out || !nca_fs_ctx || !nca_fs_ctx->enabled || !(nca_ctx = (NcaContext*)nca_fs_ctx->nca_ctx) || (nca_ctx->format_version == NcaVersion_Nca0 && \ - (nca_fs_ctx->section_type != NcaFsSectionType_Nca0RomFs || nca_fs_ctx->header.hash_type != NcaHashType_HierarchicalSha256)) || (nca_ctx->format_version != NcaVersion_Nca0 && \ - (nca_fs_ctx->section_type != NcaFsSectionType_RomFs || nca_fs_ctx->header.hash_type != NcaHashType_HierarchicalIntegrity)) || (nca_ctx->rights_id_available && !nca_ctx->titlekey_retrieved)) + if (!out || !nca_fs_ctx || !nca_fs_ctx->enabled || !(nca_ctx = (NcaContext*)nca_fs_ctx->nca_ctx) || \ + (nca_ctx->format_version == NcaVersion_Nca0 && (nca_fs_ctx->section_type != NcaFsSectionType_Nca0RomFs || nca_fs_ctx->hash_type != NcaHashType_HierarchicalSha256)) || \ + (nca_ctx->format_version != NcaVersion_Nca0 && (nca_fs_ctx->section_type != NcaFsSectionType_RomFs || \ + (nca_fs_ctx->hash_type != NcaHashType_HierarchicalIntegrity && nca_fs_ctx->hash_type != NcaHashType_HierarchicalIntegritySha3))) || \ + (nca_ctx->rights_id_available && !nca_ctx->titlekey_retrieved)) { LOG_MSG("Invalid parameters!"); return false; } - u32 layer_count = 0; - NcaRegion *hash_region = NULL; - NcaHierarchicalIntegrityVerificationLevelInformation *level_information = NULL; - /* Free output context beforehand. */ romfsFreeContext(out); /* Fill context. */ out->nca_fs_ctx = nca_fs_ctx; - if (nca_fs_ctx->section_type == NcaFsSectionType_Nca0RomFs) + if (!ncaGetFsSectionHashTargetProperties(nca_fs_ctx, &(out->offset), &(out->size))) { - if (!ncaValidateHierarchicalSha256Offsets(&(nca_fs_ctx->header.hash_data.hierarchical_sha256_data), nca_fs_ctx->section_size)) - { - LOG_MSG("Invalid HierarchicalSha256 block!"); - goto end; - } - - layer_count = nca_fs_ctx->header.hash_data.hierarchical_sha256_data.hash_region_count; - hash_region = &(nca_fs_ctx->header.hash_data.hierarchical_sha256_data.hash_region[layer_count - 1]); - - out->offset = hash_region->offset; - out->size = hash_region->size; - } else { - if (!nca_fs_ctx->has_sparse_layer && !ncaValidateHierarchicalIntegrityOffsets(&(nca_fs_ctx->header.hash_data.integrity_meta_info), nca_fs_ctx->section_size)) - { - LOG_MSG("Invalid HierarchicalIntegrity block!"); - goto end; - } - - layer_count = NCA_IVFC_LEVEL_COUNT; - level_information = &(nca_fs_ctx->header.hash_data.integrity_meta_info.info_level_hash.level_information[layer_count - 1]); - - out->offset = level_information->offset; - out->size = level_information->size; + LOG_MSG("Failed to get target hash layer properties!"); + goto end; } /* Read RomFS header. */ diff --git a/source/core/sha3.c b/source/core/sha3.c index 24ff324..cd7f10c 100644 --- a/source/core/sha3.c +++ b/source/core/sha3.c @@ -78,15 +78,6 @@ static void sha3ContextCreate(Sha3Context *out, u32 hash_size); static void sha3ProcessBlock(Sha3Context *ctx); static void sha3ProcessLastBlock(Sha3Context *ctx); -/* Functions for SHA3 context creation and simple all-in-one calculation. */ - -_SHA3_CTX_OPS(224); -_SHA3_CTX_OPS(256); -_SHA3_CTX_OPS(384); -_SHA3_CTX_OPS(512); - -#undef _SHA3_CTX_OPS - void sha3ContextUpdate(Sha3Context *ctx, const void *src, size_t size) { if (!ctx || !src || !size || ctx->finalized) @@ -162,6 +153,15 @@ void sha3ContextGetHash(Sha3Context *ctx, void *dst) memcpy(dst, ctx->internal_state, ctx->hash_size); } +/* Functions for SHA3 context creation and simple all-in-one calculation. */ + +_SHA3_CTX_OPS(224); +_SHA3_CTX_OPS(256); +_SHA3_CTX_OPS(384); +_SHA3_CTX_OPS(512); + +#undef _SHA3_CTX_OPS + static u64 rotl_u64(u64 x, int s) { int N = (sizeof(u64) * 8);