firmware-utils: replace GPL 2.0 boilerplate/reference with SPDX
[openwrt/staging/ldir.git] / tools / firmware-utils / src / mktplinkfw2.c
1 // SPDX-License-Identifier: GPL-2.0-only
2 /*
3 * Copyright (C) 2009 Gabor Juhos <juhosg@openwrt.org>
4 *
5 * This tool was based on:
6 * TP-Link WR941 V2 firmware checksum fixing tool.
7 * Copyright (C) 2008,2009 Wang Jian <lark@linux.net.cn>
8 */
9
10 #include <stdio.h>
11 #include <stdlib.h>
12 #include <stdint.h>
13 #include <string.h>
14 #include <unistd.h> /* for unlink() */
15 #include <libgen.h>
16 #include <getopt.h> /* for getopt() */
17 #include <stdarg.h>
18 #include <errno.h>
19 #include <stdbool.h>
20 #include <endian.h>
21 #include <sys/stat.h>
22
23 #include <arpa/inet.h>
24 #include <netinet/in.h>
25
26 #include "md5.h"
27 #include "mktplinkfw-lib.h"
28
29 struct fw_header {
30 uint32_t version; /* 0x00: header version */
31 char fw_version[48]; /* 0x04: fw version string */
32 uint32_t hw_id; /* 0x34: hardware id */
33 uint32_t hw_rev; /* 0x38: FIXME: hardware revision? */
34 uint32_t hw_ver_add; /* 0x3c: additional hardware version */
35 uint8_t md5sum1[MD5SUM_LEN]; /* 0x40 */
36 uint32_t unk2; /* 0x50: 0x00000000 */
37 uint8_t md5sum2[MD5SUM_LEN]; /* 0x54 */
38 uint32_t unk3; /* 0x64: 0xffffffff */
39
40 uint32_t kernel_la; /* 0x68: kernel load address */
41 uint32_t kernel_ep; /* 0x6c: kernel entry point */
42 uint32_t fw_length; /* 0x70: total length of the image */
43 uint32_t kernel_ofs; /* 0x74: kernel data offset */
44 uint32_t kernel_len; /* 0x78: kernel data length */
45 uint32_t rootfs_ofs; /* 0x7c: rootfs data offset */
46 uint32_t rootfs_len; /* 0x80: rootfs data length */
47 uint32_t boot_ofs; /* 0x84: bootloader offset */
48 uint32_t boot_len; /* 0x88: bootloader length */
49 uint16_t unk4; /* 0x8c: 0x55aa */
50 uint8_t sver_hi; /* 0x8e */
51 uint8_t sver_lo; /* 0x8f */
52 uint8_t unk5; /* 0x90: magic: 0xa5 */
53 uint8_t ver_hi; /* 0x91 */
54 uint8_t ver_mid; /* 0x92 */
55 uint8_t ver_lo; /* 0x93 */
56 uint8_t pad[364];
57 } __attribute__ ((packed));
58
59 #define FLAG_LE_KERNEL_LA_EP 0x00000001 /* Little-endian used for kernel load address & entry point */
60
61 struct board_info {
62 char *id;
63 uint32_t hw_id;
64 uint32_t hw_rev;
65 uint32_t hw_ver_add;
66 char *layout_id;
67 uint32_t hdr_ver;
68 uint32_t flags;
69 };
70
71 /*
72 * Globals
73 */
74 char *ofname;
75 char *progname;
76 static char *vendor = "TP-LINK Technologies";
77 static char *version = "ver. 1.0";
78 static char *fw_ver = "0.0.0";
79 static char *sver = "1.0";
80 static uint32_t hdr_ver = 2;
81
82 static struct board_info custom_board;
83
84 static struct board_info *board;
85 static char *layout_id;
86 struct flash_layout *layout;
87 static char *opt_hw_id;
88 static char *opt_hw_rev;
89 static char *opt_hw_ver_add;
90 static int fw_ver_lo;
91 static int fw_ver_mid;
92 static int fw_ver_hi;
93 static int sver_lo;
94 static int sver_hi;
95 struct file_info kernel_info;
96 static uint32_t kernel_la = 0;
97 static uint32_t kernel_ep = 0;
98 uint32_t kernel_len = 0;
99 struct file_info rootfs_info;
100 uint32_t rootfs_ofs = 0;
101 uint32_t rootfs_align;
102 static struct file_info boot_info;
103 int combined;
104 int strip_padding;
105 int add_jffs2_eof;
106
107 static struct file_info inspect_info;
108 static int extract = 0;
109
110 char md5salt_normal[MD5SUM_LEN] = {
111 0xdc, 0xd7, 0x3a, 0xa5, 0xc3, 0x95, 0x98, 0xfb,
112 0xdc, 0xf9, 0xe7, 0xf4, 0x0e, 0xae, 0x47, 0x37,
113 };
114
115 char md5salt_boot[MD5SUM_LEN] = {
116 0x8c, 0xef, 0x33, 0x5f, 0xd5, 0xc5, 0xce, 0xfa,
117 0xac, 0x9c, 0x28, 0xda, 0xb2, 0xe9, 0x0f, 0x42,
118 };
119
120 static struct flash_layout layouts[] = {
121 {
122 .id = "4Mmtk",
123 .fw_max_len = 0x3c0000,
124 .kernel_la = 0x80000000,
125 .kernel_ep = 0x80000000,
126 .rootfs_ofs = 0x140000,
127 }, {
128 .id = "4MLmtk",
129 .fw_max_len = 0x3d0000,
130 .kernel_la = 0x80000000,
131 .kernel_ep = 0x80000000,
132 .rootfs_ofs = 0x140000,
133 }, {
134 .id = "8Mltq",
135 .fw_max_len = 0x7a0000,
136 .kernel_la = 0x80002000,
137 .kernel_ep = 0x80002000,
138 .rootfs_ofs = 0x140000,
139 }, {
140 .id = "16Mltq",
141 .fw_max_len = 0xf90000,
142 .kernel_la = 0x80002000,
143 .kernel_ep = 0x800061b0,
144 .rootfs_ofs = 0x140000,
145 }, {
146 .id = "8Mmtk",
147 .fw_max_len = 0x7a0000,
148 .kernel_la = 0x80000000,
149 .kernel_ep = 0x80000000,
150 .rootfs_ofs = 0x140000,
151 }, {
152 .id = "8MSUmtk", /* Split U-Boot OS */
153 .fw_max_len = 0x770000,
154 .kernel_la = 0x80000000,
155 .kernel_ep = 0x80000000,
156 .rootfs_ofs = 0x140000,
157 }, {
158 .id = "8MLmtk",
159 .fw_max_len = 0x7b0000,
160 .kernel_la = 0x80000000,
161 .kernel_ep = 0x80000000,
162 .rootfs_ofs = 0x140000,
163 }, {
164 .id = "8Mqca",
165 .fw_max_len = 0x7a0000,
166 .kernel_la = 0x80060000,
167 .kernel_ep = 0x80060000,
168 .rootfs_ofs = 0x140000,
169 }, {
170 .id = "16Mqca",
171 .fw_max_len = 0xf90000,
172 .kernel_la = 0x80060000,
173 .kernel_ep = 0x80060000,
174 .rootfs_ofs = 0x140000,
175 }, {
176 /* terminating entry */
177 }
178 };
179
180 static void usage(int status)
181 {
182 FILE *stream = (status != EXIT_SUCCESS) ? stderr : stdout;
183
184 fprintf(stream, "Usage: %s [OPTIONS...]\n", progname);
185 fprintf(stream,
186 "\n"
187 "Options:\n"
188 " -c use combined kernel image\n"
189 " -e swap endianness in kernel load address and entry point\n"
190 " -E <ep> overwrite kernel entry point with <ep> (hexval prefixed with 0x)\n"
191 " -L <la> overwrite kernel load address with <la> (hexval prefixed with 0x)\n"
192 " -H <hwid> use hardware id specified with <hwid>\n"
193 " -W <hwrev> use hardware revision specified with <hwrev>\n"
194 " -w <hwveradd> use additional hardware version specified with <hwveradd>\n"
195 " -F <id> use flash layout specified with <id>\n"
196 " -k <file> read kernel image from the file <file>\n"
197 " -r <file> read rootfs image from the file <file>\n"
198 " -a <align> align the rootfs start on an <align> bytes boundary\n"
199 " -R <offset> overwrite rootfs offset with <offset> (hexval prefixed with 0x)\n"
200 " -o <file> write output to the file <file>\n"
201 " -s strip padding from the end of the image\n"
202 " -j add jffs2 end-of-filesystem markers\n"
203 " -N <vendor> set image vendor to <vendor>\n"
204 " -T <version> set header version to <version>\n"
205 " -V <version> set image version to <version>\n"
206 " -v <version> set firmware version to <version>\n"
207 " -y <version> set secondary version to <version>\n"
208 " -i <file> inspect given firmware file <file>\n"
209 " -x extract kernel and rootfs while inspecting (requires -i)\n"
210 " -h show this screen\n"
211 );
212
213 exit(status);
214 }
215
216 static int check_options(void)
217 {
218 int ret;
219
220 if (inspect_info.file_name) {
221 ret = get_file_stat(&inspect_info);
222 if (ret)
223 return ret;
224
225 return 0;
226 } else if (extract) {
227 ERR("no firmware for inspection specified");
228 return -1;
229 }
230
231 if (opt_hw_id == NULL) {
232 ERR("hardware id must be specified");
233 return -1;
234 }
235
236 board = &custom_board;
237
238 if (layout_id == NULL) {
239 ERR("flash layout is not specified");
240 return -1;
241 }
242
243 board->hw_id = strtoul(opt_hw_id, NULL, 0);
244
245 board->hw_rev = 1;
246 board->hw_ver_add = 0;
247
248 if (opt_hw_rev)
249 board->hw_rev = strtoul(opt_hw_rev, NULL, 0);
250 if (opt_hw_ver_add)
251 board->hw_ver_add = strtoul(opt_hw_ver_add, NULL, 0);
252
253 layout = find_layout(layouts, layout_id);
254 if (layout == NULL) {
255 ERR("unknown flash layout \"%s\"", layout_id);
256 return -1;
257 }
258
259 if (!kernel_la)
260 kernel_la = layout->kernel_la;
261 if (!kernel_ep)
262 kernel_ep = layout->kernel_ep;
263 if (!rootfs_ofs)
264 rootfs_ofs = layout->rootfs_ofs;
265
266 if (kernel_info.file_name == NULL) {
267 ERR("no kernel image specified");
268 return -1;
269 }
270
271 ret = get_file_stat(&kernel_info);
272 if (ret)
273 return ret;
274
275 kernel_len = kernel_info.file_size;
276
277 if (combined) {
278 if (kernel_info.file_size >
279 layout->fw_max_len - sizeof(struct fw_header)) {
280 ERR("kernel image is too big");
281 return -1;
282 }
283 } else {
284 if (rootfs_info.file_name == NULL) {
285 ERR("no rootfs image specified");
286 return -1;
287 }
288
289 ret = get_file_stat(&rootfs_info);
290 if (ret)
291 return ret;
292
293 if (rootfs_align) {
294 kernel_len += sizeof(struct fw_header);
295 rootfs_ofs = ALIGN(kernel_len, rootfs_align);
296 kernel_len -= sizeof(struct fw_header);
297
298 DBG("rootfs offset aligned to 0x%u", rootfs_ofs);
299
300 if (kernel_len + rootfs_info.file_size >
301 layout->fw_max_len - sizeof(struct fw_header)) {
302 ERR("images are too big");
303 return -1;
304 }
305 } else {
306 if (kernel_info.file_size >
307 rootfs_ofs - sizeof(struct fw_header)) {
308 ERR("kernel image is too big");
309 return -1;
310 }
311
312 if (rootfs_info.file_size >
313 (layout->fw_max_len - rootfs_ofs)) {
314 ERR("rootfs image is too big");
315 return -1;
316 }
317 }
318 }
319
320 if (ofname == NULL) {
321 ERR("no output file specified");
322 return -1;
323 }
324
325 ret = sscanf(fw_ver, "%d.%d.%d", &fw_ver_hi, &fw_ver_mid, &fw_ver_lo);
326 if (ret != 3) {
327 ERR("invalid firmware version '%s'", fw_ver);
328 return -1;
329 }
330
331 ret = sscanf(sver, "%d.%d", &sver_hi, &sver_lo);
332 if (ret != 2) {
333 ERR("invalid secondary version '%s'", sver);
334 return -1;
335 }
336
337 return 0;
338 }
339
340 void fill_header(char *buf, int len)
341 {
342 struct fw_header *hdr = (struct fw_header *)buf;
343 unsigned ver_len;
344
345 memset(hdr, '\xff', sizeof(struct fw_header));
346
347 hdr->version = htonl(bswap_32(hdr_ver));
348 ver_len = strlen(version);
349 if (ver_len > (sizeof(hdr->fw_version) - 1))
350 ver_len = sizeof(hdr->fw_version) - 1;
351
352 memcpy(hdr->fw_version, version, ver_len);
353 hdr->fw_version[ver_len] = 0;
354
355 hdr->hw_id = htonl(board->hw_id);
356 hdr->hw_rev = htonl(board->hw_rev);
357 hdr->hw_ver_add = htonl(board->hw_ver_add);
358
359 if (boot_info.file_size == 0) {
360 memcpy(hdr->md5sum1, md5salt_normal, sizeof(hdr->md5sum1));
361 hdr->boot_ofs = htonl(0);
362 hdr->boot_len = htonl(0);
363 } else {
364 memcpy(hdr->md5sum1, md5salt_boot, sizeof(hdr->md5sum1));
365 hdr->boot_ofs = htonl(rootfs_ofs + rootfs_info.file_size);
366 hdr->boot_len = htonl(rootfs_info.file_size);
367 }
368
369 hdr->kernel_la = htonl(kernel_la);
370 hdr->kernel_ep = htonl(kernel_ep);
371 hdr->fw_length = htonl(layout->fw_max_len);
372 hdr->kernel_ofs = htonl(sizeof(struct fw_header));
373 hdr->kernel_len = htonl(kernel_len);
374 if (!combined) {
375 hdr->rootfs_ofs = htonl(rootfs_ofs);
376 hdr->rootfs_len = htonl(rootfs_info.file_size);
377 }
378
379 hdr->boot_ofs = htonl(0);
380 hdr->boot_len = htonl(boot_info.file_size);
381
382 hdr->unk2 = htonl(0);
383 hdr->unk3 = htonl(0xffffffff);
384 hdr->unk4 = htons(0x55aa);
385 hdr->unk5 = 0xa5;
386
387 hdr->sver_hi = sver_hi;
388 hdr->sver_lo = sver_lo;
389
390 hdr->ver_hi = fw_ver_hi;
391 hdr->ver_mid = fw_ver_mid;
392 hdr->ver_lo = fw_ver_lo;
393
394 if (board->flags & FLAG_LE_KERNEL_LA_EP) {
395 hdr->kernel_la = bswap_32(hdr->kernel_la);
396 hdr->kernel_ep = bswap_32(hdr->kernel_ep);
397 }
398
399 get_md5(buf, len, hdr->md5sum1);
400 }
401
402 static int inspect_fw(void)
403 {
404 char *buf;
405 struct fw_header *hdr;
406 uint8_t md5sum[MD5SUM_LEN];
407 struct board_info *board;
408 int ret = EXIT_FAILURE;
409
410 buf = malloc(inspect_info.file_size);
411 if (!buf) {
412 ERR("no memory for buffer!\n");
413 goto out;
414 }
415
416 ret = read_to_buf(&inspect_info, buf);
417 if (ret)
418 goto out_free_buf;
419 hdr = (struct fw_header *)buf;
420
421 board = &custom_board;
422
423 if (board->flags & FLAG_LE_KERNEL_LA_EP) {
424 hdr->kernel_la = bswap_32(hdr->kernel_la);
425 hdr->kernel_ep = bswap_32(hdr->kernel_ep);
426 }
427
428 inspect_fw_pstr("File name", inspect_info.file_name);
429 inspect_fw_phexdec("File size", inspect_info.file_size);
430
431 switch(bswap_32(ntohl(hdr->version))) {
432 case 2:
433 case 3:
434 break;
435 default:
436 ERR("file does not seem to have V2/V3 header!\n");
437 goto out_free_buf;
438 }
439
440 inspect_fw_phexdec("Version 2 Header size", sizeof(struct fw_header));
441
442 memcpy(md5sum, hdr->md5sum1, sizeof(md5sum));
443 if (ntohl(hdr->boot_len) == 0)
444 memcpy(hdr->md5sum1, md5salt_normal, sizeof(md5sum));
445 else
446 memcpy(hdr->md5sum1, md5salt_boot, sizeof(md5sum));
447 get_md5(buf, inspect_info.file_size, hdr->md5sum1);
448
449 if (memcmp(md5sum, hdr->md5sum1, sizeof(md5sum))) {
450 inspect_fw_pmd5sum("Header MD5Sum1", md5sum, "(*ERROR*)");
451 inspect_fw_pmd5sum(" --> expected", hdr->md5sum1, "");
452 } else {
453 inspect_fw_pmd5sum("Header MD5Sum1", md5sum, "(ok)");
454 }
455 if (ntohl(hdr->unk2) != 0)
456 inspect_fw_phexdec("Unknown value 2", hdr->unk2);
457 inspect_fw_pmd5sum("Header MD5Sum2", hdr->md5sum2,
458 "(purpose yet unknown, unchecked here)");
459
460 if (ntohl(hdr->unk3) != 0xffffffff)
461 inspect_fw_phexdec("Unknown value 3", hdr->unk3);
462
463 if (ntohs(hdr->unk4) != 0x55aa)
464 inspect_fw_phexdec("Unknown value 4", hdr->unk4);
465
466 if (hdr->unk5 != 0xa5)
467 inspect_fw_phexdec("Unknown value 5", hdr->unk5);
468
469 printf("\n");
470
471 inspect_fw_pstr("Firmware version", hdr->fw_version);
472 inspect_fw_phex("Hardware ID", ntohl(hdr->hw_id));
473 inspect_fw_phex("Hardware Revision",
474 ntohl(hdr->hw_rev));
475 inspect_fw_phex("Additional HW Version",
476 ntohl(hdr->hw_ver_add));
477
478 printf("%-23s: %d.%d.%d-%d.%d\n", "Software version",
479 hdr->ver_hi, hdr->ver_mid, hdr->ver_lo,
480 hdr->sver_hi, hdr->sver_lo);
481
482 printf("\n");
483
484 inspect_fw_phexdec("Kernel data offset",
485 ntohl(hdr->kernel_ofs));
486 inspect_fw_phexdec("Kernel data length",
487 ntohl(hdr->kernel_len));
488 inspect_fw_phex("Kernel load address",
489 ntohl(hdr->kernel_la));
490 inspect_fw_phex("Kernel entry point",
491 ntohl(hdr->kernel_ep));
492 inspect_fw_phexdec("Rootfs data offset",
493 ntohl(hdr->rootfs_ofs));
494 inspect_fw_phexdec("Rootfs data length",
495 ntohl(hdr->rootfs_len));
496 inspect_fw_phexdec("Boot loader data offset",
497 ntohl(hdr->boot_ofs));
498 inspect_fw_phexdec("Boot loader data length",
499 ntohl(hdr->boot_len));
500 inspect_fw_phexdec("Total firmware length",
501 ntohl(hdr->fw_length));
502
503 if (extract) {
504 FILE *fp;
505 char *filename;
506
507 printf("\n");
508
509 filename = malloc(strlen(inspect_info.file_name) + 8);
510 sprintf(filename, "%s-kernel", inspect_info.file_name);
511 printf("Extracting kernel to \"%s\"...\n", filename);
512 fp = fopen(filename, "w");
513 if (fp) {
514 if (!fwrite(buf + ntohl(hdr->kernel_ofs),
515 ntohl(hdr->kernel_len), 1, fp)) {
516 ERR("error in fwrite(): %s", strerror(errno));
517 }
518 fclose(fp);
519 } else {
520 ERR("error in fopen(): %s", strerror(errno));
521 }
522 free(filename);
523
524 filename = malloc(strlen(inspect_info.file_name) + 8);
525 sprintf(filename, "%s-rootfs", inspect_info.file_name);
526 printf("Extracting rootfs to \"%s\"...\n", filename);
527 fp = fopen(filename, "w");
528 if (fp) {
529 if (!fwrite(buf + ntohl(hdr->rootfs_ofs),
530 ntohl(hdr->rootfs_len), 1, fp)) {
531 ERR("error in fwrite(): %s", strerror(errno));
532 }
533 fclose(fp);
534 } else {
535 ERR("error in fopen(): %s", strerror(errno));
536 }
537 free(filename);
538 }
539
540 out_free_buf:
541 free(buf);
542 out:
543 return ret;
544 }
545
546 int main(int argc, char *argv[])
547 {
548 int ret = EXIT_FAILURE;
549
550 progname = basename(argv[0]);
551
552 while ( 1 ) {
553 int c;
554
555 c = getopt(argc, argv, "a:H:E:F:L:V:N:W:w:ci:k:r:R:o:xhsjv:y:T:e");
556 if (c == -1)
557 break;
558
559 switch (c) {
560 case 'a':
561 sscanf(optarg, "0x%x", &rootfs_align);
562 break;
563 case 'H':
564 opt_hw_id = optarg;
565 break;
566 case 'E':
567 sscanf(optarg, "0x%x", &kernel_ep);
568 break;
569 case 'F':
570 layout_id = optarg;
571 break;
572 case 'W':
573 opt_hw_rev = optarg;
574 break;
575 case 'w':
576 opt_hw_ver_add = optarg;
577 break;
578 case 'L':
579 sscanf(optarg, "0x%x", &kernel_la);
580 break;
581 case 'V':
582 version = optarg;
583 break;
584 case 'v':
585 fw_ver = optarg;
586 break;
587 case 'y':
588 sver = optarg;
589 break;
590 case 'N':
591 vendor = optarg;
592 break;
593 case 'c':
594 combined++;
595 break;
596 case 'k':
597 kernel_info.file_name = optarg;
598 break;
599 case 'r':
600 rootfs_info.file_name = optarg;
601 break;
602 case 'R':
603 sscanf(optarg, "0x%x", &rootfs_ofs);
604 break;
605 case 'o':
606 ofname = optarg;
607 break;
608 case 's':
609 strip_padding = 1;
610 break;
611 case 'i':
612 inspect_info.file_name = optarg;
613 break;
614 case 'j':
615 add_jffs2_eof = 1;
616 break;
617 case 'x':
618 extract = 1;
619 break;
620 case 'T':
621 hdr_ver = atoi(optarg);
622 break;
623 case 'e':
624 custom_board.flags = FLAG_LE_KERNEL_LA_EP;
625 break;
626 case 'h':
627 usage(EXIT_SUCCESS);
628 break;
629 default:
630 usage(EXIT_FAILURE);
631 break;
632 }
633 }
634
635 ret = check_options();
636 if (ret)
637 goto out;
638
639 if (!inspect_info.file_name)
640 ret = build_fw(sizeof(struct fw_header));
641 else
642 ret = inspect_fw();
643
644 out:
645 return ret;
646 }
647