#include #include #include #include #include #include #include "f_xy.h" #include "dsi.h" #define CHUNKSIZE 0x80000 u8 *workbuffer; u32 done=0; typedef struct nocash_footer { char footerID[16]; u8 nand_cid[16]; u8 consoleid[8]; u8 pad[0x18]; } nocash_footer_t; void wait(int ticks){ while(ticks--)swiWaitForVBlank(); } u32 getMBremaining(){ struct statvfs stats; statvfs("/", &stats); u64 total_remaining = ((u64)stats.f_bsize * (u64)stats.f_bavail) / (u64)0x100000; return (u32)total_remaining; } void death(char *message){ iprintf("%s\n", message); iprintf("Hold Power to exit\n"); free(workbuffer); while(1)swiWaitForVBlank(); } void getCID(u8 *CID){ memcpy(CID,(u8*)0x02FFD7BC,16); //arm9 location } void getConsoleID(u8 *consoleID){ u8 *fifo=(u8*)0x02300000; //shared mem address that has our computed key3 stuff u8 key[16]; //key3 normalkey - keyslot 3 is used for DSi/twln NAND crypto u8 key_xy[16]; //key3_y ^ key3_x u8 key_x[16];////key3_x - contains a DSi console id (which just happens to be the LFCS on 3ds) u8 key_y[16] = {0x76, 0xDC, 0xB9, 0x0A, 0xD3, 0xC4, 0x4D, 0xBD, 0x1D, 0xDD, 0x2D, 0x20, 0x05, 0x00, 0xA0, 0xE1}; //key3_y NAND constant while(1){ if(fifoCheckValue32(FIFO_USER_01)){ //checking to see when that plucky little arm7 has finished its consoleID magic break; } swiWaitForVBlank(); } u8 empty_buff[8] = {0}; memcpy(key, fifo, 16); //receive the goods from arm7 if(memcmp(key + 8, empty_buff, 8) == 0) { //we got the consoleid directly or nothing at all, don't treat this as key3 output memcpy(consoleID, key, 8); return; } F_XY_reverse((uint32_t*)key, (uint32_t*)key_xy); //work backwards from the normalkey to get key_x that has the consoleID for(int i=0;i<16;i++){ key_x[i] = key_xy[i] ^ key_y[i]; //'' } memcpy(&consoleID[0], &key_x[0], 4); memcpy(&consoleID[4], &key_x[0xC], 4); } int dumpNAND(nocash_footer_t *footer){ consoleClear(); u32 rwTotal=nand_GetSize()*0x200; //240MB or 245.5MB printf("NAND size: %.2fMB\n", (float)rwTotal/(float)0x100000); if(rwTotal != 0xF000000 && rwTotal != 0xF580000) death("Unknown NAND size."); //there's no documented nand chip with sizes different from these two. bool batteryMsgShown=false; while(getBatteryLevel() < 0x4){ if(!batteryMsgShown) { iprintf("Battery low: plug in to proceed\r"); //user can charge to 2 bars or more OR just plug in the charger. charging state adds +0x80 to battery level. low 4 bits are the battery charge level. batteryMsgShown=true; } } char *filename="nand.bin"; int fail=0; swiSHA1context_t ctx; ctx.sha_block=0; //this is weird but it has to be done u8 sha1[20]={0}; char sha1file[0x33]={0}; FILE *f = fopen("nand.bin", "wb"); if(!f) death("Could not open nand file"); iprintf("Dumping...\n"); iprintf("Hold A & B to cancel\n"); iprintf("\x1b[16;0H"); iprintf("Progress: 0%%\n"); swiSHA1Init(&ctx); for(int i=0;inand_cid, &CID, 16) != 0) || (memcmp(footer->consoleid, &consoleID, 8) != 0)) death("Footer does not match"); fseek(f, 0, SEEK_SET); iprintf("Restoring...\n"); iprintf("Do not turn off the power\n"); iprintf("or remove the SD card.\n"); iprintf("\x1b[16;0H"); iprintf("Progress: 0%%\nSectors written: 0\n"); int i2=0; for(int i=0;inand_cid, 16); memcpy(iv, sha1, 16); memcpy(cpuid, &footer->consoleid[0], 8); key_x[0]=cpuid[0]; key_x[1]=cpuid[0] ^ 0x24EE6906; key_x[2]=cpuid[1] ^ 0xE65B601D; key_x[3]=cpuid[1]; F_XY((u32*)key, (u32*)key_x, (u32*)key_y); dsi_init_ctr(&ctx, key, (u8*)iv); dsi_crypt_ctr(&ctx, workbuffer, out, 0x200); if(out[510]==0x55 && out[511]==0xAA) return 1; return 0; } int main(void) { extern void dsiOnly(void); dsiOnly(); consoleDemoInit(); iprintf("SafeNANDManager v1.1.1 by\n"); iprintf("Rocket Robz (dumpTool by zoogie)\n"); workbuffer=(u8*)malloc(CHUNKSIZE); if(!workbuffer) death("Could not allocate workbuffer"); nocash_footer_t nocash_footer; u8 CID[16]; u8 consoleID[8]; getCID(CID); getConsoleID(consoleID); //request bruteforce with write only arm7 aes registers to get consoleID. not as easy as CID! char dirname[128]={0}; memset(nocash_footer.footerID, 0, sizeof(nocash_footer_t)); memcpy(nocash_footer.footerID, "DSi eMMC CID/CPU", 16); memcpy(nocash_footer.nand_cid, CID, 16); memcpy(nocash_footer.consoleid, consoleID, 8); if(!fatInitDefault() || !nand_Startup()) death("MMC init problem - dying..."); wait(1); //was having a race condition issue with nand_startup and nand_readsectors, so this might help if(getMBremaining() < 250) death("SD space remaining < 250MBs"); if(nand_GetSize()*0x200 > 600*0x100000) death("This isn't a DSi!"); //I'll give you a kidney if there's unmodified DSi out there with a 600MB NAND. snprintf(dirname, 32, "DT%016llX", *(u64*)CID); //that 'certain other tool' uses MAC for console-unique ID, while this one uses part of the nand CID. either is fine but I don't want to overwrite the other app's dump. mkdir(dirname, 0777); chdir(dirname); bool nandFound = (access("nand.bin", F_OK) == 0); iprintf("Verifying nocash_footer: "); bool isNocashFooterGood = verifyNocashFooter(&nocash_footer); iprintf("%s\n", isNocashFooterGood ? "\033[32mGOOD\033[39m":"\033[31mBAD\033[39m\nThis dump can't be decrypted\nwith this footer!\n\033[33mThis can occur if this tool is\nran through HiyaCFW (SDNAND),\nwhich isn't supported.\nPlease run it on SysNAND instead(DSiWare exploit or Unlaunch)."); //The color value not being reset in the "BAD" string is intended. "instead(DSiWare" because it reached the end of the line iprintf("\nConsoleID:\n"); for (int i = 7; i > -1; i--) iprintf("%02X", consoleID[i]); iprintf("\n\nCID: \n"); for (int i = 0; i < 16; i++) iprintf("%02X", CID[i]); if (!isNocashFooterGood) iprintf("\033[31m^ At least one of these is wrong"); //the CID value fills a whole line, no \n is needed at the beginning iprintf("\n\n\033[39m"); if (nandFound) { iprintf("Press Y to begin NAND restore\n"); } iprintf("Press A to begin NAND dump\nPress START to exit\n\n"); while(1) { swiWaitForVBlank(); scanKeys(); if ((keysDown() & KEY_Y) && nandFound && !done) restoreNAND(&nocash_footer); if ((keysDown() & KEY_A) && !done) dumpNAND(&nocash_footer); else if (keysDown() & KEY_START) break; } free(workbuffer); return 0; }