78e4177d2c1bf07fa5b729449187bf2641b9b32d
[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   char *filename = NULL;
163   int fd, fd_mode;
164   unsigned char *buffer = NULL;
165   unsigned char *model_id;
166   unsigned int header_crc_offset = header_len;
167   unsigned int manufacturer_id_count = 0;
168   unsigned int model_id_count = 0;
169   char *format_spec_user = "%-12s Manufacturer: %s Model: %s CRC32: %08x Length: %ld File: %s\n";
170   char *format_spec_test = "%-12s %s %s %08x %ld\n";
171   char *format_spec = format_spec_user; // default output format
172
173   unsigned int opt_len, opt_model_id, opt_match_manufacturer_id, opt_match_model_id, opt_write, opt_quiet, opt_simulate, opt_testsuite;
174   opt_len = opt_model_id = opt_match_manufacturer_id = opt_match_model_id = opt_write = opt_quiet = opt_simulate = opt_testsuite = 0;
175
176   fprintf(stderr, "%s\nVersion: %0.2f\n%s\n", title, VERSION, copyright);
177
178   if ((model_id = calloc(MODEL_ID_LEN, 1)) == NULL) {
179     pr_error_exit(0, "Unable to allocate memory (%d bytes)\n", MODEL_ID_LEN);
180   }
181   memcpy(model_id, match_model_id, MODEL_ID_LEN);
182
183   for (arg = 1; arg < (unsigned) argc; ++arg) {
184     char *p = argv[arg];
185     size_t arg_len = strlen(p);
186
187     if (p[0] == '-') {
188       if(p[1] != 0) {
189         switch (p[1]) {
190           case 'l': // length of data to generate CRC32 on
191             opt_len = 1;
192             break;
193           case 'i': // replacement model ID
194             opt_model_id = 1;
195             break;
196           case 'm': // match this manufacturer ID
197             opt_match_manufacturer_id = 1;
198             break;
199           case 'M': // match this model ID
200             opt_match_model_id = 1;
201             break;
202           case 'w': // re-write file header
203             opt_write = 1;
204             break;
205           case 's': // simulate
206             opt_simulate = 1;
207             break;
208           case 't': // display machine-readable output for test suite
209             opt_testsuite = 1;
210             format_spec = format_spec_test;
211             break;
212           case 'q': // quiet
213             opt_quiet = 1;
214             break;
215           case 'h': // help
216             pr_usage(1);
217             exit(0);
218         }
219       } else {
220         pr_error_exit(0, "cannot read data from stdin; provide a filename");
221       }
222       continue;
223     }
224
225     if (opt_len == 1) {
226       char *format = "%ld";
227       if ( *(p+1) == 'x' || *(p+1) == 'X' ) { // hex length
228         format = "%lx";
229         // FIXME: p = first hex char?
230       }
231       if (sscanf(p, format, &header_len))
232         ++opt_len;
233       else
234         pr_error_exit(0, "Illegal length value (%s)", p);
235
236     } else if (opt_model_id == 1) {
237       if (arg_len == 4) {
238         model_id = p;
239         ++opt_model_id;
240       }  else {
241         pr_error_exit(0, "Model Id ('%s') must be %d characters", p, MODEL_ID_LEN - 1);
242       }
243     } else if (opt_match_manufacturer_id == 1) {
244       match_manufacturer_id = p;
245       ++opt_match_manufacturer_id;
246     } else if (opt_match_model_id == 1) {
247       match_model_id = p;
248       ++opt_match_model_id;
249     } else if (!filename) { // remaining non-option must be the filename
250       filename = p;
251     } else {
252       if (!opt_quiet)
253         fprintf(stderr, "Can only process one file; ignoring '%s'\n", p);
254     }
255   }
256
257   if (!opt_quiet) {
258     if (opt_write == 1)
259        fprintf(stdout, "%s\n", "In-place editing of header");
260     
261     if (opt_simulate == 1)
262       fprintf(stderr, "%s\n", "Simulation mode; no file writes");
263   }
264
265   fd_mode = (opt_write && !opt_simulate) ? O_RDWR : O_RDONLY;
266
267   if (filename) {
268    if ((fd = open(filename, fd_mode)) > 0) {
269      if ( (buffer = calloc(header_len + sizeof(crc), 1)) != NULL) {
270        ssize_t qty;
271        if ( (qty = read(fd, buffer, header_len + sizeof(crc))) < header_len) {
272          if (!opt_quiet)
273            fprintf(stderr, "warning: only able to read %ld of %ld bytes\n", qty, header_len);
274          header_len = qty;
275        }
276      } else {
277        close(fd);
278        pr_error_exit(0, "unable to allocate memory (%ld bytes)\n", header_len);
279      }
280      printf( format_spec,
281              opt_testsuite ? "" : "Current",
282              buffer + TAG_VER_LEN,
283              buffer + TAG_VER_LEN + strlen(buffer + TAG_VER_LEN) + 1,
284              ntohl( *((unsigned int *)(buffer + header_crc_offset)) ),
285              header_len,
286              filename);
287
288      // do the model ID replacement in the memory buffer
289      if (strstr(buffer + TAG_VER_LEN, match_manufacturer_id) != NULL) {
290        ++manufacturer_id_count;
291        unsigned char *p = buffer + TAG_VER_LEN + strlen(match_manufacturer_id) + 1; // step over manufacturer ID
292        if ( strstr(p, match_model_id) != NULL)
293          ++model_id_count;
294        for (; p <= buffer + TAG_VER_LEN + TAG_SIG_LEN - MODEL_ID_LEN; ++p) {
295          if (strncmp(p, match_model_id, MODEL_ID_LEN) == 0) {
296            memcpy(p, model_id, MODEL_ID_LEN);
297            ++model_id_count;
298            p = p + MODEL_ID_LEN - 1;
299          }
300        }
301      }
302
303      crc = crc32(buffer, header_len, 0xffffffff);
304
305      char tmp_manufacturer[TAG_SIG_LEN];
306      strcpy(tmp_manufacturer, match_manufacturer_id);
307      strcat(tmp_manufacturer, "_");
308      strcat(tmp_manufacturer, model_id);
309
310      printf( format_spec,
311              opt_testsuite ? "" : "Calculated",
312              tmp_manufacturer,
313              model_id,
314              crc,
315              header_len,
316              filename
317      );
318
319      if (opt_write) {
320        unsigned int tmp = htonl(crc);
321        memcpy(buffer + header_crc_offset, &tmp, sizeof(tmp));
322
323        if (!opt_simulate) {
324          lseek(fd, 0, SEEK_SET);
325          write(fd, buffer, header_len);
326        }
327
328        printf( format_spec,
329                opt_testsuite ? "" : "Written",
330                buffer + TAG_VER_LEN,
331                buffer + TAG_VER_LEN + strlen(buffer + TAG_VER_LEN) + 1,
332                ntohl(*((unsigned int *)(buffer + 0xEC))),
333                header_len,
334                filename
335        );
336      }
337
338      if (!opt_quiet) {
339        fprintf(stderr, "Manufacturer ID does%s match '%s'\n",
340                match_manufacturer_id == 0 ? " not" : "",
341                match_manufacturer_id
342        );
343        if (!manufacturer_id_count)
344          fprintf(stderr, "Model ID does%s match '%s' (%u replaced)\n",
345                  model_id_count == 0 ? " not" : "",
346                  match_model_id,
347                  model_id_count - ( model_id_count == 0 ? 0 : 1 )
348          );
349      }
350
351      free(buffer);
352      close(fd);
353     } else {
354       fprintf(stderr, "Unable to open for %s (%s)\n", opt_write ? "writing" : "reading" , filename );
355     }
356   } else {
357     pr_usage(0);
358   }
359
360   return 0;
361 }
362