// SPDX-License-Identifier: GPL-2.0-or-later /* * jcgimage - Create a JCG firmware image * * Copyright (C) 2015 Reinhard Max * Copyright (C) 2019 Davide Fioravanti */ /* * JCG firmware update images consist of a 512 byte header and a * modified uImage (details below) as the payload. * * The payload is obfuscated by XORing it with a key that is generated * from parts of the header. Fortunately only non-essential parts of * the header are used for this and zeroing them results in a zero * key, effectively disabling the obfuscation and allowing us to use * clear text payloads. * * The mandatory parts of the header are: * * - A magic string of "YSZJ" at offset 0. * - A value of 1 at offset 39 (header format version?) * - A CRC32 checksum of the payload at offset 504. * - A CRC32 checksum of the header at offset 508. * * An image constructed by these rules will be accepted by JCG's * U-Boot in resuce mode via TFTP and the payload will be written to * the flash starting at offset 0x00050000. * * JCG's U-Boot does check the content or size of the payload * image. If it is too large, it wraps around and overwrites U-Boot, * requiring JTAG to revive the board. To prevent such bricking from * happening, this tool refuses to build such overlong images. * * Using -m is possible to set the maximum size of the payload. * Otherwise the default MAXSIZE will be used. * For an 8Mb flash, the corresponding maxsize is: * 8 * 1024 * 1024 - 5 * 64 * 1024 = 8388608 - 327680 = 8060928 * * Two more conditions have to be met for a JCG image to be accepted * as a valid update by the web interface of the stock firware: * * - The bytes at offsets 109 and 111 in the header must be a binary * representation of the first two components of the firmware * version as displayed in the update web form, or it will be * rejected as "incorrect product". * * - The payload must start with a valid uImage header whose data * CRC checksum matches the whole rest of the update file rather * than just the number of bytes specified in the size field of the * header. * * This last condition is met by JCG's original firmware images, * because they have both, kernel and rootfs inside the uImage and * abuse the last four bytes of the name field to record the offset of * the file system from the start of the uImage header. This tool * produces such images when called with -k and -r, which are meant to * repack the original firmware after modifying the file systen, * e.g. to add debugging tools and enable shell access. * * In contrast, OpenWrt sysupgrade images consist of a uImage that * only contains the kernel and has the rootfs appended to it. Hence, * the CRC over kernel and file system does not match the one in the * uImage header. Fixing this by adjusting the uImage header is not * possible, because it makes the uImage unusable for booting. Instead * we append four "patch" bytes to the end of the file system, that * are calculated to force the checksum of kernel+fs to be the same as * for the kernel alone. * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* * JCG Firmware image header */ #define JH_MAGIC 0x59535a4a /* "YSZJ" */ struct jcg_header { uint32_t jh_magic; uint8_t jh_version[32]; /* Firmware version string. Fill with zeros to avoid encryption */ uint32_t jh_type; /* must be 1 */ uint8_t jh_info[64]; /* Firmware info string. Fill with zeros to avoid encryption */ uint32_t jh_time; /* Image creation time in seconds since * the Epoch. Does not seem to be used * by the stock firmware. */ uint16_t jh_major; /* Major fimware version */ uint16_t jh_minor; /* Minor fimrmware version */ uint8_t jh_unknown[392]; /* Apparently unused and all zeros */ uint32_t jh_dcrc; /* CRC checksum of the payload */ uint32_t jh_hcrc; /* CRC checksum of the header */ }; /* * JCG uses a modified uImage header that replaces the last four bytes * of the image name with the length of the kernel in the image. */ #define IH_MAGIC 0x27051956 /* Image Magic Number */ #define IH_NMLEN 28 /* Image Name Length */ struct uimage_header { uint32_t ih_magic; /* Image Header Magic Number */ uint32_t ih_hcrc; /* Image Header CRC Checksum */ uint32_t ih_time; /* Image Creation Timestamp */ uint32_t ih_size; /* Image Data Size */ uint32_t ih_load; /* Data Load Address */ uint32_t ih_ep; /* Entry Point Address */ uint32_t ih_dcrc; /* Image Data CRC Checksum */ uint8_t ih_os; /* Operating System */ uint8_t ih_arch; /* CPU architecture */ uint8_t ih_type; /* Image Type */ uint8_t ih_comp; /* Compression Type */ uint8_t ih_name[IH_NMLEN];/* Image Name */ uint32_t ih_fsoff; /* Offset of the file system partition from the start of the header */ }; /* * Open the named file and return its size and file descriptor. * Exit in case of errors. */ int opensize(char *name, size_t *size) { struct stat s; int fd = open(name, O_RDONLY); if (fd < 0) err(1, "cannot open \"%s\"", name); if (fstat(fd, &s) == -1) err(1, "cannot stat \"%s\"", name); *size = s.st_size; return fd; } static time_t source_date_epoch = -1; static void set_source_date_epoch() { char *env = getenv("SOURCE_DATE_EPOCH"); char *endptr = env; errno = 0; if (env && *env) { source_date_epoch = strtoull(env, &endptr, 10); if (errno || (endptr && *endptr != '\0')) { fprintf(stderr, "Invalid SOURCE_DATE_EPOCH"); exit(1); } } } /* * Write the JCG header */ void mkjcgheader(struct jcg_header *h, size_t psize, char *version) { uLong crc; uint16_t major = 0, minor = 0; void *payload = (void *)h + sizeof(*h); time_t t; if (source_date_epoch != -1) t = source_date_epoch; else if ((time(&t) == (time_t)(-1))) err(1, "time call failed"); if (version != NULL) if (sscanf(version, "%hu.%hu", &major, &minor) != 2) err(1, "cannot parse version \"%s\"", version); memset(h, 0, sizeof(*h)); h->jh_magic = htonl(JH_MAGIC); h->jh_type = htonl(1); h->jh_time = htonl(t); h->jh_major = htons(major); h->jh_minor = htons(minor); /* CRC over JCG payload (uImage) */ crc = crc32(0L, Z_NULL, 0); crc = crc32(crc, payload, psize); h->jh_dcrc = htonl(crc); /* CRC over JCG header */ crc = crc32(0L, Z_NULL, 0); crc = crc32(crc, (void *)h, sizeof(*h)); h->jh_hcrc = htonl(crc); } /* * Write the uImage header */ void mkuheader(struct uimage_header *h, size_t ksize, size_t fsize) { uLong crc; void *payload = (void *)h + sizeof(*h); // printf("mkuheader: %p, %zd, %zd\n", h, ksize, fsize); memset(h, 0, sizeof(*h)); h->ih_magic = htonl(IH_MAGIC); h->ih_time = htonl(time(NULL)); h->ih_size = htonl(ksize + fsize); h->ih_load = htonl(0x80000000); h->ih_ep = htonl(0x80292000); h->ih_os = 0x05; h->ih_arch = 0x05; h->ih_type = 0x02; h->ih_comp = 0x03; h->ih_fsoff = htonl(sizeof(*h) + ksize); strcpy((char *)h->ih_name, "Linux Kernel Image"); /* CRC over uImage payload (kernel and file system) */ crc = crc32(0L, Z_NULL, 0); crc = crc32(crc, payload, ntohl(h->ih_size)); h->ih_dcrc = htonl(crc); printf("CRC1: %08lx\n", crc); /* CRC over uImage header */ crc = crc32(0L, Z_NULL, 0); crc = crc32(crc, (void *)h, sizeof(*h)); h->ih_hcrc = htonl(crc); printf("CRC2: %08lx\n", crc); } /* * Calculate a "patch" value and write it into the last four bytes of * buf, so that the CRC32 checksum of the whole buffer is dcrc. * * Based on: SAR-PR-2006-05: Reversing CRC – Theory and Practice. * Martin Stigge, Henryk Plötz, Wolf Müller, Jens-Peter Redlich. * http://sar.informatik.hu-berlin.de/research/publications/#SAR-PR-2006-05 */ void craftcrc(uint32_t dcrc, uint8_t *buf, size_t len) { int i; uint32_t a; uint32_t patch = 0; uint32_t crc = crc32(0L, Z_NULL, 0); a = ~dcrc; for (i = 0; i < 32; i++) { if (patch & 1) patch = (patch >> 1) ^ 0xedb88320L; else patch >>= 1; if (a & 1) patch ^= 0x5b358fd3L; a >>= 1; } patch ^= ~crc32(crc, buf, len - 4); for (i = 0; i < 4; i++) { buf[len - 4 + i] = patch & 0xff; patch >>= 8; } /* Verify that we actually get the desired result */ crc = crc32(0L, Z_NULL, 0); crc = crc32(crc, buf, len); if (crc != dcrc) errx(1, "CRC patching is broken: wanted %08x, but got %08x.", dcrc, crc); } void usage() { fprintf(stderr, "Usage:\n" "jcgimage -o outfile -u uImage [-m maxsize] [-v version]\n" "jcgimage -o outfile -k kernel -f rootfs [-m maxsize] [-v version]\n"); exit(1); } #define MODE_UNKNOWN 0 #define MODE_UIMAGE 1 #define MODE_KR 2 /* The output image must not be larger than 4MiB - 5*64kiB */ #define MAXSIZE (size_t)(4 * 1024 * 1024 - 5 * 64 * 1024) int main(int argc, char **argv) { struct jcg_header *jh; struct uimage_header *uh; int c; char *imagefile = NULL; char *file1 = NULL; char *file2 = NULL; char *version = NULL; size_t maxsize = MAXSIZE; char *endptr; int mode = MODE_UNKNOWN; int fdo, fd1, fd2; size_t size1, size2, sizeu, sizeo, off1, off2; void *map; /* Make sure the headers have the right size */ assert(sizeof(struct jcg_header) == 512); assert(sizeof(struct uimage_header) == 64); set_source_date_epoch(); while ((c = getopt(argc, argv, "o:k:f:u:v:m:h")) != -1) { switch (c) { case 'o': imagefile = optarg; break; case 'k': if (mode == MODE_UIMAGE) errx(1,"-k cannot be combined with -u"); mode = MODE_KR; file1 = optarg; break; case 'f': if (mode == MODE_UIMAGE) errx(1,"-f cannot be combined with -u"); mode = MODE_KR; file2 = optarg; break; case 'u': if (mode == MODE_KR) errx(1,"-u cannot be combined with -k and -r"); mode = MODE_UIMAGE; file1 = optarg; break; case 'm': if (optarg != NULL) maxsize = strtoimax(optarg, &endptr, 10); break; case 'v': version = optarg; break; case 'h': default: usage(); } } if (optind != argc) errx(1, "illegal arg \"%s\"", argv[optind]); if (imagefile == NULL) errx(1, "no output file specified"); if (mode == MODE_UNKNOWN) errx(1, "specify either -u or -k and -r"); if (mode == MODE_KR) { if (file1 == NULL || file2 == NULL) errx(1, "need -k and -r"); fd2 = opensize(file2, &size2); } fd1 = opensize(file1, &size1); if (mode == MODE_UIMAGE) { off1 = sizeof(*jh); sizeu = size1 + 4; sizeo = sizeof(*jh) + sizeu; } else { off1 = sizeof(*jh) + sizeof(*uh); off2 = sizeof(*jh) + sizeof(*uh) + size1; sizeu = sizeof(*uh) + size1 + size2; sizeo = sizeof(*jh) + sizeu; } if (sizeo > maxsize) errx(1, "payload too large: %zd > %zd\n", sizeo, maxsize); fdo = open(imagefile, O_RDWR | O_CREAT | O_TRUNC, 00644); if (fdo < 0) err(1, "cannot open \"%s\"", imagefile); if (ftruncate(fdo, sizeo) == -1) err(1, "cannot grow \"%s\" to %zd bytes", imagefile, sizeo); map = mmap(NULL, sizeo, PROT_READ|PROT_WRITE, MAP_SHARED, fdo, 0); uh = map + sizeof(*jh); if (map == MAP_FAILED) err(1, "cannot mmap \"%s\"", imagefile); if (read(fd1, map + off1, size1) != size1) err(1, "cannot copy %s", file1); if (mode == MODE_KR) { if (read(fd2, map+off2, size2) != size2) err(1, "cannot copy %s", file2); mkuheader(uh, size1, size2); } else if (mode == MODE_UIMAGE) craftcrc(ntohl(uh->ih_dcrc), (void*)uh + sizeof(*uh), sizeu - sizeof(*uh)); mkjcgheader(map, sizeu, version); munmap(map, sizeo); close(fdo); return 0; }