diff --git a/fusee/fusee-secondary/src/key_derivation.c b/fusee/fusee-secondary/src/key_derivation.c index 1ff61678c..05ca2bc51 100644 --- a/fusee/fusee-secondary/src/key_derivation.c +++ b/fusee/fusee-secondary/src/key_derivation.c @@ -1,4 +1,5 @@ #include "key_derivation.h" +#include "masterkey.h" #include "se.h" #include "exocfg.h" #include "fuse.h" @@ -125,6 +126,9 @@ void derive_nx_keydata(u32 target_firmware) { default: generic_panic(); } + + /* Setup master key revision, derive older master keys for use. */ + mkey_detect_revision(); } /* Sets final keyslot flags, for handover to TZ/Exosphere. Setting these will prevent the BPMP from using the device key or master key. */ diff --git a/fusee/fusee-secondary/src/kip.h b/fusee/fusee-secondary/src/kip.h new file mode 100644 index 000000000..f1bd8b7fa --- /dev/null +++ b/fusee/fusee-secondary/src/kip.h @@ -0,0 +1,47 @@ +#ifndef FUSEE_KIP_H +#define FUSEE_KIP_H +#include "utils.h" +#include + +/* Fusee definitions for INI1/KIP types. This is mostly taken from hactool. */ + +#define MAGIC_INI1 0x31494E49 +#define MAGIC_KIP1 0x3150494B +#define INI1_MAX_KIPS 0x50 + +typedef struct { + uint32_t magic; + uint32_t size; + uint32_t num_processes; + uint32_t _0xC; + char kip_data[]; +} ini1_header_t; + +typedef struct { + uint32_t out_offset; + uint32_t out_size; + uint32_t compressed_size; + uint32_t attribute; +} kip_section_header_t; + +typedef struct { + uint32_t magic; + char name[0xC]; + uint64_t title_id; + uint32_t process_category; + uint8_t main_thread_priority; + uint8_t default_core; + uint8_t _0x1E; + uint8_t flags; + kip_section_header_t section_headers[6]; + uint32_t capabilities[0x20]; + unsigned char data[]; +} kip1_header_t; + +static inline uint64_t kip1_get_size_from_header(kip1_header_t *header) { + /* Header + .text + .rodata + .rwdata */ + return 0x100 + header->section_headers[0].compressed_size + header->section_headers[1].compressed_size + header->section_headers[2].compressed_size; +} + + +#endif \ No newline at end of file diff --git a/fusee/fusee-secondary/src/masterkey.c b/fusee/fusee-secondary/src/masterkey.c new file mode 100644 index 000000000..031f39dc8 --- /dev/null +++ b/fusee/fusee-secondary/src/masterkey.c @@ -0,0 +1,90 @@ +#include +#include +#include + +#include "utils.h" +#include "masterkey.h" +#include "se.h" + +static unsigned int g_mkey_revision = 0; +static bool g_determined_mkey_revision = false; + +static uint8_t g_old_masterkeys[MASTERKEY_REVISION_MAX][0x10]; + +/* TODO: Extend with new vectors, as needed. */ +static const uint8_t mkey_vectors[MASTERKEY_REVISION_MAX][0x10] = +{ + {0x0C, 0xF0, 0x59, 0xAC, 0x85, 0xF6, 0x26, 0x65, 0xE1, 0xE9, 0x19, 0x55, 0xE6, 0xF2, 0x67, 0x3D}, /* Zeroes encrypted with Master Key 00. */ + {0x29, 0x4C, 0x04, 0xC8, 0xEB, 0x10, 0xED, 0x9D, 0x51, 0x64, 0x97, 0xFB, 0xF3, 0x4D, 0x50, 0xDD}, /* Master key 00 encrypted with Master key 01. */ + {0xDE, 0xCF, 0xEB, 0xEB, 0x10, 0xAE, 0x74, 0xD8, 0xAD, 0x7C, 0xF4, 0x9E, 0x62, 0xE0, 0xE8, 0x72}, /* Master key 01 encrypted with Master key 02. */ + {0x0A, 0x0D, 0xDF, 0x34, 0x22, 0x06, 0x6C, 0xA4, 0xE6, 0xB1, 0xEC, 0x71, 0x85, 0xCA, 0x4E, 0x07}, /* Master key 02 encrypted with Master key 03. */ + {0x6E, 0x7D, 0x2D, 0xC3, 0x0F, 0x59, 0xC8, 0xFA, 0x87, 0xA8, 0x2E, 0xD5, 0x89, 0x5E, 0xF3, 0xE9}, /* Master key 03 encrypted with Master key 04. */ +}; + +bool check_mkey_revision(unsigned int revision) { + uint8_t final_vector[0x10]; + + unsigned int check_keyslot = KEYSLOT_SWITCH_MASTERKEY; + if (revision > 0) { + /* Generate old master key array. */ + for (unsigned int i = revision; i > 0; i--) { + se_aes_ecb_decrypt_block(check_keyslot, g_old_masterkeys[i-1], 0x10, mkey_vectors[i], 0x10); + set_aes_keyslot(KEYSLOT_SWITCH_TEMPKEY, g_old_masterkeys[i-1], 0x10); + check_keyslot = KEYSLOT_SWITCH_TEMPKEY; + } + } + + se_aes_ecb_decrypt_block(check_keyslot, final_vector, 0x10, mkey_vectors[0], 0x10); + for (unsigned int i = 0; i < 0x10; i++) { + if (final_vector[i] != 0) { + return false; + } + } + return true; +} + +void mkey_detect_revision(void) { + if (g_determined_mkey_revision) { + generic_panic(); + } + + for (unsigned int rev = 0; rev < MASTERKEY_REVISION_MAX; rev++) { + if (check_mkey_revision(rev)) { + g_determined_mkey_revision = true; + g_mkey_revision = rev; + break; + } + } + + /* We must have determined the master key, or we're not running on a Switch. */ + if (!g_determined_mkey_revision) { + /* Panic in bright red. */ + panic(0x00F00060); + } +} + +unsigned int mkey_get_revision(void) { + if (!g_determined_mkey_revision) { + generic_panic(); + } + + return g_mkey_revision; +} + +unsigned int mkey_get_keyslot(unsigned int revision) { + if (!g_determined_mkey_revision || revision >= MASTERKEY_REVISION_MAX) { + generic_panic(); + } + + if (revision > g_mkey_revision) { + generic_panic(); + } + + if (revision == g_mkey_revision) { + return KEYSLOT_SWITCH_MASTERKEY; + } else { + /* Load into a temp keyslot. */ + set_aes_keyslot(KEYSLOT_SWITCH_TEMPKEY, g_old_masterkeys[revision], 0x10); + return KEYSLOT_SWITCH_TEMPKEY; + } +} \ No newline at end of file diff --git a/fusee/fusee-secondary/src/masterkey.h b/fusee/fusee-secondary/src/masterkey.h new file mode 100644 index 000000000..f6b7d61c4 --- /dev/null +++ b/fusee/fusee-secondary/src/masterkey.h @@ -0,0 +1,22 @@ +#ifndef FUSEE_MASTERKEY_H +#define FUSEE_MASTERKEY_H + +/* This is glue code to enable master key support across versions. */ + +/* TODO: Update to 0x6 on release of new master key. */ +#define MASTERKEY_REVISION_MAX 0x5 + +#define MASTERKEY_REVISION_100_230 0x00 +#define MASTERKEY_REVISION_300 0x01 +#define MASTERKEY_REVISION_301_302 0x02 +#define MASTERKEY_REVISION_400_410 0x03 +#define MASTERKEY_REVISION_500_CURRENT 0x04 + +/* This should be called during initialization. */ +void mkey_detect_revision(void); + +unsigned int mkey_get_revision(void); + +unsigned int mkey_get_keyslot(unsigned int revision); + +#endif \ No newline at end of file diff --git a/fusee/fusee-secondary/src/package2.c b/fusee/fusee-secondary/src/package2.c index 15a84fa65..831a3833d 100644 --- a/fusee/fusee-secondary/src/package2.c +++ b/fusee/fusee-secondary/src/package2.c @@ -1,47 +1,251 @@ #include "utils.h" +#include "masterkey.h" #include "package2.h" +#include "kip.h" #include "se.h" +#include "lib/printk.h" + +/* Stage 2 executes from DRAM, so we have tons of space. */ +/* This *greatly* simplifies logic. */ +unsigned char g_patched_package2[PACKAGE2_SIZE_MAX]; +unsigned char g_package2_sections[PACKAGE2_SECTION_MAX][PACKAGE2_SIZE_MAX]; +unsigned char g_package2_work_buffer[PACKAGE2_SIZE_MAX]; + +package2_header_t *g_patched_package2_header = (package2_header_t *)g_patched_package2; void package2_decrypt(void *package2_address); -void package2_add_thermosphere_section(void *package2_address); -void package2_patch_kernel(void *package2_address); -void package2_patch_ini1(void *package2_address); -void package2_fixup_header_and_section_hashes(void *package2_address); +void package2_add_thermosphere_section(void); +void package2_patch_kernel(void); +void package2_patch_ini1(void); +void package2_fixup_header_and_section_hashes(void); void package2_patch(void *package2_address) { - /* First things first: Decrypt (TODO: Relocate?) Package2. */ + /* First things first: Decrypt Package2. */ package2_decrypt(package2_address); /* Modify Package2 to add an additional thermosphere section. */ - package2_add_thermosphere_section(package2_address); + package2_add_thermosphere_section(); /* Perform any patches we want to the NX kernel. */ - package2_patch_kernel(package2_address); + package2_patch_kernel(); /* Perform any patches we want to the INI1 (This is where our built-in sysmodules will be added.) */ - package2_patch_ini1(package2_address); + package2_patch_ini1(); /* Fix all necessary data in the header to accomodate for the new patches. */ - package2_fixup_header_and_section_hashes(package2_address); + package2_fixup_header_and_section_hashes(); + + /* Relocate Package2. */ + memcpy(NX_BOOTLOADER_PACKAGE2_LOAD_ADDRESS, g_patched_package2, PACKAGE2_SIZE_MAX); } +static void package2_crypt_ctr(unsigned int master_key_rev, void *dst, size_t dst_size, const void *src, size_t src_size, const void *ctr, size_t ctr_size) { + /* Derive package2 key. */ + const uint8_t package2_key_source[0x10] = {0xFB, 0x8B, 0x6A, 0x9C, 0x79, 0x00, 0xC8, 0x49, 0xEF, 0xD2, 0x4D, 0x85, 0x4D, 0x30, 0xA0, 0xC7}; + unsigned int keyslot = mkey_get_keyslot(master_key_rev); + decrypt_data_into_keyslot(KEYSLOT_SWITCH_PACKAGE2KEY, keyslot, package2_key_source, 0x10); + + /* Perform Encryption. */ + se_aes_ctr_crypt(KEYSLOT_SWITCH_PACKAGE2KEY, dst, dst_size, src, src_size, ctr, ctr_size); +} + +bool validate_package2_metadata(package2_meta_t *metadata) { + if (metadata->magic != MAGIC_PK21) { + return false; + } + + /* Package2 size, version number is stored XORed in header CTR. */ + /* Nintendo, what the fuck? */ + uint32_t package_size = metadata->ctr_dwords[0] ^ metadata->ctr_dwords[2] ^ metadata->ctr_dwords[3]; + uint8_t header_version = (uint8_t)((metadata->ctr_dwords[1] ^ (metadata->ctr_dwords[1] >> 16) ^ (metadata->ctr_dwords[1] >> 24)) & 0xFF); + + /* Ensure package isn't too big or too small. */ + if (package_size <= sizeof(package2_header_t) || package_size > PACKAGE2_SIZE_MAX - sizeof(package2_header_t)) { + return false; + } + + /* Validate that we're working with a header we know how to handle. */ + if (header_version > MASTERKEY_REVISION_MAX) { + return false; + } + + /* Require aligned entrypoint. */ + if (metadata->entrypoint & 3) { + return false; + } + + /* Validate section size sanity. */ + if (metadata->section_sizes[0] + metadata->section_sizes[1] + metadata->section_sizes[2] + sizeof(package2_header_t) != package_size) { + return false; + } + + bool entrypoint_found = false; + + /* Header has space for 4 sections, but only 3 are validated/potentially loaded on hardware. */ + size_t cur_section_offset = 0; + for (unsigned int section = 0; section < PACKAGE2_SECTION_MAX; section++) { + /* Validate section size alignment. */ + if (metadata->section_sizes[section] & 3) { + return false; + } + + /* Validate section does not overflow. */ + if (check_32bit_additive_overflow(metadata->section_offsets[section], metadata->section_sizes[section])) { + return false; + } + + /* Check for entrypoint presence. */ + uint32_t section_end = metadata->section_offsets[section] + metadata->section_sizes[section]; + if (metadata->section_offsets[section] <= metadata->entrypoint && metadata->entrypoint < section_end) { + entrypoint_found = true; + } + + /* Ensure no overlap with later sections. */ + for (unsigned int later_section = section + 1; later_section < PACKAGE2_SECTION_MAX; later_section++) { + uint32_t later_section_end = metadata->section_offsets[later_section] + metadata->section_sizes[later_section]; + if (overlaps(metadata->section_offsets[section], section_end, metadata->section_offsets[later_section], later_section_end)) { + return false; + } + } + + /* Validate section hashes. */ + if (metadata->section_sizes[section]) { + void *section_data = (void *)((uint8_t *)NX_BOOTLOADER_PACKAGE2_LOAD_ADDRESS + sizeof(package2_header_t) + cur_section_offset); + uint8_t calculated_hash[0x20]; + se_calculate_sha256(calculated_hash, section_data, metadata->section_sizes[section]); + if (memcmp(calculated_hash, metadata->section_hashes[section], sizeof(metadata->section_hashes[section])) != 0) { + return false; + } + cur_section_offset += metadata->section_sizes[section]; + } + + } + + /* Ensure that entrypoint is present in one of our sections. */ + if (!entrypoint_found) { + return false; + } + + /* Perform version checks. */ + /* We will be compatible with all package2s released before current, but not newer ones. */ + if (metadata->version_max >= PACKAGE2_MINVER_THEORETICAL && metadata->version_min < PACKAGE2_MAXVER_500_CURRENT) { + return true; + } + + return false; +} + +uint32_t decrypt_and_validate_header(package2_header_t *header, bool is_plaintext) { + package2_meta_t metadata; + + /* TODO: Also accept plaintext package2 based on bootconfig. */ + + if (!is_plaintext) { + uint32_t mkey_rev; + + /* Try to decrypt for all possible master keys. */ + for (mkey_rev = 0; mkey_rev <= mkey_get_revision(); mkey_rev++) { + package2_crypt_ctr(mkey_rev, &metadata, sizeof(package2_meta_t), &header->metadata, sizeof(package2_meta_t), header->metadata.ctr, sizeof(header->metadata.ctr)); + /* Copy the ctr (which stores information) into the decrypted metadata. */ + memcpy(metadata.ctr, header->metadata.ctr, sizeof(header->metadata.ctr)); + /* See if this is the correct key. */ + if (validate_package2_metadata(&metadata)) { + header->metadata = metadata; + return mkey_rev; + } + } + + /* Ensure we successfully decrypted the header. */ + if (mkey_rev > mkey_get_revision()) { + panic(0xFAF00003); + } + } else if (!validate_package2_metadata(&header->metadata)) { + panic(0xFAF0003); + } + return 0; +} void package2_decrypt(void *package2_address) { + /* TODO: Actually decrypt, and copy sections into the relevant g_package2_sections[n] */ + memcpy(g_patched_package2, NX_BOOTLOADER_PACKAGE2_LOAD_ADDRESS, PACKAGE2_SIZE_MAX); + + bool is_package2_plaintext = g_patched_package2_header->signature[0]; + is_package2_plaintext &= memcmp(g_patched_package2_header->signature, g_patched_package2_header->signature + 1, sizeof(g_patched_package2_header->signature) - 1) == 0; + is_package2_plaintext &= g_patched_package2_header->metadata.magic == MAGIC_PK21; + + uint32_t pk21_mkey_revision = decrypt_and_validate_header(g_patched_package2_header, is_package2_plaintext); + + size_t cur_section_offset = 0; + /* Copy each section to its appropriate location, decrypting if necessary. */ + for (unsigned int section = 0; section < PACKAGE2_SECTION_MAX; section++) { + if (g_patched_package2_header->metadata.section_sizes[section] == 0) { + continue; + } + + void *src_start = g_patched_package2 + sizeof(package2_header_t) + cur_section_offset; + void *dst_start = g_package2_sections[section]; + size_t size = (size_t)g_patched_package2_header->metadata.section_sizes[section]; + + if (is_package2_plaintext&& size != 0) { + memcpy(dst_start, src_start, size); + } else if (size != 0) { + package2_crypt_ctr(pk21_mkey_revision, dst_start, size, src_start, size, g_patched_package2_header->metadata.section_ctrs[section], 0x10); + } + cur_section_offset += size; + } + + /* Clear the signature, to signal that this is a plaintext, unsigned package2. */ + memset(g_patched_package2_header->signature, 0, sizeof(g_patched_package2_header->signature)); +} + +void package2_add_thermosphere_section(void) { + if (g_patched_package2_header->metadata.section_sizes[PACKAGE2_SECTION_UNUSED] != 0) { + printk("Error: Package2 has no unused section for Thermosph\xe8re!\n"); + generic_panic(); + } + + /* TODO: Copy thermosphere to g_package2_sections[PACKAGE2_SECTION_UNUSED], update header size. */ +} + +void package2_patch_kernel(void) { + /* TODO: What kind of patching do we want to try to do here? */ +} + +void package2_patch_ini1(void) { + ini1_header_t *ini_header = (ini1_header_t *)g_package2_sections[PACKAGE2_SECTION_INI1]; + if (ini_header->magic != MAGIC_INI1) { + printk("Error: INI1 section appears to not contain an INI1!\n"); + generic_panic(); + } + /* TODO */ } -void package2_add_thermosphere_section(void *package2_address) { - /* TODO */ -} - -void package2_patch_kernel(void *package2_address) { - /* TODO */ -} - -void package2_patch_ini1(void *package2_address) { - /* TODO */ -} - -void package2_fixup_header_and_section_hashes(void *package2_address) { - /* TODO */ +void package2_fixup_header_and_section_hashes(void) { + size_t cur_section_offset = 0; + + /* Copy each section to its appropriate location. */ + for (unsigned int section = 0; section < PACKAGE2_SECTION_MAX; section++) { + if (g_patched_package2_header->metadata.section_sizes[section] == 0) { + continue; + } + + size_t size = (size_t)g_patched_package2_header->metadata.section_sizes[section]; + if (sizeof(package2_header_t) + cur_section_offset + size > PACKAGE2_SIZE_MAX) { + printk("Error: Patched Package2 is too big!\n"); + generic_panic(); + } + + /* Copy the section into the new package2. */ + memcpy(g_patched_package2 + sizeof(package2_header_t) + cur_section_offset, g_package2_sections[section], size); + /* Fix up the hash. */ + se_calculate_sha256(g_patched_package2_header->metadata.section_hashes[section], g_package2_sections[section], size); + + cur_section_offset += size; + } + + /* Fix up the size in XOR'd CTR. */ + uint32_t package_size = g_patched_package2_header->metadata.ctr_dwords[0] ^ g_patched_package2_header->metadata.ctr_dwords[2] ^ g_patched_package2_header->metadata.ctr_dwords[3]; + uint32_t new_package_size = sizeof(package2_header_t) + cur_section_offset; + g_patched_package2_header->metadata.ctr_dwords[3] ^= (package_size ^ new_package_size); } \ No newline at end of file diff --git a/fusee/fusee-secondary/src/package2.h b/fusee/fusee-secondary/src/package2.h index 11c0c367b..c8ab0cf48 100644 --- a/fusee/fusee-secondary/src/package2.h +++ b/fusee/fusee-secondary/src/package2.h @@ -5,8 +5,29 @@ #define MAGIC_PK21 (0x31324B50) #define PACKAGE2_SIZE_MAX 0x7FC000 + +#define PACKAGE2_SECTION_KERNEL 0x0 +#define PACKAGE2_SECTION_INI1 0x1 +#define PACKAGE2_SECTION_UNUSED 0x2 #define PACKAGE2_SECTION_MAX 0x3 +#define PACKAGE2_MINVER_THEORETICAL 0x0 +#define PACKAGE2_MAXVER_100 0x2 +#define PACKAGE2_MAXVER_200 0x3 +#define PACKAGE2_MAXVER_300 0x4 +#define PACKAGE2_MAXVER_302 0x5 +#define PACKAGE2_MAXVER_400_410 0x6 +#define PACKAGE2_MAXVER_500_CURRENT 0x7 + +#define PACKAGE2_MINVER_100 0x3 +#define PACKAGE2_MINVER_200 0x4 +#define PACKAGE2_MINVER_300 0x5 +#define PACKAGE2_MINVER_302 0x6 +#define PACKAGE2_MINVER_400_410 0x7 +#define PACKAGE2_MINVER_500_CURRENT 0x8 + +#define NX_BOOTLOADER_PACKAGE2_LOAD_ADDRESS ((void *)(0xA9800000ull)) + typedef struct { union { uint8_t ctr[0x10];