b.liu | e958203 | 2025-04-17 19:18:16 +0800 | [diff] [blame] | 1 | #!/usr/bin/python3 |
| 2 | # SPDX-License-Identifier: GPL-2.0-or-later |
| 3 | # |
| 4 | # Copyright (C) 2022 OpenWrt.org |
| 5 | # |
| 6 | # ./cameo-tag.py <uImageFileName> <OffsetOfRootFS> |
| 7 | # |
| 8 | # CAMEO tag generator used for the D-Link DGS-1210 switches. Their U-Boot |
| 9 | # loader checks for the string CAMEOTAG and a checksum in the kernel and |
| 10 | # rootfs partitions. If not found it complains about the boot image. |
| 11 | # Nevertheless it will boot if the tags are available in the secondary |
| 12 | # boot partitions. If some day we want to overwrite the original vendor |
| 13 | # partition we must have the tags in place. To solve this we insert the |
| 14 | # tag two times into the kernel image. |
| 15 | # |
| 16 | # To understand what we do here it is helpful to explain how the original |
| 17 | # CAMEO tag generation/checking works. The firmware consists of two parts. |
| 18 | # A kernel uImage (<1.5MB) and a rootfs image (<12MB) that are written to |
| 19 | # their respective mtd partitions. The default generator simply checksums |
| 20 | # both parts and appends 16 bytes [<CAMEOTAG><0001><checksum>] to each part. |
| 21 | # The checksum is only an addition of all preceding bytes (b0+b1+b2+...). |
| 22 | # A tag does not interfere with any data in the images itself. During boot |
| 23 | # the loader will scan all primary/secondary partitions (2*kernel, 2*rootfs) |
| 24 | # until it finds the CAMEO tag. If checksums match everything is fine. |
| 25 | # If all 4 fail we are lost. Luckily the loader does not care about where |
| 26 | # the tags are located and ignores any data beyond a tag. |
| 27 | # |
| 28 | # The OpenWrt image consists of a kernel (>1.5MB) and a rootfs. There is |
| 29 | # no chance to add CAMEO tags at the default locations, since the kernel spans |
| 30 | # both the original kernel partition and the start of the rootfs partition. |
| 31 | # This would leave the kernel partition without a tag. So we must find suitable |
| 32 | # space. |
| 33 | # |
| 34 | # Location for original kernel partition is at the end of the uImage header. |
| 35 | # We will reuse the last bytes of the IH_NAME field. This is the tricky part |
| 36 | # because we have the header CRC and the CAMEO checksum that must match the |
| 37 | # whole header. uImage header CRC checksums all data except the CRC itself. The |
| 38 | # for CAMEO checksum in turn, checksums all preceding data except itself. |
| 39 | # Changing one of both results in a change of the other, but data trailing the |
| 40 | # CAMEO checksum only influences the CRC. |
| 41 | # |
| 42 | # Location for original rootfs partition is very simple. It is behind the |
| 43 | # OpenWrt compressed kernel image file that spans into the rootfs. So |
| 44 | # the tag will be written somewhere to the following rootfs partition and |
| 45 | # can be found by U-Boot. The CAMEO checksum calculation must start at the |
| 46 | # offset of the original rootfs partition and includes the "second" half of the |
| 47 | # "split" kernel uImage. |
| 48 | |
| 49 | import argparse |
| 50 | import os |
| 51 | import zlib |
| 52 | |
| 53 | READ_UNTIL_EOF = -1 |
| 54 | UIMAGE_HEADER_SIZE = 64 |
| 55 | UIMAGE_CRC_OFF = 4 |
| 56 | UIMAGE_CRC_END = 8 |
| 57 | UIMAGE_NAME_OFF = 32 |
| 58 | UIMAGE_NAME_END = 56 |
| 59 | UIMAGE_SUM_OFF = 56 |
| 60 | UIMAGE_SUM_END = 60 |
| 61 | UIMAGE_INV_OFF = 60 |
| 62 | UIMAGE_INV_END = 64 |
| 63 | CAMEO_TAG = bytes([0x43, 0x41, 0x4d, 0x45, 0x4f, 0x54, 0x41, 0x47, 0x00, 0x00, 0x00, 0x01]) |
| 64 | IMAGE_NAME = bytes([0x4f, 0x70, 0x65, 0x6e, 0x57, 0x72, 0x74, 0x00, 0x00, 0x00, 0x00, 0x00]) |
| 65 | CRC_00 = bytes([0x00] * 4) |
| 66 | CRC_FF = bytes([0xff] * 4) |
| 67 | |
| 68 | def read_buffer(offset, count): |
| 69 | args.uimage_file.seek(offset) |
| 70 | return bytearray(args.uimage_file.read(count)) |
| 71 | |
| 72 | def write_buffer(whence, buf): |
| 73 | args.uimage_file.seek(0, whence) |
| 74 | args.uimage_file.write(buf) |
| 75 | |
| 76 | def cameosum(buf): |
| 77 | return (sum(buf) & 0xffffffff).to_bytes(4, 'big') |
| 78 | |
| 79 | def invertcrc(buf): |
| 80 | return (zlib.crc32(buf) ^ 0xffffffff).to_bytes(4, 'little') |
| 81 | |
| 82 | def checksum_header(buf): |
| 83 | # To efficently get a combination, we will make use of the following fact: |
| 84 | # crc32(data + littleendian(crc32(data) ^ 0xffffffff)) = 0xffffffff |
| 85 | # |
| 86 | # After manipulation the uImage header looks like this: |
| 87 | # [...<ffffffff>...<OpenWrt><000000><CAMEOTAG><0001><checksum><InvCRC>] |
| 88 | buf[UIMAGE_NAME_OFF:UIMAGE_NAME_END] = IMAGE_NAME + CAMEO_TAG |
| 89 | buf[UIMAGE_CRC_OFF:UIMAGE_CRC_END] = CRC_FF |
| 90 | buf[UIMAGE_SUM_OFF:UIMAGE_SUM_END] = cameosum(buf[0:UIMAGE_NAME_END]) |
| 91 | buf[UIMAGE_CRC_OFF:UIMAGE_CRC_END] = CRC_00 |
| 92 | buf[UIMAGE_INV_OFF:UIMAGE_INV_END] = invertcrc(buf[0:UIMAGE_SUM_END]) |
| 93 | buf[UIMAGE_CRC_OFF:UIMAGE_CRC_END] = CRC_FF |
| 94 | return buf |
| 95 | |
| 96 | parser = argparse.ArgumentParser(description='Insert CAMEO firmware tags.') |
| 97 | parser.add_argument('uimage_file', type=argparse.FileType('r+b')) |
| 98 | parser.add_argument('rootfs_start', type=int) |
| 99 | args = parser.parse_args() |
| 100 | |
| 101 | args.uimage_file.seek(0, os.SEEK_END) |
| 102 | if args.uimage_file.tell() <= args.rootfs_start: |
| 103 | raise ValueError(f"uImage must be larger than {args.rootfs_start} bytes") |
| 104 | |
| 105 | # tag for the uImage Header of 64 bytes inside the kernel |
| 106 | # partition. Read and mangle it so it contains a valid CAMEO tag |
| 107 | # and checksum that matches perfectly to the uImage header CRC. |
| 108 | |
| 109 | buf = checksum_header(read_buffer(0, UIMAGE_HEADER_SIZE)) |
| 110 | write_buffer(os.SEEK_SET, buf) |
| 111 | |
| 112 | # tag for the second part of the kernel that resides in the |
| 113 | # vendor rootfs partition. For this we will add the CAMEO tag |
| 114 | # and the checksum to the end of the image. |
| 115 | |
| 116 | buf = read_buffer(args.rootfs_start, READ_UNTIL_EOF) |
| 117 | write_buffer(os.SEEK_END, CAMEO_TAG + cameosum(buf + CAMEO_TAG)) |