diff --git a/src/common/common_paths.h b/src/common/common_paths.h index 1db4d7c3d..80aa4b5a0 100644 --- a/src/common/common_paths.h +++ b/src/common/common_paths.h @@ -51,3 +51,4 @@ #define SHARED_FONT "shared_font.bin" #define AES_KEYS "aes_keys.txt" #define BOOTROM9 "boot9.bin" +#define SECRET_SECTOR "sector0x96.bin" diff --git a/src/core/hw/aes/key.cpp b/src/core/hw/aes/key.cpp index eb0518111..0a1ab6eee 100644 --- a/src/core/hw/aes/key.cpp +++ b/src/core/hw/aes/key.cpp @@ -6,6 +6,8 @@ #include #include #include +#include +#include #include #include "common/common_paths.h" #include "common/file_util.h" @@ -37,6 +39,19 @@ struct KeyDesc { bool same_as_before; }; +AESKey HexToKey(const std::string& hex) { + if (hex.size() < 32) { + throw std::invalid_argument("hex string is too short"); + } + + AESKey key; + for (std::size_t i = 0; i < key.size(); ++i) { + key[i] = static_cast(std::stoi(hex.substr(i * 2, 2), 0, 16)); + } + + return key; +} + struct KeySlot { std::optional x; std::optional y; @@ -74,18 +89,39 @@ struct KeySlot { std::array key_slots; std::array, 6> common_key_y_slots; -AESKey HexToKey(const std::string& hex) { - if (hex.size() < 32) { - throw std::invalid_argument("hex string is too short"); - } +enum class FirmwareType : u32 { + ARM9 = 0, // uses NDMA + ARM11 = 1, // uses XDMA +}; - AESKey key; - for (std::size_t i = 0; i < key.size(); ++i) { - key[i] = static_cast(std::stoi(hex.substr(i * 2, 2), 0, 16)); - } +struct FirmwareSectionHeader { + u32_le offset; + u32_le phys_address; + u32_le size; + enum_le firmware_type; + std::array hash; // SHA-256 hash +}; - return key; -} +struct FIRM_Header { + u32_le magic; // FIRM + u32_le boot_priority; // Usually 0 + u32_le arm11_entrypoint; + u32_le arm9_entrypoint; + INSERT_PADDING_BYTES(0x30); // Reserved + std::array section_headers; // 1st ARM11?, 3rd ARM9 + std::array signature; // RSA-2048 signature of the FIRM header's hash +}; + +struct ARM9_HEADER { + AESKey enc_key_x; + AESKey key_y; + AESKey CTR; + std::array size; // in ASCII + INSERT_PADDING_BYTES(8); // Unknown + std::array control_block; + std::array hardware_debug_info; + std::array enc_key_x_slot_16; +}; std::string KeyToString(AESKey& key) { std::string s; @@ -170,8 +206,11 @@ void LoadBootromKeys() { void LoadNativeFirmKeysOld3DS() { // Use the save mode native firm instead of the normal mode since there are only 2 version of it // and thus we can use fixed offsets + constexpr u64_le save_mode_native_firm_id_low = 0x0004013800000003; + // TODO(B3N30): Add the 0x25 KeyX that gets initalized by native_firm + FileSys::NCCHArchive archive(save_mode_native_firm_id_low, Service::FS::MediaType::NAND); std::array exefs_filepath = {'.', 'f', 'i', 'r', 'm', 0, 0, 0}; FileSys::Path file_path = FileSys::MakeNCCHFilePath( @@ -213,6 +252,125 @@ void LoadNativeFirmKeysOld3DS() { } } +void LoadNativeFirmKeysNew3DS() { + // The first 0x10 bytes of the secret_sector are used as a key to decrypt a KeyX from the + // native_firm + const std::string filepath = + FileUtil::GetUserPath(FileUtil::UserPath::SysDataDir) + SECRET_SECTOR; + auto secret = FileUtil::IOFile(filepath, "rb"); + if (!secret) { + return; + } + ASSERT(secret.GetSize() > 0x10); + + AESKey secret_key; + secret.ReadArray(secret_key.data(), secret_key.size()); + + // Use the save mode native firm instead of the normal mode since there are only 1 version of it + // and thus we can use fixed offsets + constexpr u64_le save_mode_native_firm_id_low = 0x0004013820000003; + + // TODO(B3N30): Add the 0x25 KeyX that gets initalized by native_firm + + // TODO(B3N30): Add the 0x18 - 0x1F KeyX that gets initalized by native_firm. This probably + // requires the normal native firm with version > 9.6.0-X + + FileSys::NCCHArchive archive(save_mode_native_firm_id_low, Service::FS::MediaType::NAND); + std::array exefs_filepath = {'.', 'f', 'i', 'r', 'm', 0, 0, 0}; + FileSys::Path file_path = FileSys::MakeNCCHFilePath( + FileSys::NCCHFileOpenType::NCCHData, 0, FileSys::NCCHFilePathType::ExeFS, exefs_filepath); + FileSys::Mode open_mode = {}; + open_mode.read_flag.Assign(1); + auto file_result = archive.OpenFile(file_path, open_mode); + if (file_result.Failed()) + return; + + auto firm = std::move(file_result).Unwrap(); + std::vector firm_buffer(firm->GetSize()); + firm->Read(0, firm_buffer.size(), firm_buffer.data()); + firm->Close(); + + FIRM_Header header; + std::memcpy(&header, firm_buffer.data(), sizeof(header)); + + auto MakeMagic = [](char a, char b, char c, char d) -> u32 { + return a | b << 8 | c << 16 | d << 24; + }; + if (MakeMagic('F', 'I', 'R', 'M') != header.magic) { + LOG_ERROR(HW_AES, "N3DS SAVE MODE Native Firm has wrong header {}", header.magic); + return; + } + + u32 arm9_offset(0); + u32 arm9_size(0); + for (auto section_header : header.section_headers) { + if (section_header.firmware_type == FirmwareType::ARM9) { + arm9_offset = section_header.offset; + arm9_size = section_header.size; + break; + } + } + + if (arm9_offset != 0x66800) { + LOG_ERROR(HW_AES, "ARM9 binary at wrong offset: {}", arm9_offset); + return; + } + if (arm9_size != 0x8BA00) { + LOG_ERROR(HW_AES, "ARM9 binary has wrong size: {}", arm9_size); + return; + } + + ARM9_HEADER arm9_header; + std::memcpy(&arm9_header, firm_buffer.data() + arm9_offset, sizeof(arm9_header)); + + AESKey keyX_slot0x15; + CryptoPP::ECB_Mode::Decryption d; + d.SetKey(secret_key.data(), secret_key.size()); + d.ProcessData(keyX_slot0x15.data(), arm9_header.enc_key_x.data(), arm9_header.enc_key_x.size()); + + key_slots.at(0x15).SetKeyX(keyX_slot0x15); + key_slots.at(0x15).SetKeyY(arm9_header.key_y); + auto normal_key_slot0x15 = key_slots.at(0x15).normal; + if (!normal_key_slot0x15) { + LOG_ERROR(HW_AES, "Failed to get normal key for slot id 0x15"); + return; + } + + constexpr u32 ARM9_BINARY_OFFSET = 0x800; // From the beginning of the ARM9 section + std::vector enc_arm9_binary; + enc_arm9_binary.resize(arm9_size - ARM9_BINARY_OFFSET); + ASSERT(enc_arm9_binary.size() + arm9_offset + ARM9_BINARY_OFFSET < firm_buffer.size()); + std::memcpy(enc_arm9_binary.data(), firm_buffer.data() + arm9_offset + ARM9_BINARY_OFFSET, + enc_arm9_binary.size()); + + std::vector arm9_binary; + arm9_binary.resize(enc_arm9_binary.size()); + CryptoPP::CTR_Mode::Decryption d2; + d2.SetKeyWithIV(normal_key_slot0x15->data(), normal_key_slot0x15->size(), + arm9_header.CTR.data(), arm9_header.CTR.size()); + d2.ProcessData(arm9_binary.data(), enc_arm9_binary.data(), enc_arm9_binary.size()); + + AESKey key; + constexpr std::size_t SLOT_0x31_KEY_Y_OFFSET = 517368; + std::memcpy(key.data(), arm9_binary.data() + SLOT_0x31_KEY_Y_OFFSET, sizeof(key)); + key_slots.at(0x31).SetKeyY(key); + LOG_DEBUG(HW_AES, "Loaded Slot0x31 KeyY: {}", KeyToString(key)); + + auto LoadCommonKey = [&arm9_binary](std::size_t key_slot) -> AESKey { + constexpr std::size_t START_OFFSET = 541065; + constexpr std::size_t OFFSET = 0x14; // 0x10 bytes for key + 4 bytes between keys + AESKey key; + std::memcpy(key.data(), arm9_binary.data() + START_OFFSET + OFFSET * key_slot, sizeof(key)); + return key; + }; + + for (std::size_t key_slot{0}; key_slot < 6; ++key_slot) { + AESKey key = LoadCommonKey(key_slot); + common_key_y_slots[key_slot] = key; + LOG_DEBUG(HW_AES, "Loaded common key{}: {}", key_slot, KeyToString(key)); + } +} + void LoadPresetKeys() { const std::string filepath = FileUtil::GetUserPath(FileUtil::UserPath::SysDataDir) + AES_KEYS; FileUtil::CreateFullPath(filepath); // Create path if not already created @@ -288,7 +446,7 @@ void InitKeys() { return; LoadBootromKeys(); LoadNativeFirmKeysOld3DS(); - // TODO(B3N30): Load new_3ds save_native_firm + LoadNativeFirmKeysNew3DS(); LoadPresetKeys(); initialized = true; }