Fix write length so it includes the updated header (tag) CRC32
[firmware_header_edit.git] / firmware_header_editor.c
1 static const char *title =\
2 "Broadcom Consumer Router Firmware Header Editor"
3 ;
4 static const float VERSION = 1.3f;
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 3\n"
9 ;
10
11 static const char *help = \
12 "For routers using the Broadcom CFE ((Customer Premises Equipment) CPE Firmware Environment) and firmware update files.\n\n"
13
14 "Avoid the 'Invalid Model Id' error reported by the HTTP firmware upload page in a device with an ISP-specific Model ID (E.g. Eircom F1000 uses 6009 rather than ZyXel VMG8324/VMG8924 generic 6006.\n\n"
15
16 "This tool re-generates the header CRC32 checksum (usually at offset 0xEC - 236) of the package header structure and optionally alters the manufacturer Model ID of the firmware image.\n\n"
17
18 "To identify the current Model ID of the device's firmware connect to its terminal using telnet or ssh and query the manufacturer data that is stored in the ROM image:\n\n"
19
20 " > sys atsh\n"
21 " ...\n"
22 " Other Feature Bits     :\n"
23 "           4d 53 60 09 00 00 00 00-00 00 00 00 00 00 00 00\n\n"
24
25 "The first pair of bytes here are ASCII characters 'MS' (code for MitraStar)\n"
26 "The second pair are the Model ID '6009'\n\n"
27
28 "A firmware update file (usually has a .bin suffix) starts with a 256 byte header that describes the contents and contains data-verification checksums.\n"
29 "Specific to Mitrastar (MSTC), and therefore also Zyxel, the Model ID is stored in the 'signiture_1' manufacturer-specific info field starting at offset 4.\n"
30 "This 20-byte field is split into two parts, both ASCII zero-terminated strings:\n\n"
31
32 " a) the manufacturer ID and model ID (e.g. 'MSTC_6006')\n"
33 " b) the model ID (e.g. '6006')\n\n"
34
35 "By replacing the model ID with one matching the specific device and updating the header CRC32 checksum the device's HTTP firmware update interface will accept the file.\n\n"
36 "Example usage:\n\n"
37 "# display detailed help\n"
38 "fwheaditor -h\n"
39 "# display current and calculated header based on default values\n"
40 "fwheaditor V1.00(AAKL.13)C0.bin\n"
41 "# change Model ID to 6009 and write to file only if current Manufacturer is the default\n"
42 "fwheaditor -i 6009 -w V1.00(AAKL.13)C0.bin\n"
43 "# change Model ID to 6009 and write to file only if current Manufacturer is 'BRCM'\n"
44 "fwheaditor -i 6009 -m BRCM -w V1.00(AAHL.13).bin\n"
45 ;
46
47 #include <stdio.h>
48 #include <stdlib.h>
49 #include <string.h>
50 #include <stdarg.h>
51 #include <sys/types.h>
52 #include <sys/stat.h>
53 #include <fcntl.h>
54 #include <unistd.h>
55 #include <arpa/inet.h>
56 #include <stdint.h>
57 #include "heap_reap.h"
58
59 static unsigned int MESSAGE_SIZE = 1024;
60 static unsigned int TAG_VER_LEN = 4;
61 static unsigned int TAG_SIG_LEN = 20;
62 static unsigned int MODEL_ID_LEN = 5;
63 static ssize_t header_len = 236; // 0xEC; default length for Broadcom FILE_TAG header excluding TAG CRC
64 static char *match_manufacturer_id = "MSTC";
65 static char *match_model_id = "6006";
66 static char *model_id = "6006";
67
68 static unsigned int crc32_table[256] = {
69     0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x706AF48F, 0xE963A535, 0x9E6495A3,
70     0x0EDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988, 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91,
71     0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE, 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7,
72     0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, 0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5,
73     0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172, 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B,
74     0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940, 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59,
75     0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F,
76     0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, 0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D,
77     0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433,
78     0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, 0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01,
79     0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, 0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457,
80     0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA, 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65,
81     0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2, 0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB,
82     0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0, 0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9,
83     0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F,
84     0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, 0x2EB40D81, 0xB7BD5C3B, 0xC0BA6CAD,
85     0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A, 0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683,
86     0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8, 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1,
87     0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE, 0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7,
88     0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC, 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5,
89     0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B,
90     0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55, 0x316E8EEF, 0x4669BE79,
91     0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, 0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F,
92     0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D,
93     0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, 0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713,
94     0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, 0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21,
95     0x86D3D2D4, 0xF1D4E242, 0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777,
96     0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C, 0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45,
97     0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2, 0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB,
98     0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9,
99     0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605, 0xCDD70693, 0x54DE5729, 0x23D967BF,
100     0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94, 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D
101 };
102
103 static void
104 pr_usage(int verbose)
105 {
106   fprintf(stderr,
107     "Usage:\n"
108     "  -i  replacement Model ID (default '%s')\n"
109     "  -l  bytes to calculate new CRC32 over (default %ld)\n"
110     "  -m  current Manufacturer ID to match (default '%s')\n"
111     "  -M  current Model ID to match (default '%s')\n"
112     "  -w  write to file (default only prints new values)\n"
113     "  -s  simulate; don't write to file when -w is given\n"
114     "  -t  output for automated test suite\n"
115     "  -q  quiet; only display result\n"
116     "  -h  show additional help\n"
117     "\n"
118     "%s",
119     model_id,
120     header_len,
121     match_manufacturer_id,
122     match_model_id,
123     verbose ? help : ""
124   );
125 }
126
127 static void
128 pr_error_exit(unsigned int usage, const char *error, ...)
129 {
130  va_list args;
131  char error_message[MESSAGE_SIZE + 1];
132
133  if (!error) return;
134
135  va_start(args, error);
136  (void) vsnprintf(error_message, MESSAGE_SIZE + 1, error, args);
137  va_end(args);
138  fprintf(stderr, "Error: %s\n", error_message);
139
140  if (usage) pr_usage(usage);
141
142  heap_and_reap(NULL-1, 0, 0);
143  exit(EXIT_FAILURE);
144 }
145
146 /* calculate standard CRC32
147  *
148  * @param data pointer to start of input data
149  * @param len  length in bytes of data to be checksummed
150  * @param crc32 seed value (Broadcom use 0xffffffff)
151  */
152 unsigned int crc32(const unsigned char *data, ssize_t len, unsigned int crc)
153 {
154    for ( ; len > 0; --len ) {
155      crc = (crc >> 8) ^ crc32_table[ (crc ^ *data++) & 0xff ];
156    }
157    return crc;
158 }
159
160
161 int
162 main(int argc, char **argv)
163 {
164   unsigned int arg, crc;
165   char *filename = NULL;
166   int fd, fd_mode;
167   unsigned char *buffer = NULL;
168   unsigned char *model_id;
169   unsigned int header_crc_offset = header_len;
170   unsigned int manufacturer_id_count = 0;
171   unsigned int model_id_count = 0;
172   char *format_spec_user = "%-12s Manufacturer: %s Model: %s CRC32: %08x Length: %ld File: %s\n";
173   char *format_spec_test = "%-12s %s %s %08x %ld\n";
174   char *format_spec = format_spec_user; // default output format
175
176   unsigned int opt_len, opt_model_id, opt_match_manufacturer_id, opt_match_model_id, opt_write, opt_quiet, opt_simulate, opt_testsuite;
177   opt_len = opt_model_id = opt_match_manufacturer_id = opt_match_model_id = opt_write = opt_quiet = opt_simulate = opt_testsuite = 0;
178
179   fprintf(stderr, "%s\nVersion: %0.2f\n%s\n", title, VERSION, copyright);
180
181   if ((model_id = heap_and_reap(NULL, MODEL_ID_LEN, 1)) == NULL) {
182     pr_error_exit(0, "Unable to allocate memory (%d bytes)\n", MODEL_ID_LEN);
183   }
184   memcpy(model_id, match_model_id, MODEL_ID_LEN);
185
186   for (arg = 1; arg < (unsigned) argc; ++arg) {
187     char *p = argv[arg];
188     size_t arg_len = strlen(p);
189
190     if (p[0] == '-') {
191       if(p[1] != 0) {
192         switch (p[1]) {
193           case 'l': // length of data to generate CRC32 on
194             opt_len = 1;
195             break;
196           case 'i': // replacement model ID
197             opt_model_id = 1;
198             break;
199           case 'm': // match this manufacturer ID
200             opt_match_manufacturer_id = 1;
201             break;
202           case 'M': // match this model ID
203             opt_match_model_id = 1;
204             break;
205           case 'w': // re-write file header
206             opt_write = 1;
207             break;
208           case 's': // simulate
209             opt_simulate = 1;
210             break;
211           case 't': // display machine-readable output for test suite
212             opt_testsuite = 1;
213             format_spec = format_spec_test;
214             break;
215           case 'q': // quiet
216             opt_quiet = 1;
217             break;
218           case 'h': // help
219             pr_usage(1);
220             goto end;
221         }
222       } else {
223         pr_error_exit(0, "cannot read data from stdin; provide a filename");
224       }
225       continue;
226     }
227
228     if (opt_len == 1) {
229       char *format = "%ld";
230       if ( *(p+1) == 'x' || *(p+1) == 'X' ) { // hex length
231         format = "%lx";
232         // FIXME: p = first hex char?
233       }
234       if (sscanf(p, format, &header_len))
235         ++opt_len;
236       else
237         pr_error_exit(0, "Illegal length value (%s)", p);
238
239     } else if (opt_model_id == 1) {
240       if (arg_len == 4) {
241         model_id = p;
242         ++opt_model_id;
243       }  else {
244         pr_error_exit(0, "Model Id ('%s') must be %d characters", p, MODEL_ID_LEN - 1);
245       }
246     } else if (opt_match_manufacturer_id == 1) {
247       match_manufacturer_id = p;
248       ++opt_match_manufacturer_id;
249     } else if (opt_match_model_id == 1) {
250       match_model_id = p;
251       ++opt_match_model_id;
252     } else if (!filename) { // remaining non-option must be the filename
253       filename = p;
254     } else {
255       if (!opt_quiet)
256         fprintf(stderr, "Can only process one file; ignoring '%s'\n", p);
257     }
258   }
259
260   if (!opt_quiet) {
261     if (opt_write == 1)
262        fprintf(stdout, "%s\n", "In-place editing of header");
263     
264     if (opt_simulate == 1)
265       fprintf(stderr, "%s\n", "Simulation mode; no file writes");
266   }
267
268   fd_mode = (opt_write && !opt_simulate) ? O_RDWR : O_RDONLY;
269
270   if (filename) {
271    if ((fd = open(filename, fd_mode)) > 0) {
272      if ( (buffer = heap_and_reap(NULL, header_len + sizeof(crc), 1)) != NULL) {
273        ssize_t qty;
274        if ( (qty = read(fd, buffer, header_len + sizeof(crc))) < header_len) {
275          if (!opt_quiet)
276            fprintf(stderr, "warning: only able to read %ld of %ld bytes\n", qty, header_len);
277          header_len = qty;
278        }
279      } else {
280        close(fd);
281        pr_error_exit(0, "unable to allocate memory (%ld bytes)\n", header_len);
282      }
283      printf( format_spec,
284              opt_testsuite ? "" : "Current",
285              buffer + TAG_VER_LEN,
286              buffer + TAG_VER_LEN + strlen(buffer + TAG_VER_LEN) + 1,
287              ntohl( *((unsigned int *)(buffer + header_crc_offset)) ),
288              header_len,
289              filename);
290
291      // do the model ID replacement in the memory buffer
292      if (strstr(buffer + TAG_VER_LEN, match_manufacturer_id) != NULL) {
293        ++manufacturer_id_count;
294        unsigned char *p = buffer + TAG_VER_LEN + strlen(match_manufacturer_id) + 1; // step over manufacturer ID
295        if ( strstr(p, match_model_id) != NULL)
296          ++model_id_count;
297        for (; p <= buffer + TAG_VER_LEN + TAG_SIG_LEN - MODEL_ID_LEN; ++p) {
298          if (strncmp(p, match_model_id, MODEL_ID_LEN) == 0) {
299            memcpy(p, model_id, MODEL_ID_LEN);
300            ++model_id_count;
301            p = p + MODEL_ID_LEN - 1;
302          }
303        }
304      }
305
306      crc = crc32(buffer, header_len, 0xffffffff);
307
308      char tmp_manufacturer[TAG_SIG_LEN];
309      strcpy(tmp_manufacturer, match_manufacturer_id);
310      strcat(tmp_manufacturer, "_");
311      strcat(tmp_manufacturer, model_id);
312
313      printf( format_spec,
314              opt_testsuite ? "" : "Calculated",
315              tmp_manufacturer,
316              model_id,
317              crc,
318              header_len,
319              filename
320      );
321
322      if (opt_write) {
323        unsigned int tmp = htonl(crc);
324        memcpy(buffer + header_crc_offset, &tmp, sizeof(tmp));
325
326        if (!opt_simulate) {
327          ssize_t write_len = header_len > header_crc_offset + sizeof(tmp) ? header_len : header_crc_offset + sizeof(tmp);
328          lseek(fd, 0, SEEK_SET);
329          write(fd, buffer, write_len);
330        }
331
332        printf( format_spec,
333                opt_testsuite ? "" : "Written",
334                buffer + TAG_VER_LEN,
335                buffer + TAG_VER_LEN + strlen(buffer + TAG_VER_LEN) + 1,
336                ntohl(*((unsigned int *)(buffer + 0xEC))),
337                header_len,
338                filename
339        );
340      }
341
342      if (!opt_quiet) {
343        fprintf(stderr, "Manufacturer ID does%s match '%s'\n",
344                match_manufacturer_id == 0 ? " not" : "",
345                match_manufacturer_id
346        );
347        if (!manufacturer_id_count)
348          fprintf(stderr, "Model ID does%s match '%s' (%u replaced)\n",
349                  model_id_count == 0 ? " not" : "",
350                  match_model_id,
351                  model_id_count - ( model_id_count == 0 ? 0 : 1 )
352          );
353      }
354
355      heap_and_reap(buffer, 0, 0);
356      close(fd);
357     } else {
358       fprintf(stderr, "Unable to open for %s (%s)\n", opt_write ? "writing" : "reading" , filename );
359     }
360   } else {
361     pr_usage(0);
362   }
363
364 end:
365   heap_and_reap(NULL-1, 0, 0);
366   return 0;
367 }
368