STM32的加密引导程序

在本文中,我想写一下我为使用固件加密的STM32创建引导加载程序的经验。 我是个人开发人员,因此以下代码可能不符合任何公司标准

在此过程中,设置了以下任务:

  • 通过SD卡为设备用户提供固件更新。
  • 确保控制固件的完整性,并排除控制器内存中记录错误的固件。
  • 提供固件加密以防止设备克隆。

该代码使用stdperiph,fatFS和tinyAES库在Keil uVision中编写。 实验性的微控制器是STM32F103VET6,但是该代码可以轻松地适用于另一个STM控制器。 完整性控制由CRC32算法提供,校验和位于固件文件的最后4个字节中。

本文没有描述项目的创建,连接库,初始化外围设备和其他琐碎的步骤。

首先,您需要确定什么是引导加载程序。 STM32体系结构意味着当闪存,RAM,外设寄存器以及其他所有部件位于同一地址空间时,可以对存储器进行平面寻址。 引导加载程序是在微控制器启动时启动的程序,检查是否有必要更新固件(如果有必要),执行固件并启动主设备程序。 本文将介绍SD卡的更新机制,但您可以使用任何其他来源。

固件的加密由AES128算法执行,并使用tinyAES库实现。 它仅包含两个文件,一个文件的扩展名为.c,另一个文件的扩展名为.h,因此其连接应该没有问题。

创建项目后,您应该确定加载程序和主程序的大小。 为方便起见,应以微控制器存储器页面大小的倍数选择大小。 在此示例中,引导加载程序将占用64 Kb,而主程序将占用其余的448 Kb。 引导加载程序将位于闪存的开头,主程序位于引导加载程序之后。 这应该在Keil的项目设置中指定。 引导加载程序的起始地址为0x80000000(正是STM32在启动后从他那里开始执行代码),其大小为0x10000,我们在设置中对此进行了说明。



为了方便起见,主程序将以0x08010000开始,以0x08080000结尾,我们将定义所有地址:

#define MAIN_PROGRAM_START_ADDRESS 0x08010000 #define MAIN_PROGRAM_END_ADDRESS 0x08080000 

我们还将加密密钥和AES初始化向量添加到程序中。 这些密钥最好随机生成。

 static const uint8_t AES_FW_KEY[] = {0xAF, 0xAF, 0xAF, 0xAF, 0xAF, 0xAF, 0xAF, 0xAF, 0xAF, 0xAF, 0xAF, 0xAF, 0xAF, 0xAF, 0xAF, 0xAF}; static const uint8_t AES_IV[] = {0xFA, 0xFA, 0xFA, 0xFA, 0xFA, 0xFA, 0xFA, 0xFA, 0xFA, 0xFA, 0xFA, 0xFA, 0xFA, 0xFA, 0xFA, 0xFA}; 

在此示例中,整个固件更新过程被构造为状态机。 这样,更新过程就可以在屏幕上显示某些内容,重置看门狗并执行其他任何操作。 为了方便起见,我们将定义自动机的基本状态,以免混淆数字:

 #define FW_START 5 #define FW_READ 1000 #define FW_WRITE 2000 #define FW_FINISH 10000 #define FW_ERROR 100000 

初始化外围设备后,您需要检查是否需要固件更新。 在第一种状态下,尝试读取SD卡并检查其上是否存在文件。

 uint32_t t; /*   */ uint32_t fw_step; /*     */ uint32_t fw_buf[512]; /*      */ uint32_t aes_buf[512]; /*       */ /*     Flash-*/ uint32_t idx; /*     */ char tbuf[64]; /*    sprintf */ FATFS FS; /*   fatFS -   */ FIL F; /*   fatFS -  */ case FW_READ: /*   */ { if(f_mount(&FS, "" , 0) == FR_OK) /*   SD-*/ { /* ,     . */ if(f_open(&F, "FIRMWARE.BIN", FA_READ | FA_OPEN_EXISTING) == FR_OK) { f_lseek(&F, 0); /*     */ CRC_ResetDR(); /*    CRC */ lcd_putstr(" ", 1, 0); /*     */ /*        */ idx = MAIN_PROGRAM_START_ADDRESS; fw_step = FW_READ + 10; /*     */ } else {fw_step = FW_FINISH;} /*    -   */ } else {fw_step = FW_FINISH;} /*   SD- -   */ break; } 

现在我们需要检查固件的正确性。 这里,首先是校验和验证代码,在文件读取完成时执行,然后是读取本身。 也许您不应该这样写,在评论中写下您的想法。 为了方便使用闪存,读取以2 KB进行,因为 STM32F103VET6具有2 Kb的存储器页面大小。

 case FW_READ + 10: /*      */ { /*     ,    */ sprintf(tbuf, ": %d", idx - MAIN_PROGRAM_START_ADDRESS); lcd_putstr(tbuf, 2, 1); if (idx > MAIN_PROGRAM_END_ADDRESS) /*      */ { f_read(&F, &t, sizeof(t), &idx); /*  4    */ /*   4       CRC */ CRC_CalcCRC(t); if(CRC_GetCRC() == 0) /*   0,     */ { /*         */ idx = MAIN_PROGRAM_START_ADDRESS; f_lseek(&F, 0); /*     */ fw_step = FW_READ + 20; /*     */ break; } else { lcd_putstr(" ", 3, 2); /*     */ fw_step = FW_ERROR; /*       */ break; } } f_read(&F, &fw_buf, sizeof(fw_buf), &t); /*  2      */ if(t != sizeof(fw_buf)) /*     */ { lcd_putstr(" ", 3, 2); fw_step = FW_ERROR; /*       */ break; } /*     */ AES_CBC_decrypt_buffer((uint8_t*)&aes_buf, (uint8_t *)&fw_buf, sizeof(fw_buf), AES_FW_KEY, AES_IV); for(t=0;t<NELEMS(aes_buf);t++) /*     CRC */ { CRC_CalcCRC(aes_buf[t]); /*    4  */ } idx+=sizeof(fw_buf); /*     2  */ break; } 

现在,如果固件没有损坏,则需要再次读取它,但这一次将其写入闪存。

 case FW_READ + 20: // Flash Firmware { /*     ,    */ sprintf(tbuf, ": %d", idx - MAIN_PROGRAM_START_ADDRESS); lcd_putstr(tbuf, 4, 2); if (idx > MAIN_PROGRAM_END_ADDRESS) /*     */ { lcd_putstr("", 7, 3); /*     */ f_unlink("FIRMWARE.BIN"); /*     SD- */ fw_step = FW_FINISH; /*   */ break; } f_read(&F, &fw_buf, sizeof(fw_buf), &t); /*   2  */ if(t != sizeof(fw_buf)) /*     */ { lcd_putstr(" ", 3, 3); /*     */ fw_step = FW_ERROR; /*       */ break; } /*     */ AES_CBC_decrypt_buffer((uint8_t*)&aes_buf, (uint8_t *)&fw_buf, sizeof(fw_buf), AES_FW_KEY, AES_IV); FLASH_Unlock(); /*  FLash-   */ FLASH_ErasePage(idx); /*    */ for(t=0;t<sizeof(aes_buf);t+=4) /*    4  */ { FLASH_ProgramWord(idx+t, aes_buf[t/4]); } FLASH_Lock(); /*     */ idx+=sizeof(fw_buf); /*     */ break; } 

现在,为了美观,我们将创建错误处理和成功更新的状态:

 case FW_ERROR: { /*  -     */ break; } case FW_FINISH: { ExecMainFW(); /*    */ /*      */ break; } 

启动主程序ExecMainFW()的功能值得更详细地考虑。 这是:

 void ExecMainFW() { /*       */ /*    ,     */ /* +4  ,          */ uint32_t jumpAddress = *(__IO uint32_t*) (MAIN_PROGRAM_START_ADDRESS + 4); pFunction Jump_To_Application = (pFunction) jumpAddress; /*    APB1 */ RCC->APB1RSTR = 0xFFFFFFFF; RCC->APB1RSTR = 0x0; /*    APB2 */ RCC->APB2RSTR = 0xFFFFFFFF; RCC->APB2RSTR = 0x0; RCC->APB1ENR = 0x0; /*     APB1 */ RCC->APB2ENR = 0x0; /*     APB2 */ RCC->AHBENR = 0x0; /*     AHB */ /*      ,   HSI*/ RCC_DeInit(); /*   */ __disable_irq(); /*     */ NVIC_SetVectorTable(NVIC_VectTab_FLASH, MAIN_PROGRAM_START_ADDRESS); /*    */ __set_MSP(*(__IO uint32_t*) MAIN_PROGRAM_START_ADDRESS); /*     */ Jump_To_Application(); } 

启动启动文件后,它立即重新初始化了所有内容,因此主程序应再次将指针指向其地址空间内的中断向量:

 __disable_irq(); NVIC_SetVectorTable(NVIC_VectTab_FLASH, MAIN_PROGRAM_START_ADDRESS); __enable_irq(); 

在主程序的项目中,需要指定正确的地址:



实际上,这里是整个更新过程。 检查固件的正确性并对其进行加密,所有任务均已完成。 如果在更新过程中断电,则设备当然会积木,但是引导加载程序将保持不变,并且可以重复更新过程。 在特别紧急的情况下,您可以通过Option字节锁定加载程序所在的页面以进行写入。

但是,对于SD卡,您可以在引导加载程序中为自己安排一个很好的便利。 新固件版本的测试和调试完成后,可以出于某些特殊条件(例如,内部的按钮或跳线),强制设备本身对完成的固件进行加密并将其上传到SD卡。 在这种情况下,仅需从设备中取出SD卡,将其插入计算机中,然后将固件放在Internet上即可使用户满意。 我们将以有限状态机的另外两个状态的形式来执行此操作:

 case FW_WRITE: { if(f_mount(&FS, "" , 0) == FR_OK) /*   SD-*/ { /*    */ if(f_open(&F, "FIRMWARE.BIN", FA_WRITE | FA_CREATE_ALWAYS) == FR_OK) { CRC_ResetDR(); /*   CRC */ /*        */ idx = MAIN_PROGRAM_START_ADDRESS; fw_step = FW_WRITE + 10; /*     */ } else {fw_step = FW_ERROR;} /*      */ } else {fw_step = FW_ERROR;} /*      */ break; } case FW_WRITE + 10: { if (idx > MAIN_PROGRAM_END_ADDRESS) /*     */ { t = CRC_GetCRC(); f_write(&F, &t, sizeof(t), &idx); /*       */ f_close(&F); /*  ,   */ fw_step = FW_FINISH; /*   */ } /*  2    Flash-   */ memcpy(&fw_buf, (uint32_t *)idx, sizeof(fw_buf)); for(t=0;t<NELEMS(fw_buf);t++) /*  CRC    */ { CRC_CalcCRC(fw_buf[t]); } /*   */ AES_CBC_encrypt_buffer((uint8_t*)&aes_buf, (uint8_t *)&fw_buf, sizeof(fw_buf), AES_FW_KEY, AES_IV); /*      */ f_write(&F, &aes_buf, sizeof(aes_buf), &t); idx+=sizeof(fw_buf); /*     */ break; } 

实际上,这就是我想说的。 在本文结尾,我希望您在创建了这样的引导加载程序后不要忘记在Option字节中包含防止读取微控制器内存的保护措施。

参考文献


tinyAES
脂肪

Source: https://habr.com/ru/post/zh-CN432966/


All Articles