Improved AGBSAVE / CMAC handling code

This commit is contained in:
d0k3 2017-09-13 16:35:22 +02:00
parent 88a390f7a8
commit e1b135ddc5
3 changed files with 72 additions and 34 deletions

View File

@ -2,55 +2,91 @@
#include "sha.h" #include "sha.h"
#include "aes.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) { u32 GetAgbSaveSize(u32 nand_src) {
AgbSave* agbsave = (AgbSave*) NAND_BUFFER; AgbSaveHeader* header = (AgbSaveHeader*) NAND_BUFFER;
NandPartitionInfo info; NandPartitionInfo info;
if ((GetNandPartitionInfo(&info, NP_TYPE_AGB, NP_SUBTYPE_CTR, 0, nand_src) != 0) || if (LoadAgbSave(nand_src, NAND_BUFFER, NAND_BUFFER_SIZE, &info, true) != 0)
(ReadNandSectors((u8*) agbsave, info.sector, 1, info.keyslot, nand_src) != 0)) return 1;
return 0; return header->save_size; // it's recommended to also check the CMAC
return agbsave->save_size; // it's recommended to also check the CMAC
} }
u32 CheckAgbSaveCmac(u32 nand_src) { u32 CheckAgbSaveCmac(u32 nand_src) {
u8 magic[] = { AGBSAVE_MAGIC }; u8* agbsave = (u8*) NAND_BUFFER;
AgbSaveHeader* header = (AgbSaveHeader*) agbsave;
AgbSave* agbsave = (AgbSave*) NAND_BUFFER;
NandPartitionInfo info; NandPartitionInfo info;
if ((GetNandPartitionInfo(&info, NP_TYPE_AGB, NP_SUBTYPE_CTR, 0, nand_src) != 0) || if (LoadAgbSave(nand_src, agbsave, NAND_BUFFER_SIZE, &info, false) != 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))
return 1; return 1;
u8 cmac[16] __attribute__((aligned(32))); u8 cmac[16] __attribute__((aligned(32)));
u8 shasum[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); use_aeskey(0x24);
aes_cmac(shasum, cmac, 2); 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) { u32 FixAgbSaveCmac(u32 nand_dst) {
AgbSave* agbsave = (AgbSave*) NAND_BUFFER; u8* agbsave = (u8*) NAND_BUFFER;
AgbSaveHeader* header = (AgbSaveHeader*) agbsave;
NandPartitionInfo info; NandPartitionInfo info;
if ((GetNandPartitionInfo(&info, NP_TYPE_AGB, NP_SUBTYPE_CTR, 0, nand_dst) != 0) || if (LoadAgbSave(nand_dst, agbsave, NAND_BUFFER_SIZE, &info, false) != 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))
return 1; return 1;
u8 cmac[16] __attribute__((aligned(32))); u8 cmac[16] __attribute__((aligned(32)));
u8 shasum[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); use_aeskey(0x24);
aes_cmac(shasum, cmac, 2); 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 // https://www.3dbrew.org/wiki/CONFIG_Registers#CFG_BOOTENV
*(u32*) 0x10010000 = 0x7; *(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;
} }

View File

@ -3,7 +3,9 @@
#include "common.h" #include "common.h"
#include "nand.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 // see: http://3dbrew.org/wiki/3DS_Virtual_Console#NAND_Savegame
typedef struct { typedef struct {
@ -21,9 +23,9 @@ typedef struct {
u32 unknown1; // has to do with ARM7? u32 unknown1; // has to do with ARM7?
u32 unknown2; // has to do with ARM7? u32 unknown2; // has to do with ARM7?
u8 reserved3[0x198]; // always 0xFF u8 reserved3[0x198]; // always 0xFF
u8 savegame[(0x000180-1)*0x200]; // unknown on custom partitions } __attribute__((packed)) AgbSaveHeader;
} __attribute__((packed)) AgbSave;
u32 ValidateAgbSaveHeader(AgbSaveHeader* header);
u32 GetAgbSaveSize(u32 nand_src); u32 GetAgbSaveSize(u32 nand_src);
u32 CheckAgbSaveCmac(u32 nand_src); u32 CheckAgbSaveCmac(u32 nand_src);
u32 FixAgbSaveCmac(u32 nand_dst); u32 FixAgbSaveCmac(u32 nand_dst);

View File

@ -130,13 +130,13 @@ u32 CalculateFileCmac(const char* path, u8* cmac) {
if (!cmac_type) { // path independent stuff if (!cmac_type) { // path independent stuff
const char* db_names[] = { SYS_DB_NAMES }; const char* db_names[] = { SYS_DB_NAMES };
for (sid = 0; sid < sizeof(db_names) / sizeof(char*); sid++) 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*)) if (sid < sizeof(db_names) / sizeof(char*))
cmac_type = ((drv == 'A') || (drv == 'B')) ? CMAC_TITLEDB_SD : CMAC_TITLEDB_SYS; 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; cmac_type = CMAC_MOVABLE;
/* else if (strncmp(name, "agbsave.bin", 16) == 0) else if (strncasecmp(name, "agbsave.bin", 16) == 0)
cmac_type = CMAC_AGBSAVE; */ cmac_type = CMAC_AGBSAVE;
} }
// exit with cmac_type if (u8*) cmac is NULL // 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 // build hash data block, get size
if (cmac_type == CMAC_AGBSAVE) { // agbsaves if (cmac_type == CMAC_AGBSAVE) { // agbsaves
// see: https://www.3dbrew.org/wiki/Savegames#AES_CMAC_header // see: https://www.3dbrew.org/wiki/Savegames#AES_CMAC_header
AgbSave* agbsave = (AgbSave*) (void*) data; AgbSaveHeader* agbsave = (AgbSaveHeader*) (void*) data;
if ((fvx_qread(path, agbsave, 0, TEMP_BUFFER_SIZE, &br) != FR_OK) || if ((TEMP_BUFFER_SIZE < AGBSAVE_MAX_SIZE) || (fvx_qread(path, agbsave, 0, AGBSAVE_MAX_SIZE, &br) != FR_OK) ||
(br >= TEMP_BUFFER_SIZE) || (0x200 + agbsave->save_size > br)) (br < 0x200) || (ValidateAgbSaveHeader(agbsave) != 0) || (0x200 + agbsave->save_size > br))
return 1; return 1;
hashsize = (0x200 - 0x30) + agbsave->save_size; hashsize = (0x200 - 0x30) + agbsave->save_size;
hashdata = data + 0x30; hashdata = data + 0x30;