安全启动和安全固件更新(SBSFU)6:编译流程——prebuild 与 postbuild 脚本

第6篇:编译流程------prebuild 与 postbuild 脚本详解

本篇将逐行解析 X-CUBE-SBSFU 中的预编译脚本(prebuild.bat)和后编译脚本(postbuild.bat)。这些脚本是编译流程的"自动化工兵",负责密钥嵌入、固件加密、签名生成和打包。看懂这些脚本,你就真正理解了从源码到烧录文件的完整转换过程。


1. 整体编译流程概览

在深入脚本细节之前,我们先建立一个全局视图。从源码到最终烧录文件的完整路径如下:

复制代码
┌─────────────────────────────────────────────────────────────────┐
│                    第1步:SECoreBin 编译                          │
│                                                                  │
│  密钥文件(Binary/*.bin/*.txt)                                    │
│       │                                                          │
│       ▼                                                          │
│  prebuild.bat ────→ se_key.s (密钥汇编文件)                       │
│       │             postbuild.bat (供 UserApp 使用的后处理脚本)     │
│       │                                                          │
│       ▼                                                          │
│  ARMCC 编译 se_crypto_bootloader.c → .o                         │
│       │                                                          │
│       ▼                                                          │
│  链接生成 SECoreBin.axf                                          │
│       │                                                          │
│       ▼                                                          │
│  fromelf --bin → SE_CORE_Bin.bin → 转为 .s 汇编数据               │
│  (此 .s 文件供 SBSFU 链接)                                       │
└─────────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────────┐
│                    第2步:SBSFU 编译                               │
│                                                                  │
│  SE_CORE_Bin.s (来自第1步)                                       │
│       +                                                          │
│  sfu_boot.c, sfu_loader.c, sfu_mpu_isolation.c ...              │
│       +                                                          │
│  se_interface_*.c (SE 接口封装)                                   │
│       │                                                          │
│       ▼                                                          │
│  链接生成 SBSFU.axf (包含 SE Core + SBSFU 状态机)                 │
│       │                                                          │
│       ▼                                                          │
│  产物: se_interface_application.o (被 UserApp 链接)               │
│         Project.axf (完整 SBSFU 二进制,烧录到 0x08000000)        │
└─────────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────────┐
│                    第3步:UserApp 编译                             │
│                                                                  │
│  main.c, ymodem.c, fw_update_app.c ...                          │
│       +                                                          │
│  se_interface_application.o (来自第2步)                           │
│       │                                                          │
│       ▼                                                          │
│  链接生成 UserApp.axf → UserApp.bin (原始未加密)                  │
│       │                                                          │
│       ▼  (postbuild.bat 自动执行)                                 │
│  ┌──────────────────────────────────┐                            │
│  │ Step 1: prepareimage.py enc      │  AES256-CBC 加密           │
│  │         UserApp.bin → UserApp.sfu│                             │
│  ├──────────────────────────────────┤                            │
│  │ Step 2: prepareimage.py sha384   │  计算 SHA384 哈希           │
│  │         UserApp.bin → UserApp.sign│                            │
│  ├──────────────────────────────────┤                            │
│  │ Step 3: prepareimage.py pack     │  ECDSA 签名 + 打包          │
│  │         → UserApp.sfb            │  生成最终固件包              │
│  ├──────────────────────────────────┤                            │
│  │ Step 4: prepareimage.py merge    │  合并 SBSFU + UserApp       │
│  │         → SBSFU_UserApp.bin      │  一次性烧录镜像              │
│  ├──────────────────────────────────┤                            │
│  │ Step 5: prepareimage.py diff     │  生成差分包 (可选)           │
│  │         → PartialUserApp.sfb     │                             │
│  └──────────────────────────────────┘                            │
└─────────────────────────────────────────────────────────────────┘

2. SECoreBin 预编译脚本(prebuild.bat)逐行解析

prebuild.bat 是 SECoreBin 工程编译前自动执行的脚本。它的核心任务有两个:

  1. 根据加密方案生成密钥汇编文件 (se_key.s)
  2. 复制对应加密方案的后处理脚本 给 UserApp 使用

下面是完整脚本的逐段解析(文件路径:2_Images_SECoreBin/MDK-ARM/prebuild.bat)。

第1-10行:初始化

batch 复制代码
@echo off
echo prebuild.bat : started > %1\\output.txt
set "asmfile=%1\\se_key.s"
::comment this line to force python
::python is used if windows executable not found
pushd %1\..\..\..\..\..\..\Middlewares\ST\STM32_Secure_Engine\Utilities\KeysAndImages
set basedir=%cd%
popd
goto exe:
goto py:
解释
@echo off 关闭命令回显,使输出干净
%1 Keil 传入的第一个参数,即编译输出目录(如 .\MDK-ARM\NUCLEO-G474RE\
%1\\output.txt 将所有输出重定向到日志文件
set "asmfile=..." 设置 se_key.s 的目标路径
pushd ... KeysAndImages 临时跳转到 prepareimage.py 所在目录
set basedir=%cd% 记录工具目录的绝对路径
popd 返回原目录
goto exe: 跳转到 exe 标签(优先使用 Windows 可执行文件)

第11-25行:选择执行方式(exe 或 Python)

batch 复制代码
:exe
::line for window executable
echo Prebuild with windows executable
set "prepareimage=%basedir%\\win\\prepareimage\\prepareimage.exe"
set "python="
if exist %prepareimage% (
goto prebuild
)
:py
::line for python
echo Prebuild with python script
set "prepareimage=%basedir%\\prepareimage.py"
set "python=python "
echo "python: %prepareimage%" >> %1\\output.txt 2>>&1
:prebuild

这段代码实现了一个优雅的降级策略

复制代码
如果 prepareimage.exe 存在 → 使用 exe(更快,无需 Python)
           ↓ (不存在)
使用 prepareimage.py → 需要 Python 环境

注意 set "python=python " 末尾有一个空格------这是批处理拼接命令时的必要细节:%python%%prepareimage% 展开后变成 python prepareimage.py

第26-37行:清理旧文件

batch 复制代码
set "crypto_h=%1\\..\\Inc\\se_crypto_config.h"

::clean
if exist %1\\crypto.txt (
  del %1\\crypto.txt
)
if exist %asmfile% (
  del %asmfile%
)
if exist %1\\postbuild.bat (
  del %1\\postbuild.bat
)

每次编译前删除上次生成的文件,确保使用的是最新配置。清理的文件包括:

  • crypto.txt:上次读取的加密方案名称
  • se_key.s:上次生成的密钥汇编文件
  • postbuild.bat:上次复制的后处理脚本

第40-44行:读取加密方案

batch 复制代码
::get crypto name
set "command=%python%%prepareimage% conf %crypto_h% > %1\\crypto.txt"
%command%
IF %ERRORLEVEL% NEQ 0 goto error
set /P crypto=<%1\\crypto.txt >> %1\\output.txt 2>>&1
echo crypto %crypto% selected >> %1\\output.txt 2>>&1

这里发生了什么?

复制代码
prepareimage.py conf se_crypto_config.h
    │
    │  读取 #define SECBOOT_CRYPTO_SCHEME SECBOOT_ECCDSA_WITH_AES256_CBC_SHA384
    │
    ▼
输出 "SECBOOT_ECCDSA_WITH_AES256_CBC_SHA384" → crypto.txt
    │
    ▼
set /P crypto=<crypto.txt  # 将文件内容读入变量 crypto

prepareimage.py conf 命令解析 C 头文件中的宏定义,提取加密方案名称。这是整个脚本的关键决策点 ------后续所有处理都依赖 %crypto% 变量的值。

第46-49行:设置 Cortex 类型,写入汇编头

batch 复制代码
set "cortex=V7M"

:: Tabulation before section is mandatory !
echo     AREA ^|.SE_Key_Data^|, CODE>%asmfile%
解释
set "cortex=V7M" 指定 ARM Cortex 架构版本。V7M 对应 Cortex-M4(支持 MOVW/MOVT 指令)
`AREA .SE_Key_Data

> 覆盖写入,后面的 >> 追加写入

第51-69行:根据加密方案跳转

batch 复制代码
if "%crypto%"=="SECBOOT_AES128_GCM_AES128_GCM_AES128_GCM" (
  set "type=GCM"
  goto AES128
)
if "%crypto%"=="SECBOOT_ECCDSA_WITH_AES128_CBC_SHA256" (
  set "type=CBC"
  goto AES128
)
if "%crypto%"=="SECBOOT_ECCDSA_WITHOUT_ENCRYPT_SHA256" (
  goto ECDSA
)
if "%crypto%"=="SECBOOT_ECCDSA_WITH_AES256_CBC_SHA384" (
  set "type=CBC"
  goto AES256
)
if "%crypto%"=="SECBOOT_ECCDSA_WITHOUT_ENCRYPT_SHA384" (
  goto ECDSA384
)
goto end

这段代码的分支逻辑:

复制代码
加密方案名称                          → 跳转目标       处理内容
────────────────────────────────────────────────────────────
SECBOOT_AES128_GCM_AES128_GCM_AES128_GCM → AES128     嵌入 AES128 密钥 (GCM)
SECBOOT_ECCDSA_WITH_AES128_CBC_SHA256     → AES128     嵌入 AES128 密钥 (CBC) → ECDSA 公钥
SECBOOT_ECCDSA_WITHOUT_ENCRYPT_SHA256     → ECDSA      仅嵌入 ECDSA 公钥
SECBOOT_ECCDSA_WITH_AES256_CBC_SHA384     → AES256     嵌入 AES256 密钥 (CBC) → ECDSA384 公钥
SECBOOT_ECCDSA_WITHOUT_ENCRYPT_SHA384     → ECDSA384   仅嵌入 ECDSA P-384 公钥

第71-89行:AES128 密钥处理

batch 复制代码
:AES128
set "oemkey=%1\\..\\Binary\\OEM_KEY_COMPANY1_key_AES_%type%.bin"
set "command=%python%%prepareimage% trans -k %oemkey% -f SE_ReadKey_1 -v %cortex% >> %asmfile%"
%command%
IF %ERRORLEVEL% NEQ 0 goto error

set "oemkey=%1\\..\\Binary\\OEM_KEY_COMPANY2_key_AES_%type%.bin"
IF NOT EXIST %oemkey% goto :AES128_end
set "command=%python%%prepareimage% trans -k %oemkey% -f SE_ReadKey_2 -v %cortex% >> %asmfile%"
%command%
IF %ERRORLEVEL% NEQ 0 goto error

核心命令解析

bash 复制代码
prepareimage.py trans -k <密钥文件> -f <函数名> -v <Cortex版本>
参数 说明
trans 操作模式:translate,将密钥文件转换为 ARM 汇编代码
-k <file> 输入的密钥文件(二进制 .bin 或文本 .txt
-f <name> 生成的汇编函数名(SE_ReadKey_1SE_ReadKey_1_Pub 等)
-v V7M Cortex 版本:V6M(Cortex-M0)不支持 MOVW/MOVT,V7M(Cortex-M3/M4/M7)支持

生成的汇编函数结构如下(以 SE_ReadKey_1 为例):

asm 复制代码
SE_ReadKey_1
    PUSH {R1-R5}           ; 保存寄存器
    MOVW R1, #0x454f       ; 密钥字节 0-1 (低16位)
    MOVT R1, #0x5f4d       ; 密钥字节 2-3 (高16位)
    MOVW R2, #0x454b       ; 密钥字节 4-5
    MOVT R2, #0x5f59       ; 密钥字节 6-7
    ; ... 更多 MOVW/MOVT 指令 ...
    STM R0, {R1-R4}        ; 将 16 字节 (R1-R4) 写入调用者提供的缓冲区 (*R0)
    ADD R0, R0, #16        ; 缓冲区指针偏移 16 字节
    ; ... 继续写入后续字节 ...
    POP {R1-R5}            ; 恢复寄存器
    BX LR                  ; 返回

密钥混淆机制 :密钥并不是以连续的二进制数组存储在 Flash 中的。相反,每个密钥字节对(16 位)被拆散到 MOVW/MOVT 指令的立即数字段中,散布在汇编指令之间。攻击者如果想从 Flash 中直接提取密钥,看到的是 ARM 指令的编码流,而不是连续的密钥数据。这是一种轻量级的反逆向混淆

第92-110行:ECDSA P-256 公钥处理

batch 复制代码
:ECDSA
set "ecckey=%1\\..\\Binary\\ECCKEY1.txt"
set "command=%python%%prepareimage% trans -k %ecckey% -f SE_ReadKey_1_Pub -v %cortex% >> %asmfile%"
%command%
IF %ERRORLEVEL% NEQ 0 goto error

ECDSA 的处理逻辑与 AES 类似,但密钥文件是文本格式(.txt),且生成的函数名是 SE_ReadKey_1_Pub(公钥)。支持最多 3 个公司的公钥(ECCKEY1/2/3)。

第132-150行:ECDSA P-384 公钥处理

batch 复制代码
:ECDSA384
set "ecckey=%1\\..\\Binary\\ECCKEY1-384.txt"
set "command=%python%%prepareimage% trans -k %ecckey% -f SE_ReadKey_1_Pub -v %cortex% >> %asmfile%"

与本项目使用的 SECBOOT_ECCDSA_WITH_AES256_CBC_SHA384 对应的就是 P-384 密钥。ECDSA P-384 公钥为 96 字节(曲线点的 X + Y 坐标各 48 字节),比 P-256 的公钥(64 字节)长 50%。

第154-157行:写入汇编尾,复制后处理脚本

batch 复制代码
:end
echo     END >> %asmfile%
set "command=copy %1\\%crypto%.bat %1\\postbuild.bat"
%command%
IF %ERRORLEVEL% NEQ 0 goto error
exit 0
解释
echo END >> %asmfile% 写入 ARM 汇编的 END 指令,标记源文件结束
copy %crypto%.bat postbuild.bat 将对应加密方案的 bat 脚本复制为 UserApp 的后处理脚本

例如,当 SECBOOT_CRYPTO_SCHEME = SECBOOT_ECCDSA_WITH_AES256_CBC_SHA384 时:

复制代码
copy SECBOOT_ECCDSA_WITH_AES256_CBC_SHA384.bat postbuild.bat

这确保了用 AES256-CBC 加密的固件在 UserApp 后处理时使用对应的解密/签名脚本。

生成的 se_key.s 内容解析

实际生成的文件(本项目使用 ECDSA P-384 + AES256-CBC):

asm 复制代码
    AREA |.SE_Key_Data|, CODE
    EXPORT SE_ReadKey_1                 ; AES256 密钥读取函数
SE_ReadKey_1
    PUSH {R1-R5}
    MOVW R1, #0x454f                    ; ; AES 密钥以混淆方式存储
    MOVT R1, #0x5f4d
    ; ... 32 字节 AES256 密钥分散在 16 条 MOVW/MOVT 指令中 ...
    POP {R1-R5}
    BX LR

    EXPORT SE_ReadKey_1_Pub             ; ECDSA P-384 公钥读取函数
SE_ReadKey_1_Pub
    PUSH {R1-R5}
    MOVW R1, #0xf02
    MOVT R1, #0x3d9c
    ; ... 96 字节公钥分散在 48 条 MOVW/MOVT 指令中 ...
    POP {R1-R5}
    BX LR
    END

3. UserApp 后编译脚本(postbuild.bat)逐行解析

postbuild.bat 在 UserApp 工程编译完成后自动执行,负责将原始二进制固件转换为加密+签名的 .sfb 格式。它的完整路径是 2_Images_SECoreBin/MDK-ARM/SECBOOT_ECCDSA_WITH_AES256_CBC_SHA384.bat(由 prebuild.bat 复制而来)。

变量初始化

batch 复制代码
@echo off
set "projectdir=%1"
set "execname=%~n3"
set "elf=%2"
set "bin=%3"
set "fwid=%4"
set "version=%5"

Keil 传递给 postbuild.bat 的参数(在 Keil → Options for Target → User → After Build 中配置):

参数 含义 示例值
%1 工程目录 .\MDK-ARM
%2 ELF/AXF 文件完整路径 .\MDK-ARM\NUCLEO-G474RE\Exe\UserApp.axf
%3 BIN 文件完整路径 .\MDK-ARM\NUCLEO-G474RE\Exe\UserApp.bin
%4 固件 ID (1/2/3) 1
%5 固件版本号 由用户定义
batch 复制代码
set "SBSFUBootLoader=%~d0%~p0\\..\\.."

%~d0 = 脚本所在盘符,%~p0 = 脚本所在路径。这行计算出 SBSFU 项目的根目录。

batch 复制代码
set "programmertool="C:\\Program Files (x86)\\STMicroelectronics\\STM32Cube\\STM32CubeProgrammer\\bin\\STM32_Programmer_CLI""

这是 STM32CubeProgrammer CLI 的默认安装路径。如果你的安装路径不同,需要修改这一行。

输出文件路径定义

batch 复制代码
set "userAppBinary=%projectdir%\\..\\Binary\\"

set "sfu=%userAppBinary%\\%execname%.sfu"         ; 加密后的固件
set "sfb=%userAppBinary%\\%execname%.sfb"         ; 最终固件包(含头部+签名)
set "sign=%userAppBinary%\\%execname%.sign"        ; SHA384 哈希值
set "headerbin=%userAppBinary%\\%execname%sfuh.bin"; 固件头二进制
set "bigbinary=%userAppBinary%\\SBSFU_%execname%.bin"     ; 合并大镜像
set "partialbin=%userAppBinary%\\Partial%execname%.bin"   ; 差异二进制
set "partialsfb=%userAppBinary%\\Partial%execname%.sfb"   ; 差异固件包

密钥和配置路径定义

batch 复制代码
set "iv=%SBSFUBootLoader%\\2_Images_SECoreBin\\Binary\\iv.bin"
set "oemkey=%SBSFUBootLoader%\\2_Images_SECoreBin\\Binary\\OEM_KEY_COMPANY%fwid%_key_AES256_CBC.bin"
set "ecckey=%SBSFUBootLoader%\\2_Images_SECoreBin\\Binary\\ECCKEY%fwid%-384.txt"
set "sbsfuelf=%SBSFUBootLoader%\\2_Images_SBSFU\\MDK-ARM\\NUCLEO-G474RE\\Exe\\SBSFU.axf"
set "magic=SFU%fwid%"
set "offset=4096"
set "alignment=16"
变量 说明
iv Binary/iv.bin AES-CBC 初始化向量(16 字节)
oemkey OEM_KEY_COMPANY1_key_AES256_CBC.bin AES-256 加密密钥(32 字节)
ecckey ECCKEY1-384.txt ECDSA P-384 签名私钥
sbsfuelf SBSFU.axf SBSFU 的 ELF 文件(用于合并镜像)
magic SFU1 固件魔数标识
offset 4096 固件头偏移(4KB 对齐)

选择执行方式

与 prebuild.bat 相同的逻辑------优先使用 exe,备选 Python。

步骤1:AES256-CBC 加密

batch 复制代码
set "command=%python%%prepareimage% enc -k %oemkey% -i %iv% %bin% %sfu%  > %projectdir%\output.txt 2>&1"
%command%
IF %ERRORLEVEL% NEQ 0 goto :error

命令解析

bash 复制代码
prepareimage.py enc \
    -k OEM_KEY_COMPANY1_key_AES256_CBC.bin  # 256位AES密钥
    -i iv.bin                                 # 初始化向量(16字节)
    UserApp.bin                               # 输入:原始固件
    UserApp.sfu                               # 输出:加密固件

enc 模式执行 AES-CBC 加密。加密后的文件 .sfu(Secure Firmware Update)比原文件稍大(会有 PKCS#7 填充到 16 字节边界)。

AES 16 字节对齐的硬性要求 :AES 的块大小固定为 128 位(16 字节)。AES-CBC 模式要求输入数据必须是 16 字节的整数倍。如果你的固件二进制文件不是 16 字节对齐的,加密过程会通过 PKCS#7 填充补齐,但这会导致解密后的固件末尾多出填充字节。为避免此问题,建议在链接器中就将固件的 ROM 区域配置为 16 字节对齐:

c 复制代码
// Keil Scatter File 中确保 16 字节对齐
LR_IROM1 0x08010000 0x00036000  {
ER_IROM1 0x08010000 FIXED 0x00036000  {
 *.o (RESET, +First)
 *.o (+RO)
 .ANY (+RO)
 .ANY (+XO)
}
// 16 字节对齐可通过填充实现
}

在 IAR 中通过链接器配置 --config_def,在 STM32CubeIDE 中通过 .ld 文件的 ALIGN(16) 实现同样效果。三款 IDE 的对齐方式不同,但目的相同。

步骤2:SHA384 哈希计算

bash 复制代码
prepareimage.py sha384 UserApp.bin UserApp.sign
参数 说明
sha384 操作模式:计算 SHA-384 哈希
UserApp.bin 输入:原始未加密固件 (不是加密后的 .sfu!)
UserApp.sign 输出:48 字节 SHA384 哈希值

为什么对原始固件计算哈希?

因为设备端在解密后会得到原始固件,需要验证的是解密后的固件发布者签名时的固件是否一致。如果对密文计算哈希,中间人对密文的任何修改都会通过篡改检测。

步骤3:ECDSA 签名 + 打包生成 .sfb

bash 复制代码
prepareimage.py pack \
    -m SFU1                                    # 魔数
    -k ECCKEY1-384.txt                         # ECDSA P-384 私钥
    -r 4                                       # 协议版本
    -v <version>                               # 固件版本
    -i iv.bin                                  # AES IV
    -f UserApp.sfu                             # 加密固件文件
    -t UserApp.sign                            # SHA384 哈希
    UserApp.sfb                                # 输出:最终固件包
    -o 4096                                    # 固件头偏移(4KB)

pack 模式做以下事情:

  1. 读取加密固件(.sfu)和哈希文件(.sign
  2. 用 ECDSA P-384 私钥对固件头进行签名
  3. 构建 SE_FwRawHeaderTypeDef 结构体(填充魔数、版本、大小、IV、哈希等字段)
  4. 将头部 + 加密固件打包为一个 .sfb 文件

.sfb 文件结构

复制代码
┌──────────────────────┐
│   FW Header (232B)   │  ← SE_FwRawHeaderTypeDef 结构体
│   - SFUMagic: "SFU1" │
│   - FwVersion         │
│   - FwSize            │
│   - InitVector[16]    │
│   - FwTag[48]         │  ← SHA384 哈希
│   - HeaderSignature[96]│ ← ECDSA P-384 签名
│   - FwImageState[96]  │
│   - PrevFingerprint[32]│
├──────────────────────┤
│   Encrypted FW        │  ← AES256-CBC(UserApp.bin)
│   (UserApp.sfu 内容)  │
└──────────────────────┘

步骤4:固件头二进制生成 + 合并镜像

bash 复制代码
prepareimage.py header \
    -m SFU1 -k ECCKEY1-384.txt -r 4 -v <version> \
    -i iv.bin -f UserApp.sfu -t UserApp.sign -o 4096 \
    UserAppsfuh.bin

header 模式单独生成固件头二进制文件(不含加密固件体)。

bash 复制代码
prepareimage.py merge \
    -i UserAppsfuh.bin       # 固件头二进制
    -s SBSFU.axf             # SBSFU ELF 文件
    -u UserApp.axf           # UserApp ELF 文件
    SBSFU_UserApp.bin        # 输出:合并镜像

merge 模式生成一个可以在开发阶段一次性烧录的大镜像

复制代码
┌──────────────────────┐
│   SBSFU (64KB)       │  ← 0x08000000
├──────────────────────┤
│   FW Header (232B)   │  ← 0x08010000 (Active Slot 起始)
├──────────────────────┤
│   UserApp (明文)     │  ← 0x080100E8
└──────────────────────┘

这个合并镜像可以绕过 YMODEM 下载过程,直接用 STM32CubeProgrammer 烧录进行开发测试。

步骤5(可选):差分固件生成

bash 复制代码
prepareimage.py diff \
    -1 RefUserApp.bin       # 参考固件(旧版本)
    -2 UserApp.bin          # 新固件
    PartialUserApp.bin      # 差分结果
    -a 16                   # 16字节对齐
    --poffset PartialUserApp.offset  # 偏移量文件

diff 模式比较新旧固件的差异,生成差分包,只包含变化的部分。这在 OTA 场景中极为重要------无需传输完整固件,只需传输差异部分。

后续处理(加密、签名、打包)与完整固件相同,最终生成 PartialUserApp.sfb

清理中间文件

batch 复制代码
:finish
::backup and clean up the intermediate file
del %sign%
del %sfu%
del %headerbin%

编译完成后自动清理中间文件(.sfu.signsfuh.bin),只保留最终的 .sfb 和合并镜像。


4. prepareimage.py 工具详解

prepareimage.py 是整个编译流水线中的"瑞士军刀"。它支持 10 种操作模式:

模式 命令 作用 使用场景
conf prepareimage.py conf <config.h> 读取加密方案配置 prebuild.bat 中确定加密方案
keygen prepareimage.py keygen -k <file> -t <type> 生成密钥对 首次部署时生成密钥
trans prepareimage.py trans -k <key> -f <func> -v <cortex> 密钥转 ARM 汇编 prebuild.bat 中生成 se_key.s
enc prepareimage.py enc -k <key> -i <iv> <in> <out> AES 加密 postbuild.bat 中加密固件
sha256 prepareimage.py sha256 <in> <out> SHA-256 哈希 P-256 方案的哈希计算
sha384 prepareimage.py sha384 <in> <out> SHA-384 哈希 P-384 方案的哈希计算
sign prepareimage.py sign -k <key> -n <nonce> <in> <out> GCM 认证标签 AES-GCM 方案的标签生成
pack prepareimage.py pack -m <magic> -k <key> ... 打包 .sfb 最终固件包生成
merge prepareimage.py merge -i <hdr> -s <sbsfu> -u <userapp> 合并镜像 生成开发烧录镜像
diff prepareimage.py diff -1 <old> -2 <new> <out> 差分生成 OTA 差分包
header prepareimage.py header -m <magic> ... 生成头部 单独生成固件头文件

操作模式详解

conf ------ 读取配置
bash 复制代码
python prepareimage.py conf se_crypto_config.h
# 输出: SECBOOT_ECCDSA_WITH_AES256_CBC_SHA384

扫描 C 头文件中的 #define SECBOOT_CRYPTO_SCHEME ... 行,输出宏的名称。

trans ------ 密钥转汇编
bash 复制代码
python prepareimage.py trans \
    -k OEM_KEY_COMPANY1_key_AES256_CBC.bin \  # 32字节AES256密钥
    -f SE_ReadKey_1 \                          # 函数名
    -v V7M                                     # Cortex-M4 使用 V7M
    >> se_key.s

将二进制密钥文件转换为 ARM Thumb 汇编代码。核心逻辑:

  1. 读取密钥文件的每个字节
  2. 每 4 个字节组成一个 32 位值
  3. 生成 MOVW R?, #low16 + MOVT R?, #high16 指令对
  4. 每 16 个字节(4 个寄存器)后用 STM R0, {R1-R4} 写入目标缓冲区
  5. 使用 ADD R0, R0, #16 推进缓冲区指针
enc ------ AES 加密
bash 复制代码
python prepareimage.py enc \
    -k OEM_KEY_COMPANY1_key_AES256_CBC.bin \  # AES-256 密钥
    -i iv.bin \                                # 初始化向量
    UserApp.bin \                              # 明文
    UserApp.sfu                                # 密文

加密模式取决于加密方案:

  • AES256_CBC:AES-CBC 模式,PKCS#7 填充
  • AES128_GCM:AES-GCM 模式,使用 Nonce + 附加认证数据(AAD)
pack ------ 打包固件

pack 是最复杂的操作。它需要:

  1. 读取加密固件 .sfu 和哈希 .sign
  2. 用私钥对固件头进行 ECDSA 签名
  3. 构建 SE_FwRawHeaderTypeDef 结构体
  4. 将头部序列化为二进制 + 加密固件体 → .sfb
bash 复制代码
python prepareimage.py pack \
    -m SFU1              # 魔数(对应槽位1)
    -k ECCKEY1-384.txt   # ECDSA 私钥
    -r 4                 # 协议版本
    -v 1                 # 固件版本
    -i iv.bin            # AES IV
    -f UserApp.sfu       # 加密固件
    -t UserApp.sign      # SHA384 哈希标签
    -o 4096              # 头部偏移(4KB对齐)
    UserApp.sfb          # 输出

5. 编译中可能遇到的常见问题

问题1:路径过长(Windows 260 字符限制)

复制代码
fatal error: cannot open output file ...: No such file or directory

原因:X-CUBE-SBSFU 的目录嵌套很深,路径容易超过 Windows 的 260 字符限制。

解决方法 :使用 subst 命令创建虚拟驱动器:

cmd 复制代码
subst X: F:\doxc\IAP\SBSFU_v2.6.2_G474RE_ECC384_AES256_IAR_KEIL_v2.1\SBSFU_v2.6.2

然后在 X: 盘下打开工程。

问题2:Python 未安装

复制代码
'python' is not recognized as an internal or external command

解决方法

  • 安装 Python 3.x,执行 pip install -r requirements.txt(安装 cryptography 模块)
  • 或者:prebuild.bat 会自动尝试使用 win/prepareimage/prepareimage.exe,无需 Python

问题3:STM32_Programmer_CLI 路径不对

复制代码
'STM32_Programmer_CLI' is not recognized

解决方法 :修改 postbuild.bat 中的 programmertool 变量为你实际的安装路径,或者如果你不需要生成合并 ELF 文件(bigelf),可以不传第 6 个参数。

问题4:密钥文件缺失

复制代码
FileNotFoundError: ... OEM_KEY_COMPANY1_key_AES256_CBC.bin

解决方法 :运行 prepareimage.py keygen 生成密钥:

bash 复制代码
python prepareimage.py keygen -k OEM_KEY_COMPANY1_key_AES256_CBC.bin -t aes-cbc
python prepareimage.py keygen -k ECCKEY1-384.txt -t ecdsa-p384

问题5:数据库(DBANK)位未禁用

设备行为异常,SBSFU 无法正常启动。

解决方法:通过 STM32CubeProgrammer 检查 Option Bytes,确保 DBANK 位为 0(已禁用)。


6. Keil IDE 中的编译设置

6.1 C/C++ 选项

Options for Target → C/C++ → Preprocessor Symbols:

复制代码
SECBOOT_CRYPTO_SCHEME=SECBOOT_ECCDSA_WITH_AES256_CBC_SHA384
USE_HAL_DRIVER
STM32G474xx
作用
SECBOOT_CRYPTO_SCHEME 选择加密方案(影响条件编译和结构体大小)
USE_HAL_DRIVER 启用 HAL 库
STM32G474xx 指定 MCU 型号

6.2 Linker 选项

Options for Target → Linker → Scatter File:

复制代码
.\Project.sct

该文件 #include "mapping_sbsfu.h"#include "mapping_fwimg.h",定义了各内存区域的位置和大小。

6.3 User 选项(编译前/后执行命令)

Options for Target → User:

阶段 命令 工程
Before Build prebuild.bat "#P" SECoreBin
After Build postbuild.bat "#P" "#L" "#H" 1 1 UserApp

#P = Project directory, #L = Linker output (ELF), #H = Hex output (BIN)。


总结

编译流水线的设计是 X-CUBE-SBSFU 中最精妙的部分之一。通过 prebuild.batpostbuild.bat 的组合,实现了一个零手动操作的自动化安全流水线

  1. 编译前 自动根据 SECBOOT_CRYPTO_SCHEME 选择密钥、生成汇编代码、复制后处理脚本
  2. 编译后自动加密固件、计算哈希、ECDSA 签名、打包 .sfb、合并镜像
  3. 支持差分包 生成,只需将旧版本固件放到 RefUserApp.bin 即可自动生成增量更新包

整个流水线在 Keil IDE 中一键完成,开发者无需手动调用任何加密工具------这正是 "secure by default" 设计理念的体现。


下一篇:第7篇:SECoreBin------安全引擎核心详解

相关推荐
摸鱼仙人~2 小时前
借鉴自动驾驶运行态安全经验,保障 AI Coding 实时产出安全的方法论研究
人工智能·安全·自动驾驶
Paranoid-up2 小时前
安全启动和安全固件更新(SBSFU)4:内存布局 —— SBSFU 的 Flash 与 RAM 分配
安全·iap·安全启动·安全升级·sbsfu
Paranoid-up2 小时前
安全启动和安全固件更新(SBSFU)9:UserApp -- 用户应用与YMODEM升级
安全·iap·安全启动·安全升级·sbsfu
AC赳赳老秦2 小时前
数据安全合规:OpenClaw 敏感信息脱敏、操作日志审计、权限精细化管控方案,符合等保要求
网络·数据库·python·安全·web安全·oracle·openclaw
Paranoid-up3 小时前
安全启动和安全固件更新(SBSFU)10:双镜像机制 – 活动槽与下载槽的协同工作
安全·iap·安全启动·安全升级·sbsfu
XD7429716363 小时前
科技早报晚报|2026年5月10日:Agent 安全沙箱、可审计编程代理与持久化产品上下文,今晚更值得做的 3 个开源机会
科技·安全·开源·开源项目·ai agent·开发者工具
@insist1233 小时前
信息安全工程师-入侵阻断与网络流量清洗技术详解
网络·安全·软考·信息安全工程师·软件水平考试
小小测试开发3 小时前
LLM 文档处理安全指南:如何避免 AI 静默篡改你的重要数据
人工智能·安全
vortex53 小时前
无人机系统安全攻防技术深度解析
安全·系统安全·无人机