因为一个OTA升级没加密,我被客户追着骂了半个月
去年做的一个网关项目,出货大概三百多台,分布在几个不同的工厂。功能跑得挺好,数据也准,客户一开始还挺满意。结果有一天半夜,对方技术负责人直接甩过来一张截图——设备屏幕上弹出了一行他们完全不认识的字符串,然后整台设备就卡死了。
排查了三天才搞清楚问题在哪:OTA升级固件是明文传输的,没有签名校验,有人在中间链路注入了一个伪造的固件包。设备傻乎乎地就刷进去了,然后就变砖了。
这事的教训值六位数。从那之后我才认真琢磨嵌入式安全到底该怎么做。
先说一个很多嵌入式工程师容易忽略的事:攻击面比你想象的宽得多。
你可能会觉得"我就一个单片机跑着,谁会来黑我?"但现实是,IoT设备一旦联网,暴露的攻击面包括:OTA通道、物理串口/JTAG、网络端口、Web管理界面、甚至传感器的通信总线。任何一个点没封住,都是突破口。
我之前那个网关,串口调试功能release版本没有禁用。boot阶段有3秒的等待检测,如果有人在这3秒内往UART塞0x1F(固件恢复指令),设备就会进入firmware recovery模式,从SD卡刷固件。这个洞是客户自己发现的,他们团队的一个安全研究员拿着逻辑分析仪在那捅咕了两小时就找到了。
怎么说呢,那一刻脸上挺挂不住的。
所以嵌入式安全,到底要防什么
我觉得核心就三个东西:
1. 防固件被提取/逆向
这其实是很多"二次开发"需求的前提。之前有客户问我们能不能把固件dump出来给他们自己改——当然不能。但如果你没做保护,他们是真能自己读出来的。
保护手段:
- 读保护(RDP)等级设到Level 2,一次性熔断。STM32叫RDP Level 2,其他MCU叫法不同但原理一样——设置之后debug interface彻底废掉,再也读不了flash。
- 如果用外部flash存代码或者关键数据,里面的内容要加密存放。我之前用过一个方案是AES-128-CBC,密钥烧在MCU内部OTP区域,外部flash的密文数据只有CPU能解密。
- 唯一ID绑定。把MCU的unique ID参与解密密钥的派生,这样即使固件被读出来了,换一颗芯片也跑不了。
2. 防固件被篡改/注入
这就是我踩的那个坑。
// 一个简陋到不能再简陋的签名校验 // 以前我是这么写的——完全没校验 if (download_ota_package( &pkg )) { spi_flash_erase(APP_PARTITION); spi_flash_write(APP_PARTITION, pkg.data, pkg.size); system_reboot(); } // 后来改成这样 if (download_ota_package( &pkg )) { // 先验证签名 if (verify_ecdsa_signature( pkg.data, pkg.size - SIGNATURE_LEN, pkg.data + pkg.size - SIGNATURE_LEN, &public_key)) { spi_flash_erase(APP_PARTITION); spi_flash_write(APP_PARTITION, pkg.data, pkg.size - SIGNATURE_LEN); system_reboot(); } else { log_error("OTA signature verification failed!"); report_ota_failure(ERR_SIGNATURE); } }看起来就多了几行代码,但区别是一个会被黑,一个不会。公钥存在内部flash的只读区域,生产的时候烧进去之后熔断写保护。签名算法我们用的是ECDSA with P-256,密钥对用openssl生成。
这套方案后来跑了两年多没出过问题。唯一要注意的是签名验证本身不能太久——我之前用mbedTLS的软件实现,在180MHz的Cortex-M4上验一次签大概要40ms,OTA升级时勉强能接受。如果换了更慢的MCU可能需要硬件加速器,或者用更轻量的Ed25519。
3. 防通信被窃听/重放
TLS是基础操作,但在资源受限的MCU上跑full TLS握手确实有点吃力。我们的做法是:
- 支持TLS 1.3(相比1.2少了一次RTT,握手快很多)
- 用PSK模式替代证书模式,节省证书解析的开销
- 如果MCU实在带不动TLS,至少用DTLS + Pre-shared Key做应用层加密
- 每个消息包里带一个单调递增的sequence number,防止重放攻击
我之前在一个Cortex-M0+的芯片上做过实验,纯软件算TLS握手开不起来(RAM只有16KB),最后方案是MCU负责加密载荷,TLS握手交给外挂的Wi-Fi模块(ESP32)去完成,两边通过SPI传数据。
说到工具,有几个趁手的可以提一下:
- MCUboot:开源的bootloader,支持签名验证和固件回滚,用起来挺顺手。配合Zephyr或自己的RTOS都行
- Trusted Firmware-M:如果芯片支持ARM TrustZone的话可以上,隔离安全和非安全世界
- WolfSSL:比mbedTLS更轻量的TLS库,对RAM的压榨到了令人发指的程度——最小配置下ROM不到50KB
- STM32Trust:ST自己的一套安全框架,集成了secure boot、secure firmware update、secure storage
说实话嵌入式安全是个挺大的话题,我这边写到的也只是自己踩过坑的部分。真要系统做的话还有安全启动链(chain of trust)、安全日志审计、侧信道防护、甚至物理层面的防探针攻击……这些我也还在学。
不过如果你问我最想说什么:别觉得自己做的东西不值一黑。三百块的网关和三个亿的系统,防线上的漏洞往往是一样的。
