如何加密你的MC9S12(X)设备

版权声明:本文为博主(http://blog.csdn.net/lin_strong)原创文章,转载请标明出处并事先取得博主同意 https://blog.csdn.net/lin_strong/article/details/81015250

前言

我们可以使用BDM设备很方便地烧写单片机程序、查看内存、调试程序。但是反过来想,我辛辛苦苦写的程序,写的算法,别人不也可以使用BDM设备很方便的读出来看么!虽然别人只能看到二进制代码,或者说汇编代码,但是到底经过分析还是能知道你的程序怎么写的,这还得了!!!

幸好,MC9S12(X)提供了加密功能(手册上对应Secure这个单词),BDM是无法读取处于加密状态的单片机的内存的,也就很好的保护了我们的知识成果。

最近我闲着无聊,研究了下这个加密功能,顺便出个小教程分享给大家研究成果,内容基于自己的理解,可能有很多不严谨或者错误的地方,敬请指出。

教程中顺便把之前写的几个小模块串起来演示了一遍。

另外,这里所讲的基于MC9S12XEP100,对于其他衍生产品可能会有一些差别,请自行参照数据手册。

注:此文为本人通过学习MC9S12XEP100数据手册,自行测试而写出,其中所有程序(除了操作系统和工程文件自动生成的部分)完全自己编写,所有图也是自己截取,部分图是从数据手册上截取的。仅供学习交流使用,不得用于商业用途。如需转载请告知本人并注明出处。谢谢!

正文

理论篇

加密的作用

首先要明确的是,没有绝对安全的加密,飞思卡尔的策略只是让无权限用户难以读取FLASH和EEPROM中的内存。

要让你的代码足够安全还需要你写的程序的配合,加密功能是管不了你自己的代码去读取内存往外面报告的。

加密后,S12XE芯片会:
- 保护非易失性内存(Flash,EEPROM)的内容
- 限制NVM命令的执行
- 禁止通过BDM访问内部内存
- 禁止在扩展模式下访问内部Flash/EEPROM
- 禁止CPU和XGATE的调试特性

下图(来自芯片手册第9章)综述了加密对各大功能的影响。


图 1.S12XE的加密对可用特性的影响

直观的看,加密后,√少了好多好多,换句话说,加密对许多功能进行了限制。可以看到BDM那两行在加密之后就只剩下可怜的一个√了,还是限制于只能读取外设寄存器的,所以待会我们成功加密后你会发现程序一烧进单片机,BDM就报错无法通信,内存也读不出来了,不要惊慌,因为就是这样的!!而不管加不加密,都不影响应用程序代码对Flash和EEPROM的访问(第一二行)。

总之,加密后:

普通芯片模式(NS)下
- 完全禁止BDM操作
- Flash和EEPROM命令的执行受限
- 禁止通过DBG模块追踪代码执行
- 禁止调试XGATE代码(断点、单步执行)

特殊芯片模式(SS)下
- 禁用BDM固件命令
- BDM硬件命令受限于寄存器地址
- Flash和EEPROM命令的执行受限。
- 禁止通过DBG模块追踪代码执行
- 禁止调试XGATE代码(断点、单步执行)

扩展模式(NX、ES、EX和ST)下
- 完全禁止BDM操作
- 禁用内部Flash和EEPROM内存
- Flash和EEPROM命令的执行受限
- 禁止通过DBG模块追踪代码执行
- 禁止调试XGATE代码(断点、单步执行)

而NVM命令在加密前后分别有对应的限制,见下图。


图 2.加密对NVM指令的影响

细心的读者肯定发现了图中有两个指令很特别,Erase All Blocks和Unsecure Flash,这两个指令居然只在special模式下可运行,所以虽然我的FTM模块驱动中留了接口,但实际上其实基本是用不到的(注:如果不知道NVM和FTM是什么的话可以把它们当做一个东西,简单来说,FTM模块通过NVM指令来执行各种任务)。

特殊模式指在重置后BDM是激活的

解密方法

有加密总得有解密,不然不连自己都看不到了么。

解密有以下三种方法:

  1. 后面秘钥访问
    通过验证后门秘钥,可以暂时解密,这需要用户应用程序自己设计对外的接口以验证用户提供的秘钥,后面的程序示例了一个简单的接口。
  2. 重编程加密位
    在普通模式下,可以擦除后重编程FLASH配置字段以使芯片(从下次重置后)处于解密状态,但是因为Flash擦除时是一整个扇区擦除的,所以这样会导致0xFE00–0xFFFF (0x7F_FE00–0x7F_FFFF)的内存都被擦除了,里头包含后门秘钥以及如中断向量表等,所以这种方法在普通模式下并不是很推荐。
  3. 特殊模式下完全擦除内存
    在特殊模式下可以通过擦除所有EEPROM和Flash内存来解密芯片。这可以通过BDM来实现(见救救孩子篇),也可以通过(接着BDM时)在应用程序中使用NVM指令Erase All Blocks或Unsecure Flash来实现(注:Unsecure Flash就是通过擦除整个Flash和EEPROM的方式来解密芯片的)。

加密相关寄存器

FLASH配置字段

全局地址 本地地址 大小(字节) 描述
0x7F_FF00 – 0x7F_FF07 0xFF00 – 0xFF07 8 后门密钥
0x7F_FF08 – 0x7F_FF0B 0xFF08 – 0xFF0B 4 保留
0x7F_FF0C 0xFF0C 1 P-Flash 保护字节
0x7F_FF0D 0xFF0D 1 EEE 保护字节
0x7F_FF0E 0xFF0E 1 Flash 非易失字节
0x7F_FF0F 0xFF0F 1 Flash 加密字节

表 1. Flash配置字段

MC9S12XEP100的0xFF00 - 0xFF0F是Flash用于存放配置信息的地方。其中与我们这次内容相关的是第一条和最后一条,其他的我们暂时不去管它。

0xFF00 – 0xFF07这8个字节放的就是我们后门用于解密时对照用的密钥,需要我们烧写进去。

后门密钥的这四个word不能有任一是0x0000或0xFFFF。

而Flash加密字节的功能呢,请看下一章。

可能有的小可爱想:
就8个字节呀,那我暴力破解就好了,穷举所有可能的组合,就是时间问题。
这里有两个问题:
1. 验证后门那个命令每次上电后只能调用一次,失败后哪怕你后面输入的是正确的密码都会报失败,想要重试只能下电重置后再试。 如果你实现了一失败就控制下电重置,暴力破解确实是有可能。
2. 但是另一个问题就是,这个命令只能在normal模式下才能运行,也就是说必须有内置的程序为外部提供接口来输入密钥才行,所以你首先得知道接口是什么,然后就是写接口程序的人设计的好不好的问题了。
为了防止这样的暴力破解,写接口时要设置其他保护措施。比如使用非易失性内存来记录失败次数,连续失败多少次就再也不接受密钥这样的保护措施。

Flash加密寄存器(Flash Security Register,FSEC)

Flash加密寄存器
图 3.Flash加密寄存器

Flash加密寄存器是只读的。

在重置序列中,它会装载上述Flash配置字段中的Flash加密字节,如果这个过程中发现错误,会导致模块进入加密状态,并且禁止使用后门密钥来解密。

此寄存器的字段描述如下:

字段 描述
7–6 KEYEN[1:0] 后门密钥加密使能位 —— KEYEN[1:0] 决定了Flash模块是否启用后门密钥,见表3
5–2 RNV[5:2} 预留非易失位 —— RNV位应该保留在擦除状态(1)以备未来使用
1–0 SEC[1:0] Flash加密位 —— SEC[1:0] 决定了MCU的加密状态,见表4。如使用后门密钥解密了Flash模块,SEC则会被设为10

表 2. FSEC字段描述

KEYEN[1:0] 后面密钥访问状态
00 禁止
01 禁止
10 启用
11 禁止

表 3.Flash KEYEN状态

SEC[1:0] 加密状态
00 加密
01 加密
10 解密
11 加密

表 4.Flash 加密状态

所以为了设置加密你的MC9S12(X),关键就是改写这些这些寄存器的值,实际就是设置Flash配置字段的值。

发起验证密钥命令

为了发起上述验证密钥的命令,需要了解要怎么和FTM模块的内存控制器进行交互,本篇教程不准备深入这个内容,我已经写好了FTM模块驱动,可以直接拿来用。

https://blog.csdn.net/lin_strong/article/details/79938296

注意,此命令执行期间必须保证程序在RAM中运行,否则程序会跑飞。后面的实践篇会介绍方法。

实践篇

好,接下来我们开始加密我们的设备吧!

这里我使用了UCOS-II RTOS为基础开始我们的项目。
https://blog.csdn.net/lin_strong/article/details/80327520

配置 Flash配置字段

先不想接口的事,我们把设备先加密了看看效果。

之前说过,设置密码就是烧写0xFF00处的那8个字节,为了方便起见,我们就把密码设为ASCII字符串,就“ABCDEFGH”把,正好8个字节。

然后呢,参照下几张表,Flash加密字段应该设为 10111101(二进制),也就是 启用后门密钥,加密状态。

然后怎么让IDE知道在那个地址烧写这几个值呢,这就需要用到CodeWarrior的扩展语法了。

volatile const INT8U BackdoorKey[8] @0xFF00 = {'A','B','C','D','E','F','G','H'};  // key: ABCDEFGH
volatile const INT8U fsec @0xFF0F = 0b10111101;   // Enable backdoor key access, secured

就是那个@XXXXX,这个语法用来指定某个变量应该在某个地址。所以这样写我们就指定了0xFF00开始处的8个字节分别为ABCDEFGH,0xFF0F处的值为0b10111101。顺便一提,0b也是CodeWarrior扩展的语法,ANSI C不包含这个语法。

const是因为这是在Flash内存,就是用来放常量的。不写const会不会有什么后果我没试过,感兴趣的可以尝试下。

volatile是告诉编译器,这个变量是真实存在的,不要擅作主张把它优化掉了。但是光写了这个还不是很保险,为了确保这个地址有这几个值,我们还需要打开.prm文件,一般叫做Project.prm,在里头找到ENTRIES这个语法块。这样改:

ENTRIES 
   BackdoorKey;fsec
END

这样链接器就也知道这几个对象不能被丢掉了,确保他们被烧写进去。

然后编译项目。
连接BDM。
烧写项目。
一切顺利。
哎呀!

图 4. 烧写加密的程序后BDM通信失败

这TM什么鬼。怎么通信不上了!!!
诶,怎么memory里全成?了?
怎么重新烧写也烧不了了!

别急,这就是我们想要的效果呀,因为程序被加密了。所以BDM就无法和MCU通信,就看不到内存里的东西了。
我们来急救下,先往下翻到 救救孩子篇,先通信上再说。

定个通信协议

因为只是个教程,我们就简单定个通信协议,使用SCI进行通信。我们的协议是这样的:

每当收到pw这两个字符后,就把后面收到的8个字符作为密钥来验证。

所以pw为帧头,帧长度为10。

然后就是我写的通用接收机登场啦。
https://blog.csdn.net/lin_strong/article/details/80499831

我们按照模块的要求先分配内存空间以及定义函数。

……
static void onFlush(pRX_MAC sender,pRB_BYTE pBuf,INT16U len,RX_STATE state,pRX_FLAG pHorU,pRX_FLAG pEnder);
……
#define RXBUF_SIZE  20
static RX_FLAG RxFlag;
static INT8U  Header[] = {'p','w'};
static INT8U  RxBuffer[RXBUF_SIZE];
static RX_MAC  RxMac;

然后相关的初始化语句差不多长这个样子

  // 初始化帧头标志串
  RX_FLAG_INIT(&RxFlag,Header,2,FLAG_OPTION_HEADER);
  // 初始化接收机,flush时调用onFlush
  RxMac_Init(&RxMac,&RxFlag,1,2,RxBuffer,RXBUF_SIZE,NULL,NULL,onFlush);
  // 一个帧的长度固定为 2(帧头) + 8(数据) = 10
  RxMac_SetRxSize(&RxMac,10);

这样我们就完成了这个协议下接收机的设置。
大概解释下:
首先用RX_FLAG_INIT初始化RxFlag这个标志串变量,设置Header的头两个字符pw为帧头。
然后用RxMac_Init初始化了RxMac这个接收机,让他以RxFlag为标志串来判断帧头帧尾,使用RxBuffer作为缓冲区,flush结果通过onFlush来告知。
然后设置接收机的接收区大小为10,这样找到帧头后收到第8个字符就会触发flush,由于协议中没有帧尾,于是得通过缓冲区满的机制使得接收机flush。

之后每次收到数据时只要调用RxMac_FeedData给接收机传递数据,接收机就会把解析的结果通过onFlush函数告诉我们了。

将程序放到RAM中

之前说过,在执行验证密钥的操作中,程序要一直在RAM中跑,这就要求最起码发起内存控制器指令的那段程序得在RAM中,为了实现这一点,需要把这段程序定位到RAM中。具体来说需要做这些事情:

首先添加数据段,打开prm文件,找到

……
SEGMENTS
……
/* non-paged RAM */
      RAM           = READ_WRITE  DATA_NEAR            0x2000 TO   0x3FFF; 
/* non-banked FLASH */
      ROM_4000      = READ_ONLY   DATA_NEAR IBCC_NEAR  0x4000 TO   0x7FFF; 
      ROM_C000      = READ_ONLY   DATA_NEAR IBCC_NEAR  0xC000 TO   0xFEFF;
 ……
END
PLACEMENT
……
END

改为:

……
SEGMENTS
……
/* non-paged RAM */
      RAM           = READ_WRITE  DATA_NEAR            0x2000 TO   0x3F0F; 
/* non-banked FLASH */
      ROM_4000      = READ_ONLY   DATA_NEAR IBCC_NEAR  0x4000 TO   0x7FFF; 
      ROM_C000      = READ_ONLY   DATA_NEAR IBCC_NEAR  0xC000 TO   0xFE0F;
      RAM_CODE_SEG  = READ_ONLY   DATA_NEAR IBCC_NEAR  0xFE10 TO   0xFEFF   RELOCATE_TO  0x3F10;
 ……
END
PLACEMENT
……
     RAM_CODE          INTO  RAM_CODE_SEG;
……
END

然后,只要在想放到RAM中的函数(的定义和声明)前后加这个语句

#pragma push
#pragma CODE_SEG RAM_CODE
……
(函数)
……
#pragma pop

所有用到这些函数的地方就都知道要到对应的RAM中去调用这个函数了,这里是把函数存在0xFE10到0xFEFF这段地址中,然后重定位到0x3F10开头的RAM中。
因为我的FTM模块中已经写了这个语句了,所以实际上只要修改了prm文件就行。

但是有一点要注意,编译器不会自动帮你把程序拷贝过去,所以需要自己手动拷贝,方法如下:

#define RAM_CODE_RELOCATE   0x3F10

#define __SEG_START_REF(a)  __SEG_START_ ## a
#define __SEG_END_REF(a)    __SEG_END_ ## a
#define __SEG_SIZE_REF(a)   __SEG_SIZE_ ## a
#define __SEG_START_DEF(a)  extern char __SEG_START_REF (a) []
#define __SEG_END_DEF(a)    extern char __SEG_END_REF   (a) []
#define __SEG_SIZE_DEF(a)   extern char __SEG_SIZE_REF  (a) []

__SEG_START_DEF (RAM_CODE);
__SEG_END_DEF   (RAM_CODE);
__SEG_SIZE_DEF  (RAM_CODE);

#define CopyCodeToRAM() \
   memcpy((unsigned char *)RAM_CODE_RELOCATE,(unsigned char *)__SEG_START_REF(RAM_CODE),\
   (unsigned short)__SEG_SIZE_REF(RAM_CODE))

只要调用CopyCodeToRAM()就会完成拷贝工作,需要保证在调用FTM模块的指令之前调用这个函数。

加上FTM模块驱动

现在我们把FTM的驱动添加进去。这用到了我之前写的FTM驱动
https://blog.csdn.net/lin_strong/article/details/79938296

给各位省了不少事吧!
把这个驱动添加到程序中。
修改必要的设置(主要是头文件中的FCLK_DIV参数)后调用

  FTM_Init();

初始化模块。

然后验证密钥只需要使用提供的接口

  FTM_VerifyBackdoorAccessKey

就行了,很方便吧!
所以onFlush中的代码主体就长这个样

void onFlush(pRX_MAC sender,pRB_BYTE pBuf,INT16U len,RX_STATE state,pRX_FLAG pHorU,pRX_FLAG pEnder){
  INT8U err;
  // 只处理找到帧头的情况
  if(state.headerFound != 1)
    return;
  // 将缓冲区中第三个字节开始的8个字节作为KEY来验证
  err = FTM_VerifyBackdoorAccessKey((FTM_KEYGEN *)&pBuf[2]);
  switch(err){
    case FTM_ERR_NONE:
      // 成功
      break;
    case FTM_ERR_ACCERR:
      // 出错
      break;
    default:
      // 出错
      break;
  }
}

加上信道

基本都准备好了,剩下就是收发数据的事情了,为了方便,这里也直接使用我之前写的SCI模块了,当然可以改为其他方式收发数据。

https://blog.csdn.net/lin_strong/article/details/73662524

具体使用参照博客,不是重点,这里就不细说了。
注意设置SCI的中断向量。

最终程序

好了,现在所有部分都拼接好了,因为有各种现成的模块,实现业务时就好像搭积木一样简单。
我们再加上一些提示信息以及设置一些杂项,这个小程序就算完成了。最终的main.c文件如下:

/*
*********************************************************************************************************
*                                                uC/OS-II
*                                          The Real-Time Kernel
*                                               Framework
*
* By  : Lin Shijun
* Note: This is a framework for uCos-ii project with only S12CPU, none float, banked memory model.
*       You can use this framework with same modification as the start point of your project.
*       I've removed the os_probe module,since I thought it useless in most case.
*       This framework is adapted from the official release.
*********************************************************************************************************
*/

#include "includes.h"
#include "FTM.h"
#include "SCI_def.h"
#include "RxMac.h"
/*
*********************************************************************************************************
*                                      REALLOCATE
*********************************************************************************************************
*/
#define RAM_CODE_RELOCATE   0x3F10

#define __SEG_START_REF(a)  __SEG_START_ ## a
#define __SEG_END_REF(a)    __SEG_END_ ## a
#define __SEG_SIZE_REF(a)   __SEG_SIZE_ ## a
#define __SEG_START_DEF(a)  extern char __SEG_START_REF (a) []
#define __SEG_END_DEF(a)    extern char __SEG_END_REF   (a) []
#define __SEG_SIZE_DEF(a)   extern char __SEG_SIZE_REF  (a) []

__SEG_START_DEF (RAM_CODE);
__SEG_END_DEF   (RAM_CODE);
__SEG_SIZE_DEF  (RAM_CODE);

#define CopyCodeToRAM() \
   memcpy((unsigned char *)RAM_CODE_RELOCATE,(unsigned char *)__SEG_START_REF(RAM_CODE),\
   (unsigned short)__SEG_SIZE_REF(RAM_CODE))
/*
*********************************************************************************************************
*                                      FLASH PROTECTION FIELD
*********************************************************************************************************
*/

volatile const INT8U BackdoorKey[8] @0xFF00 = {'A','B','C','D','E','F','G','H'};  // key: ABCDEFGH
volatile const INT8U fsec @0xFF0F = 0b10111101;   // Enable backdoor key access, secured
/*
                                 FSEC Field Descriptions
Field                                                Description
7–6  KEYEN[1:0]     Backdoor Key Security Enable Bits—The KEYEN[1:0] bits define the enabling of backdoor
                     key access to the Flash module as shown in Table 29-11. (10 -> ENABLE)
5–2  RNV[5:2}       Reserved Nonvolatile Bits — The RNV bits should remain in the erased state for future
                     enhancements.
1–0  SEC[1:0]       Flash Security Bits — The SEC[1:0] bits define the security state of the MCU as shown
                     in Table 29-12. If the  Flash module is unsecured using backdoor key access, the SEC 
                     bits are forced to 10. (10 -> UNSECURED)
//*/
/*
*********************************************************************************************************
*                                      STACK SPACE DECLARATION
*********************************************************************************************************
*/
static  OS_STK  AppTaskStartStk[APP_TASK_START_STK_SIZE];

/*
*********************************************************************************************************
*                                      TASK FUNCTION DECLARATION
*********************************************************************************************************
*/

static  void    AppTaskStart(void *p_arg);

/*
*********************************************************************************************************
*                                      LOCAL  FUNCTION  DECLARE
*********************************************************************************************************
*/

static void onFlush(pRX_MAC sender,pRB_BYTE pBuf,INT16U len,RX_STATE state,pRX_FLAG pHorU,pRX_FLAG pEnder);


/* 协议:
   pw为帧头,后面的8个字符代表输入的key
*/
// 接收机
#define RXBUF_SIZE  20
static RX_FLAG RxFlag;
static INT8U  Header[] = {'p','w'};
static INT8U  RxBuffer[RXBUF_SIZE];
static RX_MAC  RxMac;
/*
*********************************************************************************************************
*                                           MAIN FUNCTION
*********************************************************************************************************
*/
void main(void) {
  INT8U  err;  
  BSP_IntDisAll();                        /* Disable ALL interrupts to the interrupt controller     */
  OSInit();                               /* Initialize uC/OS-II                    */

  err = OSTaskCreate(AppTaskStart,
              NULL,
              (OS_STK *)&AppTaskStartStk[APP_TASK_START_STK_SIZE - 1],
              APP_TASK_START_PRIO);           
  OSStart();
}

static  void  AppTaskStart (void *p_arg){
  INT8U c,err;
  (void)p_arg;                      /* Prevent compiler warning  */
  BSP_Init(); 
  // 初始化FTM
  FTM_Init();
  CopyCodeToRAM();

  // 初始化SCI
  SCI_Init(SCI0);
  SCI_EnableTrans(SCI0);
  SCI_EnableRxInt(SCI0);
  SCI_EnableRecv(SCI0);
  SCI_BufferInit();

  // 等待FTM初始化完成
  if(FCLKDIV != (FCLK_DIV | 0x80))    //Check to make sure value is written.
    while(1);
  // 清零标志位(如果初始化中发现错误会导致标志位置位,导致第一次验证误以为出错)
  FSTAT = (FTM_FSTAT_MASK_FPVIOL | FTM_FSTAT_MASK_ACCERR | FTM_FSTAT_MASK_MGSTAT0 | FTM_FSTAT_MASK_MGSTAT1 );
  FERSTAT = 0xFF;

  // 初始化帧头标志串
  RX_FLAG_INIT(&RxFlag,Header,2,FLAG_OPTION_HEADER);
  // 初始化接收机,flush时调用onFlush
  RxMac_Init(&RxMac,&RxFlag,1,2,RxBuffer,RXBUF_SIZE,NULL,NULL,onFlush);
  // 一个帧的长度固定为 2(帧头) + 8(帧尾) = 10
  RxMac_SetRxSize(&RxMac,10);

  SCI_PutCharsB(SCI0,"Hello world.",12,0);

  while (DEF_TRUE){
    // 不断从SCI0获取数据并喂给接收机
    c = SCI_GetCharB(SCI0,0,&err);
    RxMac_FeedData(&RxMac,c);
  }
}

void onFlush(pRX_MAC sender,pRB_BYTE pBuf,INT16U len,RX_STATE state,pRX_FLAG pHorU,pRX_FLAG pEnder){
  INT8U err;
  // 只处理找到帧头的情况
  if(state.headerFound != 1)
    return;
  // 为了防止验证过程中进入中断导致程序跑飞,这里暂时禁止中断
  DisableInterrupts;
  // 将缓冲区中第三个字节开始的8个字节作为KEY来验证
  err = FTM_VerifyBackdoorAccessKey((FTM_KEYGEN *)&pBuf[2]);
  EnableInterrupts;
  switch(err){
    case FTM_ERR_NONE:
      SCI_PutCharsB(SCI0,"\r\nsuccess.",10,0);
      break;
    case FTM_ERR_ACCERR:
      SCI_PutCharsB(SCI0,"\r\nfail.\r\nwrong key or have mismatched.",38,0);
      break;
    default:
      SCI_PutCharsB(SCI0,"\r\nfail.\r\nunknown.",17,0);
      break;
  }
}

杂项说明

首先来看看 onFlush中的这段

  // 为了防止验证过程中进入中断导致程序跑飞,这里暂时禁止中断
  DisableInterrupts;
  // 将缓冲区中第三个字节开始的8个字节作为KEY来验证
  err = FTM_VerifyBackdoorAccessKey((FTM_KEYGEN *)&pBuf[2]);
  EnableInterrupts;

之前说过,在验证操作中,程序只能在RAM中跑,因为我们已经把FTM模块的核心程序移到RAM中了,所以正常不会出问题。但是因为操作系统使用了ECT模块不断发生中断来计时和切换程序,所以有一定的概率会在验证过程中正好发生中断,而中断程序是在flash中的,这样单片机就会跑飞。为了防止这种情况出现,我在调用接口前直接禁用了中断。
另一种解决思路是确保所有可能会在执行期间运行的程序全部在RAM中,这样可能就很复杂了,但是确是是可行的。

  // 等待FTM初始化完成
  if(FCLKDIV != (FCLK_DIV | 0x80))    //Check to make sure value is written.
    while(1);
  // 清零标志位(如果初始化中发现错误会导致标志位置位,导致第一次验证误以为出错)
  FSTAT = (FTM_FSTAT_MASK_FPVIOL | FTM_FSTAT_MASK_ACCERR | FTM_FSTAT_MASK_MGSTAT0 | FTM_FSTAT_MASK_MGSTAT1 );
  FERSTAT = 0xFF;

上面这段呢,则是因为在重置序列中如果探测到错误,比如Flash中的数据校验错误,会导致标志位置位,这样安装FTM模块驱动当前的逻辑,会导致第一次调用时误判。所以清零标志位来避免这种情况。

测试篇

好了,程序准备好了,我们连上232,打开串口调试助手准备开始测试吧。

将程序烧进单片机(喂,这个不用教的吧)

图 5. 程序烧写

搓搓小手。
烧完程序后出现了图 3里的那个提示,BDM连不上单片机了,嗯,就应该是这样的。这时候不管你怎么努力都无法和单片机通信了。

这时我们把HIWAVE(调试器)窗口关掉,要不然会影响单片机工作。然后重置单片机。

在窗口调试助手我们看到了欢迎信息,经典的“hello world”!


图 6.Hello world

然后我们先来测试测试密码错误的情况。按照协议给一个错误密钥。


图 7.密钥错误

可以看到提示失败,然后再来输入正确的密钥试一下!

图 8.只能验证密钥一次

好吧,第一次失败了再试几次都没用的,我们重置下单片机,然后再试正确的密钥。


图 9.密钥正确

成功!
让我们打开HIWAVE来验证下。

直接打开HIWAVE,选择正确的BDM。自动连接上了。


图 10.解密后HIWAVE直接连接

然后就和平常使用调试器时一样了,如果想要把项目加载进来的话,先暂停程序,然后:
TBDML HCS12-> Load-> 找到自己的工程->Load Symbol


图 11.加载工程文件符号

然后整个界面就和你平时使用调试器时一模一样的了。

当然,要是你重启的话就又连不上了。

救救孩子篇

在我们测试加密功能时,一不小心就会把单片机永久加密上,然后又没有留后门,然后就爆炸了,通信不上了!

这时候怎么办呢???

什么,你说换一片就好了? 土豪,我们能做朋友么!

对于我们大部分节俭的人(穷b),肯定是要挽救一下原来的芯片的。具体操作如下:

  1. 该接的接好了,打开HIWAVE,选择对应的BDM,这时候肯定会报错无法通信。
    这里写图片描述
    图12. BDM无法与MCU通信

    莫慌,点Cancel取消
  2. 接下来我们开始用BDM解密,TBDML HCS12->Select HC12 MCU,然后选aotodectect那项,确定。

    图13. 选择MCU

    图14. 选择MCU
  3. TBDML HCS12->Select Derivative选择自己的设备,由于默认就是XEP100,所以其实可以不看这一项,其他衍生版本的话可能要设置下

    图15. 选择衍生型号

    图16. 选择衍生型号
  4. 接下来可能要设置下命令文件,如果你是从工程中启动的HIWAVE的话这步可能就能省略了。TBDML HCS12->Command Files。找到Unsecure选项卡,浏览,随便找个工程文件,里头的cmd文件夹中找到那个TBDMLXXXXXXunsecuredXXXXX.cmd文件,确定。

    图17. 设置命令文件

    图18. 设置命令文件
  5. 然后就是最激动人心的一步了!TBDML HCS12->Unsecure。一开始会让你按照晶振频率进行个设置,然后不管报什么错就一直点“是”就行了,

    图19. 解密
  6. 登登当挡!解密完成! 然后单片机就和新的一样了,又可以为所欲为了。

    图20. 解密成功

如果遇到遇到什么问题了就参照步骤再鼓捣鼓捣,基本两三次内就能成功了,实在搞不定底下留言。

伸手要源码篇

肯定有人想直接要源码,好吧,我直接把工程传到资源上去,一个工程中包含了这么多个模块,实在是太超值了!
https://download.csdn.net/download/lin_strong/10538064

后记

由于能力限制,文中肯定有很多错误或不严谨之处,请各位大佬指出。

更新历史

2018/07/26 突然发现数据手册第9章专门讲了加密,在理论部分进行了对应修改。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章