From e5106ffa2cf1d35dc37fb4c1b8516fc46e27de00 Mon Sep 17 00:00:00 2001 From: Michael Scire Date: Fri, 3 Sep 2021 18:34:20 -0700 Subject: [PATCH] fusee_cpp: implement package2 rebuild/kip patching --- emummc/Makefile | 5 +- .../program/source/boot/secmon_package2.cpp | 7 +- fusee_cpp/program/source/fusee_main.cpp | 3 + .../program/source/fusee_overlay_manager.cpp | 15 +- .../program/source/fusee_overlay_manager.hpp | 1 + .../source/fusee_secondary_archive.hpp | 3 +- .../program/source/fusee_setup_horizon.cpp | 3 +- .../program/source/fusee_stratosphere.cpp | 277 +++++++++++++++++- fusee_cpp/program/split_bin.py | 33 ++- .../exosphere/secmon/secmon_memory_layout.hpp | 2 +- 10 files changed, 330 insertions(+), 19 deletions(-) diff --git a/emummc/Makefile b/emummc/Makefile index fbfcc6063..7af45eaac 100644 --- a/emummc/Makefile +++ b/emummc/Makefile @@ -98,7 +98,10 @@ else DEPENDS := $(OFILES:.o=.d) -all : $(OUTPUT).kip +all : $(OUTPUT)_unpacked.kip + +$(OUTPUT)_unpacked.kip : $(OUTPUT).kip + @hactool -t kip --uncompressed=$(OUTPUT)_unpacked.kip $(OUTPUT).kip $(OUTPUT).kip : $(OUTPUT).elf diff --git a/exosphere/program/source/boot/secmon_package2.cpp b/exosphere/program/source/boot/secmon_package2.cpp index 3e64a0304..516b83573 100644 --- a/exosphere/program/source/boot/secmon_package2.cpp +++ b/exosphere/program/source/boot/secmon_package2.cpp @@ -154,8 +154,11 @@ namespace ams::secmon::boot { bool VerifyPackage2Payloads(const pkg2::Package2Meta &meta, uintptr_t payload_address) { /* Verify hashes match for all payloads. */ for (int i = 0; i < pkg2::PayloadCount; ++i) { - if (!VerifyHash(meta.payload_hashes[i], payload_address, meta.payload_sizes[i])) { - return false; + /* Allow all-zero bytes to match any payload. */ + if (!(meta.payload_hashes[i][0] == 0 && std::memcmp(meta.payload_hashes[i] + 0, meta.payload_hashes[i] + 1, sizeof(meta.payload_hashes[i]) - 1) == 0)) { + if (!VerifyHash(meta.payload_hashes[i], payload_address, meta.payload_sizes[i])) { + return false; + } } payload_address += meta.payload_sizes[i]; diff --git a/fusee_cpp/program/source/fusee_main.cpp b/fusee_cpp/program/source/fusee_main.cpp index 6fbbd07d9..451c0f610 100644 --- a/fusee_cpp/program/source/fusee_main.cpp +++ b/fusee_cpp/program/source/fusee_main.cpp @@ -129,6 +129,9 @@ namespace ams::nxboot { /* Restore memory clock rate. */ RestoreMemoryClockRate(); + /* Restore secure monitor code. */ + RestoreSecureMonitorOverlay(); + /* Finalize display. */ FinalizeDisplay(); diff --git a/fusee_cpp/program/source/fusee_overlay_manager.cpp b/fusee_cpp/program/source/fusee_overlay_manager.cpp index e4c46ba46..a50a215e0 100644 --- a/fusee_cpp/program/source/fusee_overlay_manager.cpp +++ b/fusee_cpp/program/source/fusee_overlay_manager.cpp @@ -22,6 +22,8 @@ namespace ams::nxboot { namespace { + constinit u8 g_secmon_debug_storage[secmon::MemoryRegionPhysicalIramSecureMonitorDebug.GetSize()]; + ALWAYS_INLINE void *GetOverlayDestination() { return reinterpret_cast(0x4002C000); } @@ -64,7 +66,7 @@ namespace ams::nxboot { /* NOTE: Erista does not do memory clock restoration. */ /* std::memcpy(const_cast(GetSecondaryArchive().ovl_mtc_erista), GetOverlayDestination(), sizeof(SecondaryArchive{}.ovl_mtc_erista)); */ } else /* if (fuse::GetSocType() == fuse::SocType_Mariko) */ { - std::memcpy(const_cast(GetSecondaryArchive().ovl_mtc_mariko), GetOverlayDestination(), sizeof(SecondaryArchive{}.ovl_mtc_mariko)); + std::memcpy(const_cast(GetSecondaryArchive().ovl_mtc_mariko), GetOverlayDestination(), sizeof(SecondaryArchive{}.ovl_mtc_mariko) - 0x2000); } } @@ -73,7 +75,16 @@ namespace ams::nxboot { /* NOTE: Erista does not do memory clock restoration. */ /* std::memcpy(GetOverlayDestination(), GetSecondaryArchive().ovl_mtc_erista, sizeof(SecondaryArchive{}.ovl_mtc_erista)); */ } else /* if (fuse::GetSocType() == fuse::SocType_Mariko) */ { - std::memcpy(GetOverlayDestination(), GetSecondaryArchive().ovl_mtc_mariko, sizeof(SecondaryArchive{}.ovl_mtc_mariko)); + std::memcpy(g_secmon_debug_storage, secmon::MemoryRegionPhysicalIramSecureMonitorDebug.GetPointer(), sizeof(g_secmon_debug_storage)); + std::memcpy(GetOverlayDestination(), GetSecondaryArchive().ovl_mtc_mariko, sizeof(SecondaryArchive{}.ovl_mtc_mariko) - 0x2000); + } + } + + void RestoreSecureMonitorOverlay() { + if (fuse::GetSocType() == fuse::SocType_Erista) { + /* NOTE: Erista does not do memory clock restoration. */ + } else /* if (fuse::GetSocType() == fuse::SocType_Mariko) */ { + std::memcpy(secmon::MemoryRegionPhysicalIramSecureMonitorDebug.GetPointer(), g_secmon_debug_storage, sizeof(g_secmon_debug_storage)); } } diff --git a/fusee_cpp/program/source/fusee_overlay_manager.hpp b/fusee_cpp/program/source/fusee_overlay_manager.hpp index 2d842f80f..f481e5424 100644 --- a/fusee_cpp/program/source/fusee_overlay_manager.hpp +++ b/fusee_cpp/program/source/fusee_overlay_manager.hpp @@ -28,5 +28,6 @@ namespace ams::nxboot { void SaveMemoryTrainingOverlay(); void RestoreMemoryTrainingOverlay(); + void RestoreSecureMonitorOverlay(); } diff --git a/fusee_cpp/program/source/fusee_secondary_archive.hpp b/fusee_cpp/program/source/fusee_secondary_archive.hpp index b0f356978..1fef806fa 100644 --- a/fusee_cpp/program/source/fusee_secondary_archive.hpp +++ b/fusee_cpp/program/source/fusee_secondary_archive.hpp @@ -47,8 +47,9 @@ namespace ams::nxboot { u32 reserved0; /* Previously entrypoint. */ u32 metadata_offset; u32 flags; + u32 meso_size; u32 num_kips; - u32 reserved1[4]; + u32 reserved1[3]; u32 magic; u32 total_size; u32 reserved2; /* Previously crt0 offset. */ diff --git a/fusee_cpp/program/source/fusee_setup_horizon.cpp b/fusee_cpp/program/source/fusee_setup_horizon.cpp index 54953d50e..55da40c58 100644 --- a/fusee_cpp/program/source/fusee_setup_horizon.cpp +++ b/fusee_cpp/program/source/fusee_setup_horizon.cpp @@ -834,7 +834,8 @@ namespace ams::nxboot { /* Build modified package2. */ RebuildPackage2(target_firmware, emummc_enabled); - WaitForReboot(); + /* Wait for confirmation that exosphere is ready. */ + WaitSecureMonitorState(pkg1::SecureMonitorState_Initialized); } } \ No newline at end of file diff --git a/fusee_cpp/program/source/fusee_stratosphere.cpp b/fusee_cpp/program/source/fusee_stratosphere.cpp index 3ca16df7b..c3f6edee9 100644 --- a/fusee_cpp/program/source/fusee_stratosphere.cpp +++ b/fusee_cpp/program/source/fusee_stratosphere.cpp @@ -85,6 +85,8 @@ namespace ams::nxboot { static_assert(sizeof(InitialProcessMeta) == 0x40); static_assert(alignof(InitialProcessMeta) == 0x10); + constexpr inline const u64 FsProgramId = 0x0100000000000000; + enum FsVersion { FsVersion_1_0_0 = 0, @@ -284,6 +286,11 @@ namespace ams::nxboot { } bool AddInitialProcess(const InitialProcessHeader *kip, const se::Sha256Hash *hash = nullptr) { + /* Check kip magic. */ + if (kip->magic != InitialProcessHeader::Magic) { + ShowFatalError("KIP seems corrupted!\n"); + } + /* Handle the initial case. */ if (g_initial_process_binary_size == 0) { AddInitialProcessImpl(std::addressof(g_initial_process_meta), kip, hash); @@ -591,6 +598,60 @@ namespace ams::nxboot { } } + struct BlzSegmentFlags { + using Offset = util::BitPack16::Field<0, 12, u32>; + using Size = util::BitPack16::Field; + }; + + void BlzUncompress(void *_end) { + /* Parse the footer, endian agnostic. */ + static_assert(sizeof(u32) == 4); + static_assert(sizeof(u16) == 2); + static_assert(sizeof(u8) == 1); + + u8 *end = static_cast(_end); + const u32 total_size = (end[-12] << 0) | (end[-11] << 8) | (end[-10] << 16) | (end[- 9] << 24); + const u32 footer_size = (end[- 8] << 0) | (end[- 7] << 8) | (end[- 6] << 16) | (end[- 5] << 24); + const u32 additional_size = (end[- 4] << 0) | (end[- 3] << 8) | (end[- 2] << 16) | (end[- 1] << 24); + + /* Prepare to decompress. */ + u8 *cmp_start = end - total_size; + u32 cmp_ofs = total_size - footer_size; + u32 out_ofs = total_size + additional_size; + + /* Decompress. */ + while (out_ofs) { + u8 control = cmp_start[--cmp_ofs]; + + /* Each bit in the control byte is a flag indicating compressed or not compressed. */ + for (size_t i = 0; i < 8 && out_ofs; ++i, control <<= 1) { + if (control & 0x80) { + /* NOTE: Nintendo does not check if it's possible to decompress. */ + /* As such, we will leave the following as a debug assertion, and not a release assertion. */ + AMS_AUDIT(cmp_ofs >= sizeof(u16)); + cmp_ofs -= sizeof(u16); + + /* Extract segment bounds. */ + const util::BitPack16 seg_flags{static_cast((cmp_start[cmp_ofs] << 0) | (cmp_start[cmp_ofs + 1] << 8))}; + const u32 seg_ofs = seg_flags.Get() + 3; + const u32 seg_size = std::min(seg_flags.Get() + 3, out_ofs); + AMS_AUDIT(out_ofs + seg_ofs <= total_size + additional_size); + + /* Copy the data. */ + out_ofs -= seg_size; + for (size_t j = 0; j < seg_size; j++) { + cmp_start[out_ofs + j] = cmp_start[out_ofs + seg_ofs + j]; + } + } else { + /* NOTE: Nintendo does not check if it's possible to copy. */ + /* As such, we will leave the following as a debug assertion, and not a release assertion. */ + AMS_AUDIT(cmp_ofs >= sizeof(u8)); + cmp_start[--out_ofs] = cmp_start[--cmp_ofs]; + } + } + } + } + void *ReadFile(s64 *out_size, const char *path, size_t align = 0x10) { fs::FileHandle file; if (R_SUCCEEDED(fs::OpenFile(std::addressof(file), path, fs::OpenMode_Read))) { @@ -674,7 +735,6 @@ namespace ams::nxboot { } /* Get meta for FS process. */ - constexpr u64 FsProgramId = 0x0100000000000000; auto *fs_meta = FindInitialProcess(FsProgramId); if (fs_meta == nullptr) { /* Get nintendo header/data. */ @@ -833,7 +893,220 @@ namespace ams::nxboot { } void RebuildPackage2(ams::TargetFirmware target_firmware, bool emummc_enabled) { - /* TODO */ + /* Get the secondary archive. */ + const auto &secondary_archive = GetSecondaryArchive(); + + /* Clear package2 header. */ + auto *package2 = secmon::MemoryRegionDramPackage2.GetPointer(); + std::memset(package2, 0, sizeof(*package2)); + + /* Get payload data pointer. */ + u8 * const payload_data = reinterpret_cast(package2 + 1); + + /* Useful values. */ + constexpr u32 KernelPayloadBase = 0x60000; + + /* Set fields. */ + package2->meta.key_generation = pkg1::KeyGeneration_Current; + std::memcpy(package2->meta.magic, pkg2::Package2Meta::Magic::String, sizeof(package2->meta.magic)); + package2->meta.entrypoint = KernelPayloadBase; + package2->meta.bootloader_version = pkg2::CurrentBootloaderVersion; + package2->meta.package2_version = pkg2::MinimumValidDataVersion; + + /* Load mesosphere. */ + s64 meso_size; + if (void *sd_meso = ReadFile(std::addressof(meso_size), "sdmc:/atmosphere/mesosphere.bin"); sd_meso != nullptr) { + std::memcpy(payload_data, sd_meso, meso_size); + } else { + meso_size = secondary_archive.header.meso_size; + std::memcpy(payload_data, secondary_archive.mesosphere, meso_size); + } + + /* Read emummc, if needed. */ + const InitialProcessHeader *emummc; + s64 emummc_size; + if (emummc_enabled) { + emummc = static_cast(ReadFile(std::addressof(emummc_size), "sdmc:/atmosphere/emummc.kip")); + if (emummc == nullptr) { + emummc = reinterpret_cast(secondary_archive.kips + secondary_archive.header.emummc_meta.offset); + emummc_size = secondary_archive.header.emummc_meta.size; + } + } + + /* Set the embedded ini pointer. */ + std::memcpy(payload_data + 8, std::addressof(meso_size), sizeof(meso_size)); + + /* Get the ini pointer. */ + InitialProcessBinaryHeader * const ini = reinterpret_cast(payload_data + meso_size); + + /* Set ini fields. */ + ini->magic = InitialProcessBinaryHeader::Magic; + ini->num_processes = 0; + ini->reserved = 0; + + /* Iterate all processes. */ + u8 * const dst_kip_start = reinterpret_cast(ini + 1); + u8 * dst_kip_cur = dst_kip_start; + + for (InitialProcessMeta *meta = std::addressof(g_initial_process_meta); meta != nullptr; meta = meta->next) { + /* Get the current kip. */ + const auto *src_kip = meta->kip; + auto *dst_kip = reinterpret_cast(dst_kip_cur); + + /* Copy the kip header */ + std::memcpy(dst_kip, src_kip, sizeof(*src_kip)); + + const u8 *src_kip_data = reinterpret_cast(src_kip + 1); + u8 *dst_kip_data = reinterpret_cast< u8 *>(dst_kip + 1); + + /* If necessary, inject emummc. */ + u32 addl_text_offset = 0; + if (dst_kip->program_id == FsProgramId && emummc_enabled) { + /* Get emummc extents. */ + addl_text_offset = emummc->bss_address + emummc->bss_size; + if ((emummc->flags & 7) || !util::IsAligned(addl_text_offset, 0x1000)) { + ShowFatalError("Invalid emummc kip!\n"); + } + + /* Copy emummc capabilities. */ + { + std::memcpy(dst_kip->capabilities, emummc->capabilities, sizeof(emummc->capabilities)); + + if (target_firmware <= ams::TargetFirmware_1_0_0) { + for (size_t i = 0; i < util::size(dst_kip->capabilities); ++i) { + if (dst_kip->capabilities[i] == 0xFFFFFFFF) { + dst_kip->capabilities[i] = 0x07000E7F; + break; + } + } + } + } + + /* Update section headers. */ + dst_kip->ro_address += addl_text_offset; + dst_kip->rw_address += addl_text_offset; + dst_kip->bss_address += addl_text_offset; + + /* Get emummc sections. */ + const u8 *emummc_data = reinterpret_cast(emummc + 1); + + /* Copy emummc sections. */ + std::memcpy(dst_kip_data + emummc->rx_address, emummc_data, emummc->rx_compressed_size); + std::memcpy(dst_kip_data + emummc->ro_address, emummc_data + emummc->rx_compressed_size, emummc->ro_compressed_size); + std::memcpy(dst_kip_data + emummc->rw_address, emummc_data + emummc->rx_compressed_size + emummc->ro_compressed_size, emummc->rw_compressed_size); + std::memset(dst_kip_data + emummc->bss_address, 0, emummc->bss_size); + + /* Advance. */ + dst_kip_data += addl_text_offset; + } + + /* Prepare to process segments. */ + u8 *dst_rx_data, *dst_ro_data, *dst_rw_data; + + /* Process .text. */ + { + dst_rx_data = dst_kip_data; + + std::memcpy(dst_kip_data, src_kip_data, src_kip->rx_compressed_size); + + /* Uncompress, if necessary. */ + if ((meta->patch_segments & src_kip->flags) & (1 << 0)) { + BlzUncompress(dst_kip_data + dst_kip->rx_compressed_size); + + dst_kip->rx_compressed_size = dst_kip->rx_size; + } + + /* Advance. */ + dst_kip_data += dst_kip->rx_compressed_size; + src_kip_data += src_kip->rx_compressed_size; + + /* Account for potential emummc. */ + dst_kip->rx_size += addl_text_offset; + dst_kip->rx_compressed_size += addl_text_offset; + } + + /* Process .rodata. */ + { + dst_ro_data = dst_kip_data; + + std::memcpy(dst_kip_data, src_kip_data, src_kip->ro_compressed_size); + + /* Uncompress, if necessary. */ + if ((meta->patch_segments & src_kip->flags) & (1 << 1)) { + BlzUncompress(dst_kip_data + dst_kip->ro_compressed_size); + + dst_kip->ro_compressed_size = dst_kip->ro_size; + } + + /* Advance. */ + dst_kip_data += dst_kip->ro_compressed_size; + src_kip_data += src_kip->ro_compressed_size; + } + + /* Process .rwdata. */ + { + dst_rw_data = dst_kip_data; + + std::memcpy(dst_kip_data, src_kip_data, src_kip->rw_compressed_size); + + /* Uncompress, if necessary. */ + if ((meta->patch_segments & src_kip->flags) & (1 << 2)) { + BlzUncompress(dst_kip_data + dst_kip->rw_compressed_size); + + dst_kip->rw_compressed_size = dst_kip->rw_size; + } + + /* Advance. */ + dst_kip_data += dst_kip->rw_compressed_size; + src_kip_data += src_kip->rw_compressed_size; + } + + /* Adjust flags. */ + dst_kip->flags &= ~meta->patch_segments; + + /* Apply patches. */ + for (auto *patch = meta->patches_head; patch != nullptr; patch = patch->next) { + /* Get the destination segment. */ + u8 *patch_dst_segment; + switch (patch->start_segment) { + case 0: patch_dst_segment = dst_rx_data; break; + case 1: patch_dst_segment = dst_ro_data; break; + case 2: patch_dst_segment = dst_rw_data; break; + default: ShowFatalError("Unknown patch segment %" PRIu32 "\n", patch->start_segment); break; + } + + /* Get the destination. */ + u8 * const patch_dst = patch_dst_segment + patch->rel_offset; + + /* Apply the patch. */ + if (patch->is_memset) { + const u8 val = *static_cast(patch->data); + std::memset(patch_dst, val, patch->size); + } else { + std::memcpy(patch_dst, patch->data, patch->size); + } + } + + /* Advance. */ + dst_kip_cur += GetInitialProcessSize(dst_kip); + + /* Increment num kips. */ + ++ini->num_processes; + } + + /* Set INI size. */ + ini->size = sizeof(*ini) + (dst_kip_cur - dst_kip_start); + if (ini->size > 12_MB) { + ShowFatalError("INI is too big! (0x%08" PRIx32 ")\n", ini->size); + } + + /* Set the payload size/offset. */ + package2->meta.payload_offsets[0] = KernelPayloadBase; + package2->meta.payload_sizes[0] = util::AlignUp(meso_size + ini->size, 0x10); + + + /* Set total size. */ + package2->meta.package2_size = sizeof(*package2) + package2->meta.payload_sizes[0]; } } diff --git a/fusee_cpp/program/split_bin.py b/fusee_cpp/program/split_bin.py index 54cf41815..f2117c455 100644 --- a/fusee_cpp/program/split_bin.py +++ b/fusee_cpp/program/split_bin.py @@ -23,7 +23,7 @@ def get_overlay(program, i): KIP_NAMES = ['Loader', 'NCM', 'ProcessManager', 'sm', 'boot', 'spl', 'ams_mitm'] def get_kips(): - emummc = read_file('../../../../emummc/emummc.kip') + emummc = read_file('../../../../emummc/emummc_unpacked.kip') loader = read_file('../../../../stratosphere/loader/loader.kip') ncm = read_file('../../../../stratosphere/ncm/ncm.kip') pm = read_file('../../../../stratosphere/pm/pm.kip') @@ -45,11 +45,11 @@ def write_kip_meta(f, kip, ofs): # Write program id f.write(kip[0x10:0x18]) # Write offset, size - f.write(pk('