From 8d8f19b229986be35b6fd32460bc7f32918e3f8f Mon Sep 17 00:00:00 2001 From: Pablo Curiel Date: Mon, 21 Mar 2022 02:49:54 +0100 Subject: [PATCH] nca: SparseInfo support (part 1). Proper CTR IV and context are generated to decrypt the SparseInfo IndirectBucket. More to come at a later time. --- include/core/nca.h | 30 +++++++++++++++++++++++------- source/core/bktr.c | 2 ++ source/core/nca.c | 39 ++++++++++++++++++++++++++++++++++++--- 3 files changed, 61 insertions(+), 10 deletions(-) diff --git a/include/core/nca.h b/include/core/nca.h index 2b94dfb..6d42a4f 100644 --- a/include/core/nca.h +++ b/include/core/nca.h @@ -47,6 +47,7 @@ extern "C" { #define NCA_IVFC_BLOCK_SIZE(x) (1U << (x)) #define NCA_BKTR_MAGIC 0x424B5452 /* "BKTR". */ +#define NCA_BKTR_VERSION 1 #define NCA_FS_SECTOR_SIZE 0x200 #define NCA_FS_SECTOR_OFFSET(x) ((u64)(x) * NCA_FS_SECTOR_SIZE) @@ -248,7 +249,7 @@ NXDT_ASSERT(NcaHashData, 0xF8); typedef struct { u32 magic; ///< "BKTR". - u32 version; ///< offset_count / node_count ? + u32 version; ///< Always NCA_BKTR_VERSION. u32 entry_count; u8 reserved[0x4]; } NcaBucketTreeHeader; @@ -285,7 +286,7 @@ NXDT_ASSERT(NcaAesCtrUpperIv, 0x8); /// Used in NCAs with sparse storage. typedef struct { - NcaBucketInfo sparse_bucket; + NcaBucketInfo bucket; u64 physical_offset; u16 generation; u8 reserved[0x6]; @@ -293,19 +294,27 @@ typedef struct { NXDT_ASSERT(NcaSparseInfo, 0x30); +/// Used in NCAs with LZ4-compressed sections. +typedef struct { + NcaBucketInfo bucket; +} NcaCompressionInfo; + +NXDT_ASSERT(NcaCompressionInfo, 0x20); + /// Four NCA FS headers are placed right after the 0x400 byte long NCA header in NCA2 and NCA3. /// NCA0 place the FS headers at the start sector from the NcaFsInfo entries. typedef struct { u16 version; - u8 fs_type; ///< NcaFsType. - u8 hash_type; ///< NcaHashType. - u8 encryption_type; ///< NcaEncryptionType. + u8 fs_type; ///< NcaFsType. + u8 hash_type; ///< NcaHashType. + u8 encryption_type; ///< NcaEncryptionType. u8 reserved_1[0x3]; NcaHashData hash_data; NcaPatchInfo patch_info; NcaAesCtrUpperIv aes_ctr_upper_iv; NcaSparseInfo sparse_info; - u8 reserved_2[0x88]; + NcaCompressionInfo compression_info; + u8 reserved_2[0x68]; } NcaFsHeader; NXDT_ASSERT(NcaFsHeader, 0x200); @@ -336,6 +345,13 @@ typedef struct { Aes128CtrContext ctr_ctx; Aes128XtsContext xts_decrypt_ctx; Aes128XtsContext xts_encrypt_ctx; + + ///< 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. + u64 sparse_table_size; ///< header.sparse_info.bucket.size. Placed here for convenience. + u8 sparse_ctr[AES_BLOCK_SIZE]; + Aes128CtrContext sparse_ctr_ctx; } NcaFsSectionContext; typedef enum { @@ -380,7 +396,7 @@ typedef struct { ///< NSP-related fields. bool header_written; ///< Set to true after the NCA header and the FS section headers have been written to an output dump. void *content_type_ctx; ///< Pointer to a content type context (e.g. ContentMetaContext, ProgramInfoContext, NacpContext, LegalInfoContext). Set to NULL if unused. - bool content_type_ctx_patch; ///< Set to true if a NCA patch generated by the content type context is needed and hasn't been completely writen yet. + bool content_type_ctx_patch; ///< Set to true if a NCA patch generated by the content type context is needed and hasn't been completely written yet. u32 content_type_ctx_data_idx; ///< Start index for the data generated by the content type context. Used while creating NSPs. } NcaContext; diff --git a/source/core/bktr.c b/source/core/bktr.c index 6bbb889..4d97da3 100644 --- a/source/core/bktr.c +++ b/source/core/bktr.c @@ -43,7 +43,9 @@ bool bktrInitializeContext(BktrContext *out, NcaFsSectionContext *base_nca_fs_ct update_nca_fs_ctx->section_type != NcaFsSectionType_PatchRomFs || update_nca_fs_ctx->encryption_type != NcaEncryptionType_AesCtrEx || \ 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 || \ __builtin_bswap32(update_nca_fs_ctx->header.patch_info.aes_ctr_ex_bucket.header.magic) != NCA_BKTR_MAGIC || \ + update_nca_fs_ctx->header.patch_info.aes_ctr_ex_bucket.header.version != NCA_BKTR_VERSION || \ (update_nca_fs_ctx->header.patch_info.indirect_bucket.offset + update_nca_fs_ctx->header.patch_info.indirect_bucket.size) != update_nca_fs_ctx->header.patch_info.aes_ctr_ex_bucket.offset || \ (update_nca_fs_ctx->header.patch_info.aes_ctr_ex_bucket.offset + update_nca_fs_ctx->header.patch_info.aes_ctr_ex_bucket.size) != update_nca_fs_ctx->section_size || \ (base_nca_ctx->rights_id_available && !base_nca_ctx->titlekey_retrieved) || (update_nca_ctx->rights_id_available && !update_nca_ctx->titlekey_retrieved)) diff --git a/source/core/nca.c b/source/core/nca.c index e22084e..1ff2e34 100644 --- a/source/core/nca.c +++ b/source/core/nca.c @@ -169,10 +169,14 @@ bool ncaInitializeContext(NcaContext *out, u8 storage_id, u8 hfs_partition_type, 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; @@ -187,9 +191,8 @@ bool ncaInitializeContext(NcaContext *out, u8 storage_id, u8 hfs_partition_type, 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 offset/size. */ - if (fs_ctx->section_offset < sizeof(NcaHeader) || !fs_ctx->section_size || \ - (fs_ctx->section_offset + fs_ctx->section_size) > out->content_size) continue; + /* 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 encryption type. */ fs_ctx->encryption_type = (out->format_version == NcaVersion_Nca0 ? NcaEncryptionType_AesXts : fs_ctx->header.encryption_type); @@ -229,6 +232,24 @@ bool ncaInitializeContext(NcaContext *out, u8 storage_id, u8 hfs_partition_type, /* Check if we're dealing with an invalid section type value. */ if (fs_ctx->section_type >= NcaFsSectionType_Invalid) continue; + /* 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_size || ((raw_storage_offset + raw_storage_size) > out->content_size) || !sparse_bucket->header.entry_count) 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_AesCtrEx) @@ -236,11 +257,22 @@ bool ncaInitializeContext(NcaContext *out, u8 storage_id, u8 hfs_partition_type, /* 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) { @@ -252,6 +284,7 @@ bool ncaInitializeContext(NcaContext *out, u8 storage_id, u8 hfs_partition_type, { /* 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); } /***else if (fs_ctx->encryption_type == NcaEncryptionType_AesCtr) {