STM32基于Chip ID的程序加密
很多时候我们为了保护自己的程序不被别人破解,会对自己的程序进行一番加密操作。今天我们来说一下一种常规的手段,基于芯片的唯一ID对程序加密。
基本描述
在STM32的全系列MCU中均有一个96位的唯一设备标识符。在ST的相关资料中,对其功能的描述有3各方面:
- 用作序列号(例如 USB 字符串序列号或其它终端应用程序)
- 在对内部 Flash 进行编程前将唯一 ID 与软件加密原语和协议结合使用时用作安全密钥以提高 Flash 中代码的安全性
- 激活安全自举过程等
96 位的唯一设备标识符提供了一个对于任何设备和任何上下文都唯一的参考号码。用户永远不能改变这些位。96 位的唯一设备标识符也可以以单字节/半字/字等不同方式读取,然后使用自定义算法连接起来。
存储地址
想要读取唯一ID,就需要知道它的存储地址,在不同系列的MCU中地址是有差别的。如下表:
MCU系列 | Unique ID地址 | Flash容量地址 |
---|---|---|
STM32FO | 0x1FFFF7AC | 0x1FFFF7CC |
STM32F1 | 0x1FFFF7E8 | 0x1FFFF7E0 |
STM32F2 | 0x1FFF7A10 | 0x1FFF7A22 |
STM32F3 | 0x1FFFF7AC | 0x1FFFF7CC |
STM32F4 | 0x1FFF7A10 | 0x1FFF7A22 |
STM32F7 | 0x1FF0F420 | 0x1FF0F442 |
STM32L0 | 0x1FF80050 | 0x1FF8007C |
STM32L1 | 0x1FF80050 | 0x1FF8004C(0x1FF800CC) |
STM32L4 | 0x1FFF7590 | 0x1FFF75E0 |
STM32H7 | 0x1FF0F420 | 0x1FF0F442 |
加密实现
接下来是程序加密操作:
首先定义一个任意数的宏定义,用来初步掩藏一下Unique ID地址
#define STM32_ID_D 319286
/*stm32芯片的ID地址,把地址减去一个数,避免汇编里面直接出现ID的地址,
不然很容易暴露加密与ID号有关*/
volatile u32 STM32_ID_addr[3]={0x1ffff7e8 - STM32_ID_D,0x1ffff7ec + STM32_ID_D,0x1ffff7f0 - STM32_ID_D};
然后来读取芯片ID
/**
* @brief Read chip ID.
* @param chip ID
* @retval None
*/
void STM32_Read_ID(uint32_t *p)
{
volatile uint32_t Addr;
Addr = STM32_ID_addr[0] + STM32_ID_D;
p[0] = *(__IO uint32_t*)(Addr);
Addr = STM32_ID_addr[1] - STM32_ID_D + 4;
p[1] = *(__IO uint32_t*)(Addr);
Addr = STM32_ID_addr[2] + STM32_ID_D + 8;
p[2] = *(__IO uint32_t*)(Addr);
}
接着对ID做一个简单的加密,并写入FLASH的某个指定地址。这里的加密算法可以根据实际需要,自己来指定,理论上越复杂越好。
/**
* @brief Encrypted chip ID and save to flash.
* @param None
* @retval None
*/
void STM32_Encrypted_ID(void)
{
uint32_t stm32ID[4],dat;
STM32_Read_ID(stm32ID);
stm32ID[3] = STM32_ID_D;
/*Treated as a 32-bit number*/
dat = (stm32ID[0] + stm32ID[1] - (stm32ID[2]/stm32ID[3])) << 0x01;
FLASH_Unlock();
FLASH_ErasePage(CHIP_ID_ADDRESS); CHPI_ID
FLASH_ProgramWord(CHIP_ID_ADDRESS,dat);
}
最后是在某个特定时候从FLASH读取这个加密结果并用上面的加密方法再计算一次ID,两两比较,设置一些坑。
/**
* @brief Compare encryption ID.
* @param None
* @retval Compare results
* 1:PASS / 0:NOPASS
*/
uint32_t STM32_CMP_Encrypted_ID(void)
{
uint32_t stm32ID[4],dat,dat2;
STM32_Read_ID(stm32ID);
stm32ID[3] = STM32_ID_D;
dat = (stm32ID[0] + stm32ID[1] - (stm32ID[2]/stm32ID[3])) << 0x01;
/*Read PassWord*/
dat2 = *(__IO uint32_t*)(CHIP_ID_ADDRESS);
if(dat == dat2){return 0;}
else {return 1;}
}
要注意的是加密和解密的算法要一样。
/*===================正常运行时==================================*/
if(STM32_CMP_Encrypted_ID())
{
/*
当程序执行到这里的时候,自然是加密校验失败了。在这里面设置一些坑。
比如,来一些累加计数,放到备份寄存器,当程序执行够一定的次数,
擦除一段FLASH,或者让程序跑飞、死循环等等。
*/
}
另外。如果要量产,可以在加密完成后,也可以擦除加密的这段代码。
if(STM32_CMP_Encrypted_ID())
{
/*量产时给一些条件,条件满足就对ID加密,然后把加密结果保存到flash中,
把该程序与芯片的ID,唯一对应起来,加密完后,你也可以让它自宫。*/
if(XX)
{
STM32_Encrypted_ID(); //加密ID
自宫 //即把加密这段代码从flash里面擦除,直接跳出去继续执行
}
}
题外话
这个加密思路是一个学长当时给我讲的,然后具体的实现是参考了网友的一些做法。思路就是对芯片的唯一ID读取,做一个简单的加密计算,保存到一段FLASH。然后在程序执行的时候再读出来,运行一下解密,和芯片ID比较一下,决定下一步的动作。
因为芯片ID是唯一的,所以即使别人能从FLASH读出你的程序,但是烧写到其他芯片上,解密过程是通不过的。这从一定的程度上可以对自己的程序进行保护,但是防那别大神级别的估计还紧张点。
另外就是可以设置读保护和写保护但是如果要做IAP升级的话就有点不方便,个中利弊,自己根据需求衡量就好。
有些方法是真的狠。玩意别人量产了再GG,这是要坑死人的节奏。
想想自己当初读取别人PLC程序的时候,突然间有点惊悚,还好自己只是修机器和学习,并未商业用,不然踩着这种坑就哭不出来了。
这个故事告诉我们,没事别乱破解别人的程序。
至此,所有!
叨叨几句... NOTHING