Full / proper verification for romFS in NCCH

This commit is contained in:
d0k3 2018-09-25 01:42:52 +02:00
parent 5c138e1219
commit 8f24ccec0c
5 changed files with 162 additions and 15 deletions

View File

@ -1,7 +1,44 @@
#include "romfs.h" #include "romfs.h"
#include "utf.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) { u32 ValidateLv3Header(RomFsLv3Header* lv3, u32 max_size) {
return ((lv3->size_header == 0x28) && return ((lv3->size_header == 0x28) &&
(lv3->offset_dirhash >= lv3->size_header) && (lv3->offset_dirhash >= lv3->size_header) &&

View File

@ -2,7 +2,7 @@
#include "common.h" #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 OFFSET_LV3 0x1000
#define LV3_GET_DIR(offset, idx) \ #define LV3_GET_DIR(offset, idx) \
@ -26,6 +26,26 @@
((idx)->dirmeta + (dm)->offset_parent : NULL))) ((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 { typedef struct {
u32 size_header; u32 size_header;
u32 offset_dirhash; u32 offset_dirhash;
@ -72,6 +92,9 @@ typedef struct {
u32 size_filemeta; u32 size_filemeta;
} __attribute__((packed)) RomFsLv3Index; } __attribute__((packed)) RomFsLv3Index;
u64 GetRomFsLvOffset(RomFsIvfcHeader* ivfc, u32 lvl);
u32 ValidateRomFsHeader(RomFsIvfcHeader* ivfc, u32 max_size);
u32 ValidateLv3Header(RomFsLv3Header* lv3, u32 max_size); u32 ValidateLv3Header(RomFsLv3Header* lv3, u32 max_size);
u32 BuildLv3Index(RomFsLv3Index* index, u8* lv3); u32 BuildLv3Index(RomFsLv3Index* index, u8* lv3);
u32 HashLv3Path(u16* wname, u32 name_len, u32 offset_parent); u32 HashLv3Path(u16* wname, u32 name_len, u32 offset_parent);

View File

@ -1479,7 +1479,7 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, PaneData** pan
const char* path = current_dir->entry[i].path; const char* path = current_dir->entry[i].path;
if (!current_dir->entry[i].marked) if (!current_dir->entry[i].marked)
continue; continue;
if (!(filetype & (GAME_CIA|GAME_TMD)) && if (!(filetype & (GAME_CIA|GAME_TMD|GAME_NCSD|GAME_NCCH)) &&
!ShowProgress(n_processed++, n_marked, path)) break; !ShowProgress(n_processed++, n_marked, path)) break;
if (!(IdentifyFileType(path) & filetype & TYPE_BASE)) { if (!(IdentifyFileType(path) & filetype & TYPE_BASE)) {
n_other++; n_other++;
@ -1492,7 +1492,8 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, PaneData** pan
char lpathstr[32+1]; char lpathstr[32+1];
TruncateString(lpathstr, path, 32, 8); TruncateString(lpathstr, path, 32, 8);
if (ShowPrompt(true, "%s\nVerification failed\n \nContinue?", lpathstr)) { 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; continue;
} else break; } else break;
} }

View File

@ -376,6 +376,7 @@ u32 VerifyNcchFile(const char* path, u32 offset, u32 size) {
} }
// thorough exefs verification (workaround for Process9) // thorough exefs verification (workaround for Process9)
if (!ShowProgress(0, 0, path)) return 1;
if ((ncch.size_exefs > 0) && (memcmp(exthdr.name, "Process9", 8) != 0)) { if ((ncch.size_exefs > 0) && (memcmp(exthdr.name, "Process9", 8) != 0)) {
for (u32 i = 0; !ver_exefs && (i < 10); i++) { for (u32 i = 0; !ver_exefs && (i < 10); i++) {
ExeFsFileHeader* exefile = exefs.files + 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); 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<<block_log), 1<<block_log, SHA256_MODE);
// verify lvl2
n_blocks = lvl2_size >> 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<<block_log), 1<<block_log, SHA256_MODE);
}
// lvl3 verification (this will take long)
u64 offset_add = (ncch.offset_romfs * NCCH_MEDIA_UNIT) + GetRomFsLvOffset(&ivfc, 3);
n_blocks = align(ivfc.size_lvl3, 1 << ivfc.log_lvl3) >> 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 if (!offset && (ver_exthdr|ver_exefs|ver_romfs)) { // verification summary
ShowPrompt(false, "%s\nNCCH verification failed:\nExtHdr/ExeFS/RomFS: %s/%s/%s", pathstr, ShowPrompt(false, "%s\nNCCH verification failed:\nExtHdr/ExeFS/RomFS: %s/%s/%s", pathstr,

View File

@ -875,22 +875,20 @@ bool OpenVGameDir(VirtualDir* vdir, VirtualFile* ventry) {
if (!BuildVGameExeFsDir()) return false; if (!BuildVGameExeFsDir()) return false;
} else if ((vdir->flags & VFLAG_ROMFS) && (offset_romfs != vdir->offset)) { } else if ((vdir->flags & VFLAG_ROMFS) && (offset_romfs != vdir->offset)) {
offset_nitro = (u64) -1; // mutually exclusive offset_nitro = (u64) -1; // mutually exclusive
// validate romFS magic // validate ivfc header
u8 magic[] = { ROMFS_MAGIC }; RomFsIvfcHeader ivfc;
u8 header[sizeof(magic)]; if ((ReadNcchImageBytes(&ivfc, vdir->offset, sizeof(RomFsIvfcHeader)) != 0) ||
if ((ReadNcchImageBytes(header, vdir->offset, sizeof(magic)) != 0) || (ValidateRomFsHeader(&ivfc, 0) != 0))
(memcmp(magic, header, sizeof(magic)) != 0))
return false; return false;
// validate lv3 header // validate lv3 header
RomFsLv3Header lv3; RomFsLv3Header lv3;
for (u32 i = 1; i < 8; i++) { offset_lv3 = vdir->offset + GetRomFsLvOffset(&ivfc, 3);
offset_lv3 = vdir->offset + (i*OFFSET_LV3); if ((ReadNcchImageBytes(&lv3, offset_lv3, sizeof(RomFsLv3Header)) != 0) ||
if (ReadNcchImageBytes(&lv3, offset_lv3, sizeof(RomFsLv3Header)) != 0) (ValidateLv3Header(&lv3, 0) != 0)) {
return false;
if (ValidateLv3Header(&lv3, 0) == 0)
break;
offset_lv3 = (u64) -1; offset_lv3 = (u64) -1;
return false;
} }
// set up filesystem buffer
if (vgame_fs_buffer) free(vgame_fs_buffer); if (vgame_fs_buffer) free(vgame_fs_buffer);
vgame_fs_buffer = malloc(lv3.offset_filedata); vgame_fs_buffer = malloc(lv3.offset_filedata);
if (!vgame_fs_buffer || (offset_lv3 == (u64) -1) || if (!vgame_fs_buffer || (offset_lv3 == (u64) -1) ||