mirror of
https://github.com/Jimmy-Z/TWLbf.git
synced 2025-06-18 18:55:31 -04:00
decrypt and diagnose NCSD and MBR partition tables
This commit is contained in:
parent
3cc4127b07
commit
07a93dcaa6
2
Makefile
2
Makefile
@ -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
51
dsi.c
@ -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
3
dsi.h
@ -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
126
sector0.c
Normal 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
71
sector0.h
Normal 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
24
twlbf.c
@ -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
54
utils.c
@ -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;
|
||||
}
|
||||
|
||||
|
9
utils.h
9
utils.h
@ -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);
|
||||
|
Loading…
Reference in New Issue
Block a user