From 91bcead04683a71acca5f35b9ac83e37545f6672 Mon Sep 17 00:00:00 2001 From: Michael Scire Date: Tue, 12 May 2020 11:40:29 -0700 Subject: [PATCH] exo2: implement through package2 decryption --- libexosphere/include/exosphere.hpp | 1 + .../exosphere/pkg1/pkg1_boot_config.hpp | 4 + libexosphere/include/exosphere/pkg2.hpp | 81 +++++++++++++++++++ libexosphere/include/exosphere/se/se_aes.hpp | 4 +- .../exosphere/secmon/secmon_memory_layout.hpp | 3 + libexosphere/source/se/se_aes.cpp | 78 ++++++++++++++++-- 6 files changed, 163 insertions(+), 8 deletions(-) create mode 100644 libexosphere/include/exosphere/pkg2.hpp diff --git a/libexosphere/include/exosphere.hpp b/libexosphere/include/exosphere.hpp index b320a52e..c6468ab6 100644 --- a/libexosphere/include/exosphere.hpp +++ b/libexosphere/include/exosphere.hpp @@ -25,6 +25,7 @@ #include #include #include +#include #include #include #include diff --git a/libexosphere/include/exosphere/pkg1/pkg1_boot_config.hpp b/libexosphere/include/exosphere/pkg1/pkg1_boot_config.hpp index 8cef0c82..5d37aa2c 100644 --- a/libexosphere/include/exosphere/pkg1/pkg1_boot_config.hpp +++ b/libexosphere/include/exosphere/pkg1/pkg1_boot_config.hpp @@ -122,6 +122,10 @@ namespace ams::pkg1 { constexpr bool IsProgramVerificationDisabled() const { return (this->flags1[0] & (1 << 0)) != 0; } + + constexpr void SetPackage2Decrypted(bool decrypted) { + this->flags |= decrypted ? 0x3 : 0x0; + } }; static_assert(util::is_pod::value); static_assert(sizeof(BootConfigSignedData) == 0x100); diff --git a/libexosphere/include/exosphere/pkg2.hpp b/libexosphere/include/exosphere/pkg2.hpp new file mode 100644 index 00000000..8df2bc65 --- /dev/null +++ b/libexosphere/include/exosphere/pkg2.hpp @@ -0,0 +1,81 @@ +/* + * 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 . + */ +#pragma once +#include + +namespace ams::pkg2 { + + constexpr inline size_t Package2SizeMax = 8_MB - 16_KB; + constexpr inline size_t SegmentAlignment = 4; + + constexpr inline int SegmentCount = 3; + + constexpr inline int MinimumValidDataVersion = 0; /* We allow older package2 to load; this value is currently 0x10 in Nintendo's code. */ + constexpr inline int CurrentBootloaderVersion = 0xD; + + struct Package2Meta { + using Magic = util::FourCC<'P','K','2','1'>; + + u32 package2_size; + u8 key_generation; + u8 header_iv_remainder[11]; + u8 segment_iv[SegmentCount][0x10]; + u8 padding_40[0x10]; + u8 magic[4]; + u32 entrypoint; + u8 padding_58[4]; + u8 package2_version; + u8 bootloader_version; + u8 padding_5E[2]; + u32 segment_sizes[SegmentCount]; + u8 padding_6C[4]; + u32 segment_offsets[SegmentCount]; + u8 padding_7C[4]; + u8 segment_hashes[SegmentCount][crypto::Sha256Generator::HashSize]; + u8 padding_E0[0x20]; + + private: + static ALWAYS_INLINE u32 ReadWord(const void *ptr, int offset) { + return util::LoadLittleEndian(reinterpret_cast(reinterpret_cast(ptr) + offset)); + } + public: + ALWAYS_INLINE u8 GetKeyGeneration() const { + return std::min(0, (this->key_generation ^ this->header_iv_remainder[1] ^ this->header_iv_remainder[2]) - 1); + } + + ALWAYS_INLINE u32 GetSize() const { + return this->package2_size ^ ReadWord(this->header_iv_remainder, 3) ^ ReadWord(this->header_iv_remainder, 7); + } + }; + static_assert(util::is_pod::value); + static_assert(sizeof(Package2Meta) == 0x100); + + struct Package2Header { + u8 signature[0x100]; + Package2Meta meta; + }; + static_assert(util::is_pod::value); + static_assert(sizeof(Package2Header) == 0x200); + + struct StorageLayout { + u8 boot_config[16_KB]; + Package2Header package2_header; + u8 data[Package2SizeMax - sizeof(Package2Header)]; + }; + static_assert(util::is_pod::value); + static_assert(sizeof(StorageLayout) == 8_MB); + +} diff --git a/libexosphere/include/exosphere/se/se_aes.hpp b/libexosphere/include/exosphere/se/se_aes.hpp index c2873f11..a9bf3e67 100644 --- a/libexosphere/include/exosphere/se/se_aes.hpp +++ b/libexosphere/include/exosphere/se/se_aes.hpp @@ -33,4 +33,6 @@ namespace ams::se { void EncryptAes128(void *dst, size_t dst_size, int slot, const void *src, size_t src_size); void DecryptAes128(void *dst, size_t dst_size, int slot, const void *src, size_t src_size); -} \ No newline at end of file + void ComputeAes128Ctr(void *dst, size_t dst_size, int slot, const void *src, size_t src_size, const void *iv, size_t iv_size); + +} diff --git a/libexosphere/include/exosphere/secmon/secmon_memory_layout.hpp b/libexosphere/include/exosphere/secmon/secmon_memory_layout.hpp index d521efad..34e4be52 100644 --- a/libexosphere/include/exosphere/secmon/secmon_memory_layout.hpp +++ b/libexosphere/include/exosphere/secmon/secmon_memory_layout.hpp @@ -80,6 +80,9 @@ namespace ams::secmon { constexpr inline const MemoryRegion MemoryRegionDramDefaultKernelCarveout = MemoryRegion(UINT64_C(0x80060000), UINT64_C(0x1FFE0000)); static_assert(MemoryRegionDram.Contains(MemoryRegionDramDefaultKernelCarveout)); + constexpr inline const MemoryRegion MemoryRegionDramPackage2 = MemoryRegion(UINT64_C(0xA9800000), UINT64_C(0x07FC0000)); + static_assert(MemoryRegionDram.Contains(MemoryRegionDramPackage2)); + constexpr inline const MemoryRegion MemoryRegionPhysicalIram = MemoryRegion(UINT64_C(0x40000000), 0x40000); constexpr inline const MemoryRegion MemoryRegionPhysicalTzram = MemoryRegion(UINT64_C(0x7C010000), 0x10000); static_assert(MemoryRegionPhysical.Contains(MemoryRegionPhysicalIram)); diff --git a/libexosphere/source/se/se_aes.cpp b/libexosphere/source/se/se_aes.cpp index f9aac61b..138e18cf 100644 --- a/libexosphere/source/se/se_aes.cpp +++ b/libexosphere/source/se/se_aes.cpp @@ -33,13 +33,21 @@ namespace ams::se { MemoryInterface_Mc = SE_CRYPTO_CONFIG_MEMIF_MCCIF, }; - constexpr inline u32 AesConfigEcb = reg::Encode(SE_REG_BITS_VALUE(CRYPTO_CONFIG_CTR_CNTN, 0), - SE_REG_BITS_ENUM (CRYPTO_CONFIG_KEYSCH_BYPASS, DISABLE), - SE_REG_BITS_ENUM (CRYPTO_CONFIG_IV_SELECT, ORIGINAL), - SE_REG_BITS_ENUM (CRYPTO_CONFIG_VCTRAM_SEL, MEMORY), - SE_REG_BITS_ENUM (CRYPTO_CONFIG_INPUT_SEL, MEMORY), - SE_REG_BITS_ENUM (CRYPTO_CONFIG_XOR_POS, BYPASS), - SE_REG_BITS_ENUM (CRYPTO_CONFIG_HASH_ENB, DISABLE)); + constexpr inline u32 AesConfigEcb = reg::Encode(SE_REG_BITS_VALUE(CRYPTO_CONFIG_CTR_CNTN, 0), + SE_REG_BITS_ENUM (CRYPTO_CONFIG_KEYSCH_BYPASS, DISABLE), + SE_REG_BITS_ENUM (CRYPTO_CONFIG_IV_SELECT, ORIGINAL), + SE_REG_BITS_ENUM (CRYPTO_CONFIG_VCTRAM_SEL, MEMORY), + SE_REG_BITS_ENUM (CRYPTO_CONFIG_INPUT_SEL, MEMORY), + SE_REG_BITS_ENUM (CRYPTO_CONFIG_XOR_POS, BYPASS), + SE_REG_BITS_ENUM (CRYPTO_CONFIG_HASH_ENB, DISABLE)); + + constexpr inline u32 AesConfigCtr = reg::Encode(SE_REG_BITS_VALUE(CRYPTO_CONFIG_CTR_CNTN, 1), + SE_REG_BITS_ENUM (CRYPTO_CONFIG_KEYSCH_BYPASS, DISABLE), + SE_REG_BITS_ENUM (CRYPTO_CONFIG_IV_SELECT, ORIGINAL), + SE_REG_BITS_ENUM (CRYPTO_CONFIG_VCTRAM_SEL, MEMORY), + SE_REG_BITS_ENUM (CRYPTO_CONFIG_INPUT_SEL, LINEAR_CTR), + SE_REG_BITS_ENUM (CRYPTO_CONFIG_XOR_POS, BOTTOM), + SE_REG_BITS_ENUM (CRYPTO_CONFIG_HASH_ENB, DISABLE)); void SetConfig(volatile SecurityEngineRegisters *SE, bool encrypt, SE_CONFIG_DST dst) { reg::Write(SE->SE_CONFIG, SE_REG_BITS_ENUM (CONFIG_ENC_MODE, AESMODE_KEY128), @@ -69,6 +77,16 @@ namespace ams::se { // reg::ReadWrite(SE->SE_CRYPTO_CONFIG, SE_REG_BITS_VALUE(CRYPTO_CONFIG_MEMIF, memif)); // } + void SetCounter(volatile SecurityEngineRegisters *SE, const void *ctr) { + const u32 *ctr_32 = reinterpret_cast(ctr); + + /* Copy the input ctr to the linear CTR registers. */ + reg::Write(SE->SE_CRYPTO_LINEAR_CTR[0], util::LoadLittleEndian(ctr_32 + 0)); + reg::Write(SE->SE_CRYPTO_LINEAR_CTR[1], util::LoadLittleEndian(ctr_32 + 1)); + reg::Write(SE->SE_CRYPTO_LINEAR_CTR[2], util::LoadLittleEndian(ctr_32 + 2)); + reg::Write(SE->SE_CRYPTO_LINEAR_CTR[3], util::LoadLittleEndian(ctr_32 + 3)); + } + void SetEncryptedAesKey(int dst_slot, int kek_slot, const void *key, size_t key_size, AesMode mode) { AMS_ABORT_UNLESS(key_size <= AesKeySizeMax); AMS_ABORT_UNLESS(0 <= dst_slot && dst_slot < AesKeySlotCount); @@ -206,4 +224,50 @@ namespace ams::se { ExecuteOperationSingleBlock(SE, dst, dst_size, src, src_size); } + void ComputeAes128Ctr(void *dst, size_t dst_size, int slot, const void *src, size_t src_size, const void *iv, size_t iv_size) { + /* If nothing to do, succeed. */ + if (src_size == 0) { return; } + + /* Validate input. */ + AMS_ABORT_UNLESS(iv_size == AesBlockSize); + AMS_ABORT_UNLESS(0 <= slot && slot < AesKeySlotCount); + + /* Get the engine. */ + auto *SE = GetRegisters(); + + /* Determine how many full blocks we can operate on. */ + const size_t num_blocks = src_size / AesBlockSize; + const size_t aligned_size = num_blocks * AesBlockSize; + const size_t fractional = src_size - aligned_size; + + /* Here Nintendo writes 1 to SE_SPARE. It's unclear why they do this, but we will do so as well. */ + SE->SE_SPARE = 0x1; + + /* Configure for AES-CTR encryption/decryption to memory. */ + SetConfig(SE, true, SE_CONFIG_DST_MEMORY); + SetAesConfig(SE, slot, true, AesConfigCtr); + + /* Set the counter. */ + SetCounter(SE, iv); + + /* Process as many aligned blocks as we can. */ + if (aligned_size > 0) { + /* Configure the engine to process the right number of blocks. */ + SetBlockCount(SE, num_blocks); + + /* Execute the operation. */ + ExecuteOperation(SE, SE_OPERATION_OP_START, dst, dst_size, src, aligned_size); + + /* Synchronize around this point. */ + hw::DataSynchronizationBarrierInnerShareable(); + } + + /* Process a single block to output. */ + if (fractional > 0 && dst_size > aligned_size) { + const size_t copy_size = std::min(fractional, dst_size - aligned_size); + + ExecuteOperationSingleBlock(SE, static_cast(dst) + aligned_size, copy_size, static_cast(src) + aligned_size, fractional); + } + } + }