1 // SPDX-License-Identifier: GPL-2.0-only 2 /* 3 * Builder/viewer/extractor utility for Poray firmware image files 4 * 5 * Copyright (C) 2013 Michel Stempin <michel.stempin@wanadoo.fr> 6 * Copyright (C) 2013 Felix Kaechele <felix@fetzig.org> 7 * Copyright (C) 2013 <admin@openschemes.com> 8 * 9 * This tool is based on: 10 * TP-Link firmware upgrade tool. 11 * Copyright (C) 2009 Gabor Juhos <juhosg@openwrt.org> 12 * 13 * Itself based on: 14 * TP-Link WR941 V2 firmware checksum fixing tool. 15 * Copyright (C) 2008,2009 Wang Jian <lark@linux.net.cn> 16 */ 17 18 #include <byteswap.h> 19 #include <stdio.h> 20 #include <stdlib.h> 21 #include <stdint.h> 22 #include <string.h> 23 #include <unistd.h> 24 #include <libgen.h> 25 #include <getopt.h> 26 #include <stdarg.h> 27 #include <errno.h> 28 #include <sys/stat.h> 29 #include <arpa/inet.h> 30 #include <netinet/in.h> 31 32 #if (__BYTE_ORDER == __BIG_ENDIAN) 33 # define HOST_TO_BE32(x) (x) 34 # define BE32_TO_HOST(x) (x) 35 # define HOST_TO_LE32(x) bswap_32(x) 36 # define LE32_TO_HOST(x) bswap_32(x) 37 #else 38 # define HOST_TO_BE32(x) bswap_32(x) 39 # define BE32_TO_HOST(x) bswap_32(x) 40 # define HOST_TO_LE32(x) (x) 41 # define LE32_TO_HOST(x) (x) 42 #endif 43 44 /* Fixed header flags */ 45 #define HEADER_FLAGS 0x020e0000 46 47 /* Recognized Hardware ID magic */ 48 #define HWID_HAME_MPR_A1_L8 0x32473352 49 #define HWID_PORAY_R50B 0x31353033 50 #define HWID_PORAY_R50D 0x33353033 51 #define HWID_PORAY_R50E 0x34353033 52 #define HWID_PORAY_M3 0x31353335 53 #define HWID_PORAY_M4 0x32353335 54 #define HWID_PORAY_Q3 0x33353335 55 #define HWID_PORAY_X5_X6 0x35353335 56 #define HWID_PORAY_X8 0x36353335 57 #define HWID_PORAY_X1 0x38353335 58 #define HWID_NEXX_WT1520 0x30353332 59 #define HWID_NEXX_WT3020 0x30323033 60 #define HWID_A5_V11 0x32473352 61 62 /* Recognized XOR obfuscation keys */ 63 #define KEY_HAME 0 64 #define KEY_PORAY_1 1 65 #define KEY_PORAY_2 2 66 #define KEY_PORAY_3 3 67 #define KEY_PORAY_4 4 68 #define KEY_NEXX_1 5 69 #define KEY_NEXX_2 6 70 #define KEY_A5_V11 7 71 72 /* XOR key length */ 73 #define KEY_LEN 15 74 75 struct file_info { 76 char *file_name; /* Name of the file */ 77 uint32_t file_size; /* Length of the file */ 78 }; 79 80 struct fw_header { 81 uint32_t hw_id; /* Hardware id */ 82 uint32_t firmware_len; /* Firmware data length */ 83 uint32_t flags; /* Header flags */ 84 uint8_t pad[16]; 85 } __attribute__ ((packed)); 86 87 struct flash_layout { 88 char *id; 89 uint32_t fw_max_len; 90 }; 91 92 struct board_info { 93 char *id; 94 uint32_t hw_id; 95 char *layout_id; 96 uint32_t key; 97 }; 98 99 /* 100 * Globals 101 */ 102 static char *ofname; 103 static char *progname; 104 105 static char *board_id; 106 static struct board_info *board; 107 static char *layout_id; 108 static struct flash_layout *layout; 109 static char *opt_hw_id; 110 static uint32_t hw_id; 111 static struct file_info firmware_info; 112 static uint32_t firmware_len = 0; 113 114 static int inspect = 0; 115 static int extract = 0; 116 117 static uint8_t key[][KEY_LEN] = { 118 {0xC8, 0x3C, 0x3A, 0x93, 0xA2, 0x95, 0xC3, 0x63, 0x48, 0x45, 0x58, 0x09, 0x12, 0x03, 0x08}, 119 {0x89, 0x6B, 0x5A, 0x93, 0x92, 0x95, 0xC3, 0x63, 0xD0, 0xA3, 0x9C, 0x92, 0x2E, 0xE6, 0xC7}, 120 {0xC9, 0x1C, 0x3A, 0x93, 0x92, 0x95, 0xC3, 0x63, 0xD0, 0xA3, 0x9C, 0x92, 0x2E, 0xE6, 0xC7}, 121 {0x19, 0x1B, 0x3A, 0x93, 0x92, 0x95, 0xC3, 0x63, 0xD0, 0xA3, 0x9C, 0x92, 0x2E, 0xE6, 0xC7}, 122 {0x79, 0x7B, 0x7A, 0x93, 0x92, 0x95, 0xC3, 0x63, 0xD0, 0xA3, 0x9C, 0x92, 0x2E, 0xE6, 0xC7}, 123 {0x19, 0x1C, 0x4A, 0x93, 0x96, 0x95, 0xC3, 0x63, 0xD0, 0xA3, 0x9C, 0x92, 0x2E, 0x16, 0xC6}, 124 {0x39, 0x1C, 0x4A, 0x93, 0x96, 0x95, 0xC3, 0x63, 0xD0, 0xA3, 0x9C, 0x92, 0x2E, 0x16, 0xC6}, 125 {0xC8, 0x3C, 0x3A, 0x93, 0xA2, 0x95, 0xC3, 0x63, 0x48, 0x45, 0x58, 0x09, 0x20, 0x11, 0x08}, 126 }; 127 128 static struct flash_layout layouts[] = { 129 { 130 .id = "4M", 131 .fw_max_len = 0x3c0000, 132 }, { 133 .id = "8M", 134 .fw_max_len = 0x7c0000, 135 }, { 136 /* terminating entry */ 137 } 138 }; 139 140 static struct board_info boards[] = { 141 { 142 .id = "A5-V11", 143 .hw_id = HWID_A5_V11, 144 .layout_id = "4M", 145 .key = KEY_A5_V11, 146 }, { 147 .id = "MPR-A1", 148 .hw_id = HWID_HAME_MPR_A1_L8, 149 .layout_id = "4M", 150 .key = KEY_HAME, 151 }, { 152 .id = "MPR-L8", 153 .hw_id = HWID_HAME_MPR_A1_L8, 154 .layout_id = "4M", 155 .key = KEY_HAME, 156 }, { 157 .id = "R50B", 158 .hw_id = HWID_PORAY_R50B, 159 .layout_id = "4M", 160 .key = KEY_PORAY_2, 161 }, { 162 .id = "R50D", 163 .hw_id = HWID_PORAY_R50D, 164 .layout_id = "4M", 165 .key = KEY_PORAY_3, 166 }, { 167 .id = "R50E", 168 .hw_id = HWID_PORAY_R50E, 169 .layout_id = "4M", 170 .key = KEY_PORAY_4, 171 }, { 172 .id = "M3", 173 .hw_id = HWID_PORAY_M3, 174 .layout_id = "4M", 175 .key = KEY_PORAY_1, 176 }, { 177 .id = "M4", 178 .hw_id = HWID_PORAY_M4, 179 .layout_id = "4M", 180 .key = KEY_PORAY_1, 181 }, { 182 .id = "Q3", 183 .hw_id = HWID_PORAY_Q3, 184 .layout_id = "4M", 185 .key = KEY_PORAY_1, 186 }, { 187 .id = "X5 or X6", 188 .hw_id = HWID_PORAY_X5_X6, 189 .layout_id = "8M", 190 .key = KEY_PORAY_1, 191 }, { 192 .id = "X5", 193 .hw_id = HWID_PORAY_X5_X6, 194 .layout_id = "8M", 195 .key = KEY_PORAY_1, 196 }, { 197 .id = "X6", 198 .hw_id = HWID_PORAY_X5_X6, 199 .layout_id = "8M", 200 .key = KEY_PORAY_1, 201 }, { 202 .id = "X8", 203 .hw_id = HWID_PORAY_X8, 204 .layout_id = "8M", 205 .key = KEY_PORAY_1, 206 }, { 207 .id = "X1", 208 .hw_id = HWID_PORAY_X1, 209 .layout_id = "8M", 210 .key = KEY_PORAY_1, 211 }, { 212 .id = "WT1520", 213 .hw_id = HWID_NEXX_WT1520, 214 .layout_id = "4M", 215 .key = KEY_NEXX_1, 216 }, { 217 .id = "WT1520", 218 .hw_id = HWID_NEXX_WT1520, 219 .layout_id = "8M", 220 .key = KEY_NEXX_1, 221 }, { 222 .id = "WT3020", 223 .hw_id = HWID_NEXX_WT3020, 224 .layout_id = "4M", 225 .key = KEY_NEXX_2, 226 }, { 227 .id = "WT3020", 228 .hw_id = HWID_NEXX_WT3020, 229 .layout_id = "8M", 230 .key = KEY_NEXX_2, 231 }, { 232 233 234 235 236 /* terminating entry */ 237 } 238 }; 239 240 /* 241 * Message macros 242 */ 243 #define ERR(fmt, ...) do { \ 244 fflush(0); \ 245 fprintf(stderr, "[%s] *** error: " fmt "\n", \ 246 progname, ## __VA_ARGS__ ); \ 247 } while (0) 248 249 #define ERRS(fmt, ...) do { \ 250 int save = errno; \ 251 fflush(0); \ 252 fprintf(stderr, "[%s] *** error: " fmt ":%s\n", \ 253 progname, ## __VA_ARGS__, strerror(save)); \ 254 } while (0) 255 256 #define DBG(fmt, ...) do { \ 257 fprintf(stderr, "[%s] " fmt "\n", progname, ## __VA_ARGS__ ); \ 258 } while (0) 259 260 /* 261 * Find a board by its name 262 */ 263 static struct board_info *find_board(char *id) 264 { 265 struct board_info *ret; 266 struct board_info *board; 267 268 ret = NULL; 269 for (board = boards; board->id != NULL; board++){ 270 if (strcasecmp(id, board->id) == 0) { 271 ret = board; 272 break; 273 } 274 }; 275 276 return ret; 277 } 278 279 /* 280 * Find a board by its hardware ID 281 */ 282 static struct board_info *find_board_by_hwid(uint32_t hw_id) 283 { 284 struct board_info *board; 285 286 for (board = boards; board->id != NULL; board++) { 287 if (hw_id == board->hw_id) 288 return board; 289 }; 290 291 return NULL; 292 } 293 294 /* 295 * Find a Flash memory layout by its name 296 */ 297 static struct flash_layout *find_layout(char *id) 298 { 299 struct flash_layout *ret; 300 struct flash_layout *l; 301 302 ret = NULL; 303 for (l = layouts; l->id != NULL; l++){ 304 if (strcasecmp(id, l->id) == 0) { 305 ret = l; 306 break; 307 } 308 }; 309 310 return ret; 311 } 312 313 /* 314 * Display usage 315 */ 316 static void usage(int status) 317 { 318 FILE *stream = (status != EXIT_SUCCESS) ? stderr : stdout; 319 320 fprintf(stream, "Usage: %s [OPTIONS...]\n", progname); 321 fprintf(stream, 322 "\n" 323 "Options:\n" 324 " -B <board> create image for the board specified with <board>\n" 325 " -H <hwid> use hardware id specified with <hwid>\n" 326 " -F <id> use flash layout specified with <id>\n" 327 " -f <file> read firmware image from the file <file>\n" 328 " -o <file> write output to the file <file>\n" 329 " -i inspect given firmware file (requires -f)\n" 330 " -x extract combined kernel and rootfs while inspecting (implies -i)\n" 331 " -h show this screen\n" 332 ); 333 334 exit(status); 335 } 336 337 /* 338 * Get file statistics 339 */ 340 static int get_file_stat(struct file_info *fdata) 341 { 342 struct stat st; 343 int res; 344 345 if (fdata->file_name == NULL) { 346 return 0; 347 } 348 res = stat(fdata->file_name, &st); 349 if (res){ 350 ERRS("stat failed on %s", fdata->file_name); 351 return res; 352 } 353 354 fdata->file_size = st.st_size; 355 return 0; 356 } 357 358 /* 359 * Read file into buffer 360 */ 361 static int read_to_buf(struct file_info *fdata, uint8_t *buf) 362 { 363 FILE *f; 364 int ret = EXIT_FAILURE; 365 366 f = fopen(fdata->file_name, "rb"); 367 if (f == NULL) { 368 ERRS("could not open \"%s\" for reading", fdata->file_name); 369 goto out; 370 } 371 372 errno = 0; 373 fread(buf, fdata->file_size, 1, f); 374 if (errno != 0) { 375 ERRS("unable to read from file \"%s\"", fdata->file_name); 376 goto out_close; 377 } 378 379 ret = EXIT_SUCCESS; 380 381 out_close: 382 fclose(f); 383 out: 384 return ret; 385 } 386 387 /* 388 * Check command line options 389 */ 390 static int check_options(void) 391 { 392 int ret; 393 394 if (firmware_info.file_name == NULL) { 395 ERR("no firmware image specified"); 396 return -1; 397 } 398 399 ret = get_file_stat(&firmware_info); 400 if (ret) 401 return ret; 402 403 if (inspect) 404 return 0; 405 406 if (board_id == NULL && opt_hw_id == NULL) { 407 ERR("either board or hardware id must be specified"); 408 return -1; 409 } 410 411 if (board_id) { 412 board = find_board(board_id); 413 if (board == NULL) { 414 ERR("unknown/unsupported board id \"%s\"", board_id); 415 return -1; 416 } 417 if (layout_id == NULL) { 418 layout_id = board->layout_id; 419 } 420 hw_id = board->hw_id; 421 } else { 422 hw_id = strtoul(opt_hw_id, NULL, 0); 423 board = find_board_by_hwid(hw_id); 424 if (layout_id == NULL) { 425 layout_id = board->layout_id; 426 } 427 } 428 429 layout = find_layout(layout_id); 430 if (layout == NULL) { 431 ERR("unknown flash layout \"%s\"", layout_id); 432 return -1; 433 } 434 435 firmware_len = firmware_info.file_size; 436 437 if (firmware_info.file_size > 438 layout->fw_max_len - sizeof (struct fw_header)) { 439 ERR("firmware image is too big"); 440 return -1; 441 } 442 443 if (ofname == NULL) { 444 ERR("no output file specified"); 445 return -1; 446 } 447 return 0; 448 } 449 450 /* 451 * Fill in firmware header 452 */ 453 static void fill_header(uint8_t *buf) 454 { 455 struct fw_header *hdr = (struct fw_header *) buf; 456 457 memset(hdr, 0, sizeof (struct fw_header)); 458 hdr->hw_id = HOST_TO_LE32(hw_id); 459 hdr->firmware_len = HOST_TO_LE32(firmware_len); 460 hdr->flags = HOST_TO_LE32(HEADER_FLAGS); 461 } 462 463 /* 464 * Compute firmware checksum 465 */ 466 static uint16_t checksum_fw(uint8_t *data, int len) 467 { 468 int i; 469 int32_t checksum = 0; 470 471 for (i = 0; i < len - 1; i += 2) { 472 checksum += (data[i + 1] << 8) | data[i]; 473 } 474 if (i < len) { 475 checksum += data[i]; 476 } 477 checksum = checksum + (checksum >> 16) + 0xffff; 478 checksum = ~(checksum + (checksum >> 16)) & 0xffff; 479 return (uint16_t) checksum; 480 } 481 482 /* 483 * (De)obfuscate firmware using an XOR operation with a fixed length key 484 */ 485 static void xor_fw(uint8_t *data, int len) 486 { 487 int i; 488 489 for (i = 0; i <= len; i++) { 490 data[i] ^= key[board->key][i % KEY_LEN]; 491 } 492 } 493 494 /* 495 * Write firmware to file 496 */ 497 static int write_fw(uint8_t *data, int len) 498 { 499 FILE *f; 500 int ret = EXIT_FAILURE; 501 502 f = fopen(ofname, "wb"); 503 if (f == NULL) { 504 ERRS("could not open \"%s\" for writing", ofname); 505 goto out; 506 } 507 508 errno = 0; 509 fwrite(data, len, 1, f); 510 if (errno) { 511 ERRS("unable to write output file"); 512 goto out_flush; 513 } 514 515 DBG("firmware file \"%s\" completed", ofname); 516 517 ret = EXIT_SUCCESS; 518 519 out_flush: 520 fflush(f); 521 fclose(f); 522 if (ret != EXIT_SUCCESS) { 523 unlink(ofname); 524 } 525 out: 526 return ret; 527 } 528 529 /* 530 * Build firmware file 531 */ 532 static int build_fw(void) 533 { 534 int buflen; 535 uint8_t *buf, *p; 536 int ret = EXIT_FAILURE; 537 int writelen = 0; 538 uint16_t checksum; 539 540 buflen = layout->fw_max_len; 541 542 buf = (uint8_t *) malloc(buflen); 543 if (!buf) { 544 ERR("no memory for buffer\n"); 545 goto out; 546 } 547 548 memset(buf, 0xff, buflen); 549 p = buf + sizeof (struct fw_header); 550 ret = read_to_buf(&firmware_info, p); 551 if (ret) { 552 goto out_free_buf; 553 } 554 writelen = sizeof (struct fw_header) + firmware_len + 2; 555 556 /* Fill in header */ 557 fill_header(buf); 558 559 /* Compute firmware checksum */ 560 checksum = checksum_fw(buf + sizeof (struct fw_header), firmware_len); 561 562 /* Cannot use network order function because checksum is not word-aligned */ 563 buf[writelen - 1] = checksum >> 8; 564 buf[writelen - 2] = checksum & 0xff; 565 566 /* XOR obfuscate firmware */ 567 xor_fw(buf + sizeof (struct fw_header), firmware_len + 2); 568 569 /* Write firmware file */ 570 ret = write_fw(buf, writelen); 571 if (ret) { 572 goto out_free_buf; 573 } 574 ret = EXIT_SUCCESS; 575 576 out_free_buf: 577 free(buf); 578 out: 579 return ret; 580 } 581 582 /* Helper functions to inspect_fw() representing different output formats */ 583 static inline void inspect_fw_pstr(char *label, char *str) 584 { 585 printf("%-23s: %s\n", label, str); 586 } 587 588 static inline void inspect_fw_phex(char *label, uint32_t val) 589 { 590 printf("%-23s: 0x%08x\n", label, val); 591 } 592 593 static inline void inspect_fw_phexpost(char *label, 594 uint32_t val, char *post) 595 { 596 printf("%-23s: 0x%08x (%s)\n", label, val, post); 597 } 598 599 static inline void inspect_fw_phexdef(char *label, 600 uint32_t val, uint32_t defval) 601 { 602 printf("%-23s: 0x%08x ", label, val); 603 604 if (val == defval) { 605 printf("(== OpenWrt default)\n"); 606 } else { 607 printf("(OpenWrt default: 0x%08x)\n", defval); 608 } 609 } 610 611 static inline void inspect_fw_phexexp(char *label, 612 uint32_t val, uint32_t expval) 613 { 614 printf("%-23s: 0x%08x ", label, val); 615 616 if (val == expval) { 617 printf("(ok)\n"); 618 } else { 619 printf("(expected: 0x%08x)\n", expval); 620 } 621 } 622 623 static inline void inspect_fw_phexdec(char *label, uint32_t val) 624 { 625 printf("%-23s: 0x%08x / %8u bytes\n", label, val, val); 626 } 627 628 static inline void inspect_fw_pchecksum(char *label, 629 uint16_t val, uint16_t expval) 630 { 631 printf("%-23s: 0x%04x ", label, val); 632 if (val == expval) { 633 printf("(ok)\n"); 634 } else { 635 printf("(expected: 0x%04x)\n", expval); 636 } 637 } 638 639 static int inspect_fw(void) 640 { 641 uint8_t *buf; 642 struct fw_header *hdr; 643 int ret = EXIT_FAILURE; 644 uint16_t computed_checksum, file_checksum; 645 646 buf = (uint8_t *) malloc(firmware_info.file_size); 647 if (!buf) { 648 ERR("no memory for buffer!\n"); 649 goto out; 650 } 651 652 ret = read_to_buf(&firmware_info, buf); 653 if (ret) { 654 goto out_free_buf; 655 } 656 hdr = (struct fw_header *)buf; 657 658 inspect_fw_pstr("File name", firmware_info.file_name); 659 inspect_fw_phexdec("File size", firmware_info.file_size); 660 661 printf("\n"); 662 663 inspect_fw_phexdec("Header size", sizeof (struct fw_header)); 664 board = find_board_by_hwid(LE32_TO_HOST(hdr->hw_id)); 665 if (board) { 666 layout = find_layout(board->layout_id); 667 inspect_fw_phexpost("Hardware ID", 668 LE32_TO_HOST( hdr->hw_id), board->id); 669 } else { 670 inspect_fw_phexpost("Hardware ID", 671 LE32_TO_HOST(hdr->hw_id), "unknown"); 672 } 673 inspect_fw_phexdec("Firmware data length", 674 LE32_TO_HOST(hdr->firmware_len)); 675 676 inspect_fw_phexexp("Flags", 677 LE32_TO_HOST(hdr->flags), HEADER_FLAGS); 678 printf("\n"); 679 680 /* XOR unobfuscate firmware */ 681 xor_fw(buf + sizeof (struct fw_header), LE32_TO_HOST(hdr->firmware_len) + 2); 682 683 /* Compute firmware checksum */ 684 computed_checksum = checksum_fw(buf + sizeof (struct fw_header), LE32_TO_HOST(hdr->firmware_len)); 685 686 /* Cannot use network order function because checksum is not word-aligned */ 687 file_checksum = (buf[firmware_info.file_size - 1] << 8) | buf[firmware_info.file_size - 2]; 688 inspect_fw_pchecksum("Firmware checksum", computed_checksum, file_checksum); 689 690 /* Verify checksum */ 691 if (computed_checksum != file_checksum) { 692 ret = -1; 693 ERR("checksums do not match"); 694 goto out_free_buf; 695 } 696 697 printf("\n"); 698 699 if (extract) { 700 FILE *fp; 701 char *filename; 702 703 if (ofname == NULL) { 704 filename = malloc(strlen(firmware_info.file_name) + 10); 705 sprintf(filename, "%s-firmware", firmware_info.file_name); 706 } else { 707 filename = ofname; 708 } 709 printf("Extracting firmware to \"%s\"...\n", filename); 710 fp = fopen(filename, "wb"); 711 if (fp) { 712 if (!fwrite(buf + sizeof (struct fw_header), 713 LE32_TO_HOST(hdr->firmware_len), 1, fp)) { 714 ERRS("error in fwrite(): %s", strerror(errno)); 715 } 716 fclose(fp); 717 } else { 718 ERRS("error in fopen(): %s", strerror(errno)); 719 } 720 if (ofname == NULL) { 721 free(filename); 722 } 723 printf("\n"); 724 } 725 726 out_free_buf: 727 free(buf); 728 out: 729 return ret; 730 } 731 732 /* 733 * Main entry point 734 */ 735 int main(int argc, char *argv[]) 736 { 737 int ret = EXIT_FAILURE; 738 739 progname = basename(argv[0]); 740 741 int c; 742 743 while ((c = getopt(argc, argv, "B:H:F:f:o:ixh")) != -1) { 744 switch (c) { 745 case 'B': 746 board_id = optarg; 747 break; 748 case 'H': 749 opt_hw_id = optarg; 750 break; 751 case 'F': 752 layout_id = optarg; 753 break; 754 case 'f': 755 firmware_info.file_name = optarg; 756 break; 757 case 'o': 758 ofname = optarg; 759 break; 760 case 'i': 761 inspect = 1; 762 break; 763 case 'x': 764 inspect = 1; 765 extract = 1; 766 break; 767 case 'h': 768 usage(EXIT_SUCCESS); 769 break; 770 default: 771 usage(EXIT_FAILURE); 772 break; 773 } 774 } 775 776 ret = check_options(); 777 if (ret) { 778 goto out; 779 } 780 if (!inspect) { 781 ret = build_fw(); 782 } else { 783 ret = inspect_fw(); 784 } 785 786 out: 787 return ret; 788 } 789
This page was automatically generated by LXR 0.3.1. • OpenWrt