From 02a554fd6a2dbe0187206683fb4c65c40ed00a04 Mon Sep 17 00:00:00 2001 From: GaryOderNichts <12049776+GaryOderNichts@users.noreply.github.com> Date: Sun, 18 Aug 2024 19:25:57 +0200 Subject: [PATCH] Add boot1 payload loading (#53) --- ios_mcp/imports.sym | 3 + ios_mcp/source/imports.h | 7 +- ios_mcp/source/menu.c | 4 + ios_mcp/source/options/LoadBoot1Payload.c | 369 ++++++++++++++++++++++ ios_mcp/source/options/LoadBoot1Payload.h | 5 + ios_mcp/source/options/options.h | 1 + ios_mcp/source/pm.c | 15 + ios_mcp/source/pm.h | 3 + ios_mcp/source/utils.c | 18 ++ ios_mcp/source/utils.h | 3 + 10 files changed, 426 insertions(+), 2 deletions(-) create mode 100644 ios_mcp/source/options/LoadBoot1Payload.c create mode 100644 ios_mcp/source/options/LoadBoot1Payload.h create mode 100644 ios_mcp/source/pm.c create mode 100644 ios_mcp/source/pm.h diff --git a/ios_mcp/imports.sym b/ios_mcp/imports.sym index bd95d4e..f28c51f 100644 --- a/ios_mcp/imports.sym +++ b/ios_mcp/imports.sym @@ -37,6 +37,8 @@ IOS_RestartTimer = 0x0505688c; IOS_StopTimer = 0x05056894; IOS_DestroyTimer = 0x0505689c; +IOS_GetAbsTime64 = 0x050568d4; + IOS_CheckDebugMode = 0x050568ec; IOS_ReadOTP = 0x050568fc; @@ -72,3 +74,4 @@ IOSC_ImportSecretKey = 0x05053504; IOSC_ImportPublicKey = 0x050532c8; setDefaultTitleId = 0x0510d984|1; +encryptPrsh = 0x0500a610|1; diff --git a/ios_mcp/source/imports.h b/ios_mcp/source/imports.h index 7700a42..3baec66 100644 --- a/ios_mcp/source/imports.h +++ b/ios_mcp/source/imports.h @@ -35,6 +35,7 @@ typedef struct __attribute__((__packed__)) { } UCSysConfig_t; int LONG_CALL setDefaultTitleId(uint64_t tid); +int LONG_CALL encryptPrsh(uint32_t addr, uint32_t size, void* ivBuf, uint32_t ivSize); typedef enum BSPHardwareVersions { BSP_HARDWARE_VERSION_UNKNOWN = 0x00000000, @@ -105,6 +106,8 @@ int IOS_RestartTimer(int timerid, uint32_t delay, uint32_t period); int IOS_StopTimer(int timerid); int IOS_DestroyTimer(int timerid); +int IOS_GetAbsTime64(uint64_t* time); + int IOS_CheckDebugMode(void); int IOS_ReadOTP(int index, void* buffer, uint32_t size); @@ -147,8 +150,8 @@ int IOSC_DeleteObject(int keyHandle); int IOSC_GenerateRand(uint8_t* randBytes, uint32_t numBytes); int IOSC_ImportSecretKey(IOSCSecretKeyHandle importedHandle, IOSCSecretKeyHandle verifyHandle, IOSCSecretKeyHandle decryptHandle, IOSCSecretKeySecurity flag, uint8_t* signbuffer, uint32_t signbufferSize, uint8_t* ivData, uint32_t ivSize, uint8_t* keybuffer, uint32_t keybufferSize); int IOSC_ImportPublicKey(uint8_t* publicKeyData, uint32_t dataSize, uint8_t* exponent, uint32_t exponentSize, IOSCPublicKeyHandle publicKeyHandle); -int IOSC_Decrypt(IOSCSecretKeyHandle decryptHandle, uint8_t* ivData, uint32_t ivSize, uint8_t* inputData, uint32_t inputSize, uint8_t* outputData, uint32_t outputSize); -int IOSC_Encrypt(IOSCSecretKeyHandle encryptHandle, uint8_t* ivData, uint32_t ivSize, uint8_t* inputData, uint32_t inputSize, uint8_t* outputData, uint32_t outputSize); +int IOSC_Decrypt(IOSCSecretKeyHandle decryptHandle, void* ivData, uint32_t ivSize, void* inputData, uint32_t inputSize, void* outputData, uint32_t outputSize); +int IOSC_Encrypt(IOSCSecretKeyHandle encryptHandle, void* ivData, uint32_t ivSize, void* inputData, uint32_t inputSize, void* outputData, uint32_t outputSize); #define WUP_CERT_SIGTYPE_ECC_SHA1 0x00010002 /* ECC with SHA-1 */ #define WUP_CERT_SIGTYPE_ECC_SHA256 0x00010005 /* ECC with SHA-256 */ diff --git a/ios_mcp/source/menu.c b/ios_mcp/source/menu.c index ff3d77e..922390a 100644 --- a/ios_mcp/source/menu.c +++ b/ios_mcp/source/menu.c @@ -49,6 +49,7 @@ static const Menu mainMenuOptions[] = { {"Debug System Region", {.callback = option_DebugSystemRegion}}, {"System Information", {.callback = option_SystemInformation}}, {"Submit System Data", {.callback = option_SubmitSystemData}}, + {"Load BOOT1 payload", {.callback = option_LoadBoot1Payload}}, {"Shutdown", {.callback = option_Shutdown}}, }; @@ -406,6 +407,9 @@ int menuThread(void* arg) // set LED to purple setNotificationLED(NOTIF_LED_PURPLE, 0); + // Check to see if we should autoboot + handleBoot1Autoboot(); + int selected = 0; while (1) { selected = drawMenu("Wii U Recovery Menu v" VERSION_STRING " by GaryOderNichts", diff --git a/ios_mcp/source/options/LoadBoot1Payload.c b/ios_mcp/source/options/LoadBoot1Payload.c new file mode 100644 index 0000000..8a7c1d9 --- /dev/null +++ b/ios_mcp/source/options/LoadBoot1Payload.c @@ -0,0 +1,369 @@ +#include "LoadBoot1Payload.h" + +#include "menu.h" +#include "gfx.h" +#include "imports.h" +#include "utils.h" +#include "fsa.h" + +#include +#include + +typedef struct { + uint16_t version; + uint16_t sector; + uint16_t reserved[4]; + uint32_t crc; +} Boot1Params; +static_assert(sizeof(Boot1Params) == 0x10); + +Boot1Params boot1Params[2] __attribute__ ((aligned (0x10))); + +static int preparePrshHax(uint32_t bootInfoOffset) +{ + // This code was taken from hexkyz' hexFW: + // + + // RAM vars + const uint32_t ramStartAddr = 0x10000000; + const uint32_t ramTestBufSize = 0x400; + + // PRSH vars + const uint32_t prshHdrOffset = ramStartAddr + ramTestBufSize + 0x5654; + const uint32_t prshHdrSize = 0x1c; + + // boot_info vars + const uint32_t bootInfoNameAddr = prshHdrOffset + prshHdrSize; + const uint32_t bootInfoNameSize = 0x100; + const uint32_t bootInfoPtrAddr = bootInfoNameAddr + bootInfoNameSize; + + // Calculate PRSH checksum + uint32_t checksumOld = 0; + uint32_t wordCounter = 0; + + while (wordCounter < 0x20d) { + checksumOld ^= *(uint32_t *) (prshHdrOffset + 0x04 + wordCounter * 0x04); + wordCounter++; + } + + // Change boot_info to point inside boot1 memory + *(uint32_t *) bootInfoPtrAddr = 0x0d400200 + bootInfoOffset; + + // Re-calculate PRSH checksum + uint32_t checksum = 0; + wordCounter = 0; + + while (wordCounter < 0x20d) { + checksum ^= *(uint32_t *) (prshHdrOffset + 0x04 + wordCounter * 0x04); + wordCounter++; + } + + // Update checksum + *(uint32_t *) prshHdrOffset = checksum; + + // Copy PRSH IV from IOS-MCP + void *prshIvBuf = IOS_HeapAlloc(CROSS_PROCESS_HEAP_ID, 0x10); + if (!prshIvBuf) { + return -1; + } + + memcpy(prshIvBuf, (void*) 0x050677c0, 0x10); + + // Encrypt PRSH + int res = encryptPrsh(0x10000400, 0x7c00, prshIvBuf, 0x10); + + // Free PRSH IV buffer + IOS_HeapFree(CROSS_PROCESS_HEAP_ID, prshIvBuf); + + // Flush cache + IOS_FlushDCache((void*) 0x10000400, 0x7c00); + + return res; +} + +static int decryptBoot1Params(int index) +{ + uint8_t iv[0x10]; + memset(iv, 0, sizeof(iv)); + + Boot1Params* params = &boot1Params[index]; + IOS_FlushDCache(params, sizeof(*params)); + return IOSC_Decrypt(0x7, iv, sizeof(iv), params, sizeof(*params), params, sizeof(*params)); +} + +static int readBoot1Params(void) +{ + int ret; + + ret = EEPROM_Read(0xe8, 8, (uint16_t*)&boot1Params[0]); + if (ret != 0) { + return ret; + } + + ret = EEPROM_Read(0xf0, 8, (uint16_t*)&boot1Params[1]); + if (ret != 0) { + return ret; + } + + decryptBoot1Params(0); + decryptBoot1Params(1); + return 1; +} + +static int determineActiveBoot1Slot(void) +{ + int activeSlot; + + // Calculate crcs + uint32_t validMask = 0; + for (int i = 0; i < 2; i++) { + if (~crc32(0xffffffff, &boot1Params[i], 0xc) == boot1Params[i].crc) { + validMask |= 1 << i; + } + } + + if (validMask == 0b01) { + activeSlot = 0; + } else if (validMask == 0b10) { + activeSlot = 1; + } else if (validMask == 0b11) { + // Both versions are valid, check for newer version + activeSlot = 0; + if (boot1Params[1].version > boot1Params[0].version) { + activeSlot = 1; + } + } else { + activeSlot = -1; + } + + return activeSlot; +} + +static void loadBoot1Payload(uint32_t index, const char* filePath) +{ + gfx_printf(16, index, 0, "Checking BOOT1 version..."); + index += CHAR_SIZE_DRC_Y + 4; + + if (!readBoot1Params()) { + gfx_set_font_color(COLOR_ERROR); + gfx_printf(16, index, 0, "Failed to read boot1 parameters"); + waitButtonInput(); + return; + } + + int activeSlot; + if ((activeSlot = determineActiveBoot1Slot()) < 0) { + gfx_set_font_color(COLOR_ERROR); + gfx_printf(16, index, 0, "Failed to determine active boot1 version"); + waitButtonInput(); + return; + } + + uint16_t activeVersion = boot1Params[activeSlot].version; + gfx_printf(16, index, 0, "Active BOOT1 version: %d (slot %d)", activeVersion, activeSlot); + index += CHAR_SIZE_DRC_Y + 4; + + // TODO support more versions / check for dev/retail + uint32_t boootInfoOffset; + switch (activeVersion) { + case 8377: + boootInfoOffset = 0xaa6d; + break; + default: + gfx_set_font_color(COLOR_ERROR); + gfx_printf(16, index, 0, "Unsupported BOOT1 version: %d", activeVersion); + waitButtonInput(); + return; + } + + int fileHandle; + int res = FSA_OpenFile(fsaHandle, filePath, "r", &fileHandle); + if (res < 0) { + gfx_set_font_color(COLOR_ERROR); + gfx_printf(16, index, 0, "Failed to open boot1.img: %x", res); + waitButtonInput(); + return; + } + + uint32_t* dataBuffer = (uint32_t*) IOS_HeapAllocAligned(CROSS_PROCESS_HEAP_ID, 0x200, 0x40); + if (!dataBuffer) { + gfx_set_font_color(COLOR_ERROR); + gfx_printf(16, index, 0, "Failed to allocate data buffer!"); + waitButtonInput(); + FSA_CloseFile(fsaHandle, fileHandle); + return; + } + + // read the ancast header + res = FSA_ReadFile(fsaHandle, dataBuffer, 1, 0x200, fileHandle, 0); + if (res != 0x200) { + gfx_set_font_color(COLOR_ERROR); + gfx_printf(16, index, 0, "Failed to read ancast header: %x", res); + waitButtonInput(); + IOS_HeapFree(CROSS_PROCESS_HEAP_ID, dataBuffer); + FSA_CloseFile(fsaHandle, fileHandle); + return; + } + + // Check ancast magic + if (dataBuffer[0] != 0xEFA282D9) { + gfx_set_font_color(COLOR_ERROR); + gfx_printf(16, index, 0, "Invalid ancast header magic: %08lx", dataBuffer[0]); + waitButtonInput(); + IOS_HeapFree(CROSS_PROCESS_HEAP_ID, dataBuffer); + FSA_CloseFile(fsaHandle, fileHandle); + return; + } + + // Check unencrypted flag + if (((dataBuffer[0x68] >> 16) & 1) == 0) { + gfx_set_font_color(COLOR_ERROR); + gfx_printf(16, index, 0, "Ancast image is encrypted!"); + waitButtonInput(); + IOS_HeapFree(CROSS_PROCESS_HEAP_ID, dataBuffer); + FSA_CloseFile(fsaHandle, fileHandle); + return; + } + + uint32_t bodySize = dataBuffer[0x6B]; + + // read and write payload to mem1 + uint32_t payloadOffset = 0x00000050; + int bytesRead = 0; + while ((res = FSA_ReadFile(fsaHandle, dataBuffer, 1, 0x40, fileHandle, 0)) > 0) { + bytesRead += res; + for (int i = 0; i < res; i += 4) { + kernWrite32(payloadOffset, dataBuffer[i/4]); + payloadOffset += 4; + } + } + + gfx_printf(16, index, 0, "Read %d / %ld bytes", bytesRead, bodySize); + index += CHAR_SIZE_DRC_Y + 4; + + IOS_HeapFree(CROSS_PROCESS_HEAP_ID, dataBuffer); + FSA_CloseFile(fsaHandle, fileHandle); + + if (res < 0) { + gfx_set_font_color(COLOR_ERROR); + gfx_printf(16, index, 0, "Failed to read boot1.img: %x", res); + waitButtonInput(); + return; + } + + // Check body size + if (bytesRead < bodySize) { + gfx_set_font_color(COLOR_ERROR); + gfx_printf(16, index, 0, "Failed to read ancast body (%d / %ld bytes)", bytesRead, bodySize); + waitButtonInput(); + return; + } + + res = preparePrshHax(boootInfoOffset); + if (res < 0) { + gfx_set_font_color(COLOR_ERROR); + gfx_printf(16, index, 0, "Failed to prepare prshhax: %x", res); + waitButtonInput(); + return; + } + + // Setup branch to MEM1 payload + kernWrite32(0x00000000, 0xEA000012); // b #0x50 + kernWrite32(0x00000004, 0xDEADC0DE); + kernWrite32(0x00000008, 0xDEADC0DE); + + IOS_Shutdown(1); + + // we're at the point of no return + while (1) + ; +} + +void option_LoadBoot1Payload(void) +{ + static const Menu boot1ControlOptions[] = { + {"Back", {0} }, + {"Load", {0} }, + }; + + gfx_clear(COLOR_BACKGROUND); + + uint32_t index = 16 + 8 + 2 + 8; + gfx_set_font_color(COLOR_PRIMARY); + + index = gfx_printf(16, index, GfxPrintFlag_ClearBG, "This will load a payload from the SD Card named boot1.img\n" + "and execute it from within boot1.\n\n" + "Do you want to continue?"); + index += CHAR_SIZE_DRC_Y + 4; + + int selected = drawMenu("Load BOOT1 Payload", + boot1ControlOptions, ARRAY_SIZE(boot1ControlOptions), 0, + MenuFlag_NoClearScreen, 16, index); + index += (CHAR_SIZE_DRC_Y + 4) * ARRAY_SIZE(boot1ControlOptions); + + if (selected == 0) + return; + + loadBoot1Payload(index, "/vol/storage_recovsd/boot1.img"); +} + +void handleBoot1Autoboot(void) +{ + static const char* autobootFile = "/vol/storage_recovsd/boot1now.img"; + + // Check if the autoboot file exists + FSStat stat; + if (FSA_GetStat(fsaHandle, autobootFile, &stat) < 0) { + return; + } + + // Make sure it's not a directory + if (stat.flags & DIR_ENTRY_IS_DIRECTORY) { + return; + } + + gfx_clear(COLOR_BACKGROUND); + drawTopBar("Autobooting..."); + + uint32_t index = 16 + 8 + 2 + 8; + gfx_set_font_color(COLOR_PRIMARY); + + gfx_printf(16, index, 0, "Detected boot1now.img"); + index += CHAR_SIZE_DRC_Y + 4; + + uint64_t startTime; + IOS_GetAbsTime64(&startTime); + + const int timeoutSecs = 5; + int lastDraw = -1; + uint8_t cur_flag = 0; + uint8_t flag = 0; + while (1) { + SMC_ReadSystemEventFlag(&flag); + if (cur_flag != flag) { + if ((flag & SYSTEM_EVENT_FLAG_EJECT_BUTTON) || (flag & SYSTEM_EVENT_FLAG_POWER_BUTTON)) { + return; + } + + cur_flag = flag; + } + + uint64_t curTime; + IOS_GetAbsTime64(&curTime); + uint32_t secondsPassed = (uint32_t) ((curTime - startTime) / 1000 / 1000); + + // Check if we reached the timeout + if (secondsPassed > timeoutSecs) { + break; + } + + // Draw if seconds changed + if (lastDraw != secondsPassed) { + gfx_printf(16, index, GfxPrintFlag_ClearBG, "Autobooting boot1now.img in %ld seconds...\nPress any button to cancel", timeoutSecs - secondsPassed); + lastDraw = secondsPassed; + } + } + index += (CHAR_SIZE_DRC_Y + 4) * 2; + + loadBoot1Payload(index, autobootFile); +} diff --git a/ios_mcp/source/options/LoadBoot1Payload.h b/ios_mcp/source/options/LoadBoot1Payload.h new file mode 100644 index 0000000..485a736 --- /dev/null +++ b/ios_mcp/source/options/LoadBoot1Payload.h @@ -0,0 +1,5 @@ +#pragma once + +void option_LoadBoot1Payload(void); + +void handleBoot1Autoboot(void); diff --git a/ios_mcp/source/options/options.h b/ios_mcp/source/options/options.h index 688ccc3..3e5e709 100644 --- a/ios_mcp/source/options/options.h +++ b/ios_mcp/source/options/options.h @@ -5,6 +5,7 @@ #include "DumpSyslogs.h" #include "EditParental.h" #include "InstallWUP.h" +#include "LoadBoot1Payload.h" #include "LoadNetConf.h" #include "PairDRC.h" #include "SetColdbootTitle.h" diff --git a/ios_mcp/source/pm.c b/ios_mcp/source/pm.c new file mode 100644 index 0000000..e57a9fd --- /dev/null +++ b/ios_mcp/source/pm.c @@ -0,0 +1,15 @@ +#include "pm.h" +#include "imports.h" + +int PM_Restart(void) +{ + int fd = IOS_Open("/dev/pm",0); + if (fd < 0) { + return fd; + } + + int res = IOS_Ioctl(fd, 0xe3, NULL, 0, NULL, 0); + IOS_Close(fd); + + return res; +} diff --git a/ios_mcp/source/pm.h b/ios_mcp/source/pm.h new file mode 100644 index 0000000..49b62ba --- /dev/null +++ b/ios_mcp/source/pm.h @@ -0,0 +1,3 @@ +#pragma once + +int PM_Restart(void); diff --git a/ios_mcp/source/utils.c b/ios_mcp/source/utils.c index 07d93ad..b972a9c 100644 --- a/ios_mcp/source/utils.c +++ b/ios_mcp/source/utils.c @@ -201,3 +201,21 @@ void setNotificationLED(uint8_t mask, uint32_t duration) currentLedMask = mask; } } + +uint32_t crc32(uint32_t seed, const void* data, size_t len) +{ + uint32_t crc = seed; + const uint8_t* src = data; + uint32_t mult; + int i; + + while (len--) { + crc ^= *src++; + for (i = 0; i < 8; i++) { + mult = (crc & 1) ? 0xedb88320 : 0; + crc = (crc >> 1) ^ mult; + } + } + + return crc; +} diff --git a/ios_mcp/source/utils.h b/ios_mcp/source/utils.h index 03a7aac..d785171 100644 --- a/ios_mcp/source/utils.h +++ b/ios_mcp/source/utils.h @@ -1,4 +1,5 @@ #pragma once +#include #include #include @@ -86,3 +87,5 @@ int SMC_ReadSystemEventFlag(uint8_t* flag); int SMC_SetODDPower(int power); void setNotificationLED(uint8_t mask, uint32_t duration); + +uint32_t crc32(uint32_t seed, const void* data, size_t len);