概述

本文档旨在分析和解释 genicf.py 文件的运行原理。该脚本用于生成加密的 ICF 文件,主要功能包括 AES 加密、数据打包、CRC32 校验等。ICF 文件通常用于固件更新或配置文件的打包和分发。

文件结构

导入模块

from Crypto.Cipher import AES
import struct
import binascii
  • Crypto.Cipher.AES: 用于 AES 加密和解密。
  • struct: 用于将 Python 数据类型打包为二进制数据。
  • binascii: 用于计算 CRC32 校验和。

AES 加密与解密

def decrypt(text: bytes, key: bytes, iv: bytes) -> bytes:
    mode = AES.MODE_CBC
    cryptor = AES.new(key, mode, iv)
    plain_text = cryptor.decrypt(text)
    return plain_text

def encrypt(text: bytes, key: bytes, iv: bytes) -> bytes:
    mode = AES.MODE_CBC
    cryptor = AES.new(key, mode, iv)
    cipher_text = cryptor.encrypt(text)
    return cipher_text
  • decrypt: 使用 AES-CBC 模式解密数据。
  • encrypt: 使用 AES-CBC 模式加密数据。

文件写入

def write_file(name: str, content: bytes) -> None:
    with open(name, "wb+") as f:
        f.write(content)
  • write_file: 将二进制数据写入文件。

数据打包

创建 .pack 文件部分

def create_part_pack(name: str, status: bool = True):
    (platid, _, major, _, minor, _, build, _, yy, mm, dd, hh, mi, ss, _, pack) = \
        struct.unpack("3ss4ss2ss2ss4s2s2s2s2s2s3s4s", name.encode())
    data_list = [build, minor, major, yy, mm, dd, hh, mi, ss, 0, build, minor, major]
    data = struct.pack("<2b2h8bh", *[int(i) for i in data_list])
    return ((b"\x02\x01" if status else b"\x02\x02") + b"\x00\x00\x00\x00" + bytes(0x1A) + data + bytes(0x10),
            (major, minor, build), platid)
  • create_part_pack: 解析 .pack 文件名并生成相应的二进制数据。

创建 .app 文件部分

函数定义

def create_part_app(name: str, sysver: tuple, app_list: dict = {}, status: bool = True):
  • name: .app 文件的名称,包含版本信息、时间戳等。
  • sysver: 系统版本信息,用于生成 .app 文件的数据。
  • app_list: 用于存储已生成的 .app 文件信息,避免重复或冲突。
  • status: 文件状态,决定生成的二进制数据的前缀。

解析文件名

基础应用

if len(name) > 33:
    is_patch = True
else:
    is_patch = False
if not is_patch:
    (appid, _, major, _, minor, _, build, _, yy, mm, dd, hh, mi, ss, _, patchno, app) = \
        struct.unpack("4ssss2ss2ss4s2s2s2s2s2sss4s", name.encode())
    if patchno != b"0":
        raise Exception("Base app number not zero!")
    data_list = [build, minor, major, yy, mm, dd, hh, mi, ss, 0, sysver[2], sysver[1], sysver[0]]
    data = struct.pack("<2b2h8bh", *[int(i) for i in data_list])
  • is_patch: 判断是否为补丁应用。
  • struct.unpack: 解析文件名,提取应用 ID、主版本号、次版本号、构建号、时间戳等信息。
  • data_list: 构建数据列表,包含版本信息和时间戳。
  • struct.pack: 将数据列表打包为二进制数据。

补丁应用

else:
    (appid, _, major, _, minor, _, build, _, yy, mm, dd, hh, mi, ss, _, patchno, _, base_major, _, base_minor, _,
     base_build, app) = \
        struct.unpack("4ssss2ss2ss4s2s2s2s2s2ssssss2ss2s4s", name.encode())
  • struct.unpack: 解析补丁应用的文件名,提取基础版本信息。

校验和存储

patchno = int(patchno)
if patchno in app_list:
    raise Exception("Duplicate patch number!",
                    f"Original: {app_list[patchno][2].decode()}.{app_list[patchno][1].decode()}.{app_list[patchno][0].decode()}",
                    f"Duplicate: {major.decode()}.{minor.decode()}.{build.decode()}")
if patchno - 1 not in app_list and patchno != 0:
    raise Exception("Patch base not found!", f"Patch: {major.decode()}.{minor.decode()}.{build.decode()}")
if is_patch and app_list[patchno - 1]:
    parent_major, parent_minor, parent_build = app_list[patchno - 1][2::-1]
    if parent_major != base_major or parent_minor != base_minor or parent_build != base_build:
        raise Exception("Patch base version mismatch!",
                        f"Patch: {base_major.decode()}.{base_minor.decode()}.{base_build.decode()}",
                        f"Base: {parent_major.decode()}.{parent_minor.decode()}.{parent_build.decode()}")
app_list[patchno] = (build, minor, major, yy, mm, dd, hh, mi, ss)
  • patchno: 补丁号,用于标识补丁应用的顺序。
  • app_list: 存储已生成的 .app 文件信息,避免重复或冲突。
  • 异常处理: 检查补丁号是否重复、基础版本是否存在、基础版本是否匹配。

生成二进制数据

data_list = [build, minor, major, yy, mm, dd, hh, mi, ss, 0, sysver[2], sysver[1], sysver[0]]
data = struct.pack("<2b2h8bh", *[int(i) for i in data_list])
base_data_list = app_list[patchno - 1] + (0, 0, 0, 0) if is_patch else [0] * 13
base_data = struct.pack("<2b2h8bh", *[int(i) for i in base_data_list])
return (b"\x02\x01" if status else b"\x02\x02") + b"\x00\x00\x01" + int(patchno).to_bytes(1, 'little') + bytes(
    0x1A) + data + base_data, appid
  • data_list: 构建数据列表,包含版本信息和时间戳。
  • struct.pack: 将数据列表打包为二进制数据。
  • base_data_list: 如果是补丁应用,则包含基础版本信息。
  • 返回值: 返回生成的二进制数据和应用 ID。

创建 .opt 文件部分

def create_part_option(name: str, status: bool = True) -> bytes:
    (appid, _, optid, _, yy, mm, dd, hh, mi, ss, _, opt) = \
        struct.unpack("4ss4ss4s2s2s2s2s2s3s3s", name.encode())
    data_list = [optid] + [int(i) for i in [yy, mm, dd, hh, mi, ss, 0, 0, 0, 0]]
    data = struct.pack("<4sh8bh", *data_list)
    return (b"\x02\x01" if status else b"\x02\x02") + b"\x00\x00\x02\x00" + bytes(0x1A) + data + bytes(0x10)
  • create_part_option: 解析 .opt 文件名并生成相应的二进制数据。

生成 ICF

生成文件主体

def create_body(content: list):
    body = bytes()
    crc2 = 0
    app_list = {}
    for item in content:
        if ".pack" in item:
            data, sysver, plat_id = create_part_pack(item)
        elif ".app" in item:
            data, app_id = create_part_app(item, sysver, app_list)
        elif ".opt" in item:
            data = create_part_option(item)
        body += data
        crc2 ^= binascii.crc32(data)
    return body, crc2, app_id, plat_id
  • create_body: 根据输入的文件列表生成 ICF 文件的主体部分,并计算 CRC32 校验和。

生成原始数据

def create_raw(content: list):
    body, crc2, app_id, plat_id = create_body(content)
    count = len(content)
    size = (count + 1) * 0x40
    plat_gen = 0
    data_list = [size, 0, 0, count, app_id, plat_id, plat_gen, crc2, 0, 0, 0, 0, 0, 0, 0]
    head = struct.pack("<I2iQ4s3sbI7i", *data_list)
    data = head + body
    crc1 = binascii.crc32(data)
    return crc1.to_bytes(4, "little") + data
  • create_raw: 生成包含头部和主体的原始数据,并计算最终的 CRC32 校验和。

生成 ICF 文件

def gen_icf(name: str, content: list) -> None:
    key = bytes.fromhex("09 CA 5E FD 30 C9 AA EF 38 04 D0 A7 E3 FA 71 20")
    iv = bytes.fromhex("B1 55 C2 2C 2E 7F 04 91 FA 7F 0F DC 21 7A FF 90")
    raw = create_raw(content)
    write_file(name, encrypt(raw, key, iv))
  • gen_icf: 生成加密的 ICF 文件。使用固定的密钥和 IV 对原始数据进行 AES 加密,并将结果写入文件。

主程序示例

if __name__ == "__main__":
    content = [
        "ACA_0071.55.01_20210915105953_0.pack",
        "SDHJ_1.10.00_20230610100449_0.app",
        "SDHJ_1.11.00_20230829201011_1_1.10.00.app",
        "SDHJ_1.12.00_20231018145409_2_1.11.00.app",
        "SDHJ_A001_20230908143845_0.opt",
    ]
    gen_icf("ICF1", content)
  • 主程序: 定义了文件列表,并调用 gen_icf 函数生成 ICF 文件。

总结

genicf.py 脚本通过解析文件名、打包数据、计算校验和、加密数据等步骤生成 ICF 文件。该文件可以用于固件更新或配置文件的打包和分发。脚本的核心在于数据的解析和打包,以及 AES 加密的使用。