/*************************************************************************** * __________ __ ___. * Open \______ \ ____ ____ | | _\_ |__ _______ ___ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ * \/ \/ \/ \/ \/ * $Id$ * * Copyright (C) 2010 Bertrik Sikken * * 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. * ****************************************************************************/ /* * .sb file parser and chunk extractor * * Based on amsinfo, which is * Copyright © 2008 Rafaël Carré */ #define _ISOC99_SOURCE /* snprintf() */ #include #include #include #include #include #include #include #include #include #include #include #include "crypto.h" #include "elf.h" #if 1 /* ANSI colors */ # define color(a) printf("%s",a) 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' }; #else /* disable colors */ # define color(a) #endif #define bug(...) do { fprintf(stderr,"ERROR: "__VA_ARGS__); exit(1); } while(0) #define bugp(a) do { perror("ERROR: "a); exit(1); } while(0) /* byte swapping */ #define get32le(a) ((uint32_t) \ ( buf[a+3] << 24 | buf[a+2] << 16 | buf[a+1] << 8 | buf[a] )) #define get16le(a) ((uint16_t)( buf[a+1] << 8 | buf[a] )) /* all blocks are sized as a multiple of 0x1ff */ #define PAD_TO_BOUNDARY(x) (((x) + 0x1ff) & ~0x1ff) /* If you find a firmware that breaks the known format ^^ */ #define assert(a) do { if(!(a)) { fprintf(stderr,"Assertion \"%s\" failed in %s() line %d!\n\nPlease send us your firmware!\n",#a,__func__,__LINE__); exit(1); } } while(0) /* globals */ size_t sz; /* file size */ uint8_t *buf; /* file content */ #define PREFIX_SIZE 128 char out_prefix[PREFIX_SIZE]; const char *key_file; #define SB_INST_NOP 0x0 #define SB_INST_TAG 0x1 #define SB_INST_LOAD 0x2 #define SB_INST_FILL 0x3 #define SB_INST_JUMP 0x4 #define SB_INST_CALL 0x5 #define SB_INST_MODE 0x6 struct sb_instruction_header_t { uint8_t checksum; uint8_t opcode; uint16_t zero_except_for_tag; } __attribute__((packed)); struct sb_instruction_load_t { struct sb_instruction_header_t hdr; uint32_t addr; uint32_t len; uint32_t crc; } __attribute__((packed)); struct sb_instruction_fill_t { struct sb_instruction_header_t hdr; uint32_t addr; uint32_t len; uint32_t pattern; } __attribute__((packed)); struct sb_instruction_call_t { struct sb_instruction_header_t hdr; uint32_t addr; uint32_t zero; uint32_t arg; } __attribute__((packed)); void *xmalloc(size_t s) /* malloc helper, used in elf.c */ { void * r = malloc(s); if(!r) bugp("malloc"); return r; } static char getchr(int offset) { char c; c = buf[offset]; return isprint(c) ? c : '_'; } static void getstrle(char string[], int offset) { int i; for (i = 0; i < 4; i++) { string[i] = getchr(offset + 3 - i); } string[4] = 0; } static void getstrbe(char string[], int offset) { int i; for (i = 0; i < 4; i++) { string[i] = getchr(offset + i); } string[4] = 0; } static void printhex(int offset, int len) { int i; for (i = 0; i < len; i++) { printf("%02X ", buf[offset + i]); } printf("\n"); } static void print_key(byte key[16]) { for(int i = 0; i < 16; i++) printf("%02X ", key[i]); } static void print_sha1(byte sha[20]) { for(int i = 0; i < 20; i++) printf("%02X ", sha[i]); } /* verify the firmware header */ static void check(unsigned long filesize) { /* check STMP marker */ char stmp[5]; getstrbe(stmp, 0x14); assert(strcmp(stmp, "STMP") == 0); color(GREEN); /* get total size */ unsigned long totalsize = 16 * get32le(0x1C); color(GREEN); assert(filesize == totalsize); } int convxdigit(char digit, byte *val) { if(digit >= '0' && digit <= '9') { *val = digit - '0'; return 0; } else if(digit >= 'A' && digit <= 'F') { *val = digit - 'A' + 10; return 0; } else if(digit >= 'a' && digit <= 'f') { *val = digit - 'a' + 10; return 0; } else return 1; } typedef byte (*key_array_t)[16]; static key_array_t read_keys(int num_keys) { int size; struct stat st; int fd = open(key_file,O_RDONLY); if(fd == -1) bugp("opening key file failed"); if(fstat(fd,&st) == -1) bugp("key file stat() failed"); size = st.st_size; char *buf = xmalloc(size); if(read(fd,buf,sz)!=(ssize_t)size) bugp("reading key file"); close(fd); key_array_t keys = xmalloc(sizeof(byte[16]) * num_keys); int pos = 0; for(int i = 0; i < num_keys; i++) { /* skip ws */ while(pos < size && isspace(buf[pos])) pos++; /* enough space ? */ if((pos + 32) > size) bugp("invalid key file (not enough keys)"); for(int j = 0; j < 16; j++) { byte a, b; if(convxdigit(buf[pos + 2 * j], &a) || convxdigit(buf[pos + 2 * j + 1], &b)) bugp(" invalid key, it should be a 128-bit key written in hexadecimal\n"); keys[i][j] = (a << 4) | b; } pos += 32; } free(buf); return keys; } #define ROUND_UP(val, round) ((((val) + (round) - 1) / (round)) * (round)) static uint8_t instruction_checksum(struct sb_instruction_header_t *hdr) { uint8_t sum = 90; byte *ptr = (byte *)hdr; for(int i = 1; i < 16; i++) sum += ptr[i]; return sum; } static void elf_write(void *user, uint32_t addr, const void *buf, size_t count) { FILE *f = user; fseek(f, addr, SEEK_SET); fwrite(buf, count, 1, f); } static void extract_elf_section(struct elf_params_t *elf, int count, const char *prefix) { char *filename = xmalloc(strlen(prefix) + 32); sprintf(filename, "%s.%d.elf", prefix, count); printf("write %s\n", filename); FILE *fd = fopen(filename, "wb"); free(filename); if(fd == NULL) return ; elf_output(elf, elf_write, fd); fclose(fd); } static void extract_section(int data_sec, char name[5], byte *buf, int size, const char *indent) { char filename[PREFIX_SIZE + 32]; snprintf(filename, sizeof filename, "%s%s.bin", out_prefix, name); FILE *fd = fopen(filename, "wb"); if (fd != NULL) { fwrite(buf, size, 1, fd); fclose(fd); } if(data_sec) return; snprintf(filename, sizeof filename, "%s%s", out_prefix, name); /* elf construction */ struct elf_params_t elf; elf_init(&elf); int elf_count = 0; /* Pretty print the content */ int pos = 0; while(pos < size) { struct sb_instruction_header_t *hdr = (struct sb_instruction_header_t *)&buf[pos]; printf("%s", indent); uint8_t checksum = instruction_checksum(hdr); if(checksum != hdr->checksum) { color(GREY); printf("[Bad checksum]"); } if(hdr->opcode == SB_INST_LOAD) { struct sb_instruction_load_t *load = (struct sb_instruction_load_t *)&buf[pos]; color(RED); printf("LOAD"); color(OFF);printf(" | "); color(BLUE); printf("addr=0x%08x", load->addr); color(OFF);printf(" | "); color(GREEN); printf("len=0x%08x", load->len); color(OFF);printf(" | "); color(YELLOW); printf("crc=0x%08x", load->crc); /* data is padded to 16-byte boundary with random data and crc'ed with it */ uint32_t computed_crc = crc(&buf[pos + sizeof(struct sb_instruction_load_t)], ROUND_UP(load->len, 16)); color(RED); if(load->crc == computed_crc) printf(" Ok\n"); else printf(" Failed (crc=0x%08x)\n", computed_crc); /* elf construction */ elf_add_load_section(&elf, load->addr, load->len, &buf[pos + sizeof(struct sb_instruction_load_t)]); pos += load->len + sizeof(struct sb_instruction_load_t); // unsure about rounding pos = ROUND_UP(pos, 16); } else if(hdr->opcode == SB_INST_FILL) { struct sb_instruction_fill_t *fill = (struct sb_instruction_fill_t *)&buf[pos]; color(RED); printf("FILL"); color(OFF);printf(" | "); color(BLUE); printf("addr=0x%08x", fill->addr); color(OFF);printf(" | "); color(GREEN); printf("len=0x%08x", fill->len); color(OFF);printf(" | "); color(YELLOW); printf("pattern=0x%08x\n", fill->pattern); color(OFF); /* elf construction */ elf_add_fill_section(&elf, fill->addr, fill->len, fill->pattern); pos += sizeof(struct sb_instruction_fill_t); // fixme: useless as pos is a multiple of 16 and fill struct is 4-bytes wide ? pos = ROUND_UP(pos, 16); } else if(hdr->opcode == SB_INST_CALL || hdr->opcode == SB_INST_JUMP) { int is_call = (hdr->opcode == SB_INST_CALL); struct sb_instruction_call_t *call = (struct sb_instruction_call_t *)&buf[pos]; color(RED); if(is_call) printf("CALL"); else printf("JUMP"); color(OFF);printf(" | "); color(BLUE); printf("addr=0x%08x", call->addr); color(OFF);printf(" | "); color(GREEN); printf("arg=0x%08x\n", call->arg); color(OFF); /* elf construction */ elf_set_start_addr(&elf, call->addr); extract_elf_section(&elf, elf_count++, filename); elf_release(&elf); elf_init(&elf); pos += sizeof(struct sb_instruction_call_t); // fixme: useless as pos is a multiple of 16 and call struct is 4-bytes wide ? pos = ROUND_UP(pos, 16); } else { color(RED); printf("Unknown instruction %d at address 0x%08lx\n", hdr->opcode, (unsigned long)pos); break; } } if(!elf_is_empty(&elf)) extract_elf_section(&elf, elf_count++, filename); elf_release(&elf); } static void extract(unsigned long filesize) { struct sha_1_params_t sha_1_params; /* Basic header info */ color(BLUE); printf("Basic info:\n"); color(GREEN); printf("\tHeader SHA-1: "); byte *hdr_sha1 = &buf[0]; color(YELLOW); print_sha1(hdr_sha1); /* Check SHA1 sum */ byte computed_sha1[20]; sha_1_init(&sha_1_params); sha_1_update(&sha_1_params, &buf[0x14], 0x4C); sha_1_finish(&sha_1_params); sha_1_output(&sha_1_params, computed_sha1); color(RED); if(memcmp(hdr_sha1, computed_sha1, 20) == 0) printf(" Ok\n"); else printf(" Failed\n"); color(GREEN); printf("\tFlags: "); printhex(0x18, 4); printf("\tTotal file size : %ld\n", filesize); /* Sizes and offsets */ color(BLUE); printf("Sizes and offsets:\n"); color(GREEN); int num_enc = get16le(0x28); printf("\t# of encryption keys = %d\n", num_enc); int num_chunks = get16le(0x2E); printf("\t# of chunk headers = %d\n", num_chunks); /* Versions */ color(BLUE); printf("Versions\n"); color(GREEN); printf("\tRandom 1: "); printhex(0x32, 6); printf("\tRandom 2: "); printhex(0x5A, 6); uint64_t micros_l = get32le(0x38); uint64_t micros_h = get32le(0x3c); uint64_t micros = ((uint64_t)micros_h << 32) | micros_l; time_t seconds = (micros / (uint64_t)1000000L); seconds += 946684800; /* 2000/1/1 0:00:00 */ struct tm *time = gmtime(&seconds); color(GREEN); printf("\tCreation date/time = %s", asctime(time)); int p_maj = get32le(0x40); int p_min = get32le(0x44); int p_sub = get32le(0x48); int c_maj = get32le(0x4C); int c_min = get32le(0x50); int c_sub = get32le(0x54); color(GREEN); printf("\tProduct version = %X.%X.%X\n", p_maj, p_min, p_sub); printf("\tComponent version = %X.%X.%X\n", c_maj, c_min, c_sub); /* encryption cbc-mac */ key_array_t keys = NULL; /* array of 16-bytes keys */ byte real_key[16]; if(num_enc > 0) { keys = read_keys(num_enc); color(BLUE), printf("Encryption data\n"); for(int i = 0; i < num_enc; i++) { color(RED); printf("\tKey %d: ", i); print_key(keys[i]); printf("\n"); color(GREEN); printf("\t\tCBC-MAC of headers: "); /* copy the cbc mac */ byte hdr_cbc_mac[16]; memcpy(hdr_cbc_mac, &buf[0x60 + 16 * num_chunks + 32 * i], 16); color(YELLOW); print_key(hdr_cbc_mac); /* check it */ byte computed_cbc_mac[16]; byte zero[16]; memset(zero, 0, 16); cbc_mac(buf, NULL, 6 + num_chunks, keys[i], zero, &computed_cbc_mac, 1); color(RED); if(memcmp(hdr_cbc_mac, computed_cbc_mac, 16) == 0) printf(" Ok\n"); else printf(" Failed\n"); color(GREEN); printf("\t\tEncrypted key : "); byte (*encrypted_key)[16]; encrypted_key = (key_array_t)&buf[0x60 + 16 * num_chunks + 32 * i + 16]; color(YELLOW); print_key(*encrypted_key); printf("\n"); color(GREEN); /* decrypt */ byte decrypted_key[16]; byte iv[16]; memcpy(iv, buf, 16); /* uses the first 16-bytes of SHA-1 sig as IV */ cbc_mac(*encrypted_key, decrypted_key, 1, keys[i], iv, NULL, 0); printf("\t\tDecrypted key : "); color(YELLOW); print_key(decrypted_key); /* cross-check or copy */ if(i == 0) memcpy(real_key, decrypted_key, 16); else if(memcmp(real_key, decrypted_key, 16) == 0) { color(RED); printf(" Cross-Check Ok"); } else { color(RED); printf(" Cross-Check Failed"); } printf("\n"); } } /* chunks */ color(BLUE); printf("Chunks\n"); for (int i = 0; i < num_chunks; i++) { uint32_t ofs = 0x60 + (i * 16); char name[5]; getstrle(name, ofs + 0); int pos = 16 * get32le(ofs + 4); int size = 16 * get32le(ofs + 8); int flags = get32le(ofs + 12); int data_sec = (flags == 2); int encrypted = !data_sec && (num_enc > 0); color(GREEN); printf("\tChunk '%s'\n", name); printf("\t\tpos = %8x - %8x\n", pos, pos+size); printf("\t\tlen = %8x\n", size); printf("\t\tflags = %8x", flags); color(RED); if(data_sec) printf(" Data Section"); else printf(" Boot Section"); if(encrypted) printf(" (Encrypted)"); printf("\n"); /* save it */ byte *sec = xmalloc(size); if(encrypted) cbc_mac(buf + pos, sec, size / 16, real_key, buf, NULL, 0); else memcpy(sec, buf + pos, size); extract_section(data_sec, name, sec, size, "\t\t\t"); free(sec); } /* final signature */ color(BLUE); printf("Final signature:\n"); color(GREEN); printf("\tEncrypted signature:\n"); color(YELLOW); printf("\t\t"); printhex(filesize - 32, 16); printf("\t\t"); printhex(filesize - 16, 16); /* decrypt it */ byte *encrypted_block = &buf[filesize - 32]; byte decrypted_block[32]; cbc_mac(encrypted_block, decrypted_block, 2, real_key, buf, NULL, 0); color(GREEN); printf("\tDecrypted SHA-1:\n\t\t"); color(YELLOW); print_sha1(decrypted_block); /* check it */ sha_1_init(&sha_1_params); sha_1_update(&sha_1_params, buf, filesize - 32); sha_1_finish(&sha_1_params); sha_1_output(&sha_1_params, computed_sha1); color(RED); if(memcmp(decrypted_block, computed_sha1, 20) == 0) printf(" Ok\n"); else printf(" Failed\n"); } int main(int argc, const char **argv) { int fd; struct stat st; if(argc != 3 && argc != 4) bug("Usage: %s []\n",*argv); if(argc == 4) snprintf(out_prefix, PREFIX_SIZE, "%s", argv[3]); else strcpy(out_prefix, ""); if( (fd = open(argv[1],O_RDONLY)) == -1 ) bugp("opening firmware failed"); key_file = argv[2]; if(fstat(fd,&st) == -1) bugp("firmware stat() failed"); sz = st.st_size; buf=xmalloc(sz); if(read(fd,buf,sz)!=(ssize_t)sz) /* load the whole file into memory */ bugp("reading firmware"); close(fd); check(st.st_size); /* verify header and checksums */ extract(st.st_size); /* split in blocks */ color(OFF); free(buf); return 0; }