diff options
Diffstat (limited to 'utils/nwztools/upgtools/upg.c')
-rw-r--r-- | utils/nwztools/upgtools/upg.c | 360 |
1 files changed, 279 insertions, 81 deletions
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; } |