前言

  本文使用的工程环境为:CLion、CubeMX、STM32G431
  一个量产产品,出厂时必定会关闭调试接口如SWD、JTAG(如提升RDP等级)以避免重要的程序泄露或被注入漏洞,但出厂后在它的全生命周期中仍有升级程序的需要,这就要求做好bootloader与OTA的工作。

  • BL:固件的完整性校验(CRC32、SHA256哈希),验权(RSA)、A/B分区切换
  • OTA:通过标准通信通道,如UART、BLE、CAN/LIN刷写固件

  仓库地址:https://github.com/Dikle-OvO/BootLoaderTest

基础

XIP机制

  XIP(eXecute In Place,就地执行),是指CPU可以直接在存储介质如FLASH中取指的技术,这种技术在MCU中比较常见,通用Linux系统(包括内核、BL)几乎不会用到XIP,必须搬运到RAM,除非使用squashfs,XIP模式内核直接挂载读取,否则都要使用ram(Linux的页缓存机制,题外话:写入也有相关的机制,这也是sync命令的作用)

FLASH零等待

  指MCU储存访问的理想状态,特指CPU访问储存介质时无需插入等待周期,通过预取指缓冲实现,接近RAM的运行水平,是XIP模式的天花板。
  反之,若 Flash XIP 是 1 等待,意味着每读取一次指令要多等 1 个时钟周期,480MHz 主频的实际等效性能会降到 240MHz。

无校验跳转

  先从最简单的无校验跳转开始,新建一个led闪烁函数,BL程序函数如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//1.定义APP地址
uint32_t APP_ADDRESS=0x08008000;
typedef void (*pFunction) (void);
void JumpToApplication(void) {
if (((*(__IO uint32_t*)APP_ADDRESS) & 0x2FFE0000) == 0x20000000) {
//2.将中断向量长重定向到应用程序地址
SCB->VTOR=APP_ADDRESS;
//3.获取应用程序的栈顶地址和复位处理函数地址
pFunction app_reset_handler =(pFunction) (*(__IO uint32_t*)(APP_ADDRESS + 4));
__set_MSP(*(__IO uint32_t*)APP_ADDRESS);
//4.跳转到应用程序
app_reset_handler();
}
}

  将BL程序烧录后,修改闪灯频率作为app以表示运行程序的不一样,而后在工程的ld链接文件中,修改Flash的位置为0x08008000(即上述BL配置的APP_ADDRESS),最后修改Keil或者CubeMX Programmer的烧录地址也为APP_ADDRESS即可。

解析

  CPU上电后默认从0地址启动,存储器重映射会根据boot选择将特定的地址映射过去,如用户空间0x0800 0000,BL程序只是判断了一下栈顶地址是否有效(在RAM空间中),有效即跳转,这是个非常简陋的校验。

完整性校验

SHA-256

  SHA-256是 SHA-2 家族的 256 位密码散列函数,由 NSA 设计、NIST 于 2001 年标准化(FIPS PUB 180-2),用于替代存在安全缺陷的 SHA-1,能将任意长度输入转为 256 位(32 字节,64 个十六进制字符)的消息摘要.

CRC

  CRC循环冗余校验是一种基于多项式除法的哈希函数

鉴权

RSA

  RSA非对称加密

综合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
static uint8_t sign_data[256];

bool ota_check_download_signature(void)
{
int ret = 0;
bool success = true;
uint8_t hash[32] = {0};
const uint8_t cnt = ota_ctx.segment_count;

for (uint8_t i = 0; i < cnt; ++i) {
if (BLOCK_TYPE_APP != ota_ctx.data_segments[i].belong_block_type) {
continue;
}

uint32_t addr = ota_get_phy_addr(i);
ret = mbedtls_sha256((const unsigned char *)addr, ota_ctx.data_segments[i].memory_size, hash, 0);
if (0 != ret) {
LOG_ERR("ota data-segment sha256 failed: %d, addr=0x%x, size=0x%x", ret,
ota_ctx.data_segments[i].memory_addr, ota_ctx.data_segments[i].memory_size);
success = false;
break;
}

/* check signature block number is valid */
if (i >= ota_ctx.sign_count) {
LOG_ERR("ota sign block count failed: %d < %d", i, ota_ctx.sign_count);
success = false;
break;
}

if (!rsa_signature_verify(ota_ctx.signatures[i], SIGNATURE_DATA_SIZE, hash, sizeof(hash))) {
LOG_ERR("ota data-segment sign failed: addr=0x%x, size=0x%x",
ota_ctx.data_segments[i].memory_addr, ota_ctx.data_segments[i].memory_size);
success = false;
break;
}
}

return success;
}