7 Commits
v1.1 ... v1.2.2

13 changed files with 228 additions and 90 deletions

View File

@ -32,7 +32,7 @@ include $(DEVKITPRO)/libnx/switch_rules
#--------------------------------------------------------------------------------- #---------------------------------------------------------------------------------
APP_TITLE := Lockpick APP_TITLE := Lockpick
APP_AUTHOR := shchmue APP_AUTHOR := shchmue
APP_VERSION := 1.1 APP_VERSION := 1.2.2
TARGET := $(subst $e ,_,$(notdir $(APP_TITLE))) TARGET := $(subst $e ,_,$(notdir $(APP_TITLE)))
BUILD := build BUILD := build

View File

@ -2,13 +2,15 @@ Lockpick
= =
Lockpick is a ground-up C++17 rewrite of homebrew key derivation software, namely [kezplez-nx](https://github.com/tesnos/kezplez-nx). It also dumps titlekeys. This will dump all keys through `*_key_05` on firmwares below `6.2.0` and through `*_key_06` on `6.2.0`. Lockpick is a ground-up C++17 rewrite of homebrew key derivation software, namely [kezplez-nx](https://github.com/tesnos/kezplez-nx). It also dumps titlekeys. This will dump all keys through `*_key_05` on firmwares below `6.2.0` and through `*_key_06` on `6.2.0`.
Due to key generation changes introduced in `7.0.0`, Lockpick is not able to dump keys ending in 07 at all. Furthermore, unfortunately the public method to dump `tsec_root_key` is only available on firmware `6.2.0` so `7.x` consoles can only dump through keys ending in 05.
What this software does differently What this software does differently
= =
* Dumps `titlekeys` and SD seed * Dumps `titlekeys` and SD seed
* Dumps all keys through `6.2.0` * Dumps all keys through `6.2.0`
* Uses the superfast `xxHash` instead of `sha256` when searching exefs for keys for a ~5x speed improvement * Uses the superfast `xxHash` instead of `sha256` when searching exefs for keys for a ~5x speed improvement
* Gets all possible keys from running process memory - this means no need to decrypt `Package2` at all, let alone decompress `KIP`s * Gets all possible keys from running process memory - this means no need to decrypt `Package2` at all, let alone decompress `KIP`s
* Gets `header_key` without `tsec`, `sbk`, `master_key_00` or `aes` sources - which may or may not be the same way `ChoiDujourNX` does it :eyes: (and I'm gonna issue a challenge to homebrew title installers to implement similar code so you don't need your users to use separate software like this :stuck_out_tongue_winking_eye: it's up to you to figure out if the same can be done for `key_area_keys` if needed) * Gets bis keys and `header_key` without `tsec`, `sbk`, `master_key_00` or `aes` sources. Shoutout to exelix11 for using this method in [SwitchThemeInjector](https://github.com/exelix11/SwitchThemeInjector)! Homebrew devs should be doing this instead of requiring users to provide key files!
Usage Usage
= =
@ -34,7 +36,7 @@ Notes
Building Building
= =
Release built with `libnx v1.6.0`. Release built with `libnx v2.0.0` but still builds and runs with `v1.6.0`.
Uses `freetype` which comes with `switch-portlibs` via `devkitPro pacman`: Uses `freetype` which comes with `switch-portlibs` via `devkitPro pacman`:
``` ```

View File

@ -1,4 +1,26 @@
# Changelog # Changelog
## Version 1.2.2
* Do not overwrite existing keyfile that contains master_key_07
* Read eticket_rsa_kek from existing keyfile in case user is only running this for titlekeys
* Create /switch folder if needed
## Version 1.2.1
* Generate bis keys without master keys
* Update file size check to support Hekate v4.8 TSEC dump
* Fixed prod.keys alphabetization error
* Fixed build warning for ff.c
* Added in-app disclaimer about which keys can be dumped
## Version 1.2
* Update for libnx v2.0.0 compatibility and still runs when built with v1.6.0
* The binary got even smaller!
* Accelerate finding FS keys
* No longer find BIS sources as they're hardcoded (whoops)
* Find all keys on first pass hashing FS instead of hashing the whole thing from the beginning repeatedly (__*whoops*__)
## Version 1.1.1
* No longer try to dump SD seed and ES keys on 1.0.0 as they're not available until 2.0.0
## Version 1.1 ## Version 1.1
* Changed titlekey dump methodology * Changed titlekey dump methodology
* No longer crashes sysmodule, reboot no longer needed * No longer crashes sysmodule, reboot no longer needed

View File

@ -34,8 +34,16 @@
#include "sha256.h" #include "sha256.h"
#ifdef RGBX8
#define LIBNX_200
#endif
namespace Common { namespace Common {
static u32 framebuf_width = 0; static u32 framebuf_width = 0;
#ifdef LIBNX_200
static Framebuffer fb;
static u32 stride;
#endif
static u32 *framebuf; static u32 *framebuf;
// FreeType vars // FreeType vars
static FT_Library library; static FT_Library library;
@ -111,7 +119,9 @@ namespace Common {
PlFontData font; PlFontData font;
#ifndef LIBNX_200
consoleInit(NULL); consoleInit(NULL);
#endif
plGetSharedFontByType(&font, PlSharedFontType_Standard); plGetSharedFontByType(&font, PlSharedFontType_Standard);
@ -119,11 +129,21 @@ namespace Common {
FT_New_Memory_Face(library, static_cast<FT_Byte *>(font.address), font.size, 0, &face); FT_New_Memory_Face(library, static_cast<FT_Byte *>(font.address), font.size, 0, &face);
FT_Set_Char_Size(face, 0, 6*64, 300, 300); FT_Set_Char_Size(face, 0, 6*64, 300, 300);
gfxSetMode(GfxMode_LinearDouble); // todo: update for nwindow/framebuffer #ifdef LIBNX_200
framebufferCreate(&fb, nwindowGetDefault(), FB_WIDTH, FB_HEIGHT, PIXEL_FORMAT_RGBA_8888, 2);
framebufferMakeLinear(&fb);
framebuf = (u32 *)framebufferBegin(&fb, &stride);
framebuf_width = stride / sizeof(u32);
memset(framebuf, 0, stride*FB_HEIGHT);
framebufferEnd(&fb);
#else
gfxSetMode(GfxMode_LinearDouble);
framebuf = (u32 *)gfxGetFramebuffer(&framebuf_width, NULL); framebuf = (u32 *)gfxGetFramebuffer(&framebuf_width, NULL);
memset(framebuf, 0, gfxGetFramebufferSize()); memset(framebuf, 0, gfxGetFramebufferSize());
#endif
draw_text(0x10, 0x020, YELLOW, "Lockpick! by shchmue"); draw_text(0x010, 0x020, YELLOW, "Lockpick! by shchmue");
draw_text(0x190, 0x020, YELLOW, "Note: This can only dump keys 00-05 (or 00-06 on 6.2.0)");
draw_text(0x190, 0x040, YELLOW, "Use Lockpick_RCM for newer keys on firmware 7.0.0+!");
draw_set_rect(814, 452 + 42 * 0, 450, 42, FLAG_RED); draw_set_rect(814, 452 + 42 * 0, 450, 42, FLAG_RED);
draw_set_rect(814, 452 + 42 * 1, 450, 42, FLAG_ORANGE); draw_set_rect(814, 452 + 42 * 1, 450, 42, FLAG_ORANGE);
@ -148,7 +168,7 @@ namespace Common {
draw_text(0x10, 0x0e0, CYAN, "Saving keys to keyfile..."); draw_text(0x10, 0x0e0, CYAN, "Saving keys to keyfile...");
draw_text(0x10, 0x110, CYAN, "Total time elapsed:"); draw_text(0x10, 0x110, CYAN, "Total time elapsed:");
consoleUpdate(NULL); update_display();
} }
void get_tegra_keys(Key &sbk, Key &tsec, Key &tsec_root) { void get_tegra_keys(Key &sbk, Key &tsec, Key &tsec_root) {
@ -167,7 +187,7 @@ namespace Common {
sbk = Key("secure_boot_key", 0x10, temp_key); sbk = Key("secure_boot_key", 0x10, temp_key);
fclose(fuse_file); fclose(fuse_file);
} }
else if (!tsec.found() && (p.file_size() == 0x30) && else if (!tsec.found() && (p.file_size() == 0x20 || p.file_size() == 0x30) &&
(std::string("tsec").compare(std::string(p.path().filename()).substr(0, 4)) == 0)) (std::string("tsec").compare(std::string(p.path().filename()).substr(0, 4)) == 0))
{ {
FILE *tsec_file = fopen(p.path().c_str(), "rb"); FILE *tsec_file = fopen(p.path().c_str(), "rb");
@ -206,17 +226,29 @@ namespace Common {
u64 kDown = hidKeysDown(CONTROLLER_P1_AUTO); u64 kDown = hidKeysDown(CONTROLLER_P1_AUTO);
if (kDown & KEY_PLUS) break; if (kDown & KEY_PLUS) break;
consoleUpdate(NULL); update_display();
} }
#ifdef LIBNX_200
framebufferClose(&fb);
#else
consoleExit(NULL);
#endif
FT_Done_Face(face); FT_Done_Face(face);
FT_Done_FreeType(library); FT_Done_FreeType(library);
consoleExit(NULL);
appletUnlockExit(); appletUnlockExit();
} }
void update_display() {
#ifdef LIBNX_200
framebufferBegin(&fb, &stride);
framebufferEnd(&fb);
#else
consoleUpdate(NULL);
#endif
}
void sha256(const u8 *data, u8 *hash, size_t length) { void sha256(const u8 *data, u8 *hash, size_t length) {
struct sha256_state ctx; struct sha256_state ctx;
sha256_init(&ctx); sha256_init(&ctx);

View File

@ -24,6 +24,9 @@
#include <switch/types.h> #include <switch/types.h>
#define FB_WIDTH 1280
#define FB_HEIGHT 720
#define GREEN RGBA8_MAXALPHA(0, 0xff, 0) #define GREEN RGBA8_MAXALPHA(0, 0xff, 0)
#define RED RGBA8_MAXALPHA(0xff, 0, 0) #define RED RGBA8_MAXALPHA(0xff, 0, 0)
#define CYAN RGBA8_MAXALPHA(0, 0xff, 0xff) #define CYAN RGBA8_MAXALPHA(0, 0xff, 0xff)
@ -61,6 +64,9 @@ namespace Common {
// print exit // print exit
void wait_to_exit(); void wait_to_exit();
// refresh display
void update_display();
void sha256(const u8 *data, u8 *hash, size_t length); void sha256(const u8 *data, u8 *hash, size_t length);
// reads "<keyname> = <hexkey>" and returns byte vector // reads "<keyname> = <hexkey>" and returns byte vector
byte_vector key_string_to_byte_vector(std::string key_string); byte_vector key_string_to_byte_vector(std::string key_string);

View File

@ -124,7 +124,7 @@ byte_vector Key::cmac(byte_vector data) {
return dest; return dest;
} }
void Key::find_key(const byte_vector &buffer) { void Key::find_key(const byte_vector &buffer, size_t start) {
if ((buffer.size() == 0) || (found())) if ((buffer.size() == 0) || (found()))
return; return;
@ -136,10 +136,11 @@ void Key::find_key(const byte_vector &buffer) {
return; return;
std::copy(buffer.begin(), buffer.begin() + length, std::back_inserter(key)); std::copy(buffer.begin(), buffer.begin() + length, std::back_inserter(key));
is_found = true; is_found = true;
return;
} }
// hash every length-sized byte chunk in buffer until it matches member hash // hash every length-sized byte chunk in buffer until it matches member hash
for (size_t i = 0; i < buffer.size() - length; i++) { for (size_t i = start; i < buffer.size() - length; i++) {
if (xx_hash == XXHash64::hash(buffer.data() + i, length, 0)) { if (xx_hash == XXHash64::hash(buffer.data() + i, length, 0)) {
// double-check sha256 since xxhash64 isn't as collision-safe // double-check sha256 since xxhash64 isn't as collision-safe
Common::sha256(buffer.data() + i, temp_hash, length); Common::sha256(buffer.data() + i, temp_hash, length);

View File

@ -54,18 +54,17 @@ public:
// return CMAC of data // return CMAC of data
byte_vector cmac(byte_vector data); byte_vector cmac(byte_vector data);
// find key in buffer by hash, optionally specify start offset // find key in buffer by hash, optionally specify start offset
void find_key(const byte_vector &buffer); void find_key(const byte_vector &buffer, size_t start = 0);
// get key encryption key // get key encryption key
byte_vector generate_kek(Key &master_key, const Key &kek_seed, const Key &key_seed); byte_vector generate_kek(Key &master_key, const Key &kek_seed, const Key &key_seed);
byte_vector key; byte_vector key;
private:
std::string name; std::string name;
u64 xx_hash; u64 xx_hash;
byte_vector hash; byte_vector hash;
u8 length; u8 length;
bool is_found = false; bool is_found = false;
private:
static size_t saved_key_count; static size_t saved_key_count;
}; };

View File

@ -21,10 +21,11 @@
#include <algorithm> #include <algorithm>
#include <chrono> #include <chrono>
#include <filesystem>
#include <functional> #include <functional>
#include <set>
#include <string> #include <string>
#include <unordered_map> #include <unordered_map>
#include <unordered_set>
#include <stdio.h> #include <stdio.h>
@ -158,10 +159,10 @@ KeyCollection::KeyCollection() {
sd_card_kek_source = {"sd_card_kek_source", 0xc408d710a3b821eb, { sd_card_kek_source = {"sd_card_kek_source", 0xc408d710a3b821eb, {
0x6B, 0x2E, 0xD8, 0x77, 0xC2, 0xC5, 0x23, 0x34, 0xAC, 0x51, 0xE5, 0x9A, 0xBF, 0xA7, 0xEC, 0x45, 0x6B, 0x2E, 0xD8, 0x77, 0xC2, 0xC5, 0x23, 0x34, 0xAC, 0x51, 0xE5, 0x9A, 0xBF, 0xA7, 0xEC, 0x45,
0x7F, 0x4A, 0x7D, 0x01, 0xE4, 0x62, 0x91, 0xE9, 0xF2, 0xEA, 0xA4, 0x5F, 0x01, 0x1D, 0x24, 0xB7}, 0x10}; 0x7F, 0x4A, 0x7D, 0x01, 0xE4, 0x62, 0x91, 0xE9, 0xF2, 0xEA, 0xA4, 0x5F, 0x01, 0x1D, 0x24, 0xB7}, 0x10};
sd_card_nca_key_source = {"sd_card_nca_key_source", 0xbea347c9f8472947, { sd_card_nca_key_source = {"sd_card_nca_key_source", 0xb026106d9699fec0, { // xxhash of first 0x10 bytes
0x2E, 0x75, 0x1C, 0xEC, 0xF7, 0xD9, 0x3A, 0x2B, 0x95, 0x7B, 0xD5, 0xFF, 0xCB, 0x08, 0x2F, 0xD0, 0x2E, 0x75, 0x1C, 0xEC, 0xF7, 0xD9, 0x3A, 0x2B, 0x95, 0x7B, 0xD5, 0xFF, 0xCB, 0x08, 0x2F, 0xD0,
0x38, 0xCC, 0x28, 0x53, 0x21, 0x9D, 0xD3, 0x09, 0x2C, 0x6D, 0xAB, 0x98, 0x38, 0xF5, 0xA7, 0xCC}, 0x20}; 0x38, 0xCC, 0x28, 0x53, 0x21, 0x9D, 0xD3, 0x09, 0x2C, 0x6D, 0xAB, 0x98, 0x38, 0xF5, 0xA7, 0xCC}, 0x20};
sd_card_save_key_source = {"sd_card_save_key_source", 0xf87fe8c3688c3022, { sd_card_save_key_source = {"sd_card_save_key_source", 0x9697ba2fec3d3ed1, { // xxhash of first 0x10 bytes
0xD4, 0x82, 0x74, 0x35, 0x63, 0xD3, 0xEA, 0x5D, 0xCD, 0xC3, 0xB7, 0x4E, 0x97, 0xC9, 0xAC, 0x8A, 0xD4, 0x82, 0x74, 0x35, 0x63, 0xD3, 0xEA, 0x5D, 0xCD, 0xC3, 0xB7, 0x4E, 0x97, 0xC9, 0xAC, 0x8A,
0x34, 0x21, 0x64, 0xFA, 0x04, 0x1A, 0x1D, 0xC8, 0x0F, 0x17, 0xF6, 0xD3, 0x1E, 0x4B, 0xC0, 0x1C}, 0x20}; 0x34, 0x21, 0x64, 0xFA, 0x04, 0x1A, 0x1D, 0xC8, 0x0F, 0x17, 0xF6, 0xD3, 0x1E, 0x4B, 0xC0, 0x1C}, 0x20};
@ -195,10 +196,6 @@ KeyCollection::KeyCollection() {
}; };
fs_rodata_keys = { fs_rodata_keys = {
&bis_kek_source,
&bis_key_source_00,
&bis_key_source_01,
&bis_key_source_02,
&header_kek_source, &header_kek_source,
&key_area_key_application_source, &key_area_key_application_source,
&key_area_key_ocean_source, &key_area_key_ocean_source,
@ -216,25 +213,10 @@ KeyCollection::KeyCollection() {
}); });
} }
package1ldr_keys = {
&keyblob_mac_key_source,
&master_key_source,
&per_console_key_source
};
ssl_keys = { ssl_keys = {
&ssl_rsa_kek_source_x, &ssl_rsa_kek_source_x,
&ssl_rsa_kek_source_y &ssl_rsa_kek_source_y
}; };
tz_keys = {
&aes_kek_generation_source,
&package2_key_source,
&titlekek_source,
&retail_specific_aes_key_source,
&aes_kek_seed_01,
&aes_kek_seed_03
};
}; };
void KeyCollection::get_keys() { void KeyCollection::get_keys() {
@ -247,8 +229,8 @@ void KeyCollection::get_keys() {
} else { } else {
Common::draw_text(0x010, 0x60, RED, "Get Tegra keys..."); Common::draw_text(0x010, 0x60, RED, "Get Tegra keys...");
Common::draw_text(0x190, 0x60, RED, "Failed"); Common::draw_text(0x190, 0x60, RED, "Failed");
Common::draw_text(0x190, 0x20, RED, "Warning: Saving limited keyset."); Common::draw_text(0x2a0, 0x60, RED, "Warning: Saving limited keyset.");
Common::draw_text(0x190, 0x40, RED, "Dump Tegra keys with payload and run again to get all keys."); Common::draw_text(0x2a0, 0x80, RED, "Dump TSEC and Fuses with Hekate.");
} }
profiler_time = profile(&KeyCollection::get_memory_keys, *this); profiler_time = profile(&KeyCollection::get_memory_keys, *this);
@ -260,8 +242,31 @@ void KeyCollection::get_keys() {
profiler_time = profile(&KeyCollection::derive_keys, *this); profiler_time = profile(&KeyCollection::derive_keys, *this);
Common::draw_text_with_time(0x10, 0x0c0, 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); // avoid crash on CFWs that don't use /switch folder
Common::draw_text_with_time(0x10, 0x0e0, GREEN, "Saving keys to keyfile...", profiler_time); if (!std::filesystem::exists("/switch"))
std::filesystem::create_directory("/switch");
// since Lockpick_RCM can dump newer keys, check for existing keyfile
bool Lockpick_RCM_file_found = false;
if (std::filesystem::exists("/switch/prod.keys")) {
FILE *key_file = fopen("/switch/prod.keys", "r");
char line[0x200];
while (fgets(line, sizeof(line), key_file)) {
if (strncmp("master_key_07", line, 13) == 0) {
Lockpick_RCM_file_found = true;
} else if (!eticket_rsa_kek.found() && (strncmp("eticket_rsa_kek", line, 15)) == 0) {
// grab eticket_rsa_kek from existing file to make sure we can dump titlekeys
eticket_rsa_kek = Key("eticket_rsa_kek", 0x10, Common::key_string_to_byte_vector(line));
}
}
fclose(key_file);
}
if (!Lockpick_RCM_file_found) {
profiler_time = profile(&KeyCollection::save_keys, *this);
Common::draw_text_with_time(0x10, 0x0e0, GREEN, "Saving keys to keyfile...", profiler_time);
} else {
Common::draw_text(0x10, 0x0e0, YELLOW, "Saving keys to keyfile...");
Common::draw_text(0x190, 0x0e0, YELLOW, "Newer keyfile found. Skipped overwriting keys");
}
total_time.stop(); total_time.stop();
Common::draw_line(0x8, 0xf0, 0x280, GREEN); Common::draw_line(0x8, 0xf0, 0x280, GREEN);
@ -270,16 +275,16 @@ void KeyCollection::get_keys() {
char keys_str[32]; char keys_str[32];
sprintf(keys_str, "Total keys found: %lu", Key::get_saved_key_count()); sprintf(keys_str, "Total keys found: %lu", Key::get_saved_key_count());
Common::draw_text(0x2a0, 0x110, 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(0x80, 0x140, YELLOW, "Keys saved to \"/switch/prod.keys\"!");
Common::draw_text(0x10, 0x170, CYAN, "Dumping titlekeys..."); Common::draw_text(0x10, 0x170, CYAN, "Dumping titlekeys...");
consoleUpdate(NULL); Common::update_display();
profiler_time = profile(&KeyCollection::get_titlekeys, *this); profiler_time = profile(&KeyCollection::get_titlekeys, *this);
Common::draw_text_with_time(0x10, 0x170, GREEN, "Dumping titlekeys...", profiler_time); Common::draw_text_with_time(0x10, 0x170, GREEN, "Dumping titlekeys...", profiler_time);
sprintf(keys_str, "Titlekeys found: %lu", titlekeys_dumped); sprintf(keys_str, "Titlekeys found: %lu", titlekeys_dumped);
Common::draw_text(0x2a0, 0x170, CYAN, keys_str); Common::draw_text(0x2a0, 0x170, CYAN, keys_str);
if (titlekeys_dumped > 0) if (titlekeys_dumped > 0)
Common::draw_text(0x80, 0x1a0, GREEN, "Titlekeys saved to \"/switch/title.keys\"!"); Common::draw_text(0x80, 0x1a0, YELLOW, "Titlekeys saved to \"/switch/title.keys\"!");
else else
Common::draw_text(0x80, 0x1a0, GREEN, "No titlekeys found. Either you've never played or installed a game or dump failed."); Common::draw_text(0x80, 0x1a0, GREEN, "No titlekeys found. Either you've never played or installed a game or dump failed.");
} }
@ -361,28 +366,59 @@ void KeyCollection::get_memory_keys() {
FSRodata.get_from_memory(FS_TID, SEG_RODATA); FSRodata.get_from_memory(FS_TID, SEG_RODATA);
FSData.get_from_memory(FS_TID, SEG_DATA); FSData.get_from_memory(FS_TID, SEG_DATA);
for (auto k : fs_rodata_keys) FSRodata.find_keys(fs_rodata_keys);
k->find_key(FSRodata.data);
header_key_source.find_key(FSData.data); size_t i = 0;
/*for ( ; i < FSData.data.size(); i++) {
// speeds things up but i'm not 100% sure this is always here
if (*reinterpret_cast<u128 *>(FSData.data.data() + i) == 0x10001)
break;
}*/
header_key_source.find_key(FSData.data, i);
SSLRodata.get_from_memory(SSL_TID, SEG_RODATA);
// using find_keys on these is actually slower
for (auto k : ssl_keys)
k->find_key(SSLRodata.data);
// firmware 1.0.0 doesn't have the ES keys
if (!kernelAbove200())
return;
ESRodata.get_from_memory(ES_TID, SEG_RODATA); ESRodata.get_from_memory(ES_TID, SEG_RODATA);
for (auto k : es_keys) for (auto k : es_keys)
k->find_key(ESRodata.data); k->find_key(ESRodata.data);
SSLRodata.get_from_memory(SSL_TID, SEG_RODATA);
for (auto k : ssl_keys)
k->find_key(SSLRodata.data);
} }
void KeyCollection::derive_keys() { void KeyCollection::derive_keys() {
header_key = {"header_key", 0x20, {}}; header_key = {"header_key", 0x20, {}};
if (header_kek_source.found() && header_key_source.found()) { if (header_kek_source.found() && header_key_source.found()) {
u8 tempheaderkek[0x10], tempheaderkey[0x20]; u8 tempheaderkek[0x10], tempheaderkey[0x20];
splCryptoInitialize();
splCryptoGenerateAesKek(header_kek_source.key.data(), 0, 0, tempheaderkek); splCryptoGenerateAesKek(header_kek_source.key.data(), 0, 0, tempheaderkek);
splCryptoGenerateAesKey(tempheaderkek, header_key_source.key.data(), tempheaderkey); splCryptoGenerateAesKey(tempheaderkek, header_key_source.key.data() + 0x00, tempheaderkey + 0x00);
splCryptoGenerateAesKey(tempheaderkek, header_key_source.key.data() + 0x10, tempheaderkey + 0x10); splCryptoGenerateAesKey(tempheaderkek, header_key_source.key.data() + 0x10, tempheaderkey + 0x10);
header_key = {"header_key", 0x20, byte_vector(&tempheaderkey[0], &tempheaderkey[0x20])}; header_key = {"header_key", 0x20, byte_vector(tempheaderkey, tempheaderkey + 0x20)};
splCryptoExit();
}
if (bis_key_source_00.found() && bis_key_source_01.found() && bis_key_source_02.found()) {
u8 tempbiskek[0x10], tempbiskey[0x20];
splFsInitialize();
splFsGenerateSpecificAesKey(bis_key_source_00.key.data() + 0x00, 0, 0, tempbiskey + 0x00);
splFsGenerateSpecificAesKey(bis_key_source_00.key.data() + 0x10, 0, 0, tempbiskey + 0x10);
bis_key.push_back(Key {"bis_key_00", 0x20, byte_vector(tempbiskey, tempbiskey + 0x20)});
splFsExit();
splCryptoInitialize();
splCryptoGenerateAesKek(bis_kek_source.key.data(), 0, 1, tempbiskek);
splCryptoGenerateAesKey(tempbiskek, bis_key_source_01.key.data() + 0x00, tempbiskey + 0x00);
splCryptoGenerateAesKey(tempbiskek, bis_key_source_01.key.data() + 0x10, tempbiskey + 0x10);
bis_key.push_back(Key {"bis_key_01", 0x20, byte_vector(tempbiskey, tempbiskey + 0x20)});
splCryptoGenerateAesKey(tempbiskek, bis_key_source_02.key.data() + 0x00, tempbiskey + 0x00);
splCryptoGenerateAesKey(tempbiskek, bis_key_source_02.key.data() + 0x10, tempbiskey + 0x10);
bis_key.push_back(Key {"bis_key_02", 0x20, byte_vector(tempbiskey, tempbiskey + 0x20)});
bis_key.push_back(Key {"bis_key_03", 0x20, bis_key[2].key});
splCryptoExit();
} }
for (u8 i = 0; i < aes_kek_generation_source.key.size(); i++) { for (u8 i = 0; i < aes_kek_generation_source.key.size(); i++) {
@ -400,15 +436,6 @@ void KeyCollection::derive_keys() {
save_mac_key = Key {"save_mac_key", 0x10, kek.aes_decrypt_ecb(save_mac_key_source.key)}; save_mac_key = Key {"save_mac_key", 0x10, kek.aes_decrypt_ecb(save_mac_key_source.key)};
} }
if (device_key.found()) {
Key kek = {device_key.aes_decrypt_ecb(retail_specific_aes_key_source.key), 0x10};
bis_key.push_back(Key {"bis_key_00", 0x20, kek.aes_decrypt_ecb(bis_key_source_00.key)});
kek = Key {bis_kek_source.generate_kek(device_key, aes_kek_generation_source, aes_key_generation_source), 0x10};
bis_key.push_back(Key {"bis_key_01", 0x20, kek.aes_decrypt_ecb(bis_key_source_01.key)});
bis_key.push_back(Key {"bis_key_02", 0x20, kek.aes_decrypt_ecb(bis_key_source_02.key)});
bis_key.push_back(Key {"bis_key_03", 0x20, bis_key[2].key});\
}
char keynum[] = "00"; char keynum[] = "00";
for (u8 i = 0; i < master_key.size(); i++) { for (u8 i = 0; i < master_key.size(); i++) {
if (!master_key[i].found()) if (!master_key[i].found())
@ -435,6 +462,8 @@ void KeyCollection::derive_keys() {
u32 bytes_read, file_pos = 0; u32 bytes_read, file_pos = 0;
// dump sd seed // dump sd seed
if (!kernelAbove200())
return;
FILE *sd_private = fopen("/Nintendo/Contents/private", "rb"); FILE *sd_private = fopen("/Nintendo/Contents/private", "rb");
if (!sd_private) return; if (!sd_private) return;
fread(seed_vector, 0x10, 1, sd_private); fread(seed_vector, 0x10, 1, sd_private);
@ -445,8 +474,13 @@ void KeyCollection::derive_keys() {
FIL save_file; FIL save_file;
fsOpenBisStorage(&storage, 31); fsOpenBisStorage(&storage, 31);
if (f_mount(&fs, "", 1) || f_chdir("/save")) return; if (f_mount(&fs, "", 1) ||
if (f_open(&save_file, "8000000000000043", FA_READ | FA_OPEN_EXISTING)) return; f_chdir("/save") ||
f_open(&save_file, "8000000000000043", FA_READ | FA_OPEN_EXISTING))
{
fsStorageClose(&storage);
return;
}
for (;;) { for (;;) {
fr = f_read(&save_file, buffer, 0x10, &bytes_read); fr = f_read(&save_file, buffer, 0x10, &bytes_read);
@ -470,11 +504,11 @@ void KeyCollection::save_keys() {
aes_kek_generation_source.save_key(key_file); aes_kek_generation_source.save_key(key_file);
aes_key_generation_source.save_key(key_file); aes_key_generation_source.save_key(key_file);
bis_kek_source.save_key(key_file); bis_kek_source.save_key(key_file);
for (auto k : bis_key)
k.save_key(key_file);
bis_key_source_00.save_key(key_file); bis_key_source_00.save_key(key_file);
bis_key_source_01.save_key(key_file); bis_key_source_01.save_key(key_file);
bis_key_source_02.save_key(key_file); bis_key_source_02.save_key(key_file);
for (auto k : bis_key)
k.save_key(key_file);
device_key.save_key(key_file); device_key.save_key(key_file);
eticket_rsa_kek.save_key(key_file); eticket_rsa_kek.save_key(key_file);
for (auto k : es_keys) for (auto k : es_keys)
@ -519,11 +553,11 @@ void KeyCollection::save_keys() {
save_mac_kek_source.save_key(key_file); save_mac_kek_source.save_key(key_file);
save_mac_key.save_key(key_file); save_mac_key.save_key(key_file);
save_mac_key_source.save_key(key_file); save_mac_key_source.save_key(key_file);
sbk.save_key(key_file);
sd_card_kek_source.save_key(key_file); sd_card_kek_source.save_key(key_file);
sd_card_nca_key_source.save_key(key_file); sd_card_nca_key_source.save_key(key_file);
sd_card_save_key_source.save_key(key_file); sd_card_save_key_source.save_key(key_file);
sd_seed.save_key(key_file); sd_seed.save_key(key_file);
sbk.save_key(key_file);
ssl_rsa_kek.save_key(key_file); ssl_rsa_kek.save_key(key_file);
for (auto k : ssl_keys) for (auto k : ssl_keys)
k->save_key(key_file); k->save_key(key_file);
@ -549,7 +583,7 @@ void KeyCollection::get_titlekeys() {
esListCommonTicket(&ids_written, common_rights_ids, sizeof(common_rights_ids)); esListCommonTicket(&ids_written, common_rights_ids, sizeof(common_rights_ids));
esListPersonalizedTicket(&ids_written, personalized_rights_ids, sizeof(personalized_rights_ids)); esListPersonalizedTicket(&ids_written, personalized_rights_ids, sizeof(personalized_rights_ids));
esExit(); esExit();
if ((common_count == 0) && (personalized_count == 0)) if (common_count + personalized_count == 0)
return; return;
/* /*
@ -558,7 +592,7 @@ void KeyCollection::get_titlekeys() {
this would be fine, except we have to match the exact list so we don't stop too early 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]; char titlekey_block[0x100], buffer[TITLEKEY_BUFFER_SIZE], rights_id_string[0x21], titlekey_string[0x21];
std::set<std::string> rights_ids; std::unordered_set<std::string> rights_ids;
for (size_t i = 0; i < common_count; i++) { for (size_t i = 0; i < common_count; i++) {
for (size_t j = 0; j < 0x10; j++) { for (size_t j = 0; j < 0x10; j++) {
sprintf(&rights_id_string[j*2], "%02x", common_rights_ids[i].c[j]); sprintf(&rights_id_string[j*2], "%02x", common_rights_ids[i].c[j]);
@ -611,7 +645,7 @@ void KeyCollection::get_titlekeys() {
for (size_t k = 0; k < 0x10; k++) for (size_t k = 0; k < 0x10; k++)
sprintf(&rights_id_string[k*2], "%02x", buffer[j + 0x2a0 + k]); sprintf(&rights_id_string[k*2], "%02x", buffer[j + 0x2a0 + k]);
// skip if rights id found but not reported by es // skip if rights id not reported by es
if (rights_ids.find(rights_id_string) == rights_ids.end()) if (rights_ids.find(rights_id_string) == rights_ids.end())
continue; continue;
// skip if rights id already in map // skip if rights id already in map
@ -639,8 +673,18 @@ void KeyCollection::get_titlekeys() {
for (size_t i = 0; i < bytes_read; i += 0x4000) { for (size_t i = 0; i < bytes_read; i += 0x4000) {
for (size_t j = i; j < i + 0x4000; j += 0x400) { for (size_t j = i; j < i + 0x4000; j += 0x400) {
if (*reinterpret_cast<u32 *>(&buffer[j]) == 0x10004) { 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]);
// skip if rights id 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;
std::copy(buffer + j + 0x180, buffer + j + 0x280, titlekey_block); std::copy(buffer + j + 0x180, buffer + j + 0x280, titlekey_block);
splUserExpMod(titlekey_block, N, D, 0x100, M); splUserExpMod(titlekey_block, N, D, 0x100, M);
// decrypts the titlekey from personalized ticket // decrypts the titlekey from personalized ticket
@ -657,16 +701,6 @@ void KeyCollection::get_titlekeys() {
if (!std::equal(db, db + 0x20, null_hash)) if (!std::equal(db, db + 0x20, null_hash))
continue; 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++) for (size_t k = 0; k < 0x10; k++)
sprintf(&titlekey_string[k*2], "%02x", db[k + 0xcf]); sprintf(&titlekey_string[k*2], "%02x", db[k + 0xcf]);
titlekeys[rights_id_string] = titlekey_string; titlekeys[rights_id_string] = titlekey_string;

View File

@ -113,7 +113,7 @@ private:
titlekek; titlekek;
std::vector<Key *> std::vector<Key *>
es_keys, fs_rodata_keys, package1ldr_keys, ssl_keys, tz_keys; es_keys, fs_rodata_keys, ssl_keys;
// hash of empty string used to verify titlekeys for personalized tickets // hash of empty string used to verify titlekeys for personalized tickets
static const u8 null_hash[0x20]; static const u8 null_hash[0x20];

View File

@ -16,9 +16,15 @@
#include "KeyLocation.hpp" #include "KeyLocation.hpp"
#include "Common.hpp"
#include "xxhash64.h"
#include <algorithm>
#include <unordered_map>
#include <switch.h> #include <switch.h>
void KeyLocation::get_from_memory(u64 tid, u8 segMask) { void KeyLocation::get_from_memory(u64 tid, u8 seg_mask) {
Handle debug_handle = INVALID_HANDLE; Handle debug_handle = INVALID_HANDLE;
u64 d[8]; u64 d[8];
@ -62,10 +68,10 @@ void KeyLocation::get_from_memory(u64 tid, u8 segMask) {
{ {
svcQueryDebugProcessMemory(&mem_info, &page_info, debug_handle, addr); svcQueryDebugProcessMemory(&mem_info, &page_info, debug_handle, addr);
// weird code to allow for bitmasking segments // weird code to allow for bitmasking segments
if ((mem_info.perm & Perm_R) && if ((mem_info.perm & Perm_R) &&
((mem_info.type & 0xff) >= MemType_CodeStatic) && ((mem_info.type & 0xff) >= MemType_CodeStatic) &&
((mem_info.type & 0xff) < MemType_Heap) && ((mem_info.type & 0xff) < MemType_Heap) &&
((segment <<= 1) >> 1 & segMask) > 0) ((segment <<= 1) >> 1 & seg_mask) > 0)
{ {
data.resize(data.size() + mem_info.size); data.resize(data.size() + mem_info.size);
if(R_FAILED(svcReadDebugProcessMemory(data.data() + data.size() - mem_info.size, debug_handle, mem_info.addr, mem_info.size))) { if(R_FAILED(svcReadDebugProcessMemory(data.data() + data.size() - mem_info.size, debug_handle, mem_info.addr, mem_info.size))) {
@ -86,4 +92,38 @@ void KeyLocation::get_keyblobs() {
data.resize(0x200 * KNOWN_KEYBLOBS); data.resize(0x200 * KNOWN_KEYBLOBS);
fsStorageRead(&boot0, KEYBLOB_OFFSET, data.data(), data.size()); fsStorageRead(&boot0, KEYBLOB_OFFSET, data.data(), data.size());
fsStorageClose(&boot0); fsStorageClose(&boot0);
}
void KeyLocation::find_keys(std::vector<Key *> &keys) {
if (data.size() == 0)
return;
u8 temp_hash[0x20];
size_t key_indices_left = keys.size();
u64 hash = 0;
std::unordered_map<u64, size_t> hash_index;
for (size_t i = 0; i < keys.size(); i++)
hash_index[keys[i]->xx_hash] = i;
// hash every length-sized byte chunk in data until it matches a key hash
for (size_t i = 0; i < data.size() - 0x10; i++) {
hash = XXHash64::hash(data.data() + i, 0x10, 0);
auto search = hash_index.find(hash);
if (search == hash_index.end()) {
continue;
}
size_t key_index = hash_index[hash];
u8 key_length = keys[key_index]->length;
// double-check sha256 since xxhash64 isn't as collision-safe
Common::sha256(data.data() + i, temp_hash, key_length);
if (!std::equal(keys[key_index]->hash.begin(), keys[key_index]->hash.end(), temp_hash))
continue;
std::copy(data.begin() + i, data.begin() + i + key_length, std::back_inserter(keys[key_index]->key));
keys[key_index]->is_found = true;
key_indices_left--;
if (key_indices_left == 0)
return;
hash_index.erase(hash);
i += key_length - 1;
}
} }

View File

@ -16,6 +16,8 @@
#pragma once #pragma once
#include "Key.hpp"
#include <vector> #include <vector>
#include <switch/types.h> #include <switch/types.h>
@ -44,9 +46,11 @@ typedef std::vector<u8> byte_vector;
class KeyLocation { class KeyLocation {
public: public:
// get memory in requested segments from running title // get memory in requested segments from running title
void get_from_memory(u64 tid, u8 segMask); void get_from_memory(u64 tid, u8 seg_mask);
// get keyblobs from BOOT0 // get keyblobs from BOOT0
void get_keyblobs(); void get_keyblobs();
// locate keys in data
void find_keys(std::vector<Key *> &keys);
// data found by get functions // data found by get functions
byte_vector data; byte_vector data;

View File

@ -15,7 +15,7 @@
/ and optional writing functions as well. */ / and optional writing functions as well. */
#define FF_FS_MINIMIZE 2 #define FF_FS_MINIMIZE 1
/* This option defines minimization level to remove some basic API functions. /* This option defines minimization level to remove some basic API functions.
/ /
/ 0: Basic functions are fully enabled. / 0: Basic functions are fully enabled.

View File

@ -23,7 +23,6 @@ extern "C" void userAppInit()
{ {
plInitialize(); plInitialize();
pmdmntInitialize(); pmdmntInitialize();
splCryptoInitialize();
splInitialize(); splInitialize();
} }
@ -31,7 +30,6 @@ extern "C" void userAppExit()
{ {
plExit(); plExit();
pmdmntExit(); pmdmntExit();
splCryptoExit();
splExit(); splExit();
} }