Major update

Changed titlekey dump methodology to no longer need reboot.
Added SD seed dumping.
Reorganized and clarified UI text.
Swapped C++-style I/O for C-style.
Tightened up dependencies.
This commit is contained in:
shchmue
2018-12-28 16:06:18 -05:00
parent 41c2604d9a
commit 922cf3f4c4
25 changed files with 23552 additions and 372 deletions

View File

@ -17,29 +17,40 @@
#include "KeyCollection.hpp"
#include "Common.hpp"
#include "creport_debug_types.hpp"
#include "Stopwatch.hpp"
#include <algorithm>
#include <chrono>
#include <filesystem>
#include <fstream>
#include <functional>
#include <iomanip>
#include <set>
#include <string>
#include <unordered_map>
#include <stdio.h>
#include <switch.h>
#include "fatfs/ff.h"
extern "C" {
#include "set_ext.h"
#include "nx/es.h"
#include "nx/set_ext.h"
}
const u8 KeyCollection::null_hash[0x20] = { // hash of empty string
#define TITLEKEY_BUFFER_SIZE 0x40000
// hash of empty string
const u8 KeyCollection::null_hash[0x20] = {
0xE3, 0xB0, 0xC4, 0x42, 0x98, 0xFC, 0x1C, 0x14, 0x9A, 0xFB, 0xF4, 0xC8, 0x99, 0x6F, 0xB9, 0x24,
0x27, 0xAE, 0x41, 0xE4, 0x64, 0x9B, 0x93, 0x4C, 0xA4, 0x95, 0x99, 0x1B, 0x78, 0x52, 0xB8, 0x55};
FsStorage storage;
// function timer
template<typename Duration = std::chrono::microseconds, typename FT, typename ... Args>
typename Duration::rep profile(FT&& fun, Args&&... args) {
const auto beg = std::chrono::high_resolution_clock::now();
std::invoke(fun, std::forward<Args>(args)...);//std::forward<FT>(fun)(std::forward<Args>(args)...);
std::invoke(fun, std::forward<Args>(args)...);
const auto end = std::chrono::high_resolution_clock::now();
return std::chrono::duration_cast<Duration>(end - beg).count();
}
@ -226,67 +237,51 @@ KeyCollection::KeyCollection() {
};
};
int KeyCollection::get_keys() {
void KeyCollection::get_keys() {
Stopwatch total_time;
total_time.start();
int64_t profiler_time = profile(Common::get_tegra_keys, sbk, tsec, tsec_root_key);
if ((sbk.found() && tsec.found()) || tsec_root_key.found()) {
Common::draw_text_with_time(0x10, 0x80, GREEN, "Get Tegra keys...", profiler_time);
Common::draw_text_with_time(0x10, 0x60, GREEN, "Get Tegra keys...", profiler_time);
} else {
Common::draw_text(0x010, 0x80, RED, "Get Tegra keys...");
Common::draw_text(0x190, 0x80, RED, "Failed");
Common::draw_text(0x010, 0x60, RED, "Get Tegra keys...");
Common::draw_text(0x190, 0x60, RED, "Failed");
Common::draw_text(0x190, 0x20, RED, "Warning: Saving limited keyset.");
Common::draw_text(0x190, 0x40, RED, "Dump Tegra keys with payload and run again to get all keys.");
}
profiler_time = profile(&KeyCollection::get_memory_keys, *this);
Common::draw_text_with_time(0x10, 0x0a0, GREEN, "Get keys from memory...", profiler_time);
Common::draw_text_with_time(0x10, 0x080, GREEN, "Get keys from memory...", profiler_time);
profiler_time = profile(&KeyCollection::get_master_keys, *this);
Common::draw_text_with_time(0x10, 0x0c0, GREEN, "Get master keys...", profiler_time);
Common::draw_text_with_time(0x10, 0x0a0, GREEN, "Get master keys...", profiler_time);
profiler_time = profile(&KeyCollection::derive_keys, *this);
Common::draw_text_with_time(0x10, 0x0e0, GREEN, "Derive remaining keys...", profiler_time);
Common::draw_text_with_time(0x10, 0x0c0, GREEN, "Derive remaining keys...", profiler_time);
profiler_time = profile(&KeyCollection::save_keys, *this);
Common::draw_text_with_time(0x10, 0x100, GREEN, "Saving keys to keyfile...", profiler_time);
Common::draw_text_with_time(0x10, 0x0e0, GREEN, "Saving keys to keyfile...", profiler_time);
total_time.stop();
Common::draw_line(0x8, 0x110, 0x280, GREEN);
Common::draw_text_with_time(0x10, 0x130, GREEN, "Total time elapsed:", total_time.get_elapsed());
Common::draw_line(0x8, 0xf0, 0x280, GREEN);
Common::draw_text_with_time(0x10, 0x110, GREEN, "Total time elapsed:", total_time.get_elapsed());
char keys_str[32]; // todo: get sd seed
char keys_str[32];
sprintf(keys_str, "Total keys found: %lu", Key::get_saved_key_count());
Common::draw_text(0x2a0, 0x130, CYAN, keys_str);
Common::draw_text(0x2a0, 0x110, CYAN, keys_str);
Common::draw_text(0x80, 0x140, GREEN, "Keys saved to \"/switch/prod.keys\"!");
Common::draw_text(0x30, 0x160, YELLOW, "WARNING: dumping titlekeys may crash homebrew or games UNLESS you reboot afterwards");
Common::draw_text(0x160, 0x180, CYAN, ">> Press A to dump titlekeys or + to exit <<");
while(appletMainLoop()) {
hidScanInput();
u64 kDown = hidKeysDown(CONTROLLER_P1_AUTO);
if (kDown & KEY_PLUS)
return Status_success_no_titlekeys;
else if (kDown & KEY_A)
break;
consoleUpdate(NULL);
}
Common::draw_text(0x10, 0x1b0, CYAN, "Dumping titlekeys...");
Common::draw_text(0x10, 0x170, CYAN, "Dumping titlekeys...");
consoleUpdate(NULL);
profiler_time = profile(&KeyCollection::get_titlekeys, *this);
if (titlekeys_dumped > 0) {
Common::draw_text_with_time(0x10, 0x1b0, GREEN, "Dumping titlekeys...", profiler_time);
sprintf(keys_str, "Titlekeys found: %lu", titlekeys_dumped);
Common::draw_text(0x2a0, 0x1b0, CYAN, keys_str);
return Status_success_titlekeys;
} else {
Common::draw_text(0x010, 0x1b0, RED, "Dumping titlekeys...");
Common::draw_text(0x190, 0x1b0, RED, "Failed. Reboot and try again!"); // todo: detect if no titles installed
return Status_success_titlekeys_failed;
}
Common::draw_text_with_time(0x10, 0x170, GREEN, "Dumping titlekeys...", profiler_time);
sprintf(keys_str, "Titlekeys found: %lu", titlekeys_dumped);
Common::draw_text(0x2a0, 0x170, CYAN, keys_str);
if (titlekeys_dumped > 0)
Common::draw_text(0x80, 0x1a0, GREEN, "Titlekeys saved to \"/switch/title.keys\"!");
else
Common::draw_text(0x80, 0x1a0, GREEN, "No titlekeys found. Either you've never played or installed a game or dump failed.");
}
void KeyCollection::get_master_keys() {
@ -435,10 +430,42 @@ void KeyCollection::derive_keys() {
if (ssl_rsa_kek_source_x.found() && ssl_rsa_kek_source_y.found() && !master_key.empty())
ssl_rsa_kek = Key {"ssl_rsa_kek", 0x10,
ssl_rsa_kek_source_x.generate_kek(master_key[0], rsa_private_kek_generation_source, ssl_rsa_kek_source_y)};
char seed_vector[0x10], seed[0x10], buffer[0x10];
u32 bytes_read, file_pos = 0;
// dump sd seed
FILE *sd_private = fopen("/Nintendo/Contents/private", "rb");
if (!sd_private) return;
fread(seed_vector, 0x10, 1, sd_private);
fclose(sd_private);
FATFS fs;
FRESULT fr;
FIL save_file;
fsOpenBisStorage(&storage, 31);
if (f_mount(&fs, "", 1) || f_chdir("/save")) return;
if (f_open(&save_file, "8000000000000043", FA_READ | FA_OPEN_EXISTING)) return;
for (;;) {
fr = f_read(&save_file, buffer, 0x10, &bytes_read);
if (fr || (bytes_read == 0)) break;
if (std::equal(seed_vector, seed_vector + 0x10, buffer)) {
f_read(&save_file, seed, 0x10, &bytes_read);
sd_seed = Key {"sd_seed", 0x10, byte_vector(seed, seed + 0x10)};
break;
}
file_pos += 0x4000;
if (f_lseek(&save_file, file_pos)) break;
}
f_close(&save_file);
fsStorageClose(&storage);
}
void KeyCollection::save_keys() {
std::ofstream key_file("/switch/prod.keys");
FILE *key_file = fopen("/switch/prod.keys", "w");
if (!key_file) return;
aes_kek_generation_source.save_key(key_file);
aes_key_generation_source.save_key(key_file);
@ -496,6 +523,7 @@ void KeyCollection::save_keys() {
sd_card_kek_source.save_key(key_file);
sd_card_nca_key_source.save_key(key_file);
sd_card_save_key_source.save_key(key_file);
sd_seed.save_key(key_file);
ssl_rsa_kek.save_key(key_file);
for (auto k : ssl_keys)
k->save_key(key_file);
@ -504,12 +532,46 @@ void KeyCollection::save_keys() {
titlekek_source.save_key(key_file);
tsec.save_key(key_file);
tsec_root_key.save_key(key_file);
fclose(key_file);
}
void KeyCollection::get_titlekeys() {
if (!kernelAbove200() || !eticket_rsa_kek.found())
return;
u32 common_count, personalized_count, bytes_read, ids_written;
esInitialize();
esCountCommonTicket(&common_count);
esCountPersonalizedTicket(&personalized_count);
NcmRightsId common_rights_ids[common_count], personalized_rights_ids[personalized_count];
esListCommonTicket(&ids_written, common_rights_ids, sizeof(common_rights_ids));
esListPersonalizedTicket(&ids_written, personalized_rights_ids, sizeof(personalized_rights_ids));
esExit();
if ((common_count == 0) && (personalized_count == 0))
return;
/*
catalog all currently installed rights ids
since we are crawling the whole save file, we might accidentally find previously deleted tickets
this would be fine, except we have to match the exact list so we don't stop too early
*/
char titlekey_block[0x100], buffer[TITLEKEY_BUFFER_SIZE], rights_id_string[0x21], titlekey_string[0x21];
std::set<std::string> rights_ids;
for (size_t i = 0; i < common_count; i++) {
for (size_t j = 0; j < 0x10; j++) {
sprintf(&rights_id_string[j*2], "%02x", common_rights_ids[i].c[j]);
}
rights_ids.insert(rights_id_string);
}
for (size_t i = 0; i < personalized_count; i++) {
for (size_t j = 0; j < 0x10; j++) {
sprintf(&rights_id_string[j*2], "%02x", personalized_rights_ids[i].c[j]);
}
rights_ids.insert(rights_id_string);
}
// get extended eticket RSA key from PRODINFO
u8 eticket_data[0x244] = {};
@ -522,8 +584,8 @@ void KeyCollection::get_titlekeys() {
byte_vector(eticket_data + 4, eticket_data + 0x14)
);
// public exponent must be 65537 == 0x10001
if (!(dec_keypair[0x201] == 1) || !(dec_keypair[0x203] == 1))
// public exponent must be 65537 == 0x10001 (big endian)
if (!(dec_keypair[0x200] == 0) || !(dec_keypair[0x201] == 1) || !(dec_keypair[0x202] == 0) || !(dec_keypair[0x203] == 1))
return;
u8 *D = &dec_keypair[0], *N = &dec_keypair[0x100], *E = &dec_keypair[0x200];
@ -531,92 +593,101 @@ void KeyCollection::get_titlekeys() {
if (!test_key_pair(E, D, N))
return;
FsFileSystem save_fs;
Result rc;
FATFS fs;
FRESULT fr;
FIL save_file;
// map of all found rights ids and corresponding titlekeys
std::unordered_map<std::string, std::string> titlekeys;
// todo: try reading as block device to not have to crash ES!
for(size_t attempts = 0; attempts < 100; attempts++) {
pmshellTerminateProcessByTitleId(ES_TID);
fsOpenBisStorage(&storage, 31);
if (f_mount(&fs, "", 1) || f_chdir("/save")) return;
if (f_open(&save_file, "80000000000000e1", FA_READ | FA_OPEN_EXISTING)) return;
while ((common_count != 0) && (titlekeys_dumped < common_count)) {
fr = f_read(&save_file, buffer, TITLEKEY_BUFFER_SIZE, &bytes_read);
if (fr || (bytes_read == 0)) break;
for (size_t i = 0; i < bytes_read; i += 0x4000) {
for (size_t j = i; j < i + 0x4000; j += 0x400) {
if (*reinterpret_cast<u32 *>(&buffer[j]) == 0x10004) {
for (size_t k = 0; k < 0x10; k++)
sprintf(&rights_id_string[k*2], "%02x", buffer[j + 0x2a0 + k]);
if (R_SUCCEEDED(rc = fsMount_SystemSaveData(&save_fs, ES_COMMON_SAVE_ID)))
break;
// skip if rights id found but not reported by es
if (rights_ids.find(rights_id_string) == rights_ids.end())
continue;
// skip if rights id already in map
if (titlekeys.find(rights_id_string) != titlekeys.end())
continue;
for (size_t k = 0; k < 0x10; k++)
sprintf(&titlekey_string[k*2], "%02x", buffer[j + 0x180 + k]);
titlekeys[rights_id_string] = titlekey_string;
titlekeys_dumped++;
} else {
break;
}
}
}
}
if (R_FAILED(rc))
return;
if (fsdevMountDevice("save", save_fs) == -1)
return;
char ca_issuer[4], titlekey_block[0x100], rights_id[0x10], write_string[0x20];
std::ofstream titlekey_file("/switch/title.keys");
std::ifstream common_ticket_bin("save:/ticket.bin", std::ios::binary);
for (size_t i = 0; ; i += 0x400) {
common_ticket_bin.seekg(i + 0x140);
common_ticket_bin.read(ca_issuer, 4);
if (!std::equal(ca_issuer, ca_issuer + 4, "Root"))
break;
common_ticket_bin.seekg(i + 0x180);
common_ticket_bin.read(titlekey_block, 0x10);
common_ticket_bin.seekg(i + 0x2a0);
common_ticket_bin.read(rights_id, 0x10);
for (size_t j = 0; j < 0x10; j++)
sprintf(&write_string[j*2], "%02x", rights_id[j]);
titlekey_file.write(write_string, 0x20);
titlekey_file.write(" = ", 3);
for (size_t j = 0; j < 0x10; j++)
sprintf(&write_string[j*2], "%02x", titlekey_block[j]);
titlekey_file.write(write_string, 0x20);
titlekey_file.write("\n", 1);
titlekeys_dumped++;
}
fsdevUnmountDevice("save");
if (R_FAILED(fsMount_SystemSaveData(&save_fs, ES_PERSONALIZED_SAVE_ID)) ||
(fsdevMountDevice("save", save_fs) == -1))
return;
std::ifstream personalized_ticket_bin("save:/ticket.bin", std::ios::binary);
f_close(&save_file);
u8 M[0x100];
for (size_t i = 0; ; i += 0x400) {
personalized_ticket_bin.seekg(i + 0x140);
personalized_ticket_bin.read(ca_issuer, 4);
if (!std::equal(ca_issuer, ca_issuer + 4, "Root"))
break;
personalized_ticket_bin.seekg(i + 0x180);
personalized_ticket_bin.read(titlekey_block, 0x100);
splUserExpMod(titlekey_block, N, D, 0x100, M);
// decrypts the titlekey from personalized ticket
u8 salt[0x20], db[0xdf];
mgf1(M + 0x21, 0xdf, salt, 0x20);
for (size_t j = 0; j < 0x20; j++)
salt[j] ^= M[j + 1];
if (f_open(&save_file, "80000000000000e2", FA_READ | FA_OPEN_EXISTING)) return;
while ((personalized_count != 0) && (titlekeys_dumped < common_count + personalized_count)) {
fr = f_read(&save_file, buffer, TITLEKEY_BUFFER_SIZE, &bytes_read);
if (fr || (bytes_read == 0)) break;
for (size_t i = 0; i < bytes_read; i += 0x4000) {
for (size_t j = i; j < i + 0x4000; j += 0x400) {
if (*reinterpret_cast<u32 *>(&buffer[j]) == 0x10004) {
std::copy(buffer + j + 0x180, buffer + j + 0x280, titlekey_block);
splUserExpMod(titlekey_block, N, D, 0x100, M);
mgf1(salt, 0x20, db, 0xdf);
for (size_t j = 0; j < 0xdf; j++)
db[j] ^= M[j + 0x21];
// decrypts the titlekey from personalized ticket
u8 salt[0x20], db[0xdf];
mgf1(M + 0x21, 0xdf, salt, 0x20);
for (size_t k = 0; k < 0x20; k++)
salt[k] ^= M[k + 1];
// verify it starts with hash of null string
if (!std::equal(db, db + 0x20, null_hash))
continue;
mgf1(salt, 0x20, db, 0xdf);
for (size_t k = 0; k < 0xdf; k++)
db[k] ^= M[k + 0x21];
personalized_ticket_bin.seekg(i + 0x2a0);
personalized_ticket_bin.read(rights_id, 0x10);
for (size_t j = 0; j < 0x10; j++)
sprintf(&write_string[j*2], "%02x", rights_id[j]);
titlekey_file.write(write_string, 0x20);
titlekey_file.write(" = ", 3);
for (size_t j = 0; j < 0x10; j++)
sprintf(&write_string[j*2], "%02x", db[j + 0xcf]);
titlekey_file.write(write_string, 0x20);
titlekey_file.write("\n", 1);
titlekeys_dumped++;
// verify it starts with hash of null string
if (!std::equal(db, db + 0x20, null_hash))
continue;
for (size_t k = 0; k < 0x10; k++)
sprintf(&rights_id_string[k*2], "%02x", buffer[j + 0x2a0 + k]);
// skip if rights id found but not reported by es
if (rights_ids.find(rights_id_string) == rights_ids.end())
continue;
// skip if rights id already in map
if (titlekeys.find(rights_id_string) != titlekeys.end())
continue;
for (size_t k = 0; k < 0x10; k++)
sprintf(&titlekey_string[k*2], "%02x", db[k + 0xcf]);
titlekeys[rights_id_string] = titlekey_string;
titlekeys_dumped++;
} else {
break;
}
}
}
}
fsdevUnmountDevice("save");
f_close(&save_file);
fsStorageClose(&storage);
if (titlekeys.empty())
return;
FILE *titlekey_file = fopen("/switch/title.keys", "wb");
if (!titlekey_file) return;
for (auto k : titlekeys)
fprintf(titlekey_file, "%s = %s\n", k.first.c_str(), k.second.c_str());
fclose(titlekey_file);
}
void KeyCollection::mgf1(const u8 *data, size_t data_length, u8 *mask, size_t mask_length) {