十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

STM32全方位安全防护:RDP、PCROP与固件加密防抄板实战

STM32全方位安全防护:RDP、PCROP与固件加密防抄板实战 最近几年做嵌入式开发越来越多人开始意识到一个问题STM32 这类微控制器早已不是“藏在板卡里没人关心”的小芯片而是大量智能硬件、工业设备、车用电子、IoT 终端的大脑。可现实是很多开发者把精力全放在功能实现上安全意识还停留在“加个密码就行”的层面。我曾经见过一款量产设备整个固件没有任何保护对方用 ST-LINK Utility 直接读出 Flash 内容换个壳就成了“自主研发”。这篇文章会把 STM32 微控制器安全这件事完整拆开芯片提供了哪些安全机制、每种机制解决什么问题、怎么在 CubeProgrammer 里亲手配置、通信侧如何做加密认证以及产线烧录时怎么防泄密。内容主要面向正在把样机往产品方向推的 STM32 开发者也适合刚接触嵌入式安全、想系统了解“微控制器安全到底指什么”的工程师。1. 先搞清楚你的 MCU 到底在防谁1.1 三个真实的“翻车”场景看完你就紧张了第一个场景是抄板。早期我做一款仪表类产品主控是 STM32F103C8T6当时为了调试方便读保护一直没开。后来发现市场上出现了一模一样的产品连屏幕显示顺序都一样。对方是怎么做到的很简单拆机用 ST-Link 连上 SWD 口ST-LINK Utility 点一下“Read”整个固件就出来了。芯片本身没有做任何防护相当于大门敞开着。第二个场景是提固件。有些工程师以为只要把调试接口禁用比如复用 SWD 引脚为 GPIO固件就安全了。这个想法只对了一半。禁用调试口只是让普通调试器连不上但如果芯片支持从系统存储器启动也就是内置 bootloader攻击者可以把 BOOT0 拉高然后通过 USART 或 USB 与内置 bootloader 通信照样能把 Flash 内容读出来。你关掉了前门但后门还开着。第三个场景是伪造节点。在一个主从结构的系统里主机通过 RS-485 轮询各个从机从机上传数据。如果通信报文是明文、没有任何身份认证攻击者只需要在总线上挂一个单片机监听一段报文就能伪造一个从机向主机发送假数据。我见过不少系统就是这么被“攻破”的不是靠高深漏洞而是靠最简单的“没人做认证”。这三个场景说明一个问题STM32 的安全不是一个开关而是一套组合策略。防谁、防到什么程度决定了你要用哪些功能。1.2 建立威胁模型想清楚“防什么”再决定“怎么防”做安全最忌讳的就是“什么都想防最后什么没防住”。你可以花两分钟先问自己四个问题我的设备被拆开后对方最想得到什么是固件代码、算法、还是敏感数据对方有没有可能修改硬件电路比如换一颗芯片、改一下启动模式我的设备与外界通信时最怕被窃听、篡改、还是伪造如果设备被完全控制损失有多大这四个问题分别对应四个防护方向固件保密、防破解、通信安全、业务安全。大多数低成本产品核心诉求是“固件不能被轻易抄走”那你要重点做的是读保护、写保护、PCROP外加把调试口和 bootloader 入口管好。如果是联网设备还要考虑密钥管理和安全启动。如果是工业控制节点那通信报文的完整性和身份认证优先级更高。我见过很多团队一上来就堆“高级功能”买带 TrustZone 的高端芯片却连最基础的 RDP读保护都没开。这完全搞反了。安全防护就像木桶短板决定整体水平。先用最简单的机制把短板补上再根据实际威胁决定要不要上更重的方案。1.3 安全不是单点功能而是“短板效应”STM32 参考手册里安全相关内容分散在 Flash、电源控制、系统存储器、加密引擎等好几个章节。很多人只记住了“把 RDP 设成 Level 1”然后以为万事大吉实际上漏洞往往出在别处。举个例子。你把 RDP 设成了 Level 1调试口也禁了但 BOOT0 引脚还是暴露在板上攻击者把 BOOT0 拉高后进入系统 bootloader。STM32 内置 bootloader 会检查 RDP 级别当 RDP 为 Level 1 时它不允许读取 Flash但允许做全片擦除。这意味着对方虽然读不到你的代码但可以把你的固件全部擦掉让你的设备变砖。如果这是攻击者想达到的目的那你的 Level 1 并没有挡住他。再举个例子。你只做了读保护没做写保护。攻击者同样可以通过系统 bootloader 往 Flash 里写入他自己的代码让设备执行恶意逻辑。你可以说“这已经破坏了设备”但对很多场景来说设备被篡改比被抄走更可怕尤其是工业控制器或医疗设备。所以我的建议是做安全设计时画一张简单的“访问路径图”。把 CPU 调试接口、bootloader、DMA、外设这些能访问 Flash 的路径都列出来然后逐条确认每条路径是否被封堵。你会发现“开了 RDP”只是众多环节中的一环。2. STM32 安全特性全景芯片里那些“隐藏关卡”2.1 读保护 RDPFlash 的第一道封锁线RDPRead Protection是 STM32 里最基础、也最容易理解的安全机制。它通过选项字节里的 RDP 等级来控制 Flash 内容的读取权限。不同的 STM32 系列在细节上略有差异但整体分为三个等级等级状态效果Level 0无保护调试接口、bootloader、DMA 都能正常读取 FlashLevel 1中等保护禁止调试接口和 bootloader 读取 Flash但允许全片擦除可通过设置回 Level 0 解除保护解除时自动执行全片擦除Level 2最高保护调试功能彻底关闭bootloader 也无法读取 Flash等级不可回退相当于把芯片“焊死”在最终版本很多开发者只关心“开了 RDP 之后还能不能调试”这个问题的答案因等级而异。Level 1 下如果你通过调试器重新下载程序ST-Link 会先触发一次全片擦除所以 RAM 里的变量内容、Flash 里的原有数据都会丢。Level 2 更极端一旦设置调试器基本就废了后续只能靠芯片上运行的应用程序自身比如 IAP去更新固件。实操中我经常提醒别人RDP 不是“越高级越好”。Level 2 虽然安全但是你自己后续也失去了所有调试手段一旦应用程序有 bug你只能通过串口打印慢慢查。再加上很多封装不支持芯片级恢复相当于给自己埋了一个“雷”。对于大多数产品Level 1 是性价比最高的起点。2.2 写保护 WRP 与 PCROP代码区也能上锁RDP 管的是“能不能读”WRPWrite Protection管的是“能不能写”。通过配置选项字节你可以把指定 Flash 页设置为只读这样无论是 CPU 自己、调试器还是 bootloader都无法对这些区域进行擦除或编程。WRP 适合保护那些“绝对不能变”的引导代码或关键参数。PCROPProprietary Code Readout Protection更进一步它保护的是“不能让任何主机读取但 CPU 可以执行”的区域。这句话怎么理解普通 RDP 是防调试器但 CPU 内部的 DMA 依然可以读取 Flash 内容传到外部接口。PCROP 把指定区域设置成“只能取指令不能当作数据读”这样即使 DMA、调试器、bootloader 想碰这块区域读出来的数据也都是无效的。这就把“固件被抄走”的最后一条路也堵上了。PCROP 的实际配置比较讲究。它通常要求地址对齐不同系列对齐单位不同有的是 256 字节有的是页而且一旦使能区域内就不能再通过常规方式读取数据。常见的坑是你把中断向量表或者某个常量数组放进了 PCROP 区运行后程序莫名其妙跑飞因为芯片“拒绝”把区域里的数据读出到寄存器。所以我的建议是PCROP 只保护真正的代码段不要把需要被 DMA 读取的数据放在里面否则会给自己找麻烦。2.3 OTP 与唯一 ID一次性写入的“身份烙印”OTPOne-Time Programmable区域是芯片里一块只能写一次的存储区通常几百字节到几 KB 不等。它常用来保存序列号、MAC 地址、密钥等不需要修改的信息。OTP 最大的特点是“写错就没有重来的机会”所以量产前一定要做充分的检查和验证。另一个冷门但极其实用的特性是芯片唯一 IDUnique Device ID。STM32 几乎全系都有 96 位唯一 ID在出厂时由工厂写入正常情况无法修改。这个 ID 可以用来做设备证书、密钥派生种子、防伪验证。比如你可以把“设备唯一 ID 一个保密密钥”一起做哈希把哈希结果烧进 OTP。运行时固件先读取唯一 ID计算哈希和 OTP 里的值比对不一致就认为芯片被更换或固件被移植。这是一套成本很低但有效的“固件绑定硬件”方案。2.4 真随机数发生器与加密引擎给数据加把锁如果设备要和外界通信加密几乎绕不开。STM32 里的 RNGTrue Random Number Generator模块可以产生随机数它基于芯片内部的模拟噪声源不是伪随机数。这个模块很多人只在做“随机点灯”时用过但它真正的价值在于密钥生成、加密时生成随机 IV 或者挑战值。更高端一些的 STM32 系列比如 F2/F4/F7/L4/H7 等还内置了硬件 AES 加密引擎支持 ECB/CBC/CTR/GCM 等多种模式有的还支持 RSA、ECC 等非对称算法加速。为什么强调用硬件而不是用软件实现 AES一方面是速度快另一方面是硬件引擎可以把密钥存在专用寄存器里降低密钥被软件意外泄露的风险。这里我多说一句对于 IoT 产品很多人一上来就想着用 TLS却忽略了 TLS 协议栈在 MCU 上占有大量 RAM 和 Flash。如果你的设备是简单的主从通信用 AES-CBC 加 HMAC 就足够如果设备要连云端那再考虑上 TLS 或者基于 STM32 的 Secure Boot 方案。2.5 从 RDP 到 TrustZone不同系列的安全能力分级STM32 家族非常庞大安全能力不是一把尺子量到底。举例来说经典 F1/F4 系列以 RDP、WRP、PCROP 为主部分高端型号带硬件加密引擎但整个系统没有隔离概念。L4/L5/U5 系列L5 和 U5 支持 Arm TrustZone可以把 Flash、RAM、外设划分为安全区和非安全区实现“安全世界”和“普通世界”的隔离。TrustZone 配合 Secure Boot可以把整个信任链建立起来。H7 系列性能强支持硬件加密、安全固件安装等特性适合对性能和安全同时有要求的场合。STM32MP1 系列虽然不算传统 MCU但它运行 Linux安全架构更复杂涉及 OP-TEE 等。对多数工程师来说第一颗芯片不需要考虑 TrustZone把 RDP、WRP、PCROP、唯一 ID 玩明白已经能挡住 90% 的“物理接触型攻击”。等产品到了需要 CA 证书、安全启动、防回滚这个级别再考虑带 TrustZone 或硬件安全模块的型号不迟。3. 一步步实操用 CubeProgrammer 把固件“锁死”3.1 基础操作设置 RDP 等级并验证效果现在 ST 官方主推的工具是 STM32CubeProgrammer旧版的 ST-LINK Utility 已经基本被取代。打开 CubeProgrammer连接目标芯片后切到“Option Bytes”标签页。在“Read Protection”区域你可以看到当前 RDP 等级选择 Level 1点击 Apply工具会提示你“该操作将触发芯片全片擦除”确认后即可生效。这里有几个细节值得注意。第一如果连接时芯片已经处于 Level 1CubeProgrammer 通常会显示“Device is in read protection mode”并且 Flash 内容显示为不可读或者全 FF。此时你可以在 Option Bytes 页面选择 Level 0 并 Apply工具会先执行擦除再解除保护。第二解除保护的过程需要断开连接再重新连接有时候还需要给目标板重新上电不然调试器状态会卡住。第三F1 系列和部分早期芯片的选项字节操作方式和较新的 L4/H7 有区别建议一律以当前芯片参考手册的“Flash programming”章节为准。设置完成后可以做个验证连上调试器尝试读取 Flash正常情况会报错或者只能读取到 0x00。然后重新给板上电程序依然正常运行。这一步确认了“能跑码但读不出码”的预期效果。3.2 进阶配置PCROP 与写保护的实际操作PCROP 的配置在 CubeProgrammer 里也有图形化入口。在 Option Bytes 页面找到“Proprietary Code Readout Protection”相关区域选择要保护的区域起始地址和大小勾选 Enable然后 Apply。实际操作中有一个很容易踩的坑PCROP 区域的起始地址必须按系列要求对齐。比如某些系列要求以 256 字节对齐有些要求以 2KB 对齐如果填了一个不对齐的地址工具要么报错要么静默修正最终保护的区域和你预期的不一样。建议配置前先用 CubeMX 或者查阅参考手册确认地址对齐规则。还要强调一点PCROP 保护的是“禁止作为数据读取”但代码可以从这个区域执行。这意味着你的代码可以调用存储在 PCROP 区里的函数但如果你试图用 DMA 从这个区域搬运数据搬出来的内容是无效的。有一次我把一个查表数组放在 PCROP 区结果程序运行结果完全错误排查了整整半天才意识到是 DMA 读不了这块区域。再加上如果开了 RDPPCROP 区域可能还会出现调试器完全看不到的情况这会增加排错难度。我的习惯是先在一个测试芯片上把 PCROP 边界试清楚再上产线。写保护 WRP 的配置相对简单选择需要保护的页范围即可。常用于保护 bootloader 区防止应用层误升级导致 boot 丢失。但要记住WRP 不是“永久不可写”只要你把 RDP 降到 Level 0WRP 保护也会解除。所以 WRP 只能防“意外写入”防不了“恶意破解”。3.3 关键选项字节解析从 nBOOT0 到 nSWBOOT0选项字节里除了 RDP、WRP、PCROP还有一组和启动相关的配置对安全影响很大。STM32 有多种启动方式从主 Flash 启动、从系统存储器内置 bootloader启动、从 SRAM 启动。启动源通常由 BOOT0/BOOT1 引脚或选项字节 nBOOT0/nBOOT1/nSWBOOT0 决定。从安全角度你要重点检查系统 bootloader 是否还会被触发如果 BOOT0 引脚可以被外部拉高或者 nSWBOOT0 配置允许软件切换到系统存储器模式那攻击者就有机会通过内置 bootloader 操作 Flash。把启动模式固定为主 Flash并禁用软件 boot 切换选项能显著缩小攻击面。这里补充一句禁用系统 bootloader 入口不等于删掉 bootloader 本身。你可以在应用里保留自己的 IAP 升级功能但通过选项字节配置让外部无法轻易进入 ST 自带的 bootloader。自己实现的 IAP 可以加签名校验、版本校验这样即使升级通道暴露攻击者也很难刷入伪造固件。3.4 一个必须记住的坑恢复与擦除的关系解除保护比如从 Level 1 降到 Level 0几乎总是伴随全片擦除。很多人第一次操作时没注意点完 Apply 才发现 Flash 里的数据全没了。这个特性既是保护机制也是风险机制攻击者把 RDP 降级时代码已经先被擦掉了他拿不到任何数据但你自己如果误操作也一样会丢代码。所以我的操作习惯是每次改动选项字节前先对当前固件做完整备份量产阶段则把选项字节配置写进烧录脚本由生产工具自动设置避免人工在界面上点错。CubeProgrammer 的 CLI 模式可以做到无人值守命令行执行STM32_Programmer_CLI -c portSWD -ob RDP0xBB这类命令来切换 RDP 等级。如果产品每一片都要先烧程序再开保护用脚本比手工操作靠谱得多。4. 通信侧安全从“裸奔”到“加密通道”4.1 不要只信“私有协议”安全通道怎么建很多工程师觉得“我的协议是自定义的别人看不懂所以安全”。这个想法在嵌入式圈子里特别常见。但你要知道攻击者不需要理解你的协议语义他只需要抓包、回放、篡改。只要通信链路上没有任何加密和完整性校验任何明文协议都能被轻易分析出来。在设计通信安全时有三个基本目标机密性不让别人看到内容、完整性不让别人篡改内容、身份认证确认对方是谁。对于大多数主从式或点对点应用用对称加密加 MAC 就能同时满足这三个目标性能开销也小。这里我用一个生活化的类比你给朋友寄一个带锁的箱子锁的钥匙双方各有一把对称加密。箱子外面还贴了一张封条封条上的字是双方约定的“防篡改印记”MAC。朋友收到后先检查封条是否完好再开锁取信。如果封条坏了或者印记不对他就知道箱子在中途被人动过。4.2 基于 AES-CBC HMAC 的轻量级方案在 STM32 上实现一条加密通信链路最常用的组合是 AES-CBC 加密 HMAC 完整性校验。大致流程如下// 伪代码发送端加密与认证 // plaintext header payload timestamp(防重放) // ciphertext AES_CBC_Encrypt(session_key, plaintext); // mac HMAC_SHA256(session_key, ciphertext); // send(ciphertext || mac);接收端收到数据后先计算并比对 MACMAC 一致才解密然后检查时间戳或序列号防止攻击者把之前录到的合法报文重新发送一遍这就是重放攻击。这里有几个细节需要特别注意。第一AES-CBC 模式必须使用随机 IV。每次加密都重新生成 IV不能让 IV 固定否则相同的明文会产生相同的密文攻击者能从密文规律中反推出信息。IV 不需要保密可以直接随密文一起发送。第二MAC 的密钥最好和加密密钥分开。用两个不同密钥分别做加密和认证可以避免某些密码学攻击。密钥可以直接从同一个主密钥派生比如enc_key HMAC(master_key, 0x01)mac_key HMAC(master_key, 0x02)。第三序列号或时间戳是防重放的关键。很多设备之间的通信不涉及复杂网络但重放攻击依然存在攻击者录下“开锁”指令反复发送。在报文里加一个单调递增的计数器接收端只接受比上次更大的计数就能防住重放。4.3 设备身份认证防伪造节点的密钥体系在主从系统中主机怎么知道一条指令真的来自合法从机如果每个从机都使用同样的全局密钥那任意一个设备被拆解后密钥都会泄露系统直接全灭。更稳妥的做法是“一机一密”。可以这样设计每台设备出厂时利用芯片唯一 ID 派生出一个设备私钥。具体做法是把唯一 ID 和一个只能保存在安全区域的主密钥做 HMAC得到的值作为该设备的设备密钥。主机侧维护一个设备 ID 到密钥的映射表。握手时从机发送自己的 ID主机用对应的设备密钥发起挑战-响应认证。这样即使某个从机被拆解攻击者也只能拿到这一个设备的密钥无法仿冒其他设备。密钥存储是个敏感环节。如果密钥以明文形式存在普通 Flash 里开了 RDP 也只能保证“不能用调试器直接读”但应用层代码只要任意一个漏洞被利用密钥就可能被读取。所以更安全的方案是把密钥存到带安全隔离功能的芯片里比如 STM32L5/U5 的 TrustZone 安全区或者外部独立安全芯片。对于普通产品先用 RDP 唯一 ID 派生密钥能覆盖大多数需求但心里要有数这属于“提高攻击成本”而不是“绝对不可破解”。5. 生产与供应链安全出厂前就该做好的事5.1 安全烧录与编程流程设计很多小批量产品是工程师拿着 ST-Link 一片一片手动烧的烧完也不做保护。这种做法在样机阶段没问题量产就会出乱子而且顺序一旦错了会导致产品出厂的防护等级不一致。一条比较稳妥的量产流程是先用 CubeProgrammer 烧录 bootloader烧录应用固件如果带签名先做签名写入设备唯一 ID、序列号、密钥等个性化数据到 OTP 区配置选项字节设 RDP、WRP、PCROP禁用无关的启动源全功能自检此时还能通过调试接口访问先别急着锁最后再设置 RDP 到目标等级并断电验证。为什么最后一步才开 RDP因为一旦开了保护后续想通过调试器查问题就很麻烦只能靠应用自身日志。在自检没有跑通之前锁死芯片等于自断后路。量产阶段一定要把流程脚本化用 CubeProgrammer CLI 批量执行避免人工点鼠标出错。5.2 密钥管理与唯一 ID 绑定生产环境中密钥管理是另一个难点。如果你的每个设备都需要唯一的密钥那么密钥的生成、分发、注入都要有完整的链路。常见做法是在产线上准备一台加密机或者受控的 PC生成密钥后用加密通道注入设备或者利用芯片唯一 ID 在设备本地派生密钥这样产线根本不需要接触明文密钥只需要在设备固件里预置一个主密钥。用唯一 ID 派生密钥有一个额外的好处它天然实现了“固件绑架到具体芯片”。即使有人拿到了你的一份固件烧到另一颗 STM32 上由于唯一 ID 不同派生出的密钥也不同设备可能无法通过云端认证甚至固件会检测到“非授权芯片”而拒绝运行。这对防抄板非常有效。但是请注意主密钥本身是整套系统的“根”。如果主密钥泄露所有设备的密钥体系都会崩溃。所以主密钥至少要保存在产线的安全环境里并且代码里不要明文存储主密钥可以考虑把主密钥拆成多段分散在不同编译单元再用编译期混淆处理。5.3 防止“偷代码”之外的“掉包”问题供应链安全不只是“防止代码被读走”还包括“防止芯片/模块被掉包”。常见的做法是设备运行后定期向服务器发送一个由唯一 ID 和密钥计算出的认证码。服务器侧如果发现某台设备的 ID 不在订单列表中或者设备数量超出预期就能立刻察觉。我遇到过的一个实际案例是某产品找代工厂生产代工厂私下多烧了一批板卡拿去卖。因为设备里没有唯一的身份认证机制货流入市场后原厂根本分辨不出来自家产品。后来他们把所有设备的唯一 ID 上报到云端出货前激活才真正堵住了漏洞。对嵌入式产品来说芯片唯一 ID 云端激活是性价比极高的“防掉包”手段。6. 常见问题与排查技巧实录6.1 调试器连不上了怎么办开了 RDP 之后发现调试器再也连不上芯片这是最多人遇到的问题。首先要区分两种情况芯片是刚出厂从未被设置过保护还是你自己设置过 Level 1 或 Level 2。如果是 Level 1CubeProgrammer 连接时通常会弹出一个提示说设备处于读保护状态。你只需要在 Option Bytes 页面把 RDP 改为 Level 0工具会执行全片擦除并解除保护。如果一直连不上可以尝试把 BOOT0 拉高让芯片进入系统 bootloader然后用 STM32CubeProgrammer 的 UART 或 USB 模式连接在“Option Bytes”里解除保护。注意解除保护时 Flash 数据会丢失所以平时要养成备份习惯。如果是 Level 2那基本没有恢复手段。这也是我反复强调不要轻易上 Level 2 的原因。有些工程师为了“安全”直接设 Level 2结果产品软件有问题要返工整个板子只能报废。6.2 开了 RDP 之后程序跑飞怎么排查有时候开了 RDP 之后程序功能异常甚至跑飞但调试器又读不了 Flash排查难度很大。这时候我建议先做最小化验证把 RDP 降回 Level 0重新烧录同一个固件看是否复现问题。如果在 Level 0 下完全正常而在 Level 1 下异常问题往往出在“代码尝试访问了被保护的功能”。最常见的原因有两个一是代码里用了 DMA 去读取被 PCROP 保护的 Flash 区域读回来的数据全是垃圾二是 bootloader 跳转应用时应用代码所在区域被写保护导致程序无法升级或跳转失败。排查时先用 CubeProgrammer 查看选项字节当前状态确认哪些区域被 PCROP 或 WRP 覆盖再对照代码里的访问范围基本能一眼看出冲突。6.3 老工程师的几条安全习惯最后分享几条我自己总结出来的安全习惯谈不上高大上但很实用。第一永远先开 RDP Level 1再评估是否要升级到 Level 2。这是性价比最高的第一步但要注意它不能防住 bootloader 全片擦除所以还要关掉不必要的启动源。第二量产固件里不要写死任何调试后门。很多产品为了售后方便在固件里留一个“万能密码”或者“调试宏”这等于自己给自己留了个大窟窿。如果确实需要售后调试用基于证书的签名升级机制而不是固定口令。第三定期重新评估你的威胁模型。产品卖得越好被盯上的概率越大。今天不起眼的防护漏洞明天可能就会被批量利用。安全不是上线那天做完就结束的工作而是一个持续迭代的过程。我的个人体会是STM32 的安全机制其实早就内置在芯片里了真正缺的是配置习惯和流程意识。花一个下午把 RDP、WRP、PCROP、唯一 ID 这些基础功能全部配一遍比纠结选哪颗“安全芯片”有用得多。安全不是买来的功能而是设计出来的流程。
返回列表