• source navigation  • diff markup  • identifier search  • freetext search  • 

Sources/firmware-utils/src/zycast.c

  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