1 // SPDX-License-Identifier: GPL-2.0-only
3 * Builder/viewer/extractor utility for Poray firmware image files
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>
9 * This tool is based on:
10 * TP-Link firmware upgrade tool.
11 * Copyright (C) 2009 Gabor Juhos <juhosg@openwrt.org>
14 * TP-Link WR941 V2 firmware checksum fixing tool.
15 * Copyright (C) 2008,2009 Wang Jian <lark@linux.net.cn>
28 #include <arpa/inet.h>
29 #include <netinet/in.h>
31 #if (__BYTE_ORDER == __BIG_ENDIAN)
32 # define HOST_TO_BE32(x) (x)
33 # define BE32_TO_HOST(x) (x)
34 # define HOST_TO_LE32(x) bswap_32(x)
35 # define LE32_TO_HOST(x) bswap_32(x)
37 # define HOST_TO_BE32(x) bswap_32(x)
38 # define BE32_TO_HOST(x) bswap_32(x)
39 # define HOST_TO_LE32(x) (x)
40 # define LE32_TO_HOST(x) (x)
43 /* Fixed header flags */
44 #define HEADER_FLAGS 0x020e0000
46 /* Recognized Hardware ID magic */
47 #define HWID_HAME_MPR_A1_L8 0x32473352
48 #define HWID_PORAY_R50B 0x31353033
49 #define HWID_PORAY_R50D 0x33353033
50 #define HWID_PORAY_R50E 0x34353033
51 #define HWID_PORAY_M3 0x31353335
52 #define HWID_PORAY_M4 0x32353335
53 #define HWID_PORAY_Q3 0x33353335
54 #define HWID_PORAY_X5_X6 0x35353335
55 #define HWID_PORAY_X8 0x36353335
56 #define HWID_PORAY_X1 0x38353335
57 #define HWID_NEXX_WT1520 0x30353332
58 #define HWID_NEXX_WT3020 0x30323033
59 #define HWID_A5_V11 0x32473352
61 /* Recognized XOR obfuscation keys */
75 char *file_name
; /* Name of the file */
76 uint32_t file_size
; /* Length of the file */
80 uint32_t hw_id
; /* Hardware id */
81 uint32_t firmware_len
; /* Firmware data length */
82 uint32_t flags
; /* Header flags */
84 } __attribute__ ((packed
));
102 static char *progname
;
104 static char *board_id
;
105 static struct board_info
*board
;
106 static char *layout_id
;
107 static struct flash_layout
*layout
;
108 static char *opt_hw_id
;
109 static uint32_t hw_id
;
110 static struct file_info firmware_info
;
111 static uint32_t firmware_len
= 0;
113 static int inspect
= 0;
114 static int extract
= 0;
116 static uint8_t key
[][KEY_LEN
] = {
117 {0xC8, 0x3C, 0x3A, 0x93, 0xA2, 0x95, 0xC3, 0x63, 0x48, 0x45, 0x58, 0x09, 0x12, 0x03, 0x08},
118 {0x89, 0x6B, 0x5A, 0x93, 0x92, 0x95, 0xC3, 0x63, 0xD0, 0xA3, 0x9C, 0x92, 0x2E, 0xE6, 0xC7},
119 {0xC9, 0x1C, 0x3A, 0x93, 0x92, 0x95, 0xC3, 0x63, 0xD0, 0xA3, 0x9C, 0x92, 0x2E, 0xE6, 0xC7},
120 {0x19, 0x1B, 0x3A, 0x93, 0x92, 0x95, 0xC3, 0x63, 0xD0, 0xA3, 0x9C, 0x92, 0x2E, 0xE6, 0xC7},
121 {0x79, 0x7B, 0x7A, 0x93, 0x92, 0x95, 0xC3, 0x63, 0xD0, 0xA3, 0x9C, 0x92, 0x2E, 0xE6, 0xC7},
122 {0x19, 0x1C, 0x4A, 0x93, 0x96, 0x95, 0xC3, 0x63, 0xD0, 0xA3, 0x9C, 0x92, 0x2E, 0x16, 0xC6},
123 {0x39, 0x1C, 0x4A, 0x93, 0x96, 0x95, 0xC3, 0x63, 0xD0, 0xA3, 0x9C, 0x92, 0x2E, 0x16, 0xC6},
124 {0xC8, 0x3C, 0x3A, 0x93, 0xA2, 0x95, 0xC3, 0x63, 0x48, 0x45, 0x58, 0x09, 0x20, 0x11, 0x08},
127 static struct flash_layout layouts
[] = {
130 .fw_max_len
= 0x3c0000,
133 .fw_max_len
= 0x7c0000,
135 /* terminating entry */
139 static struct board_info boards
[] = {
142 .hw_id
= HWID_A5_V11
,
147 .hw_id
= HWID_HAME_MPR_A1_L8
,
152 .hw_id
= HWID_HAME_MPR_A1_L8
,
157 .hw_id
= HWID_PORAY_R50B
,
162 .hw_id
= HWID_PORAY_R50D
,
167 .hw_id
= HWID_PORAY_R50E
,
172 .hw_id
= HWID_PORAY_M3
,
177 .hw_id
= HWID_PORAY_M4
,
182 .hw_id
= HWID_PORAY_Q3
,
187 .hw_id
= HWID_PORAY_X5_X6
,
192 .hw_id
= HWID_PORAY_X5_X6
,
197 .hw_id
= HWID_PORAY_X5_X6
,
202 .hw_id
= HWID_PORAY_X8
,
207 .hw_id
= HWID_PORAY_X1
,
212 .hw_id
= HWID_NEXX_WT1520
,
217 .hw_id
= HWID_NEXX_WT1520
,
222 .hw_id
= HWID_NEXX_WT3020
,
227 .hw_id
= HWID_NEXX_WT3020
,
235 /* terminating entry */
242 #define ERR(fmt, ...) do { \
244 fprintf(stderr, "[%s] *** error: " fmt "\n", \
245 progname, ## __VA_ARGS__ ); \
248 #define ERRS(fmt, ...) do { \
251 fprintf(stderr, "[%s] *** error: " fmt ":%s\n", \
252 progname, ## __VA_ARGS__, strerror(save)); \
255 #define DBG(fmt, ...) do { \
256 fprintf(stderr, "[%s] " fmt "\n", progname, ## __VA_ARGS__ ); \
260 * Find a board by its name
262 static struct board_info
*find_board(char *id
)
264 struct board_info
*ret
;
265 struct board_info
*board
;
268 for (board
= boards
; board
->id
!= NULL
; board
++){
269 if (strcasecmp(id
, board
->id
) == 0) {
279 * Find a board by its hardware ID
281 static struct board_info
*find_board_by_hwid(uint32_t hw_id
)
283 struct board_info
*board
;
285 for (board
= boards
; board
->id
!= NULL
; board
++) {
286 if (hw_id
== board
->hw_id
)
294 * Find a Flash memory layout by its name
296 static struct flash_layout
*find_layout(char *id
)
298 struct flash_layout
*ret
;
299 struct flash_layout
*l
;
302 for (l
= layouts
; l
->id
!= NULL
; l
++){
303 if (strcasecmp(id
, l
->id
) == 0) {
315 static void usage(int status
)
317 FILE *stream
= (status
!= EXIT_SUCCESS
) ? stderr
: stdout
;
319 fprintf(stream
, "Usage: %s [OPTIONS...]\n", progname
);
323 " -B <board> create image for the board specified with <board>\n"
324 " -H <hwid> use hardware id specified with <hwid>\n"
325 " -F <id> use flash layout specified with <id>\n"
326 " -f <file> read firmware image from the file <file>\n"
327 " -o <file> write output to the file <file>\n"
328 " -i inspect given firmware file (requires -f)\n"
329 " -x extract combined kernel and rootfs while inspecting (implies -i)\n"
330 " -h show this screen\n"
337 * Get file statistics
339 static int get_file_stat(struct file_info
*fdata
)
344 if (fdata
->file_name
== NULL
) {
347 res
= stat(fdata
->file_name
, &st
);
349 ERRS("stat failed on %s", fdata
->file_name
);
353 fdata
->file_size
= st
.st_size
;
358 * Read file into buffer
360 static int read_to_buf(struct file_info
*fdata
, uint8_t *buf
)
363 int ret
= EXIT_FAILURE
;
365 f
= fopen(fdata
->file_name
, "rb");
367 ERRS("could not open \"%s\" for reading", fdata
->file_name
);
372 fread(buf
, fdata
->file_size
, 1, f
);
374 ERRS("unable to read from file \"%s\"", fdata
->file_name
);
387 * Check command line options
389 static int check_options(void)
393 if (firmware_info
.file_name
== NULL
) {
394 ERR("no firmware image specified");
398 ret
= get_file_stat(&firmware_info
);
405 if (board_id
== NULL
&& opt_hw_id
== NULL
) {
406 ERR("either board or hardware id must be specified");
411 board
= find_board(board_id
);
413 ERR("unknown/unsupported board id \"%s\"", board_id
);
416 if (layout_id
== NULL
) {
417 layout_id
= board
->layout_id
;
419 hw_id
= board
->hw_id
;
421 hw_id
= strtoul(opt_hw_id
, NULL
, 0);
422 board
= find_board_by_hwid(hw_id
);
423 if (layout_id
== NULL
) {
424 layout_id
= board
->layout_id
;
428 layout
= find_layout(layout_id
);
429 if (layout
== NULL
) {
430 ERR("unknown flash layout \"%s\"", layout_id
);
434 firmware_len
= firmware_info
.file_size
;
436 if (firmware_info
.file_size
>
437 layout
->fw_max_len
- sizeof (struct fw_header
)) {
438 ERR("firmware image is too big");
442 if (ofname
== NULL
) {
443 ERR("no output file specified");
450 * Fill in firmware header
452 static void fill_header(uint8_t *buf
)
454 struct fw_header
*hdr
= (struct fw_header
*) buf
;
456 memset(hdr
, 0, sizeof (struct fw_header
));
457 hdr
->hw_id
= HOST_TO_LE32(hw_id
);
458 hdr
->firmware_len
= HOST_TO_LE32(firmware_len
);
459 hdr
->flags
= HOST_TO_LE32(HEADER_FLAGS
);
463 * Compute firmware checksum
465 static uint16_t checksum_fw(uint8_t *data
, int len
)
468 int32_t checksum
= 0;
470 for (i
= 0; i
< len
- 1; i
+= 2) {
471 checksum
+= (data
[i
+ 1] << 8) | data
[i
];
476 checksum
= checksum
+ (checksum
>> 16) + 0xffff;
477 checksum
= ~(checksum
+ (checksum
>> 16)) & 0xffff;
478 return (uint16_t) checksum
;
482 * (De)obfuscate firmware using an XOR operation with a fixed length key
484 static void xor_fw(uint8_t *data
, int len
)
488 for (i
= 0; i
<= len
; i
++) {
489 data
[i
] ^= key
[board
->key
][i
% KEY_LEN
];
494 * Write firmware to file
496 static int write_fw(uint8_t *data
, int len
)
499 int ret
= EXIT_FAILURE
;
501 f
= fopen(ofname
, "wb");
503 ERRS("could not open \"%s\" for writing", ofname
);
508 fwrite(data
, len
, 1, f
);
510 ERRS("unable to write output file");
514 DBG("firmware file \"%s\" completed", ofname
);
521 if (ret
!= EXIT_SUCCESS
) {
529 * Build firmware file
531 static int build_fw(void)
535 int ret
= EXIT_FAILURE
;
539 buflen
= layout
->fw_max_len
;
541 buf
= (uint8_t *) malloc(buflen
);
543 ERR("no memory for buffer\n");
547 memset(buf
, 0xff, buflen
);
548 p
= buf
+ sizeof (struct fw_header
);
549 ret
= read_to_buf(&firmware_info
, p
);
553 writelen
= sizeof (struct fw_header
) + firmware_len
+ 2;
558 /* Compute firmware checksum */
559 checksum
= checksum_fw(buf
+ sizeof (struct fw_header
), firmware_len
);
561 /* Cannot use network order function because checksum is not word-aligned */
562 buf
[writelen
- 1] = checksum
>> 8;
563 buf
[writelen
- 2] = checksum
& 0xff;
565 /* XOR obfuscate firmware */
566 xor_fw(buf
+ sizeof (struct fw_header
), firmware_len
+ 2);
568 /* Write firmware file */
569 ret
= write_fw(buf
, writelen
);
581 /* Helper functions to inspect_fw() representing different output formats */
582 static inline void inspect_fw_pstr(char *label
, char *str
)
584 printf("%-23s: %s\n", label
, str
);
587 static inline void inspect_fw_phex(char *label
, uint32_t val
)
589 printf("%-23s: 0x%08x\n", label
, val
);
592 static inline void inspect_fw_phexpost(char *label
,
593 uint32_t val
, char *post
)
595 printf("%-23s: 0x%08x (%s)\n", label
, val
, post
);
598 static inline void inspect_fw_phexdef(char *label
,
599 uint32_t val
, uint32_t defval
)
601 printf("%-23s: 0x%08x ", label
, val
);
604 printf("(== OpenWrt default)\n");
606 printf("(OpenWrt default: 0x%08x)\n", defval
);
610 static inline void inspect_fw_phexexp(char *label
,
611 uint32_t val
, uint32_t expval
)
613 printf("%-23s: 0x%08x ", label
, val
);
618 printf("(expected: 0x%08x)\n", expval
);
622 static inline void inspect_fw_phexdec(char *label
, uint32_t val
)
624 printf("%-23s: 0x%08x / %8u bytes\n", label
, val
, val
);
627 static inline void inspect_fw_pchecksum(char *label
,
628 uint16_t val
, uint16_t expval
)
630 printf("%-23s: 0x%04x ", label
, val
);
634 printf("(expected: 0x%04x)\n", expval
);
638 static int inspect_fw(void)
641 struct fw_header
*hdr
;
642 int ret
= EXIT_FAILURE
;
643 uint16_t computed_checksum
, file_checksum
;
645 buf
= (uint8_t *) malloc(firmware_info
.file_size
);
647 ERR("no memory for buffer!\n");
651 ret
= read_to_buf(&firmware_info
, buf
);
655 hdr
= (struct fw_header
*)buf
;
657 inspect_fw_pstr("File name", firmware_info
.file_name
);
658 inspect_fw_phexdec("File size", firmware_info
.file_size
);
662 inspect_fw_phexdec("Header size", sizeof (struct fw_header
));
663 board
= find_board_by_hwid(LE32_TO_HOST(hdr
->hw_id
));
665 layout
= find_layout(board
->layout_id
);
666 inspect_fw_phexpost("Hardware ID",
667 LE32_TO_HOST( hdr
->hw_id
), board
->id
);
669 inspect_fw_phexpost("Hardware ID",
670 LE32_TO_HOST(hdr
->hw_id
), "unknown");
672 inspect_fw_phexdec("Firmware data length",
673 LE32_TO_HOST(hdr
->firmware_len
));
675 inspect_fw_phexexp("Flags",
676 LE32_TO_HOST(hdr
->flags
), HEADER_FLAGS
);
679 /* XOR unobfuscate firmware */
680 xor_fw(buf
+ sizeof (struct fw_header
), LE32_TO_HOST(hdr
->firmware_len
) + 2);
682 /* Compute firmware checksum */
683 computed_checksum
= checksum_fw(buf
+ sizeof (struct fw_header
), LE32_TO_HOST(hdr
->firmware_len
));
685 /* Cannot use network order function because checksum is not word-aligned */
686 file_checksum
= (buf
[firmware_info
.file_size
- 1] << 8) | buf
[firmware_info
.file_size
- 2];
687 inspect_fw_pchecksum("Firmware checksum", computed_checksum
, file_checksum
);
689 /* Verify checksum */
690 if (computed_checksum
!= file_checksum
) {
692 ERR("checksums do not match");
702 if (ofname
== NULL
) {
703 filename
= malloc(strlen(firmware_info
.file_name
) + 10);
704 sprintf(filename
, "%s-firmware", firmware_info
.file_name
);
708 printf("Extracting firmware to \"%s\"...\n", filename
);
709 fp
= fopen(filename
, "wb");
711 if (!fwrite(buf
+ sizeof (struct fw_header
),
712 LE32_TO_HOST(hdr
->firmware_len
), 1, fp
)) {
713 ERRS("error in fwrite(): %s", strerror(errno
));
717 ERRS("error in fopen(): %s", strerror(errno
));
719 if (ofname
== NULL
) {
734 int main(int argc
, char *argv
[])
736 int ret
= EXIT_FAILURE
;
738 progname
= basename(argv
[0]);
742 while ((c
= getopt(argc
, argv
, "B:H:F:f:o:ixh")) != -1) {
754 firmware_info
.file_name
= optarg
;
775 ret
= check_options();