如何加密你的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章專門講了加密,在理論部分進行了對應修改。

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