es block encryption

This commit is contained in:
JimmyZ 2017-09-27 05:30:05 +08:00
parent d1f3107d19
commit 9fa3da359b
5 changed files with 120 additions and 45 deletions

View File

@ -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
View File

@ -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
View File

@ -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,

View File

@ -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
View File

@ -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");