1. 项目概述
1.1 STM32L4/L4+/G4系列为什么需要代码读取保护
嵌入式开发做到一定阶段,基本都会遇到同一个焦虑:产品发出去,方案被别人抄走怎么办。尤其是用STM32L4、L4+或者G4系列做低功耗、电机控制这类项目,前期投入大量时间调算法、调参数,结果别人用烧录器直接把Flash里的固件读出来,整个产品就等于裸奔。
代码读取保护(RDP,Readout Protection)就是用来干这个事的。它通过配置选项字节,把芯片内部的Flash保护起来,阻止调试接口和第三方工具直接读取固件内容。这个标题里提到的STM32L4、L4+和G4三个系列虽然定位略有差异——L4主打低功耗、G4偏向电机控制和数字电源,但它们的Flash保护机制是同一套体系,都基于STM32通用的RDP Level机制。
我早期做产品时对保护这块不够重视,觉得功能跑通就行。后来有一次做客户定制项目,对方要求出货前必须确认代码无法被读取,才认真把RDP、PCROP、WRP这些安全特性全部过了一遍。说实话,真正理解了之后,才发现保护方案远比想象中灵活,而且配置不当还会把芯片锁死,这里面的坑值得单独写一篇。
1.2 这篇文章能帮你解决什么问题
不管你是正在做量产产品的嵌入式工程师,还是做工业控制、传感器节点、电机驱动这类对固件安全有要求的项目,这篇文章都适合参考。
文章会围绕以下几个方面展开:
- STM32L4、L4+、G4系列上RDP的Level 0/1/2具体含义,不同级别之间如何切换
- 选项字节的编程方法,包括在代码里通过修改Flash实现RDP升级的完整流程
- 调整RDP级别后对调试功能、Flash编程、RAM执行的影响范围
- 如何解除保护,以及解除保护时芯片内部资源的恢复规则
- 实际项目中最容易踩的坑,比如回退Level 1导致芯片锁死、选项字节编程失败等
需要先说清楚的是,RDP只是STM32安全体系里的一道门禁,它能防住大多数常规的调试接口读取,但不是万能的。芯片代码的安全应该是一个纵深体系,RDP配合PCROP(专有代码读保护)、WRP(写保护)、以及外部加密一起用,效果才理想。这篇文章先把RDP这条主线讲透,其他安全特性会穿插提及。
2. 选型与原理分析:为什么是RDP而不是其他保护方式
2.1 STM32内部有哪些保护机制,各自管什么
我刚接触STM32安全特性时,第一反应是:不就是一个保护位吗,打开不就行了。实际上STM32家族的保护设计是分层的,不同机制管的维度完全不同。
- RDP(Readout Protection):控制整个Flash能不能被外部调试接口、BootLoader、以及某些内置固件读取。它管的是“你能不能读出我的代码”。
- WRP(Write Protection):按页对Flash进行写保护,防止运行时错误导致关键代码区被意外改写或擦除,也能防止攻击者向Flash注入恶意代码。它管的是“你能不能往我的代码区写东西”。
- PCROP(Proprietary Code Readout Protection):把某块Flash区域标记为“专有代码区”,这些区域无法通过任何方式被读出,但可以被CPU正常执行。它主要用于保护算法库、密钥表这类核心资产。
- OTP(One-Time Programmable):一次性可编程区域,写进去就永远不能改,通常用来存设备序列号、MAC地址、密钥散列值。
这四个机制里,RDP是总闸门,管的是整个Flash的读取权限;其他机制是在RDP的框架内再做细分控制。实际应用中,RDP Level 1 + PCROP组合是比较常见的做法,既能防止别人读Flash,又能单独保护核心算法。
2.2 RDP的三个Level到底代表什么状态
RDP的核心是一个位于选项字节区域的两字节值(0xAA表示Level 0,0xBB表示Level 1,0xCC表示Level 2),不同级别对应不同的安全状态。这里用生活化的方式理解:
- Level 0(无保护):就像家里大门敞开,调试器、BootLoader都能随意访问Flash和RAM。芯片出厂默认就是这个状态,开发调试阶段也在这个状态下进行。
- Level 1(读保护开启):相当于锁上了家门但没有拉上窗帘。调试接口还能连接芯片,能操作SRAM,也能擦除Flash,但不能读取Flash内容,也不能从RAM执行代码。这是量产产品最常用的级别。
- Level 2(最高级别,不可回退):直接把门焊死,所有外部调试接口完全失效,调试功能永久关闭,而且一旦设置进去就再也回不到Level 0或Level 1。芯片只能通过用户程序正常运行,任何调试手段都无效。
在STM32L4/L4+/G4上,RDP的配置和L0/L1/L2之间的切换路径是有严格限制的,我用一个表格总结一下不同切换操作的后果,方便后面展开讲。
| 切换操作 | 是否允许 | 对Flash内容的影响 |
|---|---|---|
| Level 0 → Level 1 | 允许 | 不影响用户代码,Flash内容保留 |
| Level 1 → Level 0 | 允许,但触发全片擦除 | 用户代码、选项字节全部被擦除 |
| Level 0 → Level 2 | 允许 | 不影响用户代码,但永久锁定调试接口 |
| Level 1 → Level 2 | 允许 | 不影响用户代码,但永久锁定调试接口 |
| Level 2 → Level 0/1 | 禁止 | 不可能,永久锁定 |
需要注意,Level 1 到 Level 0 的解除保护,在STM32L4/L4+/G4系列里会无条件触发整片Flash擦除,包括主Flash区域,这不是bug,而是设计如此,防止有人通过解除保护来读取代码。
2.3 为什么STM32L4/G4上的RDP比老系列更复杂
用过STM32F1系列的人可能觉得RDP很简单,几个选项字节一写就完事。但在L4、L4+、G4上,情况复杂了不少,主要原因是这几个系列引入了更细致的安全管理和更多的受限区域。
L4/G4系列带有TrustZone技术的MCU(例如STM32L5系列)没有混为一谈,L4本身不带TrustZone,但L4/G4在Flash控制器层面做了增强,选项字节数量翻倍,并且多了几个跟RDP状态联动的新安全特性。
比如,在RDP Level 1状态下,STM32L4/G4系列会限制BootRAM中的某些启动模式,并且对系统存储区(System Memory)的BootLoader访问也做了限制。另外,从Level 1回退到Level 0时,不只是Flash被擦除,还会把OTP区域中的部分内容标记为无效(在L4系列的某些型号上,OTP的上层64字节会受到额外限制)。这些细节如果只看参考手册很容易忽略,但实际产品设计时影响非常大。
这也是为什么我建议做L4/G4项目的朋友,不要只看中文参考手册里零散的选项字节章节,而是要结合应用笔记和芯片对应的参考手册安全章节一起读,把RDP、PCROP和WRP三者之间的联动关系理解清楚。
3. RDP状态切换实操:从Level 0到Level 1再到Level 2
3.1 用标准工具调整RDP级别:STM32CubeProgrammer操作演示
在开发调试阶段,最直接的RDP配置方式是用STM32CubeProgrammer的图形界面操作。量产阶段可以利用命令行模式做自动化烧录。我先演示图形界面的流程。
打开STM32CubeProgrammer,连接目标芯片,点击左侧的“Option Bytes”标签页,在“Read Out Protection”栏里能看到当前RDP级别。选择的级别后点击“Apply”即可。
这里特别提醒一点:如果是把一个Level 0的芯片切换到Level 1,点击Apply后软件会执行选项字节编程,Flash中的代码不会被擦除。但是如果你把Level 1的芯片重置为Level 0,会弹出一个警告,提示“This operation will erase the whole Flash”,此时务必确认是否真的需要解除保护,因为一旦执行,不仅代码没了,还无法恢复。
量产时建议用命令行模式,方便集成到产线工具里:
STM32_Programmer_CLI -c port=SWD mode=UR -ob RDP=0xBB上面这行命令通过SWD连接后,把RDP设置为Level 1(0xBB)。产线上还可以配合hex文件烧录、Flash校验等步骤一并完成。如果想让芯片进入Level 2,把0xBB改成0xCC即可。注意Level 2是不可逆操作,执行前必须确认所有调试需求都已经结束。
3.2 在用户代码中通过修改选项字节升级RDP
有些产品需要先烧录一个“引导固件”,由这个固件在特定条件下(比如首次启动时)把RDP从Level 0升级到Level 1或Level 2,然后再跳转到主应用。这种由用户代码修改选项字节的场景,在STM32L4/G4上实现方式有讲究。
先说明选项字节的本质:它存储在Flash的一个独立区域,CPU可以像写普通Flash一样去擦写它。修改选项字节有标准步骤:
static void Flash_OB_Enable(void) { // 解锁Flash控制器 HAL_FLASH_Unlock(); // 解锁选项字节编程 HAL_FLASH_OB_Unlock(); } static void Flash_OB_RDP_Level1(void) { FLASH_OBProgramInitTypeDef ob_config; // 读取当前选项字节配置 HAL_FLASH_OB_GetConfig(&ob_config); // 设置用户取下选项字节 ob_config.OptionType = OPTIONBYTE_USER; ob_config.RDPLevel = OB_RDP_LEVEL_1; // 写入选项字节寄存器 HAL_FLASH_OBProgram(&ob_config); // 触发选项字节加载(需要系统复位后生效) HAL_FLASH_OB_Launch(); }上面代码的核心逻辑是:先解锁Flash和选项字节,然后格式化配置结构体,把RDP级别设置为Level 1,写入后调用Launch触发重新加载。执行完这段代码后,芯片会在内部产生一个系统复位,复位后RDP就变成Level 1了。
在这段代码里有几个容易被忽略的细节:
- 操作结束后,不需要再调用HAL_FLASH_Lock(),因为OB_Launch之后会直接复位,锁不锁意义不大
- 如果是在中断或者RTOS任务里调用,务必确认当前没有其他Flash操作正在执行
- RDP从0升到1不会擦除Flash,所以主应用代码可以提前烧录好
3.3 切换到Level 2的两种典型场景
Level 2的切换通常发生在产品出厂前的最后一步。整体流程和Level 1类似,但有几个额外注意点。
用CubeProgrammer界面操作时,选择Level 2后,软件会提示“Level 2 protection cannot be disabled”,确认后芯片进入最高保护状态。此时SWD调试器还能连上芯片,但访问任何调试寄存器都会报错,连读芯片ID都访问不了(读UID不在其列,通常仍可访问)。
如果你的产品需要在线升级(OTA或UART Bootloader升级),就需要提前考虑好:进入Level 2之后,系统存储区里的BootLoader还能不能用。
在STM32L4/G4上,Level 2状态下,System Memory中的引导加载程序对Flash的读操作被完全禁止,只能通过它执行整片擦除之类的操作。这意味着你无法用官方BootLoader做在线固件升级了。要支持升级,只能在用户代码里自己实现一个跳转到RAM运行的Bootloader,并且在RDP Level 1或Level 2的状态下牺牲一部分RAM来运行解密固件逻辑。
这在实际产品中是一个需要权衡的点:提高安全性往往意味着牺牲一部分可维护性。很多产品会在出厂前烧录Level 2,并且额外预留一个只能写不能读的升级接口,配合PCROP保护区来替换关键算法。
3.4 解除保护:一次不可逆的Flash全片擦除
解除保护(Level 1 → Level 0)的真实行为是:芯片先强制做一次Mass Erase(全片擦除),把用户代码、选项字节全部归零,然后RDP位回到无保护状态。
之所以这样设计,是为了防止恶意攻击者把芯片降级到Level 0后,再用调试器读出原先的固件。全片擦除确保了原先的数据全部消失。
我遇到过几个工程人员在这里栽了跟头。有一个同事在产品测试时通过代码把RDP升到了Level 1,后面调试器连不上,他以为是调试器坏了,后来用CubeProgrammer解保护,弹窗没细看就点了确认,结果Flash全部被擦掉,几个星期的代码没有备份直接没了。
所以这里强烈建议:任何涉及RDP Level 1回退到Level 0的操作之前,先把固件备份好。同时要确认产品不需要保留校准参数、序列号或者运行日志,因为全片擦除会把这些都干掉了。
4. 深入理解保护状态下的行为边界
4.1 RDP Level 1下哪些功能受限
在STM32L4、L4+和G4上,RDP Level 1状态下的行为边界非常关键,尤其是产品里还依赖调试接口或者BootLoader时。
Level 1时,通过调试接口(SWD/JTAG):
- 可以连接芯片,读取CPU寄存器和SRAM内容
- 可以读写外设寄存器
- 可以对Flash执行整片擦除和编程(写入)
- 不能读取Flash内容,包括用户代码区、OTP区域、选项字节区域
- 不能从RAM执行代码(用于防止通过RAM中的恶意代码把Flash内容搬运出来)
也就是说,Level 1下调试器虽然可以擦除和重写Flash,但不能读出内容。这意味着如果你在调试现场不小心把程序跑飞了,可能没法直接查看Flash里固件是什么,只能重新烧录。但从正常产品维护角度看,这个限制可以接受。
另一个被很多人忽略的是:Level 1下通过系统BootLoader启动,也会受到同样限制。如果你尝试用UART Bootloader读取Flash数据,读回来的都是0或者无效数据。但如果只是通过Bootloader擦除和重新编程Flash,是允许的。
4.2 PCROP和WRP与RDP的配合使用
很多项目只开RDP Level 1,以为这就万事大吉。在我看来,如果产品里存有核心算法库、AES密钥或者license校验代码,应该再叠加PCROP,把这个区域保护起来。
PCROP区域在RDP Level 1的保护下,外部调试接口无法读取,CPU执行时可以正常读到指令,但任何读数据总线访问该区域都会被阻止。这就意味着即使攻击者拿了芯片,也拿不到PCROP区域的机器码,只能黑盒观察行为。
同时,PCROP区域还可以和WRP结合,把该区域的写操作也禁止掉,这样连篡改都做不到。典型的布局是:把最核心的算法函数放到PCROP区域内,调用入口暴露在普通Flash区,这样兼顾了代码执行和机密性保护。
配置PCROP需要在选项字节里指定保护区的起始地址和结束地址,并且要求对齐到一定粒度(具体按芯片型号参考手册,通常以256字节为最小对齐单位)。
4.3 L4、L4+和G4在RDP表现上的微小差异
虽然三个系列总体机制一致,但存在一些细微差异,做多平台兼容开发时需要注意。
STM32L4+相比L4,Flash容量更大,PCROP区域的对齐粒度和保护粒度可能不同,具体取决于Flash模块版本。G4系列的Flash模块是另一套设计,它的PCROP支持和L4有一些差异,比如某些G4型号可以在RDP Level 1下设置PCROP,而L4某些早期型号对PCROP区域的起始地址有不同的对齐限制。
这些差异在实际项目中主要影响一件事:选项字节配置代码的复用性。如果你写了一个通用函数用来设置不同型号芯片的PCROP参数,建议在头文件里用宏区分L4和G4,针对不同Flash模块走不同配置路径。我的经验是不要直接依靠CubeMX生成的代码打天下,把选项字节操作的业务逻辑抽取成独立模块,方便在不同型号间移植。
5. 实操环节:选项字节编程的完整流程
5.1 准备工作:查看芯片当前状态与备份固件
在开始任何RDP操作之前,必须确认芯片当前所处的保护级别,以及固件是否有备份。
连接CubeProgrammer后,在左侧导航栏选择“Option Bytes”,可以看到“Read Out Protection”一栏中明确标注当前RDP Level。同时,建议先执行一次“Full Flash Dump”把当前固件保存成bin文件。
操作路径:Memory & File editing界面,读取地址从0x08000000开始,长度设为芯片Flash容量,然后保存为bin文件。这个步骤虽然简单,却非常保命,尤其是当你准备把RDP从Level 0切换到Level 1或更高时,如果后面操作意外失败,至少固件能恢复。
但在做这一步之前要想清楚:如果芯片已经在Level 1,Flash是读不出来的,自然也就无法备份。这种情况只能连接调试器擦除整片Flash,或者烧录新固件。所以固件备份的操作一定要在芯片没有保护或者保护级别很低的时候完成。
5.2 通过代码或命令行设置RDP并验证结果
我建议在产线中使用命令行方式,配合同一个脚本完成烧录、验证、保护三个动作。示例如下:
# 1. 连接芯片并擦除整片Flash STM32_Programmer_CLI -c port=SWD mode=UR -e all # 2. 烧录固件 STM32_Programmer_CLI -c port=SWD mode=UR -w firmware.hex -v # 3. 设置RDP Level 1 STM32_Programmer_CLI -c port=SWD mode=UR -ob RDP=0xBB # 4. 验证RDP升级是否成功 STM32_Programmer_CLI -c port=SWD mode=UR -ob displ第4步会读取选项字节内容,确认RDP的值是0xBB,即Level 1。如果你在产品中还需要设置PCROP,可以在第3步的同一个参数里通过URLike方式一并设置。
代码里的验证方式也有必要掌握:在用户程序中读取选项字节区域的值,判断RDP当前状态。STM32的标准方式是通过Flash接口寄存器读取:
static uint8_t Check_RDP_Level(void) { uint32_t rdp_val = 0; // 读取选项字节中的RDP值 rdp_val = (FLASH->OPTR & FLASH_OPTR_RDP) >> FLASH_OPTR_RDP_Pos; if (rdp_val == 0xAA) { return 0; // Level 0 } else if (rdp_val == 0xBB) { return 1; // Level 1 } else if (rdp_val == 0xCC) { return 2; // Level 2 } return 0xFF; // 异常值 }5.3 RDP配置过程中常见的几个失败原因
在真实项目中,选项字节编程失败的案例并不罕见,我整理几个高发性问题。
第一个原因是Flash和选项字节没有正确解锁。如果调用HAL_FLASH_OB_Unlock()之前还没有调用HAL_FLASH_Unlock(),或者操作顺序反了,编程会直接报错。在ST HAL库里,必须先解锁Flash控制器,然后解锁选项字节控制器。
第二个原因是尝试在Level 1下读取Flash内容却被调试器挂起。这个行为本身不是故障,而是保护机制在工作。如果你在调试时发现读变量或者看反汇编窗口全是0,不要着急,先确认当前芯片是否处于Level 1。
第三个原因比较隐蔽:选项字节编程完成后的OB_Launch必须在Flash操作停止后执行,如果此时还有其他DMA正在访问Flash,或者Cache没有关闭,可能引起总线错误。稳妥的做法是在执行OB_Launch前,先关闭所有中断和DMA传输。
5.4 如何通过RAM中运行的代码绕过Flash读取限制
在某些安全评估场景下,人们会讨论一个著名的攻击路径:在RDP Level 1下,调试器不能读Flash,但可以访问SRAM,如果能向SRAM注入一段代码并让CPU执行,就能把Flash内容搬运出来。STM32的应对策略是:Level 1下禁止从RAM执行代码。
在L4/G4上,调试接口一旦检测到试图从RAM运行代码,会触发一个HardFault或者调试端口错误。如果你在安全设计时,需要确认自己的产品是否真的禁用了RAM执行,可以通过代码尝试从SRAM跳转执行一个空函数,观察是否触发异常。
这里要说明的是,这种检查主要用于评估自己的产品防护强度,做安全产品设计时的自我验证。做正常功能开发时,没有必要刻意尝试去绕过保护。
6. 项目实战经验与问题排查
6.1 踩坑实录:把RDP搞到Level 2后连不上芯片
我自己第一次把RDP升到Level 2时,其实不太清楚后果,结果芯片连上调试器后,CubeProgrammer直接弹出“Device is in RDP Level 2, debug disabled”,所有操作按钮全部变灰,那一刻才意识到问题的严重性。
后面查文档才明白,Level 2状态下,SWD调试接口被永久禁用,不管你怎么复位、上电,调试器都连不上。解决方式只有一个:把芯片完全擦除(Mass Erase)是不能解决问题的,因为Mass Erase本身需要调试接口访问选项字节,而在Level 2下,不但读Flash被禁止,连复位并进入Debug模式也被禁止。
所以如果你的产品用了Level 2,一旦进入这个状态,想再用常规方式重新烧录基本不可能。唯一的出路是使用芯片的BootLoader或者出厂时预留在用户代码中的升级通道(比如UART/I2C/SPI),并且你的用户代码必须还没有被破坏。某些型号可以通过Boot引脚强制进入System Memory,但System Memory里的代码在Level 2下也不能读Flash,只能擦除或者更新普通区域,不能改变RDP状态。
这个经历给我们的教训是:Level 2必须在产品功能全部验证完毕后,作为出厂前最后一步操作。在产品开发阶段绝对不要碰Level 2,否则会严重影响开发调试效率。
6.2 遇到“Option Bytes Programming Error”怎么处理
选项字节编程失败时,CubeProgrammer通常会提示“Programming error”或“Verification error”。常见的根源包括:
- 芯片供电电压不稳。选项字节编程对供电要求比较严格,如果供电电压低于最低编程电压,就会失败
- 目标芯片处于低功耗模式,没有正常响应调试请求
- 选项字节操作被应用代码中的Flash操作打断
- 复位引脚被外部电路拉低,导致编程过程中芯片复位
排查方法,先用万用表确认供电电压,再用示波器看复位引脚是否有毛刺。如果软件层面刚执行完Flash擦写,等待一段时间后再执行选项字节编程。另外,如果芯片内部看门狗在跑,需要在编程前暂停看门狗,否则到点复位也会导致编程失败。
6.3 产品量产时的RDP与烧录流程推荐
量产流程里,RDP配置的常见做法有两种:一种是烧录完成后,由产线工具直接设置RDP;另一种是产品首次启动时,由固件自行把RDP升级到目标级别。两者各有好处。
产线工具设置的优点是流程简单、统一,一旦设置到位,后续调试接口无法访问,可防止产品在运输过程中被外部篡改。缺点是如果固件需要返工,整片擦除重新烧录的代价比较大,但因为解保护会擦除全片,所以产线返工要有完整的固件和配置备份。
固件自行升级RDP的优点是产线不用多一步操作,节省时间,而且可以在最终启动前检查各项配置是否有效,确保万无一失。缺点是用户代码必须能可靠地在目标状态运行,否则RDP升到Level 1或2后如果主应用跑飞,现场恢复难度大。
我的建议是:低功耗传感器等小容量产品,可以通过产线工具配置RDP Level 1,流程最简单,返工擦除也不心疼。对于电机控制、数字电源、工业控制器这类价值高的产品,建议在固件逻辑里设置RDP Level 1,并在一个独立配置页记录保护状态,后续升级时可以通过专用的OTA通道绕过调试接口来做。
6.4 从开发到量产:RDP状态机的完整生命周期管理
整个项目从开发到量产,RDP状态机应该按照下面这个节奏推进:
开发阶段:保持Level 0,方便调试和在线仿真。此时不要在生产代码里嵌入任何形式的RDP升级代码,否则调试过程中如果意外触发RDP升级,麻烦无穷。
预量产阶段:固件功能稳定后,把目标板调整到Level 1,验证Level 1下对功能的影响。重点验证:启动流程是否正常、BootLoader是否能正常工作、升级通道是否可用、各外设DMA和中断是否正常。
量产阶段:产线烧录完成后,设置RDP Level 1。如果产品定位为高安全级别,出厂前再升到Level 2。从Level 1升到Level 2不影响用户代码,所以可以最后一步执行。
售后阶段:如果要返修,按照正常流程先解保护(Level 1)会擦除Flash,需要重新烧录固件。如果设备处于Level 2,建议不尝试解保护,而是直接更换主控芯片。
按照这套逻辑管理RDP状态,可以最大限度避免开发阶段的效率损失和量产阶段的安全风险冲突。
7. 安全设计经验与扩展建议
7.1 RDP不是万能的,别指望它挡住一切
在安全领域有一个常识:任何基于芯片内部的保护机制,理论上都能被物理攻击绕过。RDP能防住的是软件层面的平庸攻击者,对于能够在实验室里做芯片开盖、FIB切割、电压毛刺注入的攻击者,再高的RDP级别也挡不住。
因此,在做产品安全设计时,建议把更多精力放在业务逻辑加密和协议设计上。比如固件中的关键数据(密钥、证书、license)与设备唯一ID绑定,这样即使代码被完整提取,由于缺失运行时参与计算的物理熵源,也难以被迁移到另一颗芯片上运行。
RDP Level 1和PCROP的意义在于:提高攻击门槛,让攻击者觉得“破解这个产品的成本超过了产品本身的价值”。把握好这个尺度,安全设计才是理性的,而不是盲目堆砌保护。
7.2 L4/G4上做安全引导链(Secure Boot)的思路
如果产品有较高的安全需求,可以把RDP和“安全引导”结合起来。思路是:在芯片出厂时,通过烧录器写入一段自定义引导加解密代码,这段代码放在受保护的区域中,然后立即把RDP升到Level 1或2。
系统上电后,CPU从主Flash启动,执行引导代码。引导代码首先校验应用固件的签名,确认无误后跳转执行应用。整个过程中,调试接口被关闭,RAM执行被禁止,外部无法介入引导流程,固件只能在受信任的启动路径上运行。
此时,如果攻击者试图修改Flash里的应用固件,引导校验会发现签名不匹配,系统拒绝启动。如果攻击者试图读Flash,RDP Level 1或2会挡住调试接口访问。这就构成了一道相对完整的安全启动链。
7.3 固件升级时的RDP兼容性考虑
如果你的产品需要远程升级,务必在设计初期就考虑RDP的影响。在RDP Level 1下,由用户BootLoader接收升级包、写入Flash是允许的,前提是BootLoader本身能正常运行,并且能访问Flash控制器。但在Level 2下,任何通过SWD调试接口访问Flash的行为都被禁止,你想用外部烧录器刷固件也就不可能了。
所以在高安全产品上,一个常见实践是:使用RDP Level 1,搭配可靠的应用自升级机制;同时把关键密钥放到PCROP区域,防止通过读取Flash拿到密钥。这样既保留了远程升级的灵活性,又不会因为密钥泄露导致整个加密体系崩盘。
如果你确实需要Level 2,就必须在BootLoader里实现完整的加密下载与FLASH写入流程,同时预留一个“升级模式”的触发开关(比如按键、通信指令),并且要确保BootLoader本身不使用保护区域之外的RAM来存放解密临时数据,防止通过调试接口监听RAM中的数据。
7.4 最后分享一个调试经验
在STM32L4/G4上调试RDP相关功能,建议准备两颗芯片:一颗专门用来做RDP各种级别切换的破坏性实验,另一颗保留在Level 0用于日常功能开发。这样即使实验板被锁死或者全片擦除,也不会影响主线的开发进度。
另外,养成每次做保护操作前导出当前Flash镜像和选项字节配置的好习惯。CubeProgrammer的“Option Bytes”页面支持导出当前配置为XML或文本文件,把每个阶段的配置留档。以后万一遇到问题,可以对照历史配置快速判断是哪里改了导致异常。
RDP这块的内容,说实话每个系列芯片的参考手册里都有讲,但真正做产品时踩过的坑和约束条件,只有实操过才能体会。希望这篇笔记能帮你把STM32L4、L4+和G4上代码读取保护这件事一次做对,少走弯路。