1 // SPDX-License-Identifier: GPL-2.0-only 2 /* 3 * zycast - push images via multicast to a ZyXEL bootloader 4 * 5 * Many ZyXEL devices supports image manipulation using a multicast 6 * based protocol. The protocol is not documented publicly, and 7 * both the bootloader embedded part and the official clients are 8 * closed source. 9 * 10 * This client is based on the following description of the protocol. 11 * which is reverse engineered from bootloader binaries. It is likely 12 * to be both incomplete and inaccurate, as it only covers the 13 * observed implementation on a limited set of devices. No client 14 * implementation or network packets were available for the protocol 15 * reverse engineering. 16 * 17 * Protocol description: 18 * 19 * UDP to multicast destination address 225.0.0.0 port 5631. Source 20 * address and port is arbitrary. 21 * 22 * Payload is split in packets prepended with a 30 byte header: 23 * 24 * 4 byte signature: 'z', 'y', 'x', 0x0 [1] 25 * 16 bit checksum [2][3] 26 * 32 bit packet id [2][4] 27 * 32 bit packet length [2][5] 28 * 32 bit file length [2][6] 29 * 32 bit image bitmap [2][7] 30 * 2 byte ascii country code [8] 31 * 8 bit flags [9] 32 * 5 byte reserved [10] 33 * 34 * [1] the terminating null is not actually checked by the observed 35 * implementations, but is assumed to be safest in case the 36 * signature is treated as a string 37 * 38 * [2] all integers are in network byte order, i.e. big endian 39 * 40 * [3] checksum = sum >> 16 + sum, where sum is the sum of all 41 * payload bytes 42 * 43 * [4] starts at 0 and is incremented by 1 for each packet. Used both 44 * to ensure sequential, loss free, unidirectional transport, and to 45 * allow the transfer to start at any point. The sequence must be 46 * repeated until the transfer is complete 47 * 48 * [5] Testing indicates that some implementations expect 1024 byte 49 * packets. Smaller size results in a corrupt download, and larger 50 * size causes the download to hang - waiting for packet ids which 51 * does not exist. 52 * 53 * [6] the length of each file in case of a multi file transfer. 54 * 55 * [7] the lower 8 bits is a bitmap of all image types included in the 56 * transfer. Bits 8 - 16 contains the image type for this packet. 57 * The purpose of the upper 16 bits is unknown. 58 * 59 * The known image types are 60 * 61 * 0x01 - "bootbase" (often "Bootloader" partition) 62 * 0x02 - "rom" (often "data" partition) 63 * 0x04 - "ras" (often "Kernel" partition) 64 * 0x08 - "romd" (often "rom-d" partition) 65 * 0x10 - "backup" (often "Kernel2" partition) 66 * 67 * The supported set of images vary among implementations. 68 * The protocol may support other image types. 69 * 70 * WARNING: The flash offset of each supported image type is hard 71 * coded in the bootloader server implementation. There is no 72 * relation to the bootloader configuration, and no way to verify 73 * that those values are correct without decompiling that 74 * implementations. Device specific bugs are likely, and may 75 * result in a brick. 76 * 77 * [8] two upper case ascii characters, like 'D','E'. The purpose 78 * is unknown, but ZyXEL devices are often configured with this 79 * as one of their device specific variables 80 81 * [9] bitmap controlling actions taken after a complete transfer: 82 * 83 * 0x01 - set DebugFlag 84 * 0x02 - erase "rom" 85 * 0x04 - erase "rom-d" 86 * 87 * Other, unknown, values may exist in the protocol. Device 88 * support may vary. 89 * 90 * [10] these bytes are not used by the observed implementations. 91 * The purpose is therefore unknown. There is a risk 92 * they are interpreted by other devices, resulting in 93 * unexpected and potentially harmful behaviour. 94 * 95 * Copyright (C) 2024 Bjørn Mork <bjorn@mork.no> 96 */ 97 98 #include <arpa/inet.h> 99 #include <errno.h> 100 #include <fcntl.h> 101 #include <inttypes.h> 102 #include <netinet/in.h> 103 #include <signal.h> 104 #include <stdbool.h> 105 #include <stddef.h> 106 #include <stdio.h> 107 #include <stdlib.h> 108 #include <string.h> 109 #include <sys/mman.h> 110 #include <sys/socket.h> 111 #include <sys/stat.h> 112 #include <unistd.h> 113 114 /* defaulting to 10 ms interpacket delay */ 115 static int pktdelay = 10000; 116 static int sockfd = -1; 117 static bool exiting; 118 119 /* All integers are stored in network order (big endian) */ 120 struct zycast_t { 121 uint32_t magic; 122 uint16_t chksum; 123 uint32_t pid; 124 uint32_t plen; 125 uint32_t flen; 126 uint16_t unusedbits; 127 unsigned char type; 128 unsigned char images; 129 char cc[2]; 130 unsigned char flags; 131 char reserved[5]; 132 } __attribute__ ((packed)); 133 134 #define HDRSIZE (sizeof(struct zycast_t)) 135 #define DEST_ADDR "225.0.0.0" 136 #define DEST_PORT 5631 137 #define CHUNK 1024 138 #define MAGIC 0x7a797800 /* "zyx" */ 139 140 #define BIT(nr) (1 << (nr)) 141 142 enum imagetype { 143 BOOTBASE = 0, 144 ROM, 145 RAS, 146 ROMD, 147 BACKUP, 148 _MAX_IMAGETYPE 149 }; 150 151 #define FLAG_SET_DEBUG BIT(0) 152 #define FLAG_ERASE_ROM BIT(1) 153 #define FLAG_ERASE_ROMD BIT(2) 154 155 static void errexit(const char *msg) 156 { 157 fprintf(stderr, "ERR: %s: %s\n", msg, errno ? strerror(errno) : "unknown"); 158 exit(EXIT_FAILURE); 159 } 160 161 static void *map_input(const char *name, size_t *len) 162 { 163 struct stat stat; 164 void *mapped; 165 int fd; 166 167 fd = open(name, O_RDONLY); 168 if (fd < 0) 169 return NULL; 170 if (fstat(fd, &stat) < 0) { 171 close(fd); 172 return NULL; 173 } 174 *len = stat.st_size; 175 mapped = mmap(NULL, stat.st_size, PROT_READ, MAP_SHARED, fd, 0); 176 if (close(fd) < 0) { 177 (void) munmap(mapped, stat.st_size); 178 return NULL; 179 } 180 return mapped; 181 } 182 183 static uint16_t chksum(uint8_t *p, size_t len) 184 { 185 int i; 186 uint32_t sum = 0; 187 188 for (i = 0; i < len; i++) 189 sum += *p++; 190 return (uint16_t)((sum >> 16) + sum); 191 } 192 193 static int pushimage(void *file, struct zycast_t *phdr) 194 { 195 uint32_t count = 0; 196 uint32_t len = ntohl(phdr->flen); 197 uint32_t plen = CHUNK; 198 199 while (!exiting && len > 0) { 200 if (len < CHUNK) 201 plen = len; 202 phdr->plen = htonl(plen); 203 phdr->pid = htonl(count++); 204 phdr->chksum = htons(chksum(file, plen)); 205 if (send(sockfd, phdr, HDRSIZE, MSG_MORE | MSG_DONTROUTE) < 0) 206 errexit("send(phdr)"); 207 if (send(sockfd, file, plen, MSG_DONTROUTE) < 0) 208 errexit("send(payload)"); 209 file += plen; 210 len -= plen; 211 212 /* No need to kill the network. The target can't 213 * process packets as fast as we send them anyway. 214 */ 215 usleep(pktdelay); 216 } 217 return 0; 218 } 219 220 static void sig_handler(int signo) 221 { 222 if (signo == SIGINT) 223 exiting = true; 224 } 225 226 static void usage(const char *name) 227 { 228 fprintf(stderr, "Usage:\n"); 229 fprintf(stderr, " %s [options]\n", name); 230 fprintf(stderr, "Options:\n"); 231 fprintf(stderr, "\t-i interface outgoing interface for multicast packets\n"); 232 fprintf(stderr, "\t-t delay interpacket delay in milliseconds\n"); 233 fprintf(stderr, "\t-f rasimage primary firmware image\n"); 234 fprintf(stderr, "\t-b backupimage secondary firmware image (if supported)\n"); 235 fprintf(stderr, "\t-d rom data for the \"rom\" or \"data\" partition\n"); 236 fprintf(stderr, "\t-r romd data for the \"rom-d\" partition\n"); 237 #ifdef DO_BOOTBASE 238 fprintf(stderr, "\t-u bootloader flash new bootloader\n"); 239 fprintf(stderr, "\nWARNING: bootloader upgrades are dangerous. DON'T DO IT!\n"); 240 #endif 241 fprintf(stderr, "\nNOTE: some bootloaders will flash a rasimage to both primary and\n"); 242 fprintf(stderr, "secondary firmware partitions\n"); 243 fprintf(stderr, "\nExample:\n"); 244 fprintf(stderr, " %s -i eth1 -t 20 -f openwrt-initramfs.bin\n\n", name); 245 if (sockfd >= 0) 246 close(sockfd); 247 exit(EXIT_FAILURE); 248 } 249 250 #define ADD_IMAGE(nr) \ 251 do { \ 252 hdr.images |= BIT(nr); \ 253 file[nr] = map_input(optarg, &len[nr]); \ 254 if (!file[nr]) \ 255 errexit(optarg); \ 256 } while (0) 257 258 int main(int argc, char **argv) 259 { 260 void *file[_MAX_IMAGETYPE] = {}; 261 size_t len[_MAX_IMAGETYPE] = {}; 262 struct zycast_t hdr = { 263 .magic = htonl(MAGIC), 264 .cc = {'F', 'F' }, 265 .flags = FLAG_SET_DEBUG, 266 }; 267 const struct sockaddr_in dest = { 268 .sin_family = AF_INET, 269 .sin_addr.s_addr = inet_addr(DEST_ADDR), 270 .sin_port = htons(DEST_PORT), 271 }; 272 int i, c; 273 274 if (signal(SIGINT, sig_handler) == SIG_ERR) 275 errexit("signal()"); 276 sockfd = socket(AF_INET, SOCK_DGRAM, 0); 277 if (sockfd < 0) 278 errexit("socket()"); 279 if (connect(sockfd, (struct sockaddr *)&dest, sizeof(dest)) < 0) 280 errexit("connect()"); 281 282 while ((c = getopt(argc, argv, "i:t:f:b:d:r:u:")) != -1) { 283 switch (c) { 284 case 'i': 285 if (setsockopt(sockfd, SOL_SOCKET, SO_BINDTODEVICE, optarg, strlen(optarg)) < 0) 286 errexit(optarg); 287 break; 288 case 't': 289 i = strtoul(optarg, NULL, 0); 290 if (i < 1) 291 i = 1; 292 pktdelay = i * 1000; 293 break; 294 case 'f': 295 ADD_IMAGE(RAS); 296 break; 297 case 'b': 298 ADD_IMAGE(BACKUP); 299 break; 300 case 'd': 301 ADD_IMAGE(ROM); 302 break; 303 case 'r': 304 ADD_IMAGE(ROMD); 305 break; 306 case 'u': 307 #ifdef DO_BOOTBASE 308 ADD_IMAGE(BOOTBASE); 309 break; 310 #endif 311 default: 312 usage(argv[0]); 313 } 314 } 315 316 if (!hdr.images) 317 usage(argv[0]); 318 319 fprintf(stderr, "Press Ctrl+C to stop before rebooting target after upgrade\n"); 320 while (!exiting) { 321 for (i = 0; i < _MAX_IMAGETYPE; i++) { 322 if (hdr.images & BIT(i)) { 323 hdr.type = BIT(i); 324 hdr.flen = htonl(len[i]); 325 pushimage(file[i], &hdr); 326 } 327 } 328 }; 329 330 fprintf(stderr, "\nClosing all files\n"); 331 if (sockfd >= 0) 332 close(sockfd); 333 for (i = 0; i < _MAX_IMAGETYPE; i++) 334 if (hdr.images & BIT(i)) 335 munmap(file[i], len[i]); 336 337 return EXIT_SUCCESS; 338 } 339
This page was automatically generated by LXR 0.3.1. • OpenWrt