この記事では、ファームウェア暗号化を使用してSTM32のブートローダーを作成した経験について書きたいと思います。 私は個人の開発者なので、以下のコードは企業標準に準拠していない可能性があります
その過程で、次のタスクが設定されました。
- SDカードからデバイスユーザーにファームウェアの更新を提供します。
- ファームウェアの整合性を確実に制御し、コントローラーメモリに誤ったファームウェアを記録しないようにしてください。
- ファームウェアの暗号化を提供して、デバイスの複製を防ぎます。
コードは、stdperiph、fatFS、およびtinyAESライブラリを使用してKeil uVisionで作成されました。 実験用マイクロコントローラーはSTM32F103VET6でしたが、コードは別のSTMコントローラーに簡単に適合させることができます。 整合性制御はCRC32アルゴリズムによって提供され、チェックサムはファームウェアファイルの最後の4バイトにあります。
この記事では、プロジェクトの作成、ライブラリの接続、周辺機器の初期化、その他の簡単な手順については説明していません。
まず、ブートローダーが何であるかを決定する価値があります。 STM32アーキテクチャは、フラッシュメモリ、RAM、周辺レジスタ、その他すべてが同じアドレス空間にある場合、メモリのフラットアドレス指定を意味します。 ブートローダーは、マイクロコントローラーの起動時に起動し、ファームウェアの更新が必要かどうかを確認し、必要に応じて実行し、デバイスのメインプログラムを起動するプログラムです。 この記事では、SDカードからの更新メカニズムについて説明しますが、他のソースを使用できます。
ファームウェアの暗号化は、AES128アルゴリズムによって実行され、tinyAESライブラリを使用して実装されます。 これは、拡張子が.c、拡張子が.hの2つのファイルのみで構成されているため、接続に問題はありません。
プロジェクトを作成したら、ローダーとメインプログラムのサイズを決定する必要があります。 便宜上、サイズはマイクロコントローラのメモリページのサイズの倍数で選択する必要があります。 この例では、ブートローダーは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};
この例では、ファームウェア更新手順全体がステートマシンとして構築されています。 これにより、更新プロセス中に画面に何かを表示したり、Watchdogをリセットしたり、その他のアクションを実行したりできます。 便宜上、オートマトンの基本的な状態を定義して、数字が混乱しないようにします。
#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]; uint32_t idx; char tbuf[64]; FATFS FS; FIL F; case FW_READ: { if(f_mount(&FS, "" , 0) == FR_OK) { if(f_open(&F, "FIRMWARE.BIN", FA_READ | FA_OPEN_EXISTING) == FR_OK) { f_lseek(&F, 0); CRC_ResetDR(); lcd_putstr(" ", 1, 0); idx = MAIN_PROGRAM_START_ADDRESS; fw_step = FW_READ + 10; } else {fw_step = FW_FINISH;} } else {fw_step = FW_FINISH;} 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); CRC_CalcCRC(t); if(CRC_GetCRC() == 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); 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_CalcCRC(aes_buf[t]); } idx+=sizeof(fw_buf); break; }
ファームウェアが破損していない場合は、もう一度読み込む必要がありますが、今回はフラッシュメモリに書き込みます。
case FW_READ + 20:
美しさのために、エラー処理と正常な更新の状態を作成しましょう。
case FW_ERROR: { break; } case FW_FINISH: { ExecMainFW(); break; }
メインプログラムExecMainFW()を起動する機能は、さらに詳しく検討する価値があります。 ここにあります:
void ExecMainFW() { uint32_t jumpAddress = *(__IO uint32_t*) (MAIN_PROGRAM_START_ADDRESS + 4); pFunction Jump_To_Application = (pFunction) jumpAddress; RCC->APB1RSTR = 0xFFFFFFFF; RCC->APB1RSTR = 0x0; RCC->APB2RSTR = 0xFFFFFFFF; RCC->APB2RSTR = 0x0; RCC->APB1ENR = 0x0; RCC->APB2ENR = 0x0; RCC->AHBENR = 0x0; 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カードの場合、ブートローダーで1つの便利な方法を手配できます。 新しいファームウェアバージョンのテストとデバッグが完了したら、デバイス自体を強制的に暗号化して、何らかの特別な条件(たとえば、ボタンやジャンパーなど)のために完成したファームウェアをSDカードにアップロードできます。 この場合、SDカードをデバイスから取り外し、コンピューターに挿入し、ユーザーに喜ばれるようにインターネットにファームウェアを置くだけです。 これを、有限状態マシンのさらに2つの状態の形式で行います。
case FW_WRITE: { if(f_mount(&FS, "" , 0) == FR_OK) { if(f_open(&F, "FIRMWARE.BIN", FA_WRITE | FA_CREATE_ALWAYS) == FR_OK) { CRC_ResetDR(); 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; } memcpy(&fw_buf, (uint32_t *)idx, sizeof(fw_buf)); for(t=0;t<NELEMS(fw_buf);t++) { 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; }
実際、私が伝えたかったのはそれだけです。 記事の最後で、このようなブートローダーを作成した後、オプションバイトでマイクロコントローラーのメモリを読み取ることに対する保護を忘れないようにしてください。
参照資料
tinyAESFatFS