Forge a ticket if a legit TMD was given

With this it's able to be launched from the DSi Menu without RSA patches
This commit is contained in:
Pk11 2022-01-13 04:59:20 -06:00
parent c4e202b366
commit bbada58a7a
3 changed files with 249 additions and 16 deletions

View File

@ -3,7 +3,9 @@
#include "main.h"
#include "message.h"
#include "maketmd.h"
#include "nand/crypto.h"
#include "nand/nandio.h"
#include "nand/ticket0.h"
#include "rom.h"
#include "storage.h"
#include <dirent.h>
@ -282,6 +284,72 @@ static void _createBannerSav(tDSiHeader* h, char* dataPath)
}
}
static void _createTicket(tDSiHeader *h, char* ticketPath)
{
if (!h) return;
iprintf("Forging ticket...");
swiWaitForVBlank();
if (!ticketPath)
{
iprintf("\x1B[31m"); //red
iprintf("Failed\n");
iprintf("\x1B[47m"); //white
}
else
{
const u32 encryptedSize = sizeof(ticket_v0_t) + 0x20;
u8 *buffer = (u8*)memalign(4, encryptedSize); //memalign might be needed for encryption, but not sure
memset(buffer, 0, encryptedSize);
ticket_v0_t *ticket = (ticket_v0_t*)buffer;
ticket->sig_type[0] = 0x00;
ticket->sig_type[1] = 0x01;
ticket->sig_type[2] = 0x00;
ticket->sig_type[3] = 0x01;
strcpy(ticket->issuer, "Root-CA00000001-XS00000006");
PUT_UINT32_BE(h->tid_high, ticket->title_id, 0);
PUT_UINT32_BE(h->tid_low, ticket->title_id, 4);
memset(ticket->content_access_permissions, 0xFF, 0x20);
// Encrypt
if (dsi_es_block_crypt(buffer, encryptedSize, ENCRYPT) != 0)
{
iprintf("\x1B[31m"); //red
iprintf("Failed\n");
iprintf("\x1B[47m"); //white
free(buffer);
return;
}
FILE *file = fopen(ticketPath, "wb");
if (!file)
{
iprintf("\x1B[31m"); //red
iprintf("Failed\n");
iprintf("\x1B[47m"); //white
free(buffer);
return;
}
if (fwrite(buffer, 1, encryptedSize, file) != encryptedSize)
{
iprintf("\x1B[31m"); //red
iprintf("Failed\n");
iprintf("\x1B[47m"); //white
}
else
{
iprintf("\x1B[42m"); //green
iprintf("Done\n");
iprintf("\x1B[47m"); //white
}
free(buffer);
fclose(file);
}
}
bool install(char* fpath, bool systemTitle)
{
bool result = false;
@ -360,12 +428,21 @@ bool install(char* fpath, bool systemTitle)
clearScreen(&bottomScreen);
iprintf("Installing %s\n\n", fpath); swiWaitForVBlank();
//check for legit TMD, if found we'll generate a ticket which increases the size
int extensionPos = strrchr(fpath, '.') - fpath;
char tmdPath[PATH_MAX];
strcpy(tmdPath, fpath);
strcpy(tmdPath + extensionPos, ".tmd");
//DSi TMDs are 520, TMDs from NUS are 2,312. If 2,312 we can simply trim it to 520
bool tmdFound = (getFileSizePath(tmdPath) >= 520) || (getFileSizePath(tmdPath) == 2312);
//get install size
iprintf("Install Size: ");
swiWaitForVBlank();
unsigned long long fileSize = getRomSize(fpath);
unsigned long long installSize = fileSize + _getSaveDataSize(h);
if (tmdFound) installSize += 708;
printBytes(installSize);
iprintf("\n");
@ -404,9 +481,7 @@ bool install(char* fpath, bool systemTitle)
}
}
//check for saves or legit tmd
int extensionPos = strrchr(fpath, '.') - fpath;
//check for saves
char pubPath[PATH_MAX];
strcpy(pubPath, fpath);
strcpy(pubPath + extensionPos, ".pub");
@ -443,11 +518,6 @@ bool install(char* fpath, bool systemTitle)
goto error;
}
char tmdPath[PATH_MAX];
strcpy(tmdPath, fpath);
strcpy(tmdPath + extensionPos, ".tmd");
bool tmdFound = getFileSizePath(tmdPath) == 520;
if (_iqueHack(h))
fixHeader = true;
@ -607,7 +677,7 @@ bool install(char* fpath, bool systemTitle)
sprintf(newTmdPath, "%s/title.tmd", contentPath);
if (tmdFound)
{
if (copyFile(tmdPath, newTmdPath) != 0)
if (copyFilePart(tmdPath, 0, 520, newTmdPath) != 0)
goto error;
}
else
@ -659,6 +729,23 @@ bool install(char* fpath, bool systemTitle)
}
}
//ticket folder /ticket/XXXXXXXX
if (tmdFound)
{
//ensure folders exist
char ticketPath[32];
siprintf(ticketPath, "%s:/ticket", sdnandMode ? "sd" : "nand");
mkdir(ticketPath, 0777);
siprintf(ticketPath, "%s/%08lx", ticketPath, h->tid_high);
mkdir(ticketPath, 0777);
//actual tik path
siprintf(ticketPath, "%s/%08lx.tik", ticketPath, h->tid_low);
if (access(ticketPath, F_OK) != 0 || choicePrint("Ticket already exists.\nKeep it?") == NO)
_createTicket(h, ticketPath);
}
//end
result = true;
iprintf("\x1B[42m"); //green

View File

@ -11,7 +11,7 @@
static dsi_context nand_ctx ;
static dsi_context boot2_ctx ;
static dsi_context es_ctx ;
static dsi_es_context es_ctx ;
static uint8_t nand_ctr_iv[16];
static uint8_t boot2_ctr[16];
@ -62,13 +62,17 @@ void dsi_crypt_init(const uint8_t *console_id_be, const uint8_t *emmc_cid, int i
GET_UINT32_BE(console_id[1], console_id_be, 0);
uint8_t key[16];
generate_key(key, console_id, is3DS ? NAND_3DS : NAND);
dsi_set_key(&nand_ctx, key) ;
generate_key(key, console_id, is3DS ? NAND_3DS : NAND);
dsi_set_key(&nand_ctx, key) ;
generate_key(key, console_id, ES);
dsi_set_key(&es_ctx, key) ;
u32 normalkey[4];
u32 tadsrl_keyX[4] = {0x4E00004A, 0x4A00004E, 0, 0};
tadsrl_keyX[2] = console_id[1] ^ 0xC80C4B72;
tadsrl_keyX[3] = console_id[0];
F_XY((u8 *)normalkey, (u8 *)tadsrl_keyX, DSi_ES_KEY_Y);
dsi_es_init(&es_ctx, (u8*)normalkey);
dsi_set_key(&boot2_ctx, DSi_BOOT2_KEY) ;
dsi_set_key(&boot2_ctx, DSi_BOOT2_KEY) ;
swiSHA1Calc(nand_ctr_iv, emmc_cid, 16);
@ -99,7 +103,17 @@ void dsi_nand_crypt(uint8_t* out, const uint8_t* in, uint32_t offset, unsigned c
u128_add32(ctr, 1);
}
}
int dsi_es_block_crypt(uint8_t *buf, unsigned buf_len, crypt_mode_t mode)
{
if(mode == DECRYPT)
return dsi_es_decrypt(&es_ctx, buf, buf + buf_len - 0x20, buf_len - 0x20);
else
dsi_es_encrypt(&es_ctx, buf, buf + buf_len - 0x20, buf_len - 0x20);
return 0;
}
void dsi_boot2_crypt_set_ctr(uint32_t size_r)
{
for (int i=0;i<4;i++)

132
arm9/src/nand/ticket0.h Normal file
View File

@ -0,0 +1,132 @@
#pragma once
#include <assert.h>
#include <stdint.h>
#ifdef _MSC_VER
#pragma pack(push, 1)
#define PACKED
#elif ! defined PACKED
#define PACKED __attribute__ ((__packed__))
#endif
#define RSA_2048_LEN (2048/8)
// most, if not all, are big endian
// http://problemkaputt.de/gbatek.htm#dsisdmmcdsiwareticketsandtitlemetadata
// http://dsibrew.org/wiki/Ticket
// http://wiibrew.org/wiki/Ticket
typedef struct {
uint8_t sig_type[4];
uint8_t sig[RSA_2048_LEN];
uint8_t padding0[0x3c];
char issuer[0x40];
uint8_t ecdh[0x3c];
uint8_t padding1[3];
uint8_t encrypted_title_key[0x10];
uint8_t unknown0;
uint8_t ticket_id[8];
uint8_t console_id[4];
uint8_t title_id[8];
uint8_t unknown1[2];
uint8_t version[2];
uint8_t permitted_titles_mask[4];
uint8_t permit_mask[4];
uint8_t title_export_allowed;
uint8_t common_key_index;
uint8_t unknown[0x30];
uint8_t content_access_permissions[0x40];
uint8_t padding2[2];
uint8_t time_limits[2 * 8 * sizeof(uint32_t)];
} PACKED ticket_v0_t;
static_assert(sizeof(ticket_v0_t) == 0x2a4, "invalid sizeof(ticket_v0_t)");
// http://dsibrew.org/wiki/Tmd
// http://wiibrew.org/wiki/Title_metadata
typedef struct {
uint8_t sig_type[4];
uint8_t sig[RSA_2048_LEN];
uint8_t padding0[0x3c];
char issuer[0x40];
uint8_t version;
uint8_t ca_crl_version;
uint8_t signer_crl_version;
uint8_t padding1;
uint8_t system_version[8];
uint8_t title_id[8];
uint8_t title_type[4];
uint8_t group_id[2];
uint8_t public_save_size[4];
uint8_t private_save_size[4];
uint8_t padding2[8];
uint8_t parent_control[0x10];
uint8_t padding3[0x1e];
uint8_t access_rights[4];
uint8_t title_version[2];
uint8_t num_content[2];
uint8_t boot_index[2];
uint8_t padding4[2];
} PACKED tmd_header_v0_t;
static_assert(sizeof(tmd_header_v0_t) == 0x1e4, "invalid sizeof(tmd_header_v0_t)");
typedef struct {
uint8_t content_id[4];
uint8_t index[2];
uint8_t type[2];
uint8_t size[8];
uint8_t sha1[20];
} PACKED tmd_content_v0_t;
static_assert(sizeof(tmd_content_v0_t) == 0x24, "invalid sizeof(tmd_contend_v0_t)");
// used in ticket encryption
// http://problemkaputt.de/gbatek.htm#dsiesblockencryption
#define AES_CCM_MAC_LEN 0x10
#define AES_CCM_NONCE_LEN 0x0c
typedef struct {
uint8_t ccm_mac[AES_CCM_MAC_LEN];
union {
struct {
uint8_t fixed_3a;
uint8_t nonce[AES_CCM_NONCE_LEN];
uint8_t len24be[3];
};
struct {
uint8_t padding[AES_CCM_NONCE_LEN];
// defined for convenience, it's still big endian with only 24 effective bits
// read it as 32 bit big endian and discard the highest 8 bits
uint8_t len32be[4];
};
uint8_t encrypted[0x10];
};
} PACKED es_block_footer_t;
static_assert(sizeof(es_block_footer_t) == 0x20, "invalid sizeof(es_block_footer_t)");
// used in cert.sys
// http://problemkaputt.de/gbatek.htm#dsisdmmcfirmwaredevkpandcertsyscertificatefiles
// "DSi SD/MMC Firmware dev.kp and cert.sys Certificate Files"
typedef struct {
uint32_t signature_type;
uint8_t signature[RSA_2048_LEN];
uint8_t padding0[0x3c];
char signature_name[0x40];
uint32_t key_type;
char key_name[0x40];
uint32_t key_flags;
uint8_t rsa_key[RSA_2048_LEN];
uint8_t rsa_exp[4];
uint8_t padding1[0x34];
} PACKED cert_t;
static_assert(sizeof(cert_t) == 0x300, "invalid sizeof(cert_t)");
#ifdef _MSC_VER
#pragma pack(pop)
#endif
#undef PACKED