better FIRM checks, fix unchecked NULL that triggered a data abort

This commit is contained in:
Wolfvak 2019-08-15 23:20:14 -03:00 committed by d0k3
parent c8a7c63963
commit 1a0fe17b6c
2 changed files with 92 additions and 55 deletions

View File

@ -7,7 +7,7 @@
#include "ff.h" #include "ff.h"
// 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)
// valid addresses for FIRM section loading // valid addresses for FIRM section loading
// pairs of start / end address, provided by Wolfvak // pairs of start / end address, provided by Wolfvak
@ -26,30 +26,43 @@
FIRM_VALID_ADDRESS, \ FIRM_VALID_ADDRESS, \
0x20000000, 0x27FFFA00 0x20000000, 0x27FFFA00
static const u32 whitelist_boot[] = { FIRM_VALID_ADDRESS_BOOT };
static const u32 whitelist_install[] = { FIRM_VALID_ADDRESS_INSTALL };
#define WLIST(i) ((i) ? whitelist_install : whitelist_boot)
#define WLIST_SZ(i) (((i) ? sizeof(whitelist_install) : sizeof(whitelist_boot)) / (sizeof(u32) * 2))
#define ADDR_IN_RANGE(a, s, e) (clamp(a, s, e) == (a))
#define ADDR_IN_SECTION(a, s) (ADDR_IN_RANGE(a, (s)->address, (s)->address + (s)->size))
u32 GetFirmSize(FirmHeader* header) { u32 GetFirmSize(FirmHeader* header) {
u8 magic[] = { FIRM_MAGIC }; static const u8 magic[] = { FIRM_MAGIC };
if (memcmp(header->magic, magic, sizeof(magic)) != 0) if (memcmp(header->magic, magic, sizeof(magic)) != 0)
return 0; return 0;
u32 firm_size = sizeof(FirmHeader); u32 firm_size = sizeof(FirmHeader);
int section_arm11 = -1;
int section_arm9 = -1; int section_arm9 = -1;
for (u32 i = 0; i < 4; i++) { for (u32 i = 0; i < 4; i++) {
FirmSectionHeader* section = header->sections + i; FirmSectionHeader* section = header->sections + i;
if (!section->size) continue; if (!section->size)
if (section->offset < firm_size) return 0; continue;
if ((section->offset % 512) || (section->size % 512)) return 0;
if ((header->entry_arm11 >= section->address) && if (section->offset > FIRM_MAX_SIZE || section->size > FIRM_MAX_SIZE)
(header->entry_arm11 < section->address + section->size)) return 0;
section_arm11 = i; if (section->offset < sizeof(FirmHeader))
if ((header->entry_arm9 >= section->address) && return 0;
(header->entry_arm9 < section->address + section->size)) if ((section->offset % 512) || (section->size % 512))
return 0;
if (ADDR_IN_SECTION(header->entry_arm9, section))
section_arm9 = i; section_arm9 = i;
firm_size = section->offset + section->size;
firm_size = max(firm_size, section->offset + section->size);
} }
if (firm_size > FIRM_MAX_SIZE) return 0; if (firm_size > FIRM_MAX_SIZE)
if ((header->entry_arm11 && (section_arm11 < 0)) || (header->entry_arm9 && (section_arm9 < 0))) return 0;
if (section_arm9 < 0)
return 0; return 0;
return firm_size; return firm_size;
@ -61,45 +74,66 @@ u32 ValidateFirmHeader(FirmHeader* header, u32 data_size) {
} }
u32 ValidateFirmA9LHeader(FirmA9LHeader* header) { u32 ValidateFirmA9LHeader(FirmA9LHeader* header) {
const u8 enckeyX0x15hash[0x20] = { static const u8 enckeyX0x15hash[2][0x20] =
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 {
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
},
{
0xFC, 0x46, 0x74, 0x78, 0x73, 0x01, 0xD3, 0x23, 0x52, 0x94, 0x97, 0xED, 0xA8, 0x5B, 0xCF, 0xD2,
0xDA, 0x2D, 0xFA, 0x47, 0x8E, 0x2D, 0x98, 0x89, 0xBA, 0x60, 0xE8, 0x43, 0x5C, 0x1B, 0x93, 0x65
}
}; };
const u8 enckeyX0x15devhash[0x20] = {
0xFC, 0x46, 0x74, 0x78, 0x73, 0x01, 0xD3, 0x23, 0x52, 0x94, 0x97, 0xED, 0xA8, 0x5B, 0xCF, 0xD2, return sha_cmp(enckeyX0x15hash[IS_DEVKIT ? 1 : 0], header->keyX0x15, 0x10, SHA256_MODE);
0xDA, 0x2D, 0xFA, 0x47, 0x8E, 0x2D, 0x98, 0x89, 0xBA, 0x60, 0xE8, 0x43, 0x5C, 0x1B, 0x93, 0x65,
};
return sha_cmp((IS_DEVKIT) ? enckeyX0x15devhash : enckeyX0x15hash, header->keyX0x15, 0x10, SHA256_MODE);
} }
u32 ValidateFirm(void* firm, u32 firm_size, bool installable) { u32 ValidateFirm(void* firm, u32 firm_size, bool installable) {
FirmHeader* header = (FirmHeader*) firm; FirmHeader* header = (FirmHeader*) firm;
u32 skipchk_mask = 0;
const u32 *whitelist = WLIST(installable);
u32 whitelist_size = WLIST_SZ(installable);
// validate firm header // validate firm header
if ((firm_size < sizeof(FirmHeader)) || (ValidateFirmHeader(header, firm_size) != 0)) if ((firm_size < sizeof(FirmHeader)) || (ValidateFirmHeader(header, firm_size) != 0))
return 1; return 1;
// overrides for b9s / superhax fb3ds firms // overrides for b9s / superhax fb3ds firms
bool b9s_fix = installable && (memcmp(&(header->reserved0[0x2D]), "B9S", 3) == 0); if (installable) {
bool fb3ds_fix = installable && (header->sections[1].size == 0x200) && const u8 *resv = header->reserved0;
(header->sections[1].address == 0x07FFFE8C); if (resv[0x2D] == 'B' && resv[0x2E] == '9' && resv[0x2F] == 'S')
skipchk_mask |= BIT(3);
if ((header->sections[1].size = 0x200) &&
(header->sections[1].address = 0x07FFFE8C))
skipchk_mask |= BIT(1);
}
// hash verify all available sections and check load address // hash verify all available sections and check load address
for (u32 i = 0; i < 4; i++) { for (u32 i = 0; i < 4; i++) {
u32 whitelist_boot[] = { FIRM_VALID_ADDRESS_BOOT }; bool bad_loadaddr;
u32 whitelist_install[] = { FIRM_VALID_ADDRESS_INSTALL };
u32* whitelist = (installable) ? whitelist_install : whitelist_boot;
u32 whitelist_size = ((installable) ? sizeof(whitelist_install) : sizeof(whitelist_boot)) / (2*sizeof(u32));
FirmSectionHeader* section = header->sections + i; FirmSectionHeader* section = header->sections + i;
if (!section->size) continue;
if ((skipchk_mask & BIT(i)) || !section->size)
continue;
if (sha_cmp(section->hash, ((u8*) firm) + section->offset, section->size, SHA256_MODE) != 0) if (sha_cmp(section->hash, ((u8*) firm) + section->offset, section->size, SHA256_MODE) != 0)
return 1; return 1;
bool is_whitelisted = (b9s_fix && (i == 3)) || (fb3ds_fix && (i == 1)); // b9s / fb3ds overrides
for (u32 a = 0; (a < whitelist_size) && !is_whitelisted; a++) { bad_loadaddr = true;
if ((section->address >= whitelist[2*a]) && (section->address + section->size <= whitelist[(2*a)+1])) for (u32 a = 0; a < whitelist_size; a++) {
is_whitelisted = true; u32 start = whitelist[2 * a], end = whitelist[(2 * a) + 1];
if (ADDR_IN_RANGE(section->address, start, end) &&
ADDR_IN_RANGE(section->address + section->size, start, end)) {
bad_loadaddr = false;
break;
}
} }
if (!is_whitelisted) return 1;
if (bad_loadaddr)
return 1;
} }
// ARM9 / ARM11 entrypoints available? // ARM9 / ARM11 entrypoints available?
@ -114,9 +148,10 @@ u32 ValidateFirm(void* firm, u32 firm_size, bool installable) {
} }
FirmSectionHeader* FindFirmArm9Section(FirmHeader* firm) { FirmSectionHeader* FindFirmArm9Section(FirmHeader* firm) {
u32 entry = firm->entry_arm9;
for (u32 i = 0; i < 4; i++) { for (u32 i = 0; i < 4; i++) {
FirmSectionHeader* section = firm->sections + i; FirmSectionHeader* section = firm->sections + i;
if (section->size && (section->method == FIRM_NDMA_CPY)) if (section->size && ADDR_IN_SECTION(entry, section))
return section; return section;
} }
return NULL; return NULL;

View File

@ -76,6 +76,8 @@ u32 BootFirmHandler(const char* bootpath, bool verbose, bool delete) {
// encrypted firm handling // encrypted firm handling
FirmSectionHeader* arm9s = FindFirmArm9Section(firm); FirmSectionHeader* arm9s = FindFirmArm9Section(firm);
if (!arm9s) return 1;
FirmA9LHeader* a9l = (FirmA9LHeader*)(void*) ((u8*) firm + arm9s->offset); FirmA9LHeader* a9l = (FirmA9LHeader*)(void*) ((u8*) firm + arm9s->offset);
if (verbose && (ValidateFirmA9LHeader(a9l) == 0) && if (verbose && (ValidateFirmA9LHeader(a9l) == 0) &&
ShowPrompt(true, "%s\nFIRM is encrypted.\n \nDecrypt before boot?", pathstr) && ShowPrompt(true, "%s\nFIRM is encrypted.\n \nDecrypt before boot?", pathstr) &&