Merge pull request #5108 from B3n30/load_aes_key_X0x25_from_nativeFirm
Load NCCHSecure2 keyX from native firm
This commit is contained in:
commit
ab998b04c8
|
@ -421,6 +421,8 @@ add_library(core STATIC
|
||||||
hw/hw.h
|
hw/hw.h
|
||||||
hw/lcd.cpp
|
hw/lcd.cpp
|
||||||
hw/lcd.h
|
hw/lcd.h
|
||||||
|
hw/rsa/rsa.cpp
|
||||||
|
hw/rsa/rsa.h
|
||||||
hw/y2r.cpp
|
hw/y2r.cpp
|
||||||
hw/y2r.h
|
hw/y2r.h
|
||||||
loader/3dsx.cpp
|
loader/3dsx.cpp
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <cryptopp/aes.h>
|
#include <cryptopp/aes.h>
|
||||||
#include <cryptopp/modes.h>
|
#include <cryptopp/modes.h>
|
||||||
|
#include <cryptopp/sha.h>
|
||||||
#include <fmt/format.h>
|
#include <fmt/format.h>
|
||||||
#include "common/common_paths.h"
|
#include "common/common_paths.h"
|
||||||
#include "common/file_util.h"
|
#include "common/file_util.h"
|
||||||
|
@ -17,6 +18,7 @@
|
||||||
#include "core/hle/service/fs/archive.h"
|
#include "core/hle/service/fs/archive.h"
|
||||||
#include "core/hw/aes/arithmetic128.h"
|
#include "core/hw/aes/arithmetic128.h"
|
||||||
#include "core/hw/aes/key.h"
|
#include "core/hw/aes/key.h"
|
||||||
|
#include "core/hw/rsa/rsa.h"
|
||||||
|
|
||||||
namespace HW::AES {
|
namespace HW::AES {
|
||||||
|
|
||||||
|
@ -26,7 +28,7 @@ namespace {
|
||||||
// normal key dumped from a Wii U solving the equation:
|
// normal key dumped from a Wii U solving the equation:
|
||||||
// NormalKey = (((KeyX ROL 2) XOR KeyY) + constant) ROL 87
|
// 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
|
// 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
|
// 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.
|
// 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,
|
constexpr AESKey generator_constant = {{0x1F, 0xF9, 0xE9, 0xAA, 0xC5, 0xFE, 0x04, 0x08, 0x02, 0x45,
|
||||||
0x91, 0xDC, 0x5D, 0x52, 0x76, 0x8A}};
|
0x91, 0xDC, 0x5D, 0x52, 0x76, 0x8A}};
|
||||||
|
@ -203,14 +205,67 @@ void LoadBootromKeys() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void LoadNativeFirmKeysOld3DS() {
|
void LoadNativeFirmKeysOld3DS() {
|
||||||
// Use the save mode native firm instead of the normal mode since there are only 2 version of it
|
constexpr u64 native_firm_id = 0x00040138'00000002;
|
||||||
|
FileSys::NCCHArchive archive(native_firm_id, Service::FS::MediaType::NAND);
|
||||||
|
std::array<char, 8> 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 != 966656) {
|
||||||
|
LOG_ERROR(HW_AES, "native firm has wrong size {}", size);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto rsa = RSA::GetSlot(0);
|
||||||
|
if (!rsa) {
|
||||||
|
LOG_ERROR(HW_AES, "RSA slot is missing");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<u8> firm_buffer(size);
|
||||||
|
firm->Read(0, firm_buffer.size(), firm_buffer.data());
|
||||||
|
firm->Close();
|
||||||
|
|
||||||
|
constexpr std::size_t SLOT_0x25_KEY_X_SECRET_OFFSET = 933480;
|
||||||
|
constexpr std::size_t SLOT_0x25_KEY_X_SECRET_SIZE = 64;
|
||||||
|
std::vector<u8> secret_data(SLOT_0x25_KEY_X_SECRET_SIZE);
|
||||||
|
std::memcpy(secret_data.data(), firm_buffer.data() + SLOT_0x25_KEY_X_SECRET_OFFSET,
|
||||||
|
secret_data.size());
|
||||||
|
|
||||||
|
auto asn1 = RSA::CreateASN1Message(secret_data);
|
||||||
|
auto result = rsa.GetSignature(asn1);
|
||||||
|
if (result.size() < 0x100) {
|
||||||
|
std::vector<u8> temp(0x100);
|
||||||
|
std::copy(result.begin(), result.end(), temp.end() - result.size());
|
||||||
|
result = temp;
|
||||||
|
} else if (result.size() > 0x100) {
|
||||||
|
result.resize(0x100);
|
||||||
|
}
|
||||||
|
|
||||||
|
CryptoPP::SHA256 sha;
|
||||||
|
std::array<u8, CryptoPP::SHA256::DIGESTSIZE> hash_result;
|
||||||
|
sha.CalculateDigest(hash_result.data(), result.data(), result.size());
|
||||||
|
AESKey key;
|
||||||
|
std::memcpy(key.data(), hash_result.data(), sizeof(key));
|
||||||
|
key_slots.at(0x2F).SetKeyY(key);
|
||||||
|
std::memcpy(key.data(), hash_result.data() + sizeof(key), sizeof(key));
|
||||||
|
key_slots.at(0x25).SetKeyX(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LoadSafeModeNativeFirmKeysOld3DS() {
|
||||||
|
// Use the safe mode native firm instead of the normal mode since there are only 2 version of it
|
||||||
// and thus we can use fixed offsets
|
// and thus we can use fixed offsets
|
||||||
|
|
||||||
constexpr u64 save_mode_native_firm_id = 0x00040138'00000003;
|
constexpr u64 safe_mode_native_firm_id = 0x00040138'00000003;
|
||||||
|
|
||||||
// TODO(B3N30): Add the 0x25 KeyX that gets initalized by native_firm
|
FileSys::NCCHArchive archive(safe_mode_native_firm_id, Service::FS::MediaType::NAND);
|
||||||
|
|
||||||
FileSys::NCCHArchive archive(save_mode_native_firm_id, Service::FS::MediaType::NAND);
|
|
||||||
std::array<char, 8> exefs_filepath = {'.', 'f', 'i', 'r', 'm', 0, 0, 0};
|
std::array<char, 8> exefs_filepath = {'.', 'f', 'i', 'r', 'm', 0, 0, 0};
|
||||||
FileSys::Path file_path = FileSys::MakeNCCHFilePath(
|
FileSys::Path file_path = FileSys::MakeNCCHFilePath(
|
||||||
FileSys::NCCHFileOpenType::NCCHData, 0, FileSys::NCCHFilePathType::ExeFS, exefs_filepath);
|
FileSys::NCCHFileOpenType::NCCHData, 0, FileSys::NCCHFilePathType::ExeFS, exefs_filepath);
|
||||||
|
@ -223,7 +278,7 @@ void LoadNativeFirmKeysOld3DS() {
|
||||||
auto firm = std::move(file_result).Unwrap();
|
auto firm = std::move(file_result).Unwrap();
|
||||||
const std::size_t size = firm->GetSize();
|
const std::size_t size = firm->GetSize();
|
||||||
if (size != 843776) {
|
if (size != 843776) {
|
||||||
LOG_ERROR(HW_AES, "save mode native firm has wrong size {}", size);
|
LOG_ERROR(HW_AES, "safe mode native firm has wrong size {}", size);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
std::vector<u8> firm_buffer(size);
|
std::vector<u8> firm_buffer(size);
|
||||||
|
@ -265,16 +320,16 @@ void LoadNativeFirmKeysNew3DS() {
|
||||||
AESKey secret_key;
|
AESKey secret_key;
|
||||||
secret.ReadArray(secret_key.data(), secret_key.size());
|
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
|
// Use the safe mode native firm instead of the normal mode since there are only 1 version of it
|
||||||
// and thus we can use fixed offsets
|
// and thus we can use fixed offsets
|
||||||
constexpr u64 save_mode_native_firm_id = 0x00040138'20000003;
|
constexpr u64 safe_mode_native_firm_id = 0x00040138'20000003;
|
||||||
|
|
||||||
// TODO(B3N30): Add the 0x25 KeyX that gets initalized by native_firm
|
// 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
|
// 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
|
// requires the normal native firm with version > 9.6.0-X
|
||||||
|
|
||||||
FileSys::NCCHArchive archive(save_mode_native_firm_id, Service::FS::MediaType::NAND);
|
FileSys::NCCHArchive archive(safe_mode_native_firm_id, Service::FS::MediaType::NAND);
|
||||||
std::array<char, 8> exefs_filepath = {'.', 'f', 'i', 'r', 'm', 0, 0, 0};
|
std::array<char, 8> exefs_filepath = {'.', 'f', 'i', 'r', 'm', 0, 0, 0};
|
||||||
FileSys::Path file_path = FileSys::MakeNCCHFilePath(
|
FileSys::Path file_path = FileSys::MakeNCCHFilePath(
|
||||||
FileSys::NCCHFileOpenType::NCCHData, 0, FileSys::NCCHFilePathType::ExeFS, exefs_filepath);
|
FileSys::NCCHFileOpenType::NCCHData, 0, FileSys::NCCHFilePathType::ExeFS, exefs_filepath);
|
||||||
|
@ -296,7 +351,7 @@ void LoadNativeFirmKeysNew3DS() {
|
||||||
return a | b << 8 | c << 16 | d << 24;
|
return a | b << 8 | c << 16 | d << 24;
|
||||||
};
|
};
|
||||||
if (MakeMagic('F', 'I', 'R', 'M') != header.magic) {
|
if (MakeMagic('F', 'I', 'R', 'M') != header.magic) {
|
||||||
LOG_ERROR(HW_AES, "N3DS SAVE MODE Native Firm has wrong header {}", header.magic);
|
LOG_ERROR(HW_AES, "N3DS SAFE MODE Native Firm has wrong header {}", header.magic);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -443,11 +498,13 @@ void InitKeys() {
|
||||||
static bool initialized = false;
|
static bool initialized = false;
|
||||||
if (initialized)
|
if (initialized)
|
||||||
return;
|
return;
|
||||||
|
initialized = true;
|
||||||
|
HW::RSA::InitSlots();
|
||||||
LoadBootromKeys();
|
LoadBootromKeys();
|
||||||
LoadNativeFirmKeysOld3DS();
|
LoadNativeFirmKeysOld3DS();
|
||||||
|
LoadSafeModeNativeFirmKeysOld3DS();
|
||||||
LoadNativeFirmKeysNew3DS();
|
LoadNativeFirmKeysNew3DS();
|
||||||
LoadPresetKeys();
|
LoadPresetKeys();
|
||||||
initialized = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SetKeyX(std::size_t slot_id, const AESKey& key) {
|
void SetKeyX(std::size_t slot_id, const AESKey& key) {
|
||||||
|
|
|
@ -0,0 +1,113 @@
|
||||||
|
// Copyright 2020 Citra Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#include <sstream>
|
||||||
|
#include <cryptopp/hex.h>
|
||||||
|
#include <cryptopp/integer.h>
|
||||||
|
#include <cryptopp/nbtheory.h>
|
||||||
|
#include <cryptopp/sha.h>
|
||||||
|
#include <fmt/format.h>
|
||||||
|
#include "common/common_paths.h"
|
||||||
|
#include "common/file_util.h"
|
||||||
|
#include "common/logging/log.h"
|
||||||
|
#include "core/hw/rsa/rsa.h"
|
||||||
|
|
||||||
|
namespace HW::RSA {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
std::vector<u8> HexToBytes(const std::string& hex) {
|
||||||
|
std::vector<u8> bytes;
|
||||||
|
|
||||||
|
for (unsigned int i = 0; i < hex.length(); i += 2) {
|
||||||
|
std::string byteString = hex.substr(i, 2);
|
||||||
|
u8 byte = static_cast<u8>(std::strtol(byteString.c_str(), nullptr, 16));
|
||||||
|
bytes.push_back(byte);
|
||||||
|
}
|
||||||
|
return bytes;
|
||||||
|
};
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
constexpr std::size_t SlotSize = 4;
|
||||||
|
std::array<RsaSlot, SlotSize> rsa_slots;
|
||||||
|
|
||||||
|
std::vector<u8> RsaSlot::GetSignature(const std::vector<u8>& message) {
|
||||||
|
CryptoPP::Integer sig =
|
||||||
|
CryptoPP::ModularExponentiation(CryptoPP::Integer(message.data(), message.size()),
|
||||||
|
CryptoPP::Integer(exponent.data(), exponent.size()),
|
||||||
|
CryptoPP::Integer(modulus.data(), modulus.size()));
|
||||||
|
std::stringstream ss;
|
||||||
|
ss << std::hex << sig;
|
||||||
|
CryptoPP::HexDecoder decoder;
|
||||||
|
decoder.Put(reinterpret_cast<unsigned char*>(ss.str().data()), ss.str().size());
|
||||||
|
decoder.MessageEnd();
|
||||||
|
std::vector<u8> result(decoder.MaxRetrievable());
|
||||||
|
decoder.Get(result.data(), result.size());
|
||||||
|
return HexToBytes(ss.str());
|
||||||
|
}
|
||||||
|
|
||||||
|
void InitSlots() {
|
||||||
|
static bool initialized = false;
|
||||||
|
if (initialized)
|
||||||
|
return;
|
||||||
|
initialized = true;
|
||||||
|
|
||||||
|
const std::string filepath = FileUtil::GetUserPath(FileUtil::UserPath::SysDataDir) + BOOTROM9;
|
||||||
|
FileUtil::IOFile file(filepath, "rb");
|
||||||
|
if (!file) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::size_t length = file.GetSize();
|
||||||
|
if (length != 65536) {
|
||||||
|
LOG_ERROR(HW_AES, "Bootrom9 size is wrong: {}", length);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr std::size_t RSA_MODULUS_POS = 0xB3E0;
|
||||||
|
file.Seek(RSA_MODULUS_POS, SEEK_SET);
|
||||||
|
std::vector<u8> modulus(256);
|
||||||
|
file.ReadArray(modulus.data(), modulus.size());
|
||||||
|
|
||||||
|
constexpr std::size_t RSA_EXPONENT_POS = 0xB4E0;
|
||||||
|
file.Seek(RSA_EXPONENT_POS, SEEK_SET);
|
||||||
|
std::vector<u8> exponent(256);
|
||||||
|
file.ReadArray(exponent.data(), exponent.size());
|
||||||
|
|
||||||
|
rsa_slots[0] = RsaSlot(exponent, modulus);
|
||||||
|
// TODO(B3N30): Initalize the other slots. But since they aren't used at all, we can skip them
|
||||||
|
// for now
|
||||||
|
}
|
||||||
|
|
||||||
|
RsaSlot GetSlot(std::size_t slot_id) {
|
||||||
|
if (slot_id >= rsa_slots.size())
|
||||||
|
return RsaSlot{};
|
||||||
|
return rsa_slots[slot_id];
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<u8> CreateASN1Message(const std::vector<u8>& data) {
|
||||||
|
static constexpr std::array<u8, 224> asn1_header = {
|
||||||
|
{0x00, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
||||||
|
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
||||||
|
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
||||||
|
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
||||||
|
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
||||||
|
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
||||||
|
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
||||||
|
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
||||||
|
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
||||||
|
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
||||||
|
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
||||||
|
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
||||||
|
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
|
||||||
|
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x30, 0x31, 0x30, 0x0D, 0x06,
|
||||||
|
0x09, 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05, 0x00, 0x04, 0x20}};
|
||||||
|
|
||||||
|
std::vector<u8> message(asn1_header.begin(), asn1_header.end());
|
||||||
|
CryptoPP::SHA256 sha;
|
||||||
|
message.resize(message.size() + CryptoPP::SHA256::DIGESTSIZE);
|
||||||
|
sha.CalculateDigest(message.data() + asn1_header.size(), data.data(), data.size());
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace HW::RSA
|
|
@ -0,0 +1,36 @@
|
||||||
|
// Copyright 2020 Citra Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include "common/common_types.h"
|
||||||
|
|
||||||
|
namespace HW::RSA {
|
||||||
|
|
||||||
|
class RsaSlot {
|
||||||
|
public:
|
||||||
|
RsaSlot() : init(false) {}
|
||||||
|
RsaSlot(const std::vector<u8>& exponent, const std::vector<u8>& modulus)
|
||||||
|
: init(true), exponent(exponent), modulus(modulus) {}
|
||||||
|
std::vector<u8> GetSignature(const std::vector<u8>& message);
|
||||||
|
|
||||||
|
operator bool() const {
|
||||||
|
// TODO(B3N30): Maybe check if exponent and modulus are vailid
|
||||||
|
return init;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool init;
|
||||||
|
std::vector<u8> exponent;
|
||||||
|
std::vector<u8> modulus;
|
||||||
|
};
|
||||||
|
|
||||||
|
void InitSlots();
|
||||||
|
|
||||||
|
RsaSlot GetSlot(std::size_t slot_id);
|
||||||
|
|
||||||
|
std::vector<u8> CreateASN1Message(const std::vector<u8>& data);
|
||||||
|
|
||||||
|
} // namespace HW::RSA
|
Loading…
Reference in New Issue