diff --git a/README.md b/README.md index b0cb625..0c279a2 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,19 @@ -# nxdumptool +todo: + + hfs0 methods + tik gamecard + + + + + + + + + + + +# nxdumptool Nintendo Switch Dump Tool diff --git a/source/keys.c b/source/keys.c deleted file mode 100644 index c855136..0000000 --- a/source/keys.c +++ /dev/null @@ -1,1163 +0,0 @@ -#include -#include -#include -#include - -#include "fatfs/ff.h" -#include "keys.h" -#include "util.h" -#include "ui.h" -#include "es.h" -#include "save.h" - -/* Extern variables */ - -extern int breaks; -extern int font_height; - -extern u8 *dumpBuf; - -extern char strbuf[NAME_BUF_LEN]; - -/* Statically allocated variables */ - -nca_keyset_t nca_keyset; - -static SetCalRsa2048DeviceKey eticket_data; -static bool setcal_eticket_retrieved = false; - -static keyLocation FSRodata = { - FS_TID, - SEG_RODATA, - NULL, - 0 -}; - -static keyLocation FSData = { - FS_TID, - SEG_DATA, - NULL, - 0 -}; - -static const keyInfo header_kek_source = { - "header_kek_source", - { 0x18, 0x88, 0xCA, 0xED, 0x55, 0x51, 0xB3, 0xED, 0xE0, 0x14, 0x99, 0xE8, 0x7C, 0xE0, 0xD8, 0x68, - 0x27, 0xF8, 0x08, 0x20, 0xEF, 0xB2, 0x75, 0x92, 0x10, 0x55, 0xAA, 0x4E, 0x2A, 0xBD, 0xFF, 0xC2 }, - 0x10 -}; - -static const keyInfo header_key_source = { - "header_key_source", - { 0x8F, 0x78, 0x3E, 0x46, 0x85, 0x2D, 0xF6, 0xBE, 0x0B, 0xA4, 0xE1, 0x92, 0x73, 0xC4, 0xAD, 0xBA, - 0xEE, 0x16, 0x38, 0x00, 0x43, 0xE1, 0xB8, 0xC4, 0x18, 0xC4, 0x08, 0x9A, 0x8B, 0xD6, 0x4A, 0xA6 }, - 0x20 -}; - -static const keyInfo key_area_key_application_source = { - "key_area_key_application_source", - { 0x04, 0xAD, 0x66, 0x14, 0x3C, 0x72, 0x6B, 0x2A, 0x13, 0x9F, 0xB6, 0xB2, 0x11, 0x28, 0xB4, 0x6F, - 0x56, 0xC5, 0x53, 0xB2, 0xB3, 0x88, 0x71, 0x10, 0x30, 0x42, 0x98, 0xD8, 0xD0, 0x09, 0x2D, 0x9E }, - 0x10 -}; - -static const keyInfo key_area_key_ocean_source = { - "key_area_key_ocean_source", - { 0xFD, 0x43, 0x40, 0x00, 0xC8, 0xFF, 0x2B, 0x26, 0xF8, 0xE9, 0xA9, 0xD2, 0xD2, 0xC1, 0x2F, 0x6B, - 0xE5, 0x77, 0x3C, 0xBB, 0x9D, 0xC8, 0x63, 0x00, 0xE1, 0xBD, 0x99, 0xF8, 0xEA, 0x33, 0xA4, 0x17 }, - 0x10 -}; - -static const keyInfo key_area_key_system_source = { - "key_area_key_system_source", - { 0x1F, 0x17, 0xB1, 0xFD, 0x51, 0xAD, 0x1C, 0x23, 0x79, 0xB5, 0x8F, 0x15, 0x2C, 0xA4, 0x91, 0x2E, - 0xC2, 0x10, 0x64, 0x41, 0xE5, 0x17, 0x22, 0xF3, 0x87, 0x00, 0xD5, 0x93, 0x7A, 0x11, 0x62, 0xF7 }, - 0x10 -}; - -/* Empty string hash */ -static const u8 null_hash[0x20] = { - 0xE3, 0xB0, 0xC4, 0x42, 0x98, 0xFC, 0x1C, 0x14, 0x9A, 0xFB, 0xF4, 0xC8, 0x99, 0x6F, 0xB9, 0x24, - 0x27, 0xAE, 0x41, 0xE4, 0x64, 0x9B, 0x93, 0x4C, 0xA4, 0x95, 0x99, 0x1B, 0x78, 0x52, 0xB8, 0x55 -}; - -void freeProcessMemory(keyLocation *location) -{ - if (location && location->data) - { - free(location->data); - location->data = NULL; - } -} - -bool retrieveProcessMemory(keyLocation *location) -{ - if (!location || !location->titleID || !location->mask) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid parameters to retrieve process memory.", __func__); - return false; - } - - Result result; - Handle debug_handle = INVALID_HANDLE; - - u64 d[8]; - memset(d, 0, 8 * sizeof(u64)); - - if ((location->titleID > 0x0100000000000005) && (location->titleID != 0x0100000000000028)) - { - // If not a kernel process, get PID from pm:dmnt - u64 pid; - - result = pmdmntGetProcessId(&pid, location->titleID); - if (R_FAILED(result)) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: pmdmntGetProcessId failed! (0x%08X)", __func__, result); - return false; - } - - result = svcDebugActiveProcess(&debug_handle, pid); - if (R_FAILED(result)) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: svcDebugActiveProcess failed! (0x%08X)", __func__, result); - return false; - } - - result = svcGetDebugEvent((u8*)&d, debug_handle); - if (R_FAILED(result)) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: svcGetDebugEvent failed! (0x%08X)", __func__, result); - return false; - } - } else { - // Otherwise, query svc for the process list - u64 pids[300]; - u32 num_processes; - - result = svcGetProcessList(&num_processes, pids, 300); - if (R_FAILED(result)) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: svcGetProcessList failed! (0x%08X)", __func__, result); - return false; - } - - u32 i; - - for(i = 0; i < (num_processes - 1); i++) - { - if (R_SUCCEEDED(svcDebugActiveProcess(&debug_handle, pids[i])) && R_SUCCEEDED(svcGetDebugEvent((u8*)&d, debug_handle)) && (d[2] == location->titleID)) break; - if (debug_handle) svcCloseHandle(debug_handle); - } - - if (i == (num_processes - 1)) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to retrieve debug handle for process with Title ID %016lX!", __func__, location->titleID); - if (debug_handle) svcCloseHandle(debug_handle); - return false; - } - } - - MemoryInfo mem_info; - memset(&mem_info, 0, sizeof(MemoryInfo)); - - u32 page_info; - u64 addr = 0; - u8 segment; - u64 last_text_addr = 0; - u8 *dataTmp = NULL; - - bool success = true; - - // Locate "real" .text segment as Atmosphere emuMMC has two - for(;;) - { - result = svcQueryDebugProcessMemory(&mem_info, &page_info, debug_handle, addr); - if (R_FAILED(result)) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: svcQueryDebugProcessMemory failed! (0x%08X)", __func__, result); - success = false; - break; - } - - if ((mem_info.perm & Perm_X) && ((mem_info.type & 0xFF) >= MemType_CodeStatic) && ((mem_info.type & 0xFF) < MemType_Heap)) last_text_addr = mem_info.addr; - - addr = (mem_info.addr + mem_info.size); - if (!addr) break; - } - - if (!success) - { - svcCloseHandle(debug_handle); - return success; - } - - addr = last_text_addr; - - for(segment = 1; segment < BIT(3);) - { - result = svcQueryDebugProcessMemory(&mem_info, &page_info, debug_handle, addr); - if (R_FAILED(result)) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: svcQueryDebugProcessMemory failed! (0x%08X)", __func__, result); - success = false; - break; - } - - // Weird code to allow for bitmasking segments - if ((mem_info.perm & Perm_R) && ((mem_info.type & 0xFF) >= MemType_CodeStatic) && ((mem_info.type & 0xFF) < MemType_Heap) && ((segment <<= 1) >> 1 & location->mask) > 0) - { - // If location->data == NULL, realloc will essentially act as a malloc - dataTmp = realloc(location->data, location->dataSize + mem_info.size); - if (!dataTmp) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to resize key location data buffer to %lu bytes.", __func__, location->dataSize + mem_info.size); - success = false; - break; - } - - location->data = dataTmp; - dataTmp = NULL; - - memset(location->data + location->dataSize, 0, mem_info.size); - - result = svcReadDebugProcessMemory(location->data + location->dataSize, debug_handle, mem_info.addr, mem_info.size); - if (R_FAILED(result)) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: svcReadDebugProcessMemory failed! (0x%08X)", __func__, result); - success = false; - break; - } - - location->dataSize += mem_info.size; - } - - addr = (mem_info.addr + mem_info.size); - if (addr == 0) break; - } - - svcCloseHandle(debug_handle); - - if (success) - { - if (!location->data || !location->dataSize) success = false; - } - - return success; -} - -bool findKeyInProcessMemory(const keyLocation *location, const keyInfo *findKey, u8 *out) -{ - if (!location || !location->data || !location->dataSize || !findKey || !strlen(findKey->name) || !findKey->size) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid parameters to locate key in process memory.", __func__); - return false; - } - - u64 i; - u8 temp_hash[SHA256_HASH_SIZE]; - bool found = false; - - // Hash every key-length-sized byte chunk in data until it matches a key hash - for(i = 0; i < location->dataSize; i++) - { - if (!found && (location->dataSize - i) < findKey->size) break; - - sha256CalculateHash(temp_hash, location->data + i, findKey->size); - - if (!memcmp(temp_hash, findKey->hash, SHA256_HASH_SIZE)) - { - // Jackpot - memcpy(out, location->data + i, findKey->size); - found = true; - break; - } - } - - if (!found) uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to locate key \"%s\" in process memory!", __func__, findKey->name); - - return found; -} - -bool findFSRodataKeys(keyLocation *location) -{ - if (!location || location->titleID != FS_TID || location->mask != SEG_RODATA || !location->data || !location->dataSize) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid parameters to locate keys in FS .rodata segment.", __func__); - return false; - } - - if (!findKeyInProcessMemory(location, &header_kek_source, nca_keyset.header_kek_source)) return false; - nca_keyset.memory_key_cnt++; - - if (!findKeyInProcessMemory(location, &key_area_key_application_source, nca_keyset.key_area_key_application_source)) return false; - nca_keyset.memory_key_cnt++; - - if (!findKeyInProcessMemory(location, &key_area_key_ocean_source, nca_keyset.key_area_key_ocean_source)) return false; - nca_keyset.memory_key_cnt++; - - if (!findKeyInProcessMemory(location, &key_area_key_system_source, nca_keyset.key_area_key_system_source)) return false; - nca_keyset.memory_key_cnt++; - - return true; -} - -bool loadMemoryKeys() -{ - if (nca_keyset.memory_key_cnt > 0) return true; - - Result result; - bool proceed; - - if (!retrieveProcessMemory(&FSRodata)) return false; - proceed = findFSRodataKeys(&FSRodata); - freeProcessMemory(&FSRodata); - if (!proceed) return false; - - if (!retrieveProcessMemory(&FSData)) return false; - proceed = findKeyInProcessMemory(&FSData, &header_key_source, nca_keyset.header_key_source); - freeProcessMemory(&FSData); - if (!proceed) return false; - nca_keyset.memory_key_cnt++; - - // Derive NCA header key - result = splCryptoInitialize(); - if (R_FAILED(result)) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to initialize the spl:crypto service! (0x%08X)", __func__, result); - return false; - } - - result = splCryptoGenerateAesKek(nca_keyset.header_kek_source, 0, 0, nca_keyset.header_kek); - if (R_FAILED(result)) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: splCryptoGenerateAesKek(header_kek_source) failed! (0x%08X)", __func__, result); - splCryptoExit(); - return false; - } - - nca_keyset.memory_key_cnt++; - - result = splCryptoGenerateAesKey(nca_keyset.header_kek, nca_keyset.header_key_source + 0x00, nca_keyset.header_key + 0x00); - if (R_FAILED(result)) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: splCryptoGenerateAesKey(header_key_source + 0x00) failed! (0x%08X)", __func__, result); - splCryptoExit(); - return false; - } - - result = splCryptoGenerateAesKey(nca_keyset.header_kek, nca_keyset.header_key_source + 0x10, nca_keyset.header_key + 0x10); - if (R_FAILED(result)) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: splCryptoGenerateAesKey(header_key_source + 0x10) failed! (0x%08X)", __func__, result); - splCryptoExit(); - return false; - } - - nca_keyset.memory_key_cnt++; - - nca_keyset.total_key_cnt += nca_keyset.memory_key_cnt; - - splCryptoExit(); - - return true; -} - -bool decryptNcaKeyArea(nca_header_t *dec_nca_header, u8 *out) -{ - if (!dec_nca_header || dec_nca_header->kaek_ind > 2 || !out) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid parameters to decrypt NCA key area.", __func__); - return false; - } - - Result result; - - u8 i; - u8 tmp_kek[0x10]; - - u8 crypto_type = (dec_nca_header->crypto_type2 > dec_nca_header->crypto_type ? dec_nca_header->crypto_type2 : dec_nca_header->crypto_type); - if (crypto_type > 0x20) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid NCA keyblob index!", __func__); - return false; - } - - u8 *kek_source = (dec_nca_header->kaek_ind == 0 ? nca_keyset.key_area_key_application_source : (dec_nca_header->kaek_ind == 1 ? nca_keyset.key_area_key_ocean_source : nca_keyset.key_area_key_system_source)); - - result = splCryptoInitialize(); - if (R_FAILED(result)) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to initialize the spl:crypto service! (0x%08X)", __func__, result); - return false; - } - - result = splCryptoGenerateAesKek(kek_source, crypto_type, 0, tmp_kek); - if (R_FAILED(result)) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: splCryptoGenerateAesKek(kek_source) failed! (0x%08X)", __func__, result); - splCryptoExit(); - return false; - } - - bool success = true; - u8 decrypted_nca_keys[NCA_KEY_AREA_KEY_CNT][NCA_KEY_AREA_KEY_SIZE]; - - for(i = 0; i < NCA_KEY_AREA_KEY_CNT; i++) - { - result = splCryptoGenerateAesKey(tmp_kek, dec_nca_header->nca_keys[i], decrypted_nca_keys[i]); - if (R_FAILED(result)) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: splCryptoGenerateAesKey(nca_kaek_%02u) failed! (0x%08X)", __func__, i, result); - success = false; - break; - } - } - - splCryptoExit(); - - memcpy(out, decrypted_nca_keys, NCA_KEY_AREA_SIZE); - - return success; -} - -/** - * Reads a line from file f and parses out the key and value from it. - * The format of a line must match /^ *[A-Za-z0-9_] *[,=] *.+$/. - * If a line ends in \r, the final \r is stripped. - * The input file is assumed to have been opened with the 'b' flag. - * The input file is assumed to contain only ASCII. - * - * A line cannot exceed 512 bytes in length. - * Lines that are excessively long will be silently truncated. - * - * On success, *key and *value will be set to point to the key and value in - * the input line, respectively. - * *key and *value may also be NULL in case of empty lines. - * On failure, *key and *value will be set to NULL. - * End of file is considered failure. - * - * Because *key and *value will point to a static buffer, their contents must be - * copied before calling this function again. - * For the same reason, this function is not thread-safe. - * - * The key will be converted to lowercase. - * An empty key is considered a parse error, but an empty value is returned as - * success. - * - * This function assumes that the file can be trusted not to contain any NUL in - * the contents. - * - * Whitespace (' ', ASCII 0x20, as well as '\t', ASCII 0x09) at the beginning of - * the line, at the end of the line as well as around = (or ,) will be ignored. - * - * @param f the file to read - * @param key pointer to change to point to the key - * @param value pointer to change to point to the value - * @return 0 on success, - * 1 on end of file, - * -1 on parse error (line too long, line malformed) - * -2 on I/O error - */ -static int get_kv(FILE *f, char **key, char **value) -{ -#define SKIP_SPACE(p) do {\ - for (; (*p == ' ' || *p == '\t'); ++p);\ -} while(0); - - static char line[512]; - char *k, *v, *p, *end; - - *key = *value = NULL; - - errno = 0; - - if (fgets(line, (int)sizeof(line), f) == NULL) - { - if (feof(f)) - { - return 1; - } else { - return -2; - } - } - - if (errno != 0) return -2; - - if (*line == '\n' || *line == '\r' || *line == '\0') return 0; - - /* Not finding \r or \n is not a problem. - * The line might just be exactly 512 characters long, we have no way to - * tell. - * Additionally, it's possible that the last line of a file is not actually - * a line (i.e., does not end in '\n'); we do want to handle those. - */ - if ((p = strchr(line, '\r')) != NULL || (p = strchr(line, '\n')) != NULL) - { - end = p; - *p = '\0'; - } else { - end = (line + strlen(line) + 1); - } - - p = line; - SKIP_SPACE(p); - k = p; - - /* Validate key and convert to lower case. */ - for (; *p != ' ' && *p != ',' && *p != '\t' && *p != '='; ++p) - { - if (*p == '\0') return -1; - - if (*p >= 'A' && *p <= 'Z') - { - *p = 'a' + (*p - 'A'); - continue; - } - - if (*p != '_' && (*p < '0' && *p > '9') && (*p < 'a' && *p > 'z')) return -1; - } - - /* Bail if the final ++p put us at the end of string */ - if (*p == '\0') return -1; - - /* We should be at the end of key now and either whitespace or [,=] - * follows. - */ - if (*p == '=' || *p == ',') - { - *p++ = '\0'; - } else { - *p++ = '\0'; - SKIP_SPACE(p); - if (*p != '=' && *p != ',') return -1; - *p++ = '\0'; - } - - /* Empty key is an error. */ - if (*k == '\0') return -1; - - SKIP_SPACE(p); - v = p; - - /* Skip trailing whitespace */ - for (p = end - 1; *p == '\t' || *p == ' '; --p); - - *(p + 1) = '\0'; - - *key = k; - *value = v; - - return 0; - -#undef SKIP_SPACE -} - -static char hextoi(char c) -{ - if ('a' <= c && c <= 'f') return (c - 'a' + 0xA); - if ('A' <= c && c <= 'F') return (c - 'A' + 0xA); - if ('0' <= c && c <= '9') return (c - '0'); - return 'z'; -} - -int parse_hex_key(unsigned char *key, const char *hex, unsigned int len) -{ - u32 i; - size_t hex_str_len = (2 * len); - - if (strlen(hex) != hex_str_len) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: key (%s) must be %u hex digits!", __func__, hex, hex_str_len); - return 0; - } - - memset(key, 0, len); - - for(i = 0; i < hex_str_len; i++) - { - char val = hextoi(hex[i]); - if (val == 'z') - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: key (%s) must be %u hex digits!", __func__, hex, hex_str_len); - return 0; - } - - if ((i & 1) == 0) val <<= 4; - key[i >> 1] |= val; - } - - return 1; -} - -int readKeysFromFile(FILE *f) -{ - u32 i; - int ret; - char *key, *value; - char test_name[0x100]; - bool common_eticket = false, personalized_eticket = false; - - while((ret = get_kv(f, &key, &value)) != 1 && ret != -2) - { - if (ret == 0) - { - if (key == NULL || value == NULL) continue; - - if (!common_eticket && !personalized_eticket && strcasecmp(key, "eticket_rsa_kek") == 0) - { - if (!parse_hex_key(nca_keyset.eticket_rsa_kek, value, sizeof(nca_keyset.eticket_rsa_kek))) return 0; - nca_keyset.ext_key_cnt++; - common_eticket = true; - } else - if (!personalized_eticket && strcasecmp(key, "eticket_rsa_kek_personalized") == 0) - { - /* Use the personalized eTicket RSA kek if available */ - /* This only appears on consoles that use the new PRODINFO key generation scheme */ - if (!parse_hex_key(nca_keyset.eticket_rsa_kek, value, sizeof(nca_keyset.eticket_rsa_kek))) return 0; - if (!common_eticket) nca_keyset.ext_key_cnt++; - personalized_eticket = true; - } else { - memset(test_name, 0, sizeof(test_name)); - - for(i = 0; i < 0x20; i++) - { - snprintf(test_name, sizeof(test_name), "titlekek_%02x", i); - if (strcasecmp(key, test_name) == 0) - { - if (!parse_hex_key(nca_keyset.titlekeks[i], value, sizeof(nca_keyset.titlekeks[i]))) return 0; - nca_keyset.ext_key_cnt++; - break; - } - - snprintf(test_name, sizeof(test_name), "key_area_key_application_%02x", i); - if (strcasecmp(key, test_name) == 0) - { - if (!parse_hex_key(nca_keyset.key_area_keys[i][0], value, sizeof(nca_keyset.key_area_keys[i][0]))) return 0; - nca_keyset.ext_key_cnt++; - break; - } - - snprintf(test_name, sizeof(test_name), "key_area_key_ocean_%02x", i); - if (strcasecmp(key, test_name) == 0) - { - if (!parse_hex_key(nca_keyset.key_area_keys[i][1], value, sizeof(nca_keyset.key_area_keys[i][1]))) return 0; - nca_keyset.ext_key_cnt++; - break; - } - - snprintf(test_name, sizeof(test_name), "key_area_key_system_%02x", i); - if (strcasecmp(key, test_name) == 0) - { - if (!parse_hex_key(nca_keyset.key_area_keys[i][2], value, sizeof(nca_keyset.key_area_keys[i][2]))) return 0; - nca_keyset.ext_key_cnt++; - break; - } - } - } - } - } - - if (!nca_keyset.ext_key_cnt) return -1; - - nca_keyset.total_key_cnt += nca_keyset.ext_key_cnt; - - return 1; -} - -bool loadExternalKeys() -{ - // Check if the keyset has been already loaded - if (nca_keyset.ext_key_cnt > 0) return true; - - // Open keys file - FILE *keysFile = fopen(KEYS_FILE_PATH, "rb"); - if (!keysFile) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to open \"%s\" to retrieve \"eticket_rsa_kek\", titlekeks and KAEKs!", __func__, KEYS_FILE_PATH); - return false; - } - - // Load keys - int ret = readKeysFromFile(keysFile); - fclose(keysFile); - - if (ret < 1) - { - if (ret == -1) uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to parse necessary keys from \"%s\"! (keys file empty?)", __func__, KEYS_FILE_PATH); - return false; - } - - return true; -} - -bool testKeyPair(const void *E, const void *D, const void *N) -{ - if (!E || !D || !N) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid parameters to test RSA key pair.", __func__); - return false; - } - - Result result; - u8 X[0x100] = {0}, Y[0x100] = {0}, Z[0x100] = {0}; - size_t i; - - // 0xCAFEBABE - X[0xFC] = 0xCA; - X[0xFD] = 0xFE; - X[0xFE] = 0xBA; - X[0xFF] = 0xBE; - - result = splUserExpMod(X, N, D, 0x100, Y); - if (R_FAILED(result)) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: splUserExpMod failed! (testKeyPair #1) (0x%08X)", __func__, result); - return false; - } - - result = splUserExpMod(Y, N, E, 4, Z); - if (R_FAILED(result)) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: splUserExpMod failed! (testKeyPair #2) (0x%08X)", __func__, result); - return false; - } - - for(i = 0; i < 0x100; i++) - { - if (X[i] != Z[i]) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid RSA key pair!", __func__); - return false; - } - } - - return true; -} - -void mgf1(const u8 *data, size_t data_length, u8 *mask, size_t mask_length) -{ - if (!data || !data_length || !mask || !mask_length) return; - - u8 *data_counter = calloc(data_length + 4, sizeof(u8)); - if (!data_counter) return; - - memcpy(data_counter, data, data_length); - - sha256CalculateHash(mask, data_counter, data_length + 4); - - u32 i, j; - for(i = 1; i < ((mask_length / 0x20) + 1); i++) - { - for(j = 0; j < 4; j++) data_counter[data_length + 3 - j] = ((i >> (8 * j)) & 0xFF); - - if ((i * 0x20) <= mask_length) - { - sha256CalculateHash(mask + (i * 0x20), data_counter, data_length + 4); - } else { - u8 temp_mask[0x20]; - sha256CalculateHash(temp_mask, data_counter, data_length + 4); - memcpy(mask + (i * 0x20), temp_mask, mask_length - (i * 0x20)); - } - } - - free(data_counter); -} - -int retrieveNcaTikTitleKey(nca_header_t *dec_nca_header, u8 *out_tik, u8 *out_enc_key, u8 *out_dec_key) -{ - int ret = -1; - - if (!dec_nca_header || dec_nca_header->kaek_ind > 2 || (!out_tik && !out_dec_key && !out_enc_key)) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid parameters to retrieve NCA ticket and/or titlekey.", __func__); - return ret; - } - - u32 i, j; - bool has_rights_id = false; - - for(i = 0; i < 0x10; i++) - { - if (dec_nca_header->rights_id[i] != 0) - { - has_rights_id = true; - break; - } - } - - if (!has_rights_id) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: NCA doesn't use titlekey crypto.", __func__); - return ret; - } - - u8 crypto_type = (dec_nca_header->crypto_type2 > dec_nca_header->crypto_type ? dec_nca_header->crypto_type2 : dec_nca_header->crypto_type); - if (crypto_type) crypto_type--; - - if (crypto_type >= 0x20) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid NCA keyblob index.", __func__); - return ret; - } - - Result result; - u32 common_count, personalized_count, ids_written; - FsRightsId *common_rights_ids = NULL, *personalized_rights_ids = NULL; - - bool foundRightsId = false; - u8 rightsIdType = 0; // 1 = Common, 2 = Personalized - - Aes128CtrContext eticket_aes_ctx; - unsigned char ctr[0x10]; - - u8 *D = NULL, *N = NULL, *E = NULL; - - FRESULT fr; - FIL eTicketSave; - - save_ctx_t *save_ctx = NULL; - allocation_table_storage_ctx_t fat_storage; - save_fs_list_entry_t entry; - const char ticket_bin_path[SAVE_FS_LIST_MAX_NAME_LENGTH] = "/ticket.bin"; - - u32 buf_size = (ETICKET_ENTRY_SIZE * 0x10); - u32 br = buf_size; - u64 total_br = 0; - - char tmp[NAME_BUF_LEN / 2] = {'\0'}; - - bool foundEticket = false, proceed = true; - - u8 titlekey[0x10]; - Aes128Context titlekey_aes_ctx; - - result = esInitialize(); - if (R_FAILED(result)) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to initialize the ES service! (0x%08X)", __func__, result); - return ret; - } - - result = esCountCommonTicket(&common_count); - if (R_FAILED(result)) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: esCountCommonTicket failed! (0x%08X)", __func__, result); - esExit(); - return ret; - } - - result = esCountPersonalizedTicket(&personalized_count); - if (R_FAILED(result)) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: esCountPersonalizedTicket failed! (0x%08X)", __func__, result); - esExit(); - return ret; - } - - if (!common_count && !personalized_count) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: no tickets available!", __func__); - esExit(); - return ret; - } - - if (common_count) - { - common_rights_ids = calloc(common_count, sizeof(FsRightsId)); - if (!common_rights_ids) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to allocate memory for common tickets' rights IDs!", __func__); - esExit(); - return ret; - } - - result = esListCommonTicket(&ids_written, common_rights_ids, common_count * sizeof(FsRightsId)); - if (R_FAILED(result)) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: esListCommonTicket failed! (0x%08X)", __func__, result); - free(common_rights_ids); - esExit(); - return ret; - } - - for(i = 0; i < common_count; i++) - { - if (!memcmp(common_rights_ids[i].c, dec_nca_header->rights_id, 0x10)) - { - foundRightsId = true; - rightsIdType = 1; // Common - break; - } - } - - free(common_rights_ids); - } - - if (!foundRightsId && personalized_count) - { - personalized_rights_ids = calloc(personalized_count, sizeof(FsRightsId)); - if (!personalized_rights_ids) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to allocate memory for personalized tickets' rights IDs!", __func__); - esExit(); - return ret; - } - - result = esListPersonalizedTicket(&ids_written, personalized_rights_ids, personalized_count * sizeof(FsRightsId)); - if (R_FAILED(result)) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: esListPersonalizedTicket failed! (0x%08X)", __func__, result); - free(personalized_rights_ids); - esExit(); - return ret; - } - - for(i = 0; i < personalized_count; i++) - { - if (!memcmp(personalized_rights_ids[i].c, dec_nca_header->rights_id, 0x10)) - { - foundRightsId = true; - rightsIdType = 2; // Personalized - break; - } - } - - free(personalized_rights_ids); - } - - esExit(); - - if (!foundRightsId || (rightsIdType != 1 && rightsIdType != 2)) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: NCA rights ID unavailable in this console!", __func__); - ret = -2; - dumpSharedTikSavedata(); - return ret; - } - - // Load external keys - if (!loadExternalKeys()) return ret; - - if (!setcal_eticket_retrieved) - { - // Get extended eTicket RSA key from PRODINFO - memset(&eticket_data, 0, sizeof(SetCalRsa2048DeviceKey)); - - result = setcalInitialize(); - if (R_FAILED(result)) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to initialize the set:cal service! (0x%08X)", __func__, result); - return ret; - } - - result = setcalGetEticketDeviceKey(&eticket_data); - - setcalExit(); - - if (R_FAILED(result)) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: setcalGetEticketDeviceKey failed! (0x%08X)", __func__, result); - return ret; - } - - // Decrypt eTicket RSA key - memcpy(ctr, eticket_data.key, ETICKET_DEVKEY_RSA_CTR_SIZE); - aes128CtrContextCreate(&eticket_aes_ctx, nca_keyset.eticket_rsa_kek, ctr); - aes128CtrCrypt(&eticket_aes_ctx, eticket_data.key + ETICKET_DEVKEY_RSA_OFFSET, eticket_data.key + ETICKET_DEVKEY_RSA_OFFSET, ETICKET_DEVKEY_RSA_SIZE); - - // Public exponent must use RSA-2048 SHA-1 signature method - // The value is stored use big endian byte order - if (__builtin_bswap32(*((u32*)(eticket_data.key + ETICKET_DEVKEY_RSA_OFFSET + 0x200))) != SIGTYPE_RSA2048_SHA1) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid public RSA exponent for eTicket data! Wrong keys?\nTry running Lockpick_RCM to generate the keys file from scratch.", __func__); - return ret; - } - } - - D = (eticket_data.key + ETICKET_DEVKEY_RSA_OFFSET); - N = (eticket_data.key + ETICKET_DEVKEY_RSA_OFFSET + 0x100); - E = (eticket_data.key + ETICKET_DEVKEY_RSA_OFFSET + 0x200); - - if (!setcal_eticket_retrieved) - { - if (!testKeyPair(E, D, N)) return ret; - setcal_eticket_retrieved = true; - } - - // FatFs is used to mount the BIS System partition and read the ES savedata files to avoid 0xE02 (file already in use) errors - fr = f_open(&eTicketSave, (rightsIdType == 1 ? BIS_COMMON_TIK_SAVE_NAME : BIS_PERSONALIZED_TIK_SAVE_NAME), FA_READ | FA_OPEN_EXISTING); - if (fr) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to open ES %s eTicket save! (%u)", __func__, (rightsIdType == 1 ? "common" : "personalized"), fr); - return ret; - } - - save_ctx = calloc(1, sizeof(save_ctx_t)); - if (!save_ctx) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: failed to allocate memory for ticket savefile context!"); - f_close(&eTicketSave); - return ret; - } - - save_ctx->file = &eTicketSave; - save_ctx->tool_ctx.action = 0; - - if (!save_process(save_ctx)) - { - snprintf(tmp, MAX_CHARACTERS(tmp), "\n%s: failed to process ticket savefile!", __func__); - strcat(strbuf, tmp); - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, strbuf); - free(save_ctx); - f_close(&eTicketSave); - return ret; - } - - if (!save_hierarchical_file_table_get_file_entry_by_path(&save_ctx->save_filesystem_core.file_table, ticket_bin_path, &entry)) - { - snprintf(tmp, MAX_CHARACTERS(tmp), "\n%s: failed to get file entry for \"%s\" in ticket savefile!", __func__, ticket_bin_path); - strcat(strbuf, tmp); - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, strbuf); - save_free_contexts(save_ctx); - free(save_ctx); - f_close(&eTicketSave); - return ret; - } - - if (!save_open_fat_storage(&save_ctx->save_filesystem_core, &fat_storage, entry.value.save_file_info.start_block)) - { - snprintf(tmp, MAX_CHARACTERS(tmp), "\n%s: failed to open FAT storage at block 0x%X for \"%s\" in ticket savefile!", __func__, entry.value.save_file_info.start_block, ticket_bin_path); - strcat(strbuf, tmp); - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, strbuf); - save_free_contexts(save_ctx); - free(save_ctx); - f_close(&eTicketSave); - return ret; - } - - while(br == buf_size && total_br < entry.value.save_file_info.length) - { - br = save_allocation_table_storage_read(&fat_storage, dumpBuf, total_br, buf_size); - if (br != buf_size) - { - snprintf(tmp, MAX_CHARACTERS(tmp), "\n%s: failed to read %u bytes chunk at offset 0x%lX from \"%s\" in ticket savefile!", __func__, buf_size, total_br, ticket_bin_path); - strcat(strbuf, tmp); - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, strbuf); - proceed = false; - break; - } - - if (dumpBuf[0] == 0) break; - - total_br += br; - - for(i = 0; i < buf_size; i += ETICKET_ENTRY_SIZE) - { - // Only read eTicket entries with RSA-2048 SHA-256 signature method - // Also check if our current eTicket entry matches our rights ID - if (*((u32*)(dumpBuf + i)) != SIGTYPE_RSA2048_SHA256 || memcmp(dumpBuf + i + ETICKET_RIGHTSID_OFFSET, dec_nca_header->rights_id, 0x10) != 0) continue; - - foundEticket = true; - - if (rightsIdType == 1) - { - // Common - memcpy(titlekey, dumpBuf + i + ETICKET_TITLEKEY_OFFSET, 0x10); - } else { - // Personalized - u8 M[0x100], salt[0x20], db[0xDF]; - - u8 *titleKeyBlock = (dumpBuf + i + ETICKET_TITLEKEY_OFFSET); - - result = splUserExpMod(titleKeyBlock, N, D, 0x100, M); - if (R_FAILED(result)) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: splUserExpMod failed! (titleKeyBlock) (0x%08X)", __func__, result); - proceed = false; - break; - } - - // Decrypt the titlekey - mgf1(M + 0x21, 0xDF, salt, 0x20); - for(j = 0; j < 0x20; j++) salt[j] ^= M[j + 1]; - - mgf1(salt, 0x20, db, 0xDF); - for(j = 0; j < 0xDF; j++) db[j] ^= M[j + 0x21]; - - // Verify if it starts with a null string hash - if (memcmp(db, null_hash, 0x20) != 0) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: titlekey decryption failed! Wrong keys?\nTry running Lockpick_RCM to generate the keys file from scratch.", __func__); - proceed = false; - break; - } - - memcpy(titlekey, db + 0xCF, 0x10); - } - - break; - } - - if (foundEticket) break; - } - - save_free_contexts(save_ctx); - free(save_ctx); - f_close(&eTicketSave); - - if (!proceed) return ret; - - if (!foundEticket) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: unable to find a matching eTicket entry for NCA rights ID!", __func__); - ret = -2; - dumpSharedTikSavedata(); - return ret; - } - - ret = 0; - - // Copy ticket data to output pointer - if (out_tik != NULL) memcpy(out_tik, dumpBuf + i, ETICKET_TIK_FILE_SIZE); - - // Copy encrypted titlekey to output pointer - // It is used in personalized -> common ticket conversion - if (out_enc_key != NULL) memcpy(out_enc_key, titlekey, 0x10); - - // Generate decrypted titlekey ready to use for section decryption - // It is also used in ticket-less dumps as the NCA key area slot #2 key (before encryption) - if (out_dec_key != NULL) - { - aes128ContextCreate(&titlekey_aes_ctx, nca_keyset.titlekeks[crypto_type], false); - aes128DecryptBlock(&titlekey_aes_ctx, out_dec_key, titlekey); - } - - return ret; -} - -bool generateEncryptedNcaKeyAreaWithTitlekey(nca_header_t *dec_nca_header, u8 *decrypted_nca_keys) -{ - if (!dec_nca_header || dec_nca_header->kaek_ind > 2 || !decrypted_nca_keys || !nca_keyset.ext_key_cnt) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid parameters to generate encrypted NCA key area using titlekey!", __func__); - return false; - } - - u8 i; - Aes128Context key_area_ctx; - - u8 crypto_type = (dec_nca_header->crypto_type2 > dec_nca_header->crypto_type ? dec_nca_header->crypto_type2 : dec_nca_header->crypto_type); - if (crypto_type) crypto_type--; - - if (crypto_type >= 0x20) - { - uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "%s: invalid NCA keyblob index.", __func__); - return false; - } - - aes128ContextCreate(&key_area_ctx, nca_keyset.key_area_keys[crypto_type][dec_nca_header->kaek_ind], true); - - for(i = 0; i < NCA_KEY_AREA_KEY_CNT; i++) aes128EncryptBlock(&key_area_ctx, dec_nca_header->nca_keys[i], decrypted_nca_keys + (i * NCA_KEY_AREA_KEY_SIZE)); - - return true; -} diff --git a/source/keys.h b/source/keys.h deleted file mode 100644 index 50d083f..0000000 --- a/source/keys.h +++ /dev/null @@ -1,65 +0,0 @@ -#pragma once - -#ifndef __KEYS_H__ -#define __KEYS_H__ - -#include -#include "nca.h" - -#define FS_TID (u64)0x0100000000000000 - -#define SEG_TEXT BIT(0) -#define SEG_RODATA BIT(1) -#define SEG_DATA BIT(2) - -#define ETICKET_DEVKEY_RSA_CTR_SIZE 0x10 -#define ETICKET_DEVKEY_RSA_OFFSET ETICKET_DEVKEY_RSA_CTR_SIZE -#define ETICKET_DEVKEY_RSA_SIZE 0x230 - -#define SIGTYPE_RSA2048_SHA1 (u32)0x10001 -#define SIGTYPE_RSA2048_SHA256 (u32)0x10004 - -typedef struct { - u64 titleID; - u8 mask; - u8 *data; - u64 dataSize; -} keyLocation; - -typedef struct { - char name[128]; - u8 hash[SHA256_HASH_SIZE]; - u64 size; -} keyInfo; - -typedef struct { - u16 memory_key_cnt; /* Key counter for keys retrieved from memory. */ - u16 ext_key_cnt; /* Key counter for keys retrieved from keysfile. */ - u32 total_key_cnt; /* Total key counter. */ - - // Needed to decrypt the NCA header using AES-128-XTS - u8 header_kek_source[0x10]; /* Seed for header kek. Retrieved from the .rodata section in the FS sysmodule. */ - u8 header_key_source[0x20]; /* Seed for NCA header key. Retrieved from the .data section in the FS sysmodule. */ - u8 header_kek[0x10]; /* NCA header kek. Generated from header_kek_source. */ - u8 header_key[0x20]; /* NCA header key. Generated from header_kek and header_key_source. */ - - // Needed to derive the KAEK used to decrypt the NCA key area - u8 key_area_key_application_source[0x10]; /* Seed for kaek 0. Retrieved from the .rodata section in the FS sysmodule. */ - u8 key_area_key_ocean_source[0x10]; /* Seed for kaek 1. Retrieved from the .rodata section in the FS sysmodule. */ - u8 key_area_key_system_source[0x10]; /* Seed for kaek 2. Retrieved from the .rodata section in the FS sysmodule. */ - - // Needed to decrypt the title key block from an eTicket. Retrieved from the Lockpick_RCM keys file. - u8 eticket_rsa_kek[0x10]; /* eTicket RSA kek. */ - u8 titlekeks[0x20][0x10]; /* Title key encryption keys. */ - - // Needed to reencrypt the NCA key area for tik-less NSP dumps. Retrieved from the Lockpick_RCM keys file. - u8 key_area_keys[0x20][3][0x10]; /* Key area encryption keys. */ -} nca_keyset_t; - -bool loadMemoryKeys(); -bool decryptNcaKeyArea(nca_header_t *dec_nca_header, u8 *out); -bool loadExternalKeys(); -int retrieveNcaTikTitleKey(nca_header_t *dec_nca_header, u8 *out_tik, u8 *out_enc_key, u8 *out_dec_key); -bool generateEncryptedNcaKeyAreaWithTitlekey(nca_header_t *dec_nca_header, u8 *decrypted_nca_keys); - -#endif diff --git a/source/new/es.c b/source/new/es.c index ab514d7..75d2aff 100644 --- a/source/new/es.c +++ b/source/new/es.c @@ -6,18 +6,10 @@ #include "es.h" #include "service_guard.h" -static Service g_esSrv; +static Service g_esSrv = {0}; NX_GENERATE_SERVICE_GUARD(es); -Result _esInitialize() { - return smGetService(&g_esSrv, "es"); -} - -void _esCleanup() { - serviceClose(&g_esSrv); -} - Result esCountCommonTicket(s32 *out_count) { struct { @@ -50,7 +42,7 @@ Result esListCommonTicket(s32 *out_entries_written, FsRightsId *out_ids, s32 cou Result rc = serviceDispatchInOut(&g_esSrv, 11, *out_entries_written, out, .buffer_attrs = { SfBufferAttr_HipcMapAlias | SfBufferAttr_Out }, - .buffers = { { out_ids, count * sizeof(FsRightsId) } }, + .buffers = { { out_ids, count * sizeof(FsRightsId) } } ); if (R_SUCCEEDED(rc) && out_entries_written) *out_entries_written = out.num_rights_ids_written; @@ -66,10 +58,20 @@ Result esListPersonalizedTicket(s32 *out_entries_written, FsRightsId *out_ids, s Result rc = serviceDispatchInOut(&g_esSrv, 12, *out_entries_written, out, .buffer_attrs = { SfBufferAttr_HipcMapAlias | SfBufferAttr_Out }, - .buffers = { { out_ids, count * sizeof(FsRightsId) } }, + .buffers = { { out_ids, count * sizeof(FsRightsId) } } ); if (R_SUCCEEDED(rc) && out_entries_written) *out_entries_written = out.num_rights_ids_written; return rc; } + +NX_INLINE Result _esInitialize(void) +{ + return smGetService(&g_esSrv, "es"); +} + +static void _esCleanup(void) +{ + serviceClose(&g_esSrv); +} diff --git a/source/new/es.h b/source/new/es.h index d58f1d3..2029a45 100644 --- a/source/new/es.h +++ b/source/new/es.h @@ -5,8 +5,8 @@ #include -Result esInitialize(); -void esExit(); +Result esInitialize(void); +void esExit(void); Result esCountCommonTicket(s32 *out_count); Result esCountPersonalizedTicket(s32 *out_count); diff --git a/source/new/fs_ext.c b/source/new/fs_ext.c index ea3e1f2..5ca6b87 100644 --- a/source/new/fs_ext.c +++ b/source/new/fs_ext.c @@ -4,13 +4,13 @@ #include "fs_ext.h" -// IFileSystemProxy +/* IFileSystemProxy */ Result fsOpenGameCardStorage(FsStorage *out, const FsGameCardHandle *handle, u32 partition) { struct { - u32 handle; + FsGameCardHandle handle; u32 partition; - } in = { handle->value, partition }; + } in = { *handle, partition }; return serviceDispatchIn(fsGetServiceSession(), 30, in, .out_num_objects = 1, @@ -26,22 +26,37 @@ Result fsOpenGameCardDetectionEventNotifier(FsEventNotifier *out) ); } -// IDeviceOperator +/* IDeviceOperator */ Result fsDeviceOperatorUpdatePartitionInfo(FsDeviceOperator *d, const FsGameCardHandle *handle, u32 *out_title_version, u64 *out_title_id) { struct { - u32 handle; - } in = { handle->value }; + FsGameCardHandle handle; + } in = { *handle }; struct { - u32 title_ver; + u32 title_version; u64 title_id; } out; Result rc = serviceDispatchInOut(&d->s, 203, in, out); - if (R_SUCCEEDED(rc) && out_title_version) *out_title_version = out.title_ver; + if (R_SUCCEEDED(rc) && out_title_version) *out_title_version = out.title_version; if (R_SUCCEEDED(rc) && out_title_id) *out_title_id = out.title_id; return rc; } + +Result fsDeviceOperatorGetGameCardDeviceCertificate(FsDeviceOperator *d, const FsGameCardHandle *handle, FsGameCardCertificate *out) +{ + struct { + FsGameCardHandle handle; + u64 buf_size; + } in = { *handle, sizeof(FsGameCardCertificate) }; + + Result rc = serviceDispatchIn(&d->s, 206, in, + .buffer_attrs = { SfBufferAttr_HipcMapAlias | SfBufferAttr_Out }, + .buffers = { { out, sizeof(FsGameCardCertificate) } } + ); + + return rc; +} diff --git a/source/new/fs_ext.h b/source/new/fs_ext.h index daa7c14..0a9d8cb 100644 --- a/source/new/fs_ext.h +++ b/source/new/fs_ext.h @@ -6,11 +6,24 @@ #include #include -// IFileSystemProxy +/// Located at offset 0x7000 in the gamecard image. +typedef struct { + u8 signature[0x100]; ///< RSA-2048 PKCS #1 signature over the rest of the data. + u32 magic; ///< "CERT" + u8 reserved_1[0x4]; + u8 kek_index; + u8 reserved_2[0x7]; + u8 device_id[0x10]; + u8 reserved_3[0x10]; + u8 encrypted_data[0xD0]; +} FsGameCardCertificate; + +/* IFileSystemProxy */ Result fsOpenGameCardStorage(FsStorage *out, const FsGameCardHandle *handle, u32 partition); Result fsOpenGameCardDetectionEventNotifier(FsEventNotifier *out); -// IDeviceOperator +/* IDeviceOperator */ Result fsDeviceOperatorUpdatePartitionInfo(FsDeviceOperator *d, const FsGameCardHandle *handle, u32 *out_title_version, u64 *out_title_id); +Result fsDeviceOperatorGetGameCardDeviceCertificate(FsDeviceOperator *d, const FsGameCardHandle *handle, FsGameCardCertificate *out); #endif /* __FS_EXT_H__ */ diff --git a/source/new/gamecard.c b/source/new/gamecard.c new file mode 100644 index 0000000..973c5a6 --- /dev/null +++ b/source/new/gamecard.c @@ -0,0 +1,763 @@ +#include +#include +#include +#include + +#include "gamecard.h" +#include "service_guard.h" +#include "utils.h" + +#define GAMECARD_ACCESS_WAIT_TIME 3 /* Seconds */ + +#define GAMECARD_UPDATE_TID (u64)0x0100000000000816 + +#define GAMECARD_READ_BUFFER_SIZE 0x800000 /* 8 MiB */ + +#define GAMECARD_ECC_BLOCK_SIZE 0x200 +#define GAMECARD_ECC_DATA_SIZE 0x24 + +typedef struct { + u64 offset; ///< Relative to the start of the gamecard header. + u64 size; ///< Whole partition size. + u8 *header; ///< GameCardHashFileSystemHeader + GameCardHashFileSystemEntry + Name Table. +} GameCardHashFileSystemPartitionInfo; + +static FsDeviceOperator g_deviceOperator = {0}; +static FsEventNotifier g_gameCardEventNotifier = {0}; +static Event g_gameCardKernelEvent = {0}; +static bool g_openDeviceOperator = false, g_openEventNotifier = false, g_loadKernelEvent = false; + +static thrd_t g_gameCardDetectionThread; +static UEvent g_gameCardDetectionThreadExitEvent = {0}; +static mtx_t g_gameCardSharedDataMutex; +static bool g_gameCardDetectionThreadCreated = false, g_gameCardInserted = false, g_gameCardInfoLoaded = false; + +static FsGameCardHandle g_gameCardHandle = {0}; +static FsStorage g_gameCardStorageNormal = {0}, g_gameCardStorageSecure = {0}; +static u8 *g_gameCardReadBuf = NULL; + +static GameCardHeader g_gameCardHeader = {0}; +static u64 g_gameCardStorageNormalAreaSize = 0, g_gameCardStorageSecureAreaSize = 0; + +static u8 *g_gameCardHfsRootHeader = NULL; /* GameCardHashFileSystemHeader + GameCardHashFileSystemEntry + Name Table */ +static GameCardHashFileSystemPartitionInfo *g_gameCardHfsPartitions = NULL; + +static bool gamecardCreateDetectionThread(void); +static void gamecardDestroyDetectionThread(void); +static int gamecardDetectionThreadFunc(void *arg); + +static inline bool gamecardCheckIfInserted(void); + +static void gamecardLoadInfo(void); +static void gamecardFreeInfo(void); + +static bool gamecardGetHandle(void); +static inline void gamecardCloseHandle(void); + +static bool gamecardOpenStorageAreas(void); +static bool _gamecardStorageRead(void *out, u64 out_size, u64 offset, bool lock); +static void gamecardCloseStorageAreas(void); + +static bool gamecardGetSizesFromStorageAreas(void); + +/* Service guard used to generate thread-safe initialize + exit functions */ +NX_GENERATE_SERVICE_GUARD(gamecard); + +bool gamecardCheckReadyStatus(void) +{ + mtx_lock(&g_gameCardSharedDataMutex); + bool status = (g_gameCardInserted && g_gameCardInfoLoaded); + mtx_unlock(&g_gameCardSharedDataMutex); + return status; +} + +bool gamecardStorageRead(void *out, u64 out_size, u64 offset) +{ + return _gamecardStorageRead(out, out_size, offset, true); +} + +bool gamecardGetHeader(GameCardHeader *out) +{ + bool ret = false; + + mtx_lock(&g_gameCardSharedDataMutex); + if (g_gameCardInserted && g_gameCardInfoLoaded && out) + { + memcpy(out, &g_gameCardHeader, sizeof(GameCardHeader)); + ret = true; + } + mtx_unlock(&g_gameCardSharedDataMutex); + + return ret; +} + +bool gamecardGetTotalRomSize(u64 *out) +{ + bool ret = false; + + mtx_lock(&g_gameCardSharedDataMutex); + if (g_gameCardInserted && g_gameCardInfoLoaded && out) + { + *out = (g_gameCardStorageNormalAreaSize + g_gameCardStorageSecureAreaSize); + ret = true; + } + mtx_unlock(&g_gameCardSharedDataMutex); + + return ret; +} + +bool gamecardGetTrimmedRomSize(u64 *out) +{ + bool ret = false; + + mtx_lock(&g_gameCardSharedDataMutex); + if (g_gameCardInserted && g_gameCardInfoLoaded && out) + { + *out = (sizeof(GameCardHeader) + ((u64)g_gameCardHeader.valid_data_end_address * GAMECARD_MEDIA_UNIT_SIZE)); + ret = true; + } + mtx_unlock(&g_gameCardSharedDataMutex); + + return ret; +} + +bool gamecardGetCertificate(FsGameCardCertificate *out) +{ + Result rc = 0; + bool ret = false; + + mtx_lock(&g_gameCardSharedDataMutex); + if (g_gameCardInserted && g_gameCardHandle.value && out) + { + rc = fsDeviceOperatorGetGameCardDeviceCertificate(&g_deviceOperator, &g_gameCardHandle, out); + if (R_FAILED(rc)) LOGFILE("fsDeviceOperatorGetGameCardDeviceCertificate failed! (0x%08X)", rc); + ret = R_SUCCEEDED(rc); + } + mtx_unlock(&g_gameCardSharedDataMutex); + + return ret; +} + +bool gamecardGetBundledFirmwareUpdateVersion(u32 *out) +{ + Result rc = 0; + u64 update_id = 0; + u32 update_version = 0; + bool ret = false; + + mtx_lock(&g_gameCardSharedDataMutex); + if (g_gameCardInserted && g_gameCardHandle.value && out) + { + rc = fsDeviceOperatorUpdatePartitionInfo(&g_deviceOperator, &g_gameCardHandle, &update_version, &update_id); + if (R_FAILED(rc)) LOGFILE("fsDeviceOperatorUpdatePartitionInfo failed! (0x%08X)", rc); + ret = (R_SUCCEEDED(rc) && update_id == GAMECARD_UPDATE_TID); + if (ret) *out = update_version; + } + mtx_unlock(&g_gameCardSharedDataMutex); + + return ret; +} + + + + + + + + + + + + + + + + + + + + + + + + +NX_INLINE Result _gamecardInitialize(void) +{ + Result rc = 0; + + /* Allocate memory for the gamecard read buffer */ + g_gameCardReadBuf = malloc(GAMECARD_READ_BUFFER_SIZE); + if (!g_gameCardReadBuf) + { + LOGFILE("Unable to allocate memory for the gamecard read buffer!"); + rc = MAKERESULT(Module_Libnx, LibnxError_HeapAllocFailed); + goto out; + } + + /* Open device operator */ + rc = fsOpenDeviceOperator(&g_deviceOperator); + if (R_FAILED(rc)) + { + LOGFILE("fsOpenDeviceOperator failed! (0x%08X)", rc); + goto out; + } + + g_openDeviceOperator = true; + + /* Open gamecard detection event notifier */ + rc = fsOpenGameCardDetectionEventNotifier(&g_gameCardEventNotifier); + if (R_FAILED(rc)) + { + LOGFILE("fsOpenGameCardDetectionEventNotifier failed! (0x%08X)", rc); + goto out; + } + + g_openEventNotifier = true; + + /* Retrieve gamecard detection kernel event */ + rc = fsEventNotifierGetEventHandle(&g_gameCardEventNotifier, &g_gameCardKernelEvent, true); + if (R_FAILED(rc)) + { + LOGFILE("fsEventNotifierGetEventHandle failed! (0x%08X)", rc); + goto out; + } + + g_loadKernelEvent = true; + + /* Create usermode exit event */ + ueventCreate(&g_gameCardDetectionThreadExitEvent, false); + + /* Create gamecard detection thread */ + g_gameCardDetectionThreadCreated = gamecardCreateDetectionThread(); + if (!g_gameCardDetectionThreadCreated) + { + LOGFILE("Failed to create gamecard detection thread!"); + rc = MAKERESULT(Module_Libnx, LibnxError_IoError); + } + +out: + return rc; +} + +static void _gamecardCleanup(void) +{ + /* Destroy gamecard detection thread */ + if (g_gameCardDetectionThreadCreated) + { + gamecardDestroyDetectionThread(); + g_gameCardDetectionThreadCreated = false; + } + + /* Close gamecard detection kernel event */ + if (g_loadKernelEvent) + { + eventClose(&g_gameCardKernelEvent); + g_loadKernelEvent = false; + } + + /* Close gamecard detection event notifier */ + if (g_openEventNotifier) + { + fsEventNotifierClose(&g_gameCardEventNotifier); + g_openEventNotifier = false; + } + + /* Close device operator */ + if (g_openDeviceOperator) + { + fsDeviceOperatorClose(&g_deviceOperator); + g_openDeviceOperator = false; + } + + /* Free gamecard read buffer */ + if (g_gameCardReadBuf) + { + free(g_gameCardReadBuf); + g_gameCardReadBuf = NULL; + } +} + +static bool gamecardCreateDetectionThread(void) +{ + if (mtx_init(&g_gameCardSharedDataMutex, mtx_plain) != thrd_success) + { + LOGFILE("Failed to initialize gamecard shared data mutex!"); + return false; + } + + if (thrd_create(&g_gameCardDetectionThread, gamecardDetectionThreadFunc, NULL) != thrd_success) + { + LOGFILE("Failed to create gamecard detection thread!"); + mtx_destroy(&g_gameCardSharedDataMutex); + return false; + } + + return true; +} + +static void gamecardDestroyDetectionThread(void) +{ + /* Signal the exit event to terminate the gamecard detection thread */ + ueventSignal(&g_gameCardDetectionThreadExitEvent); + + /* Wait for the gamecard detection thread to exit */ + thrd_join(g_gameCardDetectionThread, NULL); + + /* Destroy mutex */ + mtx_destroy(&g_gameCardSharedDataMutex); +} + +static int gamecardDetectionThreadFunc(void *arg) +{ + (void)arg; + + Result rc = 0; + int idx = 0; + bool prev_status = false; + + Waiter gamecard_event_waiter = waiterForEvent(&g_gameCardKernelEvent); + Waiter exit_event_waiter = waiterForUEvent(&g_gameCardDetectionThreadExitEvent); + + mtx_lock(&g_gameCardSharedDataMutex); + + /* Retrieve initial gamecard insertion status */ + g_gameCardInserted = prev_status = gamecardCheckIfInserted(); + + /* Load gamecard info right away if a gamecard is inserted and if a handle can be retrieved */ + if (g_gameCardInserted && gamecardGetHandle()) gamecardLoadInfo(); + + mtx_unlock(&g_gameCardSharedDataMutex); + + while(true) + { + /* Wait until an event is triggered */ + rc = waitMulti(&idx, -1, gamecard_event_waiter, exit_event_waiter); + if (R_FAILED(rc)) continue; + + /* Exit event triggered */ + if (idx == 1) break; + + /* Retrieve current gamecard insertion status */ + /* Only proceed if we're dealing with a status change */ + mtx_lock(&g_gameCardSharedDataMutex); + + g_gameCardInserted = gamecardCheckIfInserted(); + + if (!prev_status && g_gameCardInserted) + { + /* Don't access the gamecard immediately to avoid conflicts with HOS / sysmodules */ + SLEEP(GAMECARD_ACCESS_WAIT_TIME); + + /* Load gamecard info if a gamecard is inserted and if a handle can be retrieved */ + if (gamecardGetHandle()) gamecardLoadInfo(); + } else { + /* Free gamecard info and close gamecard handle */ + gamecardFreeInfo(); + gamecardCloseHandle(); + } + + prev_status = g_gameCardInserted; + + mtx_unlock(&g_gameCardSharedDataMutex); + } + + /* Free gamecard info and close gamecard handle */ + mtx_lock(&g_gameCardSharedDataMutex); + gamecardFreeInfo(); + gamecardCloseHandle(); + g_gameCardInserted = false; + mtx_unlock(&g_gameCardSharedDataMutex); + + return 0; +} + +static inline bool gamecardCheckIfInserted(void) +{ + bool inserted = false; + Result rc = fsDeviceOperatorIsGameCardInserted(&g_deviceOperator, &inserted); + if (R_FAILED(rc)) LOGFILE("fsDeviceOperatorIsGameCardInserted failed! (0x%08X)", rc); + return (R_SUCCEEDED(rc) && inserted); +} + +static void gamecardLoadInfo(void) +{ + if (g_gameCardInfoLoaded) return; + + GameCardHashFileSystemHeader *fs_header = NULL; + GameCardHashFileSystemEntry *fs_entry = NULL; + + /* Open gamecard storage areas */ + if (!gamecardOpenStorageAreas()) + { + LOGFILE("Failed to open gamecard storage areas!"); + goto out; + } + + /* Read gamecard header */ + if (!_gamecardStorageRead(&g_gameCardHeader, sizeof(GameCardHeader), 0, false)) + { + LOGFILE("Failed to read gamecard header!"); + goto out; + } + + /* Check magic word from gamecard header */ + if (__builtin_bswap32(g_gameCardHeader.magic) != GAMECARD_HEAD_MAGIC) + { + LOGFILE("Invalid gamecard header magic word! (0x%08X)", __builtin_bswap32(g_gameCardHeader.magic)); + goto out; + } + + if (utilsGetCustomFirmwareType() == UtilsCustomFirmwareType_SXOS) + { + /* Total size for the secure storage area is maxed out under SX OS */ + /* Let's try to calculate it manually */ + u64 capacity = gamecardGetCapacity(&g_gameCardHeader); + if (!capacity) + { + LOGFILE("Invalid gamecard capacity value! (0x%02X)", g_gameCardHeader.rom_size); + goto out; + } + + g_gameCardStorageSecureAreaSize = ((capacity - ((capacity / GAMECARD_ECC_BLOCK_SIZE) * GAMECARD_ECC_DATA_SIZE)) - g_gameCardStorageNormalAreaSize); + } + + /* Allocate memory for the root hash FS header */ + g_gameCardHfsRootHeader = calloc(g_gameCardHeader.partition_fs_header_size, sizeof(u8)); + if (!g_gameCardHfsRootHeader) + { + LOGFILE("Unable to allocate memory for the root hash FS header!"); + goto out; + } + + /* Read root hash FS header */ + if (!_gamecardStorageRead(g_gameCardHfsRootHeader, g_gameCardHeader.partition_fs_header_size, g_gameCardHeader.partition_fs_header_address, false)) + { + LOGFILE("Failed to read root hash FS header from offset 0x%lX!", g_gameCardHeader.partition_fs_header_address); + goto out; + } + + fs_header = (GameCardHashFileSystemHeader*)g_gameCardHfsRootHeader; + + if (__builtin_bswap32(fs_header->magic) != GAMECARD_HFS0_MAGIC) + { + LOGFILE("Invalid magic word in root hash FS header! (0x%08X)", __builtin_bswap32(fs_header->magic)); + goto out; + } + + if (!fs_header->entry_count || !fs_header->name_table_size || \ + (sizeof(GameCardHashFileSystemHeader) + (fs_header->entry_count * sizeof(GameCardHashFileSystemEntry)) + fs_header->name_table_size) > g_gameCardHeader.partition_fs_header_size) + { + LOGFILE("Invalid file count and/or name table size in root hash FS header!"); + goto out; + } + + /* Allocate memory for the hash FS partitions info */ + g_gameCardHfsPartitions = calloc(fs_header->entry_count, sizeof(GameCardHashFileSystemEntry)); + if (!g_gameCardHfsPartitions) + { + LOGFILE("Unable to allocate memory for the hash FS partitions info!"); + goto out; + } + + /* Read hash FS partitions */ + for(u32 i = 0; i < fs_header->entry_count; i++) + { + fs_entry = (GameCardHashFileSystemEntry*)(g_gameCardHfsRootHeader + sizeof(GameCardHashFileSystemHeader) + (i * sizeof(GameCardHashFileSystemEntry))); + + if (!fs_entry->size) + { + LOGFILE("Invalid size for hash FS partition #%u!", i); + goto out; + } + + g_gameCardHfsPartitions[i].offset = (g_gameCardHeader.partition_fs_header_address + g_gameCardHeader.partition_fs_header_size + fs_entry->offset); + g_gameCardHfsPartitions[i].size = fs_entry->size; + + /* Partially read the current hash FS partition header */ + GameCardHashFileSystemHeader partition_header = {0}; + if (!_gamecardStorageRead(&partition_header, sizeof(GameCardHashFileSystemHeader), g_gameCardHfsPartitions[i].offset, false)) + { + LOGFILE("Failed to partially read hash FS partition #%u header from offset 0x%lX!", i, g_gameCardHfsPartitions[i].offset); + goto out; + } + + if (__builtin_bswap32(partition_header.magic) != GAMECARD_HFS0_MAGIC) + { + LOGFILE("Invalid magic word in hash FS partition #%u header! (0x%08X)", i, __builtin_bswap32(partition_header.magic)); + goto out; + } + + if (!partition_header.name_table_size) + { + LOGFILE("Invalid name table size in hash FS partition #%u header!", i); + goto out; + } + + /* Calculate the full header size for the current hash FS partition */ + u64 partition_header_size = (sizeof(GameCardHashFileSystemHeader) + (partition_header.entry_count * sizeof(GameCardHashFileSystemEntry)) + partition_header.name_table_size); + + /* Allocate memory for the hash FS partition header */ + g_gameCardHfsPartitions[i].header = calloc(partition_header_size, sizeof(u8)); + if (!g_gameCardHfsPartitions[i].header) + { + LOGFILE("Unable to allocate memory for the hash FS partition #%u header!", i); + goto out; + } + + /* Finally, read the full hash FS partition header */ + if (!_gamecardStorageRead(g_gameCardHfsPartitions[i].header, partition_header_size, g_gameCardHfsPartitions[i].offset, false)) + { + LOGFILE("Failed to read full hash FS partition #%u header from offset 0x%lX!", i, g_gameCardHfsPartitions[i].offset); + goto out; + } + } + + g_gameCardInfoLoaded = true; + +out: + if (!g_gameCardInfoLoaded) gamecardFreeInfo(); +} + +static void gamecardFreeInfo(void) +{ + memset(&g_gameCardHeader, 0, sizeof(GameCardHeader)); + + if (g_gameCardHfsRootHeader) + { + if (g_gameCardHfsPartitions) + { + GameCardHashFileSystemHeader *fs_header = (GameCardHashFileSystemHeader*)g_gameCardHfsRootHeader; + + for(u32 i = 0; i < fs_header->entry_count; i++) + { + if (g_gameCardHfsPartitions[i].header) free(g_gameCardHfsPartitions[i].header); + } + } + + free(g_gameCardHfsRootHeader); + g_gameCardHfsRootHeader = NULL; + } + + if (g_gameCardHfsPartitions) + { + free(g_gameCardHfsPartitions); + g_gameCardHfsPartitions = NULL; + } + + gamecardCloseStorageAreas(); + + g_gameCardInfoLoaded = false; +} + +static bool gamecardGetHandle(void) +{ + if (!g_gameCardInserted) + { + LOGFILE("Gamecard not inserted!"); + return false; + } + + if (g_gameCardInfoLoaded && g_gameCardHandle.value) return true; + + Result rc1 = 0, rc2 = 0; + FsStorage tmp_storage = {0}; + + /* 10 tries */ + for(u8 i = 0; i < 10; i++) + { + /* First try to open a gamecard storage area using the current gamecard handle */ + rc1 = fsOpenGameCardStorage(&tmp_storage, &g_gameCardHandle, 0); + if (R_SUCCEEDED(rc1)) + { + fsStorageClose(&tmp_storage); + break; + } + + /* If the previous call failed, we may have an invalid handle, so let's close the current one and try to retrieve a new one */ + gamecardCloseHandle(); + rc2 = fsDeviceOperatorGetGameCardHandle(&g_deviceOperator, &g_gameCardHandle); + } + + if (R_FAILED(rc1) || R_FAILED(rc2)) + { + /* Close leftover gamecard handle */ + gamecardCloseHandle(); + + if (R_FAILED(rc1)) LOGFILE("fsOpenGameCardStorage failed! (0x%08X)", rc1); + if (R_FAILED(rc2)) LOGFILE("fsDeviceOperatorGetGameCardHandle failed! (0x%08X)", rc2); + + return false; + } + + return true; +} + +static inline void gamecardCloseHandle(void) +{ + svcCloseHandle(g_gameCardHandle.value); + g_gameCardHandle.value = 0; +} + +static bool gamecardOpenStorageAreas(void) +{ + if (!g_gameCardInserted || !g_gameCardHandle.value) + { + LOGFILE("Invalid parameters!"); + return false; + } + + if (g_gamecardInfoLoaded && serviceIsActive(&(g_gameCardStorageNormal.s)) && serviceIsActive(&(g_gameCardStorageSecure.s))) return true; + + gamecardCloseStorageAreas(); + + Result rc = 0; + bool success = false; + + rc = fsOpenGameCardStorage(&g_gameCardStorageNormal, &g_gameCardHandle, 0); + if (R_FAILED(rc)) + { + LOGFILE("fsOpenGameCardStorage failed! (0x%08X) (normal)", rc); + goto out; + } + + rc = fsOpenGameCardStorage(&g_gameCardStorageSecure, &g_gameCardHandle, 1); + if (R_FAILED(rc)) + { + LOGFILE("fsOpenGameCardStorage failed! (0x%08X) (secure)", rc); + goto out; + } + + if (!gamecardGetSizesFromStorageAreas()) + { + LOGFILE("Failed to retrieve sizes from storage areas!"); + goto out; + } + + success = true; + +out: + if (!success) gamecardCloseStorageAreas(); + + return success; +} + +static bool _gamecardStorageRead(void *out, u64 out_size, u64 offset, bool lock) +{ + if (lock) mtx_lock(&g_gameCardSharedDataMutex); + + bool success = false; + + if (!g_gameCardInserted || !serviceIsActive(&(g_gameCardStorageNormal.s)) || !g_gameCardStorageNormalAreaSize || !serviceIsActive(&(g_gameCardStorageSecure.s)) || \ + !g_gameCardStorageSecureAreaSize || !out || !out_size || offset >= (g_gameCardStorageNormalAreaSize + g_gameCardStorageSecureAreaSize) || \ + (offset + out_size) > (g_gameCardStorageNormalAreaSize + g_gameCardStorageSecureAreaSize)) + { + LOGFILE("Invalid parameters!"); + goto out; + } + + Result rc = 0; + u8 *out_u8 = (u8*)out; + + /* Handle reads between the end of the normal storage area and the start of the secure storage area */ + if (offset < g_gameCardStorageNormalAreaSize && (offset + out_size) > g_gameCardStorageNormalAreaSize) + { + /* Calculate normal storage area size difference */ + u64 diff_size = (g_gameCardStorageNormalAreaSize - offset); + + if (!_gamecardStorageRead(out_u8, diff_size, offset, false)) goto out; + + /* Adjust variables to start reading right from the start of the secure storage area */ + out_u8 += diff_size; + offset = g_gameCardStorageNormalAreaSize; + out_size -= diff_size; + } + + /* Calculate appropiate storage area offset and retrieve the right storage area pointer */ + const char *area = (offset < g_gameCardStorageNormalAreaSize ? "normal" : "secure"); + u64 base_offset = (offset < g_gameCardStorageNormalAreaSize ? offset : (offset - g_gameCardStorageNormalAreaSize)); + FsStorage *storage = (offset < g_gameCardStorageNormalAreaSize ? &g_gameCardStorageNormal : &g_gameCardStorageSecure); + + if (!(base_offset % GAMECARD_MEDIA_UNIT_SIZE) && !(out_size % GAMECARD_MEDIA_UNIT_SIZE)) + { + /* Optimization for reads that are already aligned to GAMECARD_MEDIA_UNIT_SIZE bytes */ + rc = fsStorageRead(storage, base_offset, out_u8, out_size); + if (R_FAILED(rc)) + { + LOGFILE("fsStorageRead failed to read 0x%lX bytes at offset 0x%lX from %s storage area! (0x%08X) (aligned)", out_size, base_offset, area, rc); + goto out; + } + + success = true; + } else { + /* Fix offset and/or size to avoid unaligned reads */ + u64 block_start_offset = (base_offset - (base_offset % GAMECARD_MEDIA_UNIT_SIZE)); + u64 block_end_offset = round_up(base_offset + out_size, GAMECARD_MEDIA_UNIT_SIZE); + u64 block_size = (block_end_offset - block_start_offset); + + u64 chunk_size = (block_size > GAMECARD_READ_BUFFER_SIZE ? GAMECARD_READ_BUFFER_SIZE : block_size); + u64 out_chunk_size = (block_size > GAMECARD_READ_BUFFER_SIZE ? (GAMECARD_READ_BUFFER_SIZE - (base_offset - block_start_offset)) : out_size); + + rc = fsStorageRead(storage, block_start_offset, g_gameCardReadBuf, chunk_size); + if (!R_FAILED(rc)) + { + LOGFILE("fsStorageRead failed to read 0x%lX bytes at offset 0x%lX from %s storage area! (0x%08X) (unaligned)", chunk_size, block_start_offset, area, rc); + goto out; + } + + memcpy(out_u8, g_gameCardReadBuf + (base_offset - block_start_offset), out_chunk_size); + + success = (block_size > GAMECARD_READ_BUFFER_SIZE ? _gamecardStorageRead(out_u8 + out_chunk_size, out_size - out_chunk_size, base_offset + out_chunk_size, false) : true); + } + +out: + if (lock) mtx_unlock(&g_gameCardSharedDataMutex); + + return success; +} + +static void gamecardCloseStorageAreas(void) +{ + if (serviceIsActive(&(g_gameCardStorageNormal.s))) + { + fsStorageClose(&g_gameCardStorageNormal); + memset(&g_gameCardStorageNormal, 0, sizeof(FsStorage)); + } + + g_gameCardStorageNormalAreaSize = 0; + + if (serviceIsActive(&(g_gameCardStorageSecure.s))) + { + fsStorageClose(&g_gameCardStorageSecure); + memset(&g_gameCardStorageSecure, 0, sizeof(FsStorage)); + } + + g_gameCardStorageSecureAreaSize = 0; +} + +static bool gamecardGetSizesFromStorageAreas(void) +{ + if (!g_gameCardInserted || !serviceIsActive(&(g_gameCardStorageNormal.s)) || !serviceIsActive(&(g_gameCardStorageSecure.s))) + { + LOGFILE("Invalid parameters!"); + return false; + } + + Result rc = 0; + + rc = fsStorageGetSize(&g_gameCardStorageNormal, (s64*)&g_gameCardStorageNormalAreaSize); + if (R_FAILED(rc)) + { + LOGFILE("fsStorageGetSize failed! (0x%08X) (normal)", rc); + return false; + } + + rc = fsStorageGetSize(&g_gameCardStorageSecure, (s64*)&g_gameCardStorageSecureAreaSize); + if (R_FAILED(rc)) + { + LOGFILE("fsStorageGetSize failed! (0x%08X) (secure)", rc); + g_gameCardStorageNormalAreaSize = 0; + return false; + } + + return true; +} diff --git a/source/new/gamecard.h b/source/new/gamecard.h new file mode 100644 index 0000000..1e761f5 --- /dev/null +++ b/source/new/gamecard.h @@ -0,0 +1,174 @@ +#pragma once + +#ifndef __GAMECARD_H__ +#define __GAMECARD_H__ + +#include "fs_ext.h" + +#define GAMECARD_HEAD_MAGIC 0x48454144 /* "HEAD" */ +#define GAMECARD_CERT_MAGIC 0x43455254 /* "CERT" */ +#define GAMECARD_HFS0_MAGIC 0x48465330 /* "HFS0" */ + +#define GAMECARD_MEDIA_UNIT_SIZE 0x200 + +typedef enum { + GameCardKekIndex_Version0 = 0, + GameCardKekIndex_VersionForDev = 1 +} GameCardKekIndex; + +typedef struct { + u8 kek_index : 4; ///< GameCardKekIndex. + u8 titlekey_dec_index : 4; +} GameCardKeyFlags; + +typedef enum { + GameCardRomSize_1GB = 0xFA, + GameCardRomSize_2GB = 0xF8, + GameCardRomSize_4GB = 0xF0, + GameCardRomSize_8GB = 0xE0, + GameCardRomSize_16GB = 0xE1, + GameCardRomSize_32GB = 0xE2 +} GameCardRomSize; + +typedef struct { + u8 autoboot : 1; + u8 history_erase : 1; + u8 repair_tool : 1; + u8 different_region_cup_to_terra_device : 1; + u8 different_region_cup_to_global_device : 1; +} GameCardFlags; + +typedef enum { + GameCardSelSec_ForT1 = 0, + GameCardSelSec_ForT2 = 1 +} GameCardSelSec; + +typedef enum { + GameCardFwVersion_Dev = 0, + GameCardFwVersion_Prod = 1, + GameCardFwVersion_Since400NUP = 2 +} GameCardFwVersion; + +typedef enum { + GameCardAccCtrl_25MHz = 0xA10011, + GameCardAccCtrl_50MHz = 0xA10010 +} GameCardAccCtrl; + +typedef enum { + GameCardCompatibilityType_Normal = 0, + GameCardCompatibilityType_Terra = 1 +} GameCardCompatibilityType; + +typedef struct { + u64 fw_version; ///< GameCardFwVersion. + u32 acc_ctrl; ///< GameCardAccCtrl. + u32 wait_1_time_read; ///< Always 0x1388. + u32 wait_2_time_read; ///< Always 0. + u32 wait_1_time_write; ///< Always 0. + u32 wait_2_time_write; ///< Always 0. + u32 fw_mode; + u32 upp_version; + u8 compatibility_type; ///< GameCardCompatibilityType. + u8 reserved_1[0x3]; + u64 upp_hash; + u64 upp_id; ///< Must match GAMECARD_UPDATE_TID. + u8 reserved_2[0x38]; +} GameCardExtendedHeader; + +typedef struct { + u8 signature[0x100]; ///< RSA-2048 PKCS #1 signature over the rest of the header. + u32 magic; ///< "HEAD". + u32 secure_area_start_address; ///< Expressed in GAMECARD_MEDIA_UNIT_SIZE blocks. + u32 backup_area_start_address; ///< Always 0xFFFFFFFF. + GameCardKeyFlags key_flags; + u8 rom_size; ///< GameCardRomSize. + u8 header_version; + GameCardFlags flags; + u64 package_id; + u32 valid_data_end_address; ///< Expressed in GAMECARD_MEDIA_UNIT_SIZE blocks. + u8 reserved[0x4]; + u8 iv[0x10]; + u64 partition_fs_header_address; ///< Root HFS0 header offset. + u64 partition_fs_header_size; ///< Root HFS0 header size. + u8 partition_fs_header_hash[SHA256_HASH_SIZE]; + u8 initial_data_hash[SHA256_HASH_SIZE]; + u32 sel_sec; ///< GameCardSelSec. + u32 sel_t1_key_index; + u32 sel_key_index; + u32 normal_area_end_address; ///< Expressed in GAMECARD_MEDIA_UNIT_SIZE blocks. + GameCardExtendedHeader extended_header; ///< Encrypted using AES-128-CBC with 'xci_header_key', which can't dumped through current methods. +} GameCardHeader; + +typedef struct { + u32 magic; ///< "HFS0". + u32 entry_count; + u32 name_table_size; + u8 reserved[0x4]; +} GameCardHashFileSystemHeader; + +typedef struct { + u64 offset; + u64 size; + u32 name_offset; + u32 hash_target_size; + u64 hash_target_offset; + u8 hash[SHA256_HASH_SIZE]; +} GameCardHashFileSystemEntry; + +/// Initializes data needed to access raw gamecard storage areas. +/// Also spans a background thread to automatically detect gamecard status changes and to cache data from the inserted gamecard. +Result gamecardInitialize(void); + +/// Deinitializes data generated by gamecardInitialize(). +/// This includes destroying the background gamecard detection thread and freeing all cached gamecard data. +void gamecardExit(void); + +/// Used to check if a gamecard has been inserted and if info could be loaded from it. +bool gamecardCheckReadyStatus(void); + +/// Used to read data from the inserted gamecard. +/// All required handles are managed internally. +/// offset + out_size should never exceed the value returned by gamecardGetTotalRomSize(). +bool gamecardStorageRead(void *out, u64 out_size, u64 offset); + +/// Miscellaneous functions. +bool gamecardGetHeader(GameCardHeader *out); +bool gamecardGetTotalRomSize(u64 *out); +bool gamecardGetTrimmedRomSize(u64 *out); +bool gamecardGetCertificate(FsGameCardCertificate *out); +bool gamecardGetBundledFirmwareUpdateVersion(u32 *out); + +static inline u64 gamecardGetCapacity(GameCardHeader *header) +{ + if (!header) return 0; + + u64 capacity = 0; + + switch(header->rom_size) + { + case GameCardRomSize_1GB: + capacity = (u64)0x40000000; + break; + case GameCardRomSize_2GB: + capacity = (u64)0x80000000; + break; + case GameCardRomSize_4GB: + capacity = (u64)0x100000000; + break; + case GameCardRomSize_8GB: + capacity = (u64)0x200000000; + break; + case GameCardRomSize_16GB: + capacity = (u64)0x400000000; + break; + case GameCardRomSize_32GB: + capacity = (u64)0x800000000; + break; + default: + break; + } + + return out; +} + +#endif /* __GAMECARD_H__ */ diff --git a/source/new/nca.c b/source/new/nca.c index ae0a85b..8285159 100644 --- a/source/new/nca.c +++ b/source/new/nca.c @@ -4,17 +4,21 @@ #include "nca.h" #include "keys.h" +#include "rsa.h" #include "utils.h" +static const u8 g_nca0KeyAreaHash[SHA256_HASH_SIZE] = { + 0x9A, 0xBB, 0xD2, 0x11, 0x86, 0x00, 0x21, 0x9D, 0x7A, 0xDC, 0x5B, 0x43, 0x95, 0xF8, 0x4E, 0xFD, + 0xFF, 0x6B, 0x25, 0xEF, 0x9F, 0x96, 0x85, 0x28, 0x18, 0x9E, 0x76, 0xB0, 0x92, 0xF0, 0x6A, 0xCB +}; +static bool ncaCheckIfVersion0KeyAreaIsEncrypted(NcaContext *ctx); +static void ncaUpdateAesCtrIv(u8 *ctr, u64 offset); +static void ncaUpdateAesCtrExIv(u8 *ctr, u32 ctr_val, u64 offset); - - - - -size_t aes128XtsNintendoCrypt(Aes128XtsContext *ctx, void *dst, const void *src, size_t size, u64 sector, bool encrypt) +size_t aes128XtsNintendoCrypt(Aes128XtsContext *ctx, void *dst, const void *src, size_t size, u64 sector, size_t sector_size, bool encrypt) { - if (!ctx || !dst || !src || !size || (size % NCA_AES_XTS_SECTOR_SIZE) != 0) + if (!ctx || !dst || !src || !size || !sector_size || (size % sector_size) != 0) { LOGFILE("Invalid parameters!"); return 0; @@ -26,19 +30,19 @@ size_t aes128XtsNintendoCrypt(Aes128XtsContext *ctx, void *dst, const void *src, u8 *dst_u8 = (u8*)dst; const u8 *src_u8 = (const u8*)src; - for(i = 0; i < size; i += NCA_AES_XTS_SECTOR_SIZE, cur_sector++) + for(i = 0; i < size; i += sector_size, cur_sector++) { /* We have to force a sector reset on each new sector to actually enable Nintendo AES-XTS cipher tweak */ aes128XtsContextResetSector(ctx, cur_sector, true); if (encrypt) { - crypt_res = aes128XtsEncrypt(ctx, dst_u8 + i, src_u8 + i, NCA_AES_XTS_SECTOR_SIZE); + crypt_res = aes128XtsEncrypt(ctx, dst_u8 + i, src_u8 + i, sector_size); } else { - crypt_res = aes128XtsDecrypt(ctx, dst_u8 + i, src_u8 + i, NCA_AES_XTS_SECTOR_SIZE); + crypt_res = aes128XtsDecrypt(ctx, dst_u8 + i, src_u8 + i, sector_size); } - if (crypt_res != NCA_AES_XTS_SECTOR_SIZE) break; + if (crypt_res != sector_size) break; } return i; @@ -47,6 +51,19 @@ size_t aes128XtsNintendoCrypt(Aes128XtsContext *ctx, void *dst, const void *src, + + + + + + + + + + + + + bool ncaDecryptKeyArea(NcaContext *ctx) { if (!ctx) @@ -56,8 +73,15 @@ bool ncaDecryptKeyArea(NcaContext *ctx) } Result rc = 0; - u8 tmp_kek[0x10] = {0}; const u8 *kek_src = NULL; + u8 key_count, tmp_kek[0x10] = {0}; + + /* Check if we're dealing with a NCA0 with a plain text key area */ + if (ctx->format_version == NcaVersion_Nca0 && !ncaCheckIfVersion0KeyAreaIsEncrypted(ctx)) + { + memcpy(ctx->decrypted_keys, ctx->header.encrypted_keys, 0x40); + return true; + } kek_src = keysGetKeyAreaEncryptionKeySource(ctx->header.kaek_index); if (!kek_src) @@ -73,7 +97,9 @@ bool ncaDecryptKeyArea(NcaContext *ctx) return false; } - for(u8 i = 0; i < 4; i++) + key_count = (ctx->format_version == NcaVersion_Nca0 ? 2 : 4); + + for(u8 i = 0; i < key_count; i++) { rc = splCryptoGenerateAesKey(tmp_kek, ctx->header.encrypted_keys[i], ctx->decrypted_keys[i]); if (R_FAILED(rc)) @@ -86,7 +112,7 @@ bool ncaDecryptKeyArea(NcaContext *ctx) return true; } -bool ncaEncryptNcaKeyArea(NcaContext *ctx) +bool ncaEncryptKeyArea(NcaContext *ctx) { if (!ctx) { @@ -94,8 +120,16 @@ bool ncaEncryptNcaKeyArea(NcaContext *ctx) return false; } - Aes128Context key_area_ctx = {0}; + u8 key_count; const u8 *kaek = NULL; + Aes128Context key_area_ctx = {0}; + + /* Check if we're dealing with a NCA0 with a plain text key area */ + if (ctx->format_version == NcaVersion_Nca0 && !ncaCheckIfVersion0KeyAreaIsEncrypted(ctx)) + { + memcpy(ctx->header.encrypted_keys, ctx->decrypted_keys, 0x40); + return true; + } kaek = keysGetKeyAreaEncryptionKey(ctx->key_generation, ctx->header.kaek_index); if (!kaek) @@ -104,20 +138,14 @@ bool ncaEncryptNcaKeyArea(NcaContext *ctx) return false; } + key_count = (ctx->format_version == NcaVersion_Nca0 ? 2 : 4); + aes128ContextCreate(&key_area_ctx, kaek, true); - for(u8 i = 0; i < 4; i++) aes128EncryptBlock(&key_area_ctx, ctx->header.encrypted_keys[i], ctx->decrypted_keys[i]); + for(u8 i = 0; i < key_count; i++) aes128EncryptBlock(&key_area_ctx, ctx->header.encrypted_keys[i], ctx->decrypted_keys[i]); return true; } - - - - - - - - bool ncaDecryptHeader(NcaContext *ctx) { if (!ctx) @@ -129,13 +157,13 @@ bool ncaDecryptHeader(NcaContext *ctx) u32 i, magic = 0; size_t crypt_res = 0; const u8 *header_key = NULL; - Aes128XtsContext hdr_aes_ctx = {0}; + Aes128XtsContext hdr_aes_ctx = {0}, nca0_fs_header_ctx = {0}; header_key = keysGetNcaHeaderKey(); aes128XtsContextCreate(&hdr_aes_ctx, header_key, header_key + 0x10, false); - crypt_res = aes128XtsNintendoCrypt(&hdr_aes_ctx, &(ctx->header), &(ctx->header), NCA_HEADER_LENGTH, 0, false); + crypt_res = aes128XtsNintendoCrypt(&hdr_aes_ctx, &(ctx->header), &(ctx->header), NCA_HEADER_LENGTH, 0, NCA_AES_XTS_SECTOR_SIZE, false); if (crypt_res != NCA_HEADER_LENGTH) { LOGFILE("Invalid output length for decrypted NCA header! (0x%X != 0x%lX)", NCA_HEADER_LENGTH, crypt_res); @@ -147,21 +175,55 @@ bool ncaDecryptHeader(NcaContext *ctx) switch(magic) { case NCA_NCA3_MAGIC: - crypt_res = aes128XtsNintendoCrypt(&hdr_aes_ctx, &(ctx->header), &(ctx->header), NCA_FULL_HEADER_LENGTH, 0, false); + ctx->format_version = NcaVersion_Nca3; + + crypt_res = aes128XtsNintendoCrypt(&hdr_aes_ctx, &(ctx->header), &(ctx->header), NCA_FULL_HEADER_LENGTH, 0, NCA_AES_XTS_SECTOR_SIZE, false); + if (crypt_res != NCA_FULL_HEADER_LENGTH) + { + LOGFILE("Error decrypting full NCA3 header!"); + return false; + } + break; case NCA_NCA2_MAGIC: - for(i = 0; i < 4; i++) + ctx->format_version = NcaVersion_Nca2; + + for(i = 0; i < NCA_SECTION_HEADER_CNT; i++) { - if (ctx->header.fs_entries[i].enable_entry) + if (!ctx->header.fs_entries[i].enable_entry) continue; + + crypt_res = aes128XtsNintendoCrypt(&hdr_aes_ctx, &(ctx->header.fs_headers[i]), &(ctx->header.fs_headers[i]), NCA_FS_HEADER_LENGTH, 0, NCA_AES_XTS_SECTOR_SIZE, false); + if (crypt_res != NCA_FS_HEADER_LENGTH) { - crypt_res = aes128XtsNintendoCrypt(&hdr_aes_ctx, &(ctx->header.fs_headers[i]), &(ctx->header.fs_headers[i]), sizeof(NcaFsHeader), 0, false); - if (crypt_res != sizeof(NcaFsHeader)) break; - } else { - memset(&(ctx->header.fs_headers[i]), 0, sizeof(NcaFsHeader)); + LOGFILE("Error decrypting NCA2 FS section header #%u!", i); + return false; } } + break; case NCA_NCA0_MAGIC: + ctx->format_version = NcaVersion_Nca0; + + /* We first need to decrypt the key area from the NCA0 header in order to access its FS section headers */ + if (!ncaDecryptKeyArea(ctx)) + { + LOGFILE("Error decrypting key area from NCA0 header!"); + return false; + } + + aes128XtsContextCreate(&nca0_fs_header_ctx, ctx->decrypted_keys[0], ctx->decrypted_keys[1], false); + + for(i = 0; i < NCA_SECTION_HEADER_CNT; i++) + { + if (!ctx->header.fs_entries[i].enable_entry) continue; + + + + + + + + } break; default: @@ -169,10 +231,71 @@ bool ncaDecryptHeader(NcaContext *ctx) return false; } + /* Fill additional context info */ + ctx->key_generation = ncaGetKeyGenerationValue(ctx); + ctx->rights_id_available = ncaCheckRightsIdAvailability(ctx); + return true; +} + +bool ncaEncryptHeader(NcaContext *ctx) +{ + if (!ctx) + { + LOGFILE("Invalid NCA context!"); + return false; + } + u32 i; + size_t crypt_res = 0; + const u8 *header_key = NULL; + Aes128XtsContext hdr_aes_ctx = {0}; + header_key = keysGetNcaHeaderKey(); + aes128XtsContextCreate(&hdr_aes_ctx, header_key, header_key + 0x10, true); + + switch(ctx->format_version) + { + case NcaVersion_Nca3: + crypt_res = aes128XtsNintendoCrypt(&hdr_aes_ctx, &(ctx->header), &(ctx->header), NCA_FULL_HEADER_LENGTH, 0, NCA_AES_XTS_SECTOR_SIZE, true); + if (crypt_res != NCA_FULL_HEADER_LENGTH) + { + LOGFILE("Error encrypting full NCA3 header!"); + return false; + } + + break; + case NcaVersion_Nca2: + crypt_res = aes128XtsNintendoCrypt(&hdr_aes_ctx, &(ctx->header), &(ctx->header), NCA_HEADER_LENGTH, 0, NCA_AES_XTS_SECTOR_SIZE, true); + if (crypt_res != NCA_HEADER_LENGTH) + { + LOGFILE("Error encrypting partial NCA2 header!"); + return false; + } + + for(i = 0; i < NCA_SECTION_HEADER_CNT; i++) + { + if (!ctx->header.fs_entries[i].enable_entry) continue; + + crypt_res = aes128XtsNintendoCrypt(&hdr_aes_ctx, &(ctx->header.fs_headers[i]), &(ctx->header.fs_headers[i]), NCA_FS_HEADER_LENGTH, 0, NCA_AES_XTS_SECTOR_SIZE, true); + if (crypt_res != NCA_FS_HEADER_LENGTH) + { + LOGFILE("Error encrypting NCA2 FS section header #%u!", i); + return false; + } + } + + break; + case NcaVersion_Nca0: + /* There's nothing else to do */ + break; + default: + LOGFILE("Invalid NCA format version! (0x%02X)", ctx->format_version); + return false; + } + + return true; } @@ -183,6 +306,24 @@ bool ncaDecryptHeader(NcaContext *ctx) + + + + + + +static bool ncaCheckIfVersion0KeyAreaIsEncrypted(NcaContext *ctx) +{ + if (!ctx || ctx->format_version != NcaVersion_Nca0) return false; + + u8 nca0_key_area_hash[SHA256_HASH_SIZE] = {0}; + sha256CalculateHash(nca0_key_area_hash, ctx->header.encrypted_keys, 0x40); + + if (!memcmp(nca0_key_area_hash, g_nca0KeyAreaHash, SHA256_HASH_SIZE)) return false; + + return true; +} + static void ncaUpdateAesCtrIv(u8 *ctr, u64 offset) { if (!ctr) return; @@ -213,4 +354,4 @@ static void ncaUpdateAesCtrExIv(u8 *ctr, u32 ctr_val, u64 offset) ctr[0x8 - i - 1] = (u8)(ctr_val & 0xFF); ctr_val >>= 8; } -} \ No newline at end of file +} diff --git a/source/new/nca.h b/source/new/nca.h index 0986243..51c90e5 100644 --- a/source/new/nca.h +++ b/source/new/nca.h @@ -22,19 +22,12 @@ #define NCA_IVFC_BLOCK_SIZE(x) (1 << (x)) - - - typedef enum { - NcaVersion_Nca0Beta = 0, - NcaVersion_Nca0 = 1, - NcaVersion_Nca2 = 2, - NcaVersion_Nca3 = 3 + NcaVersion_Nca0 = 0, + NcaVersion_Nca2 = 1, + NcaVersion_Nca3 = 2 } NcaVersion; - - - typedef enum { NcaDistributionType_Download = 0, NcaDistributionType_GameCard = 1 @@ -51,7 +44,7 @@ typedef enum { typedef enum { NcaKeyGenerationOld_100_230 = 0, - NcaKeyGenerationOld_300 = 1 + NcaKeyGenerationOld_300 = 2 } NcaKeyGenerationOld; typedef enum { @@ -60,7 +53,7 @@ typedef enum { NcaKeyAreaEncryptionKeyIndex_System = 2 } NcaKeyAreaEncryptionKeyIndex; -/// 'NcaKeyGeneration_Latest' will always point to the last known key generation value +/// 'NcaKeyGeneration_Latest' will always point to the last known key generation value. typedef enum { NcaKeyGeneration_301_302 = 3, NcaKeyGeneration_400_410 = 4, @@ -75,8 +68,8 @@ typedef enum { } NcaKeyGeneration; typedef struct { - u32 start_block_offset; ///< Expressed in NCA_FS_ENTRY_BLOCK_SIZE blocks - u32 end_block_offset; ///< Expressed in NCA_FS_ENTRY_BLOCK_SIZE blocks + u32 start_block_offset; ///< Expressed in NCA_FS_ENTRY_BLOCK_SIZE blocks. + u32 end_block_offset; ///< Expressed in NCA_FS_ENTRY_BLOCK_SIZE blocks. u8 enable_entry; u8 reserved[0x7]; } NcaFsEntry; @@ -97,8 +90,8 @@ typedef enum { typedef enum { NcaHashType_Auto = 0, NcaHashType_None = 1, - NcaHashType_HierarchicalSha256 = 2, ///< Used by NcaFsType_PartitionFs - NcaHashType_HierarchicalIntegrity = 3 ///< Used by NcaFsType_RomFs + NcaHashType_HierarchicalSha256 = 2, ///< Used by NcaFsType_PartitionFs. + NcaHashType_HierarchicalIntegrity = 3 ///< Used by NcaFsType_RomFs. } NcaHashType; typedef enum { @@ -114,7 +107,7 @@ typedef struct { u64 size; } NcaHierarchicalSha256LayerInfo; -/// Used for NcaFsType_PartitionFs and NCA0 RomFS +/// Used for NcaFsType_PartitionFs and NCA0 RomFS. typedef struct { u8 master_hash[SHA256_HASH_SIZE]; u32 hash_block_size; @@ -130,9 +123,9 @@ typedef struct { u8 reserved[0x4]; } NcaHierarchicalIntegrityLayerInfo; -/// Used for NcaFsType_RomFs +/// Used for NcaFsType_RomFs. typedef struct { - u32 magic; ///< "IVFC" + u32 magic; ///< "IVFC". u32 version; u32 master_hash_size; u32 layer_count; @@ -145,12 +138,12 @@ typedef struct { typedef struct { union { struct { - ///< Used if hash_type == NcaHashType_HierarchicalSha256 (NcaFsType_PartitionFs) + ///< Used if hash_type == NcaHashType_HierarchicalSha256 (NcaFsType_PartitionFs). NcaHierarchicalSha256 hierarchical_sha256; u8 reserved_1[0xB0]; }; struct { - ///< Used if hash_type == NcaHashType_HierarchicalIntegrity (NcaFsType_RomFs) + ///< Used if hash_type == NcaHashType_HierarchicalIntegrity (NcaFsType_RomFs). NcaHierarchicalIntegrity hierarchical_integrity; u8 reserved_2[0x18]; }; @@ -158,13 +151,13 @@ typedef struct { } NcaHashInfo; typedef struct { - u32 magic; ///< "BKTR" + u32 magic; ///< "BKTR". u32 bucket_count; u32 entry_count; u8 reserved[0x4]; } NcaBucketTreeHeader; -/// Only used for NcaEncryptionType_AesCtrEx (PatchRomFs) +/// Only used for NcaEncryptionType_AesCtrEx (PatchRomFs). typedef struct { u64 indirect_offset; u64 indirect_size; @@ -174,16 +167,16 @@ typedef struct { NcaBucketTreeHeader aes_ctr_ex_header; } NcaPatchInfo; -/// Format unknown +/// Format unknown. typedef struct { u8 unknown[0x30]; } NcaSparseInfo; typedef struct { u16 version; - u8 fs_type; ///< NcaFsType - u8 hash_type; ///< NcaHashType - u8 encryption_type; ///< NcaEncryptionType + u8 fs_type; ///< NcaFsType. + u8 hash_type; ///< NcaHashType. + u8 encryption_type; ///< NcaEncryptionType. u8 reserved_1[0x3]; NcaHashInfo hash_info; NcaPatchInfo patch_info; @@ -201,11 +194,11 @@ typedef struct { typedef struct { u8 main_signature[0x100]; ///< RSA-PSS signature over header with fixed key. u8 acid_signature[0x100]; ///< RSA-PSS signature over header with key in NPDM. - u32 magic; ///< "NCA0" / "NCA2" / "NCA3" - u8 distribution_type; ///< NcaDistributionType - u8 content_type; ///< NcaContentType - u8 key_generation_old; ///< NcaKeyGenerationOld - u8 kaek_index; ///< NcaKeyAreaEncryptionKeyIndex + u32 magic; ///< "NCA0" / "NCA2" / "NCA3". + u8 distribution_type; ///< NcaDistributionType. + u8 content_type; ///< NcaContentType. + u8 key_generation_old; ///< NcaKeyGenerationOld. + u8 kaek_index; ///< NcaKeyAreaEncryptionKeyIndex. u64 content_size; u64 program_id; u32 content_index; @@ -218,7 +211,7 @@ typedef struct { u8 sdk_addon_major; }; }; - u8 key_generation; ///< NcaKeyGeneration + u8 key_generation; ///< NcaKeyGeneration. u8 main_signature_key_generation; u8 reserved_1[0xE]; FsRightsId rights_id; ///< Used for titlekey crypto. @@ -252,20 +245,21 @@ typedef struct { } NcaFsContext; typedef struct { - u8 storage_id; ///< NcmStorageId + u8 storage_id; ///< NcmStorageId. NcmContentStorage *ncm_storage; ///< Pointer to a NcmContentStorage instance. Used to read NCA data. - u64 gamecard_base_offset; ///< Used to read NCA data from a gamecard using a FsStorage instance when storage_id == NcmStorageId_GameCard. + u64 gc_secure_area_base_offset; ///< Used to read NCA data from a gamecard using a FsStorage instance when storage_id == NcmStorageId_GameCard. NcmContentId id; ///< Also used to read NCA data. - char id_str[0x21]; u8 hash[0x20]; char hash_str[0x41]; + u8 format_version; ///< NcaVersion. u8 type; ///< NcmContentType. Retrieved from NcmContentInfo. u64 size; ///< Retrieved from NcmContentInfo. u8 key_generation; ///< NcaKeyGenerationOld / NcaKeyGeneration. Retrieved from the decrypted header. u8 id_offset; ///< Retrieved from NcmContentInfo. bool rights_id_available; NcaHeader header; + bool dirty_header; NcaEncryptedKey decrypted_keys[4]; NcaFsContext fs_contexts[4]; } NcaContext; @@ -293,8 +287,9 @@ static inline u8 ncaGetKeyGenerationValue(NcaContext *ctx) static inline void ncaSetDownloadDistributionType(NcaContext *ctx) { - if (!ctx) return; + if (!ctx || ctx->header.distribution_type == NcaDistributionType_Download) return; ctx->header.distribution_type = NcaDistributionType_Download; + ctx->dirty_header = true; } static inline bool ncaCheckRightsIdAvailability(NcaContext *ctx) @@ -319,6 +314,7 @@ static inline void ncaWipeRightsId(NcaContext *ctx) { if (!ctx) return; memset(ctx->header.rights_id, 0, sizeof(FsRightsId)); + ctx->dirty_header = true; } @@ -327,6 +323,9 @@ static inline void ncaWipeRightsId(NcaContext *ctx) bool ncaDecryptKeyArea(NcaContext *nca_ctx); bool ncaEncryptKeyArea(NcaContext *nca_ctx); +bool ncaDecryptHeader(NcaContext *ctx); +bool ncaEncryptHeader(NcaContext *ctx); + diff --git a/source/new/service_guard.h b/source/new/service_guard.h index da077cc..1428a63 100644 --- a/source/new/service_guard.h +++ b/source/new/service_guard.h @@ -32,7 +32,7 @@ NX_INLINE void serviceGuardExit(ServiceGuard* g, void (*cleanupFunc)(void)) #define NX_GENERATE_SERVICE_GUARD_PARAMS(name, _paramdecl, _parampass) \ \ -static ServiceGuard g_##name##Guard; \ +static ServiceGuard g_##name##Guard = {0}; \ NX_INLINE Result _##name##Initialize _paramdecl; \ static void _##name##Cleanup(void); \ \ diff --git a/source/new/tik.c b/source/new/tik.c index de036cb..d0e66c1 100644 --- a/source/new/tik.c +++ b/source/new/tik.c @@ -22,7 +22,7 @@ typedef struct { u8 modulus[0x100]; u32 public_exponent; ///< Must match ETICKET_DEVKEY_PUBLIC_EXPONENT. Stored using big endian byte order. u8 padding[0x14]; - u8 device_id[0x8]; + u64 device_id; u8 ghash[0x10]; } tikEticketDeviceKeyData; @@ -272,7 +272,7 @@ bool tikGetTitleKekDecryptedTitleKeyFromTicket(void *dst, Ticket *tik) return false; } - /* Even though tickets do have a proper key_generation field, we'll default to retrieve it from the rights_id field */ + /* Even though tickets do have a proper key_generation field, we'll just retrieve it from the rights_id field */ /* Old custom tools used to wipe the key_generation field or save it to a different offset */ if (!tikGetTitleKekDecryptedTitleKey(dst, titlekey, tik_common_blk->rights_id.c[0xF])) { diff --git a/source/util.h b/source/util.h index 7d90e59..08d9122 100644 --- a/source/util.h +++ b/source/util.h @@ -3,7 +3,6 @@ #ifndef __UTIL_H__ #define __UTIL_H__ -#include #include #include "nca.h" @@ -24,13 +23,20 @@ +#define LOGFILE(fmt, ...) utilsWriteLogMessage(__func__, fmt, ##__VA_ARGS__) + +#define MEMBER_SIZE(type, member) sizeof(((type*)NULL)->member) + +#define SLEEP(x) svcSleepThread((x) * (u64)1000000000) + +static Mutex g_logfileMutex = 0; + void utilsWriteLogMessage(const char *func_name, const char *fmt, ...) { - if (!func_name || !strlen(func_name) || !fmt || !strlen(fmt)) return; + mutexLock(&g_logfileMutex); va_list args; FILE *logfile = NULL; - char str[FS_MAX_PATH] = {0}; logfile = fopen(APP_BASE_PATH "log.txt", "a+"); if (!logfile) return; @@ -47,11 +53,25 @@ void utilsWriteLogMessage(const char *func_name, const char *fmt, ...) fprintf(logfile, "\r\n"); fclose(logfile); + + mutexUnlock(&g_logfileMutex); } -#define LOGFILE(fmt, ...) utilsWriteLogMessage(__func__, fmt, ##__VA_ARGS__) +typedef enum { + UtilsCustomFirmwareType_Atmosphere = 0, + UtilsCustomFirmwareType_SXOS = 1, + UtilsCustomFirmwareType_ReiNX = 2 +} UtilsCustomFirmwareType; + + +typedef struct { + u16 major : 6; + u16 minor : 6; + u16 micro : 4; + u16 bugfix; +} TitleVersion; + -#define MEMBER_SIZE(type, member) sizeof(((type*)NULL)->member) @@ -128,7 +148,6 @@ void utilsWriteLogMessage(const char *func_name, const char *fmt, ...) #define GAMECARD_TYPE1_PARTITION_CNT 3 // "update" (0), "normal" (1), "secure" (2) #define GAMECARD_TYPE2_PARTITION_CNT 4 // "update" (0), "logo" (1), "normal" (2), "secure" (3) -#define GAMECARD_TYPE(x) ((x) == GAMECARD_TYPE1_PARTITION_CNT ? "Type 0x01" : ((x) == GAMECARD_TYPE2_PARTITION_CNT ? "Type 0x02" : "Unknown")) #define GAMECARD_TYPE1_PART_NAMES(x) ((x) == 0 ? "Update" : ((x) == 1 ? "Normal" : ((x) == 2 ? "Secure" : "Unknown"))) #define GAMECARD_TYPE2_PART_NAMES(x) ((x) == 0 ? "Update" : ((x) == 1 ? "Logo" : ((x) == 2 ? "Normal" : ((x) == 3 ? "Secure" : "Unknown")))) #define GAMECARD_PARTITION_NAME(x, y) ((x) == GAMECARD_TYPE1_PARTITION_CNT ? GAMECARD_TYPE1_PART_NAMES(y) : ((x) == GAMECARD_TYPE2_PARTITION_CNT ? GAMECARD_TYPE2_PART_NAMES(y) : "Unknown"))