当前位置:首页 > 消费电子 > 消费电子
[导读]消费电子防拷贝最常被写成“读UID→比对→死循环”,这只能挡住小白烧录工。真量产环境里,固件.bin一旦被扒出来,逆向者搜UID基址、改跳转指令就能跳过校验。 正经做法是把UID当成“信任根因子”:不在代码里硬编码母片UID,而是让BootLoader用UID派生密钥、校验绑定值或解密App段,再叠RDP读保护。


消费电子防拷贝最常被写成“读UID→比对→死循环”,这只能挡住小白烧录工。真量产环境里,固件.bin一旦被扒出来,逆向者搜UID基址、改跳转指令就能跳过校验。 正经做法是把UID当成“信任根因子”:不在代码里硬编码母片UID,而是让BootLoader用UID派生密钥、校验绑定值或解密App段,再叠RDP读保护。

一、UID绑定的正确位置:BootLoader

App里做if(uid!=xxx) while(1)最危险——校验分支就在固件明文里,特征好定位。 应该放在上电最先跑的BootLoader:

读芯片UID

拼salt做SHA256/AES派生

和Flash参数区存的binding值比对

通过才解密App或跳转

不通过进安全模式(限功能/清关键数据)

以STM32为例UID在0x1FFFF7E8(96bit),读出来别直接比字符串。

二、绑定值别存明文UID

出厂烧录时由上位机读当前片UID,算绑定值写进参数扇区:

#define UID_LEN 12

#define BIND_ADDR 0x0801F000   // 独立参数扇区


static const uint8_t SALT[16] = {

   0x9A,0x23,0xC7,0x04,0x5E,0x88,0x1B,0xD2,

   0x66,0x3F,0x90,0xAB,0x12,0x77,0xE5,0x40

};


/* 简化示意:真实项目用SHA256/AES,别用xor+srand */

void derive_bindkey(const uint8_t*uid, uint8_t*out){

   for(int i=0;i<UID_LEN;i++){

       out[i] = uid[i] ^ SALT[i] ^ SALT[i+4];

       out[i] = (out[i]<<3) | (out[i]>>5);   // 轮换

   }

}


int check_uid_binding(void){

   uint8_t uid[UID_LEN], k[UID_LEN], store[UID_LEN];

   read_chip_uid(uid);

   derive_bindkey(uid, k);

   flash_read(BIND_ADDR, store, UID_LEN);

   volatile uint8_t diff = 0;

   for(int i=0;i<UID_LEN;i++)

       diff |= k[i]^store[i];   // 防时序短路

   return diff==0;

}

要点:

绑定值存在写保护扇区(WRP)

校验用异或累加密文比较,别写if(k!=store) return 0让逆向好改

派生算法别用srand/rand,序列可复现

Release版本关掉printf调试残留

三、更进一步:UID派生解密密钥

只校验UID,攻击者把校验跳了仍能跑。把UID融进“解密密钥”才更难绕:

/* BootLoader解密App前,先由UID参与派生根密钥 */

void build_dev_key(const uint8_t*uid, uint8_t*key128){

   uint8_t seed[32];

   memcpy(seed, SALT, 16);

   memcpy(seed+16, uid, UID_LEN);

   sha256(seed, 32, key128);    // 前16字节当AES-128密钥

}

PC端加密固件时也用同样UID绑定值(产线脚本逐片算),烧进去的固件镜像就是“一芯一密”。 换到别片UID不对→解密出来是垃圾指令→App起不来。

四、安全启动闭环

推荐三级:

BootROM/ROM ─▶ BootLoader ─▶ App

(芯片固化)   (验UID+解密)  (业务)

BootLoader里顺序:

开RDP Level1(最终品再考虑Level2)

读UID→派生key

从Flash读密文App到SRAM

AES-CBC解密

SHA256校验完整性

UID binding比对

全部过→跳转;失败→擦参数/进安全模式

void bootloader_main(void){

   rdp_check();

   uint8_t uid[UID_LEN];

   read_chip_uid(uid);


   uint8_t key[16];

   build_dev_key(uid, key);


   aes_load_key(key);

   aes_decrypt_app();          // 密文区→明文SRAM


   if(sha256_verify() && check_uid_binding()){

       jump_to_app();

   }else{

       enter_safe_mode();      // 限功能,不一定要死循环

   }

}

ESP32类带硬件解密引擎的,密钥放eFuse,CPU执行前自动解密,固件Dump出来也是密文。

五、量产烧录流程

一份固件适配万片,不能手工改UID:

空白片先烧BootLoader(不含UID逻辑差异)

上位机连ST-Link/烧录器读UID

脚本算binding值+按UID加密App镜像

写参数扇区+写App密文

设RDP1、WRP锁参数区

断开调试口

这样同份源码出不同片固件,拷贝工只能复制出“绑定原片”的镜像。

六、防拷贝的天花板

UID绑定不是银弹,明确防不住:

读保护没开→Flash被dump→逆向算法

电压毛刺/故障注入跳过校验

把校验函数整体NOP掉重烧

高端芯片用PUF/OTP密钥才更抗

价值在“提高抄袭成本”:抄板客要逆向算法+改二进制+逐片算key,不如自己写。

七、落坑清单

App里明文字符串比UID→特征好搜

硬编码母片UID进固件→换片就废但易改跳转

只用xor不哈希→逆向还原算法

校验失败纯死循环→好定位好绕过

没开RDP→Flash直接读

绑定值放可写区→被覆盖

srand/rand做密钥派生→可复现

Release留printf UID→串口泄中间值

BootLoader不验签名→密文被换

八、落地顺序

BootLoader独立工程,先跑通跳转App

读UID+派生binding值,参数扇区存绑定

明文段改密文:PC AES加密、BootLoader解密

SHA256完整性校验

UID校验失败进安全模式(限功能/清校准)

开RDP1+WRP

产线脚本按UID逐片烧

高安全场景换eFuse/PUF+签名验签

UID防拷贝的本质不是“固件锁死某颗芯片”这么简单,是把UID变成密钥派生因子,让拷贝出去的bin在新片上解不出正确代码。明文比对只是入门玩具;BootLoader里做UID→key→解密→校验闭环,再配RDP和写保护,才算消费电子量产级。别承诺“不可破解”,靠谱说法是“抄袭成本高于重写成本”——这才是UID方案的真实工程价值。



本站声明: 本文章由作者或相关机构授权发布,目的在于传递更多信息,并不代表本站赞同其观点,本站亦不保证或承诺内容真实性等。需要转载请联系该专栏作者,如若文章内容侵犯您的权益,请及时联系本站删除( 邮箱:macysun@21ic.com )。
关闭