From 642dcc9a43c4a0f0700899aaf495f94152d25199 Mon Sep 17 00:00:00 2001 From: Armel FAUVEAU Date: Fri, 3 Apr 2026 05:00:38 +0200 Subject: [PATCH] Add support for decoding packed Quansheng stock firmware images --- .gitignore | 4 +- .../quansheng.k1.stock.firmware.v7.03.01.bin | Bin 0 -> 71268 bytes tools/serialtool/_fwcodec.py | 89 ++++++++++++++++++ tools/serialtool/cli.py | 88 +++++++++++++++-- 4 files changed, 171 insertions(+), 10 deletions(-) create mode 100644 archive/quansheng.k1.stock.firmware.v7.03.01.bin create mode 100644 tools/serialtool/_fwcodec.py diff --git a/.gitignore b/.gitignore index 64adb71c..581c4d75 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,6 @@ compile_commands.json k5_eeprom.raw /build -/serialtool/__pycache__ +__pycache__/ -.DS_Store \ No newline at end of file +.DS_Store diff --git a/archive/quansheng.k1.stock.firmware.v7.03.01.bin b/archive/quansheng.k1.stock.firmware.v7.03.01.bin new file mode 100644 index 0000000000000000000000000000000000000000..7beb185168f0e04989320ff4589b9b8399b7bbdf GIT binary patch literal 71268 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 R8?kv{eFXP?jEOOK{y(M bool: + if len(image) < 8: + return False + + stack_pointer = int.from_bytes(image[0:4], "little") + reset_vector = int.from_bytes(image[4:8], "little") + + return (stack_pointer & 0xFFF00000) == 0x20000000 and (reset_vector & 0xFFF00000) == 0x08000000 + + +def is_raw_image(image: bytes) -> bool: + return _vector_table_looks_valid(image) + + +def has_valid_vendor_crc(image: bytes) -> bool: + if len(image) < CRC_SIZE: + return False + + expected_crc = crc_hqx(image[:-CRC_SIZE], 0) + actual_crc = int.from_bytes(image[-CRC_SIZE:], "little") + + return expected_crc == actual_crc + + +def is_packed_image(image: bytes) -> bool: + if len(image) <= VERSION_OFFSET + VERSION_SIZE + CRC_SIZE: + return False + + if is_raw_image(image): + return False + + return has_valid_vendor_crc(image) + + +def decode_packed_image(image: bytes) -> tuple[bytes, str]: + if not is_packed_image(image): + raise ValueError("not a packed Quansheng firmware image") + + decoded = bytes(a ^ b for a, b in zip(image[:-CRC_SIZE], cycle(OBFUSCATION))) + version_bytes = decoded[VERSION_OFFSET:VERSION_OFFSET + VERSION_SIZE] + version = version_bytes.split(b"\x00", 1)[0].decode("ascii", errors="replace") + + raw = decoded[:VERSION_OFFSET] + decoded[VERSION_OFFSET + VERSION_SIZE:] + if not is_raw_image(raw): + raise ValueError("decoded firmware does not look like a raw MCU image") + + return raw, version diff --git a/tools/serialtool/cli.py b/tools/serialtool/cli.py index 861cdbf8..5aa16d07 100644 --- a/tools/serialtool/cli.py +++ b/tools/serialtool/cli.py @@ -17,14 +17,11 @@ import argparse -import serial import signal from time import sleep import os -import _prog as pp -import _dump as dd -import _restore as rr +import _fwcodec as fc def load_image(file: str) -> bytes: @@ -42,7 +39,9 @@ def load_image(file: str) -> bytes: return a -def main_dump(args, ser: serial.Serial): +def main_dump(args, ser): + + import _dump as dd dump_file: str = args.file @@ -73,7 +72,10 @@ def main_dump(args, ser: serial.Serial): sleep(0) -def main_restore(args, ser: serial.Serial): +def main_restore(args, ser): + + import _dump as dd + import _restore as rr dump_file: str = args.file @@ -105,7 +107,9 @@ def main_restore(args, ser: serial.Serial): sleep(0) -def main_flash(args, ser: serial.Serial): +def main_flash(args, ser): + + import _prog as pp bl_ver: str = args.bl_ver fw_file: str = args.file @@ -139,6 +143,56 @@ def main_flash(args, ser: serial.Serial): sleep(0) +def get_raw_output_path(file: str) -> str: + + root, ext = os.path.splitext(file) + if ext: + file_name = "{}.raw{}".format(os.path.basename(root), ext) + candidate = "{}.raw{}".format(root, ext) + else: + file_name = os.path.basename(file) + ".raw.bin" + candidate = file + ".raw.bin" + + out_dir = os.path.dirname(candidate) or "." + if os.access(out_dir, os.W_OK): + return candidate + + return os.path.join(os.getcwd(), file_name) + + +def main_decode(args): + + fw_file: str = args.file + out_file: str = args.output or get_raw_output_path(fw_file) + + try: + fw_image = load_image(fw_file) + if 0 == len(fw_image): + print("Invalid firmware image: {}: empty file".format(fw_file)) + return + except Exception as e: + print("Cannot load firmware image '{}': {}".format(fw_file, e)) + return + + if fc.is_raw_image(fw_image): + print("Firmware image already looks raw") + raw_image = fw_image + version = None + else: + try: + raw_image, version = fc.decode_packed_image(fw_image) + except Exception as e: + print("Cannot decode firmware image '{}': {}".format(fw_file, e)) + return + + with open(out_file, "wb") as fd: + fd.write(raw_image) + + print("Raw firmware image written: {}, size = {}".format(out_file, len(raw_image))) + if version: + print("Packed version field: {}".format(version)) + + def main(): # Usage: @@ -146,6 +200,7 @@ def main(): # serialtool.py .. flash [--bl-ver ] # serialtool.py .. dump {--config | --calib [| --all]} file # serialtool.py .. restore {--config | --calib [| --all]} file + # serialtool.py decode [raw.bin] ap = argparse.ArgumentParser(description="UV-K5 V2 serial tool") # TODO: have to add option to each of subcommands ?? @@ -198,14 +253,31 @@ def main(): ) ap_restore.add_argument("file", help="input dump file") + ap_decode = sp.add_parser( + "decode", help="decode a packed Quansheng stock firmware into a raw image" + ) + ap_decode.add_argument("file", help="input firmware image file") + ap_decode.add_argument( + "output", + nargs="?", + help="output raw firmware image file (default: .raw.bin)", + ) + args = ap.parse_args() - port: str = args.port sub_name: str = args.subcommand print(ap.description) # print("Press Ctrl-C to quit") + if "decode" == sub_name: + main_decode(args) + return + + port: str = args.port + try: + import serial + ser = serial.Serial(port, baudrate=38400, timeout=0.0001, write_timeout=None) except Exception as e: print("Cannot open port '{}': {}".format(port, e))