Added BOSS file decryption & sverification

This commit is contained in:
d0k3 2017-01-30 20:28:49 +01:00
parent 0ea8ac2ff1
commit 3f31807c75
7 changed files with 224 additions and 17 deletions

View File

@ -51,7 +51,10 @@ u32 IdentifyFileType(const char* path) {
return SYS_FIRM; // FIRM file
}
}
if ((fsize > sizeof(NcchInfoHeader)) &&
if ((fsize > sizeof(BossHeader)) &&
(ValidateBossHeader((BossHeader*) (void*) header, fsize) == 0)) {
return GAME_BOSS; // BOSS (SpotPass) file
} else if ((fsize > sizeof(NcchInfoHeader)) &&
(GetNcchInfoVersion((NcchInfoHeader*) (void*) header)) &&
fname && (strncasecmp(fname, NCCHINFO_NAME, 32) == 0)) {
return BIN_NCCHNFO; // ncchinfo.bin file

View File

@ -10,16 +10,17 @@
#define GAME_TMD (1<<5)
#define GAME_EXEFS (1<<6)
#define GAME_ROMFS (1<<7)
#define SYS_FIRM (1<<8)
#define BIN_NCCHNFO (1<<9)
#define BIN_LAUNCH (1<<10)
#define GAME_BOSS (1<<8)
#define SYS_FIRM (1<<9)
#define BIN_NCCHNFO (1<<10)
#define BIN_LAUNCH (1<<11)
#define FLAG_CXI (1<<30)
#define FLAG_ENCRYPTED (1<<31)
#define FTYPE_MOUNTABLE(tp) (tp&(IMG_FAT|IMG_NAND|GAME_CIA|GAME_NCSD|GAME_NCCH|GAME_EXEFS|GAME_ROMFS|SYS_FIRM))
#define FYTPE_VERIFICABLE(tp) (tp&(IMG_NAND|GAME_CIA|GAME_NCSD|GAME_NCCH|GAME_TMD|SYS_FIRM))
#define FYTPE_DECRYPTABLE(tp) (tp&(GAME_CIA|GAME_NCSD|GAME_NCCH|SYS_FIRM))
#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|SYS_FIRM))
#define FTYPE_BUILDABLE(tp) (tp&(GAME_NCSD|GAME_NCCH|GAME_TMD))
#define FTYPE_BUILDABLE_L(tp) (FTYPE_BUILDABLE(tp) && (tp&(GAME_TMD)))
#define FTYPE_HSINJECTABLE(tp) ((tp&(GAME_NCCH|FLAG_CXI|FLAG_ENCRYPTED)) == (GAME_NCCH|FLAG_CXI))

96
source/game/boss.c Normal file
View File

@ -0,0 +1,96 @@
#include "boss.h"
#include "sha.h"
#include "aes.h"
// http://3dbrew.org/wiki/SpotPass#Content_Header
u32 CheckBossHash(BossHeader* boss, bool encrypted) {
u8 hash_area[0x14] = { 0 };
u8 boss_sha256[0x20];
u8 l_sha256[0x20];
// calculate hash
memcpy(hash_area, ((u8*) boss) + 0x28, 0x12);
memcpy(boss_sha256, boss->hash_header, 0x20);
if (encrypted) {
CryptBoss(hash_area, 0x28, 0x12, boss);
CryptBoss(boss_sha256, 0x28 + 0x12, 0x20, boss);
}
sha_quick(l_sha256, hash_area, 0x14, SHA256_MODE);
return (memcmp(boss_sha256, l_sha256, 0x20) == 0) ? 0 : 1;
}
u32 ValidateBossHeader(BossHeader* header, u32 fsize) {
u8 boss_magic[] = { BOSS_MAGIC };
// base checks
if ((memcmp(header->magic, boss_magic, sizeof(boss_magic)) != 0) ||
(fsize && (fsize != getbe32(header->filesize))) ||
(getbe32(header->filesize) > BOSS_MAX_SIZE) ||
(getbe32(header->filesize) < sizeof(BossHeader)) ||
(getbe16(header->unknown0) != 0x0001) ||
(getbe16(header->cnthdr_hash_type) != 0x0002) ||
(getbe16(header->cnthdr_rsa_size) != 0x0002))
return 1;
// hash check
if ((CheckBossHash(header, false) != 0) &&
(CheckBossHash(header, true) != 0))
return 1;
return 0;
}
u32 GetBossPayloadHashHeader(u8* header, BossHeader* boss) {
memset(header, 0, BOSS_SIZE_PAYLOAD_HEADER);
memcpy(header, ((u8*) boss) + 0x15A, 0x1C);
return 0;
}
u32 CheckBossEncrypted(BossHeader* boss) {
return CheckBossHash(boss, true);
}
// on the fly de-/encryptor for BOSS
u32 CryptBoss(u8* data, u32 offset, u32 size, BossHeader* boss) {
// check data area (encrypted area starts @0x28)
if (offset + size < 0x28) return 0;
else if (offset < 0x28) {
data += 0x28 - offset;
size -= 0x28 - offset;
offset = 0x28;
}
// decrypt BOSS data
u8 ctr[16] = { 0 };
memcpy(ctr, boss->ctr12, 12);
ctr[15] = 0x01;
use_aeskey(0x38);
ctr_decrypt_byte(data, data, size, offset - 0x28, AES_CNT_CTRNAND_MODE, ctr);
return 0;
}
// on the fly de-/encryptor for BOSS - sequential
u32 CryptBossSequential(u8* data, u32 offset, u32 size) {
// warning: this will only work for sequential processing
// unexpected results otherwise
static BossHeader boss = { 0 };
static BossHeader* bossptr = NULL;
// fetch boss header from data
if ((offset == 0) && (size >= sizeof(BossHeader))) {
bossptr = NULL;
memcpy(&boss, data, sizeof(BossHeader));
if (((CheckBossEncrypted(&boss) == 0) &&
(CryptBoss((u8*) &boss, 0, sizeof(BossHeader), &boss) != 0)) ||
(ValidateBossHeader(&boss, 0) != 0))
return 1;
bossptr = &boss;
}
// safety check, boss pointer
if (!bossptr) return 1;
return CryptBoss(data, offset, size, bossptr);
}

45
source/game/boss.h Normal file
View File

@ -0,0 +1,45 @@
#pragma once
#include "common.h"
#define BOSS_MAGIC 0x62, 0x6F, 0x73, 0x73, 0x00, 0x01, 0x00, 0x01
#define BOSS_MAX_SIZE 0xF0000 // 960 kB, should be more than enough
#define BOSS_OFFSET_PAYLOAD sizeof(BossHeader)
#define BOSS_SIZE_PAYLOAD_HEADER (0x1C + 2)
// see: http://3dbrew.org/wiki/SpotPass#BOSS_Header
// and: http://3dbrew.org/wiki/SpotPass#Content_Header
// and: http://3dbrew.org/wiki/SpotPass#Payload_Content_Header
// everything is in big endian
typedef struct {
// actual BOSS header
u8 magic[8]; // "boss" + 0x00010001, see above
u8 filesize[4]; // big endian
u8 release_date[8];
u8 unknown0[2]; // always 0x0001
u8 padding[2];
u8 cnthdr_hash_type[2]; // always 0x0002
u8 cnthdr_rsa_size[2]; // always 0x0002
u8 ctr12[12]; // first 12 byte of ctr
// content header, encryption starts here (0x28)
u8 unknown1[0x10]; // usually 0x80 followed by 0x00
u8 ext_info[2]; // for generating extdata filepath
u8 hash_header[0x20];
u8 signature_header[0x100];
// payload header, first 0x1C byte used for hash (0x15A)
u8 programId[8];
u8 unknown2[4]; // typically zero
u8 data_type[4];
u8 size_payload[4];
u8 ns_dataId[4];
u8 unknown3[4];
u8 hash_payload[0x20];
u8 signature_payload[0x100];
} __attribute__((packed)) BossHeader;
u32 ValidateBossHeader(BossHeader* header, u32 fsize);
u32 GetBossPayloadHashHeader(u8* header, BossHeader* boss);
u32 CheckBossEncrypted(BossHeader* boss);
u32 CryptBoss(u8* data, u32 offset, u32 size, BossHeader* boss);
u32 CryptBossSequential(u8* data, u32 offset, u32 size);

View File

@ -6,4 +6,5 @@
#include "exefs.h"
#include "romfs.h"
#include "firm.h"
#include "boss.h"
#include "ncchinfo.h"

View File

@ -504,6 +504,48 @@ u32 VerifyFirmFile(const char* path) {
return 0;
}
u32 VerifyBossFile(const char* path) {
BossHeader* boss = (BossHeader*) TEMP_BUFFER;
u8* payload_hdr = MAIN_BUFFER;
u8* payload = MAIN_BUFFER + BOSS_SIZE_PAYLOAD_HEADER;
u32 payload_size;
bool encrypted = false;
char pathstr[32 + 1];
TruncateString(pathstr, path, 32, 8);
// read file header
UINT btr;
if ((fvx_qread(path, boss, 0, sizeof(BossHeader), &btr) != FR_OK) ||
(btr != sizeof(BossHeader)) || (ValidateBossHeader(boss, 0) != 0)) {
ShowPrompt(false, "%s\nError: Not a BOSS file", pathstr);
return 1;
}
// get / check size
payload_size = getbe32(boss->filesize) - sizeof(BossHeader);
if ((payload_size + BOSS_SIZE_PAYLOAD_HEADER > MAIN_BUFFER_SIZE) || !payload_size)
return 1;
// check if encrypted, decrypt if required
encrypted = (CheckBossEncrypted(boss) == 0);
if (encrypted) CryptBoss((u8*) boss, 0, sizeof(BossHeader), boss);
// actual hash calculation & compare
u8 hash[32];
memset(MAIN_BUFFER, 0, MAIN_BUFFER_SIZE);
GetBossPayloadHashHeader(payload_hdr, boss);
fvx_qread(path, payload, sizeof(BossHeader), payload_size, &btr);
if (encrypted) CryptBoss(payload, sizeof(BossHeader), payload_size, boss);
sha_quick(hash, MAIN_BUFFER, payload_size + BOSS_SIZE_PAYLOAD_HEADER, SHA256_MODE);
if (memcmp(hash, boss->hash_payload, 0x20) != 0) {
ShowPrompt(false, "%s\nBOSS payload hash mismatch", pathstr);
return 1;
}
return 0;
}
u32 VerifyGameFile(const char* path) {
u32 filetype = IdentifyFileType(path);
if (filetype & GAME_CIA)
@ -514,6 +556,8 @@ u32 VerifyGameFile(const char* path) {
return VerifyNcchFile(path, 0, 0);
else if (filetype & GAME_TMD)
return VerifyTmdFile(path);
else if (filetype & GAME_BOSS)
return VerifyBossFile(path);
else if (filetype & SYS_FIRM)
return VerifyFirmFile(path);
else return 1;
@ -598,6 +642,19 @@ u32 CheckEncryptedFirmFile(const char* path) {
return 1;
}
u32 CheckEncryptedBossFile(const char* path) {
BossHeader* boss = (BossHeader*) TEMP_BUFFER;
UINT btr;
// get boss header
if ((fvx_qread(path, boss, 0, sizeof(BossHeader), &btr) != FR_OK) ||
(btr != sizeof(BossHeader))) {
return 1;
}
return CheckBossEncrypted(boss);
}
u32 CheckEncryptedGameFile(const char* path) {
u32 filetype = IdentifyFileType(path);
if (filetype & GAME_CIA)
@ -606,12 +663,14 @@ u32 CheckEncryptedGameFile(const char* path) {
return CheckEncryptedNcsdFile(path);
else if (filetype & GAME_NCCH)
return CheckEncryptedNcchFile(path, 0);
else if (filetype & GAME_BOSS)
return CheckEncryptedBossFile(path);
else if (filetype & SYS_FIRM)
return CheckEncryptedFirmFile(path);
else return 1;
}
u32 CryptNcchNcsdFirmFile(const char* orig, const char* dest, u32 mode, u16 crypto, // crypto only relevant for NCCH
u32 CryptNcchNcsdBossFirmFile(const char* orig, const char* dest, u32 mode, u16 crypto, // crypto only relevant for NCCH
u32 offset, u32 size, TmdContentChunk* chunk, const u8* titlekey) { // this line only for CIA contents
// this will do a simple copy for unencrypted files
bool inplace = (strncmp(orig, dest, 256) == 0);
@ -642,13 +701,14 @@ u32 CryptNcchNcsdFirmFile(const char* orig, const char* dest, u32 mode, u16 cryp
u32 ret = 0;
if (!ShowProgress(offset, fsize, dest)) ret = 1;
if (mode & (GAME_NCCH|GAME_NCSD|SYS_FIRM)) { // for NCCH / NCSD / FIRM files
if (mode & (GAME_NCCH|GAME_NCSD|GAME_BOSS|SYS_FIRM)) { // for NCCH / NCSD / FIRM files
for (u32 i = 0; (i < size) && (ret == 0); i += MAIN_BUFFER_SIZE) {
u32 read_bytes = min(MAIN_BUFFER_SIZE, (size - i));
UINT bytes_read, bytes_written;
if (fvx_read(ofp, MAIN_BUFFER, read_bytes, &bytes_read) != FR_OK) ret = 1;
if (((mode & GAME_NCCH) && (CryptNcchSequential(MAIN_BUFFER, i, read_bytes, crypto) != 0)) ||
((mode & GAME_NCSD) && (DecryptNcsdSequential(MAIN_BUFFER, i, read_bytes) != 0)) ||
((mode & GAME_BOSS) && (CryptBossSequential(MAIN_BUFFER, i, read_bytes) != 0)) ||
((mode & SYS_FIRM) && (DecryptFirmSequential(MAIN_BUFFER, i, read_bytes) != 0)))
ret = 1;
if (inplace) fvx_lseek(ofp, fvx_tell(ofp) - read_bytes);
@ -719,7 +779,7 @@ u32 DecryptCiaFile(const char* orig, const char* dest) {
for (u32 i = 0; (i < content_count) && (i < TMD_MAX_CONTENTS); i++) {
TmdContentChunk* chunk = &(cia->content_list[i]);
u64 size = getbe64(chunk->size);
if (CryptNcchNcsdFirmFile(orig, dest, GAME_CIA, NCCH_NOCRYPTO, next_offset, size, chunk, titlekey) != 0)
if (CryptNcchNcsdBossFirmFile(orig, dest, GAME_CIA, NCCH_NOCRYPTO, next_offset, size, chunk, titlekey) != 0)
return 1;
next_offset += size;
}
@ -738,7 +798,7 @@ u32 DecryptFirmFile(const char* orig, const char* dest) {
UINT btr;
// actual decryption
if (CryptNcchNcsdFirmFile(orig, dest, SYS_FIRM, NCCH_NOCRYPTO, 0, 0, NULL, NULL) != 0)
if (CryptNcchNcsdBossFirmFile(orig, dest, SYS_FIRM, 0, 0, 0, NULL, NULL) != 0)
return 1;
// open destination file, get FIRM header
@ -814,8 +874,8 @@ u32 DecryptGameFile(const char* path, bool inplace) {
ret = DecryptCiaFile(path, destptr);
else if (filetype & SYS_FIRM)
ret = DecryptFirmFile(path, destptr);
else if (filetype & (GAME_NCCH|GAME_NCSD))
ret = CryptNcchNcsdFirmFile(path, destptr, filetype, NCCH_NOCRYPTO, 0, 0, NULL, NULL);
else if (filetype & (GAME_NCCH|GAME_NCSD|GAME_BOSS))
ret = CryptNcchNcsdBossFirmFile(path, destptr, filetype, NCCH_NOCRYPTO, 0, 0, NULL, NULL);
else ret = 1;
if (!inplace && (ret != 0))
@ -1239,9 +1299,9 @@ u32 InjectHealthAndSafety(const char* path, const char* destdrv) {
if (f_rename(path_cxi, path_bak) != FR_OK) return 1;
} else f_unlink(path_cxi);
// copy the source CXI
// copy / decrypt the source CXI
u32 ret = 0;
if (CryptNcchNcsdFirmFile(path, path_cxi, GAME_NCCH, NCCH_NOCRYPTO, 0, 0, NULL, NULL) != 0)
if (CryptNcchNcsdBossFirmFile(path, path_cxi, GAME_NCCH, NCCH_NOCRYPTO, 0, 0, NULL, NULL) != 0)
ret = 1;
// fix up the injected H&S NCCH header (copy H&S signature, title ID)
@ -1256,10 +1316,10 @@ u32 InjectHealthAndSafety(const char* path, const char* destdrv) {
} else ret = 1;
// encrypt the CXI in place
if (CryptNcchNcsdFirmFile(path_cxi, path_cxi, GAME_NCCH, crypto, 0, 0, NULL, NULL) != 0)
if (CryptNcchNcsdBossFirmFile(path_cxi, path_cxi, GAME_NCCH, crypto, 0, 0, NULL, NULL) != 0)
ret = 1;
if (ret != 0) { // try recover
if (ret != 0) { // in case of failure: try recover
f_unlink(path_cxi);
f_rename(path_bak, path_cxi);
}

View File

@ -625,6 +625,7 @@ u32 FileHandlerMenu(char* current_path, u32* cursor, u32* scroll, DirStruct* cur
(filetype & GAME_EXEFS) ? "Mount as EXEFS image" :
(filetype & GAME_ROMFS) ? "Mount as ROMFS image" :
(filetype & GAME_TMD) ? "TMD file options..." :
(filetype & GAME_BOSS) ? "BOSS file options..." :
(filetype & SYS_FIRM) ? "FIRM image options..." :
(filetype & BIN_NCCHNFO)? "NCCHinfo options..." :
(filetype & BIN_LAUNCH) ? "Launch as arm9 payload" : "???";