2. 实现CRC校验的固件完整性检查

任务目标

在Bootloader跳转到应用程序之前,对Flash中的固件执行CRC32校验,与固件头部存储的参考值比对,防止篡改或损坏的固件被执行。

实现方案

固件镜像布局约定为:前4字节存放该固件自身的CRC32值,从偏移0x04开始是实际代码区。Bootloader启动后读取头部CRC值,对偏移0x04至固件末尾(由链接脚本或固定长度决定)重新计算CRC,两者相等则跳转,否则进入错误处理。

关键完整代码(STM32 HAL库)

// crc_bootloader.c
#include "stm32f1xx_hal.h"

#define APP_BASE_ADDR   0x08008000UL   // 应用程序起始地址
#define APP_MAX_SIZE    0x10000UL      // 固件最大64KB(含4字节CRC头)
#define CRC_SEED        0xFFFFFFFFUL

// 软件CRC32(IEEE 802.3,多项式0xEDB88320)
uint32_t sw_crc32(uint32_t crc, const uint8_t *data, uint32_t len)
{
    for (uint32_t i = 0; i < len; i++) {
        crc ^= data[i];
        for (int bit = 0; bit < 8; bit++) {
            crc = (crc >> 1) ^ (0xEDB88320UL & -(crc & 1));
        }
    }
    return crc;
}

// 从Flash读取固件并校验
uint8_t verify_firmware(void)
{
    uint32_t *app_addr = (uint32_t *)APP_BASE_ADDR;
    uint32_t stored_crc = *app_addr;                    // 头部存储的CRC
    uint32_t calc_crc = CRC_SEED;

    // 对偏移0x04到固件末尾(此处用固定最大长度)计算CRC
    calc_crc = sw_crc32(calc_crc,
                        (uint8_t *)(APP_BASE_ADDR + 4),
                        APP_MAX_SIZE - 4);
    calc_crc ^= CRC_SEED;                               // 最终异或

    return (calc_crc == stored_crc) ? 1 : 0;
}

// 跳转到应用程序
void jump_to_app(void)
{
    uint32_t app_sp = *(volatile uint32_t *)APP_BASE_ADDR;
    void (*app_reset)(void) = (void (*)(void))(
        *(volatile uint32_t *)(APP_BASE_ADDR + 4));

    __disable_irq();
    HAL_RCC_DeInit();
    SysTick->CTRL = 0; SysTick->LOAD = 0; SysTick->VAL = 0;
    __set_MSP(app_sp);
    app_reset();
}

// 主流程
void bootloader_main(void)
{
    if (verify_firmware()) {
        jump_to_app();
    } else {
        // 校验失败:可在此进入固件升级模式或错误指示
        while (1) { /* 错误处理:LED闪烁等 */ }
    }
}

固件生成端配套工具(PC端)

# gen_crc.py —— 为固件bin文件添加CRC头
import struct, zlib, sys

def add_crc_header(input_bin, output_bin):
    with open(input_bin, 'rb') as f:
        fw = f.read()
    # 计算固件代码区CRC(不含头部4字节)
    crc = zlib.crc32(fw) & 0xFFFFFFFF
    with open(output_bin, 'wb') as f:
        f.write(struct.pack('<I', crc))   # 小端写入CRC
        f.write(fw)

if __name__ == '__main__':
    add_crc_header(sys.argv[1], sys.argv[2])

运行与验证

  1. 生成带CRC头的固件:编译应用程序得到app.bin,执行python gen_crc.py app.bin app_with_crc.bin,将app_with_crc.bin烧录到0x08008000。
  2. 烧录Bootloader:将上述Bootloader代码编译后烧录到0x08000000(需在链接脚本中设置起始地址)。
  3. 正常路径验证:复位后观察程序是否跳转执行应用(如LED闪烁或串口打印)。若跳转,说明CRC匹配。
  4. 篡改检测验证:使用烧录器将app_with_crc.bin中任意一个字节(偏移≥4)修改后重新烧录,复位后应停留在错误处理循环(如LED常亮),不进入应用。

通过标准:正常固件能跳转;篡改一个字节后不能跳转,且错误处理被触发。

关键说明

  • 本例使用软件CRC32,避免依赖硬件CRC外设,便于移植;若MCU自带CRC单元可替换为硬件计算。
  • APP_MAX_SIZE需与实际固件规划一致,实际项目中可在固件头增加长度字段替代固定值。
  • 跳转前必须复位时钟和中断,否则应用可能异常。
Flash 布局与校验流程 Bootloader 0x08000000 CRC 4B 固件代码区 偏移 0x04 ~ 0x10000 0x08008000 0x08008004 读取头部CRC 计算代码区CRC (偏移0x04起) 比较相等? 是 跳转应用 否 → 错误处理 CRC头(4字节) 固件代码区