Vastly improved ticket.db parser

This commit is contained in:
d0k3 2016-12-16 03:34:49 +01:00
parent 2a904dc284
commit 103641fd05
7 changed files with 237 additions and 239 deletions

View File

@ -38,7 +38,7 @@
(((v) % (a)) ? ((v) + (a) - ((v) % (a))) : (v)) (((v) % (a)) ? ((v) + (a) - ((v) % (a))) : (v))
// GodMode9 version // GodMode9 version
#define VERSION "0.8.7" #define VERSION "0.8.8"
// input / output paths // input / output paths
#define INPUT_PATHS "0:", "0:/files9", "0:/Decrypt9" #define INPUT_PATHS "0:", "0:/files9", "0:/Decrypt9"

View File

@ -40,7 +40,7 @@ u32 IdentifyFileType(const char* path) {
return GAME_EXEFS; // ExeFS file return GAME_EXEFS; // ExeFS file
} else if (memcmp(header, romfs_magic, sizeof(romfs_magic)) == 0) { } else if (memcmp(header, romfs_magic, sizeof(romfs_magic)) == 0) {
return GAME_ROMFS; // RomFS file (check could be better) 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))) if (fsize >= CIA_TMD_SIZE_N(getbe16(header + 0x1DE)))
return GAME_TMD; // TMD file return GAME_TMD; // TMD file
} }

View File

@ -6,22 +6,6 @@
#include "aes.h" #include "aes.h"
#include "sha.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) { u32 ValidateCiaHeader(CiaHeader* header) {
if ((header->size_header != CIA_HEADER_SIZE) || if ((header->size_header != CIA_HEADER_SIZE) ||
(header->size_cert != CIA_CERT_SIZE) || (header->size_cert != CIA_CERT_SIZE) ||
@ -53,68 +37,6 @@ u32 GetCiaInfo(CiaInfo* info, CiaHeader* header) {
return 0; 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) { u32 GetTmdCtr(u8* ctr, TmdContentChunk* chunk) {
memset(ctr, 0, 16); memset(ctr, 0, 16);
memcpy(ctr, chunk->index, 2); memcpy(ctr, chunk->index, 2);
@ -149,21 +71,6 @@ u32 FixCiaHeaderForTmd(CiaHeader* header, TitleMetaData* tmd) {
return 0; 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) { u32 BuildCiaCert(u8* ciacert) {
const u8 cert_hash_expected[0x20] = { const u8 cert_hash_expected[0x20] = {
0xC7, 0x2E, 0x1C, 0xA5, 0x61, 0xDC, 0x9B, 0xC8, 0x05, 0x58, 0x58, 0x9C, 0x63, 0x08, 0x1C, 0x8A, 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; 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) { 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 // safety check: number of contents
if (n_contents > CIA_MAX_CONTENTS) return 1; // !!! if (n_contents > CIA_MAX_CONTENTS) return 1; // !!!
// set TMD all zero for a clean start // 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 // file TMD values
memcpy(tmd->sig_type, sig_type, 4); memcpy(tmd->sig_type, sig_type, 4);
memset(tmd->signature, 0xFF, 0x100); memset(tmd->signature, 0xFF, 0x100);
snprintf((char*) tmd->issuer, 0x40, CIA_TMD_ISSUER); snprintf((char*) tmd->issuer, 0x40, TMD_ISSUER);
tmd->version = 0x01; tmd->version = 0x01;
memcpy(tmd->title_id, title_id, 8); memcpy(tmd->title_id, title_id, 8);
tmd->title_type[3] = 0x40; // whatever tmd->title_type[3] = 0x40; // whatever

View File

@ -1,11 +1,12 @@
#pragma once #pragma once
#include "common.h" #include "common.h"
#include "ticket.h"
#define CIA_TICKET_ISSUER "Root-CA00000003-XS0000000c" #define TICKET_ISSUER "Root-CA00000003-XS0000000c"
#define CIA_TICKET_ISSUER_DEV "Root-CA00000004-XS00000009" #define TICKET_ISSUER_DEV "Root-CA00000004-XS00000009"
#define CIA_TMD_ISSUER "Root-CA00000003-CP0000000b" #define TMD_ISSUER "Root-CA00000003-CP0000000b"
#define CIA_SIG_TYPE 0x00, 0x01, 0x00, 0x04 // RSA_2048 SHA256 #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_MAX_CONTENTS (100+1) // theme CIAs contain maximum 100 themes + 1 index content
#define CIA_HEADER_SIZE sizeof(CiaHeader) #define CIA_HEADER_SIZE sizeof(CiaHeader)
@ -25,37 +26,6 @@ typedef struct {
u8 smdh[0x36C0]; // from ExeFS u8 smdh[0x36C0]; // from ExeFS
} __attribute__((packed)) CiaMeta; } __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; // from: https://github.com/profi200/Project_CTR/blob/02159e17ee225de3f7c46ca195ff0f9ba3b3d3e4/ctrtool/tmd.h#L18-L59;
typedef struct { typedef struct {
u8 id[4]; u8 id[4];
@ -141,25 +111,13 @@ typedef struct { // first 0x20 bytes are identical with CIA header
u32 max_contents; u32 max_contents;
} __attribute__((packed)) CiaInfo; } __attribute__((packed)) CiaInfo;
typedef struct {
u64 offset;
u64 size;
u32 id;
u32 index;
u8 encrypted;
} __attribute__((packed)) CiaContentInfo;
u32 ValidateCiaHeader(CiaHeader* header); u32 ValidateCiaHeader(CiaHeader* header);
u32 GetCiaInfo(CiaInfo* info, 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 GetTmdCtr(u8* ctr, TmdContentChunk* chunk);
u32 FixTmdHashes(TitleMetaData* tmd); u32 FixTmdHashes(TitleMetaData* tmd);
u32 FixCiaHeaderForTmd(CiaHeader* header, TitleMetaData* tmd); u32 FixCiaHeaderForTmd(CiaHeader* header, TitleMetaData* tmd);
Ticket* ParseForTicket(u8* data, u32 size, u8* title_id);
u32 BuildCiaCert(u8* ciacert); u32 BuildCiaCert(u8* ciacert);
u32 BuildFakeTicket(Ticket* ticket, u8* title_id);
u32 BuildFakeTmd(TitleMetaData* tmd, u8* title_id, u32 n_contents); u32 BuildFakeTmd(TitleMetaData* tmd, u8* title_id, u32 n_contents);
u32 BuildCiaMeta(CiaMeta* meta, u8* exthdr, u8* smdh); u32 BuildCiaMeta(CiaMeta* meta, u8* exthdr, u8* smdh);
u32 BuildCiaHeader(CiaHeader* header); u32 BuildCiaHeader(CiaHeader* header);

View File

@ -180,7 +180,7 @@ u32 LoadNcchMeta(CiaMeta* meta, const char* path, u64 offset) {
} }
u32 LoadTmdFile(TitleMetaData* tmd, const char* path) { u32 LoadTmdFile(TitleMetaData* tmd, const char* path) {
const u8 magic[] = { CIA_SIG_TYPE }; const u8 magic[] = { TMD_SIG_TYPE };
FIL file; FIL file;
UINT btr; UINT btr;
@ -220,31 +220,6 @@ u32 WriteCiaStub(CiaStub* stub, const char* path) {
return 0; 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) { u32 VerifyTmdContent(const char* path, u64 offset, TmdContentChunk* chunk, const u8* titlekey) {
u8 hash[32]; u8 hash[32];
u8 ctr[16]; 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; CiaStub* cia = (CiaStub*) TEMP_BUFFER;
CiaMeta* meta = (CiaMeta*) (TEMP_BUFFER + sizeof(CiaStub)); CiaMeta* meta = (CiaMeta*) (TEMP_BUFFER + sizeof(CiaStub));
// Init progress bar
if (!ShowProgress(0, 0, path_tmd)) return 1;
// build the CIA stub // build the CIA stub
memset(cia, 0, sizeof(CiaStub)); memset(cia, 0, sizeof(CiaStub));
if ((BuildCiaHeader(&(cia->header)) != 0) || 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; if (!content_count) return 1;
// get (legit) ticket // 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); Ticket* ticket = &(cia->ticket);
if (force_legit && (SearchTicket(ticket, path_db, title_id, true) != 0)) { bool src_emunand = ((*path_tmd == 'B') || (*path_tmd == '4'));
ShowPrompt(false, "ID %016llX\nLegit ticket not found.", getbe64(title_id)); if (force_legit) {
return 1; if (GetTicket(ticket, title_id, true, src_emunand) != 0) {
} else if ((memcmp(ticket->titlekey, titlekey_placeholder, 16) == 0) && ShowPrompt(false, "ID %016llX\nLegit ticket not found.", getbe64(title_id));
(SearchTicket(ticket, path_db, title_id, false) == 0)) { return 1;
// ticket placeholder found, try to find ticket }
// if ticket found: wipe private data } else {
if (getbe32(ticket->console_id) || getbe32(ticket->eshop_id)) { 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->console_id, 0, 4); // zero out console id
memset(ticket->eshop_id, 0, 4); // zero out eshop id memset(ticket->eshop_id, 0, 4); // zero out eshop id
memset(ticket->ticket_id, 0, 8); // zero out ticket id memset(ticket->ticket_id, 0, 8); // zero out ticket id

165
source/game/ticket.c Normal file
View File

@ -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;
}

49
source/game/ticket.h Normal file
View File

@ -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);