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