Added CIA Checker Tool

This commit is contained in:
d0k3 2018-08-13 00:08:56 +02:00
parent 9467bd6050
commit 3446a43127
10 changed files with 146 additions and 28 deletions

View File

@ -52,6 +52,7 @@
#define FTYPE_TIKBUILD(tp) (tp&(GAME_TICKET|SYS_TICKDB|BIN_TIKDB))
#define FTYPE_KEYBUILD(tp) (tp&(BIN_KEYDB|BIN_LEGKEY))
#define FTYPE_TITLEINFO(tp) (tp&(GAME_SMDH|GAME_NCCH|GAME_NCSD|GAME_CIA|GAME_TMD|GAME_NDS|GAME_GBA|GAME_TAD|GAME_3DSX))
#define FTYPE_CIACHECK(tp) (tp&GAME_CIA)
#define FTYPE_RENAMABLE(tp) (tp&(GAME_NCCH|GAME_NCSD|GAME_CIA|GAME_NDS|GAME_GBA))
#define FTYPE_TRIMABLE(tp) (tp&(IMG_NAND|GAME_NCCH|GAME_NCSD|GAME_NDS|SYS_FIRM))
#define FTYPE_TRANSFERABLE(tp) ((u64) (tp&(IMG_FAT|FLAG_CTR)) == (u64) (IMG_FAT|FLAG_CTR))

21
arm9/source/game/cert.c Normal file
View File

@ -0,0 +1,21 @@
#include "cert.h"
#include "ff.h"
u32 LoadCertFromCertDb(u64 offset, Certificate* cert, u8* mod, u32* exp) {
Certificate cert_local;
FIL db;
UINT bytes_read;
// not much in terms of error checking here
if (f_open(&db, "1:/dbs/certs.db", FA_READ | FA_OPEN_EXISTING) != FR_OK)
return 1;
f_lseek(&db, offset);
if (!cert) cert = &cert_local;
f_read(&db, cert, CERT_SIZE, &bytes_read);
f_close(&db);
if (mod) memcpy(mod, cert->mod, 0x100);
if (exp) *exp = getle32(cert->exp);
return 0;
}

22
arm9/source/game/cert.h Normal file
View File

@ -0,0 +1,22 @@
#pragma once
#include "common.h"
#define CERT_SIZE sizeof(Certificate)
// from: http://3dbrew.org/wiki/Certificates
// all numbers in big endian
typedef struct {
u8 sig_type[4]; // expected: 0x010004 / RSA_2048 SHA256
u8 signature[0x100];
u8 padding0[0x3C];
u8 issuer[0x40];
u8 keytype[4]; // expected: 0x01 / RSA_2048
u8 name[0x40];
u8 unknown[4];
u8 mod[0x100];
u8 exp[0x04];
u8 padding1[0x34];
} __attribute__((packed)) Certificate;
u32 LoadCertFromCertDb(u64 offset, Certificate* cert, u8* mod, u32* exp);

View File

@ -1,5 +1,6 @@
#include "ticket.h"
#include "unittype.h"
#include "cert.h"
#include "sha.h"
#include "rsa.h"
#include "ff.h"
@ -19,19 +20,11 @@ u32 ValidateTicketSignature(Ticket* ticket) {
static u8 mod[0x100] = { 0 };
static u32 exp = 0;
// grab cert from cert.db
if (!got_modexp) {
Certificate cert;
FIL db;
UINT bytes_read;
if (f_open(&db, "1:/dbs/certs.db", FA_READ | FA_OPEN_EXISTING) != FR_OK)
return 1;
f_lseek(&db, 0x3F10);
f_read(&db, &cert, CERT_SIZE, &bytes_read);
f_close(&db);
memcpy(mod, cert.mod, 0x100);
exp = getle32(cert.exp);
got_modexp = true;
// grab mod/exp from cert from cert.db
if (LoadCertFromCertDb(0x3F10, NULL, mod, &exp) == 0)
got_modexp = true;
else return 1;
}
if (!RSA_setKey2048(3, mod, exp) ||

View File

@ -3,7 +3,6 @@
#include "common.h"
#define TICKET_SIZE sizeof(Ticket)
#define CERT_SIZE sizeof(Certificate)
#define TICKET_CDNCERT_SIZE 0x700
#define TICKET_ISSUER "Root-CA00000003-XS0000000c"
@ -44,21 +43,6 @@ typedef struct {
u8 content_index[0xAC];
} __attribute__((packed)) Ticket;
// from: http://3dbrew.org/wiki/Certificates
// all numbers in big endian
typedef struct {
u8 sig_type[4]; // expected: 0x010004 / RSA_2048 SHA256
u8 signature[0x100];
u8 padding0[0x3C];
u8 issuer[0x40];
u8 keytype[4]; // expected: 0x01 / RSA_2048
u8 name[0x40];
u8 unknown[4];
u8 mod[0x100];
u8 exp[0x04];
u8 padding1[0x34];
} __attribute__((packed)) Certificate;
u32 ValidateTicket(Ticket* ticket);
u32 ValidateTicketSignature(Ticket* ticket);
u32 BuildFakeTicket(Ticket* ticket, u8* title_id);

View File

@ -1,6 +1,8 @@
#include "tmd.h"
#include "unittype.h"
#include "cert.h"
#include "sha.h"
#include "rsa.h"
#include "ff.h"
u32 ValidateTmd(TitleMetaData* tmd) {
@ -12,6 +14,25 @@ u32 ValidateTmd(TitleMetaData* tmd) {
return 0;
}
u32 ValidateTmdSignature(TitleMetaData* tmd) {
static bool got_modexp = false;
static u8 mod[0x100] = { 0 };
static u32 exp = 0;
if (!got_modexp) {
// grab mod/exp from cert from cert.db
if (LoadCertFromCertDb(0x3C10, NULL, mod, &exp) == 0)
got_modexp = true;
else return 1;
}
if (!RSA_setKey2048(3, mod, exp) ||
!RSA_verify2048((void*) &(tmd->signature), (void*) &(tmd->issuer), 0xC4))
return 1;
return 0;
}
u32 GetTmdCtr(u8* ctr, TmdContentChunk* chunk) {
memset(ctr, 0, 16);
memcpy(ctr, chunk->index, 2);

View File

@ -58,6 +58,7 @@ typedef struct {
} __attribute__((packed)) TitleMetaData;
u32 ValidateTmd(TitleMetaData* tmd);
u32 ValidateTmdSignature(TitleMetaData* tmd);
u32 GetTmdCtr(u8* ctr, TmdContentChunk* chunk);
u32 FixTmdHashes(TitleMetaData* tmd);
u32 BuildFakeTmd(TitleMetaData* tmd, u8* title_id, u32 n_contents, u32 save_size);

View File

@ -1028,6 +1028,7 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, PaneData** pan
bool tik_buildable = (FTYPE_TIKBUILD(filetype)) && !in_output_path;
bool key_buildable = (FTYPE_KEYBUILD(filetype)) && !in_output_path && !((drvtype & DRV_VIRTUAL) && (drvtype & DRV_SYSNAND));
bool titleinfo = (FTYPE_TITLEINFO(filetype));
bool ciacheckable = (FTYPE_CIACHECK(filetype));
bool renamable = (FTYPE_RENAMABLE(filetype));
bool trimable = (FTYPE_TRIMABLE(filetype)) && !(drvtype & DRV_VIRTUAL) && !(drvtype & DRV_ALIAS) &&
!(drvtype & DRV_CTRNAND) && !(drvtype & DRV_TWLNAND) && !(drvtype & DRV_IMAGE);
@ -1230,6 +1231,7 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, PaneData** pan
// stuff for special menu starts here
n_opt = 0;
int show_info = (titleinfo) ? ++n_opt : -1;
int ciacheck = (ciacheckable) ? ++n_opt : -1;
int mount = (mountable) ? ++n_opt : -1;
int restore = (restorable) ? ++n_opt : -1;
int ebackup = (ebackupable) ? ++n_opt : -1;
@ -1266,6 +1268,7 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, PaneData** pan
if (ebackup > 0) optionstr[ebackup-1] = "Update embedded backup";
if (ncsdfix > 0) optionstr[ncsdfix-1] = "Rebuild NCSD header";
if (show_info > 0) optionstr[show_info-1] = "Show title info";
if (ciacheck > 0) optionstr[ciacheck-1] = "CIA checker tool";
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 (cia_build > 0) optionstr[cia_build-1] = (cia_build_legit < 0) ? "Build CIA from file" : "Build CIA (standard)";
@ -1647,6 +1650,10 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, PaneData** pan
ShowPrompt(false, "Title info: not found");
return 0;
}
else if (user_select == ciacheck) { // -> CIA checker tool
ShowCiaCheckerInfo(file_path);
return 0;
}
else if (user_select == hsinject) { // -> Inject to Health & Safety
char* destdrv[2] = { NULL };
n_opt = 0;

View File

@ -1816,6 +1816,73 @@ u32 ShowGameFileTitleInfo(const char* path) {
return ret;
}
u32 ShowCiaCheckerInfo(const char* path) {
CiaStub* cia = (CiaStub*) malloc(sizeof(CiaStub));
if (!cia) return 1;
// path string
char pathstr[32 + 1];
TruncateString(pathstr, path, 32, 8);
// load CIA stub
if (LoadCiaStub(cia, path) != 0) {
ShowPrompt(false, "%s\nError: Probably not a CIA file", pathstr);
free(cia);
return 1;
}
// states: 0 -> invalid / 1 -> valid / badsig / 2 -> valid / goodsig
u32 state_ticket = 0;
u32 state_tmd = 0;
u32 content_count = getbe16(cia->tmd.content_count);
u32 content_found = 0;
u64 title_id = getbe64(cia->ticket.title_id);
u32 console_id = getbe32(cia->ticket.console_id);
bool missing_first = false;
bool is_dlc = ((title_id >> 32) == 0x0004008C);
// check ticket
if (ValidateTicket(&(cia->ticket)) == 0)
state_ticket = (ValidateTicketSignature(&(cia->ticket)) == 0) ? 2 : 1;
// check tmd
if (ValidateTmd(&(cia->tmd)) == 0)
state_tmd = (ValidateTmdSignature(&(cia->tmd)) == 0) ? 2 : 1;
// check for available contents
u8* cnt_index = cia->header.content_index;
for (u32 i = 0; (i < content_count) && (i < TMD_MAX_CONTENTS); i++) {
TmdContentChunk* chunk = &(cia->content_list[i]);
u16 index = getbe16(chunk->index);
if (cnt_index[index/8] & (1 << (7-(index%8)))) content_found++;
else if (i == 0) missing_first = true;
}
// CIA type string
char typestr[32];
if (!state_ticket || !state_tmd || missing_first || (!is_dlc && (content_found != content_count)))
snprintf(typestr, 32, "Possibly Broken");
else snprintf(typestr, 32, "%s %s%s",
console_id ? "Personal" : "Universal",
(state_ticket == 2) ? "Legit" : (state_tmd == 2) ? "Pirate Legit" : "Custom",
is_dlc ? " DLC" : "");
// output results
s32 state_verify = -1;
while (true) {
if (!ShowPrompt(state_verify < 0, "%s\n%s CIA File\n \nTitle ID: %016llX\nConsole ID: %08lX\nContents in CIA: %lu/%lu\nTicket/TMD: %s/%s\nVerification: %s",
pathstr, typestr, title_id, console_id,
content_found, content_count,
(state_ticket == 0) ? "invalid" : (state_ticket == 2) ? "legit" : "illegit",
(state_tmd == 0) ? "invalid" : (state_tmd == 2) ? "legit" : "illegit",
(state_verify < 0) ? "pending\n \nProceed with verification?" : (state_verify == 0) ? "passed" : "failed") ||
(state_verify >= 0)) break;
state_verify = VerifyCiaFile(path);
}
return (state_ticket && state_tmd) ? 0 : 1;
}
u32 BuildNcchInfoXorpads(const char* destdir, const char* path) {
FIL fp_info;
FIL fp_xorpad;

View File

@ -12,6 +12,7 @@ u32 ExtractDataFromDisaDiff(const char* path);
u64 GetGameFileTrimmedSize(const char* path);
u32 TrimGameFile(const char* path);
u32 ShowGameFileTitleInfo(const char* path);
u32 ShowCiaCheckerInfo(const char* path);
u32 GetTmdContentPath(char* path_content, const char* path_tmd);
u32 BuildNcchInfoXorpads(const char* destdir, const char* path);
u32 CheckHealthAndSafetyInject(const char* hsdrv);