1
0
Fork 0
mirror of https://github.com/DarkMatterCore/nxdumptool.git synced 2024-11-26 04:02:11 +00:00

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().
This commit is contained in:
Pablo Curiel 2022-06-29 08:55:35 +02:00
parent d722683a77
commit c1e3dc719f
6 changed files with 519 additions and 392 deletions

View file

@ -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)

View file

@ -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))

View file

@ -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

View file

@ -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))
{

View file

@ -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. */

View file

@ -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);