ICF 文件生成器运行原理学习研究笔记
概述
本文档旨在分析和解释 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 加密的使用。