From 103641fd05f0764e5f2ba3388ec4c0bc8e9ffc15 Mon Sep 17 00:00:00 2001 From: d0k3 Date: Fri, 16 Dec 2016 03:34:49 +0100 Subject: [PATCH] Vastly improved ticket.db parser --- source/common.h | 2 +- source/fs/filetype.c | 2 +- source/game/cia.c | 155 +------------------------------------- source/game/cia.h | 52 ++----------- source/game/gameutil.c | 51 ++++--------- source/game/ticket.c | 165 +++++++++++++++++++++++++++++++++++++++++ source/game/ticket.h | 49 ++++++++++++ 7 files changed, 237 insertions(+), 239 deletions(-) create mode 100644 source/game/ticket.c create mode 100644 source/game/ticket.h diff --git a/source/common.h b/source/common.h index 15a9866..63d4f8e 100644 --- a/source/common.h +++ b/source/common.h @@ -38,7 +38,7 @@ (((v) % (a)) ? ((v) + (a) - ((v) % (a))) : (v)) // GodMode9 version -#define VERSION "0.8.7" +#define VERSION "0.8.8" // input / output paths #define INPUT_PATHS "0:", "0:/files9", "0:/Decrypt9" diff --git a/source/fs/filetype.c b/source/fs/filetype.c index 609cd25..b5f052f 100644 --- a/source/fs/filetype.c +++ b/source/fs/filetype.c @@ -40,7 +40,7 @@ u32 IdentifyFileType(const char* path) { return GAME_EXEFS; // ExeFS file } else if (memcmp(header, romfs_magic, sizeof(romfs_magic)) == 0) { return GAME_ROMFS; // RomFS file (check could be better) - } else if (strncmp(CIA_TMD_ISSUER, (char*) (header + 0x140), 0x40) == 0) { + } else if (strncmp(TMD_ISSUER, (char*) (header + 0x140), 0x40) == 0) { if (fsize >= CIA_TMD_SIZE_N(getbe16(header + 0x1DE))) return GAME_TMD; // TMD file } diff --git a/source/game/cia.c b/source/game/cia.c index 5bc0ac9..23ff012 100644 --- a/source/game/cia.c +++ b/source/game/cia.c @@ -6,22 +6,6 @@ #include "aes.h" #include "sha.h" -#define TIKDB_NAME_ENC "encTitleKeys.bin" -#define TIKDB_NAME_DEC "decTitleKeys.bin" - -typedef struct { - u32 commonkey_idx; - u8 reserved[4]; - u8 title_id[8]; - u8 titlekey[16]; -} __attribute__((packed)) TitleKeyEntry; - -typedef struct { - u32 n_entries; - u8 reserved[12]; - TitleKeyEntry entries[256]; // this number is only a placeholder -} __attribute__((packed)) TitleKeysInfo; - u32 ValidateCiaHeader(CiaHeader* header) { if ((header->size_header != CIA_HEADER_SIZE) || (header->size_cert != CIA_CERT_SIZE) || @@ -53,68 +37,6 @@ u32 GetCiaInfo(CiaInfo* info, CiaHeader* header) { return 0; } -u32 GetCiaContentInfo(CiaContentInfo* contents, TitleMetaData* tmd) { - TmdContentChunk* content_list = (TmdContentChunk*) (tmd + 1); - u32 content_count = getbe16(tmd->content_count); - u64 next_offset = 0; - for (u32 i = 0; (i < content_count) && (i < CIA_MAX_CONTENTS); i++) { - contents[i].offset = next_offset; - contents[i].size = getbe64(content_list[i].size); - contents[i].id = getbe32(content_list[i].id); - contents[i].index = getbe16(content_list[i].index); - contents[i].encrypted = getbe16(content_list[i].type) & 0x1; - next_offset += contents[i].size; - } - - return 0; -} - -u32 CryptTitleKey(TitleKeyEntry* tik, bool encrypt) { - // From https://github.com/profi200/Project_CTR/blob/master/makerom/pki/prod.h#L19 - static const u8 common_keyy[6][16] __attribute__((aligned(16))) = { - {0xD0, 0x7B, 0x33, 0x7F, 0x9C, 0xA4, 0x38, 0x59, 0x32, 0xA2, 0xE2, 0x57, 0x23, 0x23, 0x2E, 0xB9} , // 0 - eShop Titles - {0x0C, 0x76, 0x72, 0x30, 0xF0, 0x99, 0x8F, 0x1C, 0x46, 0x82, 0x82, 0x02, 0xFA, 0xAC, 0xBE, 0x4C} , // 1 - System Titles - {0xC4, 0x75, 0xCB, 0x3A, 0xB8, 0xC7, 0x88, 0xBB, 0x57, 0x5E, 0x12, 0xA1, 0x09, 0x07, 0xB8, 0xA4} , // 2 - {0xE4, 0x86, 0xEE, 0xE3, 0xD0, 0xC0, 0x9C, 0x90, 0x2F, 0x66, 0x86, 0xD4, 0xC0, 0x6F, 0x64, 0x9F} , // 3 - {0xED, 0x31, 0xBA, 0x9C, 0x04, 0xB0, 0x67, 0x50, 0x6C, 0x44, 0x97, 0xA3, 0x5B, 0x78, 0x04, 0xFC} , // 4 - {0x5E, 0x66, 0x99, 0x8A, 0xB4, 0xE8, 0x93, 0x16, 0x06, 0x85, 0x0F, 0xD7, 0xA1, 0x6D, 0xD7, 0x55} , // 5 - }; - // From https://github.com/profi200/Project_CTR/blob/master/makerom/pki/dev.h#L21 - /* static const u8 common_key_devkit[6][16] __attribute__((aligned(16))) = { // unused atm! - {0x55, 0xA3, 0xF8, 0x72, 0xBD, 0xC8, 0x0C, 0x55, 0x5A, 0x65, 0x43, 0x81, 0x13, 0x9E, 0x15, 0x3B} , // 0 - eShop Titles - {0x44, 0x34, 0xED, 0x14, 0x82, 0x0C, 0xA1, 0xEB, 0xAB, 0x82, 0xC1, 0x6E, 0x7B, 0xEF, 0x0C, 0x25} , // 1 - System Titles - {0xF6, 0x2E, 0x3F, 0x95, 0x8E, 0x28, 0xA2, 0x1F, 0x28, 0x9E, 0xEC, 0x71, 0xA8, 0x66, 0x29, 0xDC} , // 2 - {0x2B, 0x49, 0xCB, 0x6F, 0x99, 0x98, 0xD9, 0xAD, 0x94, 0xF2, 0xED, 0xE7, 0xB5, 0xDA, 0x3E, 0x27} , // 3 - {0x75, 0x05, 0x52, 0xBF, 0xAA, 0x1C, 0x04, 0x07, 0x55, 0xC8, 0xD5, 0x9A, 0x55, 0xF9, 0xAD, 0x1F} , // 4 - {0xAA, 0xDA, 0x4C, 0xA8, 0xF6, 0xE5, 0xA9, 0x77, 0xE0, 0xA0, 0xF9, 0xE4, 0x76, 0xCF, 0x0D, 0x63} , // 5 - };*/ - - u32 mode = (encrypt) ? AES_CNT_TITLEKEY_ENCRYPT_MODE : AES_CNT_TITLEKEY_DECRYPT_MODE; - u8 ctr[16] = { 0 }; - - // setup key 0x3D // ctr - if (tik->commonkey_idx >= 6) return 1; - setup_aeskeyY(0x3D, (void*) common_keyy[tik->commonkey_idx]); - use_aeskey(0x3D); - memcpy(ctr, tik->title_id, 8); - set_ctr(ctr); - - // decrypt / encrypt the titlekey - aes_decrypt(tik->titlekey, tik->titlekey, 1, mode); - return 0; -} - -u32 GetTitleKey(u8* titlekey, Ticket* ticket) { - TitleKeyEntry tik = { 0 }; - memcpy(tik.title_id, ticket->title_id, 8); - memcpy(tik.titlekey, ticket->titlekey, 16); - tik.commonkey_idx = ticket->commonkey_idx; - - if (CryptTitleKey(&tik, false) != 0) return 0; - memcpy(titlekey, tik.titlekey, 16); - return 0; -} - u32 GetTmdCtr(u8* ctr, TmdContentChunk* chunk) { memset(ctr, 0, 16); memcpy(ctr, chunk->index, 2); @@ -149,21 +71,6 @@ u32 FixCiaHeaderForTmd(CiaHeader* header, TitleMetaData* tmd) { return 0; } -Ticket* ParseForTicket(u8* data, u32 size, u8* title_id) { - const u8 magic[] = { CIA_SIG_TYPE }; - for (u32 i = 0; i + sizeof(Ticket) <= size; i++) { - Ticket* ticket = (Ticket*) (data + i); - if ((memcmp(ticket->sig_type, magic, sizeof(magic)) != 0) || - ((strncmp((char*) ticket->issuer, CIA_TICKET_ISSUER, 0x40) != 0) && - (strncmp((char*) ticket->issuer, CIA_TICKET_ISSUER_DEV, 0x40) != 0))) - continue; // magics not found - if (title_id && (memcmp(title_id, ticket->title_id, 8) != 0)) - continue; // title id not matching - return ticket; - } - return NULL; -} - u32 BuildCiaCert(u8* ciacert) { const u8 cert_hash_expected[0x20] = { 0xC7, 0x2E, 0x1C, 0xA5, 0x61, 0xDC, 0x9B, 0xC8, 0x05, 0x58, 0x58, 0x9C, 0x63, 0x08, 0x1C, 0x8A, @@ -195,66 +102,8 @@ u32 BuildCiaCert(u8* ciacert) { return 0; } -u32 BuildFakeTicket(Ticket* ticket, u8* title_id) { - const u8 sig_type[4] = { CIA_SIG_TYPE }; // RSA_2048 SHA256 - const u8 ticket_cnt_index[] = { // whatever this is - 0x00, 0x01, 0x00, 0x14, 0x00, 0x00, 0x00, 0xAC, 0x00, 0x00, 0x00, 0x14, 0x00, 0x01, 0x00, 0x14, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x84, - 0x00, 0x00, 0x00, 0x84, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF - }; - // set ticket all zero for a clean start - memset(ticket, 0x00, sizeof(Ticket)); - // fill ticket values - memcpy(ticket->sig_type, sig_type, 4); - memset(ticket->signature, 0xFF, 0x100); - snprintf((char*) ticket->issuer, 0x40, CIA_TICKET_ISSUER); - memset(ticket->ecdsa, 0xFF, 0x3C); - ticket->version = 0x01; - memset(ticket->titlekey, 0xFF, 16); - memcpy(ticket->title_id, title_id, 8); - ticket->commonkey_idx = 0x00; // eshop - ticket->audit = 0x01; // whatever - memcpy(ticket->content_index, ticket_cnt_index, sizeof(ticket_cnt_index)); - - // search for a titlekey inside encTitleKeys.bin / decTitleKeys.bin - for (u32 enc = 0; enc <= 1; enc++) { - const char* base[] = { INPUT_PATHS }; - bool found = false; - for (u32 i = 0; (i < (sizeof(base)/sizeof(char*))) && !found; i++) { - TitleKeysInfo* tikdb = (TitleKeysInfo*) (TEMP_BUFFER + (TEMP_BUFFER_SIZE/2)); - char path[64]; - FIL file; - UINT btr; - - snprintf(path, 64, "%s/%s", base[i], (enc) ? TIKDB_NAME_ENC : TIKDB_NAME_DEC); - if (f_open(&file, path, FA_READ | FA_OPEN_EXISTING) != FR_OK) - continue; - f_read(&file, tikdb, TEMP_BUFFER_SIZE / 2, &btr); - f_close(&file); - if (tikdb->n_entries > (btr - 16) / 32) - continue; // filesize / titlekey db size mismatch - for (u32 t = 0; t < tikdb->n_entries; t++) { - TitleKeyEntry* tik = tikdb->entries + t; - if (memcmp(title_id, tik->title_id, 8) != 0) - continue; - if (!enc && (CryptTitleKey(tik, true) != 0)) // encrypt the key first - continue; - memcpy(ticket->titlekey, tik->titlekey, 16); - ticket->commonkey_idx = tik->commonkey_idx; - found = true; // found, inserted - break; - } - } - if (found) break; - } - - return 0; -} - u32 BuildFakeTmd(TitleMetaData* tmd, u8* title_id, u32 n_contents) { - const u8 sig_type[4] = { CIA_SIG_TYPE }; + const u8 sig_type[4] = { TMD_SIG_TYPE }; // safety check: number of contents if (n_contents > CIA_MAX_CONTENTS) return 1; // !!! // set TMD all zero for a clean start @@ -262,7 +111,7 @@ u32 BuildFakeTmd(TitleMetaData* tmd, u8* title_id, u32 n_contents) { // file TMD values memcpy(tmd->sig_type, sig_type, 4); memset(tmd->signature, 0xFF, 0x100); - snprintf((char*) tmd->issuer, 0x40, CIA_TMD_ISSUER); + snprintf((char*) tmd->issuer, 0x40, TMD_ISSUER); tmd->version = 0x01; memcpy(tmd->title_id, title_id, 8); tmd->title_type[3] = 0x40; // whatever diff --git a/source/game/cia.h b/source/game/cia.h index e9500aa..7e33ae6 100644 --- a/source/game/cia.h +++ b/source/game/cia.h @@ -1,11 +1,12 @@ #pragma once #include "common.h" +#include "ticket.h" -#define CIA_TICKET_ISSUER "Root-CA00000003-XS0000000c" -#define CIA_TICKET_ISSUER_DEV "Root-CA00000004-XS00000009" -#define CIA_TMD_ISSUER "Root-CA00000003-CP0000000b" -#define CIA_SIG_TYPE 0x00, 0x01, 0x00, 0x04 // RSA_2048 SHA256 +#define TICKET_ISSUER "Root-CA00000003-XS0000000c" +#define TICKET_ISSUER_DEV "Root-CA00000004-XS00000009" +#define TMD_ISSUER "Root-CA00000003-CP0000000b" +#define TMD_SIG_TYPE 0x00, 0x01, 0x00, 0x04 // RSA_2048 SHA256 #define CIA_MAX_CONTENTS (100+1) // theme CIAs contain maximum 100 themes + 1 index content #define CIA_HEADER_SIZE sizeof(CiaHeader) @@ -25,37 +26,6 @@ typedef struct { u8 smdh[0x36C0]; // from ExeFS } __attribute__((packed)) CiaMeta; -// from: https://github.com/profi200/Project_CTR/blob/02159e17ee225de3f7c46ca195ff0f9ba3b3d3e4/ctrtool/tik.h#L15-L39 -typedef struct { - u8 sig_type[4]; - u8 signature[0x100]; - u8 padding1[0x3C]; - u8 issuer[0x40]; - u8 ecdsa[0x3C]; - u8 version; - u8 ca_crl_version; - u8 signer_crl_version; - u8 titlekey[0x10]; - u8 reserved0; - u8 ticket_id[8]; - u8 console_id[4]; - u8 title_id[8]; - u8 sys_access[2]; - u8 ticket_version[2]; - u8 time_mask[4]; - u8 permit_mask[4]; - u8 title_export; - u8 commonkey_idx; - u8 reserved1[0x2A]; - u8 eshop_id[4]; - u8 reserved2; - u8 audit; - u8 content_permissions[0x40]; - u8 reserved3[2]; - u8 timelimits[0x40]; - u8 content_index[0xAC]; -} __attribute__((packed)) Ticket; - // from: https://github.com/profi200/Project_CTR/blob/02159e17ee225de3f7c46ca195ff0f9ba3b3d3e4/ctrtool/tmd.h#L18-L59; typedef struct { u8 id[4]; @@ -141,25 +111,13 @@ typedef struct { // first 0x20 bytes are identical with CIA header u32 max_contents; } __attribute__((packed)) CiaInfo; -typedef struct { - u64 offset; - u64 size; - u32 id; - u32 index; - u8 encrypted; -} __attribute__((packed)) CiaContentInfo; - u32 ValidateCiaHeader(CiaHeader* header); u32 GetCiaInfo(CiaInfo* info, CiaHeader* header); -u32 GetCiaContentInfo(CiaContentInfo* contents, TitleMetaData* tmd); -u32 GetTitleKey(u8* titlekey, Ticket* ticket); u32 GetTmdCtr(u8* ctr, TmdContentChunk* chunk); u32 FixTmdHashes(TitleMetaData* tmd); u32 FixCiaHeaderForTmd(CiaHeader* header, TitleMetaData* tmd); -Ticket* ParseForTicket(u8* data, u32 size, u8* title_id); u32 BuildCiaCert(u8* ciacert); -u32 BuildFakeTicket(Ticket* ticket, u8* title_id); u32 BuildFakeTmd(TitleMetaData* tmd, u8* title_id, u32 n_contents); u32 BuildCiaMeta(CiaMeta* meta, u8* exthdr, u8* smdh); u32 BuildCiaHeader(CiaHeader* header); diff --git a/source/game/gameutil.c b/source/game/gameutil.c index e0abbb7..390f436 100644 --- a/source/game/gameutil.c +++ b/source/game/gameutil.c @@ -180,7 +180,7 @@ u32 LoadNcchMeta(CiaMeta* meta, const char* path, u64 offset) { } u32 LoadTmdFile(TitleMetaData* tmd, const char* path) { - const u8 magic[] = { CIA_SIG_TYPE }; + const u8 magic[] = { TMD_SIG_TYPE }; FIL file; UINT btr; @@ -220,31 +220,6 @@ u32 WriteCiaStub(CiaStub* stub, const char* path) { return 0; } -u32 SearchTicket(Ticket* ticket, const char* path, u8* title_id, bool force_legit) { - FIL db; - UINT btr; - - if (f_open(&db, path, FA_READ | FA_OPEN_EXISTING) != FR_OK) - return 1; - f_lseek(&db, 0); - - u32 fsize = f_size(&db); - u32 offset = 0; - Ticket* tick = NULL; - ShowProgress(0, 0, path); - while (!tick && (f_read(&db, MAIN_BUFFER, MAIN_BUFFER_SIZE, &btr) == FR_OK) && (btr > 0)) { - offset += (btr == MAIN_BUFFER_SIZE) ? MAIN_BUFFER_SIZE - (sizeof(Ticket) - 1) : btr; - if (!ShowProgress(offset, fsize, path)) break; - f_lseek(&db, offset); - tick = ParseForTicket(MAIN_BUFFER, btr, title_id); - if (tick && force_legit && (getbe64(tick->ticket_id) == 0)) - tick = NULL; - } - if (tick) memcpy(ticket, tick, sizeof(Ticket)); - - return (tick) ? 0 : 1; -} - u32 VerifyTmdContent(const char* path, u64 offset, TmdContentChunk* chunk, const u8* titlekey) { u8 hash[32]; u8 ctr[16]; @@ -771,6 +746,9 @@ u32 BuildCiaFromTmdFile(const char* path_tmd, const char* path_cia, bool force_l CiaStub* cia = (CiaStub*) TEMP_BUFFER; CiaMeta* meta = (CiaMeta*) (TEMP_BUFFER + sizeof(CiaStub)); + // Init progress bar + if (!ShowProgress(0, 0, path_tmd)) return 1; + // build the CIA stub memset(cia, 0, sizeof(CiaStub)); if ((BuildCiaHeader(&(cia->header)) != 0) || @@ -790,18 +768,17 @@ u32 BuildCiaFromTmdFile(const char* path_tmd, const char* path_cia, bool force_l if (!content_count) return 1; // get (legit) ticket - const char* path_db = ((*path_tmd == 'B') || (*path_tmd == '4')) ? - "4:/dbs/ticket.db" : "1:/dbs/ticket.db"; // EmuNAND / SysNAND - const u8 titlekey_placeholder[16] = { 0xFF }; Ticket* ticket = &(cia->ticket); - if (force_legit && (SearchTicket(ticket, path_db, title_id, true) != 0)) { - ShowPrompt(false, "ID %016llX\nLegit ticket not found.", getbe64(title_id)); - return 1; - } else if ((memcmp(ticket->titlekey, titlekey_placeholder, 16) == 0) && - (SearchTicket(ticket, path_db, title_id, false) == 0)) { - // ticket placeholder found, try to find ticket - // if ticket found: wipe private data - if (getbe32(ticket->console_id) || getbe32(ticket->eshop_id)) { + bool src_emunand = ((*path_tmd == 'B') || (*path_tmd == '4')); + if (force_legit) { + if (GetTicket(ticket, title_id, true, src_emunand) != 0) { + ShowPrompt(false, "ID %016llX\nLegit ticket not found.", getbe64(title_id)); + return 1; + } + } else { + if ((GetTicket(ticket, title_id, false, src_emunand) == 0) && + (getbe32(ticket->console_id) || getbe32(ticket->eshop_id))) { + // if ticket found: wipe private data memset(ticket->console_id, 0, 4); // zero out console id memset(ticket->eshop_id, 0, 4); // zero out eshop id memset(ticket->ticket_id, 0, 8); // zero out ticket id diff --git a/source/game/ticket.c b/source/game/ticket.c new file mode 100644 index 0000000..c4c3154 --- /dev/null +++ b/source/game/ticket.c @@ -0,0 +1,165 @@ +#include "ticket.h" +#include "aes.h" +#include "ff.h" + +typedef struct { + u32 commonkey_idx; + u8 reserved[4]; + u8 title_id[8]; + u8 titlekey[16]; +} __attribute__((packed)) TitleKeyEntry; + +typedef struct { + u32 n_entries; + u8 reserved[12]; + TitleKeyEntry entries[256]; // this number is only a placeholder +} __attribute__((packed)) TitleKeysInfo; + +u32 ValidateTicket(Ticket* ticket) { + const u8 magic[] = { TICKET_SIG_TYPE }; + if ((memcmp(ticket->sig_type, magic, sizeof(magic)) != 0) || + ((strncmp((char*) ticket->issuer, TICKET_ISSUER, 0x40) != 0) && + (strncmp((char*) ticket->issuer, TICKET_ISSUER_DEV, 0x40) != 0))) + return 1; + return 0; +} + +u32 CryptTitleKey(TitleKeyEntry* tik, bool encrypt) { + // From https://github.com/profi200/Project_CTR/blob/master/makerom/pki/prod.h#L19 + static const u8 common_keyy[6][16] __attribute__((aligned(16))) = { + {0xD0, 0x7B, 0x33, 0x7F, 0x9C, 0xA4, 0x38, 0x59, 0x32, 0xA2, 0xE2, 0x57, 0x23, 0x23, 0x2E, 0xB9} , // 0 - eShop Titles + {0x0C, 0x76, 0x72, 0x30, 0xF0, 0x99, 0x8F, 0x1C, 0x46, 0x82, 0x82, 0x02, 0xFA, 0xAC, 0xBE, 0x4C} , // 1 - System Titles + {0xC4, 0x75, 0xCB, 0x3A, 0xB8, 0xC7, 0x88, 0xBB, 0x57, 0x5E, 0x12, 0xA1, 0x09, 0x07, 0xB8, 0xA4} , // 2 + {0xE4, 0x86, 0xEE, 0xE3, 0xD0, 0xC0, 0x9C, 0x90, 0x2F, 0x66, 0x86, 0xD4, 0xC0, 0x6F, 0x64, 0x9F} , // 3 + {0xED, 0x31, 0xBA, 0x9C, 0x04, 0xB0, 0x67, 0x50, 0x6C, 0x44, 0x97, 0xA3, 0x5B, 0x78, 0x04, 0xFC} , // 4 + {0x5E, 0x66, 0x99, 0x8A, 0xB4, 0xE8, 0x93, 0x16, 0x06, 0x85, 0x0F, 0xD7, 0xA1, 0x6D, 0xD7, 0x55} , // 5 + }; + // From https://github.com/profi200/Project_CTR/blob/master/makerom/pki/dev.h#L21 + /* static const u8 common_key_devkit[6][16] __attribute__((aligned(16))) = { // unused atm! + {0x55, 0xA3, 0xF8, 0x72, 0xBD, 0xC8, 0x0C, 0x55, 0x5A, 0x65, 0x43, 0x81, 0x13, 0x9E, 0x15, 0x3B} , // 0 - eShop Titles + {0x44, 0x34, 0xED, 0x14, 0x82, 0x0C, 0xA1, 0xEB, 0xAB, 0x82, 0xC1, 0x6E, 0x7B, 0xEF, 0x0C, 0x25} , // 1 - System Titles + {0xF6, 0x2E, 0x3F, 0x95, 0x8E, 0x28, 0xA2, 0x1F, 0x28, 0x9E, 0xEC, 0x71, 0xA8, 0x66, 0x29, 0xDC} , // 2 + {0x2B, 0x49, 0xCB, 0x6F, 0x99, 0x98, 0xD9, 0xAD, 0x94, 0xF2, 0xED, 0xE7, 0xB5, 0xDA, 0x3E, 0x27} , // 3 + {0x75, 0x05, 0x52, 0xBF, 0xAA, 0x1C, 0x04, 0x07, 0x55, 0xC8, 0xD5, 0x9A, 0x55, 0xF9, 0xAD, 0x1F} , // 4 + {0xAA, 0xDA, 0x4C, 0xA8, 0xF6, 0xE5, 0xA9, 0x77, 0xE0, 0xA0, 0xF9, 0xE4, 0x76, 0xCF, 0x0D, 0x63} , // 5 + };*/ + + u32 mode = (encrypt) ? AES_CNT_TITLEKEY_ENCRYPT_MODE : AES_CNT_TITLEKEY_DECRYPT_MODE; + u8 ctr[16] = { 0 }; + + // setup key 0x3D // ctr + if (tik->commonkey_idx >= 6) return 1; + setup_aeskeyY(0x3D, (void*) common_keyy[tik->commonkey_idx]); + use_aeskey(0x3D); + memcpy(ctr, tik->title_id, 8); + set_ctr(ctr); + + // decrypt / encrypt the titlekey + aes_decrypt(tik->titlekey, tik->titlekey, 1, mode); + return 0; +} + +u32 GetTitleKey(u8* titlekey, Ticket* ticket) { + TitleKeyEntry tik = { 0 }; + memcpy(tik.title_id, ticket->title_id, 8); + memcpy(tik.titlekey, ticket->titlekey, 16); + tik.commonkey_idx = ticket->commonkey_idx; + + if (CryptTitleKey(&tik, false) != 0) return 0; + memcpy(titlekey, tik.titlekey, 16); + return 0; +} + +u32 GetTicket(Ticket* ticket, u8* title_id, bool force_legit, bool emunand) { + const u32 area_offsets[] = { TICKDB_AREA_OFFSETS }; + const char* path_db = emunand ? "4:/dbs/ticket.db" : "1:/dbs/ticket.db"; // EmuNAND / SysNAND + u8 data[0x400]; + FIL file; + UINT btr; + + // find active partition / offset + if (f_open(&file, path_db, FA_READ | FA_OPEN_EXISTING) != FR_OK) + return 1; + f_lseek(&file, 0); + if (f_read(&file, data, 0x200, &btr) != FR_OK) { + f_close(&file); + return 1; + } + f_lseek(&file, (getle32(data + 0x130)) ? area_offsets[1] : area_offsets[0]); + + // parse file, sector by sector + bool found = false; + for (u32 i = 0; !found && (i < TICKDB_AREA_SIZE); i++) { + Ticket* tick = (Ticket*) (data + 0x18); + if (f_read(&file, data, 0x200, &btr) != FR_OK) break; + if ((getle32(data + 0x10) == 0) || (getle32(data + 0x14) != sizeof(Ticket))) continue; + if (ValidateTicket(tick) != 0) continue; // partial ticket only + if (f_read(&file, data + 0x200, 0x200, &btr) != FR_OK) break; i++; // read the remainder of the ticket + if (memcmp(title_id, tick->title_id, 8) != 0) continue; // title id not matching + if (force_legit && (getbe64(tick->ticket_id) == 0)) continue; // legit check + memcpy(ticket, tick, sizeof(Ticket)); + found = true; + break; + } + + f_close(&file); + return (found) ? 0 : 1; +} + +u32 BuildFakeTicket(Ticket* ticket, u8* title_id) { + const u8 sig_type[4] = { TICKET_SIG_TYPE }; // RSA_2048 SHA256 + const u8 ticket_cnt_index[] = { // whatever this is + 0x00, 0x01, 0x00, 0x14, 0x00, 0x00, 0x00, 0xAC, 0x00, 0x00, 0x00, 0x14, 0x00, 0x01, 0x00, 0x14, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x28, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x84, + 0x00, 0x00, 0x00, 0x84, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF + }; + // set ticket all zero for a clean start + memset(ticket, 0x00, sizeof(Ticket)); + // fill ticket values + memcpy(ticket->sig_type, sig_type, 4); + memset(ticket->signature, 0xFF, 0x100); + snprintf((char*) ticket->issuer, 0x40, TICKET_ISSUER); + memset(ticket->ecdsa, 0xFF, 0x3C); + ticket->version = 0x01; + memset(ticket->titlekey, 0xFF, 16); + memcpy(ticket->title_id, title_id, 8); + ticket->commonkey_idx = 0x00; // eshop + ticket->audit = 0x01; // whatever + memcpy(ticket->content_index, ticket_cnt_index, sizeof(ticket_cnt_index)); + + // search for a titlekey inside encTitleKeys.bin / decTitleKeys.bin + for (u32 enc = 0; enc <= 1; enc++) { + const char* base[] = { INPUT_PATHS }; + bool found = false; + for (u32 i = 0; (i < (sizeof(base)/sizeof(char*))) && !found; i++) { + TitleKeysInfo* tikdb = (TitleKeysInfo*) (TEMP_BUFFER + (TEMP_BUFFER_SIZE/2)); + char path[64]; + FIL file; + UINT btr; + + snprintf(path, 64, "%s/%s", base[i], (enc) ? TIKDB_NAME_ENC : TIKDB_NAME_DEC); + if (f_open(&file, path, FA_READ | FA_OPEN_EXISTING) != FR_OK) + continue; + f_read(&file, tikdb, TEMP_BUFFER_SIZE / 2, &btr); + f_close(&file); + if (tikdb->n_entries > (btr - 16) / 32) + continue; // filesize / titlekey db size mismatch + for (u32 t = 0; t < tikdb->n_entries; t++) { + TitleKeyEntry* tik = tikdb->entries + t; + if (memcmp(title_id, tik->title_id, 8) != 0) + continue; + if (!enc && (CryptTitleKey(tik, true) != 0)) // encrypt the key first + continue; + memcpy(ticket->titlekey, tik->titlekey, 16); + ticket->commonkey_idx = tik->commonkey_idx; + found = true; // found, inserted + break; + } + } + if (found) break; + } + + return 0; +} diff --git a/source/game/ticket.h b/source/game/ticket.h new file mode 100644 index 0000000..876a7bf --- /dev/null +++ b/source/game/ticket.h @@ -0,0 +1,49 @@ +#pragma once + +#include "common.h" + +#define TICKET_ISSUER "Root-CA00000003-XS0000000c" +#define TICKET_ISSUER_DEV "Root-CA00000004-XS00000009" +#define TICKET_SIG_TYPE 0x00, 0x01, 0x00, 0x04 // RSA_2048 SHA256 + +#define TIKDB_NAME_ENC "encTitleKeys.bin" +#define TIKDB_NAME_DEC "decTitleKeys.bin" + +#define TICKDB_AREA_OFFSETS 0x001C0C00, 0x0137F000 +#define TICKDB_AREA_SIZE 0x00200000 // the actual area size is around 0x0010C600 + +// from: https://github.com/profi200/Project_CTR/blob/02159e17ee225de3f7c46ca195ff0f9ba3b3d3e4/ctrtool/tik.h#L15-L39 +typedef struct { + u8 sig_type[4]; + u8 signature[0x100]; + u8 padding1[0x3C]; + u8 issuer[0x40]; + u8 ecdsa[0x3C]; + u8 version; + u8 ca_crl_version; + u8 signer_crl_version; + u8 titlekey[0x10]; + u8 reserved0; + u8 ticket_id[8]; + u8 console_id[4]; + u8 title_id[8]; + u8 sys_access[2]; + u8 ticket_version[2]; + u8 time_mask[4]; + u8 permit_mask[4]; + u8 title_export; + u8 commonkey_idx; + u8 reserved1[0x2A]; + u8 eshop_id[4]; + u8 reserved2; + u8 audit; + u8 content_permissions[0x40]; + u8 reserved3[2]; + u8 timelimits[0x40]; + u8 content_index[0xAC]; +} __attribute__((packed)) Ticket; + +u32 ValidateTicket(Ticket* ticket); +u32 GetTitleKey(u8* titlekey, Ticket* ticket); +u32 GetTicket(Ticket* ticket, u8* title_id, bool force_legit, bool emunand); +u32 BuildFakeTicket(Ticket* ticket, u8* title_id);