Broadcom Consumer Router Firmware Header Editor
[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.2f;
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
57 static unsigned int MESSAGE_SIZE = 1024;
58 static unsigned int TAG_VER_LEN = 4;
59 static unsigned int TAG_SIG_LEN = 20;
60 static unsigned int MODEL_ID_LEN = 5;
61 static ssize_t header_len = 236; // 0xEC; default length for Broadcom FILE_TAG header excluding TAG CRC
62 static char *match_manufacturer_id = "MSTC";
63 static char *match_model_id = "6006";
64 static char *model_id = "6006";
65
66 static unsigned int crc32_table[256] = {
67     0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x706AF48F, 0xE963A535, 0x9E6495A3,
68     0x0EDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988, 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91,
69     0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE, 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7,
70     0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, 0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5,
71     0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172, 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B,
72     0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940, 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59,
73     0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F,
74     0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, 0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D,
75     0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433,
76     0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, 0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01,
77     0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, 0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457,
78     0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA, 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65,
79     0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2, 0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB,
80     0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0, 0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9,
81     0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F,
82     0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, 0x2EB40D81, 0xB7BD5C3B, 0xC0BA6CAD,
83     0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A, 0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683,
84     0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8, 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1,
85     0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE, 0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7,
86     0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC, 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5,
87     0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B,
88     0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55, 0x316E8EEF, 0x4669BE79,
89     0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, 0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F,
90     0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D,
91     0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, 0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713,
92     0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, 0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21,
93     0x86D3D2D4, 0xF1D4E242, 0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777,
94     0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C, 0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45,
95     0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2, 0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB,
96     0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9,
97     0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605, 0xCDD70693, 0x54DE5729, 0x23D967BF,
98     0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94, 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D
99 };
100
101 static void
102 pr_usage(int verbose)
103 {
104   fprintf(stderr,
105     "Usage:\n"
106     "  -i  replacement Model ID (default '%s')\n"
107     "  -l  bytes to calculate new CRC32 over (default %ld)\n"
108     "  -m  current Manufacturer ID to match (default '%s')\n"
109     "  -M  current Model ID to match (default '%s')\n"
110     "  -w  write to file (default only prints new values)\n"
111     "  -s  simulate; don't write to file when -w is given\n"
112     "  -t  output for automated test suite\n"
113     "  -q  quiet; only display result\n"
114     "  -h  show additional help\n"
115     "\n"
116     "%s",
117     model_id,
118     header_len,
119     match_manufacturer_id,
120     match_model_id,
121     verbose ? help : ""
122   );
123 }
124
125 static void
126 pr_error_exit(unsigned int usage, const char *error, ...)
127 {
128  va_list args;
129  char error_message[MESSAGE_SIZE + 1];
130
131  if (!error) return;
132
133  va_start(args, error);
134  (void) vsnprintf(error_message, MESSAGE_SIZE + 1, error, args);
135  va_end(args);
136  fprintf(stderr, "Error: %s\n", error_message);
137
138  if (usage) pr_usage(usage);
139
140  exit(EXIT_FAILURE);
141 }
142
143 /* calculate standard CRC32
144  *
145  * @param data pointer to start of input data
146  * @param len  length in bytes of data to be checksummed
147  * @param crc32 seed value (Broadcom use 0xffffffff)
148  */
149 unsigned int crc32(const unsigned char *data, ssize_t len, unsigned int crc)
150 {
151    for ( ; len > 0; --len ) {
152      crc = (crc >> 8) ^ crc32_table[ (crc ^ *data++) & 0xff ];
153    }
154    return crc;
155 }
156
157
158 int
159 main(int argc, char **argv)
160 {
161   unsigned int arg, crc;
162   FILE *fdata = NULL;
163   char *filename = NULL;
164   int fd, fd_mode;
165   unsigned char *buffer = NULL;
166   unsigned char *model_id;
167   unsigned int header_crc_offset = header_len;
168   unsigned int manufacturer_id_count = 0;
169   unsigned int model_id_count = 0;
170   char *format_spec_user = "%-12s Manufacturer: %s Model: %s CRC32: %08x Length: %ld File: %s\n";
171   char *format_spec_test = "%-12s %s %s %08x %ld\n";
172   char *format_spec = format_spec_user; // default output format
173
174   unsigned int opt_len, opt_model_id, opt_match_manufacturer_id, opt_match_model_id, opt_write, opt_quiet, opt_simulate, opt_testsuite;
175   opt_len = opt_model_id = opt_match_manufacturer_id = opt_match_model_id = opt_write = opt_quiet = opt_simulate = opt_testsuite = 0;
176
177   fprintf(stderr, "%s\nVersion: %0.2f\n%s\n", title, VERSION, copyright);
178
179   if ((model_id = calloc(MODEL_ID_LEN, 1)) == NULL) {
180     pr_error_exit(0, "Unable to allocate memory (%d bytes)\n", MODEL_ID_LEN);
181   }
182   memcpy(model_id, match_model_id, MODEL_ID_LEN);
183
184   for (arg = 1; arg < (unsigned) argc; ++arg) {
185     char *p = argv[arg];
186     size_t arg_len = strlen(p);
187
188     if (p[0] == '-') {
189       if(p[1] != 0) {
190         switch (p[1]) {
191           case 'l': // length of data to generate CRC32 on
192             opt_len = 1;
193             break;
194           case 'i': // replacement model ID
195             opt_model_id = 1;
196             break;
197           case 'm': // match this manufacturer ID
198             opt_match_manufacturer_id = 1;
199             break;
200           case 'M': // match this model ID
201             opt_match_model_id = 1;
202             break;
203           case 'w': // re-write file header
204             opt_write = 1;
205             break;
206           case 's': // simulate
207             opt_simulate = 1;
208             break;
209           case 't': // display machine-readable output for test suite
210             opt_testsuite = 1;
211             format_spec = format_spec_test;
212             break;
213           case 'q': // quiet
214             opt_quiet = 1;
215             break;
216           case 'h': // help
217             pr_usage(1);
218             exit(0);
219         }
220       } else {
221         pr_error_exit(0, "cannot read data from stdin; provide a filename");
222       }
223       continue;
224     }
225
226     if (opt_len == 1) {
227       char *format = "%ld";
228       if ( *(p+1) == 'x' || *(p+1) == 'X' ) { // hex length
229         format = "%lx";
230         // FIXME: p = first hex char?
231       }
232       if (sscanf(p, format, &header_len))
233         ++opt_len;
234       else
235         pr_error_exit(0, "Illegal length value (%s)", p);
236
237     } else if (opt_model_id == 1) {
238       if (arg_len == 4) {
239         model_id = p;
240         ++opt_model_id;
241       }  else {
242         pr_error_exit(0, "Model Id ('%s') must be %d characters", p, MODEL_ID_LEN - 1);
243       }
244     } else if (opt_match_manufacturer_id == 1) {
245       match_manufacturer_id = p;
246       ++opt_match_manufacturer_id;
247     } else if (opt_match_model_id == 1) {
248       match_model_id = p;
249       ++opt_match_model_id;
250     } else if (!filename) { // remaining non-option must be the filename
251       filename = p;
252     } else {
253       if (!opt_quiet)
254         fprintf(stderr, "Can only process one file; ignoring '%s'\n", p);
255     }
256   }
257
258   if (!opt_quiet) {
259     if (opt_write == 1)
260        fprintf(stdout, "%s\n", "In-place editing of header");
261     
262     if (opt_simulate == 1)
263       fprintf(stderr, "%s\n", "Simulation mode; no file writes");
264   }
265
266   fd_mode = (opt_write && !opt_simulate) ? O_RDWR : O_RDONLY;
267
268   if (filename) {
269    if ((fd = open(filename, fd_mode)) > 0) {
270      if ( (buffer = calloc(header_len + sizeof(crc), 1)) != NULL) {
271        ssize_t qty;
272        if ( (qty = read(fd, buffer, header_len + sizeof(crc))) < header_len) {
273          if (!opt_quiet)
274            fprintf(stderr, "warning: only able to read %ld of %ld bytes\n", qty, header_len);
275          header_len = qty;
276        }
277      } else {
278        close(fd);
279        pr_error_exit(0, "unable to allocate memory (%ld bytes)\n", header_len);
280      }
281      printf( format_spec,
282              opt_testsuite ? "" : "Current",
283              buffer + TAG_VER_LEN,
284              buffer + TAG_VER_LEN + strlen(buffer + TAG_VER_LEN) + 1,
285              ntohl( *((unsigned int *)(buffer + header_crc_offset)) ),
286              header_len,
287              filename);
288
289      // do the model ID replacement in the memory buffer
290      if (strstr(buffer + TAG_VER_LEN, match_manufacturer_id) != NULL) {
291        ++manufacturer_id_count;
292        unsigned char *p = buffer + TAG_VER_LEN + strlen(match_manufacturer_id) + 1; // step over manufacturer ID
293        if ( strstr(p, match_model_id) != NULL)
294          ++model_id_count;
295        for (; p <= buffer + TAG_VER_LEN + TAG_SIG_LEN - MODEL_ID_LEN; ++p) {
296          if (strncmp(p, match_model_id, MODEL_ID_LEN) == 0) {
297            memcpy(p, model_id, MODEL_ID_LEN);
298            ++model_id_count;
299            p = p + MODEL_ID_LEN - 1;
300          }
301        }
302      }
303
304      crc = crc32(buffer, header_len, 0xffffffff);
305
306      char tmp_manufacturer[TAG_SIG_LEN];
307      strcpy(tmp_manufacturer, match_manufacturer_id);
308      strcat(tmp_manufacturer, "_");
309      strcat(tmp_manufacturer, model_id);
310
311      printf( format_spec,
312              opt_testsuite ? "" : "Calculated",
313              tmp_manufacturer,
314              model_id,
315              crc,
316              header_len,
317              filename
318      );
319
320      if (opt_write) {
321        unsigned int tmp = htonl(crc);
322        memcpy(buffer + header_crc_offset, &tmp, sizeof(tmp));
323
324        if (!opt_simulate) {
325          lseek(fd, 0, SEEK_SET);
326          write(fd, buffer, header_len);
327        }
328
329        printf( format_spec,
330                opt_testsuite ? "" : "Written",
331                buffer + TAG_VER_LEN,
332                buffer + TAG_VER_LEN + strlen(buffer + TAG_VER_LEN) + 1,
333                ntohl(*((unsigned int *)(buffer + 0xEC))),
334                header_len,
335                filename
336        );
337      }
338
339      if (!opt_quiet) {
340        fprintf(stderr, "Manufacturer ID does%s match '%s'\n",
341                match_manufacturer_id == 0 ? " not" : "",
342                match_manufacturer_id
343        );
344        if (!manufacturer_id_count)
345          fprintf(stderr, "Model ID does%s match '%s' (%u replaced)\n",
346                  model_id_count == 0 ? " not" : "",
347                  match_model_id,
348                  model_id_count - ( model_id_count == 0 ? 0 : 1 )
349          );
350      }
351
352      free(buffer);
353      close(fd);
354     } else {
355       fprintf(stderr, "Unable to open for %s (%s)\n", opt_write ? "writing" : "reading" , filename );
356     }
357   } else {
358     pr_usage(0);
359   }
360
361   return 0;
362 }
363