From 8f24ccec0c90d38c836d1b9c71804954388d5d17 Mon Sep 17 00:00:00 2001 From: d0k3 Date: Tue, 25 Sep 2018 01:42:52 +0200 Subject: [PATCH] Full / proper verification for romFS in NCCH --- arm9/source/game/romfs.c | 39 +++++++++++++++- arm9/source/game/romfs.h | 25 +++++++++- arm9/source/godmode.c | 5 +- arm9/source/utils/gameutil.c | 88 ++++++++++++++++++++++++++++++++++++ arm9/source/virtual/vgame.c | 20 ++++---- 5 files changed, 162 insertions(+), 15 deletions(-) diff --git a/arm9/source/game/romfs.c b/arm9/source/game/romfs.c index d51cd58..d631953 100644 --- a/arm9/source/game/romfs.c +++ b/arm9/source/game/romfs.c @@ -1,7 +1,44 @@ #include "romfs.h" #include "utf.h" -// validate header by checking offsets and sizes + +// get lvl datablock offset from IVC (zero for total size) +// see: https://github.com/profi200/Project_CTR/blob/046bb359ee95423938886dbf477d00690aaecd3e/ctrtool/ivfc.c#L88-L111 +u64 GetRomFsLvOffset(RomFsIvfcHeader* ivfc, u32 lvl) { + // regardless of lvl given, we calculate them all + u64 offset[4]; + + // lvl1/2/3 offset and size + offset[3] = align(sizeof(RomFsIvfcHeader) + ivfc->size_masterhash, 1 << ivfc->log_lvl3); + offset[1] = offset[3] + align(ivfc->size_lvl3, 1 << ivfc->log_lvl3); + offset[2] = offset[1] + align(ivfc->size_lvl1, 1 << ivfc->log_lvl1); + offset[0] = offset[2] + align(ivfc->size_lvl2, 1 << ivfc->log_lvl2); // (min) size + + return (lvl <= 3) ? offset[lvl] : 0; +} + +// validate IVFC header by checking offsets and hash sizes +u32 ValidateRomFsHeader(RomFsIvfcHeader* ivfc, u32 max_size) { + u8 magic[] = { ROMFS_MAGIC }; + + // check magic number + if (memcmp(magic, ivfc->magic, sizeof(magic)) != 0) + return 1; + + // check hash block sizes vs data block sizes + if ((((ivfc->size_masterhash / 0x20) << ivfc->log_lvl1) < ivfc->size_lvl1) || // lvl1 + (((ivfc->size_lvl1 / 0x20) << ivfc->log_lvl2) < ivfc->size_lvl2) || // lvl2 + (((ivfc->size_lvl2 / 0x20) << ivfc->log_lvl3) < ivfc->size_lvl3)) // lvl3 + return 1; + + // check size if given + if (max_size && (max_size < GetRomFsLvOffset(ivfc, 0))) + return 1; + + return 0; +} + +// validate lvl3 header by checking offsets and sizes u32 ValidateLv3Header(RomFsLv3Header* lv3, u32 max_size) { return ((lv3->size_header == 0x28) && (lv3->offset_dirhash >= lv3->size_header) && diff --git a/arm9/source/game/romfs.h b/arm9/source/game/romfs.h index 666341c..e782dd4 100644 --- a/arm9/source/game/romfs.h +++ b/arm9/source/game/romfs.h @@ -2,7 +2,7 @@ #include "common.h" -#define ROMFS_MAGIC 0x49, 0x56, 0x46, 0x43, 0x00, 0x00, 0x01, 0x00 +#define ROMFS_MAGIC 0x49, 0x56, 0x46, 0x43, 0x00, 0x00, 0x01, 0x00 // "IVFC" 0x0001000 #define OFFSET_LV3 0x1000 #define LV3_GET_DIR(offset, idx) \ @@ -26,6 +26,26 @@ ((idx)->dirmeta + (dm)->offset_parent : NULL))) +typedef struct { + u8 magic[8]; + u32 size_masterhash; + u64 offset_lvl1; // seems to be useless? + u64 size_lvl1; + u32 log_lvl1; + u8 reserved0[4]; + u64 offset_lvl2; // seems to be useless? + u64 size_lvl2; + u32 log_lvl2; + u8 reserved1[4]; + u64 offset_lvl3; // seems to be useless? + u64 size_lvl3; + u32 log_lvl3; + u8 reserved2[4]; + u32 unknown0; + u32 unknown1; + u8 padding[4]; // masterhash follows +} __attribute__((packed)) RomFsIvfcHeader; + typedef struct { u32 size_header; u32 offset_dirhash; @@ -72,6 +92,9 @@ typedef struct { u32 size_filemeta; } __attribute__((packed)) RomFsLv3Index; + +u64 GetRomFsLvOffset(RomFsIvfcHeader* ivfc, u32 lvl); +u32 ValidateRomFsHeader(RomFsIvfcHeader* ivfc, u32 max_size); u32 ValidateLv3Header(RomFsLv3Header* lv3, u32 max_size); u32 BuildLv3Index(RomFsLv3Index* index, u8* lv3); u32 HashLv3Path(u16* wname, u32 name_len, u32 offset_parent); diff --git a/arm9/source/godmode.c b/arm9/source/godmode.c index d71ab4e..0ade79e 100644 --- a/arm9/source/godmode.c +++ b/arm9/source/godmode.c @@ -1479,7 +1479,7 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, PaneData** pan const char* path = current_dir->entry[i].path; if (!current_dir->entry[i].marked) continue; - if (!(filetype & (GAME_CIA|GAME_TMD)) && + if (!(filetype & (GAME_CIA|GAME_TMD|GAME_NCSD|GAME_NCCH)) && !ShowProgress(n_processed++, n_marked, path)) break; if (!(IdentifyFileType(path) & filetype & TYPE_BASE)) { n_other++; @@ -1492,7 +1492,8 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, PaneData** pan char lpathstr[32+1]; TruncateString(lpathstr, path, 32, 8); if (ShowPrompt(true, "%s\nVerification failed\n \nContinue?", lpathstr)) { - if (!(filetype & (GAME_CIA|GAME_TMD))) ShowProgress(0, n_marked, path); // restart progress bar + if (!(filetype & (GAME_CIA|GAME_TMD|GAME_NCSD|GAME_NCCH))) + ShowProgress(0, n_marked, path); // restart progress bar continue; } else break; } diff --git a/arm9/source/utils/gameutil.c b/arm9/source/utils/gameutil.c index 323dcef..3c5ad79 100644 --- a/arm9/source/utils/gameutil.c +++ b/arm9/source/utils/gameutil.c @@ -376,6 +376,7 @@ u32 VerifyNcchFile(const char* path, u32 offset, u32 size) { } // thorough exefs verification (workaround for Process9) + if (!ShowProgress(0, 0, path)) return 1; if ((ncch.size_exefs > 0) && (memcmp(exthdr.name, "Process9", 8) != 0)) { for (u32 i = 0; !ver_exefs && (i < 10); i++) { ExeFsFileHeader* exefile = exefs.files + i; @@ -385,6 +386,93 @@ u32 VerifyNcchFile(const char* path, u32 offset, u32 size) { ver_exefs = CheckNcchHash(hash, &file, exefile->size, offset, &ncch, &exefs); } } + + // thorough romfs verification + if (!ver_romfs && (ncch.size_romfs > 0)) { + UINT btr; + + // load ivfc header + RomFsIvfcHeader ivfc; + fvx_lseek(&file, offset + (ncch.offset_romfs * NCCH_MEDIA_UNIT)); + if ((fvx_read(&file, &ivfc, sizeof(RomFsIvfcHeader), &btr) != FR_OK) || + (DecryptNcch((u8*) &ivfc, ncch.offset_romfs * NCCH_MEDIA_UNIT, sizeof(RomFsIvfcHeader), &ncch, NULL) != 0) ) + ver_romfs = 1; + + // load data + u64 lvl1_size = 0; + u64 lvl2_size = 0; + u8* masterhash = NULL; + u8* lvl1_data = NULL; + u8* lvl2_data = NULL; + if (!ver_romfs && (ValidateRomFsHeader(&ivfc, ncch.size_romfs * NCCH_MEDIA_UNIT) == 0)) { + // load masterhash(es) + masterhash = malloc(ivfc.size_masterhash); + if (masterhash) { + u64 offset_add = (ncch.offset_romfs * NCCH_MEDIA_UNIT) + sizeof(RomFsIvfcHeader); + fvx_lseek(&file, offset + offset_add); + if ((fvx_read(&file, masterhash, ivfc.size_masterhash, &btr) != FR_OK) || + (DecryptNcch(masterhash, offset_add, ivfc.size_masterhash, &ncch, NULL) != 0)) + ver_romfs = 1; + } + + // load lvl1 + lvl1_size = align(ivfc.size_lvl1, 1 << ivfc.log_lvl1); + lvl1_data = malloc(lvl1_size); + if (lvl1_data) { + u64 offset_add = (ncch.offset_romfs * NCCH_MEDIA_UNIT) + GetRomFsLvOffset(&ivfc, 1); + fvx_lseek(&file, offset + offset_add); + if ((fvx_read(&file, lvl1_data, lvl1_size, &btr) != FR_OK) || + (DecryptNcch(lvl1_data, offset_add, lvl1_size, &ncch, NULL) != 0)) + ver_romfs = 1; + } + + // load lvl2 + lvl2_size = align(ivfc.size_lvl2, 1 << ivfc.log_lvl2); + lvl2_data = malloc(lvl2_size); + if (lvl2_data) { + u64 offset_add = (ncch.offset_romfs * NCCH_MEDIA_UNIT) + GetRomFsLvOffset(&ivfc, 2); + fvx_lseek(&file, offset + offset_add); + if ((fvx_read(&file, lvl2_data, lvl2_size, &btr) != FR_OK) || + (DecryptNcch(lvl2_data, offset_add, lvl2_size, &ncch, NULL) != 0)) + ver_romfs = 1; + } + + // check mallocs + if (!masterhash || !lvl1_data || !lvl2_data) + ver_romfs = 1; // should never happen + } + + // actual verification + if (!ver_romfs) { + // verify lvl1 + u32 n_blocks = lvl1_size >> ivfc.log_lvl1; + u32 block_log = ivfc.log_lvl1; + for (u32 i = 0; !ver_romfs && (i < n_blocks); i++) + ver_romfs = (u32) sha_cmp(masterhash + (i*0x20), lvl1_data + (i<> ivfc.log_lvl2; + block_log = ivfc.log_lvl2; + for (u32 i = 0; !ver_romfs && (i < n_blocks); i++) { + ver_romfs = sha_cmp(lvl1_data + (i*0x20), lvl2_data + (i<> ivfc.log_lvl3; + block_log = ivfc.log_lvl3; + fvx_lseek(&file, offset + offset_add); + for (u32 i = 0; !ver_romfs && (i < n_blocks); i++) { + ver_romfs = CheckNcchHash(lvl2_data + (i*0x20), &file, 1 << block_log, offset, &ncch, NULL); + offset_add += 1 << block_log; + if (!(i % 16) && !ShowProgress(i+1, n_blocks, path)) ver_romfs = 1; + } + } + + if (masterhash) free(masterhash); + if (lvl1_data) free(lvl1_data); + if (lvl2_data) free(lvl2_data); + } if (!offset && (ver_exthdr|ver_exefs|ver_romfs)) { // verification summary ShowPrompt(false, "%s\nNCCH verification failed:\nExtHdr/ExeFS/RomFS: %s/%s/%s", pathstr, diff --git a/arm9/source/virtual/vgame.c b/arm9/source/virtual/vgame.c index db80a4b..011fb7c 100644 --- a/arm9/source/virtual/vgame.c +++ b/arm9/source/virtual/vgame.c @@ -875,22 +875,20 @@ bool OpenVGameDir(VirtualDir* vdir, VirtualFile* ventry) { if (!BuildVGameExeFsDir()) return false; } else if ((vdir->flags & VFLAG_ROMFS) && (offset_romfs != vdir->offset)) { offset_nitro = (u64) -1; // mutually exclusive - // validate romFS magic - u8 magic[] = { ROMFS_MAGIC }; - u8 header[sizeof(magic)]; - if ((ReadNcchImageBytes(header, vdir->offset, sizeof(magic)) != 0) || - (memcmp(magic, header, sizeof(magic)) != 0)) + // validate ivfc header + RomFsIvfcHeader ivfc; + if ((ReadNcchImageBytes(&ivfc, vdir->offset, sizeof(RomFsIvfcHeader)) != 0) || + (ValidateRomFsHeader(&ivfc, 0) != 0)) return false; // validate lv3 header RomFsLv3Header lv3; - for (u32 i = 1; i < 8; i++) { - offset_lv3 = vdir->offset + (i*OFFSET_LV3); - if (ReadNcchImageBytes(&lv3, offset_lv3, sizeof(RomFsLv3Header)) != 0) - return false; - if (ValidateLv3Header(&lv3, 0) == 0) - break; + offset_lv3 = vdir->offset + GetRomFsLvOffset(&ivfc, 3); + if ((ReadNcchImageBytes(&lv3, offset_lv3, sizeof(RomFsLv3Header)) != 0) || + (ValidateLv3Header(&lv3, 0) != 0)) { offset_lv3 = (u64) -1; + return false; } + // set up filesystem buffer if (vgame_fs_buffer) free(vgame_fs_buffer); vgame_fs_buffer = malloc(lv3.offset_filedata); if (!vgame_fs_buffer || (offset_lv3 == (u64) -1) ||