Version 1.01
[firmware_extractor.git] / firmware_extractor.c
1 static const char *title =\
2 "Broadcom Consumer Router Firmware payload extractor"
3 ;
4 static const float VERSION = 1.01f;
5
6 static const char *copyright = \
7 "Copyright 2015-2016 TJ <hacker@iam.tj>\n"
8 "Licensed on the terms of the GNU General Public License version 2 or later\n"
9 "Includes FFMPEG LZW library code with Broadcom CMS modifications\n"
10 ;
11
12 static const char *help = \
13 "For routers using the Broadcom CFE ((Customer Premises Equipment) CPE Firmware Environment) and firmware update files.\n\n"
14
15 "Extracts ROMD and LZW payloads from firmware files.\n\n"
16
17 "The program will list and optionally extract/decompress the payloads:\n\n"
18 "$ ./fwex -d /tmp/zyxel/V100AAKL14C0.bin\n"
19 "Broadcom Consumer Router Firmware payload extractor\n"
20 "Version: 1.00\n"
21 "Copyright 2015-2016 TJ <hacker@iam.tj>\n"
22 "Licensed on the terms of the GNU General Public License version 2 or later\n"
23 "Includes FFMPEG LZW library code with Broadcom CMS modifications\n"
24 "\n"
25 "Found ROMD payload 0\n"
26 "  written to /tmp/zyxel/V100AAKL14C0.bin.00.bin\n"
27 "Image Offset:  0x00020000 (131072)\n"
28 "0000 Tag Version: 6\n"
29 "0004 Signature 1: MSTC_6006 (Model: 6006)\n"
30 "0018 Signature 2: ver. 2.0\n"
31 "0026 Chip ID: 63268\n"
32 "002c Board ID: 963168VX\n"
33 "003c Big Endian: Yes\n"
34 "003e Image Len: 25952256 (0x018c0000)\n"
35 "008e External Version: 1.00(AAKL.14)C0\n"
36 "00ae Internal Version: 1.00(AAKL.14)C0\n"
37 "00ce Image Next: 1\n"
38 "00d8 Image Validation Token: 0x1c633f00\n"
39 "00ec Tag Validation Token:   0xbdde4316\n"
40 "     Calculated Image CRC32: 0x1c633f00\n"
41 "     Calculated Tag   CRC32: 0xbdde4316\n"
42 "\n"
43 "Found LZW compressed payload 1\n"
44 "  compressed: 23265 decompressed: 80030 bytes\n"
45 "  written to /tmp/zyxel/V100AAKL14C0.bin.01.bin\n"
46 "Image Offset:  0x018e0020 (26083360)\n"
47 "0000 Image Next: 0\n"
48 "0001 Image Type: IMGDEF (0)\n"
49 "0003 Image Signature: 0\n"
50 "0005 Image Len: 23305 (0x00005b09)\n"
51 "0018 Image Validation Token: 0xd78f9ceb\n"
52 "001c Tag Validation Token:   0x8681f430\n"
53 "     Calculated Image CRC32: 0xd78f9ceb\n"
54 "     Calculated Tag CRC32:   0x8681f430\n"
55 "\n"
56 ;
57
58 #include <stdio.h>
59 #include <stdlib.h>
60 #include <string.h>
61 #include <stdarg.h>
62 #include <sys/types.h>
63 #include <sys/stat.h>
64 #include <fcntl.h>
65 #include <unistd.h>
66 #include <arpa/inet.h>
67 #include <stdint.h>
68 #include <stddef.h>
69 #include <errno.h>
70
71 #define BCMTAG_EXE_USE 1
72
73 #include "heap_reap.h"
74 #include "bcmTag.h"
75 #include "firmware_extractor.h"
76 #include "cms_lzw.h"
77
78 static unsigned int MESSAGE_SIZE = 1024;
79
80 static const char *image_type[] = {"IMGDEF", "ROMD", "HPNA"};
81
82 static char *filename = NULL;
83 static unsigned int opt_decompress, opt_list, opt_quiet;
84 static const char *msg_err_memalloc = "unable to allocate memory for %s (%lu bytes)\n";
85 static const char *lzw_prefix_string = COMPRESSED_CONFIG_HEADER;
86
87 static void
88 pr_usage(int verbose)
89 {
90   fprintf(stderr,
91     "Usage: %s [options] [filename]\n"
92     "  -h               show additional help\n"
93     "  -d               extracts payloads to same directory as 'filename'\n"
94     "  -l               list payloads\n"
95     "\n"
96     "%s",
97     PROGRAM_NAME,
98     verbose ? help : ""
99   );
100 }
101
102 static void
103 pr_error_exit(unsigned int usage, const char *error, ...)
104 {
105  va_list args;
106  char error_message[MESSAGE_SIZE + 1];
107
108  if (errno != 0) {
109    perror(NULL);
110  }
111  if (error) {
112    va_start(args, error);
113    (void) vsnprintf(error_message, MESSAGE_SIZE + 1, error, args);
114    va_end(args);
115  fprintf(stderr, "Error: %s\n", error_message);
116  }
117
118  if (usage) pr_usage(usage);
119
120  heap_and_reap(NULL-1, 0, 0);
121  exit(EXIT_FAILURE);
122 }
123
124 /* calculate standard CRC32
125  *
126  * @param data pointer to start of input data
127  * @param len  length in bytes of data to be checksummed
128  * @param crc32 seed value (Broadcom use 0xffffffff)
129  */
130 unsigned int crc32(const unsigned char *data, ssize_t len, unsigned int crc)
131 {
132    for ( ; len > 0; --len ) {
133      crc = (crc >> 8) ^ Crc32_table[ (crc ^ *data++) & 0xff ];
134    }
135    return crc;
136 }
137
138 int decompress(int fd) {
139   int result = 0;
140   unsigned int crc_header, crc_payload;
141   unsigned char *buffer = NULL;
142   unsigned long header_len = sizeof(FILE_TAG);
143
144   unsigned int offset = 0;
145   unsigned int count = 0;
146   unsigned int next = 1;
147   void *payload = NULL;
148   void *plaintext = NULL;
149   char *filename_lzw = NULL;
150   int fd_out = -1;
151   SINT32 bytes, total_bytes = 0;
152   unsigned int f_lzw_len = strlen(filename) + 8; // filename.00.lzw
153   mode_t fd_mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;
154
155   while (next) {
156     unsigned long len = 0;
157     crc_header = crc_payload = 0xffffffff;
158
159     if (count) // switch struct type after initial file header
160       header_len = sizeof(IMAGE_TAG);
161
162     lseek(fd, offset, SEEK_SET);
163     if (opt_list)
164       printf("Header Offset: 0x%08x (%u)\n", offset, offset);
165
166     if ( (buffer = heap_and_reap(NULL, header_len, 1)) != NULL) {
167       ssize_t qty;
168       if ( (qty = read(fd, buffer, header_len)) < header_len) {
169         if (!opt_quiet)
170           fprintf(stderr, "warning: only able to read %ld of %ld bytes\n", qty, header_len);
171         header_len = qty;
172       }
173
174       PFILE_TAG pfile = (PFILE_TAG) buffer;
175       PIMAGE_TAG pimage = (PIMAGE_TAG) buffer;
176
177       if (!count) {
178         next = *(unsigned char *)(pfile->imageNext);
179         len = atol(pfile->totalImageLen);
180         offset = 0x20000;
181       } else {
182         next = *(unsigned char *)(pimage->imageNext);
183         len = atol(pimage->imageLen);
184         offset += sizeof(IMAGE_TAG);
185       }
186
187       crc_header = crc32(buffer, header_len - (count ? CRC_LEN : TOKEN_LEN), crc_header);
188
189       // read payload and calculate CRC32
190       lseek(fd, offset, SEEK_SET);
191       if ( (payload = heap_and_reap(NULL, len, 1)) != NULL) {
192
193         if ( (qty = read(fd, payload, len)) < len) {
194           fprintf(stderr, "skipping CRC calculation: only able to read %ld of %ld bytes\n", qty, len);
195         } else {
196           crc_payload = crc32(payload, len, crc_payload);
197
198           if (opt_decompress) {
199
200             // allocate memory first time it is needed
201             if (!filename_lzw)
202               if((filename_lzw = heap_and_reap(NULL, f_lzw_len, 1)) == NULL)
203                 pr_error_exit(0, msg_err_memalloc, "filename_lzw", f_lzw_len);
204
205             snprintf(filename_lzw, f_lzw_len, "%s.%02x.bin", filename, count);
206
207             if ((fd_out = open(filename_lzw, O_WRONLY | O_CREAT, fd_mode)) == -1)
208               pr_error_exit(0, "unable to open '%s' for writing\n", filename_lzw);
209
210           }
211
212           if (strncmp((char *)payload, lzw_prefix_string, strlen(lzw_prefix_string)) != 0) {
213
214             printf("Found %s payload %u\n", (count == 0 ? "ROMD" : "unknown"), count);
215             if (opt_decompress)
216               write(fd_out, payload, len);
217
218           } else {
219             printf("Found LZW compressed payload %u\n", count);
220             unsigned int plaintext_len = len * 100;
221
222             if ((plaintext = heap_and_reap(NULL, plaintext_len, 1)) != NULL) {
223               LZWDecoderState *decoder_state = NULL;
224
225               cmsLzw_initDecoder(&decoder_state, payload + COMPRESSED_CONFIG_HEADER_LENGTH, len - COMPRESSED_CONFIG_HEADER_LENGTH);
226
227               do {
228                 bytes = cmsLzw_decode(decoder_state, plaintext, plaintext_len);
229                 total_bytes += bytes;
230                 if (opt_decompress) {
231                   write(fd_out, plaintext, bytes);
232                 }
233               } while (bytes == plaintext_len);
234
235               cmsLzw_cleanupDecoder(&decoder_state);
236               heap_and_reap(plaintext, 0, 0);
237
238               printf("  compressed: %lu decompressed: %u bytes\n", len - COMPRESSED_CONFIG_HEADER_LENGTH, total_bytes);
239
240             } else {
241               pr_error_exit(0, msg_err_memalloc, "decompressor", plaintext_len);
242             }
243           }
244           if (opt_decompress) {
245             printf("  written to %s\n", filename_lzw);
246             close(fd_out);
247           }
248
249           heap_and_reap(payload, 0, 0);
250         }
251       } else {
252         close(fd);
253         pr_error_exit(0, msg_err_memalloc, "payload", len);
254       }
255
256       if (!count) {
257         printf("Image Offset:  0x%08x (%u)\n", offset, offset);
258         printf("%04lx Tag Version: %s\n"
259                "%04lx Signature 1: %s (Model: %s)\n"
260                "%04lx Signature 2: %s\n"
261                "%04lx Chip ID: %s\n"
262                "%04lx Board ID: %s\n"
263                "%04lx Big Endian: %s\n"
264                "%04lx Image Len: %s (0x%08lx)\n"
265                "%04lx External Version: %s\n"
266                "%04lx Internal Version: %s\n"
267                "%04lx Image Next: %u\n"
268                "%04lx Image Validation Token: 0x%08x\n"
269                "%04lx Tag Validation Token:   0x%08x\n"
270                "     Calculated Image CRC32: 0x%08x\n"
271                "     Calculated Tag   CRC32: 0x%08x\n"
272                "\n",
273                offsetof(struct _FILE_TAG, tagVersion), pfile->tagVersion,
274                offsetof(struct _FILE_TAG, signiture_1), pfile->signiture_1, pfile->signiture_1 + strlen(pfile->signiture_1) + 1,
275                offsetof(struct _FILE_TAG, signiture_2), pfile->signiture_2,
276                offsetof(struct _FILE_TAG, chipId), pfile->chipId,
277                offsetof(struct _FILE_TAG, boardId), pfile->boardId,
278                offsetof(struct _FILE_TAG, bigEndian), *pfile->bigEndian == '1' ? "Yes" : "No",
279                offsetof(struct _FILE_TAG, totalImageLen), pfile->totalImageLen, len,
280                offsetof(struct _FILE_TAG, externalversion), pfile->externalversion,
281                offsetof(struct _FILE_TAG, internalversion), pfile->internalversion,
282                offsetof(struct _FILE_TAG, imageNext), next,
283                offsetof(struct _FILE_TAG, imageValidationToken), ntohl( *((unsigned int *)(pfile->imageValidationToken)) ),
284                offsetof(struct _FILE_TAG, tagValidationToken), ntohl( *((unsigned int *)(pfile->tagValidationToken)) ),
285                crc_payload,
286                crc_header
287         );
288       } else {
289         printf("Image Offset:  0x%08x (%u)\n", offset, offset);
290         printf("%04lx Image Next: %u\n"
291                "%04lx Image Type: %s (%lu)\n"
292                "%04lx Image Signature: %u\n"
293                "%04lx Image Len: %s (0x%08lx)\n"
294                "%04lx Image Validation Token: 0x%08x\n"
295                "%04lx Tag Validation Token:   0x%08x\n"
296                "     Calculated Image CRC32: 0x%08x\n"
297                "     Calculated Tag CRC32:   0x%08x\n"
298                "\n",
299                offsetof(struct _IMAGE_TAG, imageNext), next,
300                offsetof(struct _IMAGE_TAG, imageType), image_type[atol(pimage->imageType)], atol(pimage->imageType),
301                offsetof(struct _IMAGE_TAG, imageSignature), (unsigned int)*pimage->imageSignature,
302                offsetof(struct _IMAGE_TAG, imageLen), pimage->imageLen, len,
303                offsetof(struct _IMAGE_TAG, imageValidationToken), ntohl( *((unsigned int *)(pimage->imageValidationToken)) ),
304                offsetof(struct _IMAGE_TAG, tagValidationToken), ntohl( *((unsigned int *)(pimage->tagValidationToken)) ),
305                crc_payload,
306                crc_header
307         );
308       }
309       // next seek point will be end of current payload
310       offset += len;
311
312       heap_and_reap(buffer, 0, 0);
313
314       ++count;
315
316     } else {
317       close(fd);
318       pr_error_exit(0, "unable to allocate memory (%ld bytes)\n", header_len);
319     }
320   }
321
322   return result;
323 }
324
325 int
326 main(int argc, char **argv)
327 {
328   unsigned int arg;
329   int fd, fd_mode;
330
331   opt_decompress = opt_list = opt_quiet = 0;
332
333   fprintf(stderr, "%s\nVersion: %0.2f\n%s\n", title, VERSION, copyright);
334
335   for (arg = 1; arg < (unsigned) argc; ++arg) {
336     char *p = argv[arg];
337     size_t arg_len = strlen(p);
338
339     if (p[0] == '-') {
340       if(p[1] != 0) {
341         switch (p[1]) {
342           case 'h': // help
343             pr_usage(1);
344             goto end;
345             break;
346           case 'd': // decompress
347             opt_decompress = 1;
348             break;
349           case 'l': // list payloads
350             opt_list = 1;
351             break;
352         }
353       } else {
354         pr_error_exit(0, "cannot read data from stdin; provide a filename");
355       }
356       continue;
357     }
358     else if (!filename) { // remaining non-option must be the filename
359       filename = p;
360     } else {
361       if (!opt_quiet)
362         fprintf(stderr, "Can only process one file; ignoring '%s'\n", p);
363     }
364
365     if (opt_decompress == 1) {
366       ++opt_decompress;
367     }
368     else if (opt_list == 1) {
369       ++opt_list;
370     }
371   }
372
373   fd_mode = O_RDONLY;
374
375   if (filename) {
376    if ((fd = open(filename, fd_mode)) > 0) {
377      decompress(fd);
378      close(fd);
379     } else {
380       fprintf(stderr, "Unable to open for %s (%s)\n", "reading" , filename );
381     }
382   } else {
383     pr_usage(0);
384   }
385
386 end:
387   heap_and_reap(NULL-1, 0, 0);
388   return 0;
389 }
390