From cb09e369fb1ed41b5724a45929a49b42c2718203 Mon Sep 17 00:00:00 2001 From: Amaury Pouly Date: Sat, 3 Nov 2012 02:16:01 +0100 Subject: Introduce upgtools for sony nwz players This tool can unpack UPG archives for firmware updates. Change-Id: I32f5f1a84759198c7af4a4ecfd7aa65eaeda567a --- utils/nwztools/upgtools/Makefile | 26 ++ utils/nwztools/upgtools/fwp.c | 56 ++++ utils/nwztools/upgtools/fwp.h | 44 +++ utils/nwztools/upgtools/keysig_search.c | 157 +++++++++ utils/nwztools/upgtools/keysig_search.h | 50 +++ utils/nwztools/upgtools/mg.cpp | 67 ++++ utils/nwztools/upgtools/mg.h | 37 +++ utils/nwztools/upgtools/misc.c | 53 ++++ utils/nwztools/upgtools/misc.h | 46 +++ utils/nwztools/upgtools/upgtool.c | 542 ++++++++++++++++++++++++++++++++ 10 files changed, 1078 insertions(+) create mode 100644 utils/nwztools/upgtools/Makefile create mode 100644 utils/nwztools/upgtools/fwp.c create mode 100644 utils/nwztools/upgtools/fwp.h create mode 100644 utils/nwztools/upgtools/keysig_search.c create mode 100644 utils/nwztools/upgtools/keysig_search.h create mode 100644 utils/nwztools/upgtools/mg.cpp create mode 100644 utils/nwztools/upgtools/mg.h create mode 100644 utils/nwztools/upgtools/misc.c create mode 100644 utils/nwztools/upgtools/misc.h create mode 100644 utils/nwztools/upgtools/upgtool.c (limited to 'utils/nwztools/upgtools') diff --git a/utils/nwztools/upgtools/Makefile b/utils/nwztools/upgtools/Makefile new file mode 100644 index 0000000000..ad83c0e4c6 --- /dev/null +++ b/utils/nwztools/upgtools/Makefile @@ -0,0 +1,26 @@ +DEFINES= +CC=gcc +CXX=g++ +LD=g++ +PROFILE= +CFLAGS=-g $(PROFILE) -std=c99 -W -Wall $(DEFINES) `pkg-config --cflags openssl` `pkg-config --cflags libcrypto++` +CXXFLAGS=-g $(PROFILE) -W -Wall $(DEFINES) `pkg-config --cflags openssl` `pkg-config --cflags libcrypto++` +LDFLAGS=$(PROFILE) `pkg-config --libs openssl` `pkg-config --libs libcrypto++` -lcrypt +BINS=upgtool + +all: $(BINS) + +%.o: %.c + $(CC) $(CFLAGS) -c -o $@ $< + +%.o: %.cpp + $(CXX) $(CXXFLAGS) -c -o $@ $< + +upgtool: upgtool.o misc.o fwp.o mg.o keysig_search.o + $(LD) -o $@ $^ $(LDFLAGS) + +clean: + rm -fr *.o + +veryclean: + rm -rf $(BINS) diff --git a/utils/nwztools/upgtools/fwp.c b/utils/nwztools/upgtools/fwp.c new file mode 100644 index 0000000000..e739b8caef --- /dev/null +++ b/utils/nwztools/upgtools/fwp.c @@ -0,0 +1,56 @@ +/*************************************************************************** + * __________ __ ___. + * 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 +#include "fwp.h" +#include "misc.h" +#include "mg.h" +#include + +int fwp_read(void *in, int size, void *out, uint8_t *key) +{ + return mg_decrypt_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); +} + +int 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) + { + cprintf(GREY, "Cannot fwp_crypt non-multiple of 8!\n"); + return -1; + } + return 0; +} \ No newline at end of file diff --git a/utils/nwztools/upgtools/fwp.h b/utils/nwztools/upgtools/fwp.h new file mode 100644 index 0000000000..dcfe80027e --- /dev/null +++ b/utils/nwztools/upgtools/fwp.h @@ -0,0 +1,44 @@ +/*************************************************************************** + * __________ __ ___. + * 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 + +#ifdef __cplusplus +extern "C" { +#endif + +#define NWZ_KAS_SIZE 32 +#define NWZ_KEYSIG_SIZE 51 +#define NWZ_KEY_SIZE 8 +#define NWZ_EXPKEY_SIZE (NWZ_KEY_SIZE * NWZ_KEY_SIZE) +#define NWZ_DES_BLOCK 8 + +int fwp_read(void *in, int size, void *out, uint8_t *key); +void fwp_setkey(char key[8]); +int 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 new file mode 100644 index 0000000000..6054ea43ba --- /dev/null +++ b/utils/nwztools/upgtools/keysig_search.c @@ -0,0 +1,157 @@ +/*************************************************************************** + * __________ __ ___. + * 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 "keysig_search.h" +#include "misc.h" +#include "mg.h" +#include + +#define HEX_MAJ + +static uint8_t g_cipher[8]; +static keysig_notify_fn_t g_notify; +static uint8_t g_key[8]; +static void *g_user; +static bool is_hex[256]; +static bool is_init = false; +#ifdef HEX_MAJ +static char hex_digits[] = "02468ABEF"; +#else +static char hex_digits[] = "02468abef"; +#endif + +static void keysig_search_init() +{ + if(is_init) return; + is_init = true; + memset(is_hex, 0, sizeof(is_hex)); + for(int i = '0'; i <= '9'; i++) + is_hex[i] = true; +#ifdef HEX_MAJ + for(int i = 'A'; i <= 'F'; i++) +#else + for(int i = 'a'; i <= 'f'; i++) +#endif + is_hex[i] = true; +} + +static inline bool is_full_ascii(uint8_t *arr) +{ + for(int i = 0; i < 8; i++) + if(!is_hex[arr[i]]) + return false; + return true; +} + +static inline bool check_stupid() +{ + uint8_t res[8]; + mg_decrypt_fw(g_cipher, 8, res, g_key); + if(is_full_ascii(res)) + return g_notify(g_user, g_key, res); + return false; +} + +static bool search_stupid_rec(int rem_digit, int rem_letter, int pos) +{ + if(pos == 8) + return check_stupid(); + if(rem_digit > 0) + { + for(int i = '0'; i <= '9'; i += 2) + { + g_key[pos] = i; + if(search_stupid_rec(rem_digit - 1, rem_letter, pos + 1)) + return true; + } + } + if(rem_letter > 0) + { +#ifdef HEX_MAJ + for(int i = 'a' - 1; i <= 'f'; i += 2) +#else + for(int i = 'A' - 1; i <= 'F'; i += 2) +#endif + { + g_key[pos] = i; + if(search_stupid_rec(rem_digit, rem_letter - 1, pos + 1)) + return true; + } + } + return false; +} + +static bool search_stupid(int rem_digit, int rem_letter) +{ + return search_stupid_rec(rem_digit, rem_letter, 0); +} + +bool keysig_search_ascii_stupid(uint8_t *cipher, keysig_notify_fn_t notify, void *user) +{ + keysig_search_init(); + memcpy(g_cipher, cipher, 8); + g_notify = notify; + g_user = user; +#if 1 + return search_stupid(4, 4) || + search_stupid(3, 5) || search_stupid(5, 3) || + search_stupid(2, 6) || search_stupid(6, 2) || + search_stupid(1, 7) || search_stupid(7, 1) || + search_stupid(0, 8) || search_stupid(8, 0); +#else +#define do(i) for(int a##i = 0; a##i < sizeof(hex_digits); a##i++) { g_key[i] = hex_digits[a##i]; +#define od() } + + do(0)do(1)do(2)do(3)do(4)do(5)do(6)do(7) + if(check_stupid()) return true; + od()od()od()od()od()od()od()od() +#undef do +#undef od + return false; +#endif +} + +bool keysig_search_ascii_brute(uint8_t *cipher, keysig_notify_fn_t notify, void *user) +{ + keysig_search_init(); + return false; +} + +struct keysig_search_desc_t keysig_search_desc[KEYSIG_SEARCH_LAST] = +{ + [KEYSIG_SEARCH_NONE] = + { + .name = "none", + .fn = NULL, + .comment = "don't use", + }, + [KEYSIG_SEARCH_ASCII_STUPID] = + { + .name = "ascii-stupid", + .fn = keysig_search_ascii_stupid, + .comment = "Try to find a balance ascii key ignoring lsb" + }, + [KEYSIG_SEARCH_ASCII_BRUTE] = + { + .name = "ascii-brute", + .fn = keysig_search_ascii_brute, + .comment = "Brute force all ASCII keys" + }, +}; diff --git a/utils/nwztools/upgtools/keysig_search.h b/utils/nwztools/upgtools/keysig_search.h new file mode 100644 index 0000000000..46639dfb47 --- /dev/null +++ b/utils/nwztools/upgtools/keysig_search.h @@ -0,0 +1,50 @@ +/*************************************************************************** + * __________ __ ___. + * 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 __keysig_search_h__ +#define __keysig_search_h__ + +#include +#include + +enum keysig_search_method_t +{ + KEYSIG_SEARCH_NONE = 0, + KEYSIG_SEARCH_FIRST, + KEYSIG_SEARCH_ASCII_STUPID = KEYSIG_SEARCH_FIRST, + KEYSIG_SEARCH_ASCII_BRUTE, + KEYSIG_SEARCH_LAST +}; + +/* notify returns true if the key seems ok */ +typedef bool (*keysig_notify_fn_t)(void *user, uint8_t key[8], uint8_t sig[8]); +/* returns true if a key was accepted by notify */ +typedef bool (*keysig_search_fn_t)(uint8_t *cipher, keysig_notify_fn_t notify, void *user); + +struct keysig_search_desc_t +{ + const char *name; + const char *comment; + keysig_search_fn_t fn; +}; + +struct keysig_search_desc_t keysig_search_desc[KEYSIG_SEARCH_LAST]; + +#endif /* __keysig_search_h__ */ diff --git a/utils/nwztools/upgtools/mg.cpp b/utils/nwztools/upgtools/mg.cpp new file mode 100644 index 0000000000..8816259755 --- /dev/null +++ b/utils/nwztools/upgtools/mg.cpp @@ -0,0 +1,67 @@ +/*************************************************************************** + * __________ __ ___. + * 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 "mg.h" +#include +#include +#include +#include +#include + +using namespace CryptoPP; +namespace +{ + ECB_Mode< DES >::Decryption g_dec; + ECB_Mode< DES >::Encryption g_enc; + + inline int dec_des_ecb(void *in, int size, void *out, uint8_t *key) + { + g_dec.SetKey(key, 8); + g_dec.ProcessData((byte*)out, (byte*)in, size); + return 0; + } + + inline int enc_des_ecb(void *in, int size, void *out, uint8_t *key) + { + g_enc.SetKey(key, 8); + g_enc.ProcessData((byte*)out, (byte*)in, size); + return 0; + } +} + +int mg_decrypt_fw(void *in, int size, void *out, uint8_t *key) +{ + return dec_des_ecb(in, size, out, key); +} + +int mg_encrypt_fw(void *in, int size, void *out, uint8_t *key) +{ + return enc_des_ecb(in, size, out, key); +} + +int mg_decrypt_pass(void *in, int size, void *out, uint8_t *key) +{ + return dec_des_ecb(in, size, out, key); +} + +int mg_encrypt_pass(void *in, int size, void *out, uint8_t *key) +{ + return enc_des_ecb(in, size, out, key); +} diff --git a/utils/nwztools/upgtools/mg.h b/utils/nwztools/upgtools/mg.h new file mode 100644 index 0000000000..a0c1f2ef65 --- /dev/null +++ b/utils/nwztools/upgtools/mg.h @@ -0,0 +1,37 @@ +/*************************************************************************** + * __________ __ ___. + * 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 __mg_h__ +#define __mg_h__ + +#include + +#ifdef __cplusplus +extern "C" { +#endif +int mg_decrypt_fw(void *in, int size, void *out, uint8_t *key); +int mg_encrypt_fw(void *in, int size, void *out, uint8_t *key); +int mg_decrypt_pass(void *in, int size, void *out, uint8_t *key); +int mg_encrypt_pass(void *in, int size, void *out, uint8_t *key); +#ifdef __cplusplus +} +#endif + +#endif /* __mg_h__ */ \ No newline at end of file diff --git a/utils/nwztools/upgtools/misc.c b/utils/nwztools/upgtools/misc.c new file mode 100644 index 0000000000..108235e7fd --- /dev/null +++ b/utils/nwztools/upgtools/misc.c @@ -0,0 +1,53 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2010 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 +#include +#include +#include +#include "misc.h" + +char OFF[] = { 0x1b, 0x5b, 0x31, 0x3b, '0', '0', 0x6d, '\0' }; + +char GREY[] = { 0x1b, 0x5b, 0x31, 0x3b, '3', '0', 0x6d, '\0' }; +char RED[] = { 0x1b, 0x5b, 0x31, 0x3b, '3', '1', 0x6d, '\0' }; +char GREEN[] = { 0x1b, 0x5b, 0x31, 0x3b, '3', '2', 0x6d, '\0' }; +char YELLOW[] = { 0x1b, 0x5b, 0x31, 0x3b, '3', '3', 0x6d, '\0' }; +char BLUE[] = { 0x1b, 0x5b, 0x31, 0x3b, '3', '4', 0x6d, '\0' }; + +static bool g_color_enable = true; + +void *xmalloc(size_t s) +{ + void * r = malloc(s); + if(!r) bugp("malloc"); + return r; +} + +void enable_color(bool enable) +{ + g_color_enable = enable; +} + +void color(color_t c) +{ + if(g_color_enable) + printf("%s", (char *)c); +} diff --git a/utils/nwztools/upgtools/misc.h b/utils/nwztools/upgtools/misc.h new file mode 100644 index 0000000000..96666a2eff --- /dev/null +++ b/utils/nwztools/upgtools/misc.h @@ -0,0 +1,46 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2010 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 __MISC_H__ +#define __MISC_H__ + +#include +#include + +#define _STR(a) #a +#define STR(a) _STR(a) + +#define bug(...) do { fprintf(stderr,"["__FILE__":"STR(__LINE__)"]ERROR: "__VA_ARGS__); exit(1); } while(0) +#define bugp(...) do { fprintf(stderr, __VA_ARGS__); perror(" "); exit(1); } while(0) + +#define ROUND_UP(val, round) ((((val) + (round) - 1) / (round)) * (round)) + +typedef char color_t[]; + +extern color_t OFF, GREY, RED, GREEN, YELLOW, BLUE; +void *xmalloc(size_t s); +void color(color_t c); +void enable_color(bool enable); + +#define cprintf(col, ...) do {color(col); printf(__VA_ARGS__); }while(0) + +#define cprintf_field(str1, ...) do{ cprintf(GREEN, str1); cprintf(YELLOW, __VA_ARGS__); }while(0) + +#endif /* __MISC_H__ */ diff --git a/utils/nwztools/upgtools/upgtool.c b/utils/nwztools/upgtools/upgtool.c new file mode 100644 index 0000000000..73303e72d3 --- /dev/null +++ b/utils/nwztools/upgtools/upgtool.c @@ -0,0 +1,542 @@ +/*************************************************************************** + * __________ __ ___. + * 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 +#include +#include +#include +#include +#include +#include +#include +#include "misc.h" +#include "elf.h" +#include +#include +#include "crypt.h" +#include "fwp.h" +#include "keysig_search.h" + +#ifndef MIN +#define MIN(a,b) ((a) < (b) ? (a) : (b)) +#endif + +bool g_debug = false; +static char *g_out_prefix = NULL; +static char *g_in_file = NULL; +bool g_force = false; +static const char *g_model = NULL; +static int g_model_index = -1; +static char *g_kas = NULL; +static char *g_key = NULL; +static char *g_sig = NULL; + +enum keysig_search_method_t g_keysig_search = KEYSIG_SEARCH_NONE; + + +#define let_the_force_flow(x) do { if(!g_force) return x; } while(0) +#define continue_the_force(x) if(x) let_the_force_flow(x) + +#define check_field(v_exp, v_have, str_ok, str_bad) \ + if((v_exp) != (v_have)) \ + { cprintf(RED, str_bad); let_the_force_flow(__LINE__); } \ + else { cprintf(RED, str_ok); } + +static void print_hex(void *p, int size, int unit) +{ + uint8_t *p8 = p; + uint16_t *p16 = p; + uint32_t *p32 = p; + for(int i = 0; i < size; i += unit, p8++, p16++, p32++) + { + if(i != 0 && (i % 16) == 0) + printf("\n"); + if(unit == 1) + printf(" %02x", *p8); + else if(unit == 2) + printf(" %04x", *p16); + else + printf(" %08x", *p32); + } +} + +/* key and signature */ +struct nwz_kas_t +{ + char kas[NWZ_KAS_SIZE]; +}; + +#define HAS_KAS (1 << 0) +#define HAS_KEY (1 << 1) +#define HAS_SIG (1 << 2) +#define CONFIRMED (1 << 3) + +struct nwz_model_t +{ + const char *model; + unsigned flags; + struct nwz_kas_t kas; + char key[8]; + char sig[8]; +}; + +struct upg_md5_t +{ + uint8_t md5[16]; +}__attribute__((packed)); + +struct upg_header_t +{ + char sig[8]; + uint32_t nr_files; + uint32_t unk; +} __attribute__((packed)); + +struct upg_entry_t +{ + uint32_t offset; + uint32_t size; +} __attribute__((packed)); + +struct nwz_model_t g_model_list[] = +{ + { "nwz-e463", HAS_KAS | HAS_KEY | HAS_SIG | CONFIRMED, {"89d813f8f966efdebd9c9e0ea98156d2"}, "eb4431eb", "4f1d9cac" }, + { "nwz-a86x", HAS_KEY | HAS_SIG, {""}, "c824e4e2", "7c262bb0" }, + { "nw-a82x", HAS_KEY | HAS_SIG, {""}, "4df06482", "07fa0b6e" }, +}; + +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'; +} + +static int decrypt_keysig(char keysig[NWZ_KEYSIG_SIZE]) +{ + uint8_t src[16]; + for(int i = 32; i < NWZ_KEYSIG_SIZE; i++) + keysig[i] = 0; + for(int index = 0; index < 16; index++) + { + int a = digit_value(keysig[index * 2]); + int b = digit_value(keysig[index * 2 + 1]); + if(a < 0 || b < 0) + { + cprintf(GREY, "Invalid KAS !\n"); + return -1; + } + src[index] = a << 4 | b; + } + fwp_setkey("ed295076"); + fwp_crypt(src, sizeof(src), 1); + memcpy(keysig + 33, src, 8); + memcpy(keysig + 42, src + 8, 8); + return 0; +} + +static bool upg_notify_keysig(void *user, uint8_t key[8], uint8_t sig[8]) +{ + memcpy(user + 33, key, 8); + memcpy(user + 42, sig, 8); + return true; +} + +static int do_upg(void *buf, long size) +{ + struct upg_md5_t *md5 = buf; + cprintf(BLUE, "Preliminary\n"); + cprintf(GREEN, " MD5: "); + for(int i = 0; i < 16; i++) + cprintf(YELLOW, "%02x", md5->md5[i]); + printf(" "); + + uint8_t actual_md5[MD5_DIGEST_LENGTH]; + { + MD5_CTX c; + MD5_Init(&c); + MD5_Update(&c, md5 + 1, size - sizeof(struct upg_header_t)); + MD5_Final(actual_md5, &c); + } + check_field(memcmp(actual_md5, md5->md5, 16), 0, "Ok\n", "Mismatch\n"); + + if(g_model_index == -1 && g_keysig_search == KEYSIG_SEARCH_NONE && g_key == NULL && g_kas == NULL) + { + cprintf(GREY, "A KAS or a keysig is needed to decrypt the firmware\n"); + cprintf(GREY, "You have the following options(see hel for more details):\n"); + cprintf(GREY, "- select a model with a known KAS\n"); + cprintf(GREY, "- specify an explicit KAS or key(+optional sig)\n"); + cprintf(GREY, "- let me try to find the keysig(slow !)\n"); + return 1; + } + + struct nwz_kas_t kas; + char keysig[NWZ_KEYSIG_SIZE]; + + memset(kas.kas, '?', NWZ_KAS_SIZE); + memset(keysig, '?', NWZ_KEYSIG_SIZE); + keysig[32] = keysig[41] = keysig[50] = 0; + + if(g_kas) + { + if(strlen(g_kas) != NWZ_KAS_SIZE) + { + cprintf(GREY, "The specified KAS has wrong length (must be %d hex digits)\n", NWZ_KAS_SIZE); + return 4; + } + memcpy(keysig, g_kas, NWZ_KAS_SIZE); + decrypt_keysig(keysig); + g_kas = keysig; + g_key = keysig + 33; + g_sig = keysig + 42; + } + else if(g_key) + { + if(strlen(g_key) != 8) + { + cprintf(GREY, "The specified key has wrong length (must be 8 hex digits)\n"); + return 4; + } + if(g_sig && strlen(g_sig) != 8) + { + cprintf(GREY, "The specified sig has wrong length (must be 8 hex digits)\n"); + return 5; + } + + memcpy(keysig + 33, g_key, 8); + if(!g_sig) + cprintf(GREY, "Warning: you have specified a key but no sig, I won't be able to do any checks\n"); + else + memcpy(keysig + 42, g_sig, 8); + g_key = keysig + 33; + if(g_sig) + g_sig = keysig + 42; + } + else if(g_model_index == -1) + { + cprintf(BLUE, "keysig Search\n"); + cprintf_field(" Method: ", "%s\n", keysig_search_desc[g_keysig_search].name); + bool ok = keysig_search_desc[g_keysig_search].fn((void *)(md5 + 1), &upg_notify_keysig, keysig); + cprintf(GREEN, " Result: "); + cprintf(ok ? YELLOW : RED, "%s\n", ok ? "Key found" : "No key found"); + if(!ok) + return 2; + g_key = keysig + 33; + g_sig = keysig + 42; + } + else + { + if(g_model_list[g_model_index].flags & HAS_KAS) + g_kas = g_model_list[g_model_index].kas.kas; + if(g_model_list[g_model_index].flags & HAS_KEY) + g_key = g_model_list[g_model_index].key; + if(g_model_list[g_model_index].flags & HAS_SIG) + g_sig = g_model_list[g_model_index].sig; + + if(g_kas) + { + memcpy(keysig, g_kas, NWZ_KAS_SIZE); + decrypt_keysig(keysig); + g_kas = keysig; + g_key = keysig + 33; + g_sig = keysig + 42; + } + else + { + if(g_key) + { + memcpy(keysig + 33, g_key, 8); + g_key = keysig + 33; + } + if(g_sig) + { + memcpy(keysig + 42, g_sig, 8); + g_sig = keysig + 42; + } + } + } + + if(!g_kas) + { + g_kas = keysig; + fwp_setkey("ed295076"); + if(g_key) + { + memcpy(kas.kas, g_key, 8); + fwp_crypt(kas.kas, 8, 0); + for(int i = 0; i < 8; i++) + { + g_kas[2 * i] = hex_digit((kas.kas[i] >> 4) & 0xf); + g_kas[2 * i + 1] = hex_digit(kas.kas[i] & 0xf); + } + } + if(g_sig) + { + memcpy(kas.kas + 8, g_sig, 8); + fwp_crypt(kas.kas + 8, 8, 0); + for(int i = 8; i < 16; i++) + { + g_kas[2 * i] = hex_digit((kas.kas[i] >> 4) & 0xf); + g_kas[2 * i + 1] = hex_digit(kas.kas[i] & 0xf); + } + } + } + + cprintf(BLUE, "Keys\n"); + cprintf_field(" KAS: ", "%."STR(NWZ_KAS_SIZE)"s\n", g_kas); + cprintf_field(" Key: ", "%s\n", g_key); + if(g_sig) + cprintf_field(" Sig: ", "%s\n", g_sig); + + struct upg_header_t *hdr = (void *)(md5 + 1); + int ret = fwp_read(hdr, sizeof(struct upg_header_t), hdr, (void *)g_key); + if(ret) + return ret; + + cprintf(BLUE, "Header\n"); + cprintf_field(" Signature:", " "); + for(int i = 0; i < 8; i++) + cprintf(YELLOW, "%c", isprint(hdr->sig[i]) ? hdr->sig[i] : '.'); + if(g_sig) + { + check_field(memcmp(hdr->sig, g_sig, 8), 0, " OK\n", " Mismatch\n"); + } + else + cprintf(RED, " Can't check\n"); + cprintf_field(" Files: ", "%d\n", hdr->nr_files); + cprintf_field(" Unk: ", "0x%x\n", hdr->unk); + + cprintf(BLUE, "Files\n"); + struct upg_entry_t *entry = (void *)(hdr + 1); + for(unsigned i = 0; i < hdr->nr_files; i++, entry++) + { + int ret = fwp_read(entry, sizeof(struct upg_entry_t), entry, (void *)g_key); + if(ret) + return ret; + cprintf(GREY, " File"); + cprintf(RED, " %d\n", i); + cprintf_field(" Offset: ", "0x%x\n", entry->offset); + cprintf_field(" Size: ", "0x%x\n", entry->size); + + if(g_out_prefix) + { + char *str = malloc(strlen(g_out_prefix) + 32); + sprintf(str, "%s/%d.bin", g_out_prefix, i); + FILE *f = fopen(str, "wb"); + if(f) + { + int ret = fwp_read(buf + entry->offset, entry->size, + buf + entry->offset, (void *)g_key); + if(ret) + return ret; + fwrite(buf + entry->offset, 1, entry->size, f); + + fclose(f); + } + else + cprintf(GREY, "Cannot open '%s' for writing\n", str); + } + } + + return 0; +} + +static void usage(void) +{ + color(OFF); + printf("Usage: upgtool [options] firmware\n"); + printf("Options:\n"); + printf(" -o \t\tSet output prefix\n"); + printf(" -f/--force\t\tForce to continue on errors\n"); + printf(" -?/--help\t\tDisplay this message\n"); + printf(" -d/--debug\t\tDisplay debug messages\n"); + printf(" -c/--no-color\t\tDisable color output\n"); + printf(" -m/--model \tSelect model (or ? to list them)\n"); + printf(" -l/--search \tTry to find the keysig\n"); + printf(" -a/--kas \tForce KAS\n"); + printf(" -k/--key \tForce key\n"); + printf(" -s/--sig \tForce sig\n"); + printf("keysig search method:\n"); + for(int i = KEYSIG_SEARCH_FIRST; i < KEYSIG_SEARCH_LAST; i++) + printf(" %s\t%s\n", keysig_search_desc[i].name, keysig_search_desc[i].comment); + exit(1); +} + +int main(int argc, char **argv) +{ + while(1) + { + static struct option long_options[] = + { + {"help", no_argument, 0, '?'}, + {"debug", no_argument, 0, 'd'}, + {"no-color", no_argument, 0, 'c'}, + {"force", no_argument, 0, 'f'}, + {"model", required_argument, 0, 'm'}, + {"search", required_argument, 0, 'l'}, + {"kas", required_argument, 0, 'a'}, + {"key", required_argument, 0, 'k'}, + {"sig", required_argument, 0, 's'}, + {0, 0, 0, 0} + }; + + int c = getopt_long(argc, argv, "?dcfo:m:l:a:k:s:", long_options, NULL); + if(c == -1) + break; + switch(c) + { + case -1: + break; + case 'c': + enable_color(false); + break; + case 'd': + g_debug = true; + break; + case 'f': + g_force = true; + break; + case '?': + usage(); + break; + case 'o': + g_out_prefix = optarg; + break; + case 'm': + g_model = optarg; + break; + case 'l': + g_keysig_search = KEYSIG_SEARCH_NONE; + for(int i = KEYSIG_SEARCH_FIRST; i < KEYSIG_SEARCH_LAST; i++) + if(strcmp(keysig_search_desc[i].name, optarg) == 0) + g_keysig_search = i; + if(g_keysig_search == KEYSIG_SEARCH_NONE) + { + cprintf(GREY, "Unknown keysig search method '%s'\n", optarg); + return 1; + } + break; + case 'a': + g_kas = optarg; + break; + case 'k': + g_key = optarg; + break; + case 's': + g_sig = optarg; + break; + default: + abort(); + } + } + + if(g_model && strcmp(g_model, "?") == 0) + { + cprintf(BLUE, "Model list:\n"); + for(unsigned i = 0; i < sizeof(g_model_list) / sizeof(g_model_list[0]); i++) + { + cprintf(GREEN, " %s:", g_model_list[i].model); + if(g_model_list[i].flags & HAS_KAS) + { + cprintf(RED, " kas="); + cprintf(YELLOW, "%."STR(NWZ_KAS_SIZE)"s", g_model_list[i].kas.kas); + } + if(g_model_list[i].flags & HAS_KEY) + { + cprintf(RED, " key="); + cprintf(YELLOW, "%.8s", g_model_list[i].key); + } + if(g_model_list[i].flags & HAS_SIG) + { + cprintf(RED, " sig="); + cprintf(YELLOW, "%.8s", g_model_list[i].sig); + } + if(g_model_list[i].flags & CONFIRMED) + cprintf(RED, " confirmed"); + else + cprintf(RED, " guessed"); + printf("\n"); + } + return 1; + } + + if(g_model) + { + for(unsigned i = 0; i < sizeof(g_model_list) / sizeof(g_model_list[0]); i++) + if(strcmp(g_model, g_model_list[i].model) == 0) + g_model_index = i; + if(g_model_index == -1) + cprintf(GREY, "Warning: unknown model %s\n", g_model); + } + + if(argc - optind != 1) + { + usage(); + return 1; + } + + g_in_file = argv[optind]; + FILE *fin = fopen(g_in_file, "r"); + if(fin == NULL) + { + perror("Cannot open boot file"); + return 1; + } + fseek(fin, 0, SEEK_END); + long size = ftell(fin); + fseek(fin, 0, SEEK_SET); + + void *buf = malloc(size); + if(buf == NULL) + { + perror("Cannot allocate memory"); + return 1; + } + + if(fread(buf, size, 1, fin) != 1) + { + perror("Cannot read file"); + return 1; + } + + fclose(fin); + + int ret = do_upg(buf, size); + if(ret != 0) + { + cprintf(GREY, "Error: %d", ret); + if(!g_force) + cprintf(GREY, " (use --force to force processing)"); + printf("\n"); + ret = 2; + } + free(buf); + + color(OFF); + + return ret; +} + -- cgit