From 2e87aa8a73c8f7e9fb111f6808451c428575c108 Mon Sep 17 00:00:00 2001 From: Michael Scire Date: Fri, 20 Jul 2018 02:57:28 -0700 Subject: [PATCH] Fix XTS/NCA2 support. Add NCA0 support. --- Makefile | 4 +- extkeys.c | 6 + ivfc.h | 1 + main.c | 16 +++ nca.c | 387 +++++++++++++++++++++++++++++++++++++++++++-------- nca.h | 24 +++- nca0_romfs.c | 143 +++++++++++++++++++ nca0_romfs.h | 53 +++++++ pki.c | 63 +++++++++ pki.h | 6 + romfs.c | 3 +- rsa.c | 96 +++++++++++-- rsa.h | 1 + settings.h | 1 + 14 files changed, 730 insertions(+), 74 deletions(-) create mode 100644 nca0_romfs.c create mode 100644 nca0_romfs.h diff --git a/Makefile b/Makefile index a34c78a..a1b14c1 100644 --- a/Makefile +++ b/Makefile @@ -13,7 +13,7 @@ all: .c.o: $(CC) $(INCLUDE) -c $(CFLAGS) -o $@ $< -hactool: sha.o aes.o extkeys.o rsa.o npdm.o bktr.o kip.o packages.o pki.o pfs0.o hfs0.o romfs.o utils.o nax0.o nca.o xci.o main.o filepath.o ConvertUTF.o cJSON.o +hactool: sha.o aes.o extkeys.o rsa.o npdm.o bktr.o kip.o packages.o pki.o pfs0.o hfs0.o nca0_romfs.o romfs.o utils.o nax0.o nca.o xci.o main.o filepath.o ConvertUTF.o cJSON.o $(CC) -o $@ $^ $(LDFLAGS) -L $(LIBDIR) aes.o: aes.h types.h @@ -44,6 +44,8 @@ npdm.o: npdm.c cJSON.h types.h romfs.o: ivfc.h types.h +nca0_romfs.o: nca0_romfs.h ivfc.h types.h + rsa.o: rsa.h sha.h types.h sha.o: sha.h types.h diff --git a/extkeys.c b/extkeys.c index 806b72d..141d1b8 100644 --- a/extkeys.c +++ b/extkeys.c @@ -1,6 +1,7 @@ #include #include #include +#include "pki.h" #include "extkeys.h" /** @@ -236,6 +237,11 @@ void extkeys_initialize_keyset(nca_keyset_t *keyset, FILE *f) { } else if (strcmp(key, "tsec_key") == 0) { parse_hex_key(keyset->tsec_key, value, sizeof(keyset->tsec_key)); matched_key = 1; + } else if (strcmp(key, "beta_nca0_exponent") == 0) { + unsigned char exponent[0x100] = {0}; + parse_hex_key(exponent, value, sizeof(exponent)); + pki_set_beta_nca0_exponent(exponent); + matched_key = 1; } else { char test_name[0x100]; memset(test_name, 0, sizeof(100)); diff --git a/ivfc.h b/ivfc.h index 46ea64b..4fc4e0e 100644 --- a/ivfc.h +++ b/ivfc.h @@ -54,6 +54,7 @@ typedef struct { } romfs_hdr_t; typedef struct { + uint32_t parent; uint32_t sibling; uint32_t child; uint32_t file; diff --git a/main.c b/main.c index 2111525..0c2791c 100644 --- a/main.c +++ b/main.c @@ -205,6 +205,8 @@ int main(int argc, char **argv) { nca_ctx.tool_ctx->file_type = FILETYPE_PFS0; } else if (!strcmp(optarg, "romfs")) { nca_ctx.tool_ctx->file_type = FILETYPE_ROMFS; + } else if (!strcmp(optarg, "nca0_romfs") || !strcmp(optarg, "nca0romfs") || !strcmp(optarg, "betaromfs") || !strcmp(optarg, "beta_romfs")) { + nca_ctx.tool_ctx->file_type = FILETYPE_NCA0_ROMFS; } else if (!strcmp(optarg, "hfs0")) { nca_ctx.tool_ctx->file_type = FILETYPE_HFS0; } else if (!strcmp(optarg, "xci") || !strcmp(optarg, "gamecard") || !strcmp(optarg, "gc")) { @@ -524,6 +526,20 @@ int main(int argc, char **argv) { } break; } + case FILETYPE_NCA0_ROMFS: { + nca0_romfs_ctx_t romfs_ctx; + memset(&romfs_ctx, 0, sizeof(romfs_ctx)); + romfs_ctx.file = tool_ctx.file; + romfs_ctx.tool_ctx = &tool_ctx; + nca0_romfs_process(&romfs_ctx); + if (romfs_ctx.files) { + free(romfs_ctx.files); + } + if (romfs_ctx.directories) { + free(romfs_ctx.directories); + } + break; + } case FILETYPE_NPDM: { npdm_t raw_hdr; memset(&raw_hdr, 0, sizeof(raw_hdr)); diff --git a/nca.c b/nca.c index f158607..16e3d1a 100644 --- a/nca.c +++ b/nca.c @@ -1,6 +1,7 @@ #include #include "nca.h" #include "aes.h" +#include "pki.h" #include "sha.h" #include "rsa.h" #include "utils.h" @@ -38,11 +39,16 @@ void nca_section_fseek(nca_section_ctx_t *ctx, uint64_t offset) { if (ctx->is_decrypted) { fseeko64(ctx->file, (ctx->offset + offset), SEEK_SET); ctx->cur_seek = (ctx->offset + offset); - } else if (ctx->header->crypt_type == CRYPT_XTS) { - fseeko64(ctx->file, (ctx->offset + offset) & ~0x1FF, SEEK_SET); - ctx->cur_seek = (ctx->offset + offset) & ~0x1FF; - ctx->sector_num = offset / 0x200; - ctx->sector_ofs = offset & 0x1FF; + } else if (ctx->crypt_type == CRYPT_XTS) { + fseeko64(ctx->file, (ctx->offset + offset) & ~ctx->sector_mask, SEEK_SET); + ctx->cur_seek = (ctx->offset + offset) & ~ctx->sector_mask; + ctx->sector_num = offset / ctx->sector_size; + ctx->sector_ofs = offset & ctx->sector_mask; + } else if (ctx->crypt_type == CRYPT_NCA0) { + fseeko64(ctx->file, (ctx->offset + offset) & ~ctx->sector_mask, SEEK_SET); + ctx->cur_seek = ((ctx->offset + offset - 0x400ULL) & ~ctx->sector_mask) + 0x400ULL; + ctx->sector_num = (ctx->offset + offset - 0x400ULL) / ctx->sector_size; + ctx->sector_ofs = (ctx->offset + offset - 0x400ULL) & ctx->sector_mask; } else if (ctx->type == BKTR && ctx->bktr_ctx.subsection_block != NULL) { /* No better way to do this than to make all BKTR seeking virtual. */ ctx->bktr_ctx.virtual_seek = offset; @@ -59,7 +65,7 @@ void nca_section_fseek(nca_section_ctx_t *ctx, uint64_t offset) { ctx->bktr_ctx.base_seek = section_ofs; } } - } else if (ctx->header->crypt_type != CRYPT_NONE) { /* CTR, and BKTR until subsections are read. */ + } else if (ctx->crypt_type != CRYPT_NONE) { /* CTR, and BKTR until subsections are read. */ fseeko64(ctx->file, (ctx->offset + offset) & ~0xF, SEEK_SET); ctx->cur_seek = (ctx->offset + offset) & ~0xF; nca_update_ctr(ctx->ctr, ctx->offset + offset); @@ -133,47 +139,54 @@ size_t nca_section_fread(nca_section_ctx_t *ctx, void *buffer, size_t count) { return read; } - if (ctx->header->crypt_type == CRYPT_XTS) { /* AES-XTS requires special handling... */ - unsigned char sector_buf[0x200]; - if ((read = fread(§or_buf, size, 0x200, ctx->file)) != 0x200) { + if (ctx->crypt_type == CRYPT_XTS || ctx->crypt_type == CRYPT_NCA0) { /* AES-XTS requires special handling... */ + unsigned char *sector_buf = malloc(ctx->sector_size); + if ((read = fread(sector_buf, size, ctx->sector_size, ctx->file)) != ctx->sector_size) { + free(sector_buf); return 0; } - aes_xts_decrypt(ctx->aes, §or_buf, §or_buf, 0x200, ctx->sector_num, 0x200); - if (count > 0x200 - ctx->sector_ofs) { /* We're leaving the sector... */ - memcpy(buffer, §or_buf + ctx->sector_ofs, 0x200 - ctx->sector_ofs); + aes_xts_decrypt(ctx->aes, sector_buf, sector_buf, ctx->sector_size, ctx->sector_num, ctx->sector_size); + if (count > ctx->sector_size - ctx->sector_ofs) { /* We're leaving the sector... */ + memcpy(buffer, sector_buf + ctx->sector_ofs, ctx->sector_size - ctx->sector_ofs); + size_t remaining = count - (ctx->sector_size - ctx->sector_ofs); + size_t ofs = (ctx->sector_size - ctx->sector_ofs); ctx->sector_num++; ctx->sector_ofs = 0; - size_t remaining = count - (0x200 - ctx->sector_ofs); - size_t ofs = (0x200 - ctx->sector_ofs); - if (remaining & ~0x1FF) { /* Read intermediate sectors. */ - if ((read = fread((char *)buffer + ofs, size, (remaining & ~0x1FF), ctx->file)) != (remaining & ~0x1FF)) { + if (remaining & ~ctx->sector_mask) { /* Read intermediate sectors. */ + uint64_t addl; + if ((addl = fread((char *)buffer + ofs, size, (remaining & ~ctx->sector_mask), ctx->file)) != (remaining & ~ctx->sector_mask)) { + free(sector_buf); return ofs; } - aes_xts_decrypt(ctx->aes, (char *)buffer + ofs, (char *)buffer + ofs, remaining & ~0x1FF, ctx->sector_num, 0x200); - ctx->sector_num += remaining / 0x200; - ofs += remaining & ~0x1FF; - remaining &= 0x1FF; + aes_xts_decrypt(ctx->aes, (char *)buffer + ofs, (char *)buffer + ofs, remaining & ~ctx->sector_mask, ctx->sector_num, ctx->sector_size); + ctx->sector_num += remaining / ctx->sector_size; + ofs += remaining & ~ctx->sector_mask; + remaining &= ctx->sector_mask; + read += addl; } if (remaining) { /* Read last sector. */ - if ((read = fread(§or_buf, size, 0x200, ctx->file)) != 0x200) { + if ((read = fread(sector_buf, size, ctx->sector_size, ctx->file)) != ctx->sector_size) { + free(sector_buf); return ofs; } - aes_xts_decrypt(ctx->aes, §or_buf, §or_buf, 0x200, ctx->sector_num, 0x200); - memcpy((char *)buffer + ofs, §or_buf, remaining); + aes_xts_decrypt(ctx->aes, sector_buf, sector_buf, ctx->sector_size, ctx->sector_num, ctx->sector_size); + memcpy((char *)buffer + ofs, sector_buf, remaining); ctx->sector_ofs = remaining; read = count; } } else { - memcpy(buffer, §or_buf + ctx->sector_ofs, count); - ctx->sector_num += (ctx->sector_ofs + count) / 0x200; + memcpy(buffer, sector_buf + ctx->sector_ofs, count); + ctx->sector_num += (ctx->sector_ofs + count) / ctx->sector_size; ctx->sector_ofs += count; - ctx->sector_ofs &= 0x1FF; + ctx->sector_ofs &= ctx->sector_mask; + read = count; } + free(sector_buf); } else { /* Perform decryption, if necessary. */ /* AES-CTR. */ - if (ctx->header->crypt_type == CRYPT_CTR || (ctx->header->crypt_type == CRYPT_BKTR && ctx->bktr_ctx.subsection_block == NULL)) + if (ctx->crypt_type == CRYPT_CTR || (ctx->crypt_type == CRYPT_BKTR && ctx->bktr_ctx.subsection_block == NULL)) { if (ctx->sector_ofs) { if ((read = fread(block_buf, 1, 0x10, ctx->file)) != 0x10) { @@ -193,12 +206,12 @@ size_t nca_section_fread(nca_section_ctx_t *ctx, void *buffer, size_t count) { return read_in_block + nca_section_fread(ctx, (char *)buffer + read_in_block, count - read_in_block); } if ((read = fread(buffer, 1, count, ctx->file)) != count) { - return 0; + return 0; } aes_setiv(ctx->aes, ctx->ctr, 16); aes_decrypt(ctx->aes, buffer, buffer, count); nca_section_fseek(ctx, ctx->cur_seek - ctx->offset + count); - } else if (ctx->header->crypt_type == CRYPT_BKTR) { /* Spooky BKTR AES-CTR. */ + } else if (ctx->crypt_type == CRYPT_BKTR) { /* Spooky BKTR AES-CTR. */ /* Are we doing virtual reads, or physical reads? */ if (ctx->tool_ctx->base_file != NULL && ctx->physical_reads == 0) { bktr_relocation_entry_t *reloc = bktr_get_relocation(ctx->bktr_ctx.relocation_block, ctx->bktr_ctx.virtual_seek); @@ -263,7 +276,7 @@ void nca_free_section_contexts(nca_ctx_t *ctx) { if (ctx->section_contexts[i].aes) { free_aes_ctx(ctx->section_contexts[i].aes); } - if (ctx->section_contexts[i].pfs0_ctx.is_exefs) { + if (ctx->section_contexts[i].type == PFS0 && ctx->section_contexts[i].pfs0_ctx.is_exefs) { free(ctx->section_contexts[i].pfs0_ctx.npdm); } else if (ctx->section_contexts[i].type == ROMFS) { if (ctx->section_contexts[i].romfs_ctx.directories) { @@ -272,6 +285,13 @@ void nca_free_section_contexts(nca_ctx_t *ctx) { if (ctx->section_contexts[i].romfs_ctx.files) { free(ctx->section_contexts[i].romfs_ctx.files); } + } else if (ctx->section_contexts[i].type == NCA0_ROMFS) { + if (ctx->section_contexts[i].nca0_romfs_ctx.directories) { + free(ctx->section_contexts[i].nca0_romfs_ctx.directories); + } + if (ctx->section_contexts[i].nca0_romfs_ctx.files) { + free(ctx->section_contexts[i].nca0_romfs_ctx.files); + } } else if (ctx->section_contexts[i].type == BKTR) { if (ctx->section_contexts[i].bktr_ctx.subsection_block) { free(ctx->section_contexts[i].bktr_ctx.subsection_block); @@ -413,7 +433,7 @@ void nca_process(nca_ctx_t *ctx) { /* Parse sections. */ for (unsigned int i = 0; i < 4; i++) { if (ctx->header.section_entries[i].media_start_offset) { /* Section exists. */ - ctx->section_contexts[i].is_present = 1; + ctx->section_contexts[i].is_present = 1; ctx->section_contexts[i].is_decrypted = ctx->is_decrypted; ctx->section_contexts[i].tool_ctx = ctx->tool_ctx; ctx->section_contexts[i].file = ctx->file; @@ -421,17 +441,24 @@ void nca_process(nca_ctx_t *ctx) { ctx->section_contexts[i].offset = media_to_real(ctx->header.section_entries[i].media_start_offset); ctx->section_contexts[i].size = media_to_real(ctx->header.section_entries[i].media_end_offset) - ctx->section_contexts[i].offset; ctx->section_contexts[i].header = &ctx->header.fs_headers[i]; + ctx->section_contexts[i].crypt_type = ctx->section_contexts[i].header->crypt_type; + if (ctx->format_version == NCAVERSION_NCA0 || ctx->format_version == NCAVERSION_NCA0_BETA) { + ctx->section_contexts[i].crypt_type = CRYPT_NCA0; + } if (ctx->section_contexts[i].header->partition_type == PARTITION_PFS0 && ctx->section_contexts[i].header->fs_type == FS_TYPE_PFS0) { ctx->section_contexts[i].type = PFS0; ctx->section_contexts[i].pfs0_ctx.superblock = &ctx->section_contexts[i].header->pfs0_superblock; } else if (ctx->section_contexts[i].header->partition_type == PARTITION_ROMFS && ctx->section_contexts[i].header->fs_type == FS_TYPE_ROMFS) { - if (ctx->section_contexts[i].header->crypt_type == CRYPT_BKTR) { + if (ctx->section_contexts[i].crypt_type == CRYPT_BKTR) { ctx->section_contexts[i].type = BKTR; ctx->section_contexts[i].bktr_ctx.superblock = &ctx->section_contexts[i].header->bktr_superblock; } else { ctx->section_contexts[i].type = ROMFS; ctx->section_contexts[i].romfs_ctx.superblock = &ctx->section_contexts[i].header->romfs_superblock; } + } else if (ctx->section_contexts[i].header->partition_type == PARTITION_ROMFS && ctx->section_contexts[i].header->fs_type == FS_TYPE_PFS0 && (ctx->format_version == NCAVERSION_NCA0 || ctx->format_version == NCAVERSION_NCA0_BETA)) { + ctx->section_contexts[i].type = NCA0_ROMFS; + ctx->section_contexts[i].nca0_romfs_ctx.superblock = &ctx->section_contexts[i].header->nca0_romfs_superblock; } else { ctx->section_contexts[i].type = INVALID; } @@ -444,7 +471,7 @@ void nca_process(nca_ctx_t *ctx) { ctx->section_contexts[i].sector_num = 0; ctx->section_contexts[i].sector_ofs = 0; - if (ctx->section_contexts[i].header->crypt_type == CRYPT_NONE) { + if (ctx->section_contexts[i].crypt_type == CRYPT_NONE) { ctx->section_contexts[i].is_decrypted = 1; } @@ -454,10 +481,14 @@ void nca_process(nca_ctx_t *ctx) { if (ctx->has_rights_id) { ctx->section_contexts[i].aes = new_aes_ctx(ctx->tool_ctx->settings.dec_titlekey, 16, AES_MODE_CTR); } else { - if (ctx->section_contexts[i].header->crypt_type == CRYPT_CTR || ctx->section_contexts[i].header->crypt_type == CRYPT_BKTR) { + if (ctx->section_contexts[i].crypt_type == CRYPT_CTR || ctx->section_contexts[i].crypt_type == CRYPT_BKTR) { ctx->section_contexts[i].aes = new_aes_ctx(ctx->decrypted_keys[2], 16, AES_MODE_CTR); - } else if (ctx->section_contexts[i].header->crypt_type == CRYPT_XTS) { - ctx->section_contexts[i].aes = new_aes_ctx(ctx->decrypted_keys[0], 32, AES_MODE_XTS); + } else if (ctx->section_contexts[i].crypt_type == CRYPT_XTS || ctx->section_contexts[i].crypt_type == CRYPT_NCA0) { + ctx->section_contexts[i].aes = new_aes_ctx(ctx->decrypted_keys, 32, AES_MODE_XTS); + ctx->section_contexts[i].sector_size = 0x200ULL; + } + if (ctx->section_contexts[i].sector_size) { + ctx->section_contexts[i].sector_mask = ctx->section_contexts[i].sector_size - 1ULL; } } } @@ -481,6 +512,9 @@ void nca_process(nca_ctx_t *ctx) { case ROMFS: nca_process_ivfc_section(&ctx->section_contexts[i]); break; + case NCA0_ROMFS: + nca_process_nca0_romfs_section(&ctx->section_contexts[i]); + break; case BKTR: nca_process_bktr_section(&ctx->section_contexts[i]); break; @@ -512,6 +546,11 @@ int nca_decrypt_header(nca_ctx_t *ctx) { if (ctx->header.magic == MAGIC_NCA3 || ctx->header.magic == MAGIC_NCA2) { if (ctx->header._0x340[0] == 0 && !memcmp(ctx->header._0x340, ctx->header._0x340 + 1, 0xBF)) { ctx->is_decrypted = 1; + if (ctx->header.magic == MAGIC_NCA3) { + ctx->format_version = NCAVERSION_NCA3; + } else { + ctx->format_version = NCAVERSION_NCA2; + } return 1; } } @@ -522,30 +561,65 @@ int nca_decrypt_header(nca_ctx_t *ctx) { - aes_ctx_t *aes_ctx = new_aes_ctx(ctx->tool_ctx->settings.keyset.header_key, 32, AES_MODE_XTS); - aes_xts_decrypt(aes_ctx, &dec_header, &ctx->header, 0x400, 0, 0x200); + aes_ctx_t *hdr_aes_ctx = new_aes_ctx(ctx->tool_ctx->settings.keyset.header_key, 32, AES_MODE_XTS); + aes_xts_decrypt(hdr_aes_ctx, &dec_header, &ctx->header, 0x400, 0, 0x200); if (dec_header.magic == MAGIC_NCA3) { - aes_xts_decrypt(aes_ctx, &dec_header, &ctx->header, 0xC00, 0, 0x200); + ctx->format_version = NCAVERSION_NCA3; + aes_xts_decrypt(hdr_aes_ctx, &dec_header, &ctx->header, 0xC00, 0, 0x200); ctx->header = dec_header; } else if (dec_header.magic == MAGIC_NCA2) { + ctx->format_version = NCAVERSION_NCA2; for (unsigned int i = 0; i < 4; i++) { if (dec_header.fs_headers[i]._0x148[0] != 0 || memcmp(dec_header.fs_headers[i]._0x148, dec_header.fs_headers[i]._0x148 + 1, 0xB7)) { - aes_xts_decrypt(aes_ctx, &dec_header.fs_headers[i], &ctx->header.fs_headers[i], 0x200, 0, 0x200); + aes_xts_decrypt(hdr_aes_ctx, &dec_header.fs_headers[i], &ctx->header.fs_headers[i], 0x200, 0, 0x200); } else { memset(&dec_header.fs_headers[i], 0, sizeof(nca_fs_header_t)); } } ctx->header = dec_header; + } else if (dec_header.magic == MAGIC_NCA0) { + memset(ctx->decrypted_keys, 0, 0x40); + unsigned char out_keydata[0x100]; + size_t out_len = 0; + if (rsa2048_oaep_decrypt_verify(out_keydata, sizeof(out_keydata), (const unsigned char *)dec_header.encrypted_keys, pki_get_beta_nca0_modulus(), pki_get_beta_nca0_exponent(), 0x100, pki_get_beta_nca0_label_hash(), &out_len)) { + if (out_len >= 0x20) { + memcpy(ctx->decrypted_keys, out_keydata, 0x20); + ctx->format_version = NCAVERSION_NCA0_BETA; + } + } else { + ctx->format_version = NCAVERSION_NCA0; + aes_ctx_t *aes_ctx = new_aes_ctx(ctx->tool_ctx->settings.keyset.key_area_keys[ctx->crypto_type][dec_header.kaek_ind], 16, AES_MODE_ECB); + aes_decrypt(aes_ctx, ctx->decrypted_keys, dec_header.encrypted_keys, 0x20); + free_aes_ctx(aes_ctx); + } + if (ctx->format_version != NCAVERSION_UNKNOWN) { + memset(dec_header.fs_headers, 0, sizeof(dec_header.fs_headers)); + aes_ctx_t *aes_ctx = new_aes_ctx(ctx->decrypted_keys, 32, AES_MODE_XTS); + for (unsigned int i = 0; i < 4; i++) { + if (dec_header.section_entries[i].media_start_offset) { /* Section exists. */ + uint64_t offset = media_to_real(dec_header.section_entries[i].media_start_offset); + fseeko64(ctx->tool_ctx->file, offset, SEEK_SET); + if (fread(&dec_header.fs_headers[i], sizeof(dec_header.fs_headers[i]), 1, ctx->tool_ctx->file) != 1) { + fprintf(stderr, "Failed to read NCA0 FS header at %" PRIx64"!\n", offset); + exit(EXIT_FAILURE); + } + aes_xts_decrypt(aes_ctx, &dec_header.fs_headers[i], &dec_header.fs_headers[i], sizeof(dec_header.fs_headers[i]), (offset - 0x400ULL) >> 9ULL, 0x200); + } + } + free_aes_ctx(aes_ctx); + ctx->header = dec_header; + } } - free_aes_ctx(aes_ctx); - return ctx->header.magic == MAGIC_NCA3 || ctx->header.magic == MAGIC_NCA2; + free_aes_ctx(hdr_aes_ctx); + return ctx->format_version != NCAVERSION_UNKNOWN; } /* Decrypt key area. */ void nca_decrypt_key_area(nca_ctx_t *ctx) { + if (ctx->format_version == NCAVERSION_NCA0_BETA || ctx->format_version == NCAVERSION_NCA0) return; aes_ctx_t *aes_ctx = new_aes_ctx(ctx->tool_ctx->settings.keyset.key_area_keys[ctx->crypto_type][ctx->header.kaek_ind], 16, AES_MODE_ECB); aes_decrypt(aes_ctx, ctx->decrypted_keys, ctx->header.encrypted_keys, 0x40); free_aes_ctx(aes_ctx); @@ -589,15 +663,36 @@ char *nca_get_encryption_type(nca_ctx_t *ctx) { } void nca_print_key_area(nca_ctx_t *ctx) { - printf("Key Area (Encrypted):\n"); - for (unsigned int i = 0; i < 0x4; i++) { - printf(" Key %"PRId32" (Encrypted): ", i); - memdump(stdout, "", &ctx->header.encrypted_keys[i], 0x10); - } - printf("Key Area (Decrypted):\n"); - for (unsigned int i = 0; i < 0x4; i++) { - printf(" Key %"PRId32" (Decrypted): ", i); - memdump(stdout, "", &ctx->decrypted_keys[i], 0x10); + if (ctx->format_version == NCAVERSION_NCA0_BETA) { + printf("Key Area (Encrypted):\n"); + memdump(stdout, "Key (RSA-OAEP Encrypted): ", &ctx->header.encrypted_keys, 0x100); + printf("Key Area (Decrypted):\n"); + for (unsigned int i = 0; i < 0x2; i++) { + printf(" Key %"PRId32" (Decrypted): ", i); + memdump(stdout, "", &ctx->decrypted_keys[i], 0x10); + } + } else if (ctx->format_version == NCAVERSION_NCA0) { + printf("Key Area (Encrypted):\n"); + for (unsigned int i = 0; i < 0x2; i++) { + printf(" Key %"PRId32" (Encrypted): ", i); + memdump(stdout, "", &ctx->header.encrypted_keys[i], 0x10); + } + printf("Key Area (Decrypted):\n"); + for (unsigned int i = 0; i < 0x2; i++) { + printf(" Key %"PRId32" (Decrypted): ", i); + memdump(stdout, "", &ctx->decrypted_keys[i], 0x10); + } + } else { + printf("Key Area (Encrypted):\n"); + for (unsigned int i = 0; i < 0x4; i++) { + printf(" Key %"PRId32" (Encrypted): ", i); + memdump(stdout, "", &ctx->header.encrypted_keys[i], 0x10); + } + printf("Key Area (Decrypted):\n"); + for (unsigned int i = 0; i < 0x4; i++) { + printf(" Key %"PRId32" (Decrypted): ", i); + memdump(stdout, "", &ctx->decrypted_keys[i], 0x10); + } } } @@ -607,6 +702,7 @@ char *nca_get_section_type(nca_section_ctx_t *meta) { if (meta->pfs0_ctx.is_exefs) return "ExeFS"; return "PFS0"; } + case NCA0_ROMFS: return "NCA0 RomFS"; case ROMFS: return "RomFS"; case BKTR: return "Patch RomFS"; case INVALID: @@ -624,7 +720,9 @@ void nca_print_sections(nca_ctx_t *ctx) { printf(" Offset: 0x%012"PRIx64"\n", ctx->section_contexts[i].offset); printf(" Size: 0x%012"PRIx64"\n", ctx->section_contexts[i].size); printf(" Partition Type: %s\n", nca_get_section_type(&ctx->section_contexts[i])); - memdump(stdout, " Section CTR: ", &ctx->section_contexts[i].ctr, 16); + if (!(ctx->format_version == NCAVERSION_NCA0 || ctx->format_version == NCAVERSION_NCA0_BETA)) { + memdump(stdout, " Section CTR: ", &ctx->section_contexts[i].ctr, 16); + } switch (ctx->section_contexts[i].type) { case PFS0: { nca_print_pfs0_section(&ctx->section_contexts[i]); @@ -634,6 +732,10 @@ void nca_print_sections(nca_ctx_t *ctx) { nca_print_ivfc_section(&ctx->section_contexts[i]); break; } + case NCA0_ROMFS: { + nca_print_nca0_romfs_section(&ctx->section_contexts[i]); + break; + } case BKTR: { nca_print_bktr_section(&ctx->section_contexts[i]); break; @@ -722,10 +824,7 @@ validity_t nca_section_check_external_hash_table(nca_section_ctx_t *ctx, unsigne read_size = data_len - ofs; } - uint64_t r = nca_section_fread(ctx, block, read_size); - if (r != read_size) { - fprintf(stderr, "%012"PRIx64" %012"PRIx64" %08"PRIx64"\n", ofs, data_len, r); - fprintf(stderr, "%d %d\n", ctx->is_decrypted, ctx->section_num); + if (nca_section_fread(ctx, block, read_size) != read_size) { fprintf(stderr, "Failed to read section!\n"); exit(EXIT_FAILURE); } @@ -891,8 +990,7 @@ void nca_process_ivfc_section(nca_section_ctx_t *ctx) { exit(EXIT_FAILURE); } - /* Switch RomFS has actual entries at table offset + 4 for no good reason. */ - nca_section_fseek(ctx, ctx->romfs_ctx.romfs_offset + ctx->romfs_ctx.header.dir_meta_table_offset + 4); + nca_section_fseek(ctx, ctx->romfs_ctx.romfs_offset + ctx->romfs_ctx.header.dir_meta_table_offset); if (nca_section_fread(ctx, ctx->romfs_ctx.directories, ctx->romfs_ctx.header.dir_meta_table_size) != ctx->romfs_ctx.header.dir_meta_table_size) { fprintf(stderr, "Failed to read RomFS directory cache!\n"); exit(EXIT_FAILURE); @@ -911,6 +1009,50 @@ void nca_process_ivfc_section(nca_section_ctx_t *ctx) { } } + +void nca_process_nca0_romfs_section(nca_section_ctx_t *ctx) { + nca0_romfs_superblock_t *sb = ctx->nca0_romfs_ctx.superblock; + ctx->superblock_hash_validity = nca_section_check_external_hash_table(ctx, sb->master_hash, sb->hash_table_offset, sb->hash_table_size, sb->hash_table_size, 0); + if (ctx->tool_ctx->action & ACTION_VERIFY) { + /* Verify actual ROMFS... */ + ctx->nca0_romfs_ctx.hash_table_validity = nca_section_check_hash_table(ctx, sb->hash_table_offset, sb->romfs_offset, sb->romfs_size, sb->block_size, 0); + } + + if (ctx->superblock_hash_validity != VALIDITY_VALID) return; + + ctx->nca0_romfs_ctx.romfs_offset = sb->romfs_offset; + nca_section_fseek(ctx, ctx->nca0_romfs_ctx.romfs_offset); + if (nca_section_fread(ctx, &ctx->nca0_romfs_ctx.header, sizeof(nca0_romfs_hdr_t)) != sizeof(nca0_romfs_hdr_t)) { + fprintf(stderr, "Failed to read NCA0 RomFS header!\n"); + } + + if ((ctx->tool_ctx->action & (ACTION_EXTRACT | ACTION_LISTROMFS)) && ctx->nca0_romfs_ctx.header.header_size == NCA0_ROMFS_HEADER_SIZE) { + /* Pre-load the file/data entry caches. */ + ctx->nca0_romfs_ctx.directories = calloc(1, ctx->nca0_romfs_ctx.header.dir_meta_table_size); + if (ctx->nca0_romfs_ctx.directories == NULL) { + fprintf(stderr, "Failed to allocate NCA0 RomFS directory cache!\n"); + exit(EXIT_FAILURE); + } + + nca_section_fseek(ctx, ctx->nca0_romfs_ctx.romfs_offset + ctx->nca0_romfs_ctx.header.dir_meta_table_offset); + if (nca_section_fread(ctx, ctx->nca0_romfs_ctx.directories, ctx->nca0_romfs_ctx.header.dir_meta_table_size) != ctx->nca0_romfs_ctx.header.dir_meta_table_size) { + fprintf(stderr, "Failed to read NCA0 RomFS directory cache!\n"); + exit(EXIT_FAILURE); + } + + ctx->nca0_romfs_ctx.files = calloc(1, ctx->nca0_romfs_ctx.header.file_meta_table_size); + if (ctx->nca0_romfs_ctx.files == NULL) { + fprintf(stderr, "Failed to allocate NCA0 RomFS file cache!\n"); + exit(EXIT_FAILURE); + } + nca_section_fseek(ctx, ctx->nca0_romfs_ctx.romfs_offset + ctx->nca0_romfs_ctx.header.file_meta_table_offset); + if (nca_section_fread(ctx, ctx->nca0_romfs_ctx.files, ctx->nca0_romfs_ctx.header.file_meta_table_size) != ctx->nca0_romfs_ctx.header.file_meta_table_size) { + fprintf(stderr, "Failed to read NCA0 RomFS file cache!\n"); + exit(EXIT_FAILURE); + } + } +} + void nca_process_bktr_section(nca_section_ctx_t *ctx) { bktr_superblock_t *sb = ctx->bktr_ctx.superblock; /* Validate magics. */ @@ -1084,6 +1226,25 @@ void nca_print_ivfc_section(nca_section_ctx_t *ctx) { } } +void nca_print_nca0_romfs_section(nca_section_ctx_t *ctx) { + if (ctx->tool_ctx->action & ACTION_VERIFY) { + if (ctx->superblock_hash_validity == VALIDITY_VALID) { + memdump(stdout, " Superblock Hash (GOOD): ", &ctx->nca0_romfs_ctx.superblock->master_hash, 0x20); + } else { + memdump(stdout, " Superblock Hash (FAIL): ", &ctx->nca0_romfs_ctx.superblock->master_hash, 0x20); + } + printf(" Hash Table (%s):\n", GET_VALIDITY_STR(ctx->nca0_romfs_ctx.hash_table_validity)); + } else { + memdump(stdout, " Superblock Hash: ", &ctx->nca0_romfs_ctx.superblock->master_hash, 0x20); + printf(" Hash Table:\n"); + } + printf(" Offset: %012"PRIx64"\n", ctx->nca0_romfs_ctx.superblock->hash_table_offset); + printf(" Size: %012"PRIx64"\n", ctx->nca0_romfs_ctx.superblock->hash_table_size); + printf(" Block Size: 0x%"PRIx32"\n", ctx->nca0_romfs_ctx.superblock->block_size); + printf(" RomFS Offset: %012"PRIx64"\n", ctx->nca0_romfs_ctx.superblock->romfs_offset); + printf(" RomFS Size: %012"PRIx64"\n", ctx->nca0_romfs_ctx.superblock->romfs_size); +} + void nca_print_bktr_section(nca_section_ctx_t *ctx) { if (ctx->bktr_ctx.subsection_block == NULL) { printf(" BKTR section seems to be corrupted.\n"); @@ -1130,8 +1291,8 @@ void nca_save_section_file(nca_section_ctx_t *ctx, uint64_t ofs, uint64_t total_ } memset(buf, 0xCC, read_size); /* Debug in case I fuck this up somehow... */ uint64_t end_ofs = ofs + total_size; - nca_section_fseek(ctx, ofs); while (ofs < end_ofs) { + nca_section_fseek(ctx, ofs); if (ofs + read_size >= end_ofs) read_size = end_ofs - ofs; if (nca_section_fread(ctx, buf, read_size) != read_size) { fprintf(stderr, "Failed to read file!\n"); @@ -1163,6 +1324,10 @@ void nca_save_section(nca_section_ctx_t *ctx) { offset = ctx->romfs_ctx.ivfc_levels[IVFC_MAX_LEVEL - 1].data_offset; size = ctx->romfs_ctx.ivfc_levels[IVFC_MAX_LEVEL - 1].data_size; break; + case NCA0_ROMFS: + offset = ctx->nca0_romfs_ctx.superblock->romfs_offset; + size = ctx->nca0_romfs_ctx.superblock->romfs_size; + break; case BKTR: if (ctx->tool_ctx->base_file != NULL) { offset = ctx->bktr_ctx.ivfc_levels[IVFC_MAX_LEVEL - 1].data_offset; @@ -1182,7 +1347,7 @@ void nca_save_section(nca_section_ctx_t *ctx) { /* Handle overrides. */ if (ctx->type == PFS0 && ctx->pfs0_ctx.is_exefs && ctx->tool_ctx->settings.exefs_path.enabled && ctx->tool_ctx->settings.exefs_path.path.valid == VALIDITY_VALID) { secpath = &ctx->tool_ctx->settings.exefs_path.path; - } else if (ctx->type == ROMFS && ctx->tool_ctx->settings.romfs_path.enabled && ctx->tool_ctx->settings.romfs_path.path.valid == VALIDITY_VALID) { + } else if ((ctx->type == ROMFS || ctx->type == NCA0_ROMFS) && ctx->tool_ctx->settings.romfs_path.enabled && ctx->tool_ctx->settings.romfs_path.path.valid == VALIDITY_VALID) { secpath = &ctx->tool_ctx->settings.romfs_path.path; } if (secpath != NULL && secpath->valid == VALIDITY_VALID) { @@ -1198,6 +1363,9 @@ void nca_save_section(nca_section_ctx_t *ctx) { case ROMFS: nca_save_ivfc_section(ctx); break; + case NCA0_ROMFS: + nca_save_nca0_romfs_section(ctx); + break; case BKTR: if (ctx->tool_ctx->base_file == NULL) { fprintf(stderr, "Note: cannot save BKTR section without base romfs.\n"); @@ -1301,6 +1469,40 @@ int nca_visit_romfs_file(nca_section_ctx_t *ctx, uint32_t file_offset, filepath_ return found_file; } +int nca_visit_nca0_romfs_file(nca_section_ctx_t *ctx, uint32_t file_offset, filepath_t *dir_path) { + romfs_fentry_t *entry = romfs_get_fentry(ctx->nca0_romfs_ctx.files, file_offset); + filepath_t *cur_path = calloc(1, sizeof(filepath_t)); + if (cur_path == NULL) { + fprintf(stderr, "Failed to allocate filepath!\n"); + exit(EXIT_FAILURE); + } + + filepath_copy(cur_path, dir_path); + if (entry->name_size) { + filepath_append_n(cur_path, entry->name_size, "%s", entry->name); + } + + int found_file = 1; + + /* If we're extracting... */ + uint64_t phys_offset = ctx->nca0_romfs_ctx.romfs_offset + ctx->nca0_romfs_ctx.header.data_offset + entry->offset; + + if ((ctx->tool_ctx->action & ACTION_LISTROMFS) == 0) { + printf("Saving %s...\n", cur_path->char_path); + nca_save_section_file(ctx, phys_offset, entry->size, cur_path); + } else { + printf("rom:%s\n", cur_path->char_path); + } + + free(cur_path); + + if (entry->sibling != ROMFS_ENTRY_EMPTY) { + return found_file | nca_visit_nca0_romfs_file(ctx, entry->sibling, dir_path); + } + + return found_file; +} + int nca_visit_romfs_dir(nca_section_ctx_t *ctx, uint32_t dir_offset, filepath_t *parent_path) { romfs_direntry_t *entry; if (ctx->type == ROMFS) { @@ -1346,6 +1548,40 @@ int nca_visit_romfs_dir(nca_section_ctx_t *ctx, uint32_t dir_offset, filepath_t return any_files; } +int nca_visit_nca0_romfs_dir(nca_section_ctx_t *ctx, uint32_t dir_offset, filepath_t *parent_path) { + romfs_direntry_t *entry = romfs_get_direntry(ctx->nca0_romfs_ctx.directories, dir_offset); + filepath_t *cur_path = calloc(1, sizeof(filepath_t)); + if (cur_path == NULL) { + fprintf(stderr, "Failed to allocate filepath!\n"); + exit(EXIT_FAILURE); + } + + filepath_copy(cur_path, parent_path); + if (entry->name_size) { + filepath_append_n(cur_path, entry->name_size, "%s", entry->name); + } + + /* If we're actually extracting the romfs, make directory. */ + if ((ctx->tool_ctx->action & ACTION_LISTROMFS) == 0) { + os_makedir(cur_path->os_path); + } + + int any_files = 0; + + if (entry->file != ROMFS_ENTRY_EMPTY) { + any_files |= nca_visit_nca0_romfs_file(ctx, entry->file, cur_path); + } + if (entry->child != ROMFS_ENTRY_EMPTY) { + any_files |= nca_visit_nca0_romfs_file(ctx, entry->child, cur_path); + } + + if (entry->sibling != ROMFS_ENTRY_EMPTY) { + nca_visit_nca0_romfs_dir(ctx, entry->sibling, parent_path); + } + + free(cur_path); + return any_files; +} void nca_save_ivfc_section(nca_section_ctx_t *ctx) { if (ctx->superblock_hash_validity == VALIDITY_VALID) { @@ -1377,6 +1613,37 @@ void nca_save_ivfc_section(nca_section_ctx_t *ctx) { fprintf(stderr, "Error: section %"PRId32" is corrupted!\n", ctx->section_num); } + +void nca_save_nca0_romfs_section(nca_section_ctx_t *ctx) { + if (ctx->superblock_hash_validity == VALIDITY_VALID) { + if (ctx->nca0_romfs_ctx.header.header_size == NCA0_ROMFS_HEADER_SIZE) { + if (ctx->tool_ctx->action & ACTION_LISTROMFS) { + filepath_t fakepath; + filepath_init(&fakepath); + filepath_set(&fakepath, ""); + + nca_visit_nca0_romfs_dir(ctx, 0, &fakepath); + } else { + filepath_t *dirpath = NULL; + if (ctx->tool_ctx->settings.romfs_dir_path.enabled) { + dirpath = &ctx->tool_ctx->settings.romfs_dir_path.path; + } + if (dirpath == NULL || dirpath->valid != VALIDITY_VALID) { + dirpath = &ctx->tool_ctx->settings.section_dir_paths[ctx->section_num]; + } + if (dirpath != NULL && dirpath->valid == VALIDITY_VALID) { + os_makedir(dirpath->os_path); + nca_visit_nca0_romfs_dir(ctx, 0, dirpath); + } + } + + return; + } + } + + fprintf(stderr, "Error: section %"PRId32" is corrupted!\n", ctx->section_num); +} + void nca_save_bktr_section(nca_section_ctx_t *ctx) { if (ctx->superblock_hash_validity == VALIDITY_VALID) { if (ctx->bktr_ctx.header.header_size == ROMFS_HEADER_SIZE) { diff --git a/nca.h b/nca.h index 1c22e8b..cca33e8 100644 --- a/nca.h +++ b/nca.h @@ -9,10 +9,12 @@ #include "npdm.h" #include "pfs0.h" #include "ivfc.h" +#include "nca0_romfs.h" #include "bktr.h" #define MAGIC_NCA3 0x3341434E /* "NCA3" */ #define MAGIC_NCA2 0x3241434E /* "NCA2" */ +#define MAGIC_NCA0 0x3041434E /* "NCA0" */ typedef struct { uint32_t media_start_offset; @@ -57,7 +59,8 @@ typedef enum { CRYPT_NONE = 1, CRYPT_XTS = 2, CRYPT_CTR = 3, - CRYPT_BKTR = 4 + CRYPT_BKTR = 4, + CRYPT_NCA0 = MAGIC_NCA0 } section_crypt_type_t; /* NCA FS header. */ @@ -71,6 +74,7 @@ typedef struct { union { /* FS-specific superblock. Size = 0x138. */ pfs0_superblock_t pfs0_superblock; romfs_superblock_t romfs_superblock; + nca0_romfs_superblock_t nca0_romfs_superblock; bktr_superblock_t bktr_superblock; }; union { @@ -118,9 +122,19 @@ enum nca_section_type { PFS0, ROMFS, BKTR, + NCA0_ROMFS, INVALID }; +enum nca_version { + NCAVERSION_UNKNOWN = 0, + NCAVERSION_NCA0_BETA, + NCAVERSION_NCA0, + /* NCAVERSION_NCA1, // Does this exist? */ + NCAVERSION_NCA2, + NCAVERSION_NCA3 +}; + typedef struct { int is_present; enum nca_section_type type; @@ -130,11 +144,14 @@ typedef struct { uint32_t section_num; nca_fs_header_t *header; int is_decrypted; + uint64_t sector_size; + uint64_t sector_mask; aes_ctx_t *aes; /* AES context for the section. */ hactool_ctx_t *tool_ctx; union { pfs0_ctx_t pfs0_ctx; romfs_ctx_t romfs_ctx; + nca0_romfs_ctx_t nca0_romfs_ctx; bktr_section_ctx_t bktr_ctx; }; validity_t superblock_hash_validity; @@ -143,6 +160,7 @@ typedef struct { size_t sector_num; uint32_t sector_ofs; int physical_reads; /* Should reads be forced physical? */ + section_crypt_type_t crypt_type; } nca_section_ctx_t; typedef struct nca_ctx { @@ -151,6 +169,7 @@ typedef struct nca_ctx { unsigned char crypto_type; int has_rights_id; int is_decrypted; + enum nca_version format_version; validity_t fixed_sig_validity; validity_t npdm_sig_validity; hactool_ctx_t *tool_ctx; @@ -177,15 +196,18 @@ void nca_save_section_file(nca_section_ctx_t *ctx, uint64_t ofs, uint64_t total_ /* These have to be in nca.c, sadly... */ void nca_process_pfs0_section(nca_section_ctx_t *ctx); void nca_process_ivfc_section(nca_section_ctx_t *ctx); +void nca_process_nca0_romfs_section(nca_section_ctx_t *ctx); void nca_process_bktr_section(nca_section_ctx_t *ctx); void nca_print_pfs0_section(nca_section_ctx_t *ctx); void nca_print_ivfc_section(nca_section_ctx_t *ctx); +void nca_print_nca0_romfs_section(nca_section_ctx_t *ctx); void nca_print_bktr_section(nca_section_ctx_t *ctx); void nca_save_section(nca_section_ctx_t *ctx); void nca_save_pfs0_section(nca_section_ctx_t *ctx); void nca_save_ivfc_section(nca_section_ctx_t *ctx); +void nca_save_nca0_romfs_section(nca_section_ctx_t *ctx); void nca_save_bktr_section(nca_section_ctx_t *ctx); #endif diff --git a/nca0_romfs.c b/nca0_romfs.c new file mode 100644 index 0000000..889069a --- /dev/null +++ b/nca0_romfs.c @@ -0,0 +1,143 @@ +#include +#include "types.h" +#include "utils.h" +#include "nca0_romfs.h" + +/* NCA0 RomFS functions... */ +void nca0_romfs_visit_file(nca0_romfs_ctx_t *ctx, uint32_t file_offset, filepath_t *dir_path) { + romfs_fentry_t *entry = romfs_get_fentry(ctx->files, file_offset); + filepath_t *cur_path = calloc(1, sizeof(filepath_t)); + if (cur_path == NULL) { + fprintf(stderr, "Failed to allocate filepath!\n"); + exit(EXIT_FAILURE); + } + + filepath_copy(cur_path, dir_path); + if (entry->name_size) { + filepath_append_n(cur_path, entry->name_size, "%s", entry->name); + } + + /* If we're extracting... */ + if ((ctx->tool_ctx->action & ACTION_LISTROMFS) == 0) { + printf("Saving %s...\n", cur_path->char_path); + save_file_section(ctx->file, ctx->romfs_offset + ctx->header.data_offset + entry->offset, entry->size, cur_path); + } else { + printf("rom:%s\n", cur_path->char_path); + } + + free(cur_path); + + if (entry->sibling != ROMFS_ENTRY_EMPTY) { + nca0_romfs_visit_file(ctx, entry->sibling, dir_path); + } +} + +void nca0_romfs_visit_dir(nca0_romfs_ctx_t *ctx, uint32_t dir_offset, filepath_t *parent_path) { + romfs_direntry_t *entry = romfs_get_direntry(ctx->directories, dir_offset); + filepath_t *cur_path = calloc(1, sizeof(filepath_t)); + if (cur_path == NULL) { + fprintf(stderr, "Failed to allocate filepath!\n"); + exit(EXIT_FAILURE); + } + + filepath_copy(cur_path, parent_path); + if (entry->name_size) { + filepath_append_n(cur_path, entry->name_size, "%s", entry->name); + } + + /* If we're actually extracting the romfs, make directory. */ + if ((ctx->tool_ctx->action & ACTION_LISTROMFS) == 0) { + os_makedir(cur_path->os_path); + } + + if (entry->file != ROMFS_ENTRY_EMPTY) { + nca0_romfs_visit_file(ctx, entry->file, cur_path); + } + if (entry->child != ROMFS_ENTRY_EMPTY) { + nca0_romfs_visit_dir(ctx, entry->child, cur_path); + } + if (entry->sibling != ROMFS_ENTRY_EMPTY) { + nca0_romfs_visit_dir(ctx, entry->sibling, parent_path); + } + + free(cur_path); +} + +void nca0_romfs_process(nca0_romfs_ctx_t *ctx) { + ctx->romfs_offset = 0; + fseeko64(ctx->file, ctx->romfs_offset, SEEK_SET); + if (fread(&ctx->header, 1, sizeof(nca0_romfs_hdr_t), ctx->file) != sizeof(nca0_romfs_hdr_t)) { + fprintf(stderr, "Failed to read NCA0 RomFS header!\n"); + return; + } + + if ((ctx->tool_ctx->action & (ACTION_EXTRACT | ACTION_LISTROMFS)) && ctx->header.header_size == NCA0_ROMFS_HEADER_SIZE) { + /* Pre-load the file/data entry caches. */ + ctx->directories = calloc(1, ctx->header.dir_meta_table_size); + if (ctx->directories == NULL) { + fprintf(stderr, "Failed to allocate NCA0 RomFS directory cache!\n"); + exit(EXIT_FAILURE); + } + + fseeko64(ctx->file, ctx->romfs_offset + ctx->header.dir_meta_table_offset, SEEK_SET); + if (fread(ctx->directories, 1, ctx->header.dir_meta_table_size, ctx->file) != ctx->header.dir_meta_table_size) { + fprintf(stderr, "Failed to read NCA0 RomFS directory cache!\n"); + exit(EXIT_FAILURE); + } + + ctx->files = calloc(1, ctx->header.file_meta_table_size); + if (ctx->files == NULL) { + fprintf(stderr, "Failed to allocate NCA0 RomFS file cache!\n"); + exit(EXIT_FAILURE); + } + fseeko64(ctx->file, ctx->romfs_offset + ctx->header.file_meta_table_offset, SEEK_SET); + if (fread(ctx->files, 1, ctx->header.file_meta_table_size, ctx->file) != ctx->header.file_meta_table_size) { + fprintf(stderr, "Failed to read NCA0 RomFS file cache!\n"); + exit(EXIT_FAILURE); + } + } else { + fprintf(stderr, "NCA0 RomFS is corrupt?\n"); + return; + } + + /* If there's ever anything meaningful to print about RomFS, uncomment and implement. + * + * if (ctx->tool_ctx->action & ACTION_INFO) { + * nca0_romfs_print(ctx); + * } + */ + + if (ctx->tool_ctx->action & ACTION_EXTRACT) { + nca0_romfs_save(ctx); + } + +} + +void nca0_romfs_save(nca0_romfs_ctx_t *ctx) { + if (ctx->tool_ctx->action & ACTION_LISTROMFS) { + filepath_t fakepath; + filepath_init(&fakepath); + filepath_set(&fakepath, ""); + + nca0_romfs_visit_dir(ctx, 0, &fakepath); + } else { + /* Extract to directory. */ + filepath_t *dirpath = NULL; + if (ctx->tool_ctx->settings.romfs_dir_path.enabled) { + dirpath = &ctx->tool_ctx->settings.romfs_dir_path.path; + } + if ((dirpath == NULL || dirpath->valid != VALIDITY_VALID) && (ctx->tool_ctx->file_type == FILETYPE_NCA0_ROMFS && ctx->tool_ctx->settings.out_dir_path.enabled)) { + dirpath = &ctx->tool_ctx->settings.out_dir_path.path; + } + if (dirpath != NULL && dirpath->valid == VALIDITY_VALID) { + os_makedir(dirpath->os_path); + nca0_romfs_visit_dir(ctx, 0, dirpath); + } + } + +} + +void nca0_romfs_print(nca0_romfs_ctx_t *ctx) { + /* Is there anything meaningful to print here? */ + fprintf(stderr, "Error: NCA0 RomFS printing not implemented.\n"); +} \ No newline at end of file diff --git a/nca0_romfs.h b/nca0_romfs.h new file mode 100644 index 0000000..35b5417 --- /dev/null +++ b/nca0_romfs.h @@ -0,0 +1,53 @@ +#ifndef HACTOOL_NCA0_ROMFS_H +#define HACTOOL_NCA0_ROMFS_H + +#include "types.h" +#include "utils.h" +#include "ivfc.h" +#include "settings.h" + +/* RomFS structs. */ +#define NCA0_ROMFS_HEADER_SIZE 0x00000028 + +typedef struct { + uint32_t header_size; + uint32_t dir_hash_table_offset; + uint32_t dir_hash_table_size; + uint32_t dir_meta_table_offset; + uint32_t dir_meta_table_size; + uint32_t file_hash_table_offset; + uint32_t file_hash_table_size; + uint32_t file_meta_table_offset; + uint32_t file_meta_table_size; + uint32_t data_offset; +} nca0_romfs_hdr_t; + +typedef struct { + uint8_t master_hash[0x20]; /* SHA-256 hash of the hash table. */ + uint32_t block_size; /* In bytes. */ + uint32_t always_2; + uint64_t hash_table_offset; /* Normally zero. */ + uint64_t hash_table_size; + uint64_t romfs_offset; + uint64_t romfs_size; + uint8_t _0x48[0xF0]; +} nca0_romfs_superblock_t; + +typedef struct { + nca0_romfs_superblock_t *superblock; + FILE *file; + hactool_ctx_t *tool_ctx; + validity_t superblock_hash_validity; + validity_t hash_table_validity; + uint64_t romfs_offset; + nca0_romfs_hdr_t header; + romfs_direntry_t *directories; + romfs_fentry_t *files; +} nca0_romfs_ctx_t; + +void nca0_romfs_process(nca0_romfs_ctx_t *ctx); +void nca0_romfs_save(nca0_romfs_ctx_t *ctx); +void nca0_romfs_print(nca0_romfs_ctx_t *ctx); + + +#endif diff --git a/pki.c b/pki.c index 22a5bb0..b9cddeb 100644 --- a/pki.c +++ b/pki.c @@ -3,6 +3,69 @@ #include "aes.h" #include "pki.h" +/* Keydata for very early beta NCA0 archives' RSA-OAEP. */ +const unsigned char beta_nca0_modulus[0x100] = { + 0xAD, 0x58, 0xEE, 0x97, 0xF9, 0x47, 0x90, 0x7D, 0xF9, 0x29, 0x5F, 0x1F, 0x39, 0x68, 0xEE, 0x49, + 0x4C, 0x1E, 0x8D, 0x84, 0x91, 0x31, 0x5D, 0xE5, 0x96, 0x27, 0xB2, 0xB3, 0x59, 0x7B, 0xDE, 0xFD, + 0xB7, 0xEB, 0x40, 0xA1, 0xE7, 0xEB, 0xDC, 0x60, 0xD0, 0x3D, 0xC5, 0x50, 0x92, 0xAD, 0x3D, 0xC4, + 0x8C, 0x17, 0xD2, 0x37, 0x66, 0xE3, 0xF7, 0x14, 0x34, 0x38, 0x6B, 0xA7, 0x2B, 0x21, 0x10, 0x9B, + 0x73, 0x49, 0x15, 0xD9, 0x2A, 0x90, 0x86, 0x76, 0x81, 0x6A, 0x10, 0xBD, 0x74, 0xC4, 0x20, 0x55, + 0x25, 0xA8, 0x02, 0xC5, 0xA0, 0x34, 0x36, 0x7B, 0x66, 0x47, 0x2C, 0x7E, 0x47, 0x82, 0xA5, 0xD4, + 0xA3, 0x42, 0x45, 0xE8, 0xFD, 0x65, 0x72, 0x48, 0xA1, 0xB0, 0x44, 0x10, 0xEF, 0xAC, 0x1D, 0x0F, + 0xB5, 0x12, 0x19, 0xA8, 0x41, 0x0B, 0x76, 0x3B, 0xBC, 0xF1, 0x4A, 0x10, 0x46, 0x22, 0xB8, 0xF1, + 0xBC, 0x21, 0x81, 0x69, 0x9B, 0x63, 0x6F, 0xD7, 0xB9, 0x60, 0x2A, 0x9A, 0xE5, 0x2C, 0x47, 0x72, + 0x59, 0x65, 0xA2, 0x21, 0x60, 0xC4, 0xFC, 0xB0, 0xD7, 0x6F, 0x42, 0xC9, 0x0C, 0xF5, 0x76, 0x7D, + 0xF2, 0x5C, 0xE0, 0x80, 0x0F, 0xEE, 0x45, 0x7E, 0x4E, 0x3A, 0x8D, 0x9C, 0x5B, 0x5B, 0xD9, 0xD1, + 0x43, 0x94, 0x2C, 0xC7, 0x2E, 0xB9, 0x4A, 0xE5, 0x3E, 0x15, 0xDD, 0x43, 0x00, 0xF7, 0x78, 0xE7, + 0x7C, 0x39, 0xB0, 0x4D, 0xC5, 0xD1, 0x1C, 0xF2, 0xB4, 0x7A, 0x2A, 0xEA, 0x0A, 0x8E, 0xB9, 0x13, + 0xB4, 0x4F, 0xD7, 0x5B, 0x4D, 0x7B, 0x43, 0xB0, 0x3A, 0x9A, 0x60, 0x22, 0x47, 0x91, 0x78, 0xC7, + 0x10, 0x64, 0xE0, 0x2C, 0x69, 0xD1, 0x66, 0x3C, 0x42, 0x2E, 0xEF, 0x19, 0x21, 0x89, 0x8E, 0xE1, + 0xB0, 0xB4, 0xD0, 0x17, 0xA1, 0x0F, 0x73, 0x98, 0x5A, 0xF6, 0xEE, 0xC0, 0x2F, 0x9E, 0xCE, 0xC5 +}; + + +unsigned char beta_nca0_exponent[0x100] = { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 +}; + +const unsigned char beta_nca0_label_hash[0x20] = { + 0xE3, 0xB0, 0xC4, 0x42, 0x98, 0xFC, 0x1C, 0x14, 0x9A, 0xFB, 0xF4, 0xC8, 0x99, 0x6F, 0xB9, 0x24, + 0x27, 0xAE, 0x41, 0xE4, 0x64, 0x9B, 0x93, 0x4C, 0xA4, 0x95, 0x99, 0x1B, 0x78, 0x52, 0xB8, 0x55 +}; + + +const unsigned char *pki_get_beta_nca0_modulus(void) { + return beta_nca0_modulus; +} + +void pki_set_beta_nca0_exponent(void *exponent) { + memcpy(beta_nca0_exponent, exponent, sizeof(beta_nca0_exponent)); +} + +const unsigned char *pki_get_beta_nca0_exponent(void) { + return beta_nca0_exponent; +} + +const unsigned char *pki_get_beta_nca0_label_hash(void) { + return beta_nca0_label_hash; +} + + const nca_keyset_t nca_keys_retail = { ZEROES_KEY, /* Secure Boot Key (CONSOLE UNIQUE) */ ZEROES_KEY, /* TSEC Key (CONSOLE UNIQUE) */ diff --git a/pki.h b/pki.h index 6f5451b..1ec4469 100644 --- a/pki.h +++ b/pki.h @@ -14,4 +14,10 @@ void pki_derive_keys(nca_keyset_t *keyset); void pki_print_keys(nca_keyset_t *keyset); void pki_initialize_keyset(nca_keyset_t *keyset, keyset_variant_t variant); +/* Beta NCA0 helpers */ +const unsigned char *pki_get_beta_nca0_modulus(void); +void pki_set_beta_nca0_exponent(void *exponent); +const unsigned char *pki_get_beta_nca0_exponent(void); +const unsigned char *pki_get_beta_nca0_label_hash(void); + #endif \ No newline at end of file diff --git a/romfs.c b/romfs.c index 67a5bc3..35a2fbd 100644 --- a/romfs.c +++ b/romfs.c @@ -79,8 +79,7 @@ void romfs_process(romfs_ctx_t *ctx) { exit(EXIT_FAILURE); } - /* Switch RomFS has actual entries at table offset + 4 for no good reason. */ - fseeko64(ctx->file, ctx->romfs_offset + ctx->header.dir_meta_table_offset + 4, SEEK_SET); + fseeko64(ctx->file, ctx->romfs_offset + ctx->header.dir_meta_table_offset, SEEK_SET); if (fread(ctx->directories, 1, ctx->header.dir_meta_table_size, ctx->file) != ctx->header.dir_meta_table_size) { fprintf(stderr, "Failed to read RomFS directory cache!\n"); exit(EXIT_FAILURE); diff --git a/rsa.c b/rsa.c index d36ee30..da5da38 100644 --- a/rsa.c +++ b/rsa.c @@ -8,6 +8,26 @@ #define RSA_2048_BYTES 0x100 #define RSA_2048_BITS (RSA_2048_BYTES*8) +static void calculate_mgf1_and_xor(unsigned char *data, size_t data_size, const void *h_src, size_t h_src_size) { + unsigned char h_buf[RSA_2048_BYTES] = {0}; + memcpy(h_buf, h_src, h_src_size); + + unsigned char mgf1_buf[0x20]; + size_t ofs = 0; + unsigned int seed = 0; + while (ofs < data_size) { + for (unsigned int i = 0; i < sizeof(seed); i++) { + h_buf[h_src_size + 3 - i] = (seed >> (8 * i)) & 0xFF; + } + sha256_hash_buffer(mgf1_buf, h_buf, h_src_size + 4); + for (unsigned int i = ofs; i < data_size && i < ofs + 0x20; i++) { + data[i] ^= mgf1_buf[i - ofs]; + } + seed++; + ofs += 0x20; + } +} + /* Perform an RSA-PSS verify operation on data, with signature and N. */ int rsa2048_pss_verify(const void *data, size_t len, const unsigned char *signature, const unsigned char *modulus) { mbedtls_mpi signature_mpi; @@ -47,16 +67,8 @@ int rsa2048_pss_verify(const void *data, size_t len, const unsigned char *signat memset(h_buf, 0, 0x24); memcpy(h_buf, m_buf + RSA_2048_BYTES - 0x20 - 0x1, 0x20); - /* Decrypt maskedDB. Should MGF1 be its own function? */ - unsigned char seed = 0; - unsigned char mgf1_buf[0x20]; - for (unsigned int ofs = 0; ofs < RSA_2048_BYTES - 0x20 - 1; ofs += 0x20) { - h_buf[0x23] = seed++; - sha256_hash_buffer(mgf1_buf, h_buf, 0x24); - for (unsigned int i = ofs; i < ofs + 0x20 && i < RSA_2048_BYTES - 0x20 - 1; i++) { - m_buf[i] ^= mgf1_buf[i - ofs]; - } - } + /* Decrypt maskedDB. */ + calculate_mgf1_and_xor(m_buf, RSA_2048_BYTES - 0x20 - 1, h_buf, 0x20); m_buf[0] &= 0x7F; /* Constant lmask for rsa-2048-pss. */ @@ -132,4 +144,68 @@ int rsa2048_pkcs1_verify(const void *data, size_t len, const unsigned char *sign sha256_hash_buffer(h_buf, data, len); return memcmp(pkcs1_hash_prefix, m_buf, 0xE0) == 0 && memcmp(&m_buf[0xE0], h_buf, 0x20) == 0; +} + +/* Perform an RSA-OAEP decryption (and verification) on data, with Signature and N. */ +int rsa2048_oaep_decrypt_verify(void *out, size_t max_out_len, const unsigned char *signature, const unsigned char *modulus, const unsigned char *exponent, size_t exponent_len, const unsigned char *label_hash, size_t *out_len) { + mbedtls_mpi signature_mpi; + mbedtls_mpi modulus_mpi; + mbedtls_mpi exp_mpi; + mbedtls_mpi message_mpi; + + mbedtls_mpi_init(&signature_mpi); + mbedtls_mpi_init(&modulus_mpi); + mbedtls_mpi_init(&exp_mpi); + mbedtls_mpi_init(&message_mpi); + mbedtls_mpi_lset(&message_mpi, RSA_2048_BITS); + + unsigned char m_buf[RSA_2048_BYTES]; + + mbedtls_mpi_read_binary(&exp_mpi, exponent, exponent_len); + mbedtls_mpi_read_binary(&signature_mpi, signature, RSA_2048_BYTES); + mbedtls_mpi_read_binary(&modulus_mpi, modulus, RSA_2048_BYTES); + mbedtls_mpi_exp_mod(&message_mpi, &signature_mpi, &exp_mpi, &modulus_mpi, NULL); + + if (mbedtls_mpi_write_binary(&message_mpi, m_buf, RSA_2048_BYTES) != 0) { + FATAL_ERROR("Failed to export exponentiated RSA message!"); + } + + mbedtls_mpi_free(&signature_mpi); + mbedtls_mpi_free(&modulus_mpi); + mbedtls_mpi_free(&exp_mpi); + mbedtls_mpi_free(&message_mpi); + + /* There's no automated PSS verification as far as I can tell. */ + if (m_buf[0] != 0x00) { + return false; + } + + /* Unmask salt. */ + calculate_mgf1_and_xor(m_buf + 1, 0x20, m_buf + 0x21, RSA_2048_BYTES - 0x20 - 1); + /* Unmask DB. */ + calculate_mgf1_and_xor(m_buf + 0x21, RSA_2048_BYTES - 0x20 - 1, m_buf + 1, 0x20); + + /* Validate label hash */ + const unsigned char *db = m_buf + 0x21; + if (memcmp(db, label_hash, 0x20) != 0) { + return false; + } + + /* Validate message prefix. */ + const unsigned char *data = db + 0x20; + size_t remaining = RSA_2048_BYTES - 0x20 - 1 - 0x20; + while (*data == 0 && remaining) { + data++; + remaining--; + } + if (remaining == 0 || *data++ != 1) { + return false; + } + remaining--; + *out_len = remaining; + if (remaining > max_out_len) { + remaining = max_out_len; + } + memcpy(out, data, remaining); + return true; } \ No newline at end of file diff --git a/rsa.h b/rsa.h index c044c53..c9ef91d 100644 --- a/rsa.h +++ b/rsa.h @@ -5,5 +5,6 @@ int rsa2048_pss_verify(const void *data, size_t len, const unsigned char *signature, const unsigned char *modulus); int rsa2048_pkcs1_verify(const void *data, size_t len, const unsigned char *signature, const unsigned char *modulus); +int rsa2048_oaep_decrypt_verify(void *out, size_t max_out_len, const unsigned char *signature, const unsigned char *modulus, const unsigned char *exponent, size_t exponent_len, const unsigned char *label_hash, size_t *out_len); #endif diff --git a/settings.h b/settings.h index fcc0ed9..be5991b 100644 --- a/settings.h +++ b/settings.h @@ -93,6 +93,7 @@ enum hactool_file_type FILETYPE_NCA, FILETYPE_PFS0, FILETYPE_ROMFS, + FILETYPE_NCA0_ROMFS, FILETYPE_HFS0, FILETYPE_XCI, FILETYPE_NPDM,