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