Skip to content

Commit

Permalink
Added ability to deep decrypt CIAs
Browse files Browse the repository at this point in the history
This is in essence a one step cryptofixer. Also, made separate menu
entries for NCCH/CIA(shallow)/CIA(deep) decryptors and reorganized menu.
  • Loading branch information
d0k3 committed Oct 17, 2015
1 parent 4530bbd commit f944496
Show file tree
Hide file tree
Showing 6 changed files with 147 additions and 42 deletions.
3 changes: 3 additions & 0 deletions source/common.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@
#define getbe32(d) \
(((d)[0]<<24) | ((d)[1]<<16) | \
((d)[2]<< 8) | (d)[3])
#define getbe64(d) \
((((u64) getbe32(d))<<32) | \
((u64) getbe32(d+4)))
#define align(v,a) \
(((v) % (a)) ? ((v) + (a) - ((v) % (a))) : (v))

Expand Down
5 changes: 4 additions & 1 deletion source/decryptor/features.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,12 @@ u32 DumpNand(void);
u32 DecryptAllNandPartitions(void);
u32 DecryptTwlNandPartition(void);
u32 DecryptCtrNandPartition(void);
u32 DecryptNcsdNcchBatch(void);

u32 RestoreNand(void);
u32 InjectAllNandPartitions(void);
u32 InjectTwlNandPartition(void);
u32 InjectCtrNandPartition(void);

u32 DecryptNcsdNcch(void);
u32 DecryptCiaShallow(void);
u32 DecryptCiaDeep(void);
132 changes: 106 additions & 26 deletions source/decryptor/game.c
Original file line number Diff line number Diff line change
Expand Up @@ -343,11 +343,11 @@ u32 DecryptSdToSd(const char* filename, u32 offset, u32 size, CryptBufferInfo* i
return result;
}

u32 CheckHash(const char* filename, u32 offset, u32 size, u8* hash)
u32 GetHashFromFile(const char* filename, u32 offset, u32 size, u8* hash)
{
// uses the standard buffer, so be careful
u8* buffer = BUFFER_ADDRESS;
u8 digest[32];
u8* buffer = BUFFER_ADDRESS;
u8 digest[32];
sha256_context shactx;

if (!FileOpen(filename))
Expand All @@ -363,13 +363,24 @@ u32 CheckHash(const char* filename, u32 offset, u32 size, u8* hash)
sha256_update(&shactx, buffer, read_bytes);
}
sha256_finish(&shactx, digest);
memcpy(hash, digest, 32);
ShowProgress(0, 0);
FileClose();

return 0;
}

u32 CheckHashFromFile(const char* filename, u32 offset, u32 size, u8* hash)
{
u8 digest[32];

if (GetHashFromFile(filename, offset, size, digest) != 0)
return 1;

return (memcmp(hash, digest, 32) == 0) ? 0 : 1;
}

u32 DecryptNcch(const char* filename, u32 offset)
u32 DecryptNcch(const char* filename, u32 offset, u32 size, u64 seedId)
{
NcchHeader* ncch = (NcchHeader*) 0x20316200;
u8* buffer = (u8*) 0x20316400;
Expand All @@ -389,15 +400,25 @@ u32 DecryptNcch(const char* filename, u32 offset)
// check (again) for magic number
if (memcmp(ncch->magic, "NCCH", 4) != 0) {
Debug("Not a NCCH container");
return 0;
return 2; // not an actual error
}

// check if encrypted
if (ncch->flags[7] & 0x04) {
Debug("NCCH is not encrypted");
return 0;
return 2; // not an actual error
}

// check size
if ((size > 0) && (ncch->size * 0x200 > size)) {
Debug("NCCH size is out of bounds");
return 1;
}

// select correct title ID for seed crypto
if (seedId == 0)
seedId = ncch->partitionId;

bool uses7xCrypto = ncch->flags[3];
bool usesSeedCrypto = ncch->flags[7] & 0x20;
bool usesSec3Crypto = (ncch->flags[3] == 0x0A);
Expand Down Expand Up @@ -439,7 +460,7 @@ u32 DecryptNcch(const char* filename, u32 offset)
for (u32 i = 0x10;; i += 0x20) {
if (FileRead(entry, 0x20, i) != 0x20)
break;
if (entry->titleId == ncch->partitionId) {
if (entry->titleId == seedId) {
u8 keydata[32];
memcpy(keydata, ncch->signature, 16);
memcpy(keydata + 16, entry->external_seed, 16);
Expand Down Expand Up @@ -575,11 +596,11 @@ u32 DecryptNcch(const char* filename, u32 offset)
u32 ver_romfs = 2;

if (ncch->size_exthdr > 0)
ver_exthdr = CheckHash(filename, offset + 0x200, 0x400, ncch->hash_exthdr);
ver_exthdr = CheckHashFromFile(filename, offset + 0x200, 0x400, ncch->hash_exthdr);
if (ncch->size_exefs_hash > 0)
ver_exefs = CheckHash(filename, offset + (ncch->offset_exefs * 0x200), ncch->size_exefs_hash * 0x200, ncch->hash_exefs);
ver_exefs = CheckHashFromFile(filename, offset + (ncch->offset_exefs * 0x200), ncch->size_exefs_hash * 0x200, ncch->hash_exefs);
if (ncch->size_romfs_hash > 0)
ver_romfs = CheckHash(filename, offset + (ncch->offset_romfs * 0x200), ncch->size_romfs_hash * 0x200, ncch->hash_romfs);
ver_romfs = CheckHashFromFile(filename, offset + (ncch->offset_romfs * 0x200), ncch->size_romfs_hash * 0x200, ncch->hash_romfs);

Debug("Verify ExHdr/ExeFS/RomFS: %s/%s/%s", status_str[ver_exthdr], status_str[ver_exefs], status_str[ver_romfs]);
result = (((ver_exthdr | ver_exefs | ver_romfs) & 1) == 0) ? 0 : 1;
Expand All @@ -591,8 +612,9 @@ u32 DecryptNcch(const char* filename, u32 offset)

u32 DecryptCia(const char* filename, bool deep_decrypt)
{
u8* buffer = (u8*) 0x20316200;
u8* buffer = (u8*) 0x20316600;
__attribute__((aligned(16))) u8 titlekey[16];
u64 titleId;
u8* content_list;
u8* ticket_data;
u8* tmd_data;
Expand Down Expand Up @@ -671,15 +693,12 @@ u32 DecryptCia(const char* filename, bool deep_decrypt)
return 1;
}
TitleKeyEntry titlekeyEntry;
titleId = getbe64(ticket_data + 0x9C);
memcpy(titlekeyEntry.titleId, ticket_data + 0x9C, 8);
memcpy(titlekeyEntry.encryptedTitleKey, ticket_data + 0x7F, 16);
titlekeyEntry.commonKeyIndex = *(ticket_data + 0xB1);
DecryptTitlekey(&titlekeyEntry);
memcpy(titlekey, titlekeyEntry.encryptedTitleKey, 16);
u32* TitleId[2];
u32* TitleKey[4];
memcpy(TitleId, titlekeyEntry.titleId, 8);
memcpy(TitleKey, titlekey, 16);

// get content data from TMD
content_count = getbe16(tmd_data + 0x9E);
Expand All @@ -701,7 +720,9 @@ u32 DecryptCia(const char* filename, bool deep_decrypt)
u32 next_offset = offset_content;
CryptBufferInfo info = {.setKeyY = 0, .keyslot = 0x11, .mode = AES_CNT_TITLEKEY_MODE};
setup_aeskey(0x11, titlekey);
// memcpy(info.keyY, titlekey, 16);

if (deep_decrypt)
Debug("Pass #1: CIA decryption...");
for (u32 i = 0; i < content_count; i++) {
u32 size = getbe32(content_list + (0x30 * i) + 0xC);
u32 offset = next_offset;
Expand All @@ -718,7 +739,7 @@ u32 DecryptCia(const char* filename, bool deep_decrypt)
continue;
}
Debug("Verifying decrypted content...");
if (CheckHash(filename, offset, size, content_list + (0x30 * i) + 0x10) != 0) {
if (CheckHashFromFile(filename, offset, size, content_list + (0x30 * i) + 0x10) != 0) {
Debug("Verification failed!");
result = 1;
continue;
Expand All @@ -728,8 +749,55 @@ u32 DecryptCia(const char* filename, bool deep_decrypt)
n_decrypted++;
}

if (deep_decrypt) {
Debug("Pass #2: NCCH decryption...");
next_offset = offset_content;
for (u32 i = 0; i < content_count; i++) {
u32 ncch_state;
u32 size = getbe32(content_list + (0x30 * i) + 0xC);
u32 offset = next_offset;
next_offset = offset + size;
Debug("Processing Content %i of %i (%iMB)...", i + 1, content_count, size / (1024*1024));
ncch_state = DecryptNcch(filename, offset, size, titleId);
if (ncch_state == 0) {
Debug("Recalculating hash...");
if (GetHashFromFile(filename, offset, size, content_list + (0x30 * i) + 0x10) != 0) {
Debug("Recalculation failed!");
result = 1;
continue;
}
} else if (ncch_state == 1) {
Debug("Failed!");
result = 1;
continue;
}
n_decrypted++;
}
// recalculate content info hashes
Debug("Recalculating TMD hashes...");
for (u32 i = 0, kc = 0; i < 64 && kc < content_count; i++) {
u32 k = getbe16(tmd_data + 0xC4 + (i * 0x24) + 0x02);
u8 chunk_hash[32];
sha256_context shactx;
sha256_starts(&shactx);
sha256_update(&shactx, content_list + kc * 0x30, k * 0x30);
sha256_finish(&shactx, chunk_hash);
memcpy(tmd_data + 0xC4 + (i * 0x24) + 0x04, chunk_hash, 32);
kc += k;
}
u8 tmd_hash[32];
sha256_context shactx;
sha256_starts(&shactx);
sha256_update(&shactx, tmd_data + 0xC4, 64 * 0x24);
sha256_finish(&shactx, tmd_hash);
if (memcmp(tmd_data + 0xA4, tmd_hash, 32) != 0) {
n_encrypted++;
memcpy(tmd_data + 0xA4, tmd_hash, 32);
}
}

if (n_encrypted == 0) {
Debug("CIA is not encrypted");
Debug("CIA was not encrypted!");
} else if (n_decrypted > 0) {
if (!FileOpen(filename)) // already checked this file
return 1;
Expand All @@ -742,7 +810,7 @@ u32 DecryptCia(const char* filename, bool deep_decrypt)
}


u32 DecryptNcsdNcchBatch()
u32 DecryptGameFilesBatch(bool batchNcch, bool batchCia, bool deepCia)
{
const char* ncsd_partition_name[8] = {
"Executable", "Manual", "DPC", "Unknown", "Unknown", "Unknown", "UpdateN3DS", "UpdateO3DS"
Expand Down Expand Up @@ -770,16 +838,16 @@ u32 DecryptNcsdNcchBatch()
}
FileClose();

if (memcmp(buffer + 0x100, "NCCH", 4) == 0) {
if (batchNcch && (memcmp(buffer + 0x100, "NCCH", 4) == 0)) {
Debug("Decrypting NCCH \"%s\"", path + path_len);
if (DecryptNcch(path, 0x00) == 0) {
if (DecryptNcch(path, 0x00, 0, 0) != 1) {
Debug("Success!");
n_processed++;
} else {
Debug("Failed!");
n_failed++;
}
} else if (memcmp(buffer + 0x100, "NCSD", 4) == 0) {
} else if (batchNcch && (memcmp(buffer + 0x100, "NCSD", 4) == 0)) {
Debug("Decrypting NCSD \"%s\"", path + path_len);
u32 p;
for (p = 0; p < 8; p++) {
Expand All @@ -788,7 +856,7 @@ u32 DecryptNcsdNcchBatch()
if (size == 0)
continue;
Debug("Partition %i (%s)", p, ncsd_partition_name[p]);
if (DecryptNcch(path, offset) != 0)
if (DecryptNcch(path, offset, size, 0) == 1)
break;
}
if ( p == 8 ) {
Expand All @@ -798,9 +866,9 @@ u32 DecryptNcsdNcchBatch()
Debug("Failed!");
n_failed++;
}
} else if (memcmp(buffer, "\x20\x20", 2) == 0) {
} else if (batchCia && (memcmp(buffer, "\x20\x20", 2) == 0)) {
Debug("Decrypting CIA \"%s\"", path + path_len);
if (DecryptCia(path, false) == 0) {
if (DecryptCia(path, deepCia) == 0) {
Debug("Success!");
n_processed++;
} else {
Expand All @@ -814,8 +882,20 @@ u32 DecryptNcsdNcchBatch()

if (n_processed) {
Debug("");
Debug("%ux decrypted / %u failed ", n_processed, n_failed);
Debug("%ux decrypted / %ux failed ", n_processed, n_failed);
}

return !(n_processed);
}

u32 DecryptNcsdNcch() {
return DecryptGameFilesBatch(true, false, false);
}

u32 DecryptCiaShallow() {
return DecryptGameFilesBatch(false, true, false);
}

u32 DecryptCiaDeep() {
return DecryptGameFilesBatch(false, true, true);
}
6 changes: 4 additions & 2 deletions source/decryptor/game.h
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@ typedef struct {


u32 DecryptSdToSd(const char* filename, u32 offset, u32 size, CryptBufferInfo* info);
u32 CheckHash(const char* filename, u32 offset, u32 size, u8* hash);
u32 DecryptNcch(const char* filename, u32 offset);
u32 GetHashFromFile(const char* filename, u32 offset, u32 size, u8* hash);
u32 CheckHashFromFile(const char* filename, u32 offset, u32 size, u8* hash);
u32 DecryptNcch(const char* filename, u32 offset, u32 size, u64 seedId);
u32 DecryptCia(const char* filename, bool deep_decrypt);
u32 DecryptGameFilesBatch(bool batchNcch, bool batchCia, bool deepCia);
41 changes: 29 additions & 12 deletions source/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -23,25 +23,29 @@ MenuInfo menu[] =
}
},
{
"NAND Options", 8,
"Titlekey Options", 3,
{
{ "Titlekey Decrypt (file)", &DecryptTitlekeysFile, 0, 0 },
{ "Titlekey Decrypt (NAND)", &DecryptTitlekeysNand, 0, 0 },
{ "Titlekey Decrypt (EMU)", &DecryptTitlekeysNand, 0, 1 }
}
},
{
"NAND Dump Options", 4,
{
{ "NAND Backup", &DumpNand, 0, 0 },
{ "All Partitions Dump", &DecryptAllNandPartitions, 0, 0 },
{ "TWLNAND Partition Dump", &DecryptTwlNandPartition, 0, 0 },
{ "CTRNAND Partition Dump", &DecryptCtrNandPartition, 0, 0 },
{ "NAND Restore", &RestoreNand, 1, 0 },
{ "All Partitions Inject", &InjectAllNandPartitions, 1, 0 },
{ "TWLNAND Partition Inject", &InjectTwlNandPartition, 1, 0 },
{ "CTRNAND Partition Inject", &InjectCtrNandPartition, 1, 0 }
}
},
{
"Title Options", 4,
"NAND Inject Options", 4,
{
{ "Titlekey Decrypt (file)", &DecryptTitlekeysFile, 0, 0 },
{ "Titlekey Decrypt (NAND)", &DecryptTitlekeysNand, 0, 0 },
{ "Titlekey Decrypt (EMU)", &DecryptTitlekeysNand, 0, 1 },
{ "NCCH/CIA Decryptor", &DecryptNcsdNcchBatch, 0, 0 }
{ "NAND Restore", &RestoreNand, 1, 0 },
{ "All Partitions Inject", &InjectAllNandPartitions, 1, 0 },
{ "TWLNAND Partition Inject", &InjectTwlNandPartition, 1, 0 },
{ "CTRNAND Partition Inject", &InjectCtrNandPartition, 1, 0 }
}
},
{
Expand All @@ -66,14 +70,27 @@ MenuInfo menu[] =
{ "Update SeedDB", &UpdateSeedDb, 0, 1 }
}
},
{
"Game Decryptor Options", 3,
{
{ "NCCH/NCSD Decryptor", &DecryptNcsdNcch, 0, 0 },
{ "CIA Decryptor (shallow)", &DecryptCiaShallow, 0, 0 },
{ "CIA Decryptor (deep)", &DecryptCiaDeep, 0, 0 }
}
},
#ifdef EXPERIMENTAL
{
"EmuNAND Options", 8,
"EmuNAND Dump Options", 4,
{
{ "EmuNAND Backup", &DumpNand, 0, 1 },
{ "All Partitions Dump", &DecryptAllNandPartitions, 0, 1 },
{ "TWLNAND Partition Dump", &DecryptTwlNandPartition, 0, 1 },
{ "CTRNAND Partition Dump", &DecryptCtrNandPartition, 0, 1 },
{ "CTRNAND Partition Dump", &DecryptCtrNandPartition, 0, 1 }
}
},
{
"EmuNAND Inject Options", 4,
{
{ "EmuNAND Restore", &RestoreNand, 1, 1 },
{ "All Partitions Inject", &InjectAllNandPartitions, 1, 1 },
{ "TWLNAND Partition Inject", &InjectTwlNandPartition, 1, 1 },
Expand Down
2 changes: 1 addition & 1 deletion source/menu.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ typedef struct {
typedef struct {
char* name;
u32 n_entries;
MenuEntry entries[8];
MenuEntry entries[12];
} MenuInfo;

void ProcessMenu(MenuInfo* info, u32 nMenus);

0 comments on commit f944496

Please sign in to comment.