u32 CreatePad(PadInfo *info) { u8* buffer = BUFFER_ADDRESS; u32 size_byte = (info->size_mb) ? info->size_mb * 1024*1024 : info->size_b; u32 result = 0; if (!DebugCheckFreeSpace(size_byte)) return 1; if (!FileCreate(info->filename, true)) // No DebugFileCreate() here - messages are already given return 1; CryptBufferInfo decryptInfo = {.keyslot = info->keyslot, .setKeyY = info->setKeyY, .mode = info->mode, .buffer = buffer}; memcpy(decryptInfo.ctr, info->ctr, 16); memcpy(decryptInfo.keyY, info->keyY, 16); for (u32 i = 0; i < size_byte; i += BUFFER_MAX_SIZE) { u32 curr_block_size = min(BUFFER_MAX_SIZE, size_byte - i); decryptInfo.size = curr_block_size; memset(buffer, 0x00, curr_block_size); ShowProgress(i, size_byte); CryptBuffer(&decryptInfo); if (!DebugFileWrite((void*)buffer, curr_block_size, i)) { result = 1; break; } } ShowProgress(0, 0); FileClose(); return result; } u32 SdInfoGen(SdInfo* info, const char* base_path) { char* filelist = (char*)0x20400000; // check the base path for validity if ((strncmp(base_path, "/Nintendo 3DS", 13) != 0 ) || (strncmp(base_path, "/Nintendo 3DS/Private/", 22) == 0) || (strnlen(base_path, 255) < 13 + 33 + 33)) { Debug("Invalid base path given"); return 1; } Debug("Generating SDinfo.bin in memory..."); if (!GetFileList(base_path, filelist, 0x100000, true, true, false)) { Debug("Failed retrieving the filelist"); return 1; } u32 n_entries = 0; SdInfoEntry* entries = info->entries; for (char* path = strtok(filelist, "\n"); path != NULL; path = strtok(NULL, "\n")) { u32 plen = strnlen(path, 255); // get size in MB if (!FileOpen(path)) continue; entries[n_entries].size_mb = (FileGetSize() + (1024 * 1024) - 1) / (1024 * 1024); FileClose(); // skip to relevant part of path path += 13 + 33 + 33; // length of ("/Nintendo 3DS" + "/<id0>" + "/<id1>") plen -= 13 + 33 + 33; if ((strncmp(path, "/dbs", 4) != 0) && (strncmp(path, "/extdata", 8) != 0) && (strncmp(path, "/title", 6) != 0)) continue; // get filename char* filename = entries[n_entries].filename; filename[0] = '/'; for (u32 i = 1; i < 180 && path[i] != 0; i++) filename[i] = (path[i] == '/') ? '.' : path[i]; strncpy(filename + plen, ".xorpad", (180 - 1) - plen); // get AES counter GetSdCtr(entries[n_entries].ctr, path); if (++n_entries >= MAX_ENTRIES) break; } info->n_entries = n_entries; return (n_entries > 0) ? 0 : 1; }
u32 SelfTest(u32 param) { u8* test_data = (u8*) 0x20316000; const u8 teststr[16] = { 'D', '9', ' ', 'S', 'E', 'L', 'F', 'T', 'E', 'S', 'T', ' ', ' ', ' ', ' ' }; const u8 zeroes[16] = { 0x00 }; bool selftest = !(param & ST_REFERENCE); // check keyslots Debug("Checking keyslots..."); Debug("0x05 KeyY: %s", (CheckKeySlot(0x05, 'Y') == 0) ? "set up" : "not set up"); Debug("0x25 KeyX: %s", (CheckKeySlot(0x25, 'X') == 0) ? "set up" : "not set up"); Debug("0x18 KeyX: %s", (CheckKeySlot(0x18, 'X') == 0) ? "set up" : "not set up"); Debug("0x1B KeyX: %s", (CheckKeySlot(0x1B, 'X') == 0) ? "set up" : "not set up"); Debug(""); Debug((selftest) ? "Running selftest..." : "Creating selftest reference data..."); // process all subtests u32 num_tests = sizeof(TestList) / sizeof(SubTestInfo); u8* test_ptr = test_data; u32 fsize_test = 0; for (u32 i = 0; i < num_tests; i++) { u32 size = TestList[i].size; u32 size_a = align(size, 16); u32 type = TestList[i].type; u32 tparam = TestList[i].param; memset(test_ptr, 0x00, 16 + size_a); strncpy((char*) test_ptr, TestList[i].name, 16); test_ptr += 16; if (type == ST_NAND_CID_HARD) { sdmmc_get_cid(1, (uint32_t*) test_ptr); } else if (type == ST_NAND_CID_MEM) { memcpy(test_ptr, (void*) 0x01FFCD84, 16); } else if (type == ST_SHA) { sha_quick(test_ptr, teststr, 16, tparam); } else if ((type == ST_AES_MODE) || (type == ST_AES_KEYSLOT) || (type == ST_AES_KEYSLOT_Y)) { CryptBufferInfo info = {.setKeyY = 0, .size = 16, .buffer = test_ptr}; if (type == ST_AES_MODE) { info.mode = tparam; info.keyslot = 0x11; setup_aeskey(0x11, (void*) zeroes); } else { if (type == ST_AES_KEYSLOT_Y) { info.setKeyY = 1; memcpy(info.keyY, zeroes, 16); } info.mode = AES_CNT_CTRNAND_MODE; info.keyslot = tparam; } memset(info.ctr, 0x00, 16); memcpy(test_ptr, teststr, 16); CryptBuffer(&info); } else if (type == ST_TITLEKEYS) { TitleKeyEntry titlekey; memset(&titlekey, 0x00, sizeof(TitleKeyEntry)); for (titlekey.commonKeyIndex = 0; titlekey.commonKeyIndex < 6; titlekey.commonKeyIndex++) { memset(titlekey.titleId, 0x00, 8); memset(titlekey.titleKey, 0x00, 16); CryptTitlekey(&titlekey, false); memcpy(test_ptr + (titlekey.commonKeyIndex * 16), titlekey.titleKey, 16); } } test_ptr += size_a; fsize_test += 16 + size_a; }
u32 CtrNandPadgen(u32 param) { u32 keyslot; u32 nand_size; // legacy sizes & offset, to work with 3DSFAT16Tool if (GetUnitPlatform() == PLATFORM_3DS) { keyslot = 0x4; nand_size = 758; } else { keyslot = 0x5; nand_size = 1055; } Debug("Creating NAND FAT16 xorpad. Size (MB): %u", nand_size); Debug("Filename: nand.fat16.xorpad"); PadInfo padInfo = { .keyslot = keyslot, .setKeyY = 0, .size_mb = nand_size, .filename = "nand.fat16.xorpad", .mode = AES_CNT_CTRNAND_MODE }; if(GetNandCtr(padInfo.ctr, 0xB930000) != 0) return 1; return CreatePad(&padInfo); } u32 TwlNandPadgen(u32 param) { u32 size_mb = (partitions[0].size + (1024 * 1024) - 1) / (1024 * 1024); Debug("Creating TWLN FAT16 xorpad. Size (MB): %u", size_mb); Debug("Filename: twlnand.fat16.xorpad"); PadInfo padInfo = { .keyslot = partitions[0].keyslot, .setKeyY = 0, .size_mb = size_mb, .filename = "twlnand.fat16.xorpad", .mode = AES_CNT_TWLNAND_MODE }; if(GetNandCtr(padInfo.ctr, partitions[0].offset) != 0) return 1; return CreatePad(&padInfo); } u32 Firm0Firm1Padgen(u32 param) { u32 size_mb = (partitions[3].size + partitions[4].size + (1024 * 1024) - 1) / (1024 * 1024); Debug("Creating FIRM0FIRM1 xorpad. Size (MB): %u", size_mb); Debug("Filename: firm0firm1.xorpad"); PadInfo padInfo = { .keyslot = partitions[3].keyslot, .setKeyY = 0, .size_mb = size_mb, .filename = "firm0firm1.xorpad", .mode = AES_CNT_CTRNAND_MODE }; if(GetNandCtr(padInfo.ctr, partitions[3].offset) != 0) return 1; return CreatePad(&padInfo); } u32 GetNandCtr(u8* ctr, u32 offset) { static const char* versions[] = {"4.x", "5.x", "6.x", "7.x", "8.x", "9.x"}; static const u8* version_ctrs[] = { (u8*)0x080D7CAC, (u8*)0x080D858C, (u8*)0x080D748C, (u8*)0x080D740C, (u8*)0x080D74CC, (u8*)0x080D794C }; static const u32 version_ctrs_len = sizeof(version_ctrs) / sizeof(u32); static u8* ctr_start = NULL; if (ctr_start == NULL) { for (u32 i = 0; i < version_ctrs_len; i++) { if (*(u32*)version_ctrs[i] == 0x5C980) { Debug("System version %s", versions[i]); ctr_start = (u8*) version_ctrs[i] + 0x30; } } // If value not in previous list start memory scanning (test range) if (ctr_start == NULL) { for (u8* c = (u8*) 0x080D8FFF; c > (u8*) 0x08000000; c--) { if (*(u32*)c == 0x5C980 && *(u32*)(c + 1) == 0x800005C9) { ctr_start = c + 0x30; Debug("CTR start 0x%08X", ctr_start); break; } } } if (ctr_start == NULL) { Debug("CTR start not found!"); return 1; } } // the ctr is stored backwards in memory if (offset >= 0x0B100000) { // CTRNAND/AGBSAVE region for (u32 i = 0; i < 16; i++) ctr[i] = *(ctr_start + (0xF - i)); } else { // TWL region for (u32 i = 0; i < 16; i++) ctr[i] = *(ctr_start + 0x88 + (0xF - i)); } // increment counter add_ctr(ctr, offset / 0x10); return 0; } u32 DecryptNandToMem(u8* buffer, u32 offset, u32 size, PartitionInfo* partition) { CryptBufferInfo info = {.keyslot = partition->keyslot, .setKeyY = 0, .size = size, .buffer = buffer, .mode = partition->mode}; if(GetNandCtr(info.ctr, offset) != 0) return 1; u32 n_sectors = (size + NAND_SECTOR_SIZE - 1) / NAND_SECTOR_SIZE; u32 start_sector = offset / NAND_SECTOR_SIZE; ReadNandSectors(start_sector, n_sectors, buffer); CryptBuffer(&info); return 0; } u32 DecryptNandToFile(const char* filename, u32 offset, u32 size, PartitionInfo* partition) { u8* buffer = BUFFER_ADDRESS; u32 result = 0; if (!DebugFileCreate(filename, true)) return 1; for (u32 i = 0; i < size; i += NAND_SECTOR_SIZE * SECTORS_PER_READ) { u32 read_bytes = min(NAND_SECTOR_SIZE * SECTORS_PER_READ, (size - i)); ShowProgress(i, size); DecryptNandToMem(buffer, offset + i, read_bytes, partition); if(!DebugFileWrite(buffer, read_bytes, i)) { result = 1; break; } } ShowProgress(0, 0); FileClose(); return result; }
void *SilentAlarmLoop(void *ptr) { MaskKillSignals(); int sockfd; u_char buf[MAX_MSG_SIZE]; struct sockaddr_in sendaddr; if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) { LOG(CRITICAL, "Unable to create the silent alarm socket.", "Unable to create the silent alarm socket: "+string(strerror(errno))); close(sockfd); exit(EXIT_FAILURE); } sendaddr.sin_family = AF_INET; sendaddr.sin_port = htons(Config::Inst()->GetSaPort()); sendaddr.sin_addr.s_addr = INADDR_ANY; memset(sendaddr.sin_zero, '\0', sizeof sendaddr.sin_zero); struct sockaddr *sockaddrPtr = (struct sockaddr*) &sendaddr; socklen_t sendaddrSize = sizeof sendaddr; if(::bind(sockfd, sockaddrPtr, sendaddrSize) == -1) { LOG(CRITICAL, "Unable to bind to the silent alarm socket.", "Unable to bind to the silent alarm socket: "+string(strerror(errno))); close(sockfd); exit(EXIT_FAILURE); } stringstream ss; ss << "sudo iptables -A INPUT -p udp --dport " << Config::Inst()->GetSaPort() << " -j REJECT" " --reject-with icmp-port-unreachable"; if(system(ss.str().c_str()) == -1) { LOG(ERROR, "Failed to update iptables.", ""); } ss.str(""); ss << "sudo iptables -A INPUT -p tcp --dport " << Config::Inst()->GetSaPort() << " -j REJECT --reject-with tcp-reset"; if(system(ss.str().c_str()) == -1) { LOG(ERROR, "Failed to update iptables.", ""); } if(listen(sockfd, SOCKET_QUEUE_SIZE) == -1) { LOG(CRITICAL, "Unable to listen on the silent alarm socket.", "Unable to listen on the silent alarm socket.: "+string(strerror(errno))); close(sockfd); exit(EXIT_FAILURE); } int connectionSocket, bytesRead; Suspect suspectCopy; //Accept incoming Silent Alarm TCP Connections while(1) { bzero(buf, MAX_MSG_SIZE); //Blocking call if((connectionSocket = accept(sockfd, sockaddrPtr, &sendaddrSize)) == -1) { LOG(CRITICAL, "Problem while accepting incoming silent alarm connection.", "Problem while accepting incoming silent alarm connection: "+string(strerror(errno))); continue; } if((bytesRead = recv(connectionSocket, buf, MAX_MSG_SIZE, MSG_WAITALL))== -1) { LOG(CRITICAL, "Problem while receiving incoming silent alarm connection.", "Problem while receiving incoming silent alarm connection: "+string(strerror(errno))); close(connectionSocket); continue; } for(uint i = 0; i < hostAddrs.size(); i++) { //If this is from ourselves, then drop it. if(hostAddrs[i].sin_addr.s_addr == sendaddr.sin_addr.s_addr) { close(connectionSocket); continue; } } CryptBuffer(buf, bytesRead, DECRYPT); in_addr_t addr = 0; memcpy(&addr, buf, 4); uint64_t key = addr; Suspect *newSuspect = new Suspect(); if(newSuspect->Deserialize(buf, MAX_MSG_SIZE, MAIN_FEATURE_DATA) == 0) { close(connectionSocket); continue; } //If this suspect exists, update the information if(suspects.IsValidKey(key)) { suspectCopy = suspects.CheckOut(key); suspectCopy.SetFlaggedByAlarm(true); FeatureSet fs = newSuspect->GetFeatureSet(MAIN_FEATURES); suspectCopy.AddFeatureSet(&fs, MAIN_FEATURES); suspects.CheckIn(&suspectCopy); // TODO: This looks like it may be a memory leak of newSuspect } //If this is a new suspect put it in the table else { newSuspect->SetIsHostile(false); newSuspect->SetFlaggedByAlarm(true); //We set isHostile to false so that when we classify the first time // the suspect will go from benign to hostile and be sent to the doppelganger module suspects.AddNewSuspect(newSuspect); } LOG(CRITICAL, string("Got a silent alarm!. Suspect: "+ newSuspect->ToString()), ""); if(!Config::Inst()->GetClassificationTimeout()) { UpdateAndClassify(newSuspect->GetIpAddress()); } close(connectionSocket); } close(sockfd); LOG(CRITICAL, "The code should never get here, something went very wrong.", ""); return NULL; }