找回密码
 立即注册

QQ登录

只需一步,快速开始

搜索
查看: 58431|回复: 14
收起左侧

STM32通过读取芯片唯一ID号来实现程序的保护,防止被抄袭

  [复制链接]
ID:69862 发表于 2016-1-21 14:50 | 显示全部楼层 |阅读模式
大家好!我叫肖亚平,从小热爱上了电子技术,读书时阴差阳错的选择了电子这方面的专业,学习电子技术、一直到今年毕业。对于我的理解来说,学校里面学到的技术不是全部实用,但是必须有用,所以一直奋斗在前线。我对学习总结出一句话“压力不是有人努力,而是比你牛X N倍的人依然在努力

STM32通过读取芯片唯一ID号来实现程序的保护,防止被抄袭。STM32芯片通过STlink可以把程序读出来。只要在相同的芯片上面,就可以跑起来,那么如何才能让一个程序只能够在一个芯片上正常运行呢?
经过一个下午的时间,总结出这个方法。通过读取ID号,然后在修改HEX文件来保护自己的程序不被抄袭,相对于开发成本中等的产品,还是有用的。但是,有一点麻烦。我用的是STM32F103ZET6芯片。下面将介绍详细的步骤和方法。

一.获取ID

  1. 工欲善其事,必先利其器,准备好工具,需要一个STlink及下载软件,一个可以正常运行的硬件。
  2. uint16_t temp[12];   //存放芯片ID的临时变量
  3. uint16_t aa[12] = {0xee,0x01,0x02,0x03,0x04,
  4.                0x05,0x06,0x07,0x08,0x09,
  5.                0x10,0x11};//预置ID号,在HEX文中修改。

  6. aa这个数组用于存放预置ID号
复制代码



  1. /******************************************************************************
  2. 函数名称:读取芯片唯一ID码
  3. 创建时间:2015-08-11
  4. 修改时间:2015-08-11
  5. 备    注:
  6. ******************************************************************************/

  7. void Get_ChipID(void)
  8. {
  9.     u32 temp0,temp1,temp2;
  10. temp0 = *(__IO u32*)(0x1FFFF7E8);    //产品唯一身份标识寄存器(96位)
  11.     temp1 = *(__IO u32*)(0x1FFFF7EC);
  12. temp2 = *(__IO u32*)(0x1FFFF7F0);
  13.                                  
  14. //ID码地址: 0x1FFFF7E8   0x1FFFF7EC  0x1FFFF7F0 ,只需要读取这个地址中的数据就可以了。

  15.     temp[0] = (u8)(temp0 & 0x000000FF);
  16.     temp[1] = (u8)((temp0 & 0x0000FF00)>>8);
  17.     temp[2] = (u8)((temp0 & 0x00FF0000)>>16);
  18.     temp[3] = (u8)((temp0 & 0xFF000000)>>24);
  19.     temp[4] = (u8)(temp1 & 0x000000FF);
  20.     temp[5] = (u8)((temp1 & 0x0000FF00)>>8);
  21.     temp[6] = (u8)((temp1 & 0x00FF0000)>>16);
  22.     temp[7] = (u8)((temp1 & 0xFF000000)>>24);
  23.     temp[8] = (u8)(temp2 & 0x000000FF);
  24.     temp[9] = (u8)((temp2 & 0x0000FF00)>>8);
  25.     temp[10] = (u8)((temp2 & 0x00FF0000)>>16);
  26.     temp[11] = (u8)((temp2 & 0xFF000000)>>24);         
  27. }
复制代码




  1. 通过void Get_ChipID(void)就可以得到ID码了,这个ID码可以用串口输出,也可以用STM32 ST-LINK Utility读出来。这里,两种方法我都会讲到。

  2. 方法一:通过串口输出得到ID码。

  3. /****************************************************************************************************************************
  4. 函数名称:串口2初始化配置
  5. 创建时间:2015-08-11
  6. 修改时间:2015-08-11
  7. 备    注:
  8. *****************************************************************************************************************************/
  9. void USART2_Config(void)
  10. {
  11. GPIO_InitTypeDef   GPIO_InitStructure;  //定义GPIO类型结构体
  12. USART_InitTypeDef  USART_InitStructure; //定义串口类型结构体
  13. RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //配置GPIOA时钟,并使能时钟。
  14. RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2,ENABLE);//配置USART2的时钟,并使能时钟
  15. GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;       //配置PA.02作为TXD
  16. GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //配置成推挽输出
  17. GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;//配置GPIO时钟为50MHZ
  18. GPIO_Init(GPIOA, &GPIO_InitStructure);           //初始化GPIO
  19. GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;  //配置PA.03作为RXD
  20. GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;  //配置GPIO输入浮空
  21. GPIO_Init(GPIOA, &GPIO_InitStructure);  //初始化GPIO
  22. USART_InitStructure.USART_BaudRate = 115200;  //配置串口波特率为115200
  23. USART_InitStructure.USART_WordLength = USART_WordLength_8b; //8位数据位
  24. USART_InitStructure.USART_StopBits = USART_StopBits_1; //1位停止位
  25. USART_InitStructure.USART_Parity = USART_Parity_No; //
  26. USART_InitStructure.USART_HardwareFlowControl = T_HardwareFlowControl_None; //硬件流程控制
  27. USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; //启动发送和接收
  28. USART_Init(USART2, &USART_InitStructure); //初始化串口2

  29. USART_ITConfig(USART2, USART_IT_RXNE, ENABLE); //使能串口2接收中断
  30. USART_Cmd(USART2, ENABLE); //使能外部中断
  31. }
复制代码





  1. /****************************************************************************************************************************
  2. 函数名称:USART2发送数据函数
  3. 创建时间:2015-08-11
  4. 修改时间:2015-08-11
  5. 备    注:
  6. *****************************************************************************************************************************/
  7. void Usart2_SendData(uint16_t uiSendDataNumber,uint16_t * uiData)
  8. {
  9.   static uint16_t uiTempData = 0; //发送数据临时变量
  10. for(uiTempData = uiSendDataNumber;uiTempData > 0;uiTempData--) //数据的个数
  11. {
  12. USART_SendData(USART2, *uiData++); //调用发送函数
  13. //           uiData++; //发送数据的地址加1,切换到下一个要发送数据的地址。
  14. while(USART_GetFlagStatus(USART2, USART_FLAG_TXE) == RESET);//发送缓冲区空状态标志位。只有当缓冲区为空时,才发送下一个数据。
  15.   }
  16. }
复制代码




串口配置好后,就可以调用Usart2_SendData(12,temp);函数,就能在串口助手看到ID码了。
图片1.png


  1. 芯片唯一ID码:32 FF DA 05 43 41 38 36 30 71 02 43   96位
复制代码

方法二:通过STlink得到ID码。

1、打开STM32 ST-LINK Utility,确保STlink与硬件连接正常。
2、Address中输入“0x1FFFF7E8”芯片ID码的首地址。输入完成后,STlink自动读取ID码。
图片2.png




3、确定后,自动读取ID
图片3.png

但是,用STlink读出来的只有64位,少了32位,这个“0x1FFFF7F0”地址中读取失败,我也不知道是什么原因,可能是厂家不允许下载器访问。


地  址
0x1FFFF7E8
0x1FFFF7EC
0x1FFFF7F0
ID数据
05 DA FF 32
36 38 41 43
空(不允许读)

我们把上面ID码整理一下:32 FF DA 05 43 41 38 36,由于在内存中是小端存储方式。所以是这样的。

通过两种方法,把我们想要的ID码得到后。就可以进行最关键的一步,修改HEX文件。


二.修改HEX文件加密

1、打开STM32 ST-LINK Utility打开HEX文件
图片4.png

  1. uint16_t  aa[12] = {0xee,0x01,0x02,0x03,0x04,
  2.                0x05,0x06,0x07,0x08,0x09,
  3.                0x10,0x11};//预置ID号,在HEX文中修改。
复制代码



现在就打HEX文件打开后。找到上面定义的数组,这个是关键所在,我花了一个下午时间,才搞清楚。



[size=9.0000pt]2、找到自定义ID在地址的位置后,开始修改。
图片5.png




3、把数组“aa[12]” 里面的内容改成与芯片的实际ID码一致。如果程序被读出来,在另外一片相同的芯片上面也运行不起来。因为设置的ID与芯片实际ID不一致。
图片6.png



4、现在如果下载,会提示你文件被修改,需要保存。我们保存成HEX文件到桌面。
图片7.png


5、重新打开刚才保存的HEX文件。现在可以看到,修改的ID已经在HEX文件里面了。
图片8.png




6、下载程序到单片机。
图片9.png

7、完成下载。到这儿就基本完成了。

8、看到这儿,有的人会问,为什么不在编译时直接放在数组里,还要在HEX文件中修改,这样做的目的是方便批量生产。


图片10.png

  1. 在程序中,我做了一个简单的判断。如下
  2.   Get_ChipID();  //先读出芯片实际ID         
  3.   for(i=0;i<12;i++)
  4.         {
  5.                         if(temp == aa) //判断设置的ID与实际ID是不是一致。如果一致。LED灯不亮
  6.                         {
  7.                                 ;
  8.                         }
  9.                         else
  10.                         {
  11.                                  GPIO_ResetBits(GPIOF, GPIO_Pin_6);
  12.             }
  13.   }
复制代码





评分

参与人数 1黑币 +50 收起 理由
admin + 50 共享资料的黑币奖励!

查看全部评分

回复

使用道具 举报

ID:12816 发表于 2016-8-10 21:34 | 显示全部楼层
人家写保护了也不容易读出来吧,还考虑ID
回复

使用道具 举报

ID:251314 发表于 2017-11-21 09:17 | 显示全部楼层
我怎么感觉那么麻烦噶
回复

使用道具 举报

ID:341087 发表于 2018-6-14 08:32 | 显示全部楼层
您只要提供hex文件(为了保密,可以把一些功能注销了),我反汇编后找出程序id加密的缺陷,相当于对您的程序id加密进行测试
欢迎交流
回复

使用道具 举报

ID:608322 发表于 2019-9-9 12:07 | 显示全部楼层
//STM32F10X软加密方法及实例代码

#define ID_ENCRYPT_EOR_RESULT_ADDRESS (0x0800F000)
#define ID_ENCRYPT_ADD_RESULT_ADDRESS (0x0800F004)
volatile uint32 gU32IdAdressVar;//这里一定要定义此变量,否则会被优化器优化掉
void Stm32F10xEncryptDemo(void)
{
        uint32 *u32IdAddress;
  uint32 u32EorRslt, u32AddRslt;
        #IF 0
        //如果直接赋值0X1FFFF7E8,则程序编译结果里会有0X1FFFF7E8,这样破解人员会很轻松
        //的找到这个内容,然后非常容易进行修改,去掉软加密
        u32IdAddress = (uint32*)0x1ffff7e8;
        #else
        //千万别显式的读取ID,即要把0X1FFFF7E8运算成隐式的,例如此例中0x1FFFF7E8 = (0x455873a * 4) + 0xEA9DB00;
        //这样,别人就算破解出了你的程序,也查找不到0X1FFFF7E8,这样就不能轻易的软解密,这样处理后如果要软解密,
        //一定要反汇编出来进行复杂逆向分析,难度极大,代价极高,很难搞定软加密了,达到保护产品的目的。
        gU32IdAdressVar = 0x455873a;
        gU32IdAdressVar <<= 2;//0x11561CE8
        u32IdAddress = (uint32*)(gU32IdAdressVar + 0xEA9DB00);//0x1ffff7e8
        #endif
        //读取单片机的ID,并进行运算,具体算法可以自己定,这里只用到简单的异或及和运算
        u32EorRslt = (*u32IdAddress) ^ (*(u32IdAddress + 1)) ^ (*(u32IdAddress + 2));
        u32AddRslt = (*u32IdAddress) + (*(u32IdAddress + 1)) + (*(u32IdAddress + 2));
        //进行对比,如果运算结果与FLASH保存的结果不一样,说明非法,运行错误代码
        if(u32EorRslt != *((uint32*)ID_ENCRYPT_EOR_RESULT_ADDRESS))
        {
                while(1);//异或算法结果不正确,进行错误分支
        }
        if(u32AddRslt != *((uint32*)ID_ENCRYPT_ADD_RESULT_ADDRESS))
        {
                while(1);//和算法结果不正确,进行错误分支
        }
}
//QQ9272078
回复

使用道具 举报

ID:608322 发表于 2020-6-29 08:36 | 显示全部楼层
现在stm32f103破解出程序只要1000块,如果用id做软件加密,id地址直接出现的,只要500就可以改软件加密
回复

使用道具 举报

ID:757976 发表于 2020-6-29 08:49 | 显示全部楼层
ID加密也可能是个好方法
回复

使用道具 举报

ID:73435 发表于 2020-6-29 09:17 | 显示全部楼层
有点麻烦了,而且不太实用,生产的时候不可能每一片ID都查出来然后改烧录文件烧录
回复

使用道具 举报

ID:608322 发表于 2020-7-1 14:30 | 显示全部楼层
294479435 发表于 2020-6-29 09:17
有点麻烦了,而且不太实用,生产的时候不可能每一片ID都查出来然后改烧录文件烧录

1,如果板子上有外部存储器,可以先编写一个程序,利用算法把id计算得到一些值存入外部存储器,然后再烧写真正的程序,真正的程序去校验外部存储器的数据是否合法即可

2,利用板子上按键组合,或是上电按住某些键,程序在这个时候利用算法把id计算得到一些值存入程序区(stm8为EE区),程序运行时去验证程序区数据是否正确

3,轩微编程器有软件加密的功能,编程器会读芯片id,根据算法直接改写缓冲区,达到软件加密的作用

4,读出的id通过一定算法,例如异或加上一个数,得到的数据存入flash(只运行一次,运行后标志位也存入flash),下次读到这个标志位,就不运行这个程序。

四、做软件加密时注意
1,不要在程序中直接出现id地址,例如STM32:1FFFF7E8 1FFFF7EC 1FFFF7F0   STM8: 0x4865~0x4870
2, 利用校验和或是crc对程序区进行校验,防止改程序

评分

参与人数 1黑币 +40 收起 理由
admin + 40 回帖助人的奖励!

查看全部评分

回复

使用道具 举报

ID:595237 发表于 2020-7-1 21:07 | 显示全部楼层
加密与解密都是相互相成的。

懂功能程序的人基本上也可以根据功能来写出程序,
不懂程序的人也不一定能够仿出来,

如果能够有人仿自己写出来这个程序的产品,证明自己还可以,
有创新的头脑,如果都是自己和人家一样仿来仿去的,
说明只是停留在仿的位置。


回复

使用道具 举报

ID:595237 发表于 2020-7-1 21:18 | 显示全部楼层
多多创新一定能够赚取更多钱,
停留在一个上面保护,
也不一定能够赚取比创新赚钱来的多。

每位程序员工程师的数字头脑都比老板灵敏的多,
但是现实中都是老板赚钱比工程师多多nn倍,这是为什么?
回复

使用道具 举报

ID:367957 发表于 2021-6-23 14:31 | 显示全部楼层
meifan2010 发表于 2019-9-9 12:07
//STM32F10X软加密方法及实例代码

#define ID_ENCRYPT_EOR_RESULT_ADDRESS (0x0800F000)

没用的。抄出来可以反汇编然后进行仿真的。一步步走怎么都会把你的破掉
回复

使用道具 举报

ID:367957 发表于 2021-6-23 14:32 | 显示全部楼层
juncedz 发表于 2020-7-1 21:18
多多创新一定能够赚取更多钱,
停留在一个上面保护,
也不一定能够赚取比创新赚钱来的多。

因为你就是程序员
回复

使用道具 举报

ID:949399 发表于 2021-7-5 15:00 | 显示全部楼层
芯片读出的HEX文件可以反汇编出来吗,
回复

使用道具 举报

ID:65369 发表于 2021-12-2 10:54 | 显示全部楼层
juncedz 发表于 2020-7-1 21:18
多多创新一定能够赚取更多钱,
停留在一个上面保护,
也不一定能够赚取比创新赚钱来的多。

就是这个道理,你是程序员
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

手机版|小黑屋|51黑电子论坛 |51黑电子论坛6群 QQ 管理员QQ:125739409;技术交流QQ群281945664

Powered by 单片机教程网

快速回复 返回顶部 返回列表