From 1a0fe17b6c19dc3148c591367a8ccb037665cb97 Mon Sep 17 00:00:00 2001 From: Wolfvak Date: Thu, 15 Aug 2019 23:20:14 -0300 Subject: [PATCH] better FIRM checks, fix unchecked NULL that triggered a data abort --- arm9/source/game/firm.c | 129 +++++++++++++++++++++++++--------------- arm9/source/godmode.c | 18 +++--- 2 files changed, 92 insertions(+), 55 deletions(-) diff --git a/arm9/source/game/firm.c b/arm9/source/game/firm.c index 7dd8b51..c107fb6 100644 --- a/arm9/source/game/firm.c +++ b/arm9/source/game/firm.c @@ -7,7 +7,7 @@ #include "ff.h" // 0 -> pre 9.5 / 1 -> 9.5 / 2 -> post 9.5 -#define A9L_CRYPTO_TYPE(hdr) ((hdr->k9l[3] == 0xFF) ? 0 : (hdr->k9l[3] == '1') ? 1 : 2) +#define A9L_CRYPTO_TYPE(hdr) (((hdr)->k9l[3] == 0xFF) ? 0 : ((hdr)->k9l[3] == '1') ? 1 : 2) // valid addresses for FIRM section loading // pairs of start / end address, provided by Wolfvak @@ -20,38 +20,51 @@ #define FIRM_VALID_ADDRESS_INSTALL \ FIRM_VALID_ADDRESS, \ 0x10000000, 0x10200000 - + // valid addresses (bootable) for FIRM section loading #define FIRM_VALID_ADDRESS_BOOT \ FIRM_VALID_ADDRESS, \ 0x20000000, 0x27FFFA00 - + +static const u32 whitelist_boot[] = { FIRM_VALID_ADDRESS_BOOT }; +static const u32 whitelist_install[] = { FIRM_VALID_ADDRESS_INSTALL }; + +#define WLIST(i) ((i) ? whitelist_install : whitelist_boot) +#define WLIST_SZ(i) (((i) ? sizeof(whitelist_install) : sizeof(whitelist_boot)) / (sizeof(u32) * 2)) + +#define ADDR_IN_RANGE(a, s, e) (clamp(a, s, e) == (a)) +#define ADDR_IN_SECTION(a, s) (ADDR_IN_RANGE(a, (s)->address, (s)->address + (s)->size)) + u32 GetFirmSize(FirmHeader* header) { - u8 magic[] = { FIRM_MAGIC }; + static const u8 magic[] = { FIRM_MAGIC }; if (memcmp(header->magic, magic, sizeof(magic)) != 0) return 0; - + u32 firm_size = sizeof(FirmHeader); - int section_arm11 = -1; int section_arm9 = -1; for (u32 i = 0; i < 4; i++) { FirmSectionHeader* section = header->sections + i; - if (!section->size) continue; - if (section->offset < firm_size) return 0; - if ((section->offset % 512) || (section->size % 512)) return 0; - if ((header->entry_arm11 >= section->address) && - (header->entry_arm11 < section->address + section->size)) - section_arm11 = i; - if ((header->entry_arm9 >= section->address) && - (header->entry_arm9 < section->address + section->size)) + if (!section->size) + continue; + + if (section->offset > FIRM_MAX_SIZE || section->size > FIRM_MAX_SIZE) + return 0; + if (section->offset < sizeof(FirmHeader)) + return 0; + if ((section->offset % 512) || (section->size % 512)) + return 0; + if (ADDR_IN_SECTION(header->entry_arm9, section)) section_arm9 = i; - firm_size = section->offset + section->size; + + firm_size = max(firm_size, section->offset + section->size); } - - if (firm_size > FIRM_MAX_SIZE) return 0; - if ((header->entry_arm11 && (section_arm11 < 0)) || (header->entry_arm9 && (section_arm9 < 0))) + + if (firm_size > FIRM_MAX_SIZE) return 0; - + + if (section_arm9 < 0) + return 0; + return firm_size; } @@ -61,62 +74,84 @@ u32 ValidateFirmHeader(FirmHeader* header, u32 data_size) { } u32 ValidateFirmA9LHeader(FirmA9LHeader* header) { - const u8 enckeyX0x15hash[0x20] = { - 0x0A, 0x85, 0x20, 0x14, 0x8F, 0x7E, 0xB7, 0x21, 0xBF, 0xC6, 0xC8, 0x82, 0xDF, 0x37, 0x06, 0x3C, - 0x0E, 0x05, 0x1D, 0x1E, 0xF3, 0x41, 0xE9, 0x80, 0x1E, 0xC9, 0x97, 0x82, 0xA0, 0x84, 0x43, 0x08 + static const u8 enckeyX0x15hash[2][0x20] = + { + { + 0x0A, 0x85, 0x20, 0x14, 0x8F, 0x7E, 0xB7, 0x21, 0xBF, 0xC6, 0xC8, 0x82, 0xDF, 0x37, 0x06, 0x3C, + 0x0E, 0x05, 0x1D, 0x1E, 0xF3, 0x41, 0xE9, 0x80, 0x1E, 0xC9, 0x97, 0x82, 0xA0, 0x84, 0x43, 0x08 + }, + { + 0xFC, 0x46, 0x74, 0x78, 0x73, 0x01, 0xD3, 0x23, 0x52, 0x94, 0x97, 0xED, 0xA8, 0x5B, 0xCF, 0xD2, + 0xDA, 0x2D, 0xFA, 0x47, 0x8E, 0x2D, 0x98, 0x89, 0xBA, 0x60, 0xE8, 0x43, 0x5C, 0x1B, 0x93, 0x65 + } }; - const u8 enckeyX0x15devhash[0x20] = { - 0xFC, 0x46, 0x74, 0x78, 0x73, 0x01, 0xD3, 0x23, 0x52, 0x94, 0x97, 0xED, 0xA8, 0x5B, 0xCF, 0xD2, - 0xDA, 0x2D, 0xFA, 0x47, 0x8E, 0x2D, 0x98, 0x89, 0xBA, 0x60, 0xE8, 0x43, 0x5C, 0x1B, 0x93, 0x65, - }; - return sha_cmp((IS_DEVKIT) ? enckeyX0x15devhash : enckeyX0x15hash, header->keyX0x15, 0x10, SHA256_MODE); + + return sha_cmp(enckeyX0x15hash[IS_DEVKIT ? 1 : 0], header->keyX0x15, 0x10, SHA256_MODE); } u32 ValidateFirm(void* firm, u32 firm_size, bool installable) { FirmHeader* header = (FirmHeader*) firm; - + u32 skipchk_mask = 0; + + const u32 *whitelist = WLIST(installable); + u32 whitelist_size = WLIST_SZ(installable); + // validate firm header if ((firm_size < sizeof(FirmHeader)) || (ValidateFirmHeader(header, firm_size) != 0)) return 1; - + // overrides for b9s / superhax fb3ds firms - bool b9s_fix = installable && (memcmp(&(header->reserved0[0x2D]), "B9S", 3) == 0); - bool fb3ds_fix = installable && (header->sections[1].size == 0x200) && - (header->sections[1].address == 0x07FFFE8C); - + if (installable) { + const u8 *resv = header->reserved0; + if (resv[0x2D] == 'B' && resv[0x2E] == '9' && resv[0x2F] == 'S') + skipchk_mask |= BIT(3); + + if ((header->sections[1].size = 0x200) && + (header->sections[1].address = 0x07FFFE8C)) + skipchk_mask |= BIT(1); + } + // hash verify all available sections and check load address for (u32 i = 0; i < 4; i++) { - u32 whitelist_boot[] = { FIRM_VALID_ADDRESS_BOOT }; - u32 whitelist_install[] = { FIRM_VALID_ADDRESS_INSTALL }; - u32* whitelist = (installable) ? whitelist_install : whitelist_boot; - u32 whitelist_size = ((installable) ? sizeof(whitelist_install) : sizeof(whitelist_boot)) / (2*sizeof(u32)); + bool bad_loadaddr; FirmSectionHeader* section = header->sections + i; - if (!section->size) continue; + + if ((skipchk_mask & BIT(i)) || !section->size) + continue; + if (sha_cmp(section->hash, ((u8*) firm) + section->offset, section->size, SHA256_MODE) != 0) return 1; - bool is_whitelisted = (b9s_fix && (i == 3)) || (fb3ds_fix && (i == 1)); // b9s / fb3ds overrides - for (u32 a = 0; (a < whitelist_size) && !is_whitelisted; a++) { - if ((section->address >= whitelist[2*a]) && (section->address + section->size <= whitelist[(2*a)+1])) - is_whitelisted = true; + + bad_loadaddr = true; + for (u32 a = 0; a < whitelist_size; a++) { + u32 start = whitelist[2 * a], end = whitelist[(2 * a) + 1]; + if (ADDR_IN_RANGE(section->address, start, end) && + ADDR_IN_RANGE(section->address + section->size, start, end)) { + bad_loadaddr = false; + break; + } } - if (!is_whitelisted) return 1; + + if (bad_loadaddr) + return 1; } - + // ARM9 / ARM11 entrypoints available? if (!header->entry_arm9 || (installable && !header->entry_arm11)) return 1; - + // B9S screeninit flag? if (installable && (header->reserved0[0]&0x1)) return 1; - + return 0; } FirmSectionHeader* FindFirmArm9Section(FirmHeader* firm) { + u32 entry = firm->entry_arm9; for (u32 i = 0; i < 4; i++) { FirmSectionHeader* section = firm->sections + i; - if (section->size && (section->method == FIRM_NDMA_CPY)) + if (section->size && ADDR_IN_SECTION(entry, section)) return section; } return NULL; diff --git a/arm9/source/godmode.c b/arm9/source/godmode.c index 7d54d40..60fe653 100644 --- a/arm9/source/godmode.c +++ b/arm9/source/godmode.c @@ -53,18 +53,18 @@ typedef struct { u32 BootFirmHandler(const char* bootpath, bool verbose, bool delete) { char pathstr[32+1]; TruncateString(pathstr, bootpath, 32, 8); - + size_t firm_size = FileGetSize(bootpath); if (!firm_size) return 1; if (firm_size > FIRM_MAX_SIZE) { if (verbose) ShowPrompt(false, "%s\nFIRM too big, can't boot", pathstr); // unlikely return 1; } - + if (verbose && !ShowPrompt(true, "%s (%dkB)\nWarning: Do not boot FIRMs\nfrom untrusted sources.\n \nBoot FIRM?", pathstr, firm_size / 1024)) return 1; - + void* firm = (void*) malloc(FIRM_MAX_SIZE); if (!firm) return 1; if ((FileGetData(bootpath, firm, firm_size, 0) != firm_size) || @@ -73,9 +73,11 @@ u32 BootFirmHandler(const char* bootpath, bool verbose, bool delete) { free(firm); return 1; } - + // encrypted firm handling FirmSectionHeader* arm9s = FindFirmArm9Section(firm); + if (!arm9s) return 1; + FirmA9LHeader* a9l = (FirmA9LHeader*)(void*) ((u8*) firm + arm9s->offset); if (verbose && (ValidateFirmA9LHeader(a9l) == 0) && ShowPrompt(true, "%s\nFIRM is encrypted.\n \nDecrypt before boot?", pathstr) && @@ -83,7 +85,7 @@ u32 BootFirmHandler(const char* bootpath, bool verbose, bool delete) { free(firm); return 1; } - + // unsupported location handling char fixpath[256] = { 0 }; if (verbose && (*bootpath != '0') && (*bootpath != '1')) { @@ -94,13 +96,13 @@ u32 BootFirmHandler(const char* bootpath, bool verbose, bool delete) { bootpath = OUTPUT_PATH "/temp.firm"; } else if (!user_select) bootpath = ""; } - + // fix the boot path ("sdmc"/"nand" for Luma et al, hacky af) if ((*bootpath == '0') || (*bootpath == '1')) snprintf(fixpath, 256, "%s%s", (*bootpath == '0') ? "sdmc" : "nand", bootpath + 1); else strncpy(fixpath, bootpath, 256); fixpath[255] = '\0'; - + // boot the FIRM (if we got a proper fixpath) if (*fixpath) { if (delete) PathDelete(bootpath); @@ -109,7 +111,7 @@ u32 BootFirmHandler(const char* bootpath, bool verbose, bool delete) { BootFirm((FirmHeader*) firm, fixpath); while(1); } - + // a return was not intended free(firm); return 1;