diff --git a/Makefile b/Makefile index 3fc1612..e64e24e 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 nca.o xci.o main.o filepath.o ConvertUTF.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 $(CC) -o $@ $^ $(LDFLAGS) -L $(LIBDIR) aes.o: aes.h types.h @@ -36,6 +36,8 @@ pfs0.o: pfs0.h types.h pki.o: pki.h aes.h types.h +nax0.o: nax0.h aes.h sha.h types.h + nca.o: nca.h aes.h sha.h rsa.h bktr.h filepath.h types.h npdm.o: npdm.c types.h diff --git a/extkeys.c b/extkeys.c index 28a98e1..6cbd11a 100644 --- a/extkeys.c +++ b/extkeys.c @@ -212,6 +212,15 @@ void extkeys_initialize_keyset(nca_keyset_t *keyset, FILE *f) { } else if (strcmp(key, "package2_key_source") == 0) { parse_hex_key(keyset->package2_key_source, value, sizeof(keyset->package2_key_source)); matched_key = 1; + } else if (strcmp(key, "sd_card_kek_source") == 0) { + parse_hex_key(keyset->sd_card_kek_source, value, sizeof(keyset->sd_card_kek_source)); + matched_key = 1; + } else if (strcmp(key, "sd_card_nca_key_source") == 0) { + parse_hex_key(keyset->sd_card_key_sources[1], value, sizeof(keyset->sd_card_key_sources[1])); + matched_key = 1; + } else if (strcmp(key, "sd_card_save_key_source") == 0) { + parse_hex_key(keyset->sd_card_key_sources[0], value, sizeof(keyset->sd_card_key_sources[0])); + matched_key = 1; } else { char test_name[0x100]; memset(test_name, 0, sizeof(100)); diff --git a/main.c b/main.c index 948c746..5e2ff51 100644 --- a/main.c +++ b/main.c @@ -9,6 +9,7 @@ #include "pki.h" #include "nca.h" #include "xci.h" +#include "nax0.h" #include "extkeys.h" #include "packages.h" @@ -80,8 +81,11 @@ static void usage(void) { " --extractini1 Enable INI1 extraction to default directory (redundant with --ini1dir set).\n" " --ini1dir=dir Specify INI1 directory path. Overrides default path, if present.\n" "INI1 options:\n" - " --ini1dir=dir Specify Package1 directory path.\n" - " --outdir=dir Specify Package1 directory path. Overrides previous path, if present.\n" + " --ini1dir=dir Specify INI1 directory path.\n" + " --outdir=dir Specify INI1 directory path. Overrides previous path, if present.\n" + "NAX0 options:\n" + " --sdseed=seed Set console unique seed for SD card NAX0 encryption.\n" + " --sdpath=path Set relative path for NAX0 key derivation (ex: /registered/000000FF/cafebabecafebabecafebabecafebabe.nca).\n" "\n", __TIME__, __DATE__, prog_name); exit(EXIT_FAILURE); } @@ -152,6 +156,8 @@ int main(int argc, char **argv) { {"extractini1", 0, NULL, 29}, {"basefake", 0, NULL, 30}, {"onlyupdated", 0, NULL, 31}, + {"sdseed", 1, NULL, 32}, + {"sdpath", 1, NULL, 33}, {NULL, 0, NULL, 0}, }; @@ -201,6 +207,8 @@ int main(int argc, char **argv) { nca_ctx.tool_ctx->file_type = FILETYPE_INI1; } else if (!strcmp(optarg, "kip1") || !strcmp(optarg, "kip")) { nca_ctx.tool_ctx->file_type = FILETYPE_KIP1; + } else if (!strcmp(optarg, "nax0") || !strcmp(optarg, "nax")) { + nca_ctx.tool_ctx->file_type = FILETYPE_NAX0; } break; case 0: filepath_set(&nca_ctx.tool_ctx->settings.section_paths[0], optarg); break; @@ -273,7 +281,7 @@ int main(int argc, char **argv) { filepath_set(&tool_ctx.settings.out_dir_path.path, optarg); break; case 18: - filepath_set(&nca_ctx.tool_ctx->settings.dec_nca_path, optarg); + filepath_set(&nca_ctx.tool_ctx->settings.plaintext_path, optarg); break; case 19: filepath_set(&nca_ctx.tool_ctx->settings.header_path, optarg); @@ -319,6 +327,19 @@ int main(int argc, char **argv) { case 31: tool_ctx.action |= ACTION_ONLYUPDATEDROMFS; break; + case 32: + parse_hex_key(nca_ctx.tool_ctx->settings.sdseed, optarg, 16); + nca_ctx.tool_ctx->settings.has_sdseed = 1; + for (unsigned int key = 0; key < 2; key++) { + for (unsigned int i = 0; i < 0x20; i++) { + tool_ctx.settings.keyset.sd_card_key_sources[key][i] ^= tool_ctx.settings.sdseed[i & 0xF]; + } + } + pki_derive_keys(&tool_ctx.settings.keyset); + break; + case 33: + filepath_set(&tool_ctx.settings.nax0_sd_path, optarg); + break; default: usage(); return EXIT_FAILURE; @@ -364,6 +385,13 @@ int main(int argc, char **argv) { if (keyfile != NULL) { extkeys_initialize_keyset(&tool_ctx.settings.keyset, keyfile); + if (tool_ctx.settings.has_sdseed) { + for (unsigned int key = 0; key < 2; key++) { + for (unsigned int i = 0; i < 0x20; i++) { + tool_ctx.settings.keyset.sd_card_key_sources[key][i] ^= tool_ctx.settings.sdseed[i & 0xF]; + } + } + } pki_derive_keys(&tool_ctx.settings.keyset); fclose(keyfile); } @@ -374,11 +402,37 @@ int main(int argc, char **argv) { } else if ((optind < argc) || (argc == 1)) { usage(); } - + + /* Special case NAX0. */ + if (tool_ctx.file_type == FILETYPE_NAX0) { + nax0_ctx_t nax_ctx; + memset(&nax_ctx, 0, sizeof(nax_ctx)); + filepath_set(&nax_ctx.base_path, input_name); + nax_ctx.tool_ctx = &tool_ctx; + nax0_process(&nax_ctx); + + if (nax_ctx.aes_ctx) { + free_aes_ctx(nax_ctx.aes_ctx); + } + if (nax_ctx.num_files) { + for (unsigned int i = 0; i < nax_ctx.num_files; i++) { + fclose(nax_ctx.files[i]); + } + } + if (nax_ctx.files) { + free(nax_ctx.files); + } + printf("Done!\n"); + return EXIT_SUCCESS; + } + + if ((tool_ctx.file = fopen(input_name, "rb")) == NULL) { fprintf(stderr, "unable to open %s: %s\n", input_name, strerror(errno)); return EXIT_FAILURE; } + + switch (tool_ctx.file_type) { case FILETYPE_NCA: { diff --git a/nax0.c b/nax0.c new file mode 100644 index 0000000..88e59eb --- /dev/null +++ b/nax0.c @@ -0,0 +1,168 @@ +#include +#include "aes.h" +#include "sha.h" +#include "nax0.h" + +size_t nax0_read(nax0_ctx_t *ctx, uint64_t offset, void *dst, size_t size) { + if (ctx->num_files == 1) { + fseeko64(ctx->files[0], offset, SEEK_SET); + return fread(dst, 1, size, ctx->files[0]); + } + + FILE *which = ctx->files[offset / 0xFFFF0000ULL]; + uint64_t offset_in_file = offset % 0xFFFF0000ULL; + fseeko64(which, offset_in_file, SEEK_SET); + uint64_t left_in_file = 0xFFFF0000ULL - offset_in_file; + if (size > left_in_file) { + return fread(dst, 1, left_in_file, which) + nax0_read(ctx, offset + left_in_file, (unsigned char *)dst + left_in_file, size - left_in_file); + } else { + return fread(dst, 1, size, which); + } +} + +void nax0_process(nax0_ctx_t *ctx) { + /* First things first... */ + FILE *f_temp; + if ((f_temp = os_fopen(ctx->base_path.os_path, OS_MODE_READ)) != NULL) { + ctx->num_files = 1; + ctx->files = calloc(1, sizeof(FILE *)); + if (ctx->files == NULL) { + fprintf(stderr, "Failed to allocate NAX0 file holder!\n"); + exit(EXIT_FAILURE); + } + ctx->files[0] = f_temp; + } else { + ctx->num_files = 0; + filepath_t temp_path; + while (1) { + filepath_copy(&temp_path, &ctx->base_path); + filepath_append(&temp_path, "%02"PRIu32, ctx->num_files); + if ((f_temp = os_fopen(temp_path.os_path, OS_MODE_READ)) == NULL) { + break; + } + ctx->num_files++; + fclose(f_temp); + } + if (ctx->num_files == 0) { + fprintf(stderr, "Input path appears to neither be a NAX0, nor a NAX0 directory!\n"); + exit(EXIT_FAILURE); + } + ctx->files = calloc(ctx->num_files, sizeof(FILE *)); + if (ctx->files == NULL) { + fprintf(stderr, "Failed to allocate NAX0 file holder!\n"); + exit(EXIT_FAILURE); + } + for (unsigned int i = 0; i < ctx->num_files; i++) { + filepath_copy(&temp_path, &ctx->base_path); + filepath_append(&temp_path, "%02"PRIu32, i); + if ((ctx->files[i] = os_fopen(temp_path.os_path, OS_MODE_READ)) == NULL) { + fprintf(stderr, "Failed to open %s!\n", temp_path.char_path); + exit(EXIT_FAILURE); + } + } + } + + nax0_read(ctx, 0, &ctx->header, sizeof(ctx->header)); + if (ctx->header.magic != MAGIC_NAX0) { + printf("Error: File has invalid NAX0 magic!\n"); + return; + } + + memcpy(ctx->encrypted_keys, ctx->header.keys, sizeof(ctx->header.keys)); + + int found = 0; + for (ctx->k = 0; ctx->k < 2; ctx->k++) { + unsigned char nax_specific_keys[2][0x10]; + sha256_get_buffer_hmac(nax_specific_keys, ctx->tool_ctx->settings.keyset.sd_card_keys[ctx->k], 0x10, ctx->tool_ctx->settings.nax0_sd_path.char_path, strlen(ctx->tool_ctx->settings.nax0_sd_path.char_path)); + for (unsigned int i = 0; i < 2; i++) { + aes_ctx_t *nax_k_ctx = new_aes_ctx(nax_specific_keys[i], 0x10, AES_MODE_ECB); + aes_decrypt(nax_k_ctx, ctx->header.keys[i], ctx->encrypted_keys[i], 0x10); + free_aes_ctx(nax_k_ctx); + } + + unsigned char validation_mac[0x20]; + sha256_get_buffer_hmac(validation_mac, &ctx->header.magic, 0x60, ctx->tool_ctx->settings.keyset.sd_card_keys[ctx->k] + 0x10, 0x10); + if (memcmp(ctx->header.hmac_header, validation_mac, 0x20) == 0) { + found = 1; + break; + } + } + + if (!found) { + printf("Error: NAX0 key derivation failed. Check SD card seed and relative path?\n"); + return; + } + + ctx->aes_ctx = new_aes_ctx(ctx->header.keys, 0x20, AES_MODE_XTS); + + if (ctx->tool_ctx->action & ACTION_INFO) { + nax0_print(ctx); + } + + if (ctx->tool_ctx->action & ACTION_EXTRACT) { + nax0_save(ctx); + } +} + +void nax0_save(nax0_ctx_t *ctx) { + /* Save Decrypted Contents. */ + filepath_t *dec_path = &ctx->tool_ctx->settings.plaintext_path; + + if (dec_path->valid == VALIDITY_VALID) { + printf("Saving Decrypted NAX0 Content to %s...\n", dec_path->char_path); + FILE *f_dec = os_fopen(dec_path->os_path, OS_MODE_WRITE); + + if (f_dec != NULL) { + uint64_t ofs = 0x4000; + uint64_t end_ofs = ofs + ctx->header.size; + unsigned char *buf = malloc(0x400000); + if (buf == NULL) { + fprintf(stderr, "Failed to allocate file-save buffer!\n"); + exit(EXIT_FAILURE); + } + + uint64_t read_size = 0x400000; /* 4 MB buffer. */ + memset(buf, 0xCC, read_size); /* Debug in case I fuck this up somehow... */ + while (ofs < end_ofs) { + if (ofs + read_size >= end_ofs) read_size = end_ofs - ofs; + if (nax0_read(ctx, ofs, buf, read_size) != read_size) { + fprintf(stderr, "Failed to read file!\n"); + exit(EXIT_FAILURE); + } + + aes_xts_decrypt(ctx->aes_ctx, buf, buf, read_size, (ofs - 0x4000) >> 14, 0x4000); + + if (fwrite(buf, 1, read_size, f_dec) != read_size) { + fprintf(stderr, "Failed to write file!\n"); + exit(EXIT_FAILURE); + } + ofs += read_size; + } + + free(buf); + } else { + fprintf(stderr, "Failed to open %s!\n", dec_path->char_path); + } + } +} + +const char *nax0_get_key_summary(unsigned int k) { + switch (k) { + case 0: + return "Save"; + case 1: + return "NCA"; + default: + return "Unknown"; + } +} + +void nax0_print(nax0_ctx_t *ctx) { + printf("\nNAX0:\n"); + print_magic(" Magic: ", ctx->header.magic); + printf(" Content Type: %s\n", nax0_get_key_summary(ctx->k)); + printf(" Content Size: %012"PRIx64"\n", ctx->header.size); + memdump(stdout, " Header HMAC: ", ctx->header.hmac_header, 0x20); + memdump(stdout, " Encrypted Keys: ", ctx->encrypted_keys, 0x20); + memdump(stdout, " Decrypted Keys: ", ctx->header.keys, 0x20); +} \ No newline at end of file diff --git a/nax0.h b/nax0.h new file mode 100644 index 0000000..433b1d1 --- /dev/null +++ b/nax0.h @@ -0,0 +1,36 @@ +#ifndef HACTOOL_NAX0_H +#define HACTOOL_NAX0_H + +#include +#include "types.h" +#include "utils.h" +#include "settings.h" +#include "aes.h" + +#define MAGIC_NAX0 0x3058414E + +typedef struct { + uint8_t hmac_header[0x20]; + uint32_t magic; + uint32_t _0x24; + uint8_t keys[2][0x10]; + uint64_t size; + uint8_t _0x50[0x30]; +} nax0_header_t; + +typedef struct { + filepath_t base_path; + hactool_ctx_t *tool_ctx; + aes_ctx_t *aes_ctx; + FILE **files; + unsigned int num_files; + unsigned int k; + unsigned char encrypted_keys[2][0x10]; + nax0_header_t header; +} nax0_ctx_t; + +void nax0_process(nax0_ctx_t *ctx); +void nax0_save(nax0_ctx_t *ctx); +void nax0_print(nax0_ctx_t *ctx); + +#endif \ No newline at end of file diff --git a/nca.c b/nca.c index ebb4371..f11810a 100644 --- a/nca.c +++ b/nca.c @@ -316,7 +316,7 @@ void nca_save(nca_ctx_t *ctx) { } /* Save Decrypted NCA. */ - filepath_t *dec_path = &ctx->tool_ctx->settings.dec_nca_path; + filepath_t *dec_path = &ctx->tool_ctx->settings.plaintext_path; if (dec_path->valid == VALIDITY_VALID) { printf("Saving Decrypted NCA to %s...\n", dec_path->char_path); diff --git a/pki.c b/pki.c index fcefae0..1185d24 100644 --- a/pki.c +++ b/pki.c @@ -114,6 +114,11 @@ const nca_keyset_t nca_keys_retail = { ZEROES_KEY, /* Key Area Encryption Key Source System */ ZEROES_KEY, /* Titlekek Source */ ZEROES_KEY, /* Headerkek Source */ + ZEROES_KEY, /* SD Card kek Source. */ + { + ZEROES_XTS_KEY, /* SD Card Key Source, for NCAs. */ + ZEROES_XTS_KEY, /* SD Card Key Source, for saves. */ + }, ZEROES_XTS_KEY, /* Encrypted Header Key */ ZEROES_XTS_KEY, /* Header key */ { @@ -184,6 +189,10 @@ const nca_keyset_t nca_keys_retail = { ZEROES_KAEKS, /* Key Area Encryption Keyset 30 */ ZEROES_KAEKS /* Key Area Encryption Keyset 31 */ }, + { + ZEROES_XTS_KEY, /* SD Card Key, for NCAs. */ + ZEROES_XTS_KEY, /* SD Card Key, for saves. */ + }, { /* Fixed RSA key used to validate NCA signature 0. */ 0xBF, 0xBE, 0x40, 0x6C, 0xF4, 0xA7, 0x80, 0xE9, 0xF0, 0x7D, 0x0C, 0x99, 0x61, 0x1D, 0x77, 0x2F, 0x96, 0xBC, 0x4B, 0x9E, 0x58, 0x38, 0x1B, 0x03, 0xAB, 0xB1, 0x75, 0x49, 0x9F, 0x2B, 0x4D, 0x58, @@ -351,6 +360,11 @@ const nca_keyset_t nca_keys_dev = { ZEROES_KEY, /* Key Area Encryption Key Source System */ ZEROES_KEY, /* Titlekek Source */ ZEROES_KEY, /* Headerkek Source */ + ZEROES_KEY, /* SD Card kek Source. */ + { + ZEROES_XTS_KEY, /* SD Card Key Source, for NCAs. */ + ZEROES_XTS_KEY, /* SD Card Key Source, for saves. */ + }, ZEROES_XTS_KEY, /* Encrypted Header Key */ ZEROES_XTS_KEY, /* Header key */ { @@ -421,6 +435,10 @@ const nca_keyset_t nca_keys_dev = { ZEROES_KAEKS, /* Key Area Encryption Keyset 30 */ ZEROES_KAEKS /* Key Area Encryption Keyset 31 */ }, + { + ZEROES_XTS_KEY, /* SD Card Key, for NCAs. */ + ZEROES_XTS_KEY, /* SD Card Key, for saves. */ + }, { 0xD8, 0xF1, 0x18, 0xEF, 0x32, 0x72, 0x4C, 0xA7, 0x47, 0x4C, 0xB9, 0xEA, 0xB3, 0x04, 0xA8, 0xA4, 0xAC, 0x99, 0x08, 0x08, 0x04, 0xBF, 0x68, 0x57, 0xB8, 0x43, 0x94, 0x2B, 0xC7, 0xB9, 0x66, 0x49, @@ -539,6 +557,21 @@ void pki_derive_keys(nca_keyset_t *keyset) { free_aes_ctx(header_ctx); } + /* Derive SD Card Key */ + if (i == 0 && memcmp(keyset->sd_card_kek_source, zeroes, 0x10) != 0) { + unsigned char sd_kek[0x10]; + generate_kek(sd_kek, keyset->sd_card_kek_source, keyset->master_keys[i], keyset->aes_kek_generation_source, keyset->aes_key_generation_source); + aes_ctx_t *sd_ctx = new_aes_ctx(sd_kek, 0x10, AES_MODE_ECB); + + for (unsigned int k = 0; k < 2; k++) { + if (memcmp(keyset->sd_card_key_sources[k], zeroes, 0x20) != 0) { + aes_decrypt(sd_ctx, keyset->sd_card_keys[k], keyset->sd_card_key_sources[k], 0x20); + } + } + + free_aes_ctx(sd_ctx); + } + free_aes_ctx(master_ctx); } @@ -558,5 +591,4 @@ void pki_initialize_keyset(nca_keyset_t *keyset, keyset_variant_t variant) { } pki_derive_keys(keyset); - } \ No newline at end of file diff --git a/settings.h b/settings.h index 40e884e..541dfb6 100644 --- a/settings.h +++ b/settings.h @@ -27,10 +27,13 @@ typedef struct { unsigned char key_area_key_system_source[0x10]; /* Seed for kaek 2. */ unsigned char titlekek_source[0x10]; /* Seed for titlekeks. */ unsigned char header_kek_source[0x10]; /* Seed for header kek. */ + unsigned char sd_card_kek_source[0x10]; /* Seed for SD card kek. */ + unsigned char sd_card_key_sources[2][0x20]; /* Seed for SD card encryption keys. */ unsigned char encrypted_header_key[0x20]; /* Actual encrypted header key. */ unsigned char header_key[0x20]; /* NCA header key. */ unsigned char titlekeks[0x20][0x10]; /* Title key encryption keys. */ unsigned char key_area_keys[0x20][3][0x10]; /* Key area encryption keys. */ + unsigned char sd_card_keys[2][0x20]; unsigned char nca_hdr_fixed_key_modulus[0x100]; /* NCA header fixed key RSA pubk. */ unsigned char acid_fixed_key_modulus[0x100]; /* ACID fixed key RSA pubk. */ unsigned char package2_fixed_key_modulus[0x100]; /* Package2 Header RSA pubk. */ @@ -48,6 +51,8 @@ typedef struct { unsigned char dec_titlekey[0x10]; int has_contentkey; unsigned char contentkey[0x10]; + int has_sdseed; + unsigned char sdseed[0x10]; filepath_t section_paths[4]; filepath_t section_dir_paths[4]; override_filepath_t exefs_path; @@ -60,12 +65,14 @@ typedef struct { filepath_t pk11_dir_path; filepath_t pk21_dir_path; filepath_t ini1_dir_path; - filepath_t dec_nca_path; + filepath_t plaintext_path; filepath_t rootpt_dir_path; filepath_t update_dir_path; filepath_t normal_dir_path; filepath_t secure_dir_path; filepath_t header_path; + filepath_t nax0_path; + filepath_t nax0_sd_path; } hactool_settings_t; enum hactool_file_type @@ -79,7 +86,8 @@ enum hactool_file_type FILETYPE_PACKAGE1, FILETYPE_PACKAGE2, FILETYPE_INI1, - FILETYPE_KIP1 + FILETYPE_KIP1, + FILETYPE_NAX0 }; #define ACTION_INFO (1<<0) diff --git a/sha.c b/sha.c index 08359a7..4a0a768 100644 --- a/sha.c +++ b/sha.c @@ -52,4 +52,34 @@ void sha256_hash_buffer(unsigned char *digest, const void *data, size_t l) { sha_update(sha_ctx, data, l); sha_get_hash(sha_ctx, digest); free_sha_ctx(sha_ctx); +} + +/* SHA256-HMAC digest. */ +void sha256_get_buffer_hmac(void *digest, const void *secret, size_t s_l, const void *data, size_t d_l) { + sha_ctx_t *ctx; + + if ((ctx = malloc(sizeof(*ctx))) == NULL) { + FATAL_ERROR("Failed to allocate sha_ctx_t!"); + } + + mbedtls_md_init(&ctx->digest); + + if (mbedtls_md_setup(&ctx->digest, mbedtls_md_info_from_type(HASH_TYPE_SHA256), 1)) { + FATAL_ERROR("Failed to set up hash context!"); + } + + if (mbedtls_md_hmac_starts(&ctx->digest, secret, s_l)) { + FATAL_ERROR("Failed to set up HMAC secret context!"); + } + + if (mbedtls_md_hmac_update(&ctx->digest, data, d_l)) { + FATAL_ERROR("Failed processing HMAC input!"); + } + + if (mbedtls_md_hmac_finish(&ctx->digest, digest)) { + FATAL_ERROR("Failed getting HMAC output!"); + } + + mbedtls_md_free(&ctx->digest); + free(ctx); } \ No newline at end of file diff --git a/sha.h b/sha.h index 3624cbc..c95dd8d 100644 --- a/sha.h +++ b/sha.h @@ -22,4 +22,6 @@ void free_sha_ctx(sha_ctx_t *ctx); void sha256_hash_buffer(unsigned char *digest, const void *data, size_t l); +void sha256_get_buffer_hmac(void *digest, const void *secret, size_t s_l, const void *data, size_t d_l); + #endif