nwztools: add support for new UPG format on post-WM1/A30 devices
The new code supports reading and writing UPG files. I kept the old keysig search code but it only supports the old format (the new format has too long keys anyway). Since we now have to support two types of encryption(DES and AES), I reorganized the crypto routines and clean-up some code. Change-Id: Ie9be220ec2431ec6d0bd11699fa0493b62e1cec2
This commit is contained in:
parent
cda16f9439
commit
53d2742a48
10 changed files with 451 additions and 263 deletions
|
@ -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:
|
||||
|
|
|
@ -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 */
|
||||
}
|
|
@ -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__ */
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -26,43 +26,96 @@
|
|||
#include <stdio.h>
|
||||
|
||||
using namespace CryptoPP;
|
||||
namespace
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
void mg_decrypt_fw(void *in, int size, void *out, uint8_t *key)
|
||||
{
|
||||
dec_des_ecb(in, size, out, 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);
|
||||
}
|
||||
|
||||
void mg_encrypt_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])
|
||||
{
|
||||
enc_des_ecb(in, size, out, key);
|
||||
g_des_ecb_dec.SetKey(key, 8);
|
||||
}
|
||||
|
||||
void mg_decrypt_pass(void *in, int size, void *out, uint8_t *key)
|
||||
void des_ecb_dec(void *in, int size, void *out)
|
||||
{
|
||||
dec_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_encrypt_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])
|
||||
{
|
||||
enc_des_ecb(in, size, out, key);
|
||||
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])
|
||||
{
|
||||
g_aes_cbc_enc.SetKeyWithIV(key, 16, iv);
|
||||
}
|
||||
|
||||
void aes_cbc_enc(void *in, int size, void *out)
|
||||
{
|
||||
if(size % 16)
|
||||
abort(); /* size must be a multiple of 16 */
|
||||
g_aes_cbc_enc.ProcessData((byte*)out, (byte*)in, size);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
kas[2 * i] = hex_digit((src[i] >> 4) & 0xf);
|
||||
kas[2 * i + 1] = hex_digit(src[i] & 0xf);
|
||||
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, 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);
|
||||
|
||||
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)
|
||||
bool is_v2 = false;
|
||||
void *hdr = (void *)(md5 + 1);
|
||||
/* decrypt just the header */
|
||||
if(key_len == 8)
|
||||
{
|
||||
if(memcmp(hdr->sig, sig, NWZ_SIG_SIZE) != 0)
|
||||
{
|
||||
cprintf(RED, "Mismatch\n");
|
||||
err_printf(GREY, "Signature Mismatch\n");
|
||||
return NULL;
|
||||
}
|
||||
cprintf(RED, " Ok\n");
|
||||
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(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);
|
||||
cprintf(GREY, "I don't know how to decrypt with a key of length %s\n", key_len);
|
||||
return NULL;
|
||||
}
|
||||
/* Do a second pass to create the file structure */
|
||||
|
||||
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 = 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 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));
|
||||
/* 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;
|
||||
}
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in a new issue