diff --git a/README.md b/README.md index 3605f29..97078f9 100644 --- a/README.md +++ b/README.md @@ -121,6 +121,7 @@ With the possibilites GodMode9 provides, not everything may be obvious at first * __Restore / dump NAND partitions or even full NANDs__: Just take a look into the `S:` (or `E:`/ `I:`) drive. This is done the same as any other file operation. * __Transfer CTRNAND images between systems__: Transfer the file located at `S:/ctrnand_full.bin` (or `E:`/ `I:`). On the receiving system, press A, select `CTRNAND Options...`, then `Transfer to NAND`. * __Embed an essential backup right into a NAND dump__: This is available in the A button menu for NAND dumps. Essential backups contain NAND header, `movable.sed`, `LocalFriendCodeSeed_B`, `SecureInfo_A`, NAND CID and OTP. If your local SysNAND does not contain an embedded backup, you will be asked to do one at startup. +* __Install an AES key database to your NAND__: For `aeskeydb.bin` files the option is found in `aeskeydb.bin options` -> `Install aeskeydb.bin`. Only the recommended key database can be installed (see above). With an installed key database, it is possible to run the GodMode9 bootloader completely from NAND. * __Install FIRM files to your NAND__: Found inside the A button menu for FIRM files, select `FIRM options` -> `Install FIRM`. __Use this with caution__ - installing an incompatible FIRM file will lead to a __brick__. * __Actually use that extra NAND space__: You can setup a __bonus drive__ via the HOME menu, which will be available via drive letter `8:`. (Only available on systems that have the extra space.) * __Fix certain problems on your NANDs__: You can fix CMACs for a whole drive (works on `A:`, `B:`, `S:` and `E:`) via an entry in the R+A button menu, or even restore borked NAND headers back to a functional state (inside the A button menu of borked NANDs and available for `S:/nand_hdr.bin`). Recommended only for advanced users! @@ -148,20 +149,19 @@ This tool would not have been possible without the help of numerous people. Than * **Archshift**, for providing the base project infrastructure * **Normmatt**, for sdmmc.c / sdmmc.h and gamecart code, and for being of great help on countless other occasions * **Cha(N)**, **Kane49**, and all other FatFS contributors for [FatFS](http://elm-chan.org/fsw/ff/00index_e.html) +* **Wolfvak** for ARM11 code, FIRM binary launcher, fexception handlers, PCX code, Makefile and for help on countless other occasions * **SciresM** for helping me figure out RomFS and for boot9strap * **SciresM**, **Myria**, **Normmatt**, **TuxSH** and **hedgeberg** for figuring out sighax and giving us access to bootrom * **ihaveamac** for first developing the simple CIA generation method and for being of great help in porting it * **b1l1s** for helping me figure out A9LH compatibility * **Gelex** and **AuroraWright** for helping me figure out various things * **stuckpixel** for the new 6x10 font and help on various things -* **Wolfvak** for all ARM11 code, for the FIRM binary launcher, for the exception handlers and for help on countless other occasions * **Al3x_10m** for help with countless hours of testing and useful advice * **WinterMute** for helping me with his vast knowledge on everything gamecart related * **profi200** for always useful advice and helpful hints on various things * **JaySea**, **YodaDaCoda**, **liomajor**, **Supster131**, **imanoob**, **Kasher_CS** and countless others from freenode #Cakey and the GBAtemp forums for testing, feedback and helpful hints * **Shadowhand** for being awesome and [hosting my nightlies](https://d0k3.secretalgorithm.com/) * **Plailect** for putting his trust in my tools and recommending this in [The Guide](https://3ds.guide/) -* **Lasse Reinhold** for [QuickLZ](http://www.quicklz.com/) * **Project Nayuki** for [qrcodegen](https://github.com/nayuki/QR-Code-generator) * **Amazingmax fonts** for the Amazdoom font * The fine folks on **freenode #Cakey** diff --git a/arm9/source/crypto/keydb.c b/arm9/source/crypto/keydb.c index 5892e58..e10d2a7 100644 --- a/arm9/source/crypto/keydb.c +++ b/arm9/source/crypto/keydb.c @@ -1,21 +1,9 @@ #include "keydb.h" #include "aes.h" #include "sha.h" -#include "ff.h" +#include "vff.h" #include "support.h" -typedef struct { - u8 slot; // keyslot, 0x00...0x39 - char type; // type 'X' / 'Y' / 'N' for normalKey / 'I' for IV - char id[10]; // key ID for special keys, all zero for standard keys -} __attribute__((packed)) AesKeyDesc; - -typedef struct { - AesKeyDesc desc; // slot, type, id - u8 keyUnitType; // 0 for ALL units / 1 for devkit exclusive / 2 for retail exclusive - u8 keySha256[32]; // SHA-256 of the key -} __attribute__((packed)) AesKeyHashInfo; - typedef struct { u8 slot; // keyslot, 0x00...0x39 u8 keyUnitType; // 0 for ALL units / 1 for devkit exclusive / 2 for retail exclusive @@ -110,17 +98,16 @@ u32 CheckKeySlot(u32 keyslot, char type) return 1; } +// this function creates dependencies to "vff.h" and "support.h" +// to get rid of these, you may replace this function with anything that works for you u32 LoadKeyDb(const char* path_db, AesKeyInfo* keydb, u32 bsize) { UINT fsize = 0; - FIL fp; if (path_db) { - if (f_open(&fp, path_db, FA_READ | FA_OPEN_EXISTING) == FR_OK) { - if ((f_read(&fp, keydb, bsize, &fsize) != FR_OK) || (fsize >= bsize)) - fsize = 0; - f_close(&fp); - } - } else fsize = LoadSupportFile(KEYDB_NAME, keydb, bsize); // load key database support file + if (fvx_qread (path_db, keydb, 0, bsize, &fsize) != FR_OK) fsize = 0; + } else if (fvx_qread ("S:/" KEYDB_NAME, keydb, 0, bsize, &fsize) != FR_OK) { + fsize = LoadSupportFile(KEYDB_NAME, keydb, bsize); // load key database support file + } u32 nkeys = 0; if (fsize && !(fsize % sizeof(AesKeyInfo))) @@ -235,6 +222,7 @@ u32 InitKeyDb(const char* path) return 0; } +// creates dependency to "sha.h", not required for base keydb functions u32 CheckRecommendedKeyDb(const char* path) { // SHA-256 of the recommended aeskeydb.bin file diff --git a/arm9/source/crypto/keydb.h b/arm9/source/crypto/keydb.h index c9bcc0d..d142835 100644 --- a/arm9/source/crypto/keydb.h +++ b/arm9/source/crypto/keydb.h @@ -4,9 +4,17 @@ #define KEYDB_NAME "aeskeydb.bin" +// SHA-256 and size of the recommended aeskeydb.bin file +// equals MD5 A5B28945A7C051D7A0CD18AF0E580D1B / 1024 byte +#define KEYDB_PERFECT_HASH \ + 0x40, 0x76, 0x54, 0x3D, 0xA3, 0xFF, 0x91, 0x1C, 0xE1, 0xCC, 0x4E, 0xC7, 0x2F, 0x92, 0xE4, 0xB7, \ + 0x2B, 0x24, 0x00, 0x15, 0xBE, 0x9B, 0xFC, 0xDE, 0x7F, 0xED, 0x95, 0x1D, 0xD5, 0xAB, 0x2D, 0xCB +#define KEYDB_PERFECT_SIZE (32 * sizeof(AesKeyInfo)) // 32 keys contained + #define KEYS_UNKNOWN 0 #define KEYS_DEVKIT 1 #define KEYS_RETAIL 2 + typedef struct { u8 slot; // keyslot, 0x00...0x3F diff --git a/arm9/source/filesys/filetype.h b/arm9/source/filesys/filetype.h index 66db166..d9fee5b 100644 --- a/arm9/source/filesys/filetype.h +++ b/arm9/source/filesys/filetype.h @@ -56,6 +56,7 @@ #define FTYPE_EBACKUP(tp) (tp&(IMG_NAND)) #define FTYPE_XORPAD(tp) (tp&(BIN_NCCHNFO)) #define FTYPE_KEYINIT(tp) (tp&(BIN_KEYDB)) +#define FTYPE_KEYINSTALL(tp) (tp&(BIN_KEYDB)) #define FTYPE_SCRIPT(tp) (tp&(TXT_SCRIPT)) #define FTYPE_BOOTABLE(tp) (tp&(SYS_FIRM)) #define FTYPE_INSTALLABLE(tp) (tp&(SYS_FIRM)) diff --git a/arm9/source/godmode.c b/arm9/source/godmode.c index 9b4b8fb..ec4424e 100644 --- a/arm9/source/godmode.c +++ b/arm9/source/godmode.c @@ -937,7 +937,7 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, PaneData** pan bool cia_buildable_legit = (FTYPE_CIABUILD_L(filetype)); bool cxi_dumpable = (FTYPE_CXIDUMP(filetype)); bool tik_buildable = (FTYPE_TIKBUILD(filetype)) && !in_output_path; - bool key_buildable = (FTYPE_KEYBUILD(filetype)) && !in_output_path; + bool key_buildable = (FTYPE_KEYBUILD(filetype)) && !in_output_path && !((drvtype & DRV_VIRTUAL) && (drvtype & DRV_SYSNAND)); bool titleinfo = (FTYPE_TITLEINFO(filetype)); bool renamable = (FTYPE_RENAMABLE(filetype)); bool transferable = (FTYPE_TRANSFERABLE(filetype) && IS_A9LH && (drvtype & DRV_FAT)); @@ -947,7 +947,8 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, PaneData** pan bool ebackupable = (FTYPE_EBACKUP(filetype)); bool ncsdfixable = (FTYPE_NCSDFIXABLE(filetype)); bool xorpadable = (FTYPE_XORPAD(filetype)); - bool keyinitable = (FTYPE_KEYINIT(filetype)); + bool keyinitable = (FTYPE_KEYINIT(filetype)) && !((drvtype & DRV_VIRTUAL) && (drvtype & DRV_SYSNAND)); + bool keyinstallable = (FTYPE_KEYINSTALL(filetype)) && !((drvtype & DRV_VIRTUAL) && (drvtype & DRV_SYSNAND)); bool scriptable = (FTYPE_SCRIPT(filetype)); bool bootable = (FTYPE_BOOTABLE(filetype)); bool installable = (FTYPE_INSTALLABLE(filetype)); @@ -963,7 +964,7 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, PaneData** pan extrcodeable = (FTYPE_HASCODE(filetype_cxi)); } - bool special_opt = mountable || verificable || decryptable || encryptable || cia_buildable || cia_buildable_legit || cxi_dumpable || tik_buildable || key_buildable || titleinfo || renamable || transferable || hsinjectable || restorable || xorpadable || ebackupable || ncsdfixable || extrcodeable || keyinitable || bootable || scriptable || installable || agbexportable || agbimportable; + bool special_opt = mountable || verificable || decryptable || encryptable || cia_buildable || cia_buildable_legit || cxi_dumpable || tik_buildable || key_buildable || titleinfo || renamable || transferable || hsinjectable || restorable || xorpadable || ebackupable || ncsdfixable || extrcodeable || keyinitable || keyinstallable || bootable || scriptable || installable || agbexportable || agbimportable; char pathstr[32+1]; TruncateString(pathstr, file_path, 32, 8); @@ -1151,6 +1152,7 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, PaneData** pan int xorpad = (xorpadable) ? ++n_opt : -1; int xorpad_inplace = (xorpadable) ? ++n_opt : -1; int keyinit = (keyinitable) ? ++n_opt : -1; + int keyinstall = (keyinstallable) ? ++n_opt : -1; int install = (installable) ? ++n_opt : -1; int boot = (bootable) ? ++n_opt : -1; int script = (scriptable) ? ++n_opt : -1; @@ -1177,6 +1179,7 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, PaneData** pan if (xorpad_inplace > 0) optionstr[xorpad_inplace-1] = "Build XORpads (inplace)"; if (extrcode > 0) optionstr[extrcode-1] = "Extract " EXEFS_CODE_NAME; if (keyinit > 0) optionstr[keyinit-1] = "Init " KEYDB_NAME; + if (keyinstall > 0) optionstr[keyinstall-1] = "Install " KEYDB_NAME; if (install > 0) optionstr[install-1] = "Install FIRM"; if (boot > 0) optionstr[boot-1] = "Boot FIRM"; if (script > 0) optionstr[script-1] = "Execute GM9 script"; @@ -1558,6 +1561,10 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, PaneData** pan ShowPrompt(false, "%s\nAESkeydb init %s", pathstr, (InitKeyDb(file_path) == 0) ? "success" : "failed"); return 0; } + else if (user_select == keyinstall) { // -> install keys from aeskeydb.bin + ShowPrompt(false, "%s\nAESkeydb install %s", pathstr, (SafeInstallKeyDb(file_path) == 0) ? "success" : "failed"); + return 0; + } else if (user_select == install) { // -> install FIRM size_t firm_size = FileGetSize(file_path); u32 slots = 1; diff --git a/arm9/source/nand/essentials.h b/arm9/source/nand/essentials.h index 16573f3..a16cb95 100644 --- a/arm9/source/nand/essentials.h +++ b/arm9/source/nand/essentials.h @@ -3,6 +3,8 @@ #include "common.h" #include "exefs.h" +#define ESSENTIAL_NAME "essential.exefs" + // start sector of the essential backup in NAND // careful with this, essential backup should never reach sector 0x96 #define ESSENTIAL_SECTOR 0x1 diff --git a/arm9/source/nand/nand.c b/arm9/source/nand/nand.c index f36cff8..eafceff 100644 --- a/arm9/source/nand/nand.c +++ b/arm9/source/nand/nand.c @@ -1,7 +1,6 @@ #include "nand.h" #include "fsdrive.h" #include "unittype.h" -#include "keydb.h" #include "aes.h" #include "sha.h" #include "fatmbr.h" @@ -13,7 +12,7 @@ #define SECTOR_SHA256 ((IS_DEVKIT) ? sector0x96dev_sha256 : sector0x96_sha256) // see: https://www.3dbrew.org/wiki/NCSD#NCSD_header -static const u32 np_keyslots[9][4] = { // [NP_TYPE][NP_SUBTYPE] +static const u32 np_keyslots[10][4] = { // [NP_TYPE][NP_SUBTYPE] { 0xFF, 0xFF, 0xFF, 0xFF }, // none { 0xFF, 0x03, 0x04, 0x05 }, // standard { 0xFF, 0x03, 0x04, 0x05 }, // FAT (custom, not in NCSD) @@ -21,6 +20,7 @@ static const u32 np_keyslots[9][4] = { // [NP_TYPE][NP_SUBTYPE] { 0xFF, 0xFF, 0x07, 0xFF }, // AGBSAVE { 0xFF, 0xFF, 0xFF, 0xFF }, // NCSD (custom) { 0xFF, 0xFF, 0xFF, 0xFF }, // D0K3 (custom) + { 0xFF, 0xFF, 0xFF, 0xFF }, // KEYDB (custom) { 0xFF, 0xFF, 0xFF, 0x11 }, // SECRET (custom) { 0xFF, 0xFF, 0xFF, 0xFF } // BONUS (custom) }; @@ -470,14 +470,17 @@ u32 GetNandNcsdPartitionInfo(NandPartitionInfo* info, u32 type, u32 subtype, u32 // special, custom partition types, not in NCSD if (type >= NP_TYPE_NCSD) { if (type == NP_TYPE_NCSD) { - info->sector = 0x00; // hardcoded - info->count = 0x01; + info->sector = SECTOR_NCSD; // hardcoded + info->count = COUNT_NCSD; } else if (type == NP_TYPE_D0K3) { info->sector = SECTOR_D0K3; // hardcoded - info->count = SECTOR_SECRET - info->sector; + info->count = COUNT_D0K3; + } else if (type == NP_TYPE_KEYDB) { + info->sector = SECTOR_KEYDB; // hardcoded + info->count = COUNT_KEYDB; } else if (type == NP_TYPE_SECRET) { - info->sector = SECTOR_SECRET; - info->count = 0x01; + info->sector = SECTOR_SECRET; // hardcoded + info->count = COUNT_SECRET; } else if (type == NP_TYPE_BONUS) { info->sector = GetNandNcsdMinSizeSectors(ncsd); info->count = 0x00; // placeholder, actual size needs info from NAND chip diff --git a/arm9/source/nand/nand.h b/arm9/source/nand/nand.h index 037c1e8..e335bf2 100644 --- a/arm9/source/nand/nand.h +++ b/arm9/source/nand/nand.h @@ -1,6 +1,8 @@ #pragma once #include "common.h" +#include "essentials.h" +#include "keydb.h" #define NAND_SYSNAND (1UL<<0) #define NAND_EMUNAND (1UL<<1) @@ -12,9 +14,15 @@ #define BOOT_NTRBOOT (1UL<<1) #define BOOT_WIFI_SPI (1UL<<2) -// hardcoded start sectors -#define SECTOR_D0K3 0x000001 +// hardcoded start sectors and counts +#define COUNT_NCSD 0x01 +#define COUNT_SECRET 0x01 +#define COUNT_D0K3 ((sizeof(EssentialBackup) + 0x1FF) / 0x200) +#define COUNT_KEYDB ((KEYDB_PERFECT_SIZE + 0x1FF) / 0x200) +#define SECTOR_NCSD 0x000000 #define SECTOR_SECRET 0x000096 +#define SECTOR_D0K3 (SECTOR_NCSD + COUNT_NCSD) +#define SECTOR_KEYDB (SECTOR_SECRET - COUNT_KEYDB) // 0x110...0x118 in the NAND NCSD header // see: https://www.3dbrew.org/wiki/NCSD#NCSD_header @@ -25,8 +33,9 @@ #define NP_TYPE_AGB 4 #define NP_TYPE_NCSD 5 // this is of our own making #define NP_TYPE_D0K3 6 // my own partition ^_^ -#define NP_TYPE_SECRET 7 // this is of our own making -#define NP_TYPE_BONUS 8 // this is of our own making +#define NP_TYPE_KEYDB 7 // keydb partition, also my own ^_^ +#define NP_TYPE_SECRET 8 // this is of our own making +#define NP_TYPE_BONUS 9 // this is of our own making // 0x118...0x120 in the NAND NCSD header // see: https://www.3dbrew.org/wiki/NCSD#NCSD_header diff --git a/arm9/source/utils/nandutil.c b/arm9/source/utils/nandutil.c index 1c8c561..52b2be4 100644 --- a/arm9/source/utils/nandutil.c +++ b/arm9/source/utils/nandutil.c @@ -9,6 +9,7 @@ #include "sdmmc.h" #include "sha.h" #include "sighax.h" +#include "keydb.h" // for perfect keydb hash and length #include "essentials.h" // for essential backup struct #include "unittype.h" @@ -558,3 +559,34 @@ u32 SafeInstallFirm(const char* path, u32 slots) { return 0; } + +u32 SafeInstallKeyDb(const char* path) +{ + const u8 perfect_sha[] = { KEYDB_PERFECT_HASH }; + u8 keydb[KEYDB_PERFECT_SIZE]; + + char pathstr[32 + 1]; // truncated path string + TruncateString(pathstr, path, 32, 8); + + // already installed? + if ((ReadNandBytes(keydb, SECTOR_KEYDB*0x200, KEYDB_PERFECT_SIZE, 0xFF, NAND_SYSNAND) == 0) && + (sha_cmp(perfect_sha, keydb, KEYDB_PERFECT_SIZE, SHA256_MODE) == 0)) { + ShowPrompt(false, "Perfect " KEYDB_NAME " is already installed!"); + return 1; + } + + // check input path... + if ((fvx_qread(path, keydb, 0, KEYDB_PERFECT_SIZE, NULL) != FR_OK) || + (sha_cmp(perfect_sha, keydb, KEYDB_PERFECT_SIZE, SHA256_MODE) != 0)) { + ShowPrompt(false, "%s\nNot a perfect " KEYDB_NAME " image.\nCannot install to NAND!", pathstr); + return 1; + } + + // point of no return, install key database + if (WriteNandBytes(keydb, SECTOR_KEYDB*0x200, KEYDB_PERFECT_SIZE, 0xFF, NAND_SYSNAND) != 0) { + ShowPrompt(false, "%s\nFailed writing " KEYDB_NAME " to NAND!", pathstr); + return 1; + } + + return 0; +} diff --git a/arm9/source/utils/nandutil.h b/arm9/source/utils/nandutil.h index 0281049..ccee18a 100644 --- a/arm9/source/utils/nandutil.h +++ b/arm9/source/utils/nandutil.h @@ -8,5 +8,6 @@ u32 FixNandHeader(const char* path, bool check_size); u32 ValidateNandDump(const char* path); u32 SafeRestoreNandDump(const char* path); u32 SafeInstallFirm(const char* path, u32 slots); +u32 SafeInstallKeyDb(const char* path); u32 DumpGbaVcSavegame(const char* path); u32 InjectGbaVcSavegame(const char* path, const char* path_vcsave); diff --git a/arm9/source/virtual/vnand.c b/arm9/source/virtual/vnand.c index 323fef8..afd60da 100644 --- a/arm9/source/virtual/vnand.c +++ b/arm9/source/virtual/vnand.c @@ -1,10 +1,13 @@ #include "vnand.h" #include "nand.h" #include "essentials.h" +#include "keydb.h" +#include "sha.h" #include "unittype.h" -#define VFLAG_MBR (1UL<<28) -#define VFLAG_ESSENTIAL (1UL<<29) +#define VFLAG_MBR (1UL<<27) +#define VFLAG_ESSENTIAL (1UL<<28) +#define VFLAG_KEYDB (1UL<<29) #define VFLAG_NEEDS_OTP (1UL<<30) #define VFLAG_NAND_SIZE (1UL<<31) @@ -20,7 +23,8 @@ typedef struct { static const VirtualNandTemplate vNandTemplates[] = { { "nand_hdr.bin" , NP_TYPE_NCSD , NP_SUBTYPE_CTR , 0, 0 }, { "twlmbr.bin" , NP_TYPE_STD , NP_SUBTYPE_TWL , 0, VFLAG_MBR }, - { "essential.exefs" , NP_TYPE_D0K3 , NP_SUBTYPE_NONE , 0, VFLAG_DELETABLE | VFLAG_ESSENTIAL }, + { ESSENTIAL_NAME , NP_TYPE_D0K3 , NP_SUBTYPE_NONE , 0, VFLAG_DELETABLE | VFLAG_ESSENTIAL }, + { KEYDB_NAME , NP_TYPE_KEYDB , NP_SUBTYPE_NONE , 0, VFLAG_DELETABLE | VFLAG_KEYDB }, { "sector0x96.bin" , NP_TYPE_SECRET, NP_SUBTYPE_CTR_N, 0, VFLAG_NEEDS_OTP }, { "twln.bin" , NP_TYPE_FAT , NP_SUBTYPE_TWL , 0, 0 }, { "twlp.bin" , NP_TYPE_FAT , NP_SUBTYPE_TWL , 1, 0 }, @@ -87,6 +91,13 @@ bool ReadVNandDir(VirtualFile* vfile, VirtualDir* vdir) { // uses a generic vdir if (memcmp(data, magic, sizeof(magic)) != 0) continue; vfile->size = sizeof(EssentialBackup); } + if (vfile->flags & VFLAG_KEYDB) { + const u8 perfect_sha[] = { KEYDB_PERFECT_HASH }; + u8 keydb[KEYDB_PERFECT_SIZE]; + ReadNandBytes(keydb, vfile->offset, KEYDB_PERFECT_SIZE, vfile->keyslot, nand_src); + if (sha_cmp(perfect_sha, keydb, KEYDB_PERFECT_SIZE, SHA256_MODE) != 0) continue; + vfile->size = KEYDB_PERFECT_SIZE; + } // found if arriving here vfile->flags |= nand_src;