Compare commits
7 Commits
Author | SHA1 | Date | |
---|---|---|---|
867d2d6520 | |||
986468a32b | |||
9deeefee9d | |||
d4b4f868b1 | |||
352c39ae8a | |||
49c6fd8dc7 | |||
4d460e9509 |
2
Makefile
2
Makefile
@ -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
|
||||||
|
@ -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`:
|
||||||
```
|
```
|
||||||
|
22
changelog.md
22
changelog.md
@ -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
|
||||||
|
@ -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);
|
||||||
|
@ -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);
|
||||||
|
@ -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);
|
||||||
|
@ -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;
|
||||||
};
|
};
|
@ -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;
|
||||||
|
@ -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];
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
@ -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;
|
||||||
|
@ -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.
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user