From 1f7d73e8ac6867b35fcf29b2dfc6054e56d0d407 Mon Sep 17 00:00:00 2001 From: Pablo Curiel Date: Sun, 26 Nov 2023 22:16:39 +0100 Subject: [PATCH] tik: recreate forged tickets from scratch if detected --- include/core/tik.h | 4 +++- source/core/tik.c | 44 +++++++++++++++++++++++++++++++++----------- 2 files changed, 36 insertions(+), 12 deletions(-) diff --git a/include/core/tik.h b/include/core/tik.h index 02d9e6a..4cb3107 100644 --- a/include/core/tik.h +++ b/include/core/tik.h @@ -33,6 +33,8 @@ extern "C" { #define SIGNED_TIK_MIN_SIZE sizeof(TikSigHmac160) /* Assuming no ESV1/ESV2 records are available. */ #define SIGNED_TIK_MAX_SIZE 0x400 /* Max ticket entry size in the ES ticket system savedata file. */ +#define TIK_FORMAT_VERSION 2 + #define GENERATE_TIK_STRUCT(sigtype, tiksize) \ typedef struct { \ SignatureBlock##sigtype sig_block; \ @@ -72,7 +74,7 @@ typedef enum { typedef struct { char issuer[0x40]; u8 titlekey_block[0x100]; - u8 format_version; + u8 format_version; ///< Always matches TIK_FORMAT_VERSION. u8 titlekey_type; ///< TikTitleKeyType. u16 ticket_version; u8 license_type; ///< TikLicenseType. diff --git a/source/core/tik.c b/source/core/tik.c index ff8b4a1..6bd2dfa 100644 --- a/source/core/tik.c +++ b/source/core/tik.c @@ -386,8 +386,9 @@ static bool tikFixTamperedCommonTicket(Ticket *tik) TikCommonBlock *tik_common_block = NULL; u32 sig_type = 0; - u8 *signature = NULL; - u64 signature_size = 0, hash_area_size = 0; + bool dev_cert = false; + TikSigRsa2048 *tik_data = NULL; + u64 hash_area_size = 0; bool success = false; @@ -397,15 +398,23 @@ static bool tikFixTamperedCommonTicket(Ticket *tik) return false; } - /* Get ticket signature and its properties, as well as the ticket hash area size. */ + /* Get ticket signature type. Also determine if it's a development ticket. */ sig_type = signatureGetTypeFromSignedBlob(tik->data, false); - signature = signatureGetSigFromSignedBlob(tik->data); - signature_size = signatureGetSigSizeByType(sig_type); + dev_cert = (strstr(tik_common_block->issuer, TIK_DEV_CERT_ISSUER) != NULL); + + /* Return right away if we're not dealing with a common ticket or if the signature type doesn't match RSA-2048 + SHA-256. */ + if (tik_common_block->titlekey_type != TikTitleKeyType_Common || sig_type != SignatureType_Rsa2048Sha256) + { + success = true; + goto end; + } + + /* Make sure we're dealing with a tampered ticket by verifying its signature. */ + tik_data = (TikSigRsa2048*)tik->data; + tik_common_block = &(tik_data->tik_common_block); hash_area_size = tikGetSignedTicketBlobHashAreaSize(tik->data); - /* Return right away if we're not dealing with a common ticket, if the signature type doesn't match RSA-2048 + SHA-256, or if the signature is valid. */ - if (tik_common_block->titlekey_type != TikTitleKeyType_Common || sig_type != SignatureType_Rsa2048Sha256 || \ - tikVerifyRsa2048Sha256Signature(tik_common_block, hash_area_size, signature)) + if (tikVerifyRsa2048Sha256Signature(tik_common_block, hash_area_size, tik_data->sig_block.signature)) { success = true; goto end; @@ -416,22 +425,35 @@ static bool tikFixTamperedCommonTicket(Ticket *tik) /* Nintendo didn't start putting the key generation value into the rights ID until HOS 3.0.1. */ /* Old custom tools used to wipe the key generation field and/or save its value into a different offset. */ /* We're gonna take care of that by setting the correct values where they need to go. */ - memset(signature, 0xFF, signature_size); + memset(tik_data->sig_block.signature, 0xFF, sizeof(tik_data->sig_block.signature)); + memset(tik_data->sig_block.padding, 0, sizeof(tik_data->sig_block.padding)); + memset(tik_common_block->issuer, 0, sizeof(tik_common_block->issuer)); + sprintf(tik_common_block->issuer, "Root-CA%08X-%s", dev_cert ? 4 : 3, TIK_COMMON_CERT_NAME); + + memset(tik_common_block->titlekey_block + 0x10, 0, sizeof(tik_common_block->titlekey_block) - 0x10); + + tik_common_block->format_version = TIK_FORMAT_VERSION; tik_common_block->titlekey_type = TikTitleKeyType_Common; + tik_common_block->ticket_version = 0; tik_common_block->license_type = TikLicenseType_Permanent; tik_common_block->key_generation = tik->key_generation; tik_common_block->property_mask = TikPropertyMask_None; + memset(tik_common_block->reserved, 0, sizeof(tik_common_block->reserved)); + tik_common_block->ticket_id = 0; tik_common_block->device_id = 0; tik_common_block->account_id = 0; tik_common_block->sect_total_size = 0; - tik_common_block->sect_hdr_offset = (u32)tik->size; + tik_common_block->sect_hdr_offset = (u32)sizeof(TikSigRsa2048); tik_common_block->sect_hdr_count = 0; tik_common_block->sect_hdr_entry_size = 0; + /* Update ticket size. */ + tik->size = sizeof(TikSigRsa2048); + /* Update return value. */ success = true; @@ -447,8 +469,8 @@ static bool tikVerifyRsa2048Sha256Signature(const TikCommonBlock *tik_common_blo return false; } - const char *cert_name = (strrchr(tik_common_block->issuer, '-') + 1); Certificate cert = {0}; + const char *cert_name = (strrchr(tik_common_block->issuer, '-') + 1); const u8 *modulus = NULL, *public_exponent = NULL; /* Get certificate for the ticket signature issuer. */