Enable building titlekey databases

This commit is contained in:
d0k3 2017-04-08 14:17:58 +02:00
parent f856211773
commit dd377dbf8e
8 changed files with 167 additions and 44 deletions

View File

@ -53,7 +53,7 @@
// GodMode9 version
#define VERSION "1.0.8"
#define VERSION "1.0.9"
// Maximum payload size (arbitrary value!)
#define SELF_MAX_SIZE (320 * 1024) // 320kB

View File

@ -9,6 +9,7 @@ u32 IdentifyFileType(const char* path) {
const u8 romfs_magic[] = { ROMFS_MAGIC };
const u8 tickdb_magic[] = { TICKDB_MAGIC };
const u8 smdh_magic[] = { SMDH_MAGIC };
if (!path) return 0; // safety
u8 header[0x200] __attribute__((aligned(32))); // minimum required size
void* data = (void*) header;
size_t fsize = FileGetSize(path);
@ -90,9 +91,11 @@ u32 IdentifyFileType(const char* path) {
strncpy(ext_cetk, "cetk", 5);
if (FileGetSize(path_cetk) > 0)
return GAME_NUSCDN; // NUS/CDN type 2
} else if (strncasecmp(fname, TIKDB_NAME_ENC, sizeof(TIKDB_NAME_ENC)) == 0) {
return BIN_TIKDB | FLAG_ENC; // titlekey database / encrypted
} else if (strncasecmp(fname, TIKDB_NAME_DEC, sizeof(TIKDB_NAME_DEC)) == 0) {
return BIN_TIKDB; // titlekey database / decrypted
} else if ((strncasecmp(fname, "seeddb.bin", 11) == 0) ||
(strncasecmp(fname, "encTitlekeys.bin", 17) == 0) ||
(strncasecmp(fname, "decTitlekeys.bin", 17) == 0) ||
(strncasecmp(fname, "aeskeydb.bin", 13) == 0) ||
(strncasecmp(fname, "otp.bin", 8) == 0) ||
(strncasecmp(fname, "secret_sector.bin", 18) == 0) ||

View File

@ -19,9 +19,11 @@
#define SYS_TICKDB (1UL<<14)
#define BIN_NCCHNFO (1UL<<15)
#define BIN_LAUNCH (1UL<<16)
#define BIN_SUPPORT (1UL<<17)
#define BIN_TIKDB (1UL<<17)
#define BIN_SUPPORT (1UL<<18)
#define TYPE_BASE 0x00FFFFFF // 24 bit reserved for base types
#define FLAG_ENC (1UL<<28)
#define FLAG_CTR (1UL<<29)
#define FLAG_NUSCDN (1UL<<30)
#define FLAG_CXI (1UL<<31)
@ -30,8 +32,9 @@
#define FYTPE_VERIFICABLE(tp) (tp&(IMG_NAND|GAME_CIA|GAME_NCSD|GAME_NCCH|GAME_TMD|GAME_BOSS|SYS_FIRM))
#define FYTPE_DECRYPTABLE(tp) (tp&(GAME_CIA|GAME_NCSD|GAME_NCCH|GAME_BOSS|GAME_NUSCDN|SYS_FIRM))
#define FYTPE_ENCRYPTABLE(tp) (tp&(GAME_CIA|GAME_NCSD|GAME_NCCH|GAME_BOSS))
#define FTYPE_BUILDABLE(tp) (tp&(GAME_NCSD|GAME_NCCH|GAME_TMD))
#define FTYPE_BUILDABLE_L(tp) (FTYPE_BUILDABLE(tp) && (tp&(GAME_TMD)))
#define FTYPE_CIABUILD(tp) (tp&(GAME_NCSD|GAME_NCCH|GAME_TMD))
#define FTYPE_CIABUILD_L(tp) (FTYPE_CIABUILD(tp) && (tp&(GAME_TMD)))
#define FTYPE_TIKBUILD(tp) (tp&(GAME_TICKET|SYS_TICKDB|BIN_TIKDB))
#define FTYPE_TITLEINFO(tp) (tp&(GAME_SMDH|GAME_NCCH|GAME_NCSD|GAME_CIA|GAME_TMD|GAME_NDS))
#define FTYPE_TRANSFERABLE(tp) ((u32) (tp&(IMG_FAT|FLAG_CTR)) == (u32) (IMG_FAT|FLAG_CTR))
#define FTYPE_HSINJECTABLE(tp) ((u32) (tp&(GAME_NCCH|FLAG_CXI)) == (u32) (GAME_NCCH|FLAG_CXI))

View File

@ -1662,3 +1662,70 @@ u32 InjectHealthAndSafety(const char* path, const char* destdrv) {
return ret;
}
u32 BuildTitleKeyInfo(const char* path, bool dec, bool dump) {
TitleKeysInfo* tik_info = (TitleKeysInfo*) MAIN_BUFFER;
const char* path_out = (dec) ? OUTPUT_PATH "/" TIKDB_NAME_DEC : OUTPUT_PATH "/" TIKDB_NAME_ENC;
const char* path_in = path;
UINT br;
if (!path_in && !dump) { // no input path given - initialize
memset(tik_info, 0, 16);
if ((fvx_stat(path_out, NULL) == FR_OK) &&
(ShowPrompt(true, "%s\nOutput file already exists.\nUpdate this?", path_out)))
path_in = path_out;
else return 0;
}
u32 filetype = path_in ? IdentifyFileType(path_in) : 0;
if (filetype & GAME_TICKET) {
Ticket* ticket = (Ticket*) TEMP_BUFFER;
if ((fvx_qread(path_in, ticket, 0, TICKET_SIZE, &br) != FR_OK) || (br != TICKET_SIZE) ||
(TIKDB_SIZE(tik_info) + 32 > MAIN_BUFFER_SIZE) || (AddTicketToInfo(tik_info, ticket, dec) != 0)) return 1;
} else if (filetype & SYS_TICKDB) {
const u32 area_offsets[] = { TICKDB_AREA_OFFSETS };
FIL file;
if (fvx_open(&file, path, FA_READ | FA_OPEN_EXISTING) != FR_OK) return 1;
// parse file, sector by sector
for (u32 p = 0; p < sizeof(area_offsets) / sizeof(u32); p++) {
fvx_lseek(&file, area_offsets[p]);
fvx_sync(&file);
for (u32 i = 0; i < TICKDB_AREA_SIZE; i += (TEMP_BUFFER_SIZE - 0x200)) {
u32 read_bytes = min(TEMP_BUFFER_SIZE, TICKDB_AREA_SIZE - i);
u8* data = (u8*) TEMP_BUFFER;
if ((fvx_read(&file, data, read_bytes, &br) != FR_OK) || (br != read_bytes)) {
fvx_close(&file);
return 1;
}
for (; data + TICKET_SIZE < ((u8*) TEMP_BUFFER) + read_bytes; data += 0x200) {
Ticket* ticket = TicketFromTickDbChunk(data, NULL, false);
if (!ticket || (ticket->commonkey_idx >= 2) || !getbe64(ticket->ticket_id)) continue;
if (TIKDB_SIZE(tik_info) + 32 > MAIN_BUFFER_SIZE) return 1;
AddTicketToInfo(tik_info, ticket, dec); // ignore result
}
}
}
fvx_close(&file);
} else if (filetype & BIN_TIKDB) {
TitleKeysInfo* tik_info_merge = (TitleKeysInfo*) TEMP_BUFFER;
if ((fvx_qread(path_in, tik_info_merge, 0, TEMP_BUFFER_SIZE, &br) != FR_OK) ||
(TIKDB_SIZE(tik_info_merge) != br)) return 1;
// merge and rebuild TitleKeyInfo
u32 n_entries = tik_info_merge->n_entries;
TitleKeyEntry* tik = tik_info_merge->entries;
for (u32 i = 0; i < n_entries; i++, tik++) {
if (TIKDB_SIZE(tik_info) + 32 > MAIN_BUFFER_SIZE) return 1;
AddTitleKeyToInfo(tik_info, tik, !(filetype & FLAG_ENC), dec, false); // ignore result
}
}
if (dump) {
u32 dump_size = TIKDB_SIZE(tik_info);
f_unlink(path_out);
if ((dump_size <= 16) || (fvx_qwrite(path_out, tik_info, 0, dump_size, &br) != FR_OK) || (br != dump_size))
return 1;
}
return 0;
}

View File

@ -10,3 +10,4 @@ u32 ShowGameFileTitleInfo(const char* path);
u32 BuildNcchInfoXorpads(const char* destdir, const char* path);
u32 CheckHealthAndSafetyInject(const char* hsdrv);
u32 InjectHealthAndSafety(const char* path, const char* destdrv);
u32 BuildTitleKeyInfo(const char* path, bool dec, bool dump);

View File

@ -4,19 +4,6 @@
#include "sha.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) ||
@ -148,20 +135,31 @@ u32 FindTitleKey(Ticket* ticket, u8* title_id) {
return (found) ? 0 : 1;
}
/*u32 BuildTitleKeyInfo(TitleKeysInfo* tik_info, TicketInfo* tick_info, bool decrypt) {
memset(tik_info, 0, 16);
for (u32 i = 0; i < tick_info->n_entries; i++) {
TicketEntry* tick_entry = tick_info->entries + i;
TitleKeyEntry* tik_entry = tik_info->entries + tik_info->n_entries;
if (!getbe64(tick_entry->ticket_id)) continue;
tik_entry->commonkey_idx = tick_entry->commonkey_idx;
memcpy(tik_entry->title_id, tick_entry->title_id, 8);
memcpy(tik_entry->titlekey, tick_entry->titlekey, 16);
if (decrypt) CryptTitleKey(tik_entry, false, false);
tik_info->n_entries++;
u32 AddTitleKeyToInfo(TitleKeysInfo* tik_info, TitleKeyEntry* tik_entry, bool decrypted_in, bool decrypted_out, bool devkit) {
if (!tik_entry) { // no titlekey entry -> reset database
memset(tik_info, 0, 16);
return 0;
}
// check if entry already in DB
u32 n_entries = tik_info->n_entries;
TitleKeyEntry* tik = tik_info->entries;
for (u32 i = 0; i < n_entries; i++, tik++)
if (memcmp(tik->title_id, tik_entry->title_id, 8) == 0) return 0;
// actually a new titlekey
memcpy(tik, tik_entry, sizeof(TitleKeyEntry));
if ((decrypted_in != decrypted_out) && (CryptTitleKey(tik, !decrypted_out, devkit) != 0)) return 1;
tik_info->n_entries++;
return 0;
}*/
}
u32 AddTicketToInfo(TitleKeysInfo* tik_info, Ticket* ticket, bool decrypt) { // TODO: check for legit tickets?
if (!ticket) return AddTitleKeyToInfo(tik_info, NULL, false, false, false);
TitleKeyEntry tik = { 0 };
memcpy(tik.title_id, ticket->title_id, 8);
memcpy(tik.titlekey, ticket->titlekey, 16);
tik.commonkey_idx = ticket->commonkey_idx;
return AddTitleKeyToInfo(tik_info, &tik, false, decrypt, TICKET_DEVKIT(ticket));
}
u32 BuildFakeTicket(Ticket* ticket, u8* title_id) {
const u8 sig_type[4] = { TICKET_SIG_TYPE }; // RSA_2048 SHA256

View File

@ -13,6 +13,7 @@
#define TIKDB_NAME_ENC "encTitleKeys.bin"
#define TIKDB_NAME_DEC "decTitleKeys.bin"
#define TIKDB_SIZE(tdb) (16 + ((tdb)->n_entries * sizeof(TitleKeyEntry)))
#define TICKDB_PATH(emu) ((emu) ? "4:/dbs/ticket.db" : "1:/dbs/ticket.db") // EmuNAND / SysNAND
#define TICKDB_AREA_OFFSETS 0x0137F000, 0x001C0C00 // second partition is more likely to be in use
@ -56,10 +57,25 @@ typedef struct {
u8 content_index[0xAC];
} __attribute__((packed)) Ticket;
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);
u32 GetTitleKey(u8* titlekey, Ticket* ticket);
Ticket* TicketFromTickDbChunk(u8* chunk, u8* title_id, bool legit_pls);
u32 FindTicket(Ticket* ticket, u8* title_id, bool force_legit, bool emunand);
u32 FindTitleKey(Ticket* ticket, u8* title_id);
u32 AddTitleKeyToInfo(TitleKeysInfo* tik_info, TitleKeyEntry* tik_entry, bool decrypted_in, bool decrypted_out, bool devkit);
u32 AddTicketToInfo(TitleKeysInfo* tik_info, Ticket* ticket, bool decrypt);
u32 BuildFakeTicket(Ticket* ticket, u8* title_id);
u32 BuildTicketCert(u8* tickcert);

View File

@ -12,6 +12,7 @@
#include "nand.h"
#include "virtual.h"
#include "vcart.h"
#include "game.h"
#include "nandcmac.h"
#include "ctrtransfer.h"
#include "ncchinfo.h"
@ -651,8 +652,9 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, DirStruct* cur
bool decryptable = (FYTPE_DECRYPTABLE(filetype));
bool encryptable = (FYTPE_ENCRYPTABLE(filetype));
bool cryptable_inplace = ((encryptable||decryptable) && !in_output_path && (drvtype & DRV_FAT));
bool buildable = (FTYPE_BUILDABLE(filetype));
bool buildable_legit = (FTYPE_BUILDABLE_L(filetype));
bool cia_buildable = (FTYPE_CIABUILD(filetype));
bool cia_buildable_legit = (FTYPE_CIABUILD_L(filetype));
bool tik_buildable = (FTYPE_TIKBUILD(filetype)) && !in_output_path;
bool titleinfo = (FTYPE_TITLEINFO(filetype));
bool transferable = (FTYPE_TRANSFERABLE(filetype) && IS_A9LH && (drvtype & DRV_FAT));
bool hsinjectable = (FTYPE_HSINJECTABLE(filetype));
@ -660,8 +662,8 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, DirStruct* cur
bool ebackupable = (FTYPE_EBACKUP(filetype));
bool xorpadable = (FTYPE_XORPAD(filetype));
bool launchable = ((FTYPE_PAYLOAD(filetype)) && (drvtype & DRV_FAT));
bool special_opt = mountable || verificable || decryptable || encryptable || buildable || buildable_legit ||
titleinfo || hsinjectable || restorable || xorpadable || launchable || ebackupable;
bool special_opt = mountable || verificable || decryptable || encryptable || cia_buildable || cia_buildable_legit ||
tik_buildable || titleinfo || hsinjectable || restorable || xorpadable || launchable || ebackupable;
char pathstr[32+1];
TruncateString(pathstr, curr_entry->path, 32, 8);
@ -697,9 +699,11 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, DirStruct* cur
(filetype & GAME_BOSS ) ? "BOSS file options..." :
(filetype & GAME_NUSCDN)? "Decrypt NUS/CDN file" :
(filetype & GAME_SMDH) ? "Show SMDH title info" :
(filetype & GAME_NDS) ? "Show NDS title info" :
(filetype & GAME_NDS) ? "Show NDS title info" :
(filetype & GAME_TICKET)? "Ticket options..." :
(filetype & SYS_FIRM ) ? "FIRM image options..." :
(filetype & SYS_TICKDB) ? "Mount as ticket.db" :
(filetype & SYS_TICKDB) ? (tik_buildable) ? "Ticket.db options..." : "Mount as ticket.db" :
(filetype & BIN_TIKDB) ? "Titlekey options..." :
(filetype & BIN_NCCHNFO)? "NCCHinfo options..." :
(filetype & BIN_LAUNCH) ? "Launch as arm9 payload" : "???";
optionstr[hexviewer-1] = "Show in Hexeditor";
@ -795,8 +799,10 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, DirStruct* cur
int ebackup = (ebackupable) ? ++n_opt : -1;
int decrypt = (decryptable) ? ++n_opt : -1;
int encrypt = (encryptable) ? ++n_opt : -1;
int build = (buildable) ? ++n_opt : -1;
int build_legit = (buildable_legit) ? ++n_opt : -1;
int cia_build = (cia_buildable) ? ++n_opt : -1;
int cia_build_legit = (cia_buildable_legit) ? ++n_opt : -1;
int tik_build_enc = (tik_buildable) ? ++n_opt : -1;
int tik_build_dec = (tik_buildable) ? ++n_opt : -1;
int verify = (verificable) ? ++n_opt : -1;
int ctrtransfer = (transferable) ? ++n_opt : -1;
int hsinject = (hsinjectable) ? ++n_opt : -1;
@ -809,8 +815,10 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, DirStruct* cur
if (show_info > 0) optionstr[show_info-1] = "Show title info";
if (decrypt > 0) optionstr[decrypt-1] = (cryptable_inplace) ? "Decrypt file (...)" : "Decrypt file (" OUTPUT_PATH ")";
if (encrypt > 0) optionstr[encrypt-1] = (cryptable_inplace) ? "Encrypt file (...)" : "Encrypt file (" OUTPUT_PATH ")";
if (build > 0) optionstr[build-1] = (build_legit < 0) ? "Build CIA from file" : "Build CIA (standard)";
if (build_legit > 0) optionstr[build_legit-1] = "Build CIA (legit)";
if (cia_build > 0) optionstr[cia_build-1] = (cia_build_legit < 0) ? "Build CIA from file" : "Build CIA (standard)";
if (cia_build_legit > 0) optionstr[cia_build_legit-1] = "Build CIA (legit)";
if (tik_build_enc > 0) optionstr[tik_build_enc-1] = "Build " TIKDB_NAME_ENC;
if (tik_build_dec > 0) optionstr[tik_build_dec-1] = "Build " TIKDB_NAME_DEC;
if (verify > 0) optionstr[verify-1] = "Verify file";
if (ctrtransfer > 0) optionstr[ctrtransfer-1] = "Transfer image to CTRNAND";
if (hsinject > 0) optionstr[hsinject-1] = "Inject to H&S";
@ -930,8 +938,8 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, DirStruct* cur
else ShowPrompt(false, "%s\nEncrypted to %s", pathstr, OUTPUT_PATH);
}
return 0;
} else if ((user_select == build) || (user_select == build_legit)) { // -> build CIA
bool force_legit = (user_select == build_legit);
} else if ((user_select == cia_build) || (user_select == cia_build_legit)) { // -> build CIA
bool force_legit = (user_select == cia_build_legit);
if ((n_marked > 1) && ShowPrompt(true, "Try to process all %lu selected files?", n_marked)) {
u32 n_success = 0;
u32 n_other = 0;
@ -998,6 +1006,33 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, DirStruct* cur
(VerifyGameFile(curr_entry->path) == 0) ? "success" : "failed");
}
return 0;
} else if ((user_select == tik_build_enc) || (user_select == tik_build_dec)) { // -> (Re)Build titlekey database
bool dec = (user_select == tik_build_dec);
const char* path_out = (dec) ? OUTPUT_PATH "/" TIKDB_NAME_DEC : OUTPUT_PATH "/" TIKDB_NAME_ENC;
if (BuildTitleKeyInfo(NULL, dec, false) != 0) return 1; // init database
ShowString("Building %s...", (dec) ? TIKDB_NAME_DEC : TIKDB_NAME_ENC);
if (n_marked > 1) {
u32 n_success = 0;
u32 n_other = 0;
for (u32 i = 0; i < current_dir->n_entries; i++) {
const char* path = current_dir->entry[i].path;
if (!current_dir->entry[i].marked)
continue;
if (!FTYPE_TIKBUILD(IdentifyFileType(path))) {
n_other++;
continue;
}
current_dir->entry[i].marked = false;
if (BuildTitleKeyInfo(path, dec, false) == 0) n_success++; // ignore failures for now
}
if (BuildTitleKeyInfo(NULL, dec, true) == 0) {
if (n_other) ShowPrompt(false, "%s\n%lu/%lu files processed\n%lu/%lu files ignored",
path_out, n_success, n_marked, n_other, n_marked);
else ShowPrompt(false, "%s\n%lu/%lu files processed", path_out, n_success, n_marked);
} else ShowPrompt(false, "%s\nBuild database failed.");
} else ShowPrompt(false, "%s\nBuild database %s.", path_out,
(BuildTitleKeyInfo(curr_entry->path, dec, true) == 0) ? "success" : "failed");
return 0;
} else if (user_select == show_info) { // -> Show title info
if (ShowGameFileTitleInfo(curr_entry->path) != 0)
ShowPrompt(false, "Title info: not found");
@ -1019,7 +1054,7 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, DirStruct* cur
(InjectHealthAndSafety(curr_entry->path, destdrv[user_select-1]) == 0) ? "success" : "failed");
}
return 0;
} else if (user_select == ctrtransfer) { // -> adapt CTRNAND image to SysNAND
} else if (user_select == ctrtransfer) { // -> transfer CTRNAND image to SysNAND
char* destdrv[2] = { NULL };
n_opt = 0;
if (DriveType("1:")) {