Improved FIRM detection / verification routines

This commit is contained in:
d0k3 2017-02-03 02:21:16 +01:00
parent d2e16c9de5
commit 1ad48969ca
8 changed files with 68 additions and 28 deletions

View File

@ -36,3 +36,9 @@ void sha_quick(void* res, const void* src, u32 size, u32 mode) {
sha_update(src, size); sha_update(src, size);
sha_get(res); 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);
}

View File

@ -26,3 +26,4 @@ void sha_init(u32 mode);
void sha_update(const void* src, u32 size); void sha_update(const void* src, u32 size);
void sha_get(void* res); void sha_get(void* res);
void sha_quick(void* res, const void* src, u32 size, u32 mode); 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);

View File

@ -6,8 +6,8 @@
u32 IdentifyFileType(const char* path) { u32 IdentifyFileType(const char* path) {
const u8 romfs_magic[] = { ROMFS_MAGIC }; const u8 romfs_magic[] = { ROMFS_MAGIC };
const u8 firm_magic[] = { FIRM_MAGIC };
u8 header[0x200] __attribute__((aligned(32))); // minimum required size u8 header[0x200] __attribute__((aligned(32))); // minimum required size
void* data = (void*) header;
size_t fsize = FileGetSize(path); size_t fsize = FileGetSize(path);
char* fname = strrchr(path, '/'); char* fname = strrchr(path, '/');
char* ext = (fname) ? strrchr(++fname, '.') : NULL; char* ext = (fname) ? strrchr(++fname, '.') : NULL;
@ -20,42 +20,42 @@ u32 IdentifyFileType(const char* path) {
return IMG_NAND; // NAND image return IMG_NAND; // NAND image
} else if (ValidateFatHeader(header) == 0) { } else if (ValidateFatHeader(header) == 0) {
return IMG_FAT; // FAT image file return IMG_FAT; // FAT image file
} else if (ValidateMbrHeader((MbrHeader*) (void*) header) == 0) { } else if (ValidateMbrHeader((MbrHeader*) data) == 0) {
MbrHeader* mbr = (MbrHeader*) (void*) header; MbrHeader* mbr = (MbrHeader*) data;
MbrPartitionInfo* partition0 = mbr->partitions; MbrPartitionInfo* partition0 = mbr->partitions;
if ((partition0->sector + partition0->count) <= (fsize / 0x200)) // size check if ((partition0->sector + partition0->count) <= (fsize / 0x200)) // size check
return IMG_FAT; // possibly an MBR -> also treat as FAT image 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 // this only works because these functions ignore CIA content index
CiaInfo info; CiaInfo info;
GetCiaInfo(&info, (CiaHeader*) header); GetCiaInfo(&info, (CiaHeader*) header);
if (fsize >= info.size_cia) if (fsize >= info.size_cia)
return GAME_CIA; // CIA file return GAME_CIA; // CIA file
} else if (ValidateNcsdHeader((NcsdHeader*) (void*) header) == 0) { } else if (ValidateNcsdHeader((NcsdHeader*) data) == 0) {
NcsdHeader* ncsd = (NcsdHeader*) (void*) header; NcsdHeader* ncsd = (NcsdHeader*) data;
if (fsize >= GetNcsdTrimmedSize(ncsd)) if (fsize >= GetNcsdTrimmedSize(ncsd))
return GAME_NCSD; // NCSD (".3DS") file return GAME_NCSD; // NCSD (".3DS") file
} else if (ValidateNcchHeader((NcchHeader*) (void*) header) == 0) { } else if (ValidateNcchHeader((NcchHeader*) data) == 0) {
NcchHeader* ncch = (NcchHeader*) (void*) header; NcchHeader* ncch = (NcchHeader*) data;
u32 type = GAME_NCCH | (NCCH_IS_CXI(ncch) ? FLAG_CXI : 0); u32 type = GAME_NCCH | (NCCH_IS_CXI(ncch) ? FLAG_CXI : 0);
if (fsize >= (ncch->size * NCCH_MEDIA_UNIT)) if (fsize >= (ncch->size * NCCH_MEDIA_UNIT))
return type; // NCCH (".APP") file 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) return GAME_EXEFS; // ExeFS file (false positives possible)
} else if (memcmp(header, romfs_magic, sizeof(romfs_magic)) == 0) { } else if (memcmp(header, romfs_magic, sizeof(romfs_magic)) == 0) {
return GAME_ROMFS; // RomFS file (check could be better) return GAME_ROMFS; // RomFS file (check could be better)
} else if (strncmp(TMD_ISSUER, (char*) (header + 0x140), 0x40) == 0) { } else if (strncmp(TMD_ISSUER, (char*) (header + 0x140), 0x40) == 0) {
if (fsize >= TMD_SIZE_N(getbe16(header + 0x1DE))) if (fsize >= TMD_SIZE_N(getbe16(header + 0x1DE)))
return GAME_TMD; // TMD file 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 return SYS_FIRM; // FIRM file
} }
} }
if ((fsize > sizeof(BossHeader)) && if ((fsize > sizeof(BossHeader)) &&
(ValidateBossHeader((BossHeader*) (void*) header, fsize) == 0)) { (ValidateBossHeader((BossHeader*) data, fsize) == 0)) {
return GAME_BOSS; // BOSS (SpotPass) file return GAME_BOSS; // BOSS (SpotPass) file
} else if ((fsize > sizeof(NcchInfoHeader)) && } else if ((fsize > sizeof(NcchInfoHeader)) &&
(GetNcchInfoVersion((NcchInfoHeader*) (void*) header)) && (GetNcchInfoVersion((NcchInfoHeader*) data)) &&
fname && (strncasecmp(fname, NCCHINFO_NAME, 32) == 0)) { fname && (strncasecmp(fname, NCCHINFO_NAME, 32) == 0)) {
return BIN_NCCHNFO; // ncchinfo.bin file return BIN_NCCHNFO; // ncchinfo.bin file
#if PAYLOAD_MAX_SIZE <= TEMP_BUFFER_SIZE #if PAYLOAD_MAX_SIZE <= TEMP_BUFFER_SIZE

View File

@ -8,9 +8,23 @@
// 0 -> pre 9.5 / 1 -> 9.5 / 2 -> post 9.5 // 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)
u32 ValidateFirmHeader(FirmHeader* header) { u32 ValidateFirmHeader(FirmHeader* header, u32 data_size) {
u8 magic[] = { FIRM_MAGIC }; 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) { 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, 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 0x0E, 0x05, 0x1D, 0x1E, 0xF3, 0x41, 0xE9, 0x80, 0x1E, 0xC9, 0x97, 0x82, 0xA0, 0x84, 0x43, 0x08
}; };
u8 hash[0x20]; return sha_cmp(enckeyX0x15hash, header->keyX0x15, 0x10, SHA256_MODE);
sha_quick(hash, header->keyX0x15, 0x10, SHA256_MODE);
return memcmp(hash, enckeyX0x15hash, 0x20);
} }
FirmSectionHeader* FindFirmArm9Section(FirmHeader* firm) { FirmSectionHeader* FindFirmArm9Section(FirmHeader* firm) {
@ -194,7 +206,7 @@ u32 DecryptFirmSequential(u8* data, u32 offset, u32 size) {
// fetch firm header from data // fetch firm header from data
if ((offset == 0) && (size >= sizeof(FirmHeader))) { if ((offset == 0) && (size >= sizeof(FirmHeader))) {
memcpy(&firm, data, 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; arm9s = (firmptr) ? FindFirmArm9Section(firmptr) : NULL;
a9lptr = NULL; a9lptr = NULL;
} }

View File

@ -7,6 +7,7 @@
#define SECTOR_NAME "sector0x96.bin" #define SECTOR_NAME "sector0x96.bin"
#define SECRET_NAME "secret_sector.bin" #define SECRET_NAME "secret_sector.bin"
#define FIRM_MAX_SIZE 0x400000 // 4MB, due to FIRM partition size
#define ARM9BIN_OFFSET 0x800 #define ARM9BIN_OFFSET 0x800
// see: https://www.3dbrew.org/wiki/FIRM#Firmware_Section_Headers // see: https://www.3dbrew.org/wiki/FIRM#Firmware_Section_Headers
@ -42,7 +43,7 @@ typedef struct {
u8 padding[0x1A0]; u8 padding[0x1A0];
} __attribute__((packed, aligned(16))) FirmA9LHeader; } __attribute__((packed, aligned(16))) FirmA9LHeader;
u32 ValidateFirmHeader(FirmHeader* header); u32 ValidateFirmHeader(FirmHeader* header, u32 data_size);
u32 ValidateFirmA9LHeader(FirmA9LHeader* header); u32 ValidateFirmA9LHeader(FirmA9LHeader* header);
FirmSectionHeader* FindFirmArm9Section(FirmHeader* firm); FirmSectionHeader* FindFirmArm9Section(FirmHeader* firm);

View File

@ -471,12 +471,15 @@ u32 VerifyFirmFile(const char* path) {
FIL file; FIL file;
UINT btr; UINT btr;
char pathstr[32 + 1];
TruncateString(pathstr, path, 32, 8);
// open file, get FIRM header // open file, get FIRM header
if (fvx_open(&file, path, FA_READ | FA_OPEN_EXISTING) != FR_OK) if (fvx_open(&file, path, FA_READ | FA_OPEN_EXISTING) != FR_OK)
return 1; return 1;
fvx_lseek(&file, 0); fvx_lseek(&file, 0);
if ((fvx_read(&file, &header, sizeof(FirmHeader), &btr) != FR_OK) || if ((fvx_read(&file, &header, sizeof(FirmHeader), &btr) != FR_OK) ||
(ValidateFirmHeader(&header) != 0)) { (ValidateFirmHeader(&header, fvx_size(&file)) != 0)) {
fvx_close(&file); fvx_close(&file);
return 1; return 1;
} }
@ -496,8 +499,6 @@ u32 VerifyFirmFile(const char* path) {
u8 hash[0x20]; u8 hash[0x20];
sha_get(hash); sha_get(hash);
if (memcmp(hash, section->hash, 0x20) != 0) { 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); ShowPrompt(false, "%s\nSection %u hash mismatch", pathstr, i);
fvx_close(&file); fvx_close(&file);
return 1; return 1;
@ -505,6 +506,25 @@ u32 VerifyFirmFile(const char* path) {
} }
fvx_close(&file); 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; return 0;
} }
@ -625,7 +645,7 @@ u32 CheckEncryptedFirmFile(const char* path) {
return 1; return 1;
fvx_lseek(&file, 0); fvx_lseek(&file, 0);
if ((fvx_read(&file, &header, sizeof(FirmHeader), &btr) != FR_OK) || if ((fvx_read(&file, &header, sizeof(FirmHeader), &btr) != FR_OK) ||
(ValidateFirmHeader(&header) != 0)) { (ValidateFirmHeader(&header, fvx_size(&file)) != 0)) {
fvx_close(&file); fvx_close(&file);
return 1; return 1;
} }
@ -714,7 +734,7 @@ u32 CryptNcchNcsdBossFirmFile(const char* orig, const char* dest, u32 mode, u16
u32 ret = 0; u32 ret = 0;
if (!ShowProgress(offset, fsize, dest)) ret = 1; if (!ShowProgress(offset, fsize, dest)) ret = 1;
if (mode & (GAME_NCCH|GAME_NCSD|GAME_BOSS|SYS_FIRM)) { // for NCCH / NCSD / BOSS / FIRM files 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)); u32 read_bytes = min(MAIN_BUFFER_SIZE, (size - i));
UINT bytes_read, bytes_written; UINT bytes_read, bytes_written;
if (fvx_read(ofp, MAIN_BUFFER, read_bytes, &bytes_read) != FR_OK) ret = 1; 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); GetTmdCtr(ctr, chunk);
fvx_lseek(ofp, offset); fvx_lseek(ofp, offset);
sha_init(SHA256_MODE); 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)); u32 read_bytes = min(MAIN_BUFFER_SIZE, (size - i));
if (fvx_read(ofp, MAIN_BUFFER, read_bytes, &bytes_read) != FR_OK) ret = 1; 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; 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; return 1;
fvx_lseek(&file, 0); fvx_lseek(&file, 0);
if ((fvx_read(&file, &firm, sizeof(FirmHeader), &btr) != FR_OK) || if ((fvx_read(&file, &firm, sizeof(FirmHeader), &btr) != FR_OK) ||
(ValidateFirmHeader(&firm) != 0)) { (ValidateFirmHeader(&firm, fvx_size(&file)) != 0)) {
fvx_close(&file); fvx_close(&file);
return 1; return 1;
} }

View File

@ -77,7 +77,7 @@ u32 ValidateNandDump(const char* path) {
for (u32 i = 0; i < sizeof(firm_sectors) / sizeof(u32); i++) { for (u32 i = 0; i < sizeof(firm_sectors) / sizeof(u32); i++) {
u32 keyslot = 0x06; u32 keyslot = 0x06;
if ((ReadNandFile(&file, &firm, firm_sectors[i], 1, keyslot) != 0) || 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 (getbe32(firm.dec_magic) != 0)) { // decrypted firms are not allowed
ShowPrompt(false, "%s\nError: FIRM%u header is corrupt", pathstr, i); ShowPrompt(false, "%s\nError: FIRM%u header is corrupt", pathstr, i);
fvx_close(&file); fvx_close(&file);

View File

@ -490,7 +490,7 @@ bool OpenVGameDir(VirtualDir* vdir, VirtualFile* ventry) {
// build directories where required // build directories where required
if ((vdir->flags & VFLAG_FIRM) && (offset_firm != vdir->offset)) { if ((vdir->flags & VFLAG_FIRM) && (offset_firm != vdir->offset)) {
if ((ReadImageBytes((u8*) firm, 0, sizeof(FirmHeader)) != 0) || if ((ReadImageBytes((u8*) firm, 0, sizeof(FirmHeader)) != 0) ||
(ValidateFirmHeader(firm) != 0)) return false; (ValidateFirmHeader(firm, 0) != 0)) return false;
offset_firm = vdir->offset; offset_firm = vdir->offset;
FirmSectionHeader* arm9s = FindFirmArm9Section(firm); FirmSectionHeader* arm9s = FindFirmArm9Section(firm);
if (arm9s && (ReadImageBytes((u8*) a9l, arm9s->offset, sizeof(FirmA9LHeader)) == 0) && if (arm9s && (ReadImageBytes((u8*) a9l, arm9s->offset, sizeof(FirmA9LHeader)) == 0) &&