mirror of
https://github.com/d0k3/GodMode9.git
synced 2025-06-26 21:52:48 +00:00
Improved FIRM detection / verification routines
This commit is contained in:
parent
d2e16c9de5
commit
1ad48969ca
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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) &&
|
||||
|
Loading…
x
Reference in New Issue
Block a user