From 4355a2b03615e0839ab125d5dfda6f64b20afa3d Mon Sep 17 00:00:00 2001 From: Michael Scire Date: Wed, 1 Sep 2021 00:50:52 -0700 Subject: [PATCH] fusee_cpp: implement read/decryption of package2 --- fusee_cpp/program/source/fusee_package2.cpp | 165 ++++++++++++++++++ fusee_cpp/program/source/fusee_package2.hpp | 23 +++ .../program/source/fusee_setup_horizon.cpp | 39 ++++- 3 files changed, 226 insertions(+), 1 deletion(-) create mode 100644 fusee_cpp/program/source/fusee_package2.cpp create mode 100644 fusee_cpp/program/source/fusee_package2.hpp diff --git a/fusee_cpp/program/source/fusee_package2.cpp b/fusee_cpp/program/source/fusee_package2.cpp new file mode 100644 index 000000000..2c0d6c28c --- /dev/null +++ b/fusee_cpp/program/source/fusee_package2.cpp @@ -0,0 +1,165 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include +#include "fusee_package2.hpp" +#include "fusee_key_derivation.hpp" +#include "fusee_fatal.hpp" + +namespace ams::nxboot { + + namespace { + + alignas(se::AesBlockSize) constexpr inline const u8 Package2KeySource[se::AesBlockSize] = { + 0xFB, 0x8B, 0x6A, 0x9C, 0x79, 0x00, 0xC8, 0x49, 0xEF, 0xD2, 0x4D, 0x85, 0x4D, 0x30, 0xA0, 0xC7 + }; + + void PreparePackage2Key(int pkg2_slot, int key_generation) { + /* Get keyslot for the desired master key. */ + const int master_slot = PrepareMasterKey(key_generation); + + /* Load the package2 key into the desired keyslot. */ + se::SetEncryptedAesKey128(pkg2_slot, master_slot, Package2KeySource, sizeof(Package2KeySource)); + } + + void DecryptPackage2(void *dst, size_t dst_size, const void *src, size_t src_size, const void *iv, size_t iv_size, u8 key_generation) { + /* Ensure that the SE sees consistent data. */ + hw::FlushDataCache(src, src_size); + if (src != dst) { + hw::FlushDataCache(dst, dst_size); + } + + /* Load the package2 key into the temporary keyslot. */ + PreparePackage2Key(pkg1::AesKeySlot_Temporary, key_generation); + + /* Decrypt the data. */ + se::ComputeAes128Ctr(dst, dst_size, pkg1::AesKeySlot_Temporary, src, src_size, iv, iv_size); + + /* Clear the keyslot we just used. */ + se::ClearAesKeySlot(pkg1::AesKeySlot_Temporary); + + /* Ensure that the cpu sees consistent data. */ + hw::InvalidateDataCache(dst, dst_size); + } + + void DecryptPackage2Header(pkg2::Package2Meta *dst, const pkg2::Package2Meta &src) { + constexpr int IvSize = 0x10; + + /* Decrypt the header. */ + DecryptPackage2(dst, sizeof(*dst), std::addressof(src), sizeof(src), std::addressof(src), IvSize, src.GetKeyGeneration()); + + /* Copy back the iv, which encodes encrypted metadata. */ + std::memcpy(dst, std::addressof(src), IvSize); + } + + bool VerifyPackage2Meta(const pkg2::Package2Meta &meta) { + /* Get the obfuscated metadata. */ + const size_t size = meta.GetSize(); + const u8 key_generation = meta.GetKeyGeneration(); + + /* Check that size is big enough for the header. */ + if (size <= sizeof(pkg2::Package2Header)) { + return false; + } + + /* Check that the size isn't larger than what we allow. */ + if (size > pkg2::Package2SizeMax) { + return false; + } + + /* Check that the key generation is one that we can use. */ + static_assert(pkg1::KeyGeneration_Count == 12); + if (key_generation >= pkg1::KeyGeneration_Count) { + return false; + } + + /* Check the magic number. */ + if (!crypto::IsSameBytes(meta.magic, pkg2::Package2Meta::Magic::String, sizeof(meta.magic))) { + return false; + } + + /* Check the payload alignments. */ + if ((meta.entrypoint % pkg2::PayloadAlignment) != 0) { + return false; + } + + for (int i = 0; i < pkg2::PayloadCount; ++i) { + if ((meta.payload_sizes[i] % pkg2::PayloadAlignment) != 0) { + return false; + } + } + + /* Check that the sizes sum to the total. */ + if (size != sizeof(pkg2::Package2Header) + meta.payload_sizes[0] + meta.payload_sizes[1] + meta.payload_sizes[2]) { + return false; + } + + /* Check that the payloads do not overflow. */ + for (int i = 0; i < pkg2::PayloadCount; ++i) { + if (meta.payload_offsets[i] > meta.payload_offsets[i] + meta.payload_sizes[i]) { + return false; + } + } + + /* Verify that no payloads overlap. */ + for (int i = 0; i < pkg2::PayloadCount - 1; ++i) { + for (int j = i + 1; j < pkg2::PayloadCount; ++j) { + if (util::HasOverlap(meta.payload_offsets[i], meta.payload_sizes[i], meta.payload_offsets[j], meta.payload_sizes[j])) { + return false; + } + } + } + + /* Check whether any payload contains the entrypoint. */ + for (int i = 0; i < pkg2::PayloadCount; ++i) { + if (util::Contains(meta.payload_offsets[i], meta.payload_sizes[i], meta.entrypoint)) { + return true; + } + } + + /* No payload contains the entrypoint, so we're not valid. */ + return false; + } + + } + + void DecryptPackage2(u8 *package2) { + /* Decrypt package2 header. */ + pkg2::Package2Header *header = reinterpret_cast(package2); + { + pkg2::Package2Header tmp = *header; + DecryptPackage2Header(std::addressof(header->meta), tmp.meta); + } + + /* Check package2 magic. */ + if (!VerifyPackage2Meta(header->meta)) { + ShowFatalError("Package2 meta is invalid!\n"); + } + + /* Decrypt package2 payloads. */ + u8 *payload = package2 + sizeof(*header); + const u8 key_generation = header->meta.GetKeyGeneration(); + for (int i = 0; i < pkg2::PayloadCount; ++i) { + if (header->meta.payload_sizes[i] == 0) { + continue; + } + + DecryptPackage2(payload, header->meta.payload_sizes[i], payload, header->meta.payload_sizes[i], header->meta.payload_ivs[i], sizeof(header->meta.payload_ivs[i]), key_generation); + + payload += header->meta.payload_sizes[i]; + } + } + +} diff --git a/fusee_cpp/program/source/fusee_package2.hpp b/fusee_cpp/program/source/fusee_package2.hpp new file mode 100644 index 000000000..a595d5e27 --- /dev/null +++ b/fusee_cpp/program/source/fusee_package2.hpp @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2018-2020 Atmosphère-NX + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +#include +#pragma once + +namespace ams::nxboot { + + void DecryptPackage2(u8 *package2); + +} \ No newline at end of file diff --git a/fusee_cpp/program/source/fusee_setup_horizon.cpp b/fusee_cpp/program/source/fusee_setup_horizon.cpp index 5620f3872..97ac13a3d 100644 --- a/fusee_cpp/program/source/fusee_setup_horizon.cpp +++ b/fusee_cpp/program/source/fusee_setup_horizon.cpp @@ -21,6 +21,7 @@ #include "fusee_emummc.hpp" #include "fusee_mmc.hpp" #include "fusee_fatal.hpp" +#include "fusee_package2.hpp" #include "fusee_malloc.hpp" #include "fs/fusee_fs_api.hpp" @@ -333,6 +334,39 @@ namespace ams::nxboot { return target_firmware; } + u8 *LoadBootConfigAndPackage2() { + Result result; + + /* Load boot config. */ + if (R_FAILED((result = ReadPackage2(0, secmon::MemoryRegionPhysicalIramBootConfig.GetPointer(), secmon::MemoryRegionPhysicalIramBootConfig.GetSize())))) { + ShowFatalError("Failed to read boot config: 0x%08" PRIx32 "!\n", result.GetValue()); + } + + /* Read package2 header. */ + u8 *package2; + size_t package2_size; + { + constexpr s64 Package2Offset = __builtin_offsetof(pkg2::StorageLayout, package2_header); + + pkg2::Package2Header header; + if (R_FAILED((result = ReadPackage2(Package2Offset, std::addressof(header), sizeof(header))))) { + ShowFatalError("Failed to read package2 header: 0x%08" PRIx32 "!\n", result.GetValue()); + } + + package2_size = header.meta.GetSize(); + package2 = static_cast(AllocateAligned(util::AlignUp(package2_size, 0x4000), 0x4000)); + + if (R_FAILED((result = ReadPackage2(Package2Offset, package2, util::AlignUp(package2_size, 0x4000))))) { + ShowFatalError("Failed to read package2: 0x%08" PRIx32 "!\n", result.GetValue()); + } + } + + /* Decrypt package2. */ + DecryptPackage2(package2); + + return package2; + } + } void SetupAndStartHorizon() { @@ -356,7 +390,9 @@ namespace ams::nxboot { const auto target_firmware = GetTargetFirmware(package1); AMS_UNUSED(target_firmware); - /* TODO: Read/decrypt package2. */ + /* Read/decrypt package2. */ + u8 * const package2 = LoadBootConfigAndPackage2(); + AMS_UNUSED(package2); /* TODO: Setup warmboot firmware. */ @@ -366,6 +402,7 @@ namespace ams::nxboot { /* NOTE: Security Engine unusable past this point. */ /* TODO: Build modified package2. */ + WaitForReboot(); } } \ No newline at end of file