From e1b135ddc5d18994ad939ff6dffbb2a87fbc761b Mon Sep 17 00:00:00 2001 From: d0k3 Date: Wed, 13 Sep 2017 16:35:22 +0200 Subject: [PATCH] Improved AGBSAVE / CMAC handling code --- source/nand/agbsave.c | 84 ++++++++++++++++++++++++++++++------------ source/nand/agbsave.h | 8 ++-- source/nand/nandcmac.c | 14 +++---- 3 files changed, 72 insertions(+), 34 deletions(-) diff --git a/source/nand/agbsave.c b/source/nand/agbsave.c index 58b1ae4..c299886 100644 --- a/source/nand/agbsave.c +++ b/source/nand/agbsave.c @@ -2,55 +2,91 @@ #include "sha.h" #include "aes.h" + +u32 ValidateAgbSaveHeader(AgbSaveHeader* header) { + u8 magic[] = { AGBSAVE_MAGIC }; + + // basic checks + if ((memcmp(header->magic, magic, sizeof(magic)) != 0) || + (header->unknown0 != 1) || (header->save_start != 0x200) || + (header->save_size > AGBSAVE_MAX_SSIZE)) + return 1; + + // reserved area checks + for (u32 i = 0; i < sizeof(header->reserved0); i++) if (header->reserved0[i] != 0xFF) return 1; + for (u32 i = 0; i < sizeof(header->reserved1); i++) if (header->reserved1[i] != 0xFF) return 1; + for (u32 i = 0; i < sizeof(header->reserved2); i++) if (header->reserved2[i] != 0xFF) return 1; + for (u32 i = 0; i < sizeof(header->reserved3); i++) if (header->reserved3[i] != 0xFF) return 1; + + // all fine if arriving here + return 0; +} + +u32 LoadAgbSave(u32 nand_src, u8* agbsave, u32 max_size, NandPartitionInfo* info, bool header_only) { + AgbSaveHeader* header = (AgbSaveHeader*) agbsave; + + // need at least room for the header + if (max_size < sizeof(AgbSaveHeader)) return 1; + + // load the header + if ((GetNandPartitionInfo(info, NP_TYPE_AGB, NP_SUBTYPE_CTR, 0, nand_src) != 0) || + (ReadNandSectors(agbsave, info->sector, 1, info->keyslot, nand_src) != 0) || + (ValidateAgbSaveHeader(header) != 0) || + (sizeof(AgbSaveHeader) + header->save_size > info->count * 0x200)) + return 1; + + // done if we only want the header + if (header_only) return 0; + + // load the savegame + if ((sizeof(AgbSaveHeader) + header->save_size > max_size) || + (ReadNandBytes(agbsave + 0x200, (info->sector+1) * 0x200, header->save_size, info->keyslot, nand_src) != 0)) + return 1; + + return 0; +} + u32 GetAgbSaveSize(u32 nand_src) { - AgbSave* agbsave = (AgbSave*) NAND_BUFFER; + AgbSaveHeader* header = (AgbSaveHeader*) NAND_BUFFER; NandPartitionInfo info; - if ((GetNandPartitionInfo(&info, NP_TYPE_AGB, NP_SUBTYPE_CTR, 0, nand_src) != 0) || - (ReadNandSectors((u8*) agbsave, info.sector, 1, info.keyslot, nand_src) != 0)) - return 0; - return agbsave->save_size; // it's recommended to also check the CMAC + if (LoadAgbSave(nand_src, NAND_BUFFER, NAND_BUFFER_SIZE, &info, true) != 0) + return 1; + return header->save_size; // it's recommended to also check the CMAC } u32 CheckAgbSaveCmac(u32 nand_src) { - u8 magic[] = { AGBSAVE_MAGIC }; - - AgbSave* agbsave = (AgbSave*) NAND_BUFFER; + u8* agbsave = (u8*) NAND_BUFFER; + AgbSaveHeader* header = (AgbSaveHeader*) agbsave; NandPartitionInfo info; - if ((GetNandPartitionInfo(&info, NP_TYPE_AGB, NP_SUBTYPE_CTR, 0, nand_src) != 0) || - (ReadNandSectors((u8*) agbsave, info.sector, 1, info.keyslot, nand_src) != 0) || - (memcmp(agbsave->magic, magic, sizeof(magic)) != 0) || - (0x200 + agbsave->save_size > info.count * 0x200) || - (ReadNandBytes(agbsave->savegame, (info.sector+1) * 0x200, agbsave->save_size, info.keyslot, nand_src) != 0)) + if (LoadAgbSave(nand_src, agbsave, NAND_BUFFER_SIZE, &info, false) != 0) return 1; u8 cmac[16] __attribute__((aligned(32))); u8 shasum[32]; - sha_quick(shasum, ((u8*) agbsave) + 0x30, (0x200 - 0x30) + agbsave->save_size, SHA256_MODE); + sha_quick(shasum, agbsave + 0x30, (0x200 - 0x30) + header->save_size, SHA256_MODE); use_aeskey(0x24); aes_cmac(shasum, cmac, 2); - return (memcmp(cmac, agbsave->cmac, 16) == 0) ? 0 : 1; + return (memcmp(cmac, header->cmac, 16) == 0) ? 0 : 1; } u32 FixAgbSaveCmac(u32 nand_dst) { - AgbSave* agbsave = (AgbSave*) NAND_BUFFER; + u8* agbsave = (u8*) NAND_BUFFER; + AgbSaveHeader* header = (AgbSaveHeader*) agbsave; NandPartitionInfo info; - if ((GetNandPartitionInfo(&info, NP_TYPE_AGB, NP_SUBTYPE_CTR, 0, nand_dst) != 0) || - (ReadNandSectors((u8*) agbsave, info.sector, 1, info.keyslot, nand_dst) != 0) || - (0x200 + agbsave->save_size > info.count * 0x200) || - (ReadNandBytes(agbsave->savegame, (info.sector+1) * 0x200, agbsave->save_size, info.keyslot, nand_dst) != 0)) + if (LoadAgbSave(nand_dst, agbsave, NAND_BUFFER_SIZE, &info, false) != 0) return 1; u8 cmac[16] __attribute__((aligned(32))); u8 shasum[32]; - sha_quick(shasum, ((u8*) agbsave) + 0x30, (0x200 - 0x30) + agbsave->save_size, SHA256_MODE); + sha_quick(shasum, agbsave + 0x30, (0x200 - 0x30) + header->save_size, SHA256_MODE); use_aeskey(0x24); aes_cmac(shasum, cmac, 2); - memcpy(agbsave->cmac, cmac, 16); + memcpy(header->cmac, cmac, 16); - // set CFG_BOOTENV = 0x7 so the save is taken over + // set CFG_BOOTENV to 0x7 so the save is taken over // https://www.3dbrew.org/wiki/CONFIG_Registers#CFG_BOOTENV *(u32*) 0x10010000 = 0x7; - return (WriteNandSectors((u8*) agbsave, info.sector, 1, info.keyslot, nand_dst) == 0) ? 0 : 1; + return (WriteNandSectors(header, info.sector, 1, info.keyslot, nand_dst) == 0) ? 0 : 1; } diff --git a/source/nand/agbsave.h b/source/nand/agbsave.h index b171649..2f4c83f 100644 --- a/source/nand/agbsave.h +++ b/source/nand/agbsave.h @@ -3,7 +3,9 @@ #include "common.h" #include "nand.h" -#define AGBSAVE_MAGIC '.', 'S', 'A', 'V' +#define AGBSAVE_MAGIC '.', 'S', 'A', 'V' +#define AGBSAVE_MAX_SIZE (0x000180 * 0x200) // standard size of the NAND partition +#define AGBSAVE_MAX_SSIZE (AGBSAVE_MAX_SIZE - sizeof(AgbSaveHeader)) // see: http://3dbrew.org/wiki/3DS_Virtual_Console#NAND_Savegame typedef struct { @@ -21,9 +23,9 @@ typedef struct { u32 unknown1; // has to do with ARM7? u32 unknown2; // has to do with ARM7? u8 reserved3[0x198]; // always 0xFF - u8 savegame[(0x000180-1)*0x200]; // unknown on custom partitions -} __attribute__((packed)) AgbSave; +} __attribute__((packed)) AgbSaveHeader; +u32 ValidateAgbSaveHeader(AgbSaveHeader* header); u32 GetAgbSaveSize(u32 nand_src); u32 CheckAgbSaveCmac(u32 nand_src); u32 FixAgbSaveCmac(u32 nand_dst); diff --git a/source/nand/nandcmac.c b/source/nand/nandcmac.c index 4f85a64..ee9bdb5 100644 --- a/source/nand/nandcmac.c +++ b/source/nand/nandcmac.c @@ -130,13 +130,13 @@ u32 CalculateFileCmac(const char* path, u8* cmac) { if (!cmac_type) { // path independent stuff const char* db_names[] = { SYS_DB_NAMES }; for (sid = 0; sid < sizeof(db_names) / sizeof(char*); sid++) - if (strncmp(name, db_names[sid], 16) == 0) break; + if (strncasecmp(name, db_names[sid], 16) == 0) break; if (sid < sizeof(db_names) / sizeof(char*)) cmac_type = ((drv == 'A') || (drv == 'B')) ? CMAC_TITLEDB_SD : CMAC_TITLEDB_SYS; - else if (strncmp(name, "movable.sed", 16) == 0) + else if (strncasecmp(name, "movable.sed", 16) == 0) cmac_type = CMAC_MOVABLE; - /* else if (strncmp(name, "agbsave.bin", 16) == 0) - cmac_type = CMAC_AGBSAVE; */ + else if (strncasecmp(name, "agbsave.bin", 16) == 0) + cmac_type = CMAC_AGBSAVE; } // exit with cmac_type if (u8*) cmac is NULL @@ -164,9 +164,9 @@ u32 CalculateFileCmac(const char* path, u8* cmac) { // build hash data block, get size if (cmac_type == CMAC_AGBSAVE) { // agbsaves // see: https://www.3dbrew.org/wiki/Savegames#AES_CMAC_header - AgbSave* agbsave = (AgbSave*) (void*) data; - if ((fvx_qread(path, agbsave, 0, TEMP_BUFFER_SIZE, &br) != FR_OK) || - (br >= TEMP_BUFFER_SIZE) || (0x200 + agbsave->save_size > br)) + AgbSaveHeader* agbsave = (AgbSaveHeader*) (void*) data; + if ((TEMP_BUFFER_SIZE < AGBSAVE_MAX_SIZE) || (fvx_qread(path, agbsave, 0, AGBSAVE_MAX_SIZE, &br) != FR_OK) || + (br < 0x200) || (ValidateAgbSaveHeader(agbsave) != 0) || (0x200 + agbsave->save_size > br)) return 1; hashsize = (0x200 - 0x30) + agbsave->save_size; hashdata = data + 0x30;