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

nca: SparseInfo support (part 1).

Proper CTR IV and context are generated to decrypt the SparseInfo IndirectBucket. More to come at a later time.
This commit is contained in:
Pablo Curiel 2022-03-21 02:49:54 +01:00
parent b51dd1674c
commit 8d8f19b229
3 changed files with 61 additions and 10 deletions

View file

@ -47,6 +47,7 @@ extern "C" {
#define NCA_IVFC_BLOCK_SIZE(x) (1U << (x)) #define NCA_IVFC_BLOCK_SIZE(x) (1U << (x))
#define NCA_BKTR_MAGIC 0x424B5452 /* "BKTR". */ #define NCA_BKTR_MAGIC 0x424B5452 /* "BKTR". */
#define NCA_BKTR_VERSION 1
#define NCA_FS_SECTOR_SIZE 0x200 #define NCA_FS_SECTOR_SIZE 0x200
#define NCA_FS_SECTOR_OFFSET(x) ((u64)(x) * NCA_FS_SECTOR_SIZE) #define NCA_FS_SECTOR_OFFSET(x) ((u64)(x) * NCA_FS_SECTOR_SIZE)
@ -248,7 +249,7 @@ NXDT_ASSERT(NcaHashData, 0xF8);
typedef struct { typedef struct {
u32 magic; ///< "BKTR". u32 magic; ///< "BKTR".
u32 version; ///< offset_count / node_count ? u32 version; ///< Always NCA_BKTR_VERSION.
u32 entry_count; u32 entry_count;
u8 reserved[0x4]; u8 reserved[0x4];
} NcaBucketTreeHeader; } NcaBucketTreeHeader;
@ -285,7 +286,7 @@ NXDT_ASSERT(NcaAesCtrUpperIv, 0x8);
/// Used in NCAs with sparse storage. /// Used in NCAs with sparse storage.
typedef struct { typedef struct {
NcaBucketInfo sparse_bucket; NcaBucketInfo bucket;
u64 physical_offset; u64 physical_offset;
u16 generation; u16 generation;
u8 reserved[0x6]; u8 reserved[0x6];
@ -293,6 +294,13 @@ typedef struct {
NXDT_ASSERT(NcaSparseInfo, 0x30); 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. /// 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. /// NCA0 place the FS headers at the start sector from the NcaFsInfo entries.
typedef struct { typedef struct {
@ -305,7 +313,8 @@ typedef struct {
NcaPatchInfo patch_info; NcaPatchInfo patch_info;
NcaAesCtrUpperIv aes_ctr_upper_iv; NcaAesCtrUpperIv aes_ctr_upper_iv;
NcaSparseInfo sparse_info; NcaSparseInfo sparse_info;
u8 reserved_2[0x88]; NcaCompressionInfo compression_info;
u8 reserved_2[0x68];
} NcaFsHeader; } NcaFsHeader;
NXDT_ASSERT(NcaFsHeader, 0x200); NXDT_ASSERT(NcaFsHeader, 0x200);
@ -336,6 +345,13 @@ typedef struct {
Aes128CtrContext ctr_ctx; Aes128CtrContext ctr_ctx;
Aes128XtsContext xts_decrypt_ctx; Aes128XtsContext xts_decrypt_ctx;
Aes128XtsContext xts_encrypt_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; } NcaFsSectionContext;
typedef enum { typedef enum {
@ -380,7 +396,7 @@ typedef struct {
///< NSP-related fields. ///< 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. 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. 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. u32 content_type_ctx_data_idx; ///< Start index for the data generated by the content type context. Used while creating NSPs.
} NcaContext; } NcaContext;

View file

@ -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 || \ 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 || \ 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 || \ __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 || \ __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.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 || \ (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)) (base_nca_ctx->rights_id_available && !base_nca_ctx->titlekey_retrieved) || (update_nca_ctx->rights_id_available && !update_nca_ctx->titlekey_retrieved))

View file

@ -169,10 +169,14 @@ bool ncaInitializeContext(NcaContext *out, u8 storage_id, u8 hfs_partition_type,
NcaFsSectionContext *fs_ctx = &(out->fs_ctx[i]); NcaFsSectionContext *fs_ctx = &(out->fs_ctx[i]);
u8 *fs_header_hash = out->header.fs_header_hash[i].hash; 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. */ /* Fill section context. */
fs_ctx->nca_ctx = out; fs_ctx->nca_ctx = out;
fs_ctx->section_num = i; fs_ctx->section_num = i;
fs_ctx->section_type = NcaFsSectionType_Invalid; /* Placeholder. */ 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. */ /* Don't proceed if this NCA FS section isn't populated. */
if (!ncaIsFsInfoEntryValid(fs_info)) continue; 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_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); 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. */ /* 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 || \ if (fs_ctx->section_offset < sizeof(NcaHeader) || !fs_ctx->section_size) continue;
(fs_ctx->section_offset + fs_ctx->section_size) > out->content_size) continue;
/* Determine encryption type. */ /* Determine encryption type. */
fs_ctx->encryption_type = (out->format_version == NcaVersion_Nca0 ? NcaEncryptionType_AesXts : fs_ctx->header.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. */ /* Check if we're dealing with an invalid section type value. */
if (fs_ctx->section_type >= NcaFsSectionType_Invalid) continue; 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. */ /* Initialize crypto data. */
if ((!out->rights_id_available || (out->rights_id_available && out->titlekey_retrieved)) && fs_ctx->encryption_type > NcaEncryptionType_None && \ if ((!out->rights_id_available || (out->rights_id_available && out->titlekey_retrieved)) && fs_ctx->encryption_type > NcaEncryptionType_None && \
fs_ctx->encryption_type <= NcaEncryptionType_AesCtrEx) 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. */ /* Initialize the partial AES counter for this section. */
aes128CtrInitializePartialCtr(fs_ctx->ctr, fs_ctx->header.aes_ctr_upper_iv.value, fs_ctx->section_offset); 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. */ /* Initialize AES context. */
if (out->rights_id_available) if (out->rights_id_available)
{ {
/* AES-128-CTR is always used for FS crypto in NCAs with a rights ID. */ /* 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); 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 { } else {
if (fs_ctx->encryption_type == NcaEncryptionType_AesXts) 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. */ /* 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); 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 } /***else
if (fs_ctx->encryption_type == NcaEncryptionType_AesCtr) if (fs_ctx->encryption_type == NcaEncryptionType_AesCtr)
{ {