orbital/tools/dumper/source/self_decrypter.c
Alexandro Sanchez Bach 94ed646bd0
Added PUPMGR spawn/exit code
Signed-off-by: Alexandro Sanchez Bach <alexandro@phi.nz>
2019-06-29 14:55:13 +02:00

610 lines
20 KiB
C

/**
* (c) 2017-2018 Alexandro Sanchez Bach.
* Released under MIT license. Read LICENSE for more details.
*
* Based in previous tools and research by: fail0verflow, flatz.
*/
#include "self_decrypter.h"
#include "ksdk.h"
#include "blob.h"
#include "debug.h"
/* debugging */
#define DEBUG_SELF 0
#define assert(cond) if (!(cond)) { \
dprintf("%s:%d: failed.\n", __FUNCTION__, __LINE__); \
goto error; \
}
#define kassert(cond) if (!(cond)) { \
kdprintf("%s:%d: failed.\n", __FUNCTION__, __LINE__); \
goto error; \
}
/* constants */
#define SELF_MAGIC 0x1D3D154F
#define SELF_VERSION 0x0
#define SELF_MODE 0x1
#define SELF_ENDIANNESS 0x1
#define SELF_AUTH_INFO_SIZE 0x88
#define SELF_KEY_SIZE 0x10
#define SELF_DIGEST_SIZE 0x20
#define SELF_SEGMENT_BLOCK_ALIGNMENT 0x10
/* fields */
#define SELF_PROPS_ORDERED(B) B( 0, 0)
#define SELF_PROPS_ENCRYPTED(B) B( 1, 1)
#define SELF_PROPS_SIGNED(B) B( 2, 2)
#define SELF_PROPS_COMPRESSED(B) B( 3, 3)
#define SELF_PROPS_WINDOW(B) B(10, 8)
#define SELF_PROPS_BLOCKED(B) B(11, 11)
#define SELF_PROPS_BLOCK_SIZE(B) B(15, 12)
#define SELF_PROPS_HAS_DIGESTS(B) B(16, 16)
#define SELF_PROPS_HAS_EXTENTS(B) B(17, 17)
#define SELF_PROPS_SEGMENT_INDEX(B) B(31, 20)
typedef struct self_block_extent_t {
uint32_t offset;
uint32_t size;
} self_block_extent_t;
typedef struct self_block_info_t {
uint32_t size;
uint16_t index;
struct self_block_extent_t extent;
uint8_t digest[SELF_DIGEST_SIZE];
} self_block_info_t;
/* debug */
void trace_self(self_t *self)
{
#define BOOL(x) ((x) ? "true" : "false")
int i;
self_entry_t *entry;
dprintf("self:\n");
dprintf(" file-path: %s\n", self->file_path);
dprintf(" file-size: 0x%llX bytes\n", self->file_size);
dprintf(" header:\n");
dprintf(" magic: 0x%08X\n", self->header.magic);
dprintf(" version: 0x%02X\n", self->header.version);
dprintf(" mode: 0x%02X\n", self->header.mode);
dprintf(" endian: %s (0x%02X)\n",
self->header.endian == 1 ? "little-endian" : (
self->header.endian == 2 ? "big-endian" : "???"),
self->header.endian);
dprintf(" attr: 0x%02X\n", self->header.attr);
dprintf(" header-size: 0x%llX bytes\n", self->header.header_size);
dprintf(" meta-size: 0x%llX bytes\n", self->header.meta_size);
dprintf(" file-size: 0x%llX bytes\n", self->header.file_size);
dprintf(" num-entries: 0x%X\n", self->header.num_entries);
dprintf(" entries:\n");
for (i = 0; i < self->header.num_entries; i++) {
entry = &self->entries[i];
dprintf(" [%d]\n", i);
dprintf(" props: 0x%08X\n", entry->props);
dprintf(" ordered: %s\n", BOOL(EXTRACT(entry->props, SELF_PROPS_ORDERED)));
dprintf(" encrypted: %s\n", BOOL(EXTRACT(entry->props, SELF_PROPS_ENCRYPTED)));
dprintf(" signed: %s\n", BOOL(EXTRACT(entry->props, SELF_PROPS_SIGNED)));
dprintf(" compressed: %s\n", BOOL(EXTRACT(entry->props, SELF_PROPS_COMPRESSED)));
if (EXTRACT(entry->props, SELF_PROPS_COMPRESSED))
dprintf(" window: 0x%llX\n",
(1 << EXTRACT(entry->props, SELF_PROPS_WINDOW)) - 1);
dprintf(" has-blocks: %s\n", BOOL(EXTRACT(entry->props, SELF_PROPS_BLOCKED)));
if (EXTRACT(entry->props, SELF_PROPS_BLOCKED))
dprintf(" block-size: 0x%llX\n",
(1 << (EXTRACT(entry->props, SELF_PROPS_BLOCK_SIZE) + 12)));
dprintf(" has-digests: %s\n", BOOL(EXTRACT(entry->props, SELF_PROPS_HAS_DIGESTS)));
dprintf(" has-extents: %s\n", BOOL(EXTRACT(entry->props, SELF_PROPS_HAS_EXTENTS)));
dprintf(" segment-index: %d\n", EXTRACT(entry->props, SELF_PROPS_SEGMENT_INDEX));
dprintf(" offset: 0x%llX\n", entry->offset);
dprintf(" filesz: 0x%llX bytes\n", entry->filesz);
dprintf(" memsz: 0x%llX bytes\n", entry->memsz);
}
#undef BOOL
}
/* kernel */
#define SELF_MAX_CONTEXTS 4
typedef struct self_kdecrypt_segment_args_t {
unsigned int segment_idx;
unsigned int is_block_table;
uint8_t *segment_data_user;
size_t segment_data_user_size;
} self_kdecrypt_segment_args_t;
typedef struct self_kdecrypt_block_args_t {
struct self_block_info_t *block;
unsigned int segment_idx;
uint8_t *blob_data;
size_t blob_size;
} self_kdecrypt_block_args_t;
typedef struct self_kmethod_uap_t {
void *kmethod;
self_t *self;
void *args;
} self_kmethod_uap_t;
int self_kacquire_context(
struct thread *td, struct self_kmethod_uap_t *uap)
{
return 0;
}
int self_krelease_context(
struct thread *td, struct self_kmethod_uap_t *uap)
{
int ctx_id;
self_t *self = uap->self;
ctx_id = self->ctx_id;
if (0 <= ctx_id && ctx_id <= 3) {
self_ctx_status[ctx_id] = 3;
self->ctx_id = -1;
}
return 0;
}
int self_kverify_header(
struct thread *td, struct self_kmethod_uap_t *uap)
{
int ret, ctx_id;
char payload[0x80];
void* header_data = NULL;
uint64_t header_data_size;
uint64_t header_data_mapped = NULL;
uint64_t header_data_mapdesc = NULL;
void* auth_info = NULL;
uint64_t auth_info_size;
uint64_t auth_info_mapped = NULL;
uint64_t auth_info_mapdesc = NULL;
self_t *self = uap->self;
/* acquire context */
kdprintf("Waiting for free context...\n");
if (self->ctx_id == -1) {
ctx_id = 0;
while (self_ctx_status[ctx_id] != 3) {
ctx_id = (ctx_id + 1) % SELF_MAX_CONTEXTS;
}
kdprintf("Available context ID: %d\n", ctx_id);
self_ctx_status[ctx_id] = 1;
self->ctx_id = ctx_id;
}
if (self->svc_id == -1) {
self->svc_id = *sceSblAuthMgrModuleId;
}
kassert(self->ctx_id >= 0);
kassert(self->ctx_id <= 3);
self->ctx = &self_contexts[self->ctx_id];
_sceSblAuthMgrSmFinalize(self->ctx);
/* allocate memory for command */
header_data_size = ALIGN_PAGE(self->header.header_size + self->header.meta_size);
header_data = kmalloc(header_data_size, M_AUTHMGR, 0x102);
kassert(header_data);
memset(header_data, 0, header_data_size);
memcpy(header_data, self->data, MIN(self->file_size, header_data_size));
kassert(!sceSblDriverMapPages(&header_data_mapped, header_data, 1, 0x61, NULL, &header_data_mapdesc));
auth_info_size = ALIGN_PAGE(SELF_AUTH_INFO_SIZE);
auth_info = kmalloc(auth_info_size, M_AUTHMGR, 0x102);
kassert(auth_info);
memset(auth_info, 0, auth_info_size);
kassert(!sceSblDriverMapPages(&auth_info_mapped, auth_info, 1, 0x61, NULL, &auth_info_mapdesc));
/* send command */
sbl_authmgr_verify_header_t *args = (void*)&payload[0];
memset(payload, 0, sizeof(payload));
args->function = AUTHMGR_CMD_VERIFY_HEADER;
args->status = 0;
args->header_addr = header_data_mapped;
args->header_size = self->header.header_size + self->header.meta_size;
args->context_id = self->ctx_id;
args->auth_info_addr = auth_info_mapped;
args->key_id = 0;
memset(&args->key, 0, SELF_KEY_SIZE);
kdprintf("Sending AUTHMGR_CMD_VERIFY_HEADER...\n");
sceSblServiceMailbox_locked(ret, self->svc_id, &payload, &payload);
kassert(!ret);
kassert(!args->status);
kassert(args->function == AUTHMGR_CMD_VERIFY_HEADER);
kdprintf("Confirmed context ID: %d\n", self->ctx_id);
self->auth_ctx_id = args->context_id;
memcpy(&self->auth_info, auth_info, sizeof(self->auth_info));
self->verified = 1;
if (auth_info_mapped)
kassert(!sceSblDriverUnmapPages(auth_info_mapdesc));
if (header_data_mapped)
kassert(!sceSblDriverUnmapPages(header_data_mapdesc));
return 0;
error:
self_krelease_context(td, uap);
if (auth_info_mapped)
kassert(!sceSblDriverUnmapPages(auth_info_mapdesc));
if (header_data_mapped)
kassert(!sceSblDriverUnmapPages(header_data_mapdesc));
return 1;
}
int self_kdecrypt_segment(
struct thread *td, struct self_kmethod_uap_t *uap)
{
int ret;
char payload[0x80];
size_t segment_data_size;
uint64_t segment_data_gpu_paddr = NULL;
uint64_t segment_data_gpu_desc = NULL;
uint8_t *segment_data = NULL;
uint64_t chunk_table_gpu_paddr = NULL;
uint64_t chunk_table_gpu_desc = NULL;
uint8_t *chunk_table = NULL;
self_kdecrypt_segment_args_t *args = uap->args;
self_t *self = uap->self;
ret = 1;
/* copy segment data */
segment_data_size = ALIGN_PAGE(args->segment_data_user_size);
segment_data = kmalloc(segment_data_size, M_AUTHMGR, 0x102);
kassert(segment_data);
memset(segment_data, 0, segment_data_size);
memcpy(segment_data, args->segment_data_user, args->segment_data_user_size);
/* create chunk table */
chunk_table = kmalloc(0x4000, M_AUTHMGR, 0x102);
kassert(chunk_table);
make_chunk_table(
&segment_data_gpu_paddr,
&segment_data_gpu_desc,
segment_data,
segment_data_size,
chunk_table,
0x4000, 1);
kassert(segment_data_gpu_paddr);
kassert(segment_data_gpu_desc);
map_chunk_table(
&chunk_table_gpu_paddr,
&chunk_table_gpu_desc,
chunk_table);
kassert(chunk_table_gpu_paddr);
kassert(chunk_table_gpu_desc);
/* decrypt segment */
sbl_authmgr_load_self_segment_t *cmd = (void*)&payload[0];
memset(payload, 0, sizeof(payload));
cmd->function = AUTHMGR_CMD_LOAD_SELF_SEGMENT;
cmd->status = 0;
cmd->chunk_table_addr = chunk_table_gpu_paddr;
cmd->segment_index = args->segment_idx;
cmd->is_block_table = args->is_block_table;
cmd->context_id = self->auth_ctx_id;
kdprintf("Sending AUTHMGR_CMD_LOAD_SELF_SEGMENT...\n");
sceSblServiceMailbox_locked(ret, self->svc_id, &payload, &payload);
kassert(!ret);
kassert(!cmd->status);
kassert(cmd->function == AUTHMGR_CMD_LOAD_SELF_SEGMENT);
memcpy(args->segment_data_user, segment_data, args->segment_data_user_size);
ret = 0;
error:
if (chunk_table_gpu_paddr)
kassert(!sceSblDriverUnmapPages(chunk_table_gpu_desc));
if (segment_data_gpu_paddr)
kassert(!sceSblDriverUnmapPages(segment_data_gpu_desc));
if (chunk_table)
kfree(chunk_table, M_AUTHMGR);
if (segment_data)
kfree(segment_data, M_AUTHMGR);
return ret;
}
int self_kdecrypt_block(
struct thread *td, struct self_kmethod_uap_t *uap)
{
int ret;
char payload[0x80];
void* input_data = NULL;
uint64_t input_size;
uint64_t input_mapped = NULL;
uint64_t input_mapdesc = NULL;
void* output_data = NULL;
uint64_t output_size;
uint64_t output_mapped = NULL;
uint64_t output_mapdesc = NULL;
self_kdecrypt_block_args_t *args = uap->args;
self_block_info_t *block = args->block;
self_t *self = uap->self;
ret = 1;
/* allocate memory for command */
input_size = ALIGN_PAGE(args->blob_size);
input_data = kmalloc(input_size, M_AUTHMGR, 0x102);
kassert(input_data);
memset(input_data, 0, input_size);
memcpy(input_data, args->blob_data, args->blob_size);
kassert(!sceSblDriverMapPages(&input_mapped, input_data, 1, 0x61, NULL, &input_mapdesc));
output_size = ALIGN_PAGE(args->blob_size);
output_data = kmalloc(output_size, M_AUTHMGR, 0x102);
kassert(output_data);
memset(output_data, 0, output_size);
kassert(!sceSblDriverMapPages(&output_mapped, output_data, 1, 0x61, NULL, &output_mapdesc));
/* decrypt block */
sbl_authmgr_load_self_block_t *cmd = (void*)&payload[0];
memset(payload, 0, sizeof(payload));
memcpy(&cmd->digest, &block->digest, sizeof(block->digest));
memcpy(&cmd->extent, &block->extent, sizeof(block->extent));
cmd->function = AUTHMGR_CMD_LOAD_SELF_BLOCK;
cmd->status = 0;
cmd->pages_addr = output_mapped;
cmd->segment_index = args->segment_idx;
cmd->context_id = self->auth_ctx_id;
cmd->block_index = block->index;
cmd->data_offset = 0;
cmd->data_size = args->blob_size;
cmd->data_start_addr = input_mapped;
cmd->data_end_addr = 0;
kdprintf("Sending AUTHMGR_CMD_LOAD_SELF_BLOCK...\n");
sceSblServiceMailbox_locked(ret, self->svc_id, &payload, &payload);
kassert(!ret);
kassert(!cmd->status);
kassert(cmd->function == AUTHMGR_CMD_LOAD_SELF_BLOCK);
memcpy(args->blob_data, output_data, args->blob_size);
ret = 0;
error:
if (input_mapped)
kassert(!sceSblDriverUnmapPages(input_mapdesc));
if (output_mapped)
kassert(!sceSblDriverUnmapPages(output_mapdesc));
if (input_data)
kfree(input_data, M_AUTHMGR);
if (output_data)
kfree(output_data, M_AUTHMGR);
return ret;
}
/* functions */
static void self_get_block_info(self_t *self, unsigned int target_entry_idx, self_block_info_t *info)
{
struct self_entry_t* table_segment;
struct self_entry_t* target_segment;
struct self_block_extent_t* extents;
const uint8_t* segment_data;
unsigned int target_num_blocks;
unsigned int i;
memset(&info->digest, 0, sizeof(info->digest));
memset(&info->extent, 0, sizeof(info->extent));
for (i = 0; i < self->header.num_entries; ++i) {
table_segment = &self->entries[i];
if (!EXTRACT(table_segment->props, SELF_PROPS_HAS_DIGESTS) &&
!EXTRACT(table_segment->props, SELF_PROPS_HAS_EXTENTS))
continue;
if (EXTRACT(table_segment->props, SELF_PROPS_SEGMENT_INDEX) != target_entry_idx)
continue;
target_segment = &self->entries[target_entry_idx];
target_num_blocks = (target_segment->memsz + (info->size - 1)) / info->size;
segment_data = &self->data[table_segment->offset];
if (EXTRACT(table_segment->props, SELF_PROPS_HAS_DIGESTS)) {
memcpy(&info->digest, &segment_data[info->index * sizeof(info->digest)], sizeof(info->digest));
}
if (EXTRACT(table_segment->props, SELF_PROPS_HAS_EXTENTS)) {
if (EXTRACT(table_segment->props, SELF_PROPS_HAS_DIGESTS))
extents = (void*)(&segment_data[target_num_blocks * sizeof(info->digest)]);
else
extents = (void*)(&segment_data[0]);
memcpy(&info->extent, &extents[info->index], sizeof(info->extent));
}
return;
}
}
self_t* self_open(const char* file)
{
self_t* self;
ssize_t size;
off_t off;
int fd;
/* allocate object */
self = malloc(sizeof(self_t));
assert(self);
memset(self, 0, sizeof(self_t));
/* open file */
fd = open(file, O_RDONLY, 0);
assert(fd >= 0);
self->fd = fd;
/* get self size */
off = lseek(fd, 0, SEEK_END);
assert(off >= 0);
self->file_size = off;
off = lseek(fd, 0, SEEK_SET);
assert(off >= 0);
/* get self header */
size = read(fd, &self->header, sizeof(self_header_t));
assert(size == sizeof(self_header_t));
assert(self->header.magic == SELF_MAGIC);
assert(self->header.version == SELF_VERSION);
assert(self->header.mode == SELF_MODE);
assert(self->header.endian == SELF_ENDIANNESS);
assert(self->header.file_size == self->file_size);
/* get self entries */
self->entries_size = self->header.num_entries * sizeof(self_entry_t);
self->entries = malloc(self->entries_size);
assert(self->entries);
assert(self->header.header_size >= sizeof(self_header_t) + self->entries_size);
assert(self->header.header_size + self->header.meta_size <= 0x4000);
memset(self->entries, 0, self->entries_size);
size = read(fd, self->entries, self->entries_size);
assert(size == self->entries_size);
/* copy file contents */
self->data = malloc(self->file_size);
off = lseek(fd, 0, SEEK_SET);
assert(off >= 0);
size = read(fd, self->data, self->file_size);
assert(size == self->file_size);
self->fd = fd;
self->file_path = strdup(file);
self->ctx_id = -1;
if (DEBUG_SELF) {
trace_self(self);
}
return self;
error:
self_close(self);
return NULL;
}
int self_verify_header(self_t *self)
{
syscall(11, self_kverify_header, self);
return 0;
}
int self_load_segments(self_t *self)
{
struct self_kdecrypt_segment_args_t args_ds;
struct self_kdecrypt_block_args_t args_db;
struct self_block_info_t block;
struct self_entry_t* segment;
struct blob_t *blob;
unsigned int this_segment_idx;
unsigned int that_segment_idx;
unsigned int num_blocks;
unsigned int block_idx_offset;
unsigned int block_offset;
unsigned int block_size;
unsigned int i, j;
if (!self->verified)
goto error;
/* prepare linked list of blobs */
blob = malloc(sizeof(blob_t));
memset(blob, 0, sizeof(blob_t));
self->blobs = blob;
/* load block table segments */
for (i = 0; i < self->header.num_entries; i++) {
segment = &self->entries[i];
if (!EXTRACT(segment->props, SELF_PROPS_HAS_DIGESTS) &&
!EXTRACT(segment->props, SELF_PROPS_HAS_EXTENTS))
continue;
dprintf("Processing block table segment @ entry #%u...\n", i);
that_segment_idx = EXTRACT(segment->props, SELF_PROPS_SEGMENT_INDEX);
this_segment_idx = EXTRACT(self->entries[that_segment_idx].props, SELF_PROPS_SEGMENT_INDEX);
dprintf(" that-segment-idx: %u\n", that_segment_idx);
dprintf(" this-segment-idx: %u\n", this_segment_idx);
blob->size = segment->filesz;
blob->data = malloc(blob->size);
assert(blob->data);
assert(segment->offset + segment->filesz <= self->file_size);
memcpy(blob->data, &self->data[segment->offset], blob->size);
blob_set_path_hash(blob, blob->data, blob->size);
args_ds.segment_idx = this_segment_idx;
args_ds.is_block_table = 1;
args_ds.segment_data_user = blob->data;
args_ds.segment_data_user_size = blob->size;
syscall(11, self_kdecrypt_segment, self, &args_ds);
memcpy(&self->data[segment->offset], blob->data, blob->size);
blob = blob_add(blob);
}
/* load blocked segments */
for (i = 0; i < self->header.num_entries; i++) {
segment = &self->entries[i];
if (!EXTRACT(segment->props, SELF_PROPS_BLOCKED))
continue;
dprintf("Processing blocked segment @ entry #%u...\n", i);
memset(&block, 0, sizeof(block));
block.size = (1 << (EXTRACT(segment->props, SELF_PROPS_BLOCK_SIZE) + 12));
this_segment_idx = EXTRACT(segment->props, SELF_PROPS_SEGMENT_INDEX);
num_blocks = (segment->memsz + block.size - 1) / block.size;
for (j = 0; j < num_blocks; j++) {
block.index = j;
self_get_block_info(self, i, &block);
block_idx_offset = block.extent.offset & ~(block.size - 1);
block_offset = block.extent.offset & (block.size - 1);
block_size = block.extent.size & ~(SELF_SEGMENT_BLOCK_ALIGNMENT - 1);
if (block_size == 0) {
block_idx_offset = block.index * block.size;
block_size = (block_idx_offset + block.size <= segment->memsz) ?
(block.size) :
(segment->memsz - block_idx_offset);
} else if (block.index * block.size + block.extent.size == segment->memsz) {
block_size = block.extent.size;
}
blob->size = block_size;
blob->data = malloc(blob->size);
assert(blob->data);
memcpy(blob->data, &self->data[segment->offset + block_idx_offset + block_offset], blob->size);
blob_set_path_hash(blob, blob->data, blob->size);
args_db.block = &block;
args_db.segment_idx = this_segment_idx;
args_db.blob_data = blob->data;
args_db.blob_size = blob->size;
syscall(11, self_kdecrypt_block, self, &args_db);
blob = blob_add(blob);
}
}
return 0;
error:
return 1;
}
void self_close(self_t *self)
{
struct blob_t *blob;
struct blob_t *next;
if (!self) {
return;
}
/* remove blobs */
blob = self->blobs;
while (blob) {
next = blob->next;
free(blob->data);
free(blob);
blob = next;
}
/* close file */
if (self->fd) {
close(self->fd);
}
syscall(11, self_krelease_context, self);
free(self->file_path);
free(self->entries);
free(self);
}