From 1849e8b09c8d3852b8f9de8151b8206ecb64e859 Mon Sep 17 00:00:00 2001 From: B3n30 Date: Wed, 17 Oct 2018 16:07:11 +0200 Subject: [PATCH 1/4] HW::AES: add generator_constant --- src/core/hw/aes/key.cpp | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/src/core/hw/aes/key.cpp b/src/core/hw/aes/key.cpp index 99e1b7fb5..b69aad977 100644 --- a/src/core/hw/aes/key.cpp +++ b/src/core/hw/aes/key.cpp @@ -19,7 +19,14 @@ namespace AES { namespace { -std::optional generator_constant; +// The generator constant was calculated using the 0x39 KeyX and KeyY retrieved from a 3DS and the +// normal key dumped from a Wii U solving the equation: +// NormalKey = (((KeyX ROL 2) XOR KeyY) + constant) ROL 87 +// On a real 3DS the generation for the normal key is hardware based, and thus the constant can't +// get dumped . generated normal keys are also not accesible on a 3DS. The used formula for +// calculating the constant is a software implementation of what the hardware generator does. +constexpr AESKey generator_constant = {{0x1F, 0xF9, 0xE9, 0xAA, 0xC5, 0xFE, 0x04, 0x08, 0x02, 0x45, + 0x91, 0xDC, 0x5D, 0x52, 0x76, 0x8A}}; struct KeyDesc { char key_type; @@ -48,8 +55,8 @@ struct KeySlot { } void GenerateNormalKey() { - if (x && y && generator_constant) { - normal = Lrot128(Add128(Xor128(Lrot128(*x, 2), *y), *generator_constant), 87); + if (x && y) { + normal = Lrot128(Add128(Xor128(Lrot128(*x, 2), *y), generator_constant), 87); } else { normal = {}; } @@ -181,11 +188,6 @@ void LoadPresetKeys() { continue; } - if (name == "generator") { - generator_constant = key; - continue; - } - std::size_t common_key_index; if (std::sscanf(name.c_str(), "common%zd", &common_key_index) == 1) { if (common_key_index >= common_key_y_slots.size()) { @@ -236,10 +238,6 @@ void InitKeys() { initialized = true; } -void SetGeneratorConstant(const AESKey& key) { - generator_constant = key; -} - void SetKeyX(std::size_t slot_id, const AESKey& key) { key_slots.at(slot_id).SetKeyX(key); } From 15c9db08835cac26e17c3d0657fae400401fff6d Mon Sep 17 00:00:00 2001 From: B3n30 Date: Sun, 14 Oct 2018 00:06:39 +0200 Subject: [PATCH 2/4] Load keys from the o3DS save mode native firm --- src/core/hw/aes/key.cpp | 65 +++++++++++++++++++++++++++++++++++++---- 1 file changed, 60 insertions(+), 5 deletions(-) diff --git a/src/core/hw/aes/key.cpp b/src/core/hw/aes/key.cpp index b69aad977..eb0518111 100644 --- a/src/core/hw/aes/key.cpp +++ b/src/core/hw/aes/key.cpp @@ -11,6 +11,8 @@ #include "common/file_util.h" #include "common/logging/log.h" #include "common/string_util.h" +#include "core/file_sys/archive_ncch.h" +#include "core/hle/service/fs/archive.h" #include "core/hw/aes/arithmetic128.h" #include "core/hw/aes/key.h" @@ -85,6 +87,14 @@ AESKey HexToKey(const std::string& hex) { return key; } +std::string KeyToString(AESKey& key) { + std::string s; + for (auto pos : key) { + s += fmt::format("{:02X}", pos); + } + return s; +} + void LoadBootromKeys() { constexpr std::array keys = { {{'X', 0x2C, false}, {'X', 0x2D, true}, {'X', 0x2E, true}, {'X', 0x2F, true}, @@ -137,11 +147,8 @@ void LoadBootromKeys() { } } - std::string s; - for (auto pos : new_key) { - s += fmt::format("{:02X}", pos); - } - LOG_DEBUG(HW_AES, "Loaded Slot{:#02x} Key{}: {}", key.slot_id, key.key_type, s); + LOG_DEBUG(HW_AES, "Loaded Slot{:#02x} Key{}: {}", key.slot_id, key.key_type, + KeyToString(new_key)); switch (key.key_type) { case 'X': @@ -160,6 +167,52 @@ 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; + + 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(); + const std::size_t size = firm->GetSize(); + if (size != 843776) { + LOG_ERROR(HW_AES, "save mode native firm has wrong size {}", size); + return; + } + std::vector firm_buffer(size); + firm->Read(0, firm_buffer.size(), firm_buffer.data()); + firm->Close(); + + AESKey key; + constexpr std::size_t SLOT_0x31_KEY_Y_OFFSET = 817672; + std::memcpy(key.data(), firm_buffer.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 = [&firm_buffer](std::size_t key_slot) -> AESKey { + constexpr std::size_t START_OFFSET = 836533; + constexpr std::size_t OFFSET = 0x14; // 0x10 bytes for key + 4 bytes between keys + AESKey key; + std::memcpy(key.data(), firm_buffer.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 @@ -234,6 +287,8 @@ void InitKeys() { if (initialized) return; LoadBootromKeys(); + LoadNativeFirmKeysOld3DS(); + // TODO(B3N30): Load new_3ds save_native_firm LoadPresetKeys(); initialized = true; } From be3bd18c4252a55af703b77f771e88fb5eb967cb Mon Sep 17 00:00:00 2001 From: B3n30 Date: Wed, 17 Oct 2018 13:21:02 +0200 Subject: [PATCH 3/4] Load keys from new3DS native firm --- src/common/common_paths.h | 1 + src/core/hw/aes/key.cpp | 180 +++++++++++++++++++++++++++++++++++--- 2 files changed, 170 insertions(+), 11 deletions(-) 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; } From ad232efbf07c5819019da2b1ba388db1d7f64a80 Mon Sep 17 00:00:00 2001 From: Weiyi Wang Date: Wed, 17 Oct 2018 18:03:27 +0200 Subject: [PATCH 4/4] apply fixes --- src/core/hw/aes/key.cpp | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/core/hw/aes/key.cpp b/src/core/hw/aes/key.cpp index 0a1ab6eee..7f034f9a3 100644 --- a/src/core/hw/aes/key.cpp +++ b/src/core/hw/aes/key.cpp @@ -112,7 +112,7 @@ struct FIRM_Header { std::array signature; // RSA-2048 signature of the FIRM header's hash }; -struct ARM9_HEADER { +struct ARM9_Header { AESKey enc_key_x; AESKey key_y; AESKey CTR; @@ -207,11 +207,11 @@ 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; + constexpr u64 save_mode_native_firm_id = 0x00040138'00000003; // 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); + FileSys::NCCHArchive archive(save_mode_native_firm_id, 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); @@ -268,14 +268,14 @@ void LoadNativeFirmKeysNew3DS() { // 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; + constexpr u64 save_mode_native_firm_id = 0x00040138'20000003; // 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); + FileSys::NCCHArchive archive(save_mode_native_firm_id, 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); @@ -320,7 +320,7 @@ void LoadNativeFirmKeysNew3DS() { return; } - ARM9_HEADER arm9_header; + ARM9_Header arm9_header; std::memcpy(&arm9_header, firm_buffer.data() + arm9_offset, sizeof(arm9_header)); AESKey keyX_slot0x15;