diff options
Diffstat (limited to 'utils/nwztools')
-rw-r--r-- | utils/nwztools/upgtools/Makefile | 2 | ||||
-rw-r--r-- | utils/nwztools/upgtools/fwp.c | 57 | ||||
-rw-r--r-- | utils/nwztools/upgtools/fwp.h | 48 | ||||
-rw-r--r-- | utils/nwztools/upgtools/keysig_search.c | 5 | ||||
-rw-r--r-- | utils/nwztools/upgtools/keysig_search.h | 5 | ||||
-rw-r--r-- | utils/nwztools/upgtools/mg.cpp | 105 | ||||
-rw-r--r-- | utils/nwztools/upgtools/mg.h | 26 | ||||
-rw-r--r-- | utils/nwztools/upgtools/upg.c | 360 | ||||
-rw-r--r-- | utils/nwztools/upgtools/upg.h | 58 | ||||
-rw-r--r-- | utils/nwztools/upgtools/upgtool.c | 38 |
10 files changed, 446 insertions, 258 deletions
diff --git a/utils/nwztools/upgtools/Makefile b/utils/nwztools/upgtools/Makefile index 046eb1a1a9..5dede1447c 100644 --- a/utils/nwztools/upgtools/Makefile +++ b/utils/nwztools/upgtools/Makefile @@ -30,7 +30,7 @@ all: $(BINS) %.o: %.cpp $(CXX) $(CXXFLAGS) -c -o $@ $< -upgtool: upgtool.o upg.o misc.o fwp.o mg.o keysig_search.o md5.o +upgtool: upgtool.o upg.o misc.o mg.o keysig_search.o md5.o $(LD) -o $@ $^ $(LDFLAGS) clean: diff --git a/utils/nwztools/upgtools/fwp.c b/utils/nwztools/upgtools/fwp.c deleted file mode 100644 index 7d8f8002a8..0000000000 --- a/utils/nwztools/upgtools/fwp.c +++ /dev/null @@ -1,57 +0,0 @@ -/*************************************************************************** - * __________ __ ___. - * Open \______ \ ____ ____ | | _\_ |__ _______ ___ - * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / - * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < - * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ - * \/ \/ \/ \/ \/ - * $Id$ - * - * Copyright (C) 2012 Amaury Pouly - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY - * KIND, either express or implied. - * - ****************************************************************************/ -#include <string.h> -#include <stdlib.h> -#include "fwp.h" -#include "misc.h" -#include "mg.h" - -void fwp_read(void *in, int size, void *out, uint8_t *key) -{ - mg_decrypt_fw(in, size, out, key); -} - -void fwp_write(void *in, int size, void *out, uint8_t *key) -{ - mg_encrypt_fw(in, size, out, key); -} - -static uint8_t g_key[NWZ_KEY_SIZE]; - -void fwp_setkey(char key[NWZ_KEY_SIZE]) -{ - memcpy(g_key, key, NWZ_KEY_SIZE); -} - -void fwp_crypt(void *buf, int size, int mode) -{ - while(size >= NWZ_KEY_SIZE) - { - if(mode) - mg_decrypt_pass(buf, NWZ_KEY_SIZE, buf, g_key); - else - mg_encrypt_pass(buf, NWZ_KEY_SIZE, buf, g_key); - buf += NWZ_KEY_SIZE; - size -= NWZ_KEY_SIZE; - } - if(size != 0) - abort(); /* size is not a multiple of 8 */ -} diff --git a/utils/nwztools/upgtools/fwp.h b/utils/nwztools/upgtools/fwp.h deleted file mode 100644 index 32fe260090..0000000000 --- a/utils/nwztools/upgtools/fwp.h +++ /dev/null @@ -1,48 +0,0 @@ -/*************************************************************************** - * __________ __ ___. - * Open \______ \ ____ ____ | | _\_ |__ _______ ___ - * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / - * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < - * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ - * \/ \/ \/ \/ \/ - * $Id$ - * - * Copyright (C) 2012 Amaury Pouly - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation; either version 2 - * of the License, or (at your option) any later version. - * - * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY - * KIND, either express or implied. - * - ****************************************************************************/ -#ifndef __fwp_h__ -#define __fwp_h__ - -#include <stdint.h> - -#ifdef __cplusplus -extern "C" { -#endif - -#define NWZ_KAS_SIZE 32 -#define NWZ_KEYSIG_SIZE 16 -#define NWZ_KEY_SIZE 8 -#define NWZ_SIG_SIZE 8 -#define NWZ_EXPKEY_SIZE (NWZ_KEY_SIZE * NWZ_KEY_SIZE) -#define NWZ_DES_BLOCK 8 -#define NWZ_MD5_SIZE 16 - -/* size must be a multiple of 8 */ -void fwp_read(void *in, int size, void *out, uint8_t *key); -void fwp_write(void *in, int size, void *out, uint8_t *key); -void fwp_setkey(char key[8]); -void fwp_crypt(void *buf, int size, int mode); - -#ifdef __cplusplus -} -#endif - -#endif /* __fwp_h__ */ diff --git a/utils/nwztools/upgtools/keysig_search.c b/utils/nwztools/upgtools/keysig_search.c index 2f234d6358..c16dae5260 100644 --- a/utils/nwztools/upgtools/keysig_search.c +++ b/utils/nwztools/upgtools/keysig_search.c @@ -218,11 +218,10 @@ typedef bool (*sig_validate_fn_t)(uint8_t *key); static bool check_key(uint8_t key[NWZ_KEY_SIZE], sig_validate_fn_t validate) { struct upg_header_t hdr; - mg_decrypt_fw(g_keysig_search.enc_buf, sizeof(hdr.sig), (void *)&hdr, key); + mg_decrypt_fw(g_keysig_search.enc_buf, sizeof(hdr), (void *)&hdr, key); if(validate(hdr.sig)) { - /* the signature looks correct, so decrypt the header futher to be sure */ - mg_decrypt_fw(g_keysig_search.enc_buf, sizeof(hdr), (void *)&hdr, key); + /* the signature looks correct, so check the header to be sure */ /* we expect the number of files to be small and the padding to be 0 */ if(hdr.nr_files == 0 || hdr.nr_files > 10 || hdr.pad != 0) return false; diff --git a/utils/nwztools/upgtools/keysig_search.h b/utils/nwztools/upgtools/keysig_search.h index 877da2f89c..72649e042e 100644 --- a/utils/nwztools/upgtools/keysig_search.h +++ b/utils/nwztools/upgtools/keysig_search.h @@ -24,7 +24,10 @@ #include <stdbool.h> #include <stdint.h> #include <stddef.h> -#include "fwp.h" + +/* these values are for the V1 format */ +#define NWZ_KEY_SIZE 8 +#define NWZ_SIG_SIZE 8 enum keysig_search_method_t { diff --git a/utils/nwztools/upgtools/mg.cpp b/utils/nwztools/upgtools/mg.cpp index 66566770f9..4bab5d9c61 100644 --- a/utils/nwztools/upgtools/mg.cpp +++ b/utils/nwztools/upgtools/mg.cpp @@ -26,43 +26,96 @@ #include <stdio.h> using namespace CryptoPP; -namespace + +void mg_decrypt_fw(void *in, int size, void *out, uint8_t *key) { - inline void dec_des_ecb(void *in, int size, void *out, uint8_t *key) - { - ECB_Mode< DES >::Decryption dec; - if(size % 8) - abort(); /* size must be a multiple of 8 */ - dec.SetKey(key, 8); - dec.ProcessData((byte*)out, (byte*)in, size); - } - - inline void enc_des_ecb(void *in, int size, void *out, uint8_t *key) - { - ECB_Mode< DES >::Encryption enc; - if(size % 8) - abort(); /* size must be a multiple of 8 */ - enc.SetKey(key, 8); - enc.ProcessData((byte*)out, (byte*)in, size); - } + ECB_Mode< DES >::Decryption dec; + if(size % 8) + abort(); /* size must be a multiple of 8 */ + dec.SetKey(key, 8); + dec.ProcessData((byte*)out, (byte*)in, size); } -void mg_decrypt_fw(void *in, int size, void *out, uint8_t *key) +static ECB_Mode< DES >::Decryption g_des_ecb_dec; + +void des_ecb_dec_set_key(const uint8_t key[8]) { - dec_des_ecb(in, size, out, key); + g_des_ecb_dec.SetKey(key, 8); } -void mg_encrypt_fw(void *in, int size, void *out, uint8_t *key) +void des_ecb_dec(void *in, int size, void *out) { - enc_des_ecb(in, size, out, key); + if(size % 8) + abort(); /* size must be a multiple of 8 */ + g_des_ecb_dec.ProcessData((byte*)out, (byte*)in, size); } -void mg_decrypt_pass(void *in, int size, void *out, uint8_t *key) +static ECB_Mode< DES >::Encryption g_des_ecb_enc; + +void des_ecb_enc_set_key(const uint8_t key[8]) +{ + g_des_ecb_enc.SetKey(key, 8); +} + +void des_ecb_enc(void *in, int size, void *out) +{ + if(size % 8) + abort(); /* size must be a multiple of 8 */ + g_des_ecb_enc.ProcessData((byte*)out, (byte*)in, size); +} + +static ECB_Mode< AES >::Decryption g_aes_ecb_dec; + +void aes_ecb_dec_set_key(const uint8_t key[16]) +{ + g_aes_ecb_dec.SetKey(key, 16); +} + +void aes_ecb_dec(void *in, int size, void *out) +{ + if(size % 16) + abort(); /* size must be a multiple of 16 */ + g_aes_ecb_dec.ProcessData((byte*)out, (byte*)in, size); +} + +static ECB_Mode< AES >::Encryption g_aes_ecb_enc; + +void aes_ecb_enc_set_key(const uint8_t key[16]) +{ + g_aes_ecb_enc.SetKey(key, 16); +} + +void aes_ecb_enc(void *in, int size, void *out) +{ + if(size % 16) + abort(); /* size must be a multiple of 16 */ + g_aes_ecb_enc.ProcessData((byte*)out, (byte*)in, size); +} + +static CBC_Mode< AES >::Decryption g_aes_cbc_dec; + +void aes_cbc_dec_set_key_iv(const uint8_t key[16], const uint8_t iv[16]) +{ + g_aes_cbc_dec.SetKeyWithIV(key, 16, iv); +} + +void aes_cbc_dec(void *in, int size, void *out) +{ + if(size % 16) + abort(); /* size must be a multiple of 16 */ + g_aes_cbc_dec.ProcessData((byte*)out, (byte*)in, size); +} + +static CBC_Mode< AES >::Encryption g_aes_cbc_enc; + +void aes_cbc_enc_set_key_iv(const uint8_t key[16], const uint8_t iv[16]) { - dec_des_ecb(in, size, out, key); + g_aes_cbc_enc.SetKeyWithIV(key, 16, iv); } -void mg_encrypt_pass(void *in, int size, void *out, uint8_t *key) +void aes_cbc_enc(void *in, int size, void *out) { - enc_des_ecb(in, size, out, key); + if(size % 16) + abort(); /* size must be a multiple of 16 */ + g_aes_cbc_enc.ProcessData((byte*)out, (byte*)in, size); } diff --git a/utils/nwztools/upgtools/mg.h b/utils/nwztools/upgtools/mg.h index ef8dcd5ecb..fe6ec7c2f6 100644 --- a/utils/nwztools/upgtools/mg.h +++ b/utils/nwztools/upgtools/mg.h @@ -26,11 +26,27 @@ #ifdef __cplusplus extern "C" { #endif -/* size must be a multiple of 8 */ -void mg_decrypt_fw(void *in, int size, void *out, uint8_t *key); -void mg_encrypt_fw(void *in, int size, void *out, uint8_t *key); -void mg_decrypt_pass(void *in, int size, void *out, uint8_t *key); -void mg_encrypt_pass(void *in, int size, void *out, uint8_t *key); +/* size must be a multiple of 8, this function is thread-safe */ +void mg_decrypt_fw(void *in, int size, void *out, uint8_t key[8]); + +/* for simplicity, these function use some global variables, this could be + * change if necessary in the future */ + +/* DES: sizes must be a multiple of 8 */ +void des_ecb_dec_set_key(const uint8_t key[8]); +void des_ecb_dec(void *in, int size, void *out); +void des_ecb_enc_set_key(const uint8_t key[8]); +void des_ecb_enc(void *in, int size, void *out); + +/* AES: size must be a multiple of 16 */ +void aes_ecb_dec_set_key(const uint8_t key[16]); +void aes_ecb_dec(void *in, int size, void *out); +void aes_ecb_enc_set_key(const uint8_t key[16]); +void aes_ecb_enc(void *in, int size, void *out); +void aes_cbc_dec_set_key_iv(const uint8_t key[16], const uint8_t iv[16]); +void aes_cbc_dec(void *in, int size, void *out); +void aes_cbc_enc_set_key_iv(const uint8_t key[16], const uint8_t iv[16]); +void aes_cbc_enc(void *in, int size, void *out); #ifdef __cplusplus } #endif diff --git a/utils/nwztools/upgtools/upg.c b/utils/nwztools/upgtools/upg.c index 599fbbeaf6..910c37a9bf 100644 --- a/utils/nwztools/upgtools/upg.c +++ b/utils/nwztools/upgtools/upg.c @@ -54,6 +54,12 @@ struct nwz_model_t g_model_list[] = { 0 } }; +/* KEY/IV for pre-WM1/A30 models */ +static uint8_t g_des_passkey[9] = "ed295076"; +/* device after WM1/NW-A30 */ +static uint8_t g_aes_passkey[17] = "9cc4419c8bef488c"; +static uint8_t g_aes_iv[17] = "6063ce1efa1d543a"; + static int digit_value(char c) { if(c >= '0' && c <= '9') return c - '0'; @@ -67,42 +73,83 @@ static char hex_digit(unsigned v) return (v < 10) ? v + '0' : (v < 16) ? v - 10 + 'a' : 'x'; } -int decrypt_keysig(const char kas[NWZ_KAS_SIZE], char key[NWZ_KEY_SIZE], - char sig[NWZ_SIG_SIZE]) +int decrypt_keysig(const char *kas, char **key, char **sig) { - uint8_t src[NWZ_KAS_SIZE / 2]; - for(int index = 0; index < NWZ_KAS_SIZE / 2; index++) + int len = strlen(kas); + if(len % 2) + return -1; /* length must be a multiple of two */ + uint8_t *src = malloc(len / 2); + for(int index = 0; index < len / 2; index++) { int a = digit_value(kas[index * 2]); int b = digit_value(kas[index * 2 + 1]); if(a < 0 || b < 0) - return -1; + return -1; /* bad digit */ src[index] = a << 4 | b; } - fwp_setkey("ed295076"); - fwp_crypt(src, sizeof(src), 1); - memcpy(key, src, NWZ_KEY_SIZE); - memcpy(sig, src + NWZ_KEY_SIZE, NWZ_SIG_SIZE); + if(*key == NULL) + *key = malloc(len / 4 + 1); + if(*sig == NULL) + *sig = malloc(len / 4 + 1); + + if(len == 32) + { + /* Device before WM1/NW-A30 use DES */ + des_ecb_dec_set_key(g_des_passkey); + des_ecb_dec(src, len / 2, src); + } + else if(len == 64) + { + /* device after WM1/NW-A30 */ + aes_cbc_dec_set_key_iv(g_aes_passkey, g_aes_iv); + aes_cbc_dec(src, len / 2, src); + } + else + { + free(src); + return -1; + } + memcpy(*key, src, len / 4); + (*key)[len / 4] = 0; + memcpy(*sig, src + len / 4, len / 4); + (*sig)[len / 4] = 0; + free(src); return 0; } -void encrypt_keysig(char kas[NWZ_KEY_SIZE], - const char key[NWZ_SIG_SIZE], const char sig[NWZ_KAS_SIZE]) +void encrypt_keysig(char **kas, const char *key, const char *sig) { - uint8_t src[NWZ_KAS_SIZE / 2]; - fwp_setkey("ed295076"); - memcpy(src, key, NWZ_KEY_SIZE); - memcpy(src + NWZ_KEY_SIZE, sig, NWZ_SIG_SIZE); - fwp_crypt(src, sizeof(src), 0); - for(int i = 0; i < NWZ_KAS_SIZE / 2; i++) + int len = strlen(key); + if(len != strlen(sig)) + abort(); + uint8_t *src = malloc(len * 2); + memcpy(src, key, len); + memcpy(src + len, sig, len); + if(len == 8) + { + des_ecb_enc_set_key(g_des_passkey); + des_ecb_enc(src, len * 2, src); + } + else if(len == 16) { - kas[2 * i] = hex_digit((src[i] >> 4) & 0xf); - kas[2 * i + 1] = hex_digit(src[i] & 0xf); + aes_cbc_enc_set_key_iv(g_aes_passkey, g_aes_iv); + aes_cbc_enc(src, len * 2, src); } + else + abort(); + if(*kas == NULL) + *kas = malloc(len * 4 + 1); + for(int i = 0; i < len * 2; i++) + { + (*kas)[2 * i] = hex_digit((src[i] >> 4) & 0xf); + (*kas)[2 * i + 1] = hex_digit(src[i] & 0xf); + } + (*kas)[len * 4] = 0; + free(src); } -struct upg_file_t *upg_read_memory(void *buf, size_t size, char key[NWZ_KEY_SIZE], - char *sig, void *u, generic_printf_t printf) +struct upg_file_t *upg_read_memory(void *buf, size_t size, const char *key, + const char *sig, void *u, generic_printf_t printf) { #define cprintf(col, ...) printf(u, false, col, __VA_ARGS__) #define cprintf_field(str1, ...) do{ cprintf(GREEN, str1); cprintf(YELLOW, __VA_ARGS__); }while(0) @@ -114,6 +161,8 @@ struct upg_file_t *upg_read_memory(void *buf, size_t size, char key[NWZ_KEY_SIZE cprintf(YELLOW, "%02x", md5->md5[i]); cprintf(OFF, " "); + int key_len = strlen(key); + /* check MD5 */ uint8_t actual_md5[NWZ_MD5_SIZE]; MD5_CalculateDigest(actual_md5, (md5 + 1), size - sizeof(struct upg_header_t)); @@ -125,72 +174,186 @@ struct upg_file_t *upg_read_memory(void *buf, size_t size, char key[NWZ_KEY_SIZE } cprintf(RED, "Ok\n"); - struct upg_header_t *hdr = (void *)(md5 + 1); - /* decrypt the whole file at once */ - fwp_read(hdr, size - sizeof(*md5), hdr, (void *)key); + bool is_v2 = false; + void *hdr = (void *)(md5 + 1); + /* decrypt just the header */ + if(key_len == 8) + { + des_ecb_dec_set_key((uint8_t *)key); + des_ecb_dec(hdr, sizeof(struct upg_header_t), hdr); + } + else if(key_len == 16) + { + aes_ecb_dec_set_key((uint8_t *)key); + aes_ecb_dec(hdr, sizeof(struct upg_header_v2_t), hdr); + is_v2 = true; + } + else + { + cprintf(GREY, "I don't know how to decrypt with a key of length %s\n", key_len); + return NULL; + } cprintf(BLUE, "Header\n"); - cprintf_field(" Signature:", " "); - for(int i = 0; i < NWZ_SIG_SIZE; i++) - cprintf(YELLOW, "%c", isprint(hdr->sig[i]) ? hdr->sig[i] : '.'); - if(sig) + int nr_files = 0; + void *content = NULL; + if(!is_v2) { - if(memcmp(hdr->sig, sig, NWZ_SIG_SIZE) != 0) + struct upg_header_t *hdr_v1 = hdr; + cprintf_field(" Signature: ", ""); + for(int i = 0; i < 8; i++) + cprintf_field("", "%c", isprint(hdr_v1->sig[i]) ? hdr_v1->sig[i] : '.'); + if(sig) { - cprintf(RED, "Mismatch\n"); - err_printf(GREY, "Signature Mismatch\n"); - return NULL; + if(memcmp(hdr_v1->sig, sig, 8) != 0) + { + cprintf(RED, " Mismatch\n"); + err_printf(GREY, "Signature Mismatch\n"); + return NULL; + } + cprintf(RED, " Ok\n"); } - cprintf(RED, " Ok\n"); + else + cprintf(RED, " Can't check\n"); + cprintf_field(" Files: ", "%d\n", hdr_v1->nr_files); + if(hdr_v1->pad != 0) + cprintf_field(" Pad: ", "0x%x\n", hdr_v1->pad); + + nr_files = hdr_v1->nr_files; + content = hdr_v1 + 1; } else - cprintf(RED, " Can't check\n"); - cprintf_field(" Files: ", "%d\n", hdr->nr_files); - cprintf_field(" Pad: ", "0x%x\n", hdr->pad); - - - /* Do a first pass to decrypt in-place */ - cprintf(BLUE, "Files\n"); - struct upg_entry_t *entry = (void *)(hdr + 1); - for(unsigned i = 0; i < hdr->nr_files; i++, entry++) { - cprintf(GREY, " File"); - cprintf(RED, " %d\n", i); - cprintf_field(" Offset: ", "0x%x\n", entry->offset); - cprintf_field(" Size: ", "0x%x\n", entry->size); + struct upg_header_v2_t *hdr_v2 = hdr; + cprintf_field(" Signature: ", ""); + for(int i = 0; i < 16; i++) + cprintf_field("", "%c", isprint(hdr_v2->sig[i]) ? hdr_v2->sig[i] : '.'); + if(sig) + { + if(memcmp(hdr_v2->sig, sig, 16) != 0) + { + cprintf(RED, " Mismatch\n"); + err_printf(GREY, "Signature Mismatch\n"); + return NULL; + } + cprintf(RED, " Ok\n"); + } + else + cprintf(RED, " Can't check\n"); + cprintf_field(" Files: ", "%d\n", hdr_v2->nr_files); + if(hdr_v2->pad[0] != 0 || hdr_v2->pad[1] != 0 || hdr_v2->pad[2] != 0) + cprintf_field(" Pad: ", "0x%x 0x%x 0x%x\n", hdr_v2->pad[0], hdr_v2->pad[1], hdr_v2->pad[2]); + + nr_files = hdr_v2->nr_files; + content = hdr_v2 + 1; } - /* Do a second pass to create the file structure */ + /* create file */ struct upg_file_t *file = malloc(sizeof(struct upg_file_t)); memset(file, 0, sizeof(struct upg_file_t)); - file->nr_files = hdr->nr_files; + file->nr_files = nr_files; file->files = malloc(sizeof(struct upg_file_entry_t) * file->nr_files); - entry = (void *)(hdr + 1); - for(unsigned i = 0; i < hdr->nr_files; i++, entry++) + /* decrypt the file list */ + if(key_len == 8) + des_ecb_dec(content, sizeof(struct upg_entry_t) * nr_files, content); + else if(key_len == 16) + aes_ecb_dec(content, sizeof(struct upg_entry_v2_t) * nr_files, content); + + /* Extract files */ + cprintf(BLUE, "Files\n"); + struct upg_entry_t *entry_v1 = content; + struct upg_entry_v2_t *entry_v2 = content; + for(unsigned i = 0; i < nr_files; i++) { + uint32_t offset, size; + cprintf(GREY, " File"); + cprintf(RED, " %d\n", i); + if(!is_v2) + { + offset = entry_v1[i].offset; + size = entry_v1[i].size; + } + else + { + offset = entry_v2[i].offset; + size = entry_v2[i].size; + } + cprintf_field(" Offset: ", "0x%x\n", offset); + cprintf_field(" Size: ", "0x%x\n", size); + if(is_v2 && (entry_v2[i].pad[0] != 0 || entry_v2[i].pad[1] != 0)) + cprintf_field(" Pad:", " %x %x\n", entry_v2[i].pad[0], entry_v2[i].pad[1]); + + /* decrypt file content, we round up the size to make sure it's a multiple of 8/16 */ + if(key_len == 8) + des_ecb_dec(buf + offset, ROUND_UP(size, 8), buf + offset); + else if(key_len == 16) + { + aes_cbc_dec_set_key_iv((uint8_t *)key, (uint8_t *)g_aes_iv); + aes_cbc_dec(buf + offset, ROUND_UP(size, 16), buf + offset); + } + + /* in V2 of the format, some entries can be compressed using zlib but there is no marker for + * that; instead the OF has the fwpup program that can extract the nth entry of the archive + * and takes an optional -z flag to specify whether to uncompress(). Hence we don't support + * that at the moment. */ memset(&file->files[i], 0, sizeof(struct upg_file_entry_t)); - file->files[i].size = entry->size; + file->files[i].size = size; file->files[i].data = malloc(file->files[i].size); - memcpy(file->files[i].data, buf + entry->offset, entry->size); + memcpy(file->files[i].data, buf + offset, size); } return file; } -void *upg_write_memory(struct upg_file_t *file, char key[NWZ_KEY_SIZE], - char sig[NWZ_SIG_SIZE], size_t *out_size, void *u, generic_printf_t printf) +void *upg_write_memory(struct upg_file_t *file, const char *key, + const char *sig, size_t *out_size, void *u, generic_printf_t printf) { + int key_len = strlen(key); + if(strlen(sig) != key_len) + { + err_printf(GREY, "The key must have the same length as the signature\n"); + return NULL; + } if(file->nr_files == 0) { err_printf(GREY, "A UPG file must have at least one file\n"); return NULL; } + if(key_len == 16 && file->nr_files == 1) + { + err_printf(RED, "This will probably not work: the firmware updater for this device expects at least two files in the archive.\n"); + err_printf(RED, "The first one is a shell script and the second is a MD5 file. You can probably put whatever you want in this file,\n"); + err_printf(RED, "even make it empty, but it needs to be there.\n"); + /* let it run just in case */ + } + + bool is_v2 = false; + size_t min_chunk_size, hdr_sz, ent_sz; + if(key_len == 8) + { + min_chunk_size = 8; + hdr_sz = sizeof(struct upg_header_t); + ent_sz = sizeof(struct upg_entry_t); + } + else if(key_len == 16) + { + min_chunk_size = 16; + hdr_sz = sizeof(struct upg_header_v2_t); + ent_sz = sizeof(struct upg_entry_v2_t); + is_v2 = true; + } + else + { + cprintf(GREY, "I don't know how to decrypt with a key of length %s\n", key_len); + return NULL; + } + /* compute total size and create buffer */ - size_t tot_size = sizeof(struct upg_md5_t) + sizeof(struct upg_header_t) - + file->nr_files * sizeof(struct upg_entry_t); + size_t tot_hdr_siz = hdr_sz + file->nr_files * ent_sz; + size_t tot_size = sizeof(struct upg_md5_t) + tot_hdr_siz; for(int i = 0; i < file->nr_files; i++) - tot_size += ROUND_UP(file->files[i].size, 8); + tot_size += ROUND_UP(file->files[i].size, min_chunk_size); /* allocate buffer */ void *buf = malloc(tot_size); @@ -198,40 +361,75 @@ void *upg_write_memory(struct upg_file_t *file, char key[NWZ_KEY_SIZE], struct upg_md5_t *md5 = buf; memset(md5, 0, sizeof(*md5)); /* create the encrypted signature and header */ - struct upg_header_t *hdr = (void *)(md5 + 1); - memcpy(hdr->sig, sig, NWZ_SIG_SIZE); - hdr->nr_files = file->nr_files; - hdr->pad = 0; + if(!is_v2) + { + struct upg_header_t *hdr = (void *)(md5 + 1); + memcpy(hdr->sig, sig, 8); + hdr->nr_files = file->nr_files; + hdr->pad = 0; + } + else + { + struct upg_header_v2_t *hdr = (void *)(md5 + 1); + memcpy(hdr->sig, sig, 16); + hdr->nr_files = file->nr_files; + hdr->pad[0] = hdr->pad[1] = hdr->pad[2] = 0; + } /* create file headers */ - size_t offset = sizeof(*md5) + sizeof(*hdr) + file->nr_files * sizeof(struct upg_entry_t); - struct upg_entry_t *entry = (void *)(hdr + 1); + size_t offset = sizeof(struct upg_md5_t) + tot_hdr_siz; + struct upg_entry_t *entry_v1 = (void *)((uint8_t *)(md5 + 1) + hdr_sz); + struct upg_entry_v2_t *entry_v2 = (void *)entry_v1; cprintf(BLUE, "Files\n"); for(int i = 0; i < file->nr_files; i++) { - entry[i].offset = offset; - entry[i].size = file->files[i].size; - offset += ROUND_UP(entry[i].size, 8); /* pad each file to a multiple of 8 for encryption */ - cprintf(GREY, " File"); cprintf(RED, " %d\n", i); - cprintf_field(" Offset: ", "0x%lx\n", entry[i].offset); - cprintf_field(" Size: ", "0x%lx\n", entry[i].size); - } - - /* add file data */ - for(int i = 0; i < file->nr_files; i++) - { - /* copy data to buffer, and then encrypt in-place */ - size_t r_size = ROUND_UP(file->files[i].size, 8); - void *data_ptr = (uint8_t *)buf + entry[i].offset; + cprintf_field(" Offset: ", "0x%lx\n", offset); + cprintf_field(" Size: ", "0x%lx\n", file->files[i].size); + if(!is_v2) + { + entry_v1[i].offset = offset; + entry_v1[i].size = file->files[i].size; + } + else + { + entry_v2[i].offset = offset; + entry_v2[i].size = file->files[i].size; + entry_v2[i].pad[0] = entry_v2[i].pad[1] = 0; + } + /* copy data to buffer, with padding */ + size_t r_size = ROUND_UP(file->files[i].size, min_chunk_size); + void *data_ptr = (uint8_t *)buf + offset; memset(data_ptr, 0, r_size); /* the padding will be zero 0 */ memcpy(data_ptr, file->files[i].data, file->files[i].size); + /* encrypt in-place */ + if(!is_v2) + { + des_ecb_enc_set_key((uint8_t *)key); + des_ecb_enc(data_ptr, r_size, data_ptr); + } + else + { + aes_cbc_enc_set_key_iv((uint8_t *)key, (uint8_t *)g_aes_iv); + aes_cbc_enc(data_ptr, r_size, data_ptr); + } + + offset += r_size; + } + /* encrypt headers */ + if(!is_v2) + { + des_ecb_enc_set_key((uint8_t *)key); + des_ecb_enc(md5 + 1, tot_hdr_siz, md5 + 1); + } + else + { + aes_ecb_enc_set_key((uint8_t *)key); + aes_ecb_enc(md5 + 1, tot_hdr_siz, md5 + 1); } - /* encrypt everything and hash everything */ - fwp_write(hdr, tot_size - sizeof(*md5), hdr, (void *)key); - /* write final MD5 */ - MD5_CalculateDigest(md5->md5, (void *)hdr, tot_size - sizeof(*md5)); + /* compute MD5 of the whole file */ + MD5_CalculateDigest(md5->md5, md5 + 1, tot_size - sizeof(*md5)); *out_size = tot_size; return buf; } diff --git a/utils/nwztools/upgtools/upg.h b/utils/nwztools/upgtools/upg.h index bc7c9787c9..e6cdaba1f7 100644 --- a/utils/nwztools/upgtools/upg.h +++ b/utils/nwztools/upgtools/upg.h @@ -22,10 +22,9 @@ #define __UPG_H__ #include "misc.h" -#include "fwp.h" #include "mg.h" -/** Firmware format +/** Firmware format V1/V2 * * The firmware starts with the MD5 hash of the entire file (except the MD5 hash * itself of course). This is used to check that the file was not corrupted. @@ -35,7 +34,20 @@ * the key and finding the right signature serves to authenticate the firmware. * The header is followed by N entries (where N is the number of files) giving * the offset, within the file, and size of each file. Note that the files in - * the firmware have no name. */ + * the firmware have no name. The only difference between V1 and V2 is that the + * size of the signature is 16 bytes instead of 8 and the upg entries are 16 bytes + * long so they are padded. + * + * There is, however a practical difference between how the OF performs the update on + * newer devices (and hence corrolates exactly with V2 usage). On these devices, the + * update script will first extract the first file (the bash script) and the second file + * which is called "md5.txt". At this point it then runs the script. Hence it is not + * important what the content of the second file is, it is not checked unless fwpup is + * called. For the records, here is an exerct of such a file: + * 838860800 eae2acabcd6523a750f61f5ea3e9a80b system.img + */ + +#define NWZ_MD5_SIZE 16 struct upg_md5_t { @@ -44,7 +56,7 @@ struct upg_md5_t struct upg_header_t { - uint8_t sig[NWZ_SIG_SIZE]; + uint8_t sig[8]; uint32_t nr_files; uint32_t pad; // make sure structure size is a multiple of 8 } __attribute__((packed)); @@ -55,6 +67,20 @@ struct upg_entry_t uint32_t size; } __attribute__((packed)); +struct upg_header_v2_t +{ + uint8_t sig[16]; + uint32_t nr_files; + uint32_t pad[3]; // make sure structure size is a multiple of 16 +} __attribute__((packed)); + +struct upg_entry_v2_t +{ + uint32_t offset; + uint32_t size; + uint32_t pad[2]; // make sure structure size is a multiple of 16 +} __attribute__((packed)); + /** KAS / Key / Signature * * Since this is all very confusing, we need some terminology and notations: @@ -131,7 +157,7 @@ struct nwz_model_t * it is a KAS built from a key and sig brute-forced from an upgrade. In this * case, the KAS might be different from the 'official' one although for all * intent and purposes it should not make any difference. */ - char *kas; + const char *kas; }; /* list of models with keys and status. Sentinel NULL entry at the end */ @@ -150,21 +176,21 @@ struct upg_file_t struct upg_file_entry_t *files; }; -/* decrypt a KAS into a key and signature, return <0 if the KAS contains a non-hex - * character */ -int decrypt_keysig(const char kas[NWZ_KAS_SIZE], char key[NWZ_KEY_SIZE], - char sig[NWZ_SIG_SIZE]); -/* encrypt a key and signature into a KAS */ -void encrypt_keysig(char kas[NWZ_KEY_SIZE], - const char key[NWZ_SIG_SIZE], const char sig[NWZ_KAS_SIZE]); +/* IMPORTANT: all functions assume that the kas/key/sig are string and are zero terminated */ + +/* Decrypt a KAS into a key and signature, return <0 if the KAS contains a non-hex + * character. The function will allocate key and sig if *key and/or *sig is NULL */ +int decrypt_keysig(const char *kas, char **key, char **sig); +/* Encrypt a key and signature into a KAS, it will allocate kas if *kas is NULL */ +void encrypt_keysig(char **kas, const char *key, const char *sig); /* Read a UPG file: return a structure on a success or NULL on error. * Note that the memory buffer is modified to perform in-place decryption. */ -struct upg_file_t *upg_read_memory(void *file, size_t size, char key[NWZ_KEY_SIZE], - char sig[NWZ_SIG_SIZE], void *u, generic_printf_t printf); +struct upg_file_t *upg_read_memory(void *file, size_t size, const char *key, + const char *sig, void *u, generic_printf_t printf); /* Write a UPG file: return a buffer containing the whole image, or NULL on error. */ -void *upg_write_memory(struct upg_file_t *file, char key[NWZ_KEY_SIZE], - char sig[NWZ_SIG_SIZE], size_t *out_size, void *u, generic_printf_t printf); +void *upg_write_memory(struct upg_file_t *file, const char *key, + const char *sig, size_t *out_size, void *u, generic_printf_t printf); /* create empty upg file */ struct upg_file_t *upg_new(void); /* append a file to a upg, data is NOT copied */ diff --git a/utils/nwztools/upgtools/upgtool.c b/utils/nwztools/upgtools/upgtool.c index ff2a1f60f7..e0ccc15c48 100644 --- a/utils/nwztools/upgtools/upgtool.c +++ b/utils/nwztools/upgtools/upgtool.c @@ -18,6 +18,7 @@ * KIND, either express or implied. * ****************************************************************************/ +#define _XOPEN_SOURCE 500 /* for strdup */ #include <stdio.h> #include <stdint.h> #include <stdbool.h> @@ -30,7 +31,6 @@ #include "elf.h" #include <sys/stat.h> #include "crypt.h" -#include "fwp.h" #include "keysig_search.h" #include "upg.h" @@ -71,50 +71,49 @@ static bool upg_notify_keysig(void *user, uint8_t key[NWZ_KEY_SIZE], uint8_t sig[NWZ_SIG_SIZE]) { g_key = user; - g_sig = user + NWZ_KEY_SIZE; + g_sig = user + 9; memcpy(g_key, key, NWZ_KEY_SIZE); + g_key[8] = 0; memcpy(g_sig, sig, NWZ_SIG_SIZE); + g_sig[8] = 0; return true; } static int get_key_and_sig(bool is_extract, void *buf) { - static char keysig[NWZ_KEYSIG_SIZE]; - static char kas[NWZ_KAS_SIZE]; /* database lookup */ if(g_model_index != -1) - g_kas = g_model_list[g_model_index].kas; + g_kas = strdup(g_model_list[g_model_index].kas); /* always prefer KAS because it contains everything */ if(g_kas) { - if(strlen(g_kas) != NWZ_KAS_SIZE) + if(strlen(g_kas) != 32 && strlen(g_kas) != 64) { - cprintf(GREY, "The KAS has wrong length (must be %d hex digits)\n", NWZ_KAS_SIZE); + cprintf(GREY, "The KAS has wrong length (must be 32 or 64 hex digits)\n"); return 4; } - g_key = keysig; - g_sig = keysig + NWZ_KEY_SIZE; - decrypt_keysig(g_kas, g_key, g_sig); + decrypt_keysig(g_kas, &g_key, &g_sig); } /* Otherwise require key and signature */ else if(g_key && g_sig) { /* check key and signature size */ - if(strlen(g_key) != 8) + if(strlen(g_key) != 8 && strlen(g_key) != 16) { - cprintf(GREY, "The specified key has wrong length (must be 8 hex digits)\n"); + cprintf(GREY, "The specified key has wrong length (must be 8 or 16 hex digits)\n"); return 4; } - if(strlen(g_sig) != 8) + if(strlen(g_sig) != strlen(g_key)) { - cprintf(GREY, "The specified sig has wrong length (must be 8 hex digits)\n"); + cprintf(GREY, "The specified sig has wrong length (must match key length)\n"); return 5; } } /* for extraction, we offer a brute force search method from the MD5 */ else if(is_extract && g_keysig_search != KEYSIG_SEARCH_NONE) { + static char keysig[18]; /* 8+NUL+8+NULL */ struct upg_md5_t *md5 = (void *)buf; void *encrypted_hdr = (md5 + 1); cprintf(BLUE, "keysig Search\n"); @@ -145,14 +144,13 @@ static int get_key_and_sig(bool is_extract, void *buf) { /* This is useful to print the KAS for the user when brute-forcing since * the process will produce a key+sig and the database requires a KAS */ - g_kas = kas; - encrypt_keysig(g_kas, g_key, g_sig); + encrypt_keysig(&g_kas, g_key, g_sig); } cprintf(BLUE, "Keys\n"); - cprintf_field(" KAS: ", "%."STR(NWZ_KAS_SIZE)"s\n", g_kas); - cprintf_field(" Key: ", "%."STR(NWZ_KEY_SIZE)"s\n", g_key); - cprintf_field(" Sig: ", "%."STR(NWZ_SIG_SIZE)"s\n", g_sig); + cprintf_field(" KAS: ", "%s\n", g_kas); + cprintf_field(" Key: ", "%s\n", g_key); + cprintf_field(" Sig: ", "%s\n", g_sig); return 0; } @@ -268,7 +266,7 @@ static int create_upg(int argc, char **argv) if(f == NULL) { upg_free(upg); - printf(GREY, "Cannot open input file '%s': %m\n", argv[i + 1]); + cprintf(GREY, "Cannot open input file '%s': %m\n", argv[i + 1]); return 1; } size_t size = filesize(f); |