1 // SPDX-License-Identifier: GPL-3.0-only 2 /* 3 * NPK Kernel Packer - C implementation 4 * 5 * This tool creates MikroTik NPK packages containing kernel images. 6 * It's a C reimplementation of the Python poc_pack_kernel.py tool 7 * written by John Thomson <git@johnthomson.fastmail.com.au> 8 * which is based on npkpy https://github.com/botlabsDev/npkpy 9 * provided by @botlabsDev under GPL-3.0. 10 * 11 * created within minutes using Claude Sonnet 4 instructed by 12 * Daniel Golle <daniel@makrotopia.org> 13 */ 14 15 #include <stdio.h> 16 #include <stdlib.h> 17 #include <string.h> 18 #include <stdint.h> 19 #include <sys/stat.h> 20 #include <unistd.h> 21 #include <zlib.h> 22 #include <arpa/inet.h> 23 #include <err.h> 24 #include <fcntl.h> 25 26 /* Ensure we have the file type constants */ 27 #ifndef S_IFDIR 28 #define S_IFDIR 0040000 /* Directory */ 29 #endif 30 #ifndef S_IFREG 31 #define S_IFREG 0100000 /* Regular file */ 32 #endif 33 34 /* NPK format constants */ 35 #define NPK_MAGIC_BYTES 0xBAD0F11E 36 #define NPK_NULL_BLOCK 22 37 #define NPK_SQUASH_FS_IMAGE 21 38 #define NPK_ZLIB_COMPRESSED_DATA 4 39 40 /* Container alignment */ 41 #define SQUASHFS_ALIGNMENT 0x1000 42 43 /* File mode constants (from stat.h) */ 44 #define FILE_MODE_EXEC (S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH) 45 #define FILE_MODE_REG (FILE_MODE_EXEC & ~(S_IXUSR | S_IXGRP | S_IXOTH)) 46 47 static char *progname; 48 49 #pragma pack(push, 1) 50 51 /* NPK file header */ 52 typedef struct { 53 uint32_t magic; /* Magic bytes: 0x1EF1D0BA */ 54 uint32_t payload_len; /* Length of all containers */ 55 } npk_header_t; 56 57 /* Container header */ 58 typedef struct { 59 uint16_t cnt_id; /* Container type ID */ 60 uint32_t payload_len; /* Container payload length */ 61 } container_header_t; 62 63 /* Zlib compressed object header */ 64 typedef struct { 65 uint16_t obj_mode; /* File mode (stat.h format) */ 66 uint16_t zeroes1[3]; /* Padding */ 67 uint32_t timestamps[3]; /* create, access, modify timestamps */ 68 uint32_t zeroes2; /* More padding */ 69 uint32_t payload_len; /* Payload length */ 70 uint16_t name_len; /* Name length */ 71 /* Followed by name and payload */ 72 } zlib_obj_header_t; 73 74 #pragma pack(pop) 75 76 /* Structure to hold container data */ 77 typedef struct { 78 uint16_t cnt_id; 79 uint32_t payload_len; 80 uint8_t *payload; 81 } container_t; 82 83 /* Structure to hold zlib object data */ 84 typedef struct { 85 uint16_t obj_mode; 86 uint32_t timestamps[3]; 87 char *name; 88 uint8_t *payload; 89 uint32_t payload_len; 90 } zlib_object_t; 91 92 /* 93 * Calculate the size needed for a container including header 94 */ 95 static size_t container_full_size(const container_t *cnt) 96 { 97 return sizeof(container_header_t) + cnt->payload_len; 98 } 99 100 /* 101 * Write a container to a buffer 102 */ 103 static size_t write_container(uint8_t *buffer, const container_t *cnt) 104 { 105 container_header_t *header = (container_header_t *)buffer; 106 header->cnt_id = cnt->cnt_id; 107 header->payload_len = cnt->payload_len; 108 109 if (cnt->payload && cnt->payload_len > 0) { 110 memcpy(buffer + sizeof(container_header_t), cnt->payload, cnt->payload_len); 111 } 112 113 return container_full_size(cnt); 114 } 115 116 /* 117 * Create a null block container for alignment 118 */ 119 static container_t create_null_block(size_t alignment_size) 120 { 121 container_t cnt = { 0 }; 122 size_t header_size, padding; 123 124 cnt.cnt_id = NPK_NULL_BLOCK; 125 126 /* Calculate padding needed to align next container to boundary */ 127 header_size = sizeof(npk_header_t) + sizeof(container_header_t); 128 padding = alignment_size - (header_size + sizeof(container_header_t)) % alignment_size; 129 if (padding == alignment_size) 130 padding = 0; 131 132 cnt.payload_len = padding; 133 if (padding > 0) { 134 if ((cnt.payload = calloc(1, padding)) == NULL) 135 err(EXIT_FAILURE, "calloc"); 136 } 137 138 return cnt; 139 } 140 141 /* 142 * Create a SquashFS container with dummy payload 143 */ 144 static container_t create_squashfs_container(void) 145 { 146 container_t cnt = { 0 }; 147 cnt.cnt_id = NPK_SQUASH_FS_IMAGE; 148 cnt.payload_len = SQUASHFS_ALIGNMENT; 149 150 if ((cnt.payload = calloc(1, cnt.payload_len)) == NULL) 151 err(EXIT_FAILURE, "calloc"); 152 153 return cnt; 154 } 155 156 /* 157 * Serialize a zlib object to binary format 158 */ 159 static uint8_t *serialize_zlib_object(const zlib_object_t *obj, size_t *out_size) 160 { 161 size_t name_len = strlen(obj->name); 162 size_t total_size = sizeof(zlib_obj_header_t) + name_len + obj->payload_len; 163 uint8_t *buffer; 164 zlib_obj_header_t *header; 165 166 if ((buffer = malloc(total_size)) == NULL) 167 err(EXIT_FAILURE, "malloc"); 168 169 header = (zlib_obj_header_t *)buffer; 170 header->obj_mode = obj->obj_mode; 171 memset(header->zeroes1, 0, sizeof(header->zeroes1)); 172 memcpy(header->timestamps, obj->timestamps, sizeof(header->timestamps)); 173 header->zeroes2 = 0; 174 header->payload_len = obj->payload_len; 175 header->name_len = name_len; 176 177 /* Copy name */ 178 memcpy(buffer + sizeof(zlib_obj_header_t), obj->name, name_len); 179 180 /* Copy payload */ 181 if (obj->payload && obj->payload_len > 0) { 182 memcpy(buffer + sizeof(zlib_obj_header_t) + name_len, obj->payload, 183 obj->payload_len); 184 } 185 186 *out_size = total_size; 187 return buffer; 188 } 189 190 /* 191 * Compress data using the exact same method as Python implementation 192 * This matches the Python set_cnt_payload_decompressed function exactly 193 */ 194 static uint8_t *compress_zlib_data(const uint8_t *input, size_t input_len, size_t *out_len, 195 size_t block_size) 196 { 197 size_t max_output_size = input_len * 2 + 1024; /* Conservative estimate */ 198 uint8_t *buffer_out; 199 size_t output_offset = 0; 200 size_t offset = 0; 201 uint32_t adler32; 202 203 if ((buffer_out = malloc(max_output_size)) == NULL) 204 err(EXIT_FAILURE, "malloc"); 205 206 /* Compression method magic - matches Python b"\x78\x01" */ 207 buffer_out[output_offset++] = 0x78; 208 buffer_out[output_offset++] = 0x01; 209 210 /* Initialize adler32 - matches Python zlib.adler32(b"") */ 211 adler32 = adler32_z(1L, NULL, 0); 212 213 /* Process data in blocks - matches Python while loop */ 214 while (offset < input_len) { 215 size_t buffer_in_len = (offset + block_size <= input_len) ? block_size : 216 (input_len - offset); 217 const uint8_t *buffer_in = input + offset; 218 uLong max_block_compressed = compressBound(buffer_in_len); 219 uint8_t *compressed; 220 uLong compressed_len; 221 int result; 222 223 if ((compressed = malloc(max_block_compressed)) == NULL) 224 err(EXIT_FAILURE, "malloc"); 225 226 compressed_len = max_block_compressed; 227 result = compress2(compressed, &compressed_len, buffer_in, buffer_in_len, 228 0); /* level=0 */ 229 230 if (result != Z_OK) 231 err(EXIT_FAILURE, "compress2 failed: %d", result); 232 233 /* Extract the right portion based on block type */ 234 if (buffer_in_len == block_size) { 235 /* Not-last-block: block = b"\x00" + compressed[3:-4] */ 236 /* Skip first 3 bytes and last 4 bytes */ 237 size_t copy_len = compressed_len - 7; 238 239 buffer_out[output_offset++] = 0x00; 240 241 if (output_offset + copy_len >= max_output_size) { 242 max_output_size *= 2; 243 if ((buffer_out = realloc(buffer_out, max_output_size)) == NULL) 244 err(EXIT_FAILURE, "realloc"); 245 } 246 memcpy(buffer_out + output_offset, compressed + 3, copy_len); 247 output_offset += copy_len; 248 } else { 249 /* Last block: block = compressed[2:-4] */ 250 size_t copy_len = 251 compressed_len - 6; /* Skip first 2 bytes and last 4 bytes */ 252 253 if (output_offset + copy_len >= max_output_size) { 254 max_output_size *= 2; 255 if ((buffer_out = realloc(buffer_out, max_output_size)) == NULL) 256 err(EXIT_FAILURE, "realloc"); 257 } 258 memcpy(buffer_out + output_offset, compressed + 2, copy_len); 259 output_offset += copy_len; 260 } 261 262 /* Update adler32 - matches Python zlib.adler32(compressed[7:-4], adler32) */ 263 if (compressed_len > 11) { /* Ensure we have enough bytes */ 264 adler32 = adler32_z(adler32, compressed + 7, compressed_len - 11); 265 } 266 267 free(compressed); 268 offset += block_size; 269 } 270 271 /* Add final adler32 checksum - matches Python struct.pack(">L", adler32) */ 272 if (output_offset + 4 >= max_output_size) { 273 max_output_size += 4; 274 if ((buffer_out = realloc(buffer_out, max_output_size)) == NULL) 275 err(EXIT_FAILURE, "realloc"); 276 } 277 278 buffer_out[output_offset++] = (adler32 >> 24) & 0xFF; 279 buffer_out[output_offset++] = (adler32 >> 16) & 0xFF; 280 buffer_out[output_offset++] = (adler32 >> 8) & 0xFF; 281 buffer_out[output_offset++] = adler32 & 0xFF; 282 283 /* Shrink to actual size */ 284 if ((buffer_out = realloc(buffer_out, output_offset)) == NULL) 285 err(EXIT_FAILURE, "realloc final"); 286 287 *out_len = output_offset; 288 return buffer_out; 289 } 290 291 /* 292 * Create a zlib compressed container with kernel objects 293 */ 294 static container_t create_zlib_container(const uint8_t *kernel_data, size_t kernel_size) 295 { 296 container_t cnt = { 0 }; 297 zlib_object_t objects[3]; 298 uint8_t *obj_data[3]; 299 size_t obj_sizes[3]; 300 size_t total_uncompressed = 0; 301 uint8_t *uncompressed_data; 302 size_t offset = 0; 303 size_t compressed_len; 304 uint8_t *compressed_data; 305 int i; 306 307 cnt.cnt_id = NPK_ZLIB_COMPRESSED_DATA; 308 309 /* Create zlib objects */ 310 311 /* Boot directory object */ 312 objects[0].obj_mode = S_IFDIR | FILE_MODE_EXEC; 313 objects[0].name = "boot"; 314 objects[0].payload = NULL; 315 objects[0].payload_len = 0; 316 memset(objects[0].timestamps, 0, sizeof(objects[0].timestamps)); 317 318 /* Kernel file object */ 319 objects[1].obj_mode = S_IFREG | FILE_MODE_EXEC; 320 objects[1].name = "boot/kernel"; 321 objects[1].payload = (uint8_t *)kernel_data; 322 objects[1].payload_len = kernel_size; 323 memset(objects[1].timestamps, 0, sizeof(objects[1].timestamps)); 324 325 /* UPGRADED file object */ 326 objects[2].obj_mode = S_IFREG | FILE_MODE_REG; 327 objects[2].name = "UPGRADED"; 328 if ((objects[2].payload = calloc(1, 0x20)) == NULL) 329 err(EXIT_FAILURE, "calloc"); 330 objects[2].payload_len = 0x20; 331 memset(objects[2].timestamps, 0, sizeof(objects[2].timestamps)); 332 333 /* Serialize objects */ 334 for (i = 0; i < 3; i++) { 335 obj_data[i] = serialize_zlib_object(&objects[i], &obj_sizes[i]); 336 total_uncompressed += obj_sizes[i]; 337 } 338 339 /* Concatenate all objects */ 340 if ((uncompressed_data = malloc(total_uncompressed)) == NULL) 341 err(EXIT_FAILURE, "malloc"); 342 343 for (i = 0; i < 3; i++) { 344 memcpy(uncompressed_data + offset, obj_data[i], obj_sizes[i]); 345 offset += obj_sizes[i]; 346 free(obj_data[i]); 347 } 348 349 /* Compress the data */ 350 compressed_data = 351 compress_zlib_data(uncompressed_data, total_uncompressed, &compressed_len, 0x8000); 352 353 free(uncompressed_data); 354 free(objects[2].payload); /* Free the UPGRADED payload we allocated */ 355 356 cnt.payload = compressed_data; 357 cnt.payload_len = compressed_len; 358 359 return cnt; 360 } 361 362 /* 363 * Read file contents into memory 364 */ 365 static uint8_t *read_file(const char *filename, size_t *file_size) 366 { 367 int fd; 368 struct stat st; 369 uint8_t *buffer; 370 ssize_t read_bytes; 371 372 if ((fd = open(filename, O_RDONLY)) == -1) 373 err(EXIT_FAILURE, "%s", filename); 374 375 if (fstat(fd, &st) == -1) 376 err(EXIT_FAILURE, "%s", filename); 377 378 *file_size = st.st_size; 379 380 if ((buffer = malloc(*file_size)) == NULL) 381 err(EXIT_FAILURE, "malloc"); 382 383 if ((read_bytes = read(fd, buffer, *file_size)) != *file_size) 384 err(EXIT_FAILURE, "read %s", filename); 385 386 close(fd); 387 return buffer; 388 } 389 390 /* 391 * Write NPK file 392 */ 393 static int write_npk_file(const char *filename, container_t *containers, int num_containers) 394 { 395 int fd; 396 uint32_t total_payload = 0; 397 npk_header_t header; 398 int i; 399 400 /* Calculate total payload size */ 401 for (i = 0; i < num_containers; i++) { 402 total_payload += container_full_size(&containers[i]); 403 } 404 405 if ((fd = open(filename, O_WRONLY | O_CREAT | O_TRUNC, 0644)) == -1) 406 err(EXIT_FAILURE, "%s", filename); 407 408 /* Write NPK header */ 409 header.magic = NPK_MAGIC_BYTES; 410 header.payload_len = total_payload; 411 412 if (write(fd, &header, sizeof(header)) != sizeof(header)) 413 err(EXIT_FAILURE, "write header"); 414 415 /* Write containers */ 416 for (i = 0; i < num_containers; i++) { 417 size_t container_size = container_full_size(&containers[i]); 418 uint8_t *buffer; 419 420 if ((buffer = malloc(container_size)) == NULL) 421 err(EXIT_FAILURE, "malloc"); 422 423 write_container(buffer, &containers[i]); 424 425 if (write(fd, buffer, container_size) != container_size) 426 err(EXIT_FAILURE, "write container"); 427 428 free(buffer); 429 } 430 431 close(fd); 432 return 0; 433 } 434 435 /* 436 * Print usage information 437 */ 438 static void usage(void) 439 { 440 fprintf(stderr, "Usage: %s <kernel> <output>\n", progname); 441 exit(EXIT_FAILURE); 442 } 443 444 int main(int argc, char *argv[]) 445 { 446 const char *kernel_file; 447 const char *output_file; 448 size_t kernel_size; 449 uint8_t *kernel_data; 450 container_t containers[3]; 451 int i; 452 453 progname = argv[0]; 454 455 if (argc != 3) 456 usage(); 457 458 kernel_file = argv[1]; 459 output_file = argv[2]; 460 461 /* Read kernel file */ 462 kernel_data = read_file(kernel_file, &kernel_size); 463 464 /* Create containers */ 465 /* Null block for alignment */ 466 containers[0] = create_null_block(SQUASHFS_ALIGNMENT); 467 468 /* SquashFS container */ 469 containers[1] = create_squashfs_container(); 470 471 /* Zlib container with kernel */ 472 containers[2] = create_zlib_container(kernel_data, kernel_size); 473 474 /* Write NPK file */ 475 write_npk_file(output_file, containers, 3); 476 477 /* Cleanup */ 478 free(kernel_data); 479 for (i = 0; i < 3; i++) 480 if (containers[i].payload) 481 free(containers[i].payload); 482 483 return EXIT_SUCCESS; 484 } 485
This page was automatically generated by LXR 0.3.1. • OpenWrt