diff --git a/source/crypto/sha.c b/source/crypto/sha.c index a3b9bbd..7db1308 100644 --- a/source/crypto/sha.c +++ b/source/crypto/sha.c @@ -36,3 +36,9 @@ void sha_quick(void* res, const void* src, u32 size, u32 mode) { sha_update(src, size); sha_get(res); } + +int sha_cmp(const void* sha, const void* src, u32 size, u32 mode) { + u8 res[0x20]; + sha_quick(res, src, size, mode); + return memcmp(sha, res, 0x20); +} diff --git a/source/crypto/sha.h b/source/crypto/sha.h index d4347d4..10782da 100644 --- a/source/crypto/sha.h +++ b/source/crypto/sha.h @@ -26,3 +26,4 @@ void sha_init(u32 mode); void sha_update(const void* src, u32 size); void sha_get(void* res); void sha_quick(void* res, const void* src, u32 size, u32 mode); +int sha_cmp(const void* sha, const void* src, u32 size, u32 mode); diff --git a/source/fs/filetype.c b/source/fs/filetype.c index 8be9df9..c2a7358 100644 --- a/source/fs/filetype.c +++ b/source/fs/filetype.c @@ -6,8 +6,8 @@ u32 IdentifyFileType(const char* path) { const u8 romfs_magic[] = { ROMFS_MAGIC }; - const u8 firm_magic[] = { FIRM_MAGIC }; u8 header[0x200] __attribute__((aligned(32))); // minimum required size + void* data = (void*) header; size_t fsize = FileGetSize(path); char* fname = strrchr(path, '/'); char* ext = (fname) ? strrchr(++fname, '.') : NULL; @@ -20,42 +20,42 @@ u32 IdentifyFileType(const char* path) { return IMG_NAND; // NAND image } else if (ValidateFatHeader(header) == 0) { return IMG_FAT; // FAT image file - } else if (ValidateMbrHeader((MbrHeader*) (void*) header) == 0) { - MbrHeader* mbr = (MbrHeader*) (void*) header; + } else if (ValidateMbrHeader((MbrHeader*) data) == 0) { + MbrHeader* mbr = (MbrHeader*) data; MbrPartitionInfo* partition0 = mbr->partitions; if ((partition0->sector + partition0->count) <= (fsize / 0x200)) // size check return IMG_FAT; // possibly an MBR -> also treat as FAT image - } else if (ValidateCiaHeader((CiaHeader*) (void*) header) == 0) { + } else if (ValidateCiaHeader((CiaHeader*) data) == 0) { // this only works because these functions ignore CIA content index CiaInfo info; GetCiaInfo(&info, (CiaHeader*) header); if (fsize >= info.size_cia) return GAME_CIA; // CIA file - } else if (ValidateNcsdHeader((NcsdHeader*) (void*) header) == 0) { - NcsdHeader* ncsd = (NcsdHeader*) (void*) header; + } else if (ValidateNcsdHeader((NcsdHeader*) data) == 0) { + NcsdHeader* ncsd = (NcsdHeader*) data; if (fsize >= GetNcsdTrimmedSize(ncsd)) return GAME_NCSD; // NCSD (".3DS") file - } else if (ValidateNcchHeader((NcchHeader*) (void*) header) == 0) { - NcchHeader* ncch = (NcchHeader*) (void*) header; + } else if (ValidateNcchHeader((NcchHeader*) data) == 0) { + NcchHeader* ncch = (NcchHeader*) data; u32 type = GAME_NCCH | (NCCH_IS_CXI(ncch) ? FLAG_CXI : 0); if (fsize >= (ncch->size * NCCH_MEDIA_UNIT)) return type; // NCCH (".APP") file - } else if (ValidateExeFsHeader((ExeFsHeader*) (void*) header, fsize) == 0) { + } else if (ValidateExeFsHeader((ExeFsHeader*) data, fsize) == 0) { return GAME_EXEFS; // ExeFS file (false positives possible) } else if (memcmp(header, romfs_magic, sizeof(romfs_magic)) == 0) { return GAME_ROMFS; // RomFS file (check could be better) } else if (strncmp(TMD_ISSUER, (char*) (header + 0x140), 0x40) == 0) { if (fsize >= TMD_SIZE_N(getbe16(header + 0x1DE))) return GAME_TMD; // TMD file - } else if (memcmp(header, firm_magic, sizeof(firm_magic)) == 0) { + } else if (ValidateFirmHeader((FirmHeader*) data, fsize) == 0) { return SYS_FIRM; // FIRM file } } if ((fsize > sizeof(BossHeader)) && - (ValidateBossHeader((BossHeader*) (void*) header, fsize) == 0)) { + (ValidateBossHeader((BossHeader*) data, fsize) == 0)) { return GAME_BOSS; // BOSS (SpotPass) file } else if ((fsize > sizeof(NcchInfoHeader)) && - (GetNcchInfoVersion((NcchInfoHeader*) (void*) header)) && + (GetNcchInfoVersion((NcchInfoHeader*) data)) && fname && (strncasecmp(fname, NCCHINFO_NAME, 32) == 0)) { return BIN_NCCHNFO; // ncchinfo.bin file #if PAYLOAD_MAX_SIZE <= TEMP_BUFFER_SIZE diff --git a/source/game/firm.c b/source/game/firm.c index d87800d..eedc950 100644 --- a/source/game/firm.c +++ b/source/game/firm.c @@ -8,9 +8,23 @@ // 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) -u32 ValidateFirmHeader(FirmHeader* header) { +u32 ValidateFirmHeader(FirmHeader* header, u32 data_size) { u8 magic[] = { FIRM_MAGIC }; - return memcmp(header->magic, magic, sizeof(magic)); // duh + if (memcmp(header->magic, magic, sizeof(magic)) != 0) + return 1; + + u32 firm_size = sizeof(FirmHeader); + for (u32 i = 0; i < 4; i++) { + FirmSectionHeader* section = header->sections + i; + if (!section->size) continue; + if (section->offset < firm_size) return 1; + firm_size = section->offset + section->size; + } + + if ((firm_size > FIRM_MAX_SIZE) || (data_size && (firm_size > data_size))) + return 1; + + return 0; } u32 ValidateFirmA9LHeader(FirmA9LHeader* header) { @@ -18,9 +32,7 @@ u32 ValidateFirmA9LHeader(FirmA9LHeader* header) { 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 }; - u8 hash[0x20]; - sha_quick(hash, header->keyX0x15, 0x10, SHA256_MODE); - return memcmp(hash, enckeyX0x15hash, 0x20); + return sha_cmp(enckeyX0x15hash, header->keyX0x15, 0x10, SHA256_MODE); } FirmSectionHeader* FindFirmArm9Section(FirmHeader* firm) { @@ -194,7 +206,7 @@ u32 DecryptFirmSequential(u8* data, u32 offset, u32 size) { // fetch firm header from data if ((offset == 0) && (size >= sizeof(FirmHeader))) { memcpy(&firm, data, sizeof(FirmHeader)); - firmptr = (ValidateFirmHeader(&firm) == 0) ? &firm : NULL; + firmptr = (ValidateFirmHeader(&firm, 0) == 0) ? &firm : NULL; arm9s = (firmptr) ? FindFirmArm9Section(firmptr) : NULL; a9lptr = NULL; } diff --git a/source/game/firm.h b/source/game/firm.h index e9de762..f6b2746 100644 --- a/source/game/firm.h +++ b/source/game/firm.h @@ -7,6 +7,7 @@ #define SECTOR_NAME "sector0x96.bin" #define SECRET_NAME "secret_sector.bin" +#define FIRM_MAX_SIZE 0x400000 // 4MB, due to FIRM partition size #define ARM9BIN_OFFSET 0x800 // see: https://www.3dbrew.org/wiki/FIRM#Firmware_Section_Headers @@ -42,7 +43,7 @@ typedef struct { u8 padding[0x1A0]; } __attribute__((packed, aligned(16))) FirmA9LHeader; -u32 ValidateFirmHeader(FirmHeader* header); +u32 ValidateFirmHeader(FirmHeader* header, u32 data_size); u32 ValidateFirmA9LHeader(FirmA9LHeader* header); FirmSectionHeader* FindFirmArm9Section(FirmHeader* firm); diff --git a/source/game/gameutil.c b/source/game/gameutil.c index 1e4fe82..51d22e5 100644 --- a/source/game/gameutil.c +++ b/source/game/gameutil.c @@ -471,12 +471,15 @@ u32 VerifyFirmFile(const char* path) { FIL file; UINT btr; + char pathstr[32 + 1]; + TruncateString(pathstr, path, 32, 8); + // open file, get FIRM header if (fvx_open(&file, path, FA_READ | FA_OPEN_EXISTING) != FR_OK) return 1; fvx_lseek(&file, 0); if ((fvx_read(&file, &header, sizeof(FirmHeader), &btr) != FR_OK) || - (ValidateFirmHeader(&header) != 0)) { + (ValidateFirmHeader(&header, fvx_size(&file)) != 0)) { fvx_close(&file); return 1; } @@ -496,8 +499,6 @@ u32 VerifyFirmFile(const char* path) { u8 hash[0x20]; sha_get(hash); if (memcmp(hash, section->hash, 0x20) != 0) { - char pathstr[32 + 1]; - TruncateString(pathstr, path, 32, 8); ShowPrompt(false, "%s\nSection %u hash mismatch", pathstr, i); fvx_close(&file); return 1; @@ -505,6 +506,25 @@ u32 VerifyFirmFile(const char* path) { } fvx_close(&file); + // check arm11 / arm9 entrypoints + int section_arm11 = -1; + int section_arm9 = -1; + for (u32 i = 0; i < 4; i++) { + FirmSectionHeader* section = header.sections + i; + 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)) + section_arm9 = i; + } + + // sections for arm11 / arm9 entrypoints not found? + if ((section_arm11 < 0) || (section_arm9 < 0)) { + ShowPrompt(false, "%s\nARM11/ARM9 entrypoint not found", pathstr); + return 1; + } + return 0; } @@ -625,7 +645,7 @@ u32 CheckEncryptedFirmFile(const char* path) { return 1; fvx_lseek(&file, 0); if ((fvx_read(&file, &header, sizeof(FirmHeader), &btr) != FR_OK) || - (ValidateFirmHeader(&header) != 0)) { + (ValidateFirmHeader(&header, fvx_size(&file)) != 0)) { fvx_close(&file); return 1; } @@ -714,7 +734,7 @@ u32 CryptNcchNcsdBossFirmFile(const char* orig, const char* dest, u32 mode, u16 u32 ret = 0; if (!ShowProgress(offset, fsize, dest)) ret = 1; if (mode & (GAME_NCCH|GAME_NCSD|GAME_BOSS|SYS_FIRM)) { // for NCCH / NCSD / BOSS / FIRM files - for (u32 i = 0; (i < size) && (ret == 0); i += MAIN_BUFFER_SIZE) { + for (u64 i = 0; (i < size) && (ret == 0); i += MAIN_BUFFER_SIZE) { u32 read_bytes = min(MAIN_BUFFER_SIZE, (size - i)); UINT bytes_read, bytes_written; if (fvx_read(ofp, MAIN_BUFFER, read_bytes, &bytes_read) != FR_OK) ret = 1; @@ -745,7 +765,7 @@ u32 CryptNcchNcsdBossFirmFile(const char* orig, const char* dest, u32 mode, u16 GetTmdCtr(ctr, chunk); fvx_lseek(ofp, offset); sha_init(SHA256_MODE); - for (u32 i = 0; (i < size) && (ret == 0); i += MAIN_BUFFER_SIZE) { + for (u64 i = 0; (i < size) && (ret == 0); i += MAIN_BUFFER_SIZE) { u32 read_bytes = min(MAIN_BUFFER_SIZE, (size - i)); if (fvx_read(ofp, MAIN_BUFFER, read_bytes, &bytes_read) != FR_OK) ret = 1; if (cia_crypto && (DecryptCiaContentSequential(MAIN_BUFFER, read_bytes, ctr, titlekey) != 0)) ret = 1; @@ -818,7 +838,7 @@ u32 DecryptFirmFile(const char* orig, const char* dest) { return 1; fvx_lseek(&file, 0); if ((fvx_read(&file, &firm, sizeof(FirmHeader), &btr) != FR_OK) || - (ValidateFirmHeader(&firm) != 0)) { + (ValidateFirmHeader(&firm, fvx_size(&file)) != 0)) { fvx_close(&file); return 1; } diff --git a/source/nand/nandutil.c b/source/nand/nandutil.c index cd74a85..ac56714 100644 --- a/source/nand/nandutil.c +++ b/source/nand/nandutil.c @@ -77,7 +77,7 @@ u32 ValidateNandDump(const char* path) { for (u32 i = 0; i < sizeof(firm_sectors) / sizeof(u32); i++) { u32 keyslot = 0x06; if ((ReadNandFile(&file, &firm, firm_sectors[i], 1, keyslot) != 0) || - (ValidateFirmHeader(&firm) != 0) || + (ValidateFirmHeader(&firm, 0) != 0) || (getbe32(firm.dec_magic) != 0)) { // decrypted firms are not allowed ShowPrompt(false, "%s\nError: FIRM%u header is corrupt", pathstr, i); fvx_close(&file); diff --git a/source/virtual/vgame.c b/source/virtual/vgame.c index 9f21830..8e1fc3e 100644 --- a/source/virtual/vgame.c +++ b/source/virtual/vgame.c @@ -490,7 +490,7 @@ bool OpenVGameDir(VirtualDir* vdir, VirtualFile* ventry) { // build directories where required if ((vdir->flags & VFLAG_FIRM) && (offset_firm != vdir->offset)) { if ((ReadImageBytes((u8*) firm, 0, sizeof(FirmHeader)) != 0) || - (ValidateFirmHeader(firm) != 0)) return false; + (ValidateFirmHeader(firm, 0) != 0)) return false; offset_firm = vdir->offset; FirmSectionHeader* arm9s = FindFirmArm9Section(firm); if (arm9s && (ReadImageBytes((u8*) a9l, arm9s->offset, sizeof(FirmA9LHeader)) == 0) &&