Chinese title: 让单片机(如STM32)在编译时自动嵌入固件CRC32,以实现Bootloader对固件的完整性检查
Notes
This is a method I started using many years ago. Feedback is welcome; please credit the source if you repost it.
The method reserves two int32-sized values, crc32Value and crc32Value2, at the very end of the FLASH memory.
Common self-check scenarios
Here are several common self-check scenarios for embedded programs, with a brief outline of how each is implemented:
No bootloader: the bare program runs and checks itself before the program starts;
With a bootloader: the developer (you) flashes the bootloader into the MCU beforehand. After a reset, the bootloader runs first and verifies the application’s integrity. If the check fails, it enters bootloader operation; otherwise, it jumps to the application;
With or without a bootloader, the original program contains encrypted bytes: one value or a set of check values is derived from the MCU’s unique ID. For simplicity, call this check value Security_ID. This case requires:
a setCheckValue() routine in the original program, which performs the initialization calculation on the first power-up, including generating encrypted data such as Security_ID, and provides valid check data for the program’s anti-copy (anti-theft) functions;
an erase_setCheckValue() routine, which erases the setCheckValue() routine to conceal the Security_ID algorithm. Even if someone obtains the MCU firmware, it is difficult to work out the check algorithm for the MCU’s internal data, which protects the program from being cracked;
and a setCRC32Value2() routine, which calculates the CRC32 of the firmware after setCheckValue() has been erased and saves it in FLASH.
The procedure is:
a) At compile time, calculate the firmware’s CRC32 (excluding the addresses of crc32Value and crc32Value2) and write it into crc32Value. At this stage the firmware is complete and can be encrypted according to the bootloader’s features;
b) Download the firmware into the MCU. On power-up, the bootloader computes the firmware’s CRC32 and compares it with crc32Value; if they do not match, it compares it with crc32Value2, and so determines whether the firmware is valid;
c) Once the bootloader confirms the firmware is valid, it jumps into the firmware. setCheckValue() computes a series of values from the MCU’s unique ID (call it Security_ID) and writes them into the internal FLASH storage for the encrypted check. Next, erase_setCheckValue() erases the program at the address of setCheckValue(), then calls setCrc32Value2() to calculate a new CRC32 value, which is written to crc32Value2 by in-application FLASH programming.
d) The program then enters normal operation, during which it checks the relationship between Security_ID and the MCU’s unique ID to determine whether the program is legitimate.
For cases 1 and 2, the integrity check is simple to implement: the program code doesn’t change at run time, so a single crc32Value is enough.
For case 3, the program needs an integrity check before its first run, and crc32Value is enough for that. After the first run, the program’s own code has changed, so another value, crc32Value2, is needed for the check.
Combining cases 1, 2 and 3, the check can be unified: check whether crc32Value is correct. If not, check whether crc32Value2 is correct. If neither is correct, the program is faulty and the bootloader enters its error-handling stage.
Compiler settings and CRC32 calculation
With Keil MDK, we want crc32Value to be generated automatically for convenience. The following mainly explains the compiler settings. On the MCU side, just follow the logic above.
First, in Options, add a command that runs automatically after Build

The autocalcrc32.bat file is
"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" 393216The first line calls fromelf to generate the binary image .bin
The second line runs crc32.exe to generate the file authorization_auto_generated.c; 393216 is the size of the bin file.
authorization_auto_generated.c is an automatically generated file:
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;Here, the variable program_crc32_chk_value_original stores the CRC32 value at compile time;
the variable program_crc32_chk_value_reserved holds the value used as crc32Value2.
crc32.exe is a program I compiled myself. Here is the file 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;
}You can modify crc32.c as needed.
The last eight bytes are left out of the calculation, because they are reserved for program_crc32_chk_value_original and program_crc32_chk_value_reserved. The file size must also be a whole number of words.
After Keil finishes compiling, it calls autocalcrc32.bat, calculates the CRC32 value, and updates program_crc32_chk_value_original in authorization_auto_generated.c.
Then build the project again manually in Keil, so that the new program_crc32_chk_value_original is compiled into the final binary.
CRC parameters used by crc32.c in this article
The CRC32 results above match the following settings at http://www.zorc.breitbandkatze.de/crc.html. In other words, the CRC parameters of crc32.c above are

0 Comments