From 71c1c21234bfe52c20c0da56c075232f5fca39e5 Mon Sep 17 00:00:00 2001 From: Edoardo Lolletti Date: Thu, 25 Apr 2024 22:42:38 +0200 Subject: [PATCH] Add hash and size check for known unlaunch installer versions Also check the installer to be valid at startup, rather than once the install process has started, so that the install option can be properly grayed out --- arm9/Makefile | 2 +- arm9/src/main.c | 5 +- arm9/src/main.h | 8 +++ arm9/src/menu.h | 8 +++ arm9/src/message.c | 10 ++-- arm9/src/message.h | 18 ++++-- arm9/src/sha1digest.h | 48 ++++++++++++++++ arm9/src/storage.c | 35 ++++++++++++ arm9/src/storage.h | 10 ++++ arm9/src/{unlaunch.c => unlaunch.cpp} | 82 ++++++++++++++++++++++++--- arm9/src/unlaunch.h | 10 ++++ 11 files changed, 214 insertions(+), 22 deletions(-) create mode 100644 arm9/src/sha1digest.h rename arm9/src/{unlaunch.c => unlaunch.cpp} (75%) diff --git a/arm9/Makefile b/arm9/Makefile index 16fba63..7110017 100644 --- a/arm9/Makefile +++ b/arm9/Makefile @@ -31,7 +31,7 @@ CFLAGS := -g -Wall -O2\ $(ARCH) CFLAGS += $(INCLUDE) -DARM9 -CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions +CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions -std=gnu++23 ASFLAGS := -g $(ARCH) -march=armv5te -mtune=arm946e-s diff --git a/arm9/src/main.c b/arm9/src/main.c index 8bcac47..1506e9f 100644 --- a/arm9/src/main.c +++ b/arm9/src/main.c @@ -56,7 +56,7 @@ static int mainMenu(int cursor) "\nlow, of \x1B[41mbricking\x1B[47m your system" "\nand should be done with caution!\n"); iprintf("\n\t \x1B[46mhttps://dsi.cfw.guide\x1B[47m\n"); - iprintf("\x1b[23;0HJeff - 2018-2019"); + iprintf("\x1b[21;0HJeff - 2018-2019"); iprintf("\x1b[22;0HPk11 - 2022-2023"); iprintf("\x1b[23;0Hedo9300 - 2024"); @@ -68,7 +68,6 @@ static int mainMenu(int cursor) sprintf(uninstallStr, "\x1B[%02omUninstall unlaunch", unlaunchFound ? 047 : 037); sprintf(installStr, "\x1B[%02omInstall unlaunch", unlaunchInstallerFound ? 047 : 037); addMenuItem(m, uninstallStr, NULL, 0); - addMenuItem(m, uninstallStr, NULL, 0); addMenuItem(m, installStr, NULL, 0); addMenuItem(m, "\x1B[47mExit", NULL, 0); @@ -139,7 +138,7 @@ int main(int argc, char **argv) return 0; } - unlaunchInstallerFound = fileExists("sd:/unlaunch.dsi"); + unlaunchInstallerFound = loadUnlaunchInstaller("sd:/unlaunch.dsi"); if (!unlaunchInstallerFound) { messageBox("\x1B[41mWARNING:\x1B[47m unlaunch.dsi was not found in\n" diff --git a/arm9/src/main.h b/arm9/src/main.h index 99a0799..974ce78 100644 --- a/arm9/src/main.h +++ b/arm9/src/main.h @@ -5,6 +5,10 @@ #include #include +#ifdef __cplusplus +extern "C" { +#endif + extern volatile bool programEnd; extern bool charging; extern u8 batteryLevel; @@ -14,4 +18,8 @@ extern PrintConsole bottomScreen; void clearScreen(PrintConsole* screen); +#ifdef __cplusplus +} +#endif + #endif \ No newline at end of file diff --git a/arm9/src/menu.h b/arm9/src/menu.h index ea0ebc5..2942394 100644 --- a/arm9/src/menu.h +++ b/arm9/src/menu.h @@ -3,6 +3,10 @@ #include +#ifdef __cplusplus +extern "C" { +#endif + #define ITEMS_PER_PAGE 20 typedef struct { @@ -34,4 +38,8 @@ void printMenu(Menu* m); bool moveCursor(Menu* m); +#ifdef __cplusplus +} +#endif + #endif \ No newline at end of file diff --git a/arm9/src/message.c b/arm9/src/message.c index 83fec38..830a1c5 100644 --- a/arm9/src/message.c +++ b/arm9/src/message.c @@ -13,7 +13,7 @@ void keyWait(u32 key) } } -bool choiceBox(char* message) +bool choiceBox(const char* message) { const int choiceRow = 10; int cursor = 0; @@ -53,7 +53,7 @@ bool choiceBox(char* message) return (cursor == 0)? YES: NO; } -bool choicePrint(char* message) +bool choicePrint(const char* message) { bool choice = NO; @@ -87,7 +87,7 @@ bool choicePrint(char* message) const static u16 keys[] = {KEY_UP, KEY_DOWN, KEY_RIGHT, KEY_LEFT, KEY_A, KEY_B, KEY_X, KEY_Y}; const static char *keysLabels[] = {"\x18", "\x19", "\x1A", "\x1B", "", "", "", ""}; -bool randomConfirmBox(char* message) +bool randomConfirmBox(const char* message) { const int choiceRow = 10; int sequencePosition = 0; @@ -137,13 +137,13 @@ bool randomConfirmBox(char* message) return sequencePosition == sizeof(sequence); } -void messageBox(char* message) +void messageBox(const char* message) { clearScreen(&bottomScreen); messagePrint(message); } -void messagePrint(char* message) +void messagePrint(const char* message) { iprintf("%s\n", message); iprintf("\nOkay - [A]\n"); diff --git a/arm9/src/message.h b/arm9/src/message.h index 56d47af..a873a28 100644 --- a/arm9/src/message.h +++ b/arm9/src/message.h @@ -3,16 +3,24 @@ #include +#ifdef __cplusplus +extern "C" { +#endif + enum { YES = true, NO = false }; void keyWait(u32 key); -bool choiceBox(char* message); -bool choicePrint(char* message); -bool randomConfirmBox(char* message); -void messageBox(char* message); -void messagePrint(char* message); +bool choiceBox(const char* message); +bool choicePrint(const char* message); +bool randomConfirmBox(const char* message); +void messageBox(const char* message); +void messagePrint(const char* message); + +#ifdef __cplusplus +} +#endif #endif \ No newline at end of file diff --git a/arm9/src/sha1digest.h b/arm9/src/sha1digest.h new file mode 100644 index 0000000..791230e --- /dev/null +++ b/arm9/src/sha1digest.h @@ -0,0 +1,48 @@ +#ifndef SHA1DIGEST_H +#define SHA1DIGEST_H + +#include +#include + +static constexpr int SHA1_LEN = 20; + +class Sha1Digest { +public: + friend constexpr inline bool operator==(const Sha1Digest& lhs, const Sha1Digest& rhs); + Sha1Digest() {} + constexpr Sha1Digest(std::string_view digestString) + { + auto hex2int = [](char ch) { + if (ch >= '0' && ch <= '9') return ch - '0'; + if (ch >= 'A' && ch <= 'F') return ch - 'A' + 10; + if (ch >= 'a' && ch <= 'f') return ch - 'a' + 10; + return -1; + }; + for(size_t i = 0; i < SHA1_LEN; ++i) { + auto c1 = digestString[i * 2]; + auto c2 = digestString[(i * 2) + 1]; + digest[i] = hex2int(c1) << 4 | hex2int(c2); + } + } + + auto data() + { + return digest.data(); + } + +private: + std::array digest{}; + +}; + +constexpr inline bool operator==(const Sha1Digest& lhs, const Sha1Digest& rhs) +{ + return lhs.digest == rhs.digest; +} + +consteval Sha1Digest operator ""_sha1(const char* args, size_t len) { + return Sha1Digest{std::string_view{args, len}}; +} + + +#endif \ No newline at end of file diff --git a/arm9/src/storage.c b/arm9/src/storage.c index 334127b..f7634c0 100644 --- a/arm9/src/storage.c +++ b/arm9/src/storage.c @@ -2,6 +2,7 @@ #include "main.h" #include "message.h" #include +#include #include #define TITLE_LIMIT 39 @@ -175,6 +176,40 @@ bool writeToFile(FILE* fd, const char* buffer, size_t size) return toWrite == 0; } +bool calculateFileSha1(FILE* f, void* digest) +{ + fseek(f, 0, SEEK_SET); + + swiSHA1context_t ctx; + ctx.sha_block = 0; //this is weird but it has to be done + swiSHA1Init(&ctx); + + char buffer[512]; + size_t n = 0; + while ((n = fread(buffer, sizeof(char), sizeof(buffer), f)) > 0) + { + swiSHA1Update(&ctx, buffer, n); + } + if (ferror(f) || !feof(f)) + { + return false; + } + swiSHA1Final(digest, &ctx); + return true; +} + +bool calculateFileSha1Path(const char* path, void* digest) +{ + FILE* targetFile = fopen(path, "rb"); + if (!targetFile) + { + return false; + } + bool res = calculateFileSha1(targetFile, digest); + fclose(targetFile); + return res; +} + bool safeCreateDir(const char* path) { if (((mkdir(path, 0777) == 0) || errno == EEXIST)) diff --git a/arm9/src/storage.h b/arm9/src/storage.h index e258749..7f1f8ab 100644 --- a/arm9/src/storage.h +++ b/arm9/src/storage.h @@ -4,6 +4,10 @@ #include #include +#ifdef __cplusplus +extern "C" { +#endif + //Files bool fileExists(char const* path); int copyFile(char const* src, char const* dst); @@ -12,8 +16,14 @@ unsigned long long getFileSize(FILE* f); unsigned long long getFileSizePath(char const* path); bool toggleFileReadOnly(const char* path, bool readOnly); bool writeToFile(FILE* fd, const char* buffer, size_t size); +bool calculateFileSha1(FILE* f, void* digest); +bool calculateFileSha1Path(const char* path, void* digest); //Directories bool safeCreateDir(const char* path); +#ifdef __cplusplus +} +#endif + #endif \ No newline at end of file diff --git a/arm9/src/unlaunch.c b/arm9/src/unlaunch.cpp similarity index 75% rename from arm9/src/unlaunch.c rename to arm9/src/unlaunch.cpp index c99fc64..e1db1f4 100644 --- a/arm9/src/unlaunch.c +++ b/arm9/src/unlaunch.cpp @@ -1,6 +1,10 @@ #include "message.h" +#include "sha1digest.h" #include "storage.h" #include "unlaunch.h" +#include +#include +#include #include #include #include @@ -9,6 +13,37 @@ static char unlaunchInstallerBuffer[0x30000]; static const char* hnaaTmdPath = "nand:/title/00030017/484e4141/content/title.tmd"; static const char* hnaaBackupTmdPath = "nand:/title/00030017/484e4141/content/title.tmd.bak"; +enum UNLAUNCH_VERSION { + v1_8, + v1_9, + v2_0, + INVALID, +}; + +UNLAUNCH_VERSION installerVersion{INVALID}; +size_t unlaunchInstallerSize{}; + +constexpr std::array knownUnlaunchHashes{ + /*"9e6a8d95062533dfc422362f99ff3e24e7de9920"_sha1, // v0.8: blacklisted, doesn't like this install method*/ + /*"fb0d0ffebda67b786f608bf5cbcb2efee6ab42bb"_sha1, // v0.9: blacklisted, doesn't like this install method*/ + /*"d710ff585e321082b33456dd4e0568200c9adcc7"_sha1, // v1.0: blacklisted, doesn't like this install method*/ + /*"4f3e455e0a752d35a219a3ff10ba14a6c98bff13"_sha1, // v1.1: blacklisted, doesn't like this install method*/ + /*"25db1a47ba84748f911d9f4357bfc417533121c7"_sha1, // v1.2: blacklisted, doesn't like this install method*/ + /*"068f1d56da02bb4f93fb76d7874e14010c7e7a3d"_sha1, // v1.3: blacklisted, doesn't like this install method*/ + /*"43197370de74d302ef7c4420059c3ca7d50c4f3d"_sha1, // v1.4: blacklisted, doesn't like this install method*/ + /*"0525b28cc59b6f7fc00ad592aebadd7257bf7efb"_sha1, // v1.5: blacklisted, doesn't like this install method*/ + /*"9470a51fde188235052b119f6bfabf6689cb2343"_sha1, // v1.6: blacklisted, doesn't like this install method*/ + /*"672c11eb535b97b0d32ff580d314a2ad6411d5fe"_sha1, // v1.7: blacklisted, doesn't like this install method*/ + "b76c2b1722e769c6c0b4b3d4bc73250e41993229"_sha1, // v1.8 + "f3eb41cba136a3477523155f8b05df14917c55f4"_sha1, // v1.9 + "15f4a36251d1408d71114019b2825fe8f5b4c8cc"_sha1, // v2.0 +}; + +bool isValidUnlaunchInstallerSize(size_t size) +{ + return size == 163320 /*1.8*/ || size == 196088 /*1.9*/; +} + bool isLauncherTmdPatched(const char* path) { FILE* launcherTmd = fopen(path, "rb"); @@ -148,7 +183,10 @@ static bool writeUnlaunchTmd(const char* path) return false; } - if(!writeToFile(targetTmd, unlaunchInstallerBuffer, sizeof(unlaunchInstallerBuffer))) + Sha1Digest expectedDigest, actualDigest; + swiSHA1Calc(expectedDigest.data(), unlaunchInstallerBuffer, unlaunchInstallerSize + 520); + + if(!writeToFile(targetTmd, unlaunchInstallerBuffer, unlaunchInstallerSize + 520)) { fclose(targetTmd); remove(path); @@ -156,6 +194,14 @@ static bool writeUnlaunchTmd(const char* path) return false; } fclose(targetTmd); + calculateFileSha1Path(path, actualDigest.data()); + + if(expectedDigest != actualDigest) + { + remove(path); + messageBox("\x1B[31mError:\x1B[33m Unlaunch tmd was not properly written\n"); + return false; + } return true; } @@ -267,23 +313,23 @@ static bool installUnlaunchProtoConsole(void) return true; } -bool readUnlaunchInstaller(void) +bool readUnlaunchInstaller(const char* path) { - FILE* unlaunchInstaller = fopen("sd:/unlaunch.dsi", "rb"); + FILE* unlaunchInstaller = fopen(path, "rb"); if (!unlaunchInstaller) { messageBox("\x1B[31mError:\x1B[33m Failed to open unlaunch installer\n(sd:/unlaunch.dsi)\n"); return false; } - int toRead = sizeof(unlaunchInstallerBuffer) - 520; - size_t installerFilesize = getFileSize(unlaunchInstaller); - if(installerFilesize != toRead) + unlaunchInstallerSize = getFileSize(unlaunchInstaller); + if(isValidUnlaunchInstallerSize(unlaunchInstallerSize)) { messageBox("\x1B[31mError:\x1B[33m Unlaunch installer wrong file size\n"); return false; } + int toRead = unlaunchInstallerSize; char* buff = unlaunchInstallerBuffer; // Pad the installer with 520 bytes, those being the size of a valid tmd buff += 520; @@ -301,7 +347,22 @@ bool readUnlaunchInstaller(void) return false; } - fclose(unlaunchInstaller); + fclose(unlaunchInstaller); + return true; +} + +bool verifyUnlaunchInstaller(void) +{ + Sha1Digest digest; + swiSHA1Calc(digest.data(), unlaunchInstallerBuffer + 520, unlaunchInstallerSize); + auto it = std::ranges::find(knownUnlaunchHashes, digest); + if(it == knownUnlaunchHashes.end()) + { + messageBox("\x1B[31mError:\x1B[33m Provided unlaunch installer has an unknown hash\n"); + return false; + } + auto idx = std::distance(knownUnlaunchHashes.begin(), it); + installerVersion = static_cast(idx); return true; } @@ -311,9 +372,14 @@ bool patchUnlaunchInstaller(void) return true; } +bool loadUnlaunchInstaller(const char* path) +{ + return readUnlaunchInstaller(path) && verifyUnlaunchInstaller(); +} + bool installUnlaunch(bool retailConsole, const char* retailLauncherTmdPath) { - if (!readUnlaunchInstaller() || !patchUnlaunchInstaller()) + if (installerVersion == INVALID || !patchUnlaunchInstaller()) return false; // Treat protos differently diff --git a/arm9/src/unlaunch.h b/arm9/src/unlaunch.h index bbc5cee..9d9e76f 100644 --- a/arm9/src/unlaunch.h +++ b/arm9/src/unlaunch.h @@ -2,9 +2,19 @@ #define UNLAUNCH_H #include +#ifdef __cplusplus +extern "C" { +#endif + bool uninstallUnlaunch(bool notProto, const char* retailLauncherTmdPath); bool installUnlaunch(bool retailConsole, const char* retailLauncherTmdPath); bool isLauncherTmdPatched(const char* path); +bool loadUnlaunchInstaller(const char* path); + +#ifdef __cplusplus +} +#endif + #endif \ No newline at end of file