1 // SPDX-License-Identifier: GPL-2.0-only 2 /* 3 * Copyright (C) 2025-2026 Andreas Boehler <dev@aboehler.at> 4 * Based on reverse-engineering by Jan Hoffmann (obfuscation) 5 * 6 * Create images for the XikeStor SKS7300 series 7 */ 8 9 10 #include <stdio.h> 11 #include <stdlib.h> 12 #include <stdint.h> 13 #include <string.h> 14 #include <byteswap.h> 15 #include <unistd.h> 16 #include <libgen.h> 17 #include <getopt.h> 18 #include <sys/stat.h> 19 #include <fcntl.h> 20 #include <sys/mman.h> 21 #include <zlib.h> 22 23 #if (__BYTE_ORDER == __LITTLE_ENDIAN) 24 # define HOST_TO_LE16(x) (x) 25 # define HOST_TO_LE32(x) (x) 26 # define LE16_TO_HOST(x) (x) 27 # define LE32_TO_HOST(x) (x) 28 # define HOST_TO_BE16(x) bswap_16(x) 29 # define HOST_TO_BE32(x) bswap_32(x) 30 # define BE16_TO_HOST(x) bswap_16(x) 31 # define BE32_TO_HOST(x) bswap_32(x) 32 #else 33 # define HOST_TO_BE16(x) (x) 34 # define HOST_TO_BE32(x) (x) 35 # define BE16_TO_HOST(x) (x) 36 # define BE32_TO_HOST(x) (x) 37 # define HOST_TO_LE16(x) bswap_16(x) 38 # define HOST_TO_LE32(x) bswap_32(x) 39 # define LE16_TO_HOST(x) bswap_16(x) 40 # define LE32_TO_HOST(x) bswap_32(x) 41 #endif 42 43 #define ALIGN(x,y) (((x)+((y)-1)) & ~((y)-1)) 44 45 /* 46 * Message macros 47 */ 48 #define ERR(fmt, ...) do { \ 49 fflush(0); \ 50 fprintf(stderr, "[%s] *** error: " fmt "\n", \ 51 progname, ## __VA_ARGS__ ); \ 52 } while (0) 53 54 55 #define MAX_ARG_COUNT 32 56 #define MAX_ARG_LEN 1024 57 58 struct sks7300_hdr { 59 uint32_t image_magic; // Image Magic 0xfe071301 60 uint32_t hdr_crc; // Header CRC 61 uint32_t image_size; // Image Size (Bytes) 62 uint32_t timestamp; // Image Timestamp (Unix Timestamp) 63 uint32_t image00_offset; // Image 00 Offset 64 uint32_t image00_size; // Image 00 size 65 uint8_t image00_type; // Image 00 Type; 0x52 = Kernel Image, 0x5B = Unknown Image, 0x53 = RAMDisk 66 uint8_t image00_comp; // Image 00 Compression; 0x67 = LZMA, 0x00 = uncompressed 67 uint16_t unknown1; // Unknown1 68 uint32_t image01_offset; // Image 01 Offset 69 uint32_t image01_size; // Image 01 size 70 uint32_t unknown2; // Unknown2 71 uint32_t image02_offset; // Image 02 Offset 72 uint32_t image02_size; // Image 02 size 73 uint32_t unknown3; // Unknown3 74 uint8_t padding[60]; // unknown padding, probably for another 4 images 75 char image_name[64]; // Image Name 76 uint32_t image_id; // Image ID 0xfe009300 77 uint32_t unknown4; // Unknown4 78 uint32_t load_addr; // Kernel Load Address 79 uint32_t entry_point; // Kernel Entry Point 80 uint32_t payload_crc; // CRC32 of entire payload 81 uint8_t image_os; // Image OS Type; 0x0f = Linux 82 uint8_t image_arch; // Image Arch; 0x37 = MIPS 83 uint8_t unknown5[4]; // Unknown5 84 uint8_t stk_header[6]; // STK Header 0xaa552288bb66 85 uint8_t unknown6[84]; // Unknown6 86 uint32_t image_id2; // Image ID 0xfe009300 87 uint8_t unknown7[20]; // Unknown7 88 char version_string[396]; // Version string; probably shorter, the rest is padding 89 } __attribute__((packed)); 90 91 char *ofname = NULL; 92 char *ifname = NULL; 93 char *image_name = NULL; 94 char *image_version = NULL; 95 96 void *input_file = NULL; 97 char *progname; 98 uint32_t load_addr = 0x80100000; 99 uint32_t entry_point = 0x80100000; 100 101 /* 102 * Helper routines 103 */ 104 void 105 usage(int status) 106 { 107 FILE *stream = (status != EXIT_SUCCESS) ? stderr : stdout; 108 109 fprintf(stream, "Usage: %s [OPTIONS...]\n", progname); 110 fprintf(stream, "\nOptions:\n"); 111 fprintf(stream, 112 " -i <file>\n" 113 " input file, e.g. OpenWrt firmware image\n" 114 " -o <file>\n" 115 " write output to the file <file>\n" 116 " -n <name>\n" 117 " image name (e.g. OpenWrt)\n" 118 " -v <version>\n" 119 " version (e.g. 25.12.0)\n" 120 " -h show this screen\n" 121 ); 122 123 exit(status); 124 } 125 126 static void 127 *map_input(const char *name, size_t *len) 128 { 129 struct stat stat; 130 void *mapped; 131 int fd; 132 133 fd = open(name, O_RDONLY); 134 if (fd < 0) 135 return NULL; 136 if (fstat(fd, &stat) < 0) { 137 close(fd); 138 return NULL; 139 } 140 *len = stat.st_size; 141 mapped = mmap(NULL, stat.st_size, PROT_READ, MAP_SHARED, fd, 0); 142 if (close(fd) < 0) { 143 (void) munmap(mapped, stat.st_size); 144 return NULL; 145 } 146 return mapped; 147 } 148 149 int 150 parse_arg(char *arg, char *buf, char *argv[]) 151 { 152 int res = 0; 153 size_t argl; 154 char *tok; 155 char **ap = &buf; 156 int i; 157 158 memset(argv, 0, MAX_ARG_COUNT * sizeof(void *)); 159 160 if ((arg == NULL)) { 161 /* no arguments */ 162 return 0; 163 } 164 165 argl = strlen(arg); 166 if (argl == 0) { 167 /* no arguments */ 168 return 0; 169 } 170 171 if (argl >= MAX_ARG_LEN) { 172 /* argument is too long */ 173 argl = MAX_ARG_LEN-1; 174 } 175 176 memcpy(buf, arg, argl); 177 buf[argl] = '\0'; 178 179 for (i = 0; i < MAX_ARG_COUNT; i++) { 180 tok = strsep(ap, ":"); 181 if (tok == NULL) { 182 break; 183 } 184 argv[i] = tok; 185 res++; 186 } 187 188 return res; 189 } 190 191 int 192 required_arg(char c, char *arg) 193 { 194 if (arg == NULL || *arg != '-') 195 return 0; 196 197 ERR("option -%c requires an argument\n", c); 198 return -1; 199 } 200 201 int 202 parse_opt_name(char ch, char *arg, char **dest) 203 { 204 205 if (*dest != NULL) { 206 ERR("only one input/output file allowed"); 207 return -1; 208 } 209 210 if (required_arg(ch, arg)) 211 return -1; 212 213 *dest = arg; 214 215 return 0; 216 } 217 218 uint8_t* 219 obfuscate_file(uint8_t *input_file, size_t len) { 220 uint8_t *out_file = malloc(len+3); // The new header adds 3 bytes to the total size 221 uint8_t lc; 222 uint8_t lp; 223 uint8_t pb; 224 uint8_t ds1; 225 uint8_t ds2; 226 uint8_t ds3; 227 uint8_t ds4; 228 int pos; 229 int index; 230 231 struct hdr { 232 uint8_t props; 233 uint32_t dicsize; 234 uint64_t size; 235 } __attribute__((packed)); 236 237 struct newhdr { 238 uint16_t magic; // Magic Number 239 uint16_t special; // Special Numbers for obfuscation 240 uint8_t pb; // LZMA pb 241 uint8_t lp; // LZMA lp 242 uint8_t lc; // LZMA lc 243 uint8_t pad; // Padding 244 uint8_t ds2; // Data size byte 2 245 uint8_t ds3; // Data size byte 3 246 uint8_t ds4; // Data size byte 4 247 uint8_t ds1; // Data size byte 1 248 uint32_t size; // Size 249 } __attribute__((packed)); 250 251 struct hdr old_hdr; 252 struct newhdr new_hdr; 253 254 if(out_file == NULL) 255 return out_file; 256 257 memcpy(&old_hdr, input_file, sizeof(old_hdr)); 258 lc = old_hdr.props % 9; 259 lp = ((old_hdr.props - lc) / 9) % 5; 260 pb = (((old_hdr.props - lc) / 9) - lp) / 5; 261 lc ^= 0xb9; 262 lp ^= 0x5e; 263 pb ^= 0x37; 264 265 ds1 = old_hdr.dicsize >> 24; 266 ds2 = old_hdr.dicsize >> 16; 267 ds3 = old_hdr.dicsize >> 8; 268 ds4 = old_hdr.dicsize; 269 270 memset(&new_hdr, 0, sizeof(new_hdr)); 271 new_hdr.magic = HOST_TO_BE16(0x5e71); 272 /* We set special 1 and special 2 to 1 so that the algorithm does not require any magic values */ 273 new_hdr.special = 0x0101; 274 new_hdr.pb = pb; 275 new_hdr.lp = lp; 276 new_hdr.lc = lc; 277 new_hdr.ds1 = ds1; 278 new_hdr.ds2 = ds2; 279 new_hdr.ds3 = ds3; 280 new_hdr.ds4 = ds4; 281 /* This narrows the uint64_t to uint32_t; a file bigger than the 32 bits limit 282 wouldn't be accepted anyway */ 283 new_hdr.size = HOST_TO_BE32(old_hdr.size); 284 285 memcpy(out_file, &new_hdr, sizeof(new_hdr)); 286 memcpy(&out_file[sizeof(new_hdr)], &input_file[sizeof(old_hdr)], len - sizeof(old_hdr)); 287 288 pos = sizeof(new_hdr); 289 while(pos < (len - sizeof(old_hdr))) { 290 for(int i=0; i<8; i++) { 291 index = pos + i; 292 if(index >= (len + sizeof(old_hdr))) 293 break; 294 295 out_file[index] = i ^ out_file[index]; 296 } 297 pos += 0x4000; 298 } 299 300 return out_file; 301 } 302 303 int 304 is_empty_arg(char *arg) 305 { 306 int ret = 1; 307 if (arg != NULL) { 308 if (*arg) ret = 0; 309 }; 310 return ret; 311 } 312 313 int main(int argc, char *argv[]) { 314 size_t file_len = 0; 315 size_t out_len = 0; 316 int optinvalid = 0; /* flag for invalid option */ 317 int res = EXIT_FAILURE; 318 int c; 319 uint8_t *obfuscated_file = NULL; 320 321 struct sks7300_hdr sks_hdr; 322 323 FILE *outfile; 324 325 progname=basename(argv[0]); 326 327 opterr = 0; /* could not print standard getopt error messages */ 328 while ( 1 ) { 329 optinvalid = 0; 330 331 c = getopt(argc, argv, "i:o:n:v:h"); 332 if (c == -1) 333 break; 334 335 switch (c) { 336 case 'i': 337 optinvalid = parse_opt_name(c,optarg,&ifname); 338 break; 339 case 'o': 340 optinvalid = parse_opt_name(c,optarg,&ofname); 341 break; 342 case 'n': 343 optinvalid = parse_opt_name(c,optarg,&image_name); 344 break; 345 case 'v': 346 optinvalid = parse_opt_name(c,optarg,&image_version); 347 break; 348 case 'h': 349 usage(EXIT_SUCCESS); 350 break; 351 default: 352 optinvalid = 1; 353 break; 354 } 355 if (optinvalid != 0 ) { 356 ERR("invalid option: -%c", optopt); 357 goto out; 358 } 359 } 360 361 if(!ifname) { 362 ERR("input file is mandatory"); 363 goto out; 364 } 365 366 if(!ofname) { 367 ERR("output file is mandatory"); 368 goto out; 369 } 370 371 if(!image_name) { 372 ERR("image name is mandatory"); 373 goto out; 374 } 375 376 if(!image_version) { 377 ERR("image version is mandatory"); 378 goto out; 379 } 380 381 input_file = map_input(ifname, &file_len); 382 if(!input_file) { 383 ERR("input file not found."); 384 goto out; 385 } 386 387 obfuscated_file = obfuscate_file(input_file, file_len); 388 out_len = file_len + 3; // The obfuscation adds 3 bytes to the total length 389 390 memset(&sks_hdr, 0, sizeof(sks_hdr)); 391 sks_hdr.image_magic = HOST_TO_BE32(0xfe071301); 392 sks_hdr.image_id = HOST_TO_BE32(0xfe009300); 393 sks_hdr.load_addr = HOST_TO_BE32(load_addr); 394 sks_hdr.entry_point = HOST_TO_BE32(entry_point); 395 sks_hdr.stk_header[0] = 0xaa; 396 sks_hdr.stk_header[1] = 0x55; 397 sks_hdr.stk_header[2] = 0x22; 398 sks_hdr.stk_header[3] = 0x88; 399 sks_hdr.stk_header[4] = 0xbb; 400 sks_hdr.stk_header[5] = 0x66; 401 sks_hdr.image_id2 = HOST_TO_BE32(0xfe009300); 402 strcpy(sks_hdr.image_name, image_name); 403 strcpy(sks_hdr.version_string, image_version); 404 sks_hdr.image00_type = 0x52; // Linux Kernel 405 sks_hdr.image00_comp = 0x67; // LZMA compressed 406 sks_hdr.image_os = 0x0f; // Linux 407 sks_hdr.image_arch = 0x37; // MIPS 408 409 /* 410 // The below values are set in the OEM upgrade file, but their meaning 411 // is unknown 412 sks_hdr.unknown4 = HOST_TO_BE32(0x01); 413 sks_hdr.unknown5[2] = 0x8c; 414 sks_hdr.unknown5[3] = 0x08; 415 416 sks_hdr.unknown6[3] = 0x01; 417 sks_hdr.unknown6[7] = 0x84; 418 sks_hdr.unknown6[8] = 0x01; 419 sks_hdr.unknown6[10] = 0x01; 420 sks_hdr.unknown6[11] = 0x07; 421 sks_hdr.unknown6[15] = 0x74; 422 sks_hdr.unknown6[83] = 0x74; 423 424 sks_hdr.unknown7[3] = 0x02; 425 sks_hdr.unknown7[7] = 0x16; 426 */ 427 uint32_t crc = crc32(0, obfuscated_file, out_len); 428 sks_hdr.payload_crc = HOST_TO_BE32(crc); 429 sks_hdr.image00_size = HOST_TO_BE32(out_len); 430 sks_hdr.image_size = HOST_TO_BE32(out_len); 431 crc = crc32(0, (unsigned char *)&sks_hdr, sizeof(sks_hdr)); 432 sks_hdr.hdr_crc = HOST_TO_BE32(crc); 433 434 outfile = fopen(ofname, "w"); 435 fwrite(&sks_hdr, sizeof(sks_hdr), 1, outfile); 436 fwrite(obfuscated_file, out_len, 1, outfile); 437 fflush(outfile); 438 fclose(outfile); 439 440 res = EXIT_SUCCESS; 441 442 out: 443 if (res != EXIT_SUCCESS) { 444 unlink(ofname); 445 } 446 if(input_file) 447 munmap(input_file, file_len); 448 return res; 449 } 450
This page was automatically generated by LXR 0.3.1. • OpenWrt