Allow NAND safe restore and validation

This commit is contained in:
d0k3 2017-01-02 17:37:08 +01:00
parent f600402cee
commit 9e40ce3e86
9 changed files with 343 additions and 17 deletions

32
source/fs/fatmbr.c Normal file
View File

@ -0,0 +1,32 @@
#include "fatmbr.h"
u32 ValidateMbrHeader(MbrHeader* mbr) {
if (mbr->magic != FATMBR_MAGIC) return 1; // check magic
u32 sector = 1; // check partitions
for (u32 i = 0; i < 4; i++) {
MbrPartitionInfo* partition = mbr->partitions + i;
if (!partition->count && i) continue;
else if (!partition->count) return 1; // first partition can't be empty
if ((partition->type != 0x1) && (partition->type != 0x4) && (partition->type != 0x6) &&
(partition->type != 0xB) && (partition->type != 0xC) && (partition->type != 0xE))
return 1; // bad / unknown filesystem type
if (partition->sector < sector) return 1; // overlapping partitions
sector = partition->sector + partition->count;
}
return 0;
}
u32 ValidateFatHeader(void* fat) {
if (getle16((u8*) fat + 0x1FE) != FATMBR_MAGIC) return 1; // check magic
Fat32Header* fat32 = (Fat32Header*) fat;
if (strncmp(fat32->fs_type, "FAT32 ", 8) == 0)
return 0; // is FAT32 header
Fat16Header* fat16 = (Fat16Header*) fat;
if ((strncmp(fat16->fs_type, "FAT16 ", 8) == 0) ||
(strncmp(fat16->fs_type, "FAT12 ", 8) == 0) ||
(strncmp(fat16->fs_type, "FAT ", 8) == 0))
return 0; // is FAT16 / FAT12 header
if ((getle64(fat16->fs_type) == 0) && (fat16->sct_size == 0x200))
return 0; // special case for public.sav
return 1; // failed, not a FAT header
}

90
source/fs/fatmbr.h Normal file
View File

@ -0,0 +1,90 @@
#pragma once
#include "common.h"
#define FATMBR_MAGIC 0xAA55 // little endian!
typedef struct {
u8 status; // 0x80
u8 chs_start[3]; // 0x01 0x01 0x00
u8 type; // 0x0C
u8 chs_end[3]; // 0xFE 0xFF 0xFF
u32 sector; // 0x2000 (4MB offset, 512 byte sectors)
u32 count;
} __attribute__((packed)) MbrPartitionInfo;
typedef struct {
char text[446];
MbrPartitionInfo partitions[4];
u16 magic; // 0xAA55
} __attribute__((packed)) MbrHeader;
typedef struct { // unused
u32 signature0; // 0x41615252
u8 reserved0[480];
u32 signature1; // 0x61417272
u32 clr_free; // 0xFFFFFFFF
u32 clr_next; // 0xFFFFFFFF
u8 reserved1[14];
u16 magic; // 0xAA55
} __attribute__((packed)) FileSystemInfo;
typedef struct {
u8 jmp[3]; // 0x90 0x00 0xEB
char oemname[8]; // "anything"
u16 sct_size; // 0x0200
u8 clr_size; // 0x40 -> 32kB clusters with 512byte sectors
u16 sct_reserved; // 0x20
u8 fat_n; // 0x02
u16 reserved0; // root entry count in FAT16
u16 reserved1; // partition size when <= 32MB
u8 mediatype; // 0xF8
u16 reserved2; // FAT size in sectors in FAT16
u16 sct_track; // 0x3F
u16 sct_heads; // 0xFF
u32 sct_hidden; // same as partition offset in MBR
u32 sct_total; // same as partition size in MBR
u32 fat_size; // roundup((((sct_total - sct_reserved) / clr_size) * 4) / sct_size)
u16 flags; // 0x00
u16 version; // 0x00
u32 clr_root; // 0x02
u16 sct_fsinfo; // 0x01
u16 sct_backup; // 0x06
u8 reserved3[12];
u8 ndrive; // 0x80
u8 head_cur; // 0x00
u8 boot_sig; // 0x29
u32 vol_id; // volume id / 0x00
char vol_label[11]; // "anything "
char fs_type[8]; // "FAT32 "
u8 reserved4[420];
u16 magic; // 0xAA55
} __attribute__((packed)) Fat32Header;
typedef struct { // this struct is not tested enough!
u8 jmp[3]; // 0x90 0x00 0xEB
char oemname[8]; // "anything"
u16 sct_size; // 0x0200
u8 clr_size; // 0x20 (???) -> 16kB clusters with 512byte sectors
u16 sct_reserved; // 0x01
u8 fat_n; // 0x02
u16 root_n; // 0x0200
u16 reserved0; // partition size when <= 32MB
u8 mediatype; // 0xF8
u16 fat_size; // roundup((((sct_total - sct_reserved) / clr_size) * 2) / sct_size)
u16 sct_track; // 0x3F
u16 sct_heads; // 0xFF
u32 sct_hidden; // same as partition offset in MBR
u32 sct_total; // same as partition size in MBR
u8 ndrive; // 0x80
u8 head_cur; // 0x00
u8 boot_sig; // 0x29
u32 vol_id; // volume id / 0x00
char vol_label[11]; // "anything "
char fs_type[8]; // "FAT16 "
u8 reserved4[448];
u16 magic; // 0xAA55
} __attribute__((packed)) Fat16Header;
u32 ValidateMbrHeader(MbrHeader* mbr);
u32 ValidateFatHeader(void* fat);

View File

@ -1,5 +1,6 @@
#include "filetype.h"
#include "fsutil.h"
#include "fatmbr.h"
#include "game.h"
u32 IdentifyFileType(const char* path) {
@ -12,17 +13,13 @@ u32 IdentifyFileType(const char* path) {
if ((getbe32(header + 0x100) == 0x4E435344) && (getbe64(header + 0x110) == (u64) 0x0104030301000000) &&
(getbe64(header + 0x108) == (u64) 0) && (fsize >= 0x8FC8000)) {
return IMG_NAND; // NAND image
} else if (getbe16(header + 0x1FE) == 0x55AA) { // migt be FAT or MBR
if ((strncmp((char*) header + 0x36, "FAT12 ", 8) == 0) || (strncmp((char*) header + 0x36, "FAT16 ", 8) == 0) ||
(strncmp((char*) header + 0x36, "FAT ", 8) == 0) || (strncmp((char*) header + 0x52, "FAT32 ", 8) == 0) ||
((getle64(header + 0x36) == 0) && (getle16(header + 0x0B) == 0x200))) { // last one is a special case for public.sav
return IMG_FAT; // this is an actual FAT header
} else if (((getle32(header + 0x1BE + 0x8) + getle32(header + 0x1BE + 0xC)) < (fsize / 0x200)) && // check file size
(getle32(header + 0x1BE + 0x8) > 0) && (getle32(header + 0x1BE + 0xC) >= 0x800) && // check first partition sanity
((header[0x1BE + 0x4] == 0x1) || (header[0x1BE + 0x4] == 0x4) || (header[0x1BE + 0x4] == 0x6) || // filesystem type
(header[0x1BE + 0x4] == 0xB) || (header[0x1BE + 0x4] == 0xC) || (header[0x1BE + 0x4] == 0xE))) {
return IMG_FAT; // this might be an MBR -> give it the benefit of doubt
}
} else if (ValidateFatHeader(header) == 0) {
return IMG_FAT; // FAT image file
} else if (ValidateMbrHeader((MbrHeader*) (void*) header) == 0) {
MbrHeader* mbr = (MbrHeader*) (void*) header;
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) {
// this only works because these functions ignore CIA content index
CiaInfo info;

View File

@ -15,9 +15,10 @@
#define SYS_FIRM (1<<8)
#define FTYPE_MOUNTABLE (IMG_FAT|IMG_NAND|GAME_CIA|GAME_NCSD|GAME_NCCH|GAME_EXEFS|GAME_ROMFS|SYS_FIRM)
#define FYTPE_VERIFICABLE (GAME_CIA|GAME_NCSD|GAME_NCCH|GAME_TMD|SYS_FIRM)
#define FYTPE_VERIFICABLE (IMG_NAND|GAME_CIA|GAME_NCSD|GAME_NCCH|GAME_TMD|SYS_FIRM)
#define FYTPE_DECRYPTABLE (GAME_CIA|GAME_NCSD|GAME_NCCH|SYS_FIRM)
#define FTYPE_BUILDABLE (GAME_NCSD|GAME_NCCH|GAME_TMD)
#define FTYPE_BUILDABLE_L (FTYPE_BUILDABLE&(GAME_TMD))
#define FTYPE_RESTORABLE (IMG_NAND)
u32 IdentifyFileType(const char* path);

View File

@ -6,6 +6,7 @@
#include "fsutil.h"
#include "fsperm.h"
#include "gameutil.h"
#include "nandutil.h"
#include "filetype.h"
#include "platform.h"
#include "nand.h"
@ -576,6 +577,7 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, DirStruct* cur
bool decryptable_inplace = (decryptable && (drvtype & (DRV_SDCARD|DRV_RAMDRIVE)));
bool buildable = (filetype & FTYPE_BUILDABLE);
bool buildable_legit = (filetype & FTYPE_BUILDABLE_L);
bool restorable = CheckA9lh() && (filetype & FTYPE_RESTORABLE);
char pathstr[32 + 1];
TruncateString(pathstr, curr_entry->path, 32, 8);
@ -592,7 +594,7 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, DirStruct* cur
(int) ++n_opt : -1;
int searchdrv = (DriveType(current_path) & DRV_SEARCH) ? ++n_opt : -1;
if (special > 0) optionstr[special-1] =
(filetype == IMG_NAND ) ? "Mount as NAND image" :
(filetype == IMG_NAND ) ? "NAND image options..." :
(filetype == IMG_FAT ) ? "Mount as FAT image" :
(filetype == GAME_CIA ) ? "CIA image options..." :
(filetype == GAME_NCSD ) ? "NCSD image options..." :
@ -640,12 +642,14 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, DirStruct* cur
// stuff for special menu starts here
n_opt = 0;
int mount = (mountable) ? ++n_opt : -1;
int restore = (restorable) ? ++n_opt : -1;
int decrypt = (decryptable) ? ++n_opt : -1;
int decrypt_inplace = (decryptable_inplace) ? ++n_opt : -1;
int build = (buildable) ? ++n_opt : -1;
int build_legit = (buildable_legit) ? ++n_opt : -1;
int verify = (verificable) ? ++n_opt : -1;
if (mount > 0) optionstr[mount-1] = "Mount image to drive";
if (restore > 0) optionstr[restore-1] = "Restore SysNAND (safe)";
if (decrypt > 0) optionstr[decrypt-1] = "Decrypt file (SD output)";
if (decrypt_inplace > 0) optionstr[decrypt_inplace-1] = "Decrypt file (inplace)";
if (build > 0) optionstr[build-1] = (build_legit < 0) ? "Build CIA from file" : "Build CIA (standard)";
@ -752,7 +756,7 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, DirStruct* cur
else ShowPrompt(false, "%s\nCIA build failed", pathstr);
}
return 0;
} else if (user_select == verify) { // -> verify game file
} else if (user_select == verify) { // -> verify game / nand file
if ((n_marked > 1) && ShowPrompt(true, "Try to verify all %lu selected files?", n_marked)) {
u32 n_success = 0;
u32 n_other = 0;
@ -768,7 +772,9 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, DirStruct* cur
if (!(filetype & (GAME_CIA|GAME_TMD)) &&
!ShowProgress(n_processed++, n_marked, path)) break;
current_dir->entry[i].marked = false;
if (VerifyGameFile(path) == 0) n_success++;
if (filetype & IMG_NAND) {
if (ValidateNandDump(path) == 0) n_success++;
} else if (VerifyGameFile(path) == 0) n_success++;
else { // on failure: set *cursor on failed title, break;
*cursor = i;
break;
@ -779,10 +785,16 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, DirStruct* cur
else ShowPrompt(false, "%lu/%lu files verified ok", n_success, n_marked);
} else {
ShowString("%s\nVerifying file, please wait...", pathstr);
ShowPrompt(false, "%s\nVerification %s", pathstr,
if (filetype & IMG_NAND) {
ShowPrompt(false, "%s\nNAND validation %s", pathstr,
(ValidateNandDump(curr_entry->path) == 0) ? "success" : "failed");
} else ShowPrompt(false, "%s\nVerification %s", pathstr,
(VerifyGameFile(curr_entry->path) == 0) ? "success" : "failed");
}
return 0;
} else if (user_select == restore) { // -> restore SysNAND (A9LH preserving)
ShowPrompt(false, "%s\nNAND restore %s", pathstr,
(SafeRestoreNandDump(curr_entry->path) == 0) ? "success" : "failed");
}
return 1;

View File

@ -8,7 +8,7 @@
#include "sdmmc.h"
#include "image.h"
#define NAND_MIN_SECTORS ((GetUnitPlatform() == PLATFORM_N3DS) ? 0x26C000 : 0x1D7800)
#define NAND_MIN_SECTORS ((GetUnitPlatform() == PLATFORM_N3DS) ? NAND_MIN_SECTORS_N3DS : NAND_MIN_SECTORS_O3DS)
static u8 slot0x05KeyY[0x10] = { 0x00 }; // need to load this from FIRM0 / external file
static u8 slot0x05KeyY_sha256[0x20] = { // hash for slot0x05KeyY (16 byte)
@ -34,6 +34,14 @@ static u8 nand_magic_o3ds[0x60] = { // NCSD NAND header O3DS magic
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
static u8 twl_mbr[0x42] = { // encrypted version inside the NCSD NAND header (@0x1BE)
0x00, 0x04, 0x18, 0x00, 0x06, 0x01, 0xA0, 0x3F, 0x97, 0x00, 0x00, 0x00, 0xA9, 0x7D, 0x04, 0x00,
0x00, 0x04, 0x8E, 0x40, 0x06, 0x01, 0xA0, 0xC3, 0x8D, 0x80, 0x04, 0x00, 0xB3, 0x05, 0x01, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x55, 0xAA
};
static u8 CtrNandCtr[16];
static u8 TwlNandCtr[16];
static u8 OtpSha256[32] = { 0 };
@ -338,6 +346,24 @@ int WriteNandSectors(const u8* buffer, u32 sector, u32 count, u32 keyslot, u32 n
return 0;
}
u32 CheckNandHeader(u8* header)
{
// TWL MBR check
u8 header_dec[0x200];
memcpy(header_dec, header, 0x200);
CryptNand(header_dec, 0, 1, 0x03);
if (memcmp(header_dec + 0x1BE, twl_mbr, sizeof(twl_mbr)) != 0)
return 0; // header does not belong to console
// header type check
if (memcmp(header + 0x100, nand_magic_n3ds, sizeof(nand_magic_n3ds) == 0) == 0)
return (GetUnitPlatform() == PLATFORM_3DS) ? 0 : NAND_TYPE_N3DS;
else if (memcmp(header + 0x100, nand_magic_o3ds, sizeof(nand_magic_o3ds) == 0) == 0)
return NAND_TYPE_O3DS;
return 0;
}
u32 CheckNandType(u32 nand_src)
{
if (ReadNandSectors(NAND_BUFFER, 0, 1, 0xFF, nand_src) != 0)

View File

@ -10,6 +10,9 @@
#define NAND_TYPE_N3DS (1<<5)
#define NAND_TYPE_NO3DS (1<<6)
#define NAND_MIN_SECTORS_O3DS 0x1D7800
#define NAND_MIN_SECTORS_N3DS 0x26C000
bool InitNandCrypto(void);
bool CheckSlot0x05Crypto(void);
bool CheckSector0x96Crypto(void);
@ -23,6 +26,7 @@ int ReadNandSectors(u8* buffer, u32 sector, u32 count, u32 keyslot, u32 src);
int WriteNandSectors(const u8* buffer, u32 sector, u32 count, u32 keyslot, u32 dest);
u64 GetNandSizeSectors(u32 src);
u32 CheckNandHeader(u8* header);
u32 CheckNandType(u32 src);
bool InitEmuNandBase(void);

152
source/nand/nandutil.c Normal file
View File

@ -0,0 +1,152 @@
#include "nandutil.h"
#include "nand.h"
#include "firm.h"
#include "fatmbr.h"
#include "fsperm.h"
#include "sha.h"
#include "ui.h"
#include "ff.h"
u32 ReadNandFile(FIL* file, void* buffer, u32 sector, u32 count, u32 keyslot) {
u32 offset = sector * 0x200;
u32 size = count * 0x200;
UINT btr;
if ((f_tell(file) != offset) && (f_lseek(file, offset) != FR_OK))
return 1; // seek failed
if ((f_read(file, buffer, size, &btr) != FR_OK) || (btr != size))
return 1; // read failed
if (keyslot < 0x40) CryptNand(buffer, sector, count, keyslot);
return 0;
}
u32 ValidateNandDump(const char* path) {
const u32 mbr_sectors[] = { TWL_OFFSET, CTR_OFFSET };
const u32 firm_sectors[] = { FIRM_OFFSETS };
u8 buffer[0x200];
FirmHeader firm;
MbrHeader mbr;
u32 nand_type;
FIL file;
// truncated path string
char pathstr[32 + 1];
TruncateString(pathstr, path, 32, 8);
// open file
if (f_open(&file, path, FA_READ | FA_OPEN_EXISTING) != FR_OK)
return 1;
// check NAND header
if ((ReadNandFile(&file, buffer, 0, 1, 0xFF) != 0) ||
((nand_type = CheckNandHeader(buffer)) == 0)) { // zero means header not recognized
ShowPrompt(false, "%s\nHeader does not belong to device", pathstr);
f_close(&file);
return 1;
}
// check size
if (f_size(&file) < ((nand_type == NAND_TYPE_O3DS) ? NAND_MIN_SECTORS_O3DS : NAND_MIN_SECTORS_N3DS)) {
ShowPrompt(false, "%s\nNAND dump misses data", pathstr);
f_close(&file);
return 1;
}
// check MBRs (TWL & CTR)
for (u32 i = 0; i < sizeof(mbr_sectors) / sizeof(u32); i++) {
u32 keyslot = (i == 0) ? 0x03 : (nand_type == NAND_TYPE_O3DS) ? 0x04 : 0x05;
char* section_type = (i) ? "CTR" : "MBR";
if ((ReadNandFile(&file, &mbr, mbr_sectors[i], 1, keyslot) != 0) ||
(ValidateMbrHeader(&mbr) != 0)) {
ShowPrompt(false, "%s\nError: %s MBR is corrupt", pathstr, section_type);
f_close(&file);
return 1; // impossible to happen
}
for (u32 p = 0; p < 4; p++) {
u32 p_sector = mbr.partitions[p].sector;
if (!p_sector) continue;
if ((ReadNandFile(&file, buffer, mbr_sectors[i] + p_sector, 1, keyslot) != 0) ||
(ValidateFatHeader(buffer) != 0)) {
ShowPrompt(false, "%s\nError: %s partition%u is corrupt", pathstr, section_type, p);
f_close(&file);
return 1;
}
}
}
// check FIRMs (FIRM1 must be valid)
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) ||
(getbe32(firm.dec_magic) != 0)) { // decrypted firms are not allowed
ShowPrompt(false, "%s\nError: FIRM%u header is corrupt", pathstr, i);
f_close(&file);
return 1;
}
// hash verify all available sections
if (i == 0) continue; // no hash checks for FIRM0 (might be A9LH)
for (u32 s = 0; s < 4; s++) {
FirmSectionHeader* section = firm.sections + s;
u32 sector = firm_sectors[i] + (section->offset / 0x200);
u32 count = section->size / 0x200;
if (!count) continue;
sha_init(SHA256_MODE);
// relies on sections being aligned to sectors
for (u32 c = 0; c < count; c += MAIN_BUFFER_SIZE / 0x200) {
u32 read_sectors = min(MAIN_BUFFER_SIZE / 0x200, (count - c));
ReadNandFile(&file, MAIN_BUFFER, sector + c, read_sectors, keyslot);
sha_update(MAIN_BUFFER, read_sectors * 0x200);
}
u8 hash[0x20];
sha_get(hash);
if (memcmp(hash, section->hash, 0x20) != 0) {
ShowPrompt(false, "%s\nFIRM%u/%u hash mismatch", pathstr, i, s);
f_close(&file);
return 1;
}
}
}
return 0;
}
u32 SafeRestoreNandDump(const char* path) {
u32 safe_sectors[] = { SAFE_SECTORS };
FIL file;
/* if (ValidateNandDump(path) != 0) { // NAND dump validation
ShowPrompt(false, "NAND dump corrupt or not from console.\nYou can still try mount and copy.");
return 1;
}*/
if (!CheckA9lh()) {
ShowPrompt(false, "Error: A9LH not detected.");
return 1;
}
if (!SetWritePermissions(PERM_SYSNAND, true)) return 1;
// open file, get size
if (f_open(&file, path, FA_READ | FA_OPEN_EXISTING) != FR_OK)
return 1;
u32 fsize = f_size(&file);
safe_sectors[(sizeof(safe_sectors) / sizeof(u32)) - 1] = fsize / 0x200;
// main processing loop
u32 ret = 0;
if (!ShowProgress(0, 0, path)) ret = 1;
for (u32 p = 0; p < sizeof(safe_sectors) / sizeof(u32); p += 2) {
u32 sector0 = safe_sectors[p];
u32 sector1 = safe_sectors[p+1];
f_lseek(&file, sector0 * 0x200);
for (u32 s = sector0; (s < sector1) && (ret == 0); s += MAIN_BUFFER_SIZE / 0x200) {
UINT btr;
u32 count = min(MAIN_BUFFER_SIZE / 0x200, (sector1 - s));
if (f_read(&file, MAIN_BUFFER, count * 0x200, &btr) != FR_OK) ret = 1;
if (WriteNandSectors(MAIN_BUFFER, s, count, 0xFF, NAND_SYSNAND)) ret = 1;
if (btr != count * 0x200) ret = 1;
if (!ShowProgress(s + count, fsize / 0x200, path)) ret = 1;
}
}
f_close(&file);
return ret;
}

12
source/nand/nandutil.h Normal file
View File

@ -0,0 +1,12 @@
#pragma once
#include "common.h"
#define TWL_OFFSET 0x000000
#define CTR_OFFSET 0x05C980
#define FIRM_OFFSETS 0x058980, 0x05A980
#define SAFE_SECTORS 0x000001, 0x000096, 0x000097, 0x058980, 0x05C980, 0x000000 // last one is a placeholder
u32 ValidateNandDump(const char* path);
u32 SafeRestoreNandDump(const char* path);