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])
运行与验证
- 生成带CRC头的固件:编译应用程序得到
app.bin,执行python gen_crc.py app.bin app_with_crc.bin,将app_with_crc.bin烧录到0x08008000。 - 烧录Bootloader:将上述Bootloader代码编译后烧录到
0x08000000(需在链接脚本中设置起始地址)。 - 正常路径验证:复位后观察程序是否跳转执行应用(如LED闪烁或串口打印)。若跳转,说明CRC匹配。
- 篡改检测验证:使用烧录器将
app_with_crc.bin中任意一个字节(偏移≥4)修改后重新烧录,复位后应停留在错误处理循环(如LED常亮),不进入应用。
通过标准:正常固件能跳转;篡改一个字节后不能跳转,且错误处理被触发。
关键说明
- 本例使用软件CRC32,避免依赖硬件CRC外设,便于移植;若MCU自带CRC单元可替换为硬件计算。
APP_MAX_SIZE需与实际固件规划一致,实际项目中可在固件头增加长度字段替代固定值。- 跳转前必须复位时钟和中断,否则应用可能异常。