1. 固件镜像哈希校验与启动完整性验证
任务说明
本案例面向STM32F4系列MCU,实现固件启动时的完整性自校验:在跳转到主应用程序前,由Bootloader计算固件镜像的SHA-256哈希值,与烧录时存储在独立Flash扇区中的参考哈希比对。若一致则跳转执行,否则停止启动并进入错误处理流程。
环境与依赖
- 硬件:STM32F407开发板(或兼容F4系列)
- 工具链:arm-none-eabi-gcc + OpenOCD 或 ST-Link
- 固件库:STM32F4xx HAL 驱动(仅需Flash与CRC外设)
- 哈希实现:使用STM32硬件CRC32作为轻量校验(SHA-256需软件实现,本案例为聚焦启动流程,采用CRC32演示,实际产品可替换为SHA-256软件库)
实现方案
Bootloader位于0x08000000,应用程序位于0x08020000。参考哈希值在烧录时由主机工具计算并写入0x0801FF00(最后一个Flash扇区末尾)。启动时Bootloader读取应用程序区全部内容计算CRC32,与参考值比较。
关键完整代码
// bootloader.c - 固件完整性校验核心逻辑
#include "stm32f4xx_hal.h"
#include <string.h>
#define APP_BASE_ADDR 0x08020000 // 应用程序起始地址
#define APP_MAX_SIZE 0x0001FF00 // 应用程序最大128KB-256B
#define HASH_REF_ADDR 0x0801FF00 // 参考哈希存储地址(最后扇区)
#define HASH_REF_SIZE 4 // CRC32为4字节
// 计算指定Flash区域的CRC32
uint32_t calc_flash_crc(uint32_t start_addr, uint32_t length) {
uint32_t crc = 0;
// 启用CRC外设
__HAL_RCC_CRC_CLK_ENABLE();
CRC->CR = CRC_CR_RESET;
// 按32位字读取Flash内容并送入CRC计算器
uint32_t word_count = length / 4;
uint32_t *flash_ptr = (uint32_t *)start_addr;
for (uint32_t i = 0; i < word_count; i++) {
CRC->DR = flash_ptr[i];
}
crc = CRC->DR;
return crc;
}
// 从Flash读取参考哈希值
uint32_t read_ref_hash(void) {
return *(__IO uint32_t *)HASH_REF_ADDR;
}
// 启动完整性校验主函数
int verify_firmware_integrity(void) {
uint32_t calc_hash = calc_flash_crc(APP_BASE_ADDR, APP_MAX_SIZE);
uint32_t ref_hash = read_ref_hash();
if (calc_hash == ref_hash) {
return 0; // 校验通过
} else {
return -1; // 校验失败
}
}
// 跳转到应用程序
void jump_to_app(void) {
uint32_t app_stack = *(volatile uint32_t *)APP_BASE_ADDR;
void (*app_reset)(void) = (void (*)(void))(*(volatile uint32_t *)(APP_BASE_ADDR + 4));
// 设置主栈指针并跳转
__set_MSP(app_stack);
app_reset();
}
int main(void) {
HAL_Init();
if (verify_firmware_integrity() == 0) {
jump_to_app();
} else {
// 校验失败:进入错误处理(LED闪烁或死循环)
while (1) {
// 错误指示代码
}
}
}
参考哈希烧录工具(主机端)
# 使用Python生成参考哈希并写入指定地址
python3 -c "
import struct, zlib
# 读取应用程序二进制
with open('app.bin', 'rb') as f:
data = f.read()
# 计算CRC32
crc = zlib.crc32(data) & 0xFFFFFFFF
# 生成烧录脚本片段(通过OpenOCD写入)
print(f'参考CRC32: 0x{crc:08X}')
print(f'烧录命令: flash write_bank 0 0x0801FF00 {{0x{crc:08X}}}')
"
链接脚本配置
/* STM32F407ZGT6 - Bootloader链接脚本关键段 */
MEMORY
{
FLASH_BOOT (rx) : ORIGIN = 0x08000000, LENGTH = 128K
FLASH_APP (rx) : ORIGIN = 0x08020000, LENGTH = 896K
FLASH_REF (r) : ORIGIN = 0x0801FF00, LENGTH = 256
RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 128K
}
/* 应用程序需在0x08020000处编译,其向量表偏移需设置 */
/* 应用程序工程需添加: SCB->VTOR = 0x08020000; */
运行与验证
- 编译Bootloader:
make生成 bootloader.bin,烧录至0x08000000 - 编译应用程序:设置向量表偏移后编译,生成 app.bin,烧录至0x08020000
- 写入参考哈希:使用上述Python脚本计算CRC32,通过OpenOCD写入0x0801FF00
- 验证通过条件:复位后程序跳转至应用程序正常运行;修改app.bin任意字节后重新烧录,复位后Bootloader停留在错误处理循环(LED不闪烁或特定闪烁模式),不进入应用程序
验证判定标准
- 原始固件启动:程序正常进入应用,外设工作正常
- 篡改固件启动:程序不进入应用,错误指示生效
- 参考哈希值存储位置与计算范围一致(APP_MAX_SIZE需与实际应用大小匹配)
说明:本案例使用CRC32作为教学演示,实际产品建议替换为SHA-256等密码学安全哈希。替换时仅需修改
calc_flash_crc函数内部实现,并相应调整HASH_REF_SIZE为32字节。