mirror of
https://github.com/Jimmy-Z/TWLbf.git
synced 2025-06-18 18:55:31 -04:00
es block encryption
This commit is contained in:
parent
d1f3107d19
commit
9fa3da359b
2
crypto.h
2
crypto.h
@ -3,6 +3,8 @@
|
||||
|
||||
#include "common.h"
|
||||
|
||||
#define AES_BLOCK_LEN 16
|
||||
|
||||
// definition in sha1_16.c
|
||||
void sha1_16(u8 out[16], const u8 in[16]);
|
||||
|
||||
|
106
dsi.c
106
dsi.c
@ -187,7 +187,7 @@ static void aes_ctr_1(u8 *d, const u8 *ctr) {
|
||||
|
||||
// http://problemkaputt.de/gbatek.htm#dsiesblockencryption
|
||||
// "DSi SD/MMC DSiware Files on Internal eMMC Storage"
|
||||
void dsi_es_block_crypt(const u8 *console_id,
|
||||
void dsi_es_block_crypt(const u8 *console_id, crypt_mode_t mode,
|
||||
const char *in_file, const char *out_file)
|
||||
{
|
||||
crypto_init();
|
||||
@ -209,22 +209,23 @@ void dsi_es_block_crypt(const u8 *console_id,
|
||||
printf("file size %u, block size would be %06x\n",
|
||||
(unsigned)input_size, (unsigned)(input_size - sizeof(es_block_footer_t)));
|
||||
|
||||
// AES-CTR crypt later half of footer
|
||||
es_block_footer_t footer;
|
||||
u8 nonce[sizeof(footer.nonce)];
|
||||
// save footer since it might be overwritten by padding
|
||||
memcpy(&footer, input_buf + input_size - sizeof(es_block_footer_t), sizeof(es_block_footer_t));
|
||||
// save nonce since the one in ther footer becomes gargage after footer verification
|
||||
memcpy(nonce, footer.nonce, sizeof(nonce));
|
||||
es_block_footer_t *footer, footer_backup;
|
||||
footer = (es_block_footer_t*)(input_buf + input_size - sizeof(es_block_footer_t));
|
||||
// backup footer since it might be overwritten by padding
|
||||
// and also nonce in it becomes garbage after decryption
|
||||
memcpy(&footer_backup, footer, sizeof(es_block_footer_t));
|
||||
u8 ctr[16] = { 0 };
|
||||
memcpy(ctr + 1, nonce, sizeof(nonce));
|
||||
aes_ctr_1(((u8*)&footer) + 0x10, ctr);
|
||||
if (mode == DECRYPT) {
|
||||
// AES-CTR crypt later half of footer
|
||||
memcpy(ctr + 1, footer_backup.nonce, AES_CCM_NONCE_LEN);
|
||||
aes_ctr_1(footer->encrypted, ctr);
|
||||
}
|
||||
// check decrypted footer
|
||||
if (footer.fixed_3a != 0x3a) {
|
||||
printf("footer decryption failed, offset 0x10 should be 0x3a, got 0x%02x\n", footer.fixed_3a);
|
||||
if (footer->fixed_3a != 0x3a) {
|
||||
printf("footer offset 0x10 should be 0x3a, got 0x%02x\n", footer->fixed_3a);
|
||||
return;
|
||||
}
|
||||
unsigned block_size = (((((unsigned)footer.len2) << 8) | footer.len1) << 8) | footer.len0;
|
||||
unsigned block_size = u32be(footer->len32be) & 0xffffff;
|
||||
if (block_size + sizeof(es_block_footer_t) != input_size) {
|
||||
printf("block size in footer doesn't match, %06x != %06x\n",
|
||||
block_size, (unsigned)(input_size - sizeof(es_block_footer_t)));
|
||||
@ -234,45 +235,84 @@ void dsi_es_block_crypt(const u8 *console_id,
|
||||
unsigned remainder = block_size & 0xf;
|
||||
if (remainder > 0) {
|
||||
u8 padding[16] = { 0 };
|
||||
u8 ctr[16] = { 0 };
|
||||
*(u32*)ctr = (block_size >> 4) + 1;
|
||||
memcpy(ctr + 3, nonce, sizeof(nonce));
|
||||
ctr[0xf] = 2;
|
||||
aes_ctr_1(padding, ctr);
|
||||
if (mode == DECRYPT) {
|
||||
u8 ctr[16] = { 0 };
|
||||
*(u32*)ctr = (block_size >> 4) + 1;
|
||||
memcpy(ctr + 3, footer_backup.nonce, AES_CCM_NONCE_LEN);
|
||||
ctr[0xf] = 2;
|
||||
aes_ctr_1(padding, ctr);
|
||||
}
|
||||
memcpy(input_buf + block_size, padding + remainder, sizeof(padding) - remainder);
|
||||
block_size += sizeof(padding) - remainder;
|
||||
}
|
||||
// AES-CCM MAC
|
||||
u8 mac[16];
|
||||
*(u32*)mac = block_size;
|
||||
memcpy(mac + 3, nonce, sizeof(nonce));
|
||||
memcpy(mac + 3, footer_backup.nonce, AES_CCM_NONCE_LEN);
|
||||
mac[15] = 0x3a;
|
||||
aes_ecb_rev(mac, mac);
|
||||
// printf("AES-CCM MAC: %s\n", hexdump(mac, 16, 1));
|
||||
// AES-CCM CTR
|
||||
ctr[0] = 0; ctr[1] = 0; ctr[2] = 0;
|
||||
memcpy(ctr + 3, nonce, sizeof(nonce));
|
||||
memcpy(ctr + 3, footer_backup.nonce, AES_CCM_NONCE_LEN);
|
||||
ctr[15] = 2;
|
||||
// printf("AES-CCM CTR: %s\n", hexdump(ctr, 16, 1));
|
||||
// what?
|
||||
u8 S0[16] = { 0 };
|
||||
aes_ctr_1(S0, ctr);
|
||||
u8 s0[16] = { 0 };
|
||||
aes_ctr_1(s0, ctr);
|
||||
add_128_64((u64*)ctr, 1);
|
||||
// printf("AES-CCM S0 : %s\n", hexdump(S0, 16, 1));
|
||||
// printf("AES-CCM S0 : %s\n", hexdump(s0, 16, 1));
|
||||
// CCM loop
|
||||
for (unsigned i = 0; i < block_size; i += 16) {
|
||||
aes_ctr_1(input_buf + i, ctr);
|
||||
// printf("AES-CCM DUMP %s\n", hexdump(input_buf + i, 16, 1));
|
||||
add_128_64((u64*)ctr, 1);
|
||||
xor_128((u64*)mac, (u64*)mac, (u64*)(input_buf + i));
|
||||
aes_ecb_rev(mac, mac);
|
||||
// printf("AES-CCM MAC: %s\n", hexdump(mac_rev, 16, 1));
|
||||
if (mode == DECRYPT) {
|
||||
for (unsigned i = 0; i < block_size; i += 16) {
|
||||
aes_ctr_1(input_buf + i, ctr);
|
||||
add_128_64((u64*)ctr, 1);
|
||||
// printf("AES-CCM DUMP %s\n", hexdump(input_buf + i, 16, 1));
|
||||
xor_128((u64*)mac, (u64*)mac, (u64*)(input_buf + i));
|
||||
aes_ecb_rev(mac, mac);
|
||||
// printf("AES-CCM MAC: %s\n", hexdump(mac_rev, 16, 1));
|
||||
}
|
||||
} else {
|
||||
for (unsigned i = 0; i < block_size; i += 16) {
|
||||
xor_128((u64*)mac, (u64*)mac, (u64*)(input_buf + i));
|
||||
aes_ecb_rev(mac, mac);
|
||||
aes_ctr_1(input_buf + i, ctr);
|
||||
add_128_64((u64*)ctr, 1);
|
||||
}
|
||||
}
|
||||
xor_128((u64*)mac, (u64*)mac, (u64*)S0);
|
||||
xor_128((u64*)mac, (u64*)mac, (u64*)s0);
|
||||
|
||||
printf("MAC in footer : %s\n", hexdump(&footer, 16, 1));
|
||||
printf("MAC calculated : %s\n", hexdump(mac, 16, 1));
|
||||
printf("MAC in footer : %s\n", hexdump(footer_backup.ccm_mac, 16, 1));
|
||||
|
||||
if (mode == DECRYPT) {
|
||||
if (!memcmp(mac, footer_backup.ccm_mac, 16)) {
|
||||
puts("decryption successful");
|
||||
if (remainder) {
|
||||
// restore MAC in case it's been overwritten by padding
|
||||
memcpy(footer->ccm_mac, footer_backup.ccm_mac, AES_CCM_MAC_LEN);
|
||||
}
|
||||
// restore nonce
|
||||
memcpy(footer->nonce, footer_backup.nonce, AES_CCM_NONCE_LEN);
|
||||
// I saved the footer with decrypted flag and size, but original MAC and nonce
|
||||
// thus we can encrypt it back exactly
|
||||
dump_to_file(out_file, input_buf, input_size);
|
||||
} else {
|
||||
puts("MAC verification failed");
|
||||
}
|
||||
} else {
|
||||
puts("encrypted");
|
||||
if (memcmp(mac, footer_backup.ccm_mac, 16)) {
|
||||
puts("MAC changed, it's normal if you modified the decrypted content before");
|
||||
}
|
||||
memcpy(footer->ccm_mac, mac, AES_CCM_MAC_LEN);
|
||||
// AES-CTR crypt later half of footer
|
||||
ctr[0] = 0; ctr[13] = 0; ctr[14] = 0; ctr[15] = 0;
|
||||
memcpy(ctr + 1, footer_backup.nonce, AES_CCM_NONCE_LEN);
|
||||
aes_ctr_1(footer->encrypted, ctr);
|
||||
// restore nonce
|
||||
memcpy(footer->nonce, footer_backup.nonce, AES_CCM_NONCE_LEN);
|
||||
dump_to_file(out_file, input_buf, input_size);
|
||||
}
|
||||
free(input_buf);
|
||||
}
|
||||
|
||||
|
7
dsi.h
7
dsi.h
@ -41,13 +41,18 @@ typedef enum {
|
||||
CTR = 3
|
||||
} brute_mode_t;
|
||||
|
||||
typedef enum {
|
||||
ENCRYPT,
|
||||
DECRYPT
|
||||
} crypt_mode_t;
|
||||
|
||||
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_es_block_crypt(const u8 *console_id,
|
||||
void dsi_es_block_crypt(const u8 *console_id, crypt_mode_t mode,
|
||||
const char *in_file, const char *out_file);
|
||||
|
||||
void dsi_brute_emmc_cid(const u8 *console_id, const u8 *emmc_cid_template,
|
||||
|
26
ticket0.h
26
ticket0.h
@ -2,6 +2,7 @@
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdint.h>
|
||||
#include "crypto.h"
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma pack(push, 1)
|
||||
@ -77,13 +78,26 @@ static_assert(sizeof(tmd_content_v0_t) == 36, "invalid sizeof(tmd_contend_v0_t)"
|
||||
|
||||
// used in ticket encryption
|
||||
// http://problemkaputt.de/gbatek.htm#dsiesblockencryption
|
||||
|
||||
#define AES_CCM_MAC_LEN 0x10
|
||||
#define AES_CCM_NONCE_LEN 0x0c
|
||||
|
||||
typedef struct {
|
||||
uint8_t ccm_mac[0x10];
|
||||
uint8_t fixed_3a;
|
||||
uint8_t nonce[0x0c];
|
||||
uint8_t len2;
|
||||
uint8_t len1;
|
||||
uint8_t len0;
|
||||
uint8_t ccm_mac[AES_CCM_MAC_LEN];
|
||||
union {
|
||||
struct {
|
||||
uint8_t fixed_3a;
|
||||
uint8_t nonce[AES_CCM_NONCE_LEN];
|
||||
uint8_t len24be[3];
|
||||
};
|
||||
struct {
|
||||
uint8_t padding[AES_CCM_NONCE_LEN];
|
||||
// defined for convenience, it's still big endian with only 24 effective bits
|
||||
// read it as 32 bit big endian and discard the highest 8 bits
|
||||
uint8_t len32be[4];
|
||||
};
|
||||
uint8_t encrypted[0x10];
|
||||
};
|
||||
} PACKED es_block_footer_t;
|
||||
|
||||
static_assert(sizeof(es_block_footer_t) == 0x20, "invalid sizeof(es_block_footer_t)");
|
||||
|
24
twlbf.c
24
twlbf.c
@ -11,11 +11,16 @@ const char * const CRYPT = "crypt";
|
||||
const char * const CRYPT_3DS = "crypt_3ds";
|
||||
const char * const DECRYPT_MBR = "decrypt_mbr";
|
||||
|
||||
const char * const ENCRYPT_ES = "encrypt_es";
|
||||
const char * const DECRYPT_ES = "decrypt_es";
|
||||
|
||||
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";
|
||||
|
||||
const char * const INVALID_PARAMETERS = "invalid parameters";
|
||||
|
||||
int main(int argc, const char *argv[]){
|
||||
if(argc == 6){
|
||||
// twlbf crypt(_3ds) [Console ID] [EMMC CID] [offset] [src]
|
||||
@ -35,7 +40,7 @@ int main(int argc, const char *argv[]){
|
||||
}else if(!strcmp(argv[1], DECRYPT_MBR)){
|
||||
dsi_decrypt_mbr(console_id, emmc_cid, argv[4], argv[5]);
|
||||
}else{
|
||||
puts("invalid parameters");
|
||||
puts(INVALID_PARAMETERS);
|
||||
}
|
||||
}else if(argc == 7){
|
||||
// twlbf emmc_cid/console_id(_bcd) [Console ID] [EMMC CID] [offset] [src] [verify]
|
||||
@ -55,13 +60,22 @@ int main(int argc, const char *argv[]){
|
||||
}else if(!strcmp(argv[1], "console_id_3ds")){
|
||||
dsi_brute_console_id(console_id, emmc_cid, offset, src, verify, CTR);
|
||||
}else{
|
||||
puts("invalid parameters");
|
||||
puts(INVALID_PARAMETERS);
|
||||
}
|
||||
}else if(argc == 5 && !strcmp(argv[1], "es_decrypt")){
|
||||
}else if(argc == 5){
|
||||
// BEWARE the decrypted content comes with a decrypted es block footer, thus incompatible with twltool
|
||||
// with the benefit of preservation of original nonce, we can encrypt it back to original byte exact
|
||||
// twlbf es_decrypt [Console ID] [in_file] [out_file]
|
||||
// twlbf es_encrypt [Console ID] [in_file] [out_file]
|
||||
u8 console_id[8];
|
||||
hex2bytes(console_id, 8, argv[2], 1);
|
||||
dsi_es_block_crypt(console_id, argv[3], argv[4]);
|
||||
if (!strcmp(argv[1], ENCRYPT_ES)) {
|
||||
dsi_es_block_crypt(console_id, ENCRYPT, argv[3], argv[4]);
|
||||
} else if (!strcmp(argv[1], DECRYPT_ES)){
|
||||
dsi_es_block_crypt(console_id, DECRYPT, argv[3], argv[4]);
|
||||
} else {
|
||||
puts(INVALID_PARAMETERS);
|
||||
}
|
||||
}else if(argc >= 2 && !strcmp(argv[1], "crypto_test")){
|
||||
u8 key[16] = {1, 2, 3, 4, 5, 6, 7, 8, 1, 2, 3, 4, 5, 6, 7, 8};
|
||||
u8 src[32] = {8, 7, 6, 5, 4, 3, 2, 1, 1, 2, 3, 4, 5, 6, 7, 8,
|
||||
@ -79,7 +93,7 @@ int main(int argc, const char *argv[]){
|
||||
aes_128_ecb_crypt(out, src, 32);
|
||||
puts(hexdump(out, 32, 1));
|
||||
}else{
|
||||
puts("invalid parameters");
|
||||
puts(INVALID_PARAMETERS);
|
||||
}
|
||||
#ifdef _WIN32
|
||||
system("pause");
|
||||
|
Loading…
Reference in New Issue
Block a user