GD32F303 Flash安全实战:从配置字到固件加密的全面防护

张开发
2026/4/7 16:37:53 15 分钟阅读

分享文章

GD32F303 Flash安全实战:从配置字到固件加密的全面防护
1. GD32F303 Flash安全防护的必要性在嵌入式产品量产过程中Flash安全防护是每个开发者都必须重视的关键环节。以GD32F303为例这颗国产MCU凭借出色的性价比在工业控制、消费电子等领域广泛应用但同时也面临着程序被非法读取、篡改的风险。想象一下你花了几个月心血开发的智能家居控制器被人轻易复制了固件甚至植入恶意代码这种损失不仅是经济上的更可能危及品牌信誉。Flash安全防护的核心目标有三个防读取防止固件被dump、防篡改防止固件被修改、防克隆防止产品被仿制。GD32F303提供了从硬件层到软件层的多重防护机制我们需要根据产品安全等级组合使用这些方案。比如一个简单的智能灯控可能只需要配置字加密而支付终端这类高安全需求产品则需要固件加密安全启动外部加密芯片的全套方案。我曾参与过一个智能门锁项目最初仅使用了基础的读保护功能。结果量产三个月后市场上就出现了仿制品对方通过物理攻击手段提取了Flash内容。后来我们升级为UID校验固件分段加密的方案成功遏制了克隆现象。这个教训让我深刻认识到安全防护必须走在产品生命周期的最前端。2. 基础防护FMC配置字加密实战2.1 配置字加密原理剖析GD32F303的FMCFlash Memory Controller模块内置了硬件级安全机制通过修改选项字节Option Bytes中的配置字来实现。这个机制就像给Flash上了一把物理锁——当配置字设置为FMC_USPC0xBB时芯片会禁止通过SWD/JTAG接口读取Flash内容关闭系统编程ISP功能阻止非法地址访问限制调试器仅能访问有限资源需要注意的是配置字加密属于全有或全无的防护。一旦启用整个Flash区域都会被保护包括你自己也无法通过调试器读取。我在早期项目中就犯过这样的错误启用保护后发现问题却无法通过ST-Link读取日志最后只能整批返厂擦除。2.2 代码实现与注意事项GD32标准库已经封装了配置字操作函数下面是一个增强版的实现示例#include gd32f30x_fmc.h void enable_read_protection(void) { // 检查当前保护状态 if(ob_spc_get() ! SET) { fmc_unlock(); // 解锁主Flash操作 ob_unlock(); // 解锁选项字节操作 // 先读取原有选项字节内容重要 uint32_t ob_data[2]; ob_data[0] OB_DATA_ADDRESS0; ob_data[1] OB_DATA_ADDRESS1; ob_erase(); // 擦除选项字节 // 恢复其他选项字节设置如看门狗配置等 ob_write(OB_DATA_ADDRESS0, ob_data[0]); ob_write(OB_DATA_ADDRESS1, ob_data[1]); // 设置读保护 ob_security_protection_config(FMC_USPC); ob_lock(); // 锁定选项字节 fmc_lock(); // 锁定主Flash } }关键点说明一定要备份原有选项字节GD32的选项字节还包含看门狗、复位等配置直接擦除会导致这些设置丢失操作时序严格必须先解锁→擦除→写入→锁定步骤不能颠倒保护不可逆启用后只能通过整片擦除解除量产前务必充分测试实测中发现一个坑某些GD32F303批次在启用读保护后RAM中的内容也会被禁止读取。这意味着如果你用RT-Thread这类OS调试时无法查看线程栈信息。解决方案是在开发阶段使用分级保护FMC_LSPC量产时再改为全保护。3. 中级防护UID校验与动态加密3.1 芯片唯一ID的妙用每颗GD32F303都内置96位的唯一标识符UID这个ID就像芯片的身份证号。我们可以利用它实现两种防护方案A静态绑定// 烧录时执行一次 void write_uid_to_flash(void) { uint32_t uid[3]; uid[0] *(uint32_t*)(0x1FFFF7E8); uid[1] *(uint32_t*)(0x1FFFF7EC); uid[2] *(uint32_t*)(0x1FFFF7F0); fmc_unlock(); fmc_page_erase(FLASH_UID_ADDR); fmc_word_program(FLASH_UID_ADDR, uid[0]); fmc_word_program(FLASH_UID_ADDR4, uid[1]); fmc_word_program(FLASH_UID_ADDR8, uid[2]); fmc_lock(); } // 启动时校验 bool check_uid_valid(void) { uint32_t flash_uid[3] { *(uint32_t*)FLASH_UID_ADDR, *(uint32_t*)(FLASH_UID_ADDR4), *(uint32_t*)(FLASH_UID_ADDR8) }; return (flash_uid[0] *(uint32_t*)(0x1FFFF7E8)) (flash_uid[1] *(uint32_t*)(0x1FFFF7EC)) (flash_uid[2] *(uint32_t*)(0x1FFFF7F0)); }方案B动态加密更安全void encrypt_with_uid(uint8_t* data, uint32_t len) { uint32_t uid_key *(uint32_t*)(0x1FFFF7E8) ^ 0x55AA55AA; for(uint32_t i0; ilen; i4) { *(uint32_t*)(datai) ^ uid_key; } }实际项目中我推荐将关键算法库如电机控制PID用方案B加密存储运行时解密到RAM执行。这样即使有人提取Flash内容也无法直接获取有效算法。3.2 进阶技巧UID分段存储为增加破解难度可以采用藏头露尾策略将UID拆分为3部分分别存储在Flash不同扇区加入伪数据混淆比如在真实UID前后填充随机数校验时使用指针跳跃读取避免连续内存访问// 存储布局示例 typedef struct { uint32_t dummy1[12]; // 伪数据 uint32_t uid_part1; // UID第一部分 uint32_t dummy2[7]; uint8_t uid_part2[4];// 第二部分 uint16_t dummy3; uint16_t uid_part3; // 第三部分 } uid_storage_t;这种方案在智能门锁项目中将破解成本提高了约300%攻击者需要完整逆向固件才能找到所有UID片段。4. 高级防护固件加密与安全启动4.1 AES硬件加密实战GD32F303内置了AES-128/192/256硬件加速引擎我们可以用它实现固件全加密。具体流程编译阶段使用Python脚本加密bin文件from Crypto.Cipher import AES import hashlib def encrypt_firmware(in_file, out_file, key): iv hashlib.md5(key).digest() cipher AES.new(key, AES.MODE_CBC, iv) with open(in_file, rb) as f: data f.read() # 填充到16字节对齐 pad_len 16 - (len(data) % 16) data bytes([pad_len] * pad_len) encrypted cipher.encrypt(data) with open(out_file, wb) as f: f.write(encrypted)Bootloader实现void decrypt_and_jump(uint32_t addr) { AES_KeyInit(AES_KEY_128, key, AES_ENCRYPTION); AES_IVInit(iv); uint32_t len get_fw_size(addr); uint8_t* src (uint8_t*)(addr FLASH_OFFSET); uint8_t* dest (uint8_t*)RAM_EXEC_ADDR; for(uint32_t i0; ilen; i16) { AES_DataProcess(srci, desti); } // 校验解密后的固件头 if(*(uint32_t*)RAM_EXEC_ADDR 0x20000000) { ((void(*)(void))RAM_EXEC_ADDR)(); } }4.2 安全启动链设计完整的启动验证流程应包含一级Bootloader存储在芯片内部Flash验证二级Bootloader签名二级Bootloader解密应用固件并校验CRC应用程序运行时验证关键数据段完整性我在电动工具控制器项目中采用的方案是使用SHA-256校验Bootloader完整性每24小时检查一次应用程序签名关键参数存储时采用UID派生密钥加密bool verify_signature(uint32_t addr, uint32_t len) { uint8_t hash[32]; sha256_calculate((uint8_t*)addr, len, hash); return ecc_verify(hash, signature, public_key); }5. 外部加密芯片协同方案5.1 ATECC608A实战集成当需要更高安全等级时可以引入专用加密芯片。以ATECC608A为例与GD32F303的典型连接方式GD32F303 ATECC608A PA9 (I2C_SCL) -- SCL PA10 (I2C_SDA) -- SDA PC13 (GPIO) -- /WAKE安全握手流程GD32上电后发送唤醒脉冲ATECC608A返回SN[0:1]作为挑战码GD32用预共享密钥加密挑战码ATECC608A验证加密结果并返回操作权限bool secure_handshake(void) { uint8_t challenge[2]; uint8_t response[32]; atecc_wakeup(); atecc_read_serial(challenge); aes128_encrypt(challenge, response, shared_key); return atecc_verify_response(response); }5.2 防克隆设计技巧通过加密芯片可以实现一芯一密每个产品使用不同的加密密钥动态令牌每次启动生成临时会话密钥功能锁关键算法存储在加密芯片内部实际项目中我将电机控制的核心算法放在ATECC608A的Secure Boot区域GD32只发送输入参数并接收计算结果。这样即使拆解芯片也无法获取完整算法。

更多文章