🛡️ 第八层:安全机制与远程升级
🔹 嵌入式系统安全基础
- 威胁模型分析
- 物理攻击:
- 探针访问调试接口(JTAG/SWD)读取 Flash 内容。
- 电压 / 时钟干扰导致程序异常(故障注入攻击)。
- 网络攻击:
- 中间人攻击(MITM)篡改通信数据。
- 恶意固件注入(利用未加密 OTA 通道)。
- 软件攻击:
- 缓冲区溢出执行恶意代码。
- 逆向工程获取算法逻辑(如加密密钥)。
- 安全设计原则
- 最小权限原则:
每个组件仅拥有完成任务所需的最小权限(如 MPU 配置)。
- 防御纵深:
多层次安全机制(如安全启动 + 通信加密 + 运行时防护)。
- 故障安全:
系统在异常情况下自动进入安全状态(如看门狗复位)。
🔹 安全启动(Secure Boot)
保证启动时加载的固件是可信的
- 基本原理
BootROM → 加载并验证一级Bootloader → 加载并验证二级Bootloader → 加载并验证应用固件
- 信任链传递:
每个阶段只信任经过上一阶段验证的代码。 2. 数字签名验证流程
// 简化的签名验证伪代码
bool VerifyFirmwareSignature(uint8_t *firmware, uint32_t size, uint8_t *signature) {
// 1. 从OTP读取可信根公钥
const uint8_t *trusted_public_key = GetTrustedPublicKey();
// 2. 计算固件哈希值
uint8_t calculated_hash[32];
SHA256(firmware, size, calculated_hash);
// 3. 使用公钥解密签名获取原始哈希
uint8_t decrypted_hash[32];
RSA_PKCS1_Verify(trusted_public_key, signature, decrypted_hash);
// 4. 比较哈希值
return (memcmp(calculated_hash, decrypted_hash, 32) == 0);
}
- STM32 Secure Boot 实现
- 选项字节配置:
// 启用读保护(RDP)
HAL_FLASH_OB_Unlock();
FLASH_OBProgramInitTypeDef obInit = {0};
obInit.OptionType = OPTIONBYTE_RDP;
obInit.RDPLevel = OB_RDP_LEVEL_1; // 禁用调试接口
HAL_FLASHEx_OBProgram(&obInit);
HAL_FLASH_OB_Lock();
- TrustZone 配置(适用于 STM32L5 等支持型号):
// 配置安全/非安全区域
MPU_Region_InitTypeDef MPU_InitStruct = {0};
// 配置SRAM为安全区域
MPU_InitStruct.Number = MPU_REGION_0;
MPU_InitStruct.BaseAddress = 0x20000000;
MPU_InitStruct.Size = MPU_REGION_SIZE_512KB;
MPU_InitStruct.SubRegionDisable = 0x00;
MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL0;
MPU_InitStruct.AccessPermission = MPU_REGION_FULL_ACCESS;
MPU_InitStruct.DisableExec = DISABLE;
MPU_InitStruct.IsShareable = ENABLE;
MPU_InitStruct.IsCacheable = DISABLE;
MPU_InitStruct.IsBufferable = DISABLE;
HAL_MPU_ConfigRegion(&MPU_InitStruct);
🔹 固件加密与防逆向
- AES 加密固件,防止泄露源码逻辑
- 加密流程:
- 开发阶段:使用工具链(如 GCC 插件)加密固件。
- 部署阶段:Bootloader 解密后加载到 RAM 执行。
- 密钥管理:
- 主密钥存储在 OTP(一次性可编程)区域。
- 会话密钥通过主密钥派生(如 AES-KDF)
-
Flash 读保护(RDP)
RDP 级别 保护效果 可逆性 Level 0 无保护(默认) 是 Level 1 禁止调试接口,Flash 只能运行不能读取 降级会擦除所有 Flash Level 2 永久禁止调试接口和 Flash 读取 不可逆 -
代码混淆技术
- 控制流平坦化:
将线性代码转换为基于状态机的结构,增加逆向难度。
- 指令替换:
用等效指令序列替换关键操作(如a+b替换为a-(-b))。
🔹 权限隔离与防护
- MPU(内存保护单元)配置
// 配置MPU保护关键数据区
void ConfigureMPU(void) {
// 使能MPU
HAL_MPU_Enable(MPU_PRIVILEGED_DEFAULT);
// 配置区域0保护关键代码区
MPU_Region_InitTypeDef MPU_InitStruct = {0};
MPU_InitStruct.Number = MPU_REGION_0;
MPU_InitStruct.BaseAddress = 0x08000000; // Flash起始地址
MPU_InitStruct.Size = MPU_REGION_SIZE_128KB;
MPU_InitStruct.SubRegionDisable = 0x00;
MPU_InitStruct.TypeExtField = MPU_TEX_LEVEL0;
MPU_InitStruct.AccessPermission = MPU_REGION_PRIV_RW_URO; // 特权可读写,用户只读
MPU_InitStruct.DisableExec = DISABLE;
MPU_InitStruct.IsShareable = DISABLE;
MPU_InitStruct.IsCacheable = DISABLE;
MPU_InitStruct.IsBufferable = DISABLE;
HAL_MPU_ConfigRegion(&MPU_InitStruct);
}
- TrustZone 安全域隔离
-
安全资产分类:
类别 示例 存储位置 密钥 TLS 私钥、加密密钥 安全 SRAM 敏感算法 密码验证、加密函数 安全代码区 安全服务 OTA 签名验证、证书管理 安全任务 -
安全 / 非安全通信:
// 从非安全代码调用安全服务
__attribute__((section(".nonsecure_call")))
uint32_t SecureService_Call(uint32_t service_id, uint32_t param1, uint32_t param2) {
// 通过SVC指令切换到安全模式
__asm("SVC #0");
// 返回值通过R0传递
}
🔹 OTA(Over-the-Air)升级机制
支持远程更新嵌入式系统的固件版本
- 双分区升级架构
Flash布局:
+-------------------+ 0x08000000
| Bootloader |
+-------------------+ 0x08010000
| Application Slot A|
+-------------------+ 0x08040000
| Application Slot B|
+-------------------+ 0x08070000
| Configuration Area|
+-------------------+
- 升级状态机实现
typedef enum {
OTA_IDLE, // 空闲状态
OTA_CHECKING, // 检查更新
OTA_DOWNLOADING, // 下载中
OTA_VERIFYING, // 校验中
OTA_READY, // 准备重启
OTA_UPGRADING, // 升级中
OTA_FAILED // 升级失败
} OTA_State_t;
// OTA状态机处理函数
void OTA_Process(void) {
switch (ota_state) {
case OTA_IDLE:
if (check_update_flag) {
ota_state = OTA_CHECKING;
vCheckForUpdate();
}
break;
case OTA_DOWNLOADING:
if (download_complete) {
ota_state = OTA_VERIFYING;
vVerifyFirmware();
} else if (download_error) {
ota_state = OTA_FAILED;
vHandleError(DOWNLOAD_ERROR);
}
break;
// 其他状态处理...
}
}
- 失败回滚机制
// 启动时验证应用完整性
bool ValidateApplication(uint32_t start_address) {
// 检查向量表签名
uint32_t *vector_table = (uint32_t *)start_address;
if (vector_table[0] == 0xFFFFFFFF) { // 检查栈顶指针是否有效
return false;
}
// 计算应用哈希并验证
uint8_t calculated_hash[32];
SHA256((uint8_t *)start_address, APPLICATION_SIZE, calculated_hash);
// 从配置区获取预期哈希
uint8_t *expected_hash = GetExpectedHash();
return (memcmp(calculated_hash, expected_hash, 32) == 0);
}
// 主程序
int main(void) {
// 初始化硬件
HAL_Init();
SystemClock_Config();
// 检查主应用是否有效
if (ValidateApplication(APPLICATION_SLOT_A_ADDRESS)) {
// 跳转到主应用
JumpToApplication(APPLICATION_SLOT_A_ADDRESS);
} else if (ValidateApplication(APPLICATION_SLOT_B_ADDRESS)) {
// 主应用无效,尝试从备份应用启动
JumpToApplication(APPLICATION_SLOT_B_ADDRESS);
} else {
// 两个应用都无效,进入恢复模式
EnterRecoveryMode();
}
}
✅ OTA 流程核心步骤
- 检查版本更新(HTTP/MQTT 下载 manifest)
- 下载固件(二进制)
- 存储到备份区(Backup Slot)
- 校验 CRC/Hash / 签名
- 设置 Bootloader 标志位并重启
- Bootloader 引导进入新固件
- 若失败则回滚(Fail-safe 机制)
✅ 常用升级协议
- HTTP / HTTPS
- MQTT + Base64 二进制块传输
- CoAP(轻量级)
🔹 Bootloader 开发建议
- 通用功能:下载、校验、重启、回滚
- 支持双分区升级(Slot A / Slot B)
- 防止电量中断、写失败后的砖机风险
- 可设置升级标志位(Upgrade Flag)
🔹 安全通信实践
- TLS 握手优化
- 预共享密钥(PSK)模式:
减少证书验证开销,适合资源受限设备。
// mbed TLS配置PSK
mbedtls_ssl_config_set_psk(&ssl_conf,
psk, // 预共享密钥
psk_length,
identity, // 身份标识
strlen(identity));
- 证书管理方案
- 证书存储:
- 根证书存储在安全 Flash 区域。
- 设备证书通过安全通道动态更新。
- 证书验证:
// 验证服务器证书链
int verify_cert(void *data, mbedtls_x509_crt *crt, int depth, uint32_t *flags) {
// 检查证书有效期
if (mbedtls_x509_crt_check_validity(crt, time(NULL)) != 0) {
return MBEDTLS_ERR_X509_CERT_VERIFY_FAILED;
}
// 检查证书颁发者
if (!mbedtls_x509_crt_verify(crt, trusted_certs, NULL, NULL, flags, NULL, NULL)) {
return MBEDTLS_ERR_X509_CERT_VERIFY_FAILED;
}
return 0;
}
🔹 安全测试
- 固件逆向分析
- 工具链:
- Ghidra:反编译二进制文件,生成 C 语言伪代码。
- IDA Pro:专业逆向工程工具,支持 ARM 架构。
- 防御措施:
- 固件加密:使用 AES-256 加密整个固件。
- 反调试机制:检测调试接口是否被连接。
// 检测SWD/JTAG调试接口
bool IsDebuggerAttached(void) {
// 读取DBGMCU_IDCODE寄存器
uint32_t idcode = DBGMCU->IDCODE;
// 检查调试使能位
return ((DBGMCU->CR & (DBGMCU_CR_DBG_SLEEP | DBGMCU_CR_DBG_STOP | DBGMCU_CR_DBG_STANDBY)) != 0);
}
- 侧信道攻击防护
- 电源分析攻击:
通过测量设备功耗分析加密密钥。 - 防护措施:
常量时间实现:避免条件分支依赖密钥值。
// 常量时间比较(防止时序攻击)
bool ConstantTimeCompare(const uint8_t *a, const uint8_t *b, size_t len) {
uint8_t result = 0;
for (size_t i = 0; i < len; i++) {
result |= a[i] ^ b[i];
}
return (result == 0);
}
面试高频问题
- 安全启动与普通启动的区别:
安全启动通过数字签名验证每个启动阶段的代码,确保只运行可信固件;普通启动无条件执行存储的代码。
- 如何实现 OTA 升级的原子性:
使用双分区设计,升级前备份当前固件,验证新固件通过后再擦除旧固件;设置标志位记录升级状态,异常时可回滚。
- TLS 握手过程中客户端如何验证服务器证书:
客户端使用内置根证书验证服务器证书的签名链,检查证书有效期、域名匹配和吊销状态。
- MPU 与 TrustZone 的区别:
MPU 提供内存区域访问控制,保护不同数据段;TrustZone 在硬件层面划分安全 / 非安全区域,实现更彻底的隔离。