English Title: Let the microcontroller (such as STM32 MCU) compiler automatically embed the CRC32 value for firmware integrity check in bootloader

说明

这是我多年前开始采用的一种方法,欢迎交流,转载请注明出处。

这种方法通过在FLASH存储空间的最末尾设置两个int32大小的crc32Value和crc32Value2来实现。

几种常见的自检形式

以下是几种常见的嵌入式程序自检情形,先来简述一下其实现方式:

  1. 无bootloader,裸程序运行,程序运行前进行自检;

  2. 有bootloader,bootloader为预先由开发人员(你)烧写到MCU里,复位后先运行bootloader,bootloader进行应用程序的完整性检查,完整性检查失败后,进行bootloader操作。否则,跳转至应用程序;

  3. 有bootloader,或者无bootloader,原始程序里面有加密的字节,根据MCU唯一ID计算出来一个或一组校验值。为表述方便,设这个校验值为Security_ID。对于这种情况需要:

    • 原始程序里有一个setCheckValue()的程序,用以实现在首次上电运行时的初始化计算,同时包含某些加密数据如Security_ID的初始化生成,为程序防复制(防盗)函数提供有效性的检验数据;

    • 一个erase_setCheckValue()的程序,用以实现对setCheckValue()程序的擦除,用以实现对Security_ID计算算法的隐藏,因此就算MCU固件被获取,也难以得知MCU内部数据的校验算法,实现程序的防破解;

    • 和一个setCRC32Value2()的程序,对擦除setCheckValue()后的固件进行校验,将CRC32存储到FLASH中。

    • 具体过程为:

      a) 在编译时将固件CRC32校验值(排除crc32Value和crc32Value2所在地址)写入crc32Value中,此时固件为完整固件,并可根据bootloader功能进行加密;

      b) 将固件下载到MCU中,上电后,bootloader计算固件的CRC32与crc32Value是否相符,如不相符继续判定是否与crc32Value相符,从而确定固件是否有效;

      c) bootloader判定固件有效后,跳转入固件程序。setCheckValue()根据MCU的唯一ID计算出一系列值(记为Security_ID),将其烧写到FLASH内部的存储空间中,用以实现加密校验功能;并且之后,erase_setCheckValue()将setCheckValue()所在地址的程序擦除,然后调用setCrc32Value2()计算新的CRC32值,FLASH在线烧写到crc32Value2。

      d) 之后程序进入正常运行阶段,在程序的正常运行阶段,校验Security_ID和MCU唯一ID值的关系来确定程序合法性。

对于第1、2种情况,程序的完整性校验实现起来简单,因为程序代码在运行时不改变,因而只需要一个crc32Value即可。

对于第3种情况,程序在第一次运行前,需要完整性检查,使用crc32Value即可。程序在第一次运行后,程序本身的代码发生了变化,需要使用另一个值crc32Value2进行检查。

总结第1、2、3种情况,检查过程可以统一为,检查crc32Value的值是否正确,若不正确,检查crc32Value2的值是否正确。若都不正确,则程序错误,进行bootloader错误处理阶段。

编译器设置与CRC32计算

使用Keil MDK时,我们为了方便,需要自动生成crc32Value,下面主要说明编译器的设置,单片机代码端按上述逻辑方式处理即可。

首先,在options里面添加Build后自动运行命令

alt

autocalcrc32.bat文件为

"F:\Program Files\Keil\ARM\ARMCC\bin\fromelf.exe" --bin --bincombined --output="E:\Project\Workspace\test\final.bin" "E:\Project\Workspace\test\IO_Toggle\MDK-ARM\IO_Toggle\IO_Toggle.axf"

"E:\Project\Workspace\test\Libraries\AUTHORIZSTION_LIB\crc32\crc32.exe" "E:\Project\Workspace\test\final.bin" "E:\Project\Workspace\test\Libraries\AUTHORIZSTION_LIB\authorization_auto_generated.c" 393216

第一条,调用fromelf文件,生成二进制程序镜像.bin

第二条,调用crc32.exe生成文件authorization_auto_generated.c,393216为bin文件的大小。

authorization_auto_generated.c为自动生成的文件,为

const unsigned int  program_crc32_chk_value_original __attribute__((used)) __attribute__((at(0x0806FFF8))) = 0xAF1EBAB1;

const unsigned int  program_crc32_chk_value_reserved __attribute__((used)) __attribute__((at(0x0806FFFC))) = 0xFFFFFFFF;

其中,变量program_crc32_chk_value_original为编译时的CRC32值;
变量program_crc32_chk_value_reserved为crc32Value2作用的值。

crc32.exe为我自己编译的程序,如下为文件crc32.c:

/*****************************************************
** Name         : crc32.c
** Author       : www.technblogy.com
** Version      : 2.1
** Date         : 2014-8-24
** Description  : CRC32 Checking For Cortex Embedded
******************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
//#include <errno.h>
//#include <unistd.h>
//#include <fcntl.h>
//#include <sys/stat.h>

/*指定程序的大小*/
/*实际校验字节的大小为DOCSIZE-8,留有两个int32的空间*/
/*DOCSIZE-8应该小于MAXBUFSIZE*/
#define MAXBUFSIZE     1024*1024

static void usage(void);
static int calc_img_crc(const char * in_file, unsigned int * img_crc, unsigned int docsize);

static void usage(void)
{

}

/*我的32位int数据CRC32计算程序,应该与下位机的一致*/
unsigned int CrcGen_INT32(unsigned int crc,unsigned int data[], unsigned int size)
{
  unsigned int i;
    for(i=0;i<size;i++){
        unsigned int temp = data[i];
                unsigned int j;
        for(j=0;j<32;j++){
            if( (crc ^ temp) & 0x80000000 ){
                crc = 0x04C11DB7 ^ (crc<<1);
            }else{
                crc <<=1;
            }
            temp<<=1;
        }
    }
    return crc;
}
/*
**计算大文件的CRC校验码:crc32函数,是对一个buffer进行处理,
**但如果一个文件相对较大,显然不能直接读取到内存当中
**所以只能将文件分段读取出来进行crc校验,
**然后循环将上一次的crc校验码再传递给新的buffer校验函数,
**到最后,生成的crc校验码就是该文件的crc校验码.(经过测试)
*/
unsigned char buf[MAXBUFSIZE];
static int calc_img_crc(const char *in_file, unsigned int *img_crc, unsigned int docsize)
{
    FILE* fd;
    int nread;
        int size;
    int ret;

    /*第一次传入的值需要固定,如果发送端使用该值计算crc校验码,
    **那么接收端也同样需要使用该值进行计算 */
    unsigned int crc = 0x6E59438A;

    fd = fopen(in_file, "rb");
    if (!fd) {
        printf("%d:open %s.\n", __LINE__, strerror(errno));
        return -1;
    }

        size = 0;
    while ((nread = fread(buf, 1, docsize-8, fd)) > 0) {
        crc    = CrcGen_INT32(crc, (int*)buf, nread/4);
                size  += nread;
                break;
    }

    *img_crc = crc;
    printf("calculate first %d bytes of file: ",size);
    close(fd);

    if (nread < 0) {
        printf("%d:read %s.\n", __LINE__, strerror(errno));
        return -1;
    }

    return 0;
}

int main(int argc, char **argv)
{
        FILE* filsave;
        FILE* filver;
        int status;
        unsigned int ver = 0;
        unsigned int verdate = 0;
        unsigned int img_crc;
        const char          *in_file   = argv[1];
        const char          *out_file  = argv[2];
        const unsigned int  docsize    = atoi(argv[3]);
        if (argc < 3) {
                usage();
                exit(1);
        }

        /*Version 号产生程序,做相应更改以适用你自己的代码*/
        {
                int i;
                for(i=0;__DATE__[i];i++)
                {
                        verdate += __DATE__[i];
                        verdate  = verdate ^ (verdate>>1)^ (verdate>>2)^ (verdate>>3)^ (verdate>>4)^ (verdate>>5);
                }
        }

        /*计算CRC32*/
    status = calc_img_crc(in_file, &img_crc, docsize);
    if (status < 0) {
        exit(1);
    }

    printf("[%s] is:\nCRC32:%08X\n", in_file, img_crc);

        /*编译次数文件,计算当前的编译次数*/
          filver = fopen("ver.txt","r");
          if(!filver)
                {
                        filver = fopen("ver.txt","w");
                        fprintf(filver,"%d",0);
                        fclose(filver);
                        filver = fopen("ver.txt","r");
                }
          if(filver)
          {
                  fscanf(filver,"%d",&ver);
                  fclose(filver);
                  ver ++;
                  filver = fopen("ver.txt","w");
                  if(filver)
                          {
                          fprintf(filver,"%d",ver);
                          fclose(filver);
                  }
                  else
                          {
                                  printf("%d:open %s.\n", __LINE__, strerror(errno));
      return -1;
                          }
          }
           else
    {
                printf("%d:open %s.\n", __LINE__, strerror(errno));
                return -1;
    }

        /*生成.c文件*/
    filsave = fopen(out_file,"w");
    if(filsave)
    {
                /*!三个变量的地址需要根据项目进行更改*/
                fprintf(filsave,"const unsigned int  program_crc32_chk_value_original __attribute__((used)) __attribute__((at(0x0806FFF8))) = 0x%08X;\r\n",img_crc);
                fprintf(filsave,"const unsigned int  program_crc32_chk_value_reserved __attribute__((used)) __attribute__((at(0x0806FFFC))) = 0xFFFFFFFF;\r\n");
//                fprintf(filsave,"const unsigned int  CompileTick __attribute__((used)) __attribute__((at(0x0806FFF4))) = %d;\r\n",verdate);
                fclose(filsave);
    }
    else
    {
                printf("%d:open %s.\n", __LINE__, strerror(errno));
                return -1;
    }

        /*输出编译次数,退出*/
    printf("ver = %d\n",ver);

    return 0;
}

crc32.c可以根据需要修改。

这里计算略去了最后的八个字节,因为是program_crc32_chk_value_original和program_crc32_chk_value_reserved的保留空间。并且文件大小需要整字大小。

Keil编译完后,会调用autocalcrc32.bat,计算出CRC32的值之后,更改文件authorization_auto_generated.c中的program_crc32_chk_value_original.

之后需要重新手动编译一遍Keil,使得新的program_crc32_chk_value_original编译到最终的二进制文件里。

本文crc32.c所采用的CRC Parameters

上述CRC32的计算结果,与http://www.zorc.breitbandkatze.de/crc.html 如下的设定一致。即上述crc32.c的CRC parameters为

Alt

Categories: Digital Controller

0 Comments

Leave a Reply

Your email address will not be published.