1 static const char *title =\
2 "Broadcom Consumer Router Firmware Header Editor"
4 static const float VERSION = 1.3f;
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"
11 static const char *help = \
12 "For routers using the Broadcom CFE ((Customer Premises Equipment) CPE Firmware Environment) and firmware update files.\n\n"
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"
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"
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"
22 " Other Feature Bits :\n"
23 " 4d 53 60 09 00 00 00 00-00 00 00 00 00 00 00 00\n\n"
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"
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"
32 " a) the manufacturer ID and model ID (e.g. 'MSTC_6006')\n"
33 " b) the model ID (e.g. '6006')\n\n"
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"
37 "# display detailed help\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"
51 #include <sys/types.h>
55 #include <arpa/inet.h>
57 #include "heap_reap.h"
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";
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
104 pr_usage(int verbose)
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"
121 match_manufacturer_id,
128 pr_error_exit(unsigned int usage, const char *error, ...)
131 char error_message[MESSAGE_SIZE + 1];
135 va_start(args, error);
136 (void) vsnprintf(error_message, MESSAGE_SIZE + 1, error, args);
138 fprintf(stderr, "Error: %s\n", error_message);
140 if (usage) pr_usage(usage);
142 heap_and_reap(NULL-1, 0, 0);
146 /* calculate standard CRC32
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)
152 unsigned int crc32(const unsigned char *data, ssize_t len, unsigned int crc)
154 for ( ; len > 0; --len ) {
155 crc = (crc >> 8) ^ crc32_table[ (crc ^ *data++) & 0xff ];
162 main(int argc, char **argv)
164 unsigned int arg, crc;
165 char *filename = NULL;
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
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;
179 fprintf(stderr, "%s\nVersion: %0.2f\n%s\n", title, VERSION, copyright);
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);
184 memcpy(model_id, match_model_id, MODEL_ID_LEN);
186 for (arg = 1; arg < (unsigned) argc; ++arg) {
188 size_t arg_len = strlen(p);
193 case 'l': // length of data to generate CRC32 on
196 case 'i': // replacement model ID
199 case 'm': // match this manufacturer ID
200 opt_match_manufacturer_id = 1;
202 case 'M': // match this model ID
203 opt_match_model_id = 1;
205 case 'w': // re-write file header
208 case 's': // simulate
211 case 't': // display machine-readable output for test suite
213 format_spec = format_spec_test;
223 pr_error_exit(0, "cannot read data from stdin; provide a filename");
229 char *format = "%ld";
230 if ( *(p+1) == 'x' || *(p+1) == 'X' ) { // hex length
232 // FIXME: p = first hex char?
234 if (sscanf(p, format, &header_len))
237 pr_error_exit(0, "Illegal length value (%s)", p);
239 } else if (opt_model_id == 1) {
244 pr_error_exit(0, "Model Id ('%s') must be %d characters", p, MODEL_ID_LEN - 1);
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) {
251 ++opt_match_model_id;
252 } else if (!filename) { // remaining non-option must be the filename
256 fprintf(stderr, "Can only process one file; ignoring '%s'\n", p);
262 fprintf(stdout, "%s\n", "In-place editing of header");
264 if (opt_simulate == 1)
265 fprintf(stderr, "%s\n", "Simulation mode; no file writes");
268 fd_mode = (opt_write && !opt_simulate) ? O_RDWR : O_RDONLY;
271 if ((fd = open(filename, fd_mode)) > 0) {
272 if ( (buffer = heap_and_reap(NULL, header_len + sizeof(crc), 1)) != NULL) {
274 if ( (qty = read(fd, buffer, header_len + sizeof(crc))) < header_len) {
276 fprintf(stderr, "warning: only able to read %ld of %ld bytes\n", qty, header_len);
281 pr_error_exit(0, "unable to allocate memory (%ld bytes)\n", header_len);
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)) ),
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)
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);
301 p = p + MODEL_ID_LEN - 1;
306 crc = crc32(buffer, header_len, 0xffffffff);
308 char tmp_manufacturer[TAG_SIG_LEN];
309 strcpy(tmp_manufacturer, match_manufacturer_id);
310 strcat(tmp_manufacturer, "_");
311 strcat(tmp_manufacturer, model_id);
314 opt_testsuite ? "" : "Calculated",
323 unsigned int tmp = htonl(crc);
324 memcpy(buffer + header_crc_offset, &tmp, sizeof(tmp));
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);
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))),
343 fprintf(stderr, "Manufacturer ID does%s match '%s'\n",
344 match_manufacturer_id == 0 ? " not" : "",
345 match_manufacturer_id
347 if (!manufacturer_id_count)
348 fprintf(stderr, "Model ID does%s match '%s' (%u replaced)\n",
349 model_id_count == 0 ? " not" : "",
351 model_id_count - ( model_id_count == 0 ? 0 : 1 )
355 heap_and_reap(buffer, 0, 0);
358 fprintf(stderr, "Unable to open for %s (%s)\n", opt_write ? "writing" : "reading" , filename );
365 heap_and_reap(NULL-1, 0, 0);