e371dee4a3
It turns out the calculation gives the right result for the wrong reason, this fixes it. Change-Id: I36053c8993b5ae1e85380da59546ffade265fb3f
464 lines
16 KiB
C
464 lines
16 KiB
C
/***************************************************************************
|
|
* __________ __ ___.
|
|
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
|
|
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
|
|
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
|
|
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
|
|
* \/ \/ \/ \/ \/
|
|
* $Id$
|
|
*
|
|
* Copyright (C) 2016 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 "upg.h"
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <ctype.h>
|
|
|
|
struct nwz_model_t g_model_list[] =
|
|
{
|
|
{ "nwz-a10", true, "2572f4a7b8c1a08aeb5142ce9cb834d6" },
|
|
{ "nw-a20", true, "d91a61c7263bafc626e9a5b66f983c0b" },
|
|
{ "nwz-e350", true, "8a01b624bfbfde4a1662a1772220e3c5" },
|
|
{ "nwz-e450", true, "8a01b624bfbfde4a1662a1772220e3c5" },
|
|
{ "nwz-e460", true, "89d813f8f966efdebd9c9e0ea98156d2" },
|
|
{ "nwz-a860", true, "a7c4af6c28b8900a783f307c1ba538c5" },
|
|
{ "nwz-a850", true, "a2efb9168616c2e84d78291295c1aa5d" },
|
|
{ "nwz-a840", true, "78033fe79a67786fd79fbc138c865c68" },
|
|
{ "nwz-e470", true, "e4144baaa2707913f17b5634034262c4" },
|
|
{ "nwz-e580", true, "6e25f79812eca7ceed04819d833e80af" },
|
|
{ "nwz-s750", true, "6d4f4d9adec781baf197e6255cedd0f6" },
|
|
{ "nwz-x1000",true, "efafdee4c628fa7536de0b878cfe23af" },
|
|
{ "nw-zx100", true, "cdda8d5e5360fd4373154388743f84d2" },
|
|
/* The following models use a different encryption */
|
|
{ "nw-wm1a", true, "e8d171a5d92f35eed9658c03fb9f86a169591659851fd7c49525f587a70b526c" },
|
|
{ "nw-wm1z", true, "2b07114f06d0f63b8ef8e31c8bc9332c7bd70281f7f8d2f80dab58cd36f82c82" },
|
|
{ "nw-zx300", true, "3ab5bbcb999463c50aaa957496b066c6b76a25f4505bf5b42c0bc4815cbe3db6" },
|
|
{ "nw-a30", true, "c40d91e7efff3e3aa5c8831dd85526fe4972086283419c8cd8fa3b7dcd39dee4" },
|
|
{ "nw-a40", true, "a0d2b1317794074aff77dd2afb9c7aa6b28d6cf24a5e5eb60df87a87eb562de5" },
|
|
{ "nw-a50", true, "dd49de9dab2bce5a59090c01049576d537af6a313e5c0a2c24353937a87352d6" },
|
|
{ "dmp-z1", true, "2b07114f06d0f63b8ef8e31c8bc9332c7bd70281f7f8d2f80dab58cd36f82c82" },
|
|
/* The following keys were obtained by brute forcing firmware upgrades,
|
|
* someone with a device needs to confirm that they work */
|
|
{ "nw-a820", false, "0c9869c268e0eaa6d1ba62daab09cebc" },
|
|
{ "nw-s10", false, "20f65807a9506f9bc591123cea2c2291" },
|
|
{ "nwz-s610", false, "fe14a16d7c5c52cf56846d04305f994c"},
|
|
{ 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';
|
|
if(c >= 'a' && c <= 'f') return c - 'a' + 10;
|
|
if(c >= 'A' && c <= 'F') return c - 'A' + 10;
|
|
return -1;
|
|
}
|
|
|
|
static char hex_digit(unsigned v)
|
|
{
|
|
return (v < 10) ? v + '0' : (v < 16) ? v - 10 + 'a' : 'x';
|
|
}
|
|
|
|
int decrypt_keysig(const char *kas, char **key, char **sig)
|
|
{
|
|
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; /* bad digit */
|
|
src[index] = a << 4 | b;
|
|
}
|
|
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, const char *key, const char *sig)
|
|
{
|
|
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)
|
|
{
|
|
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, 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)
|
|
#define err_printf(col, ...) printf(u, true, col, __VA_ARGS__)
|
|
struct upg_md5_t *md5 = buf;
|
|
cprintf(BLUE, "Preliminary\n");
|
|
cprintf(GREEN, " MD5: ");
|
|
for(int i = 0; i < NWZ_MD5_SIZE; i++)
|
|
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 - NWZ_MD5_SIZE);
|
|
if(memcmp(actual_md5, md5->md5, NWZ_MD5_SIZE) != 0)
|
|
{
|
|
cprintf(RED, "Mismatch\n");
|
|
err_printf(GREY, "MD5 Mismatch\n");
|
|
return NULL;
|
|
}
|
|
cprintf(RED, "Ok\n");
|
|
|
|
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 %d\n", key_len);
|
|
return NULL;
|
|
}
|
|
|
|
cprintf(BLUE, "Header\n");
|
|
int nr_files = 0;
|
|
void *content = NULL;
|
|
if(!is_v2)
|
|
{
|
|
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)
|
|
{
|
|
if(memcmp(hdr_v1->sig, sig, 8) != 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_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
|
|
{
|
|
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;
|
|
}
|
|
|
|
/* create file */
|
|
struct upg_file_t *file = malloc(sizeof(struct upg_file_t));
|
|
memset(file, 0, sizeof(struct upg_file_t));
|
|
file->nr_files = nr_files;
|
|
file->files = malloc(sizeof(struct upg_file_entry_t) * file->nr_files);
|
|
|
|
/* 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 = size;
|
|
file->files[i].data = malloc(file->files[i].size);
|
|
memcpy(file->files[i].data, buf + offset, size);
|
|
}
|
|
|
|
return file;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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 = 4096; /* experimentally, V2 UPG files always seem to have 4k sizes */
|
|
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;
|
|
}
|
|
|
|
/* V2 wants at least two files, the second of which is supposed to contain MD5 sums */
|
|
if(is_v2 && 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 */
|
|
}
|
|
|
|
/* compute total size and create buffer */
|
|
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, min_chunk_size);
|
|
/* allocate buffer */
|
|
void *buf = malloc(tot_size);
|
|
|
|
/* create md5 context, we push data to the context as we create it */
|
|
struct upg_md5_t *md5 = buf;
|
|
memset(md5, 0, sizeof(*md5));
|
|
/* create the encrypted signature and header */
|
|
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(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++)
|
|
{
|
|
cprintf(GREY, " File");
|
|
cprintf(RED, " %d\n", i);
|
|
cprintf_field(" Offset: ", "0x%lx\n", offset);
|
|
cprintf_field(" Size: ", "0x%lx\n", file->files[i].size);
|
|
/* 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);
|
|
}
|
|
/* write header */
|
|
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 = r_size; /* the V2 seems to write the padded size here */
|
|
entry_v2[i].pad[0] = entry_v2[i].pad[1] = 0;
|
|
}
|
|
|
|
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);
|
|
}
|
|
/* compute MD5 of the whole file */
|
|
MD5_CalculateDigest(md5->md5, md5 + 1, tot_size - sizeof(*md5));
|
|
*out_size = tot_size;
|
|
return buf;
|
|
}
|
|
|
|
struct upg_file_t *upg_new(void)
|
|
{
|
|
struct upg_file_t *f = malloc(sizeof(struct upg_file_t));
|
|
memset(f, 0, sizeof(struct upg_file_t));
|
|
return f;
|
|
}
|
|
|
|
void upg_append(struct upg_file_t *file, void *data, size_t size)
|
|
{
|
|
file->files = realloc(file->files, (file->nr_files + 1) * sizeof(struct upg_file_entry_t));
|
|
file->files[file->nr_files].data = data;
|
|
file->files[file->nr_files].size = size;
|
|
file->nr_files++;
|
|
}
|
|
|
|
void upg_free(struct upg_file_t *file)
|
|
{
|
|
if(file)
|
|
{
|
|
for(int i = 0; i < file->nr_files; i++)
|
|
free(file->files[i].data);
|
|
free(file->files);
|
|
}
|
|
free(file);
|
|
}
|