decrypt and diagnose NCSD and MBR partition tables

This commit is contained in:
JimmyZ 2017-09-08 19:46:59 +08:00
parent 3cc4127b07
commit 07a93dcaa6
8 changed files with 326 additions and 14 deletions

View File

@ -1,5 +1,5 @@
PNAME = twlbf
OBJS = $(PNAME).o utils.o dsi.o sha1_16.o
OBJS = $(PNAME).o utils.o dsi.o sha1_16.o sector0.o
MBEDTLS_OBJS = aes.o aesni.o
CFLAGS += -std=c11 -Wall -O2

51
dsi.c
View File

@ -6,6 +6,7 @@
#include <assert.h>
#include "dsi.h"
#include "utils.h"
#include "sector0.h"
#include "crypto.h"
#define DSI_PROFILE 0
@ -99,7 +100,7 @@ static inline void add_128_64(u64 *a, u64 b){
}
}
// Answer to life, universe and everything.
// Answer to life, universe and everything?
static inline void rol42_128(u64 *a){
u64 t = a[1];
a[1] = (t << 42 ) | (a[0] >> 22);
@ -129,7 +130,8 @@ static inline void dsi_make_key(u64 *key, u64 console_id, int is3DS){
}
void dsi_aes_ctr_crypt_block(const u8 *console_id, const u8 *emmc_cid,
const u8 *offset, const u8 *src, int is3DS){
const u8 *offset, const u8 *src, int is3DS)
{
crypto_init();
u64 key[2];
dsi_make_key(key, u64be(console_id), is3DS);
@ -164,6 +166,48 @@ void dsi_aes_ctr_crypt_block(const u8 *console_id, const u8 *emmc_cid,
printf("Decrypted: %s\n", hexdump(out, 16, 1));
}
#define MBR_SIZE 0x200
void dsi_decrypt_mbr(const u8 *console_id, const u8 *emmc_cid,
const char *in_file, const char *out_file)
{
u8 mbr[MBR_SIZE];
read_block_from_file(mbr, in_file, 0, MBR_SIZE);
int is3DS = parse_ncsd(mbr);
crypto_init();
u64 key[2];
dsi_make_key(key, u64be(console_id), is3DS);
u8 key_reversed[16];
byte_reverse_16(key_reversed, (u8*)key);
aes_128_ecb_set_key(key_reversed);
u8 emmc_cid_sha1[20];
sha1_16(emmc_cid_sha1, emmc_cid);
for (unsigned offset = 0; offset < MBR_SIZE; offset += 16) {
u8 ctr[16];
byte_reverse_16(ctr, emmc_cid_sha1);
u8 xor[16];
aes_128_ecb_crypt_1(ctr, ctr);
byte_reverse_16(xor, ctr);
xor_128((u64*)(mbr + offset), (u64*)(mbr + offset), (u64*) xor );
add_128_64((u64*)emmc_cid_sha1, 1);
}
if (parse_mbr(mbr, is3DS, 1)) {
dump_to_file(out_file, mbr, MBR_SIZE);
}
else {
printf("invalid MBR, decryption failed\n");
}
}
#define BLOCK_SIZE 16
#if DSI_PROFILE
#define CHUNK_BITS 22
@ -174,7 +218,8 @@ void dsi_aes_ctr_crypt_block(const u8 *console_id, const u8 *emmc_cid,
#define CHUNK_COUNT (1 << (32 - CHUNK_BITS))
void dsi_brute_emmc_cid(const u8 *console_id, const u8 *emmc_cid_template,
const u8 *offset, const u8 *src, const u8 *ver){
const u8 *offset, const u8 *src, const u8 *ver)
{
u8 emmc_cid[16];
memcpy(emmc_cid, emmc_cid_template, sizeof(emmc_cid));

3
dsi.h
View File

@ -12,6 +12,9 @@ typedef enum {
void dsi_aes_ctr_crypt_block(const u8 *console_id, const u8 *emmc_cid,
const u8 *offset, const u8 *src, int is3DS);
void dsi_decrypt_mbr(const u8 *console_id, const u8 *emmc_cid,
const char *in_file, const char *out_file);
void dsi_brute_emmc_cid(const u8 *console_id, const u8 *emmc_cid_template,
const u8 *offset, const u8 *src, const u8 *verify);

126
sector0.c Normal file
View File

@ -0,0 +1,126 @@
#include <stdio.h>
#include <string.h>
#include "utils.h"
#include "sector0.h"
// return 1 for valid NCSD header
int parse_ncsd(const u8 sector0[SECTOR_SIZE]) {
const ncsd_header_t * h = (ncsd_header_t *)sector0;
if (h->magic == 0x4453434e) {
printf("NCSD magic found\n");
} else {
printf("NCSD magic not found\n");
return 0;
}
printf("size: %d sectors, %s MB\n", h->size, to_Mebi(h->size * SECTOR_SIZE));
printf("media ID: %s\n", hexdump(&h->media_id, 8, 0));
for (unsigned i = 0; i < NCSD_PARTITIONS; ++i) {
unsigned fs_type = h->fs_types[i];
if (fs_type == 0) {
break;
}
const char *s_fs_type;
switch (fs_type) {
case 1:
s_fs_type = "Normal";
break;
case 3:
s_fs_type = "FIRM";
break;
case 4:
s_fs_type = "AGB_FIRM save";
break;
default:
printf("invalid partition type %d\n", fs_type);
return 0;
}
// yes I use MB for "MiB", bite me
printf("partition %u, %s, crypt: %u, offset: 0x%08x, length: 0x%08x(%s MB)\n",
i, s_fs_type, h->crypt_types[i],
h->partitions[i].offset, h->partitions[i].length, to_Mebi(h->partitions[i].length * SECTOR_SIZE));
}
return 1;
}
static const mbr_partition_t ptable_DSi[MBR_PARTITIONS] = {
{0, {3, 24, 4}, 6, {15, 224, 59}, 0x00000877, 0x00066f89},
{0, {2, 206, 60}, 6, {15, 224, 190}, 0x0006784d, 0x000105b3},
{0, {2, 222, 191}, 1, {15, 224, 191}, 0x00077e5d, 0x000001a3},
{0, {0, 0, 0}, 0, {0, 0, 0}, 0, 0}
};
static const mbr_partition_t ptable_3DS[MBR_PARTITIONS] = {
{0, {4, 24, 0}, 6, {1, 160, 63}, 0x00000097, 0x00047da9},
{0, {4, 142, 64}, 6, {1, 160, 195}, 0x0004808d, 0x000105b3},
{0, {0, 0, 0}, 0, {0, 0, 0}, 0, 0},
{0, {0, 0, 0}, 0, {0, 0, 0}, 0, 0}
};
// return 1 for valid MBR
int parse_mbr(const u8 sector0[SECTOR_SIZE], int is3DS, int verbose) {
const mbr_t *m = (mbr_t*)sector0;
const mbr_partition_t *ref_ptable; // reference partition table
int ret = 1;
if (m->boot_signature_0 != 0x55 || m->boot_signature_1 != 0xaa) {
printf("invalid boot signature(0x55, 0xaa)\n");
ret = 0;
}
if (!is3DS) {
for (unsigned i = 0; i < sizeof(m->bootstrap); ++i) {
if (m->bootstrap[i]) {
printf("bootstrap on DSi should be all zero\n");
ret = 0;
break;
}
}
ref_ptable = ptable_DSi;
} else {
ref_ptable = ptable_3DS;
}
if (memcmp(ref_ptable, sector0 + MBR_BOOTSTRAP_SIZE,
sizeof(mbr_partition_t) * MBR_PARTITIONS)) {
printf("invalid partition table\n");
ret = 0;
}
if (!verbose) {
return ret;
}
for (unsigned i = 0; i < MBR_PARTITIONS; ++i) {
const mbr_partition_t *rp = &ref_ptable[i]; // reference
const mbr_partition_t *p = &m->partitions[i];
if (p->status != rp->status) {
printf("invalid partition %d status: %02x, should be %02x\n",
i, p->status, rp->status);
}
if (p->type != rp->type) {
printf("invalid partition %d type: %02x, should be %02x\n",
i, p->type, rp->type);
}
if (memcmp(&p->chs_first, &rp->chs_first, sizeof(chs_t))) {
printf("invalid partition %d first C/H/S: %d/%d/%d, should be %d/%d/%d\n",
i, p->chs_first.cylinder, p->chs_first.head, p->chs_first.sector,
rp->chs_first.cylinder, rp->chs_first.head, rp->chs_first.sector);
}
if (memcmp(&p->chs_last, &rp->chs_last, sizeof(chs_t))) {
printf("invalid partition %d last C/H/S: %d/%d/%d, should be %d/%d/%d\n",
i, p->chs_last.cylinder, p->chs_last.head, p->chs_last.sector,
rp->chs_last.cylinder, rp->chs_last.head, rp->chs_last.sector);
}
if (p->offset != rp->offset) {
printf("invalid partition %d LBA offset: 0x%08x, should be 0x%08x\n",
i, p->offset, rp->offset);
}
if (p->length != rp->length) {
printf("invalid partition %d LBA length: 0x%08x, should be 0x%08x\n",
i, p->length, rp->length);
}
printf("status: %02x, type: %02x, offset: 0x%08x, length: 0x%08x(%s MB)\n"
"\t C/H/S: %u/%u/%u - %u/%u/%u\n",
p->status, p->type, p->offset, p->length, to_Mebi(p->length * SECTOR_SIZE),
p->chs_first.cylinder, p->chs_first.head, p->chs_first.sector,
p->chs_last.cylinder, p->chs_last.head, p->chs_last.sector);
}
return ret;
}

71
sector0.h Normal file
View File

@ -0,0 +1,71 @@
#pragma once
#include <assert.h>
#include "common.h"
// https://3dbrew.org/wiki/NCSD#NCSD_header
#define SECTOR_SIZE 0x200
#define NCSD_PARTITIONS 8
#ifdef _MSC_VER
#pragma pack(push, 1)
#define PACKED
#elif defined __GNUC__
#define PACKED __attribute__ ((__packed__))
#endif
typedef struct {
u32 offset;
u32 length;
} PACKED ncsd_partition_t;
typedef struct {
u8 signature[0x100];
u32 magic;
u32 size;
u64 media_id;
u8 fs_types[NCSD_PARTITIONS];
u8 crypt_types[NCSD_PARTITIONS];
ncsd_partition_t partitions[NCSD_PARTITIONS];
} PACKED ncsd_header_t;
typedef struct {
u8 head;
u8 sector;
u8 cylinder;
} PACKED chs_t;
typedef struct {
u8 status;
chs_t chs_first;
u8 type;
chs_t chs_last;
u32 offset;
u32 length;
} PACKED mbr_partition_t;
#define MBR_PARTITIONS 4
// or 446 in decimal, all zero on DSi in all my samples
#define MBR_BOOTSTRAP_SIZE 0x1be
typedef struct {
u8 bootstrap[MBR_BOOTSTRAP_SIZE];
mbr_partition_t partitions[MBR_PARTITIONS];
u8 boot_signature_0;
u8 boot_signature_1;
} PACKED mbr_t;
#ifdef _MSC_VER
#pragma pack(pop)
#endif
#undef PACKED
static_assert(sizeof(ncsd_header_t) == 0x160, "sizeof(ncsd_header_t) should equal 0x160");
static_assert(sizeof(mbr_t) == SECTOR_SIZE, "sizeof(mbr_t) should equal 0x200");
int parse_ncsd(const u8 sector0[SECTOR_SIZE]);
int parse_mbr(const u8 sector0[SECTOR_SIZE], int is3DS, int verbose);

24
twlbf.c
View File

@ -7,19 +7,33 @@
#include "dsi.h"
#include "crypto.h"
const char * const CRYPT = "crypt";
const char * const CRYPT_3DS = "crypt_3ds";
const char * const DECRYPT_MBR = "decrypt_mbr";
const char * const EMMC_CID = "emmc_cid";
const char * const CONSOLE_ID = "console_id";
const char * const CONSOLE_ID_BCD = "console_id_bcd";
const char * const CONSOLE_ID_3DS = "console_id_3ds";
int main(int argc, const char *argv[]){
if(argc == 6){
// twlbf crypt [Console ID] [EMMC CID] [offset] [src]
// twlbf crypt(_3ds) [Console ID] [EMMC CID] [offset] [src]
// twlbf decrypt_mbr [Console ID] [EMMC CID] [in_file] [out_file]
u8 console_id[8], emmc_cid[16], src[16], offset[2];
hex2bytes(console_id, 8, argv[2], 1);
hex2bytes(emmc_cid, 16, argv[3], 1);
hex2bytes(offset, 2, argv[4], 1);
hex2bytes(src, 16, argv[5], 1);
if (!strcmp(argv[1], CRYPT) || !strcmp(argv[1], CRYPT_3DS)) {
hex2bytes(offset, 2, argv[4], 1);
hex2bytes(src, 16, argv[5], 1);
}
if(!strcmp(argv[1], "crypt")){
if(!strcmp(argv[1], CRYPT)){
dsi_aes_ctr_crypt_block(console_id, emmc_cid, offset, src, 0);
}else if(!strcmp(argv[1], "crypt_3ds")){
}else if(!strcmp(argv[1], CRYPT_3DS)){
dsi_aes_ctr_crypt_block(console_id, emmc_cid, offset, src, 1);
}else if(!strcmp(argv[1], DECRYPT_MBR)){
dsi_decrypt_mbr(console_id, emmc_cid, argv[4], argv[5]);
}else{
puts("invalid parameters");
}

54
utils.c
View File

@ -38,12 +38,10 @@ int hex2bytes(u8 *out, unsigned byte_len, const char *in, int critical){
return 0;
}
#ifndef HEXDUMP_BUF_SIZE
#define HEXDUMP_BUF_SIZE 0x100
#endif
static char hexdump_buf[HEXDUMP_BUF_SIZE];
// CAUTION, this always assume you have a buffer big enough
// CAUTION, this always assume the buffer is big enough
// it uses a static buffer so the value is only valid until next call
// and of course this is not thread safe
const char *hexdump(const void *b, unsigned l, int space){
const u8 *p = (u8*)b;
char *out = hexdump_buf;
@ -57,3 +55,49 @@ const char *hexdump(const void *b, unsigned l, int space){
return hexdump_buf;
}
void read_block_from_file(void *out, const char *file_name, size_t offset, size_t size) {
FILE * f = fopen(file_name, "rb");
if (f == NULL) {
fprintf(stderr, "can't read file: %s", file_name);
exit(-1);
}
fseek(f, offset, SEEK_SET);
size_t read = fread(out, 1, size, f);
if (read != size) {
fprintf(stderr, "failed to read %u bytes at offset %u from file: %s",
(unsigned)size, (unsigned)offset, file_name);
exit(-1);
}
fclose(f);
}
void dump_to_file(const char *file_name, const void *buf, size_t len) {
FILE *f = fopen(file_name, "r");
if (f != NULL) {
fclose(f);
fprintf(stderr, "%s exists, won't overwrite\n", file_name);
return;
}
f = fopen(file_name, "wb");
if (f == NULL) {
fprintf(stderr, "can't open file to write: %s\n", file_name);
return;
}
size_t written = fwrite(buf, 1, len, f);
if (written != len) {
fprintf(stderr, "failed to write %u bytes to %s\n",
(unsigned)len, file_name);
}
fclose(f);
}
// this stupid thing shares the hexdump_buf with hexdump
const char *to_Mebi(size_t size) {
if (size % (1024 * 1024)) {
sprintf(hexdump_buf, "%.2f", (float)(((double)size) / 1024 / 1024));
} else {
sprintf(hexdump_buf, "%u", (unsigned)(size >> 20));
}
return hexdump_buf;
}

View File

@ -3,7 +3,16 @@
#include "common.h"
#ifndef HEXDUMP_BUF_SIZE
#define HEXDUMP_BUF_SIZE 0x100
#endif
int hex2bytes(u8 *out, unsigned byte_len, const char *in, int critical);
const char * hexdump(const void *a, unsigned l, int space);
void read_block_from_file(void *out, const char *file_name, size_t offset, size_t size);
void dump_to_file(const char *file_name, const void *buf, size_t len);
const char * to_Mebi(size_t size);