1 static const char *title =\
2 "Broadcom Consumer Router Firmware Header Editor"
4 static const float VERSION = 1.2f;
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 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";
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
102 pr_usage(int verbose)
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"
119 match_manufacturer_id,
126 pr_error_exit(unsigned int usage, const char *error, ...)
129 char error_message[MESSAGE_SIZE + 1];
133 va_start(args, error);
134 (void) vsnprintf(error_message, MESSAGE_SIZE + 1, error, args);
136 fprintf(stderr, "Error: %s\n", error_message);
138 if (usage) pr_usage(usage);
143 /* calculate standard CRC32
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)
149 unsigned int crc32(const unsigned char *data, ssize_t len, unsigned int crc)
151 for ( ; len > 0; --len ) {
152 crc = (crc >> 8) ^ crc32_table[ (crc ^ *data++) & 0xff ];
159 main(int argc, char **argv)
161 unsigned int arg, crc;
163 char *filename = NULL;
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
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;
177 fprintf(stderr, "%s\nVersion: %0.2f\n%s\n", title, VERSION, copyright);
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);
182 memcpy(model_id, match_model_id, MODEL_ID_LEN);
184 for (arg = 1; arg < (unsigned) argc; ++arg) {
186 size_t arg_len = strlen(p);
191 case 'l': // length of data to generate CRC32 on
194 case 'i': // replacement model ID
197 case 'm': // match this manufacturer ID
198 opt_match_manufacturer_id = 1;
200 case 'M': // match this model ID
201 opt_match_model_id = 1;
203 case 'w': // re-write file header
206 case 's': // simulate
209 case 't': // display machine-readable output for test suite
211 format_spec = format_spec_test;
221 pr_error_exit(0, "cannot read data from stdin; provide a filename");
227 char *format = "%ld";
228 if ( *(p+1) == 'x' || *(p+1) == 'X' ) { // hex length
230 // FIXME: p = first hex char?
232 if (sscanf(p, format, &header_len))
235 pr_error_exit(0, "Illegal length value (%s)", p);
237 } else if (opt_model_id == 1) {
242 pr_error_exit(0, "Model Id ('%s') must be %d characters", p, MODEL_ID_LEN - 1);
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) {
249 ++opt_match_model_id;
250 } else if (!filename) { // remaining non-option must be the filename
254 fprintf(stderr, "Can only process one file; ignoring '%s'\n", p);
260 fprintf(stdout, "%s\n", "In-place editing of header");
262 if (opt_simulate == 1)
263 fprintf(stderr, "%s\n", "Simulation mode; no file writes");
266 fd_mode = (opt_write && !opt_simulate) ? O_RDWR : O_RDONLY;
269 if ((fd = open(filename, fd_mode)) > 0) {
270 if ( (buffer = calloc(header_len + sizeof(crc), 1)) != NULL) {
272 if ( (qty = read(fd, buffer, header_len + sizeof(crc))) < header_len) {
274 fprintf(stderr, "warning: only able to read %ld of %ld bytes\n", qty, header_len);
279 pr_error_exit(0, "unable to allocate memory (%ld bytes)\n", header_len);
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)) ),
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)
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);
299 p = p + MODEL_ID_LEN - 1;
304 crc = crc32(buffer, header_len, 0xffffffff);
306 char tmp_manufacturer[TAG_SIG_LEN];
307 strcpy(tmp_manufacturer, match_manufacturer_id);
308 strcat(tmp_manufacturer, "_");
309 strcat(tmp_manufacturer, model_id);
312 opt_testsuite ? "" : "Calculated",
321 unsigned int tmp = htonl(crc);
322 memcpy(buffer + header_crc_offset, &tmp, sizeof(tmp));
325 lseek(fd, 0, SEEK_SET);
326 write(fd, buffer, header_len);
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))),
340 fprintf(stderr, "Manufacturer ID does%s match '%s'\n",
341 match_manufacturer_id == 0 ? " not" : "",
342 match_manufacturer_id
344 if (!manufacturer_id_count)
345 fprintf(stderr, "Model ID does%s match '%s' (%u replaced)\n",
346 model_id_count == 0 ? " not" : "",
348 model_id_count - ( model_id_count == 0 ? 0 : 1 )
355 fprintf(stderr, "Unable to open for %s (%s)\n", opt_write ? "writing" : "reading" , filename );