Pada artikel ini saya ingin menulis tentang pengalaman saya dalam membuat bootloader untuk STM32 dengan enkripsi firmware. Saya seorang pengembang individu, jadi kode di bawah ini mungkin tidak mematuhi standar perusahaan apa pun
Dalam proses, tugas-tugas berikut ditetapkan:
- Berikan pembaruan firmware untuk pengguna perangkat dari kartu SD.
- Pastikan kontrol integritas firmware dan kecualikan rekaman firmware yang salah dalam memori pengontrol.
- Berikan enkripsi firmware untuk mencegah kloning perangkat.
Kode ini ditulis dalam Keil uVision menggunakan perpustakaan stdperiph, fatFS, dan tinyAES. Mikrokontroler eksperimental adalah STM32F103VET6, tetapi kode dapat dengan mudah disesuaikan dengan pengontrol STM lainnya. Kontrol integritas disediakan oleh algoritma CRC32, checksum terletak di 4 byte terakhir dari file firmware.
Artikel ini tidak menjelaskan pembuatan proyek, menghubungkan perpustakaan, menginisialisasi periferal dan langkah-langkah sepele lainnya.
Pertama, Anda perlu memutuskan apa itu bootloader. Arsitektur STM32 menyiratkan pengalamatan datar dari memori ketika dalam satu ruang alamat terdapat memori Flash, RAM, register periferal dan yang lainnya. Bootloader adalah program yang dimulai ketika mikrokontroler mulai, memeriksa apakah perlu memperbarui firmware, jika perlu, menjalankannya, dan meluncurkan program utama perangkat. Artikel ini akan menjelaskan mekanisme pembaruan dari kartu SD, tetapi Anda dapat menggunakan sumber lainnya.
Enkripsi firmware dilakukan oleh algoritma AES128 dan diimplementasikan menggunakan perpustakaan tinyAES. Ini terdiri dari hanya dua file, satu dengan ekstensi .c, yang lain dengan ekstensi .h, jadi seharusnya tidak ada masalah dengan koneksinya.
Setelah membuat proyek, Anda harus menentukan ukuran loader dan program utama. Untuk kenyamanan, ukuran harus dipilih dalam beberapa ukuran halaman memori mikrokontroler. Dalam contoh ini, bootloader akan menempati 64 Kb, dan program utama akan menempati sisa 448 Kb. Bootloader akan berlokasi di awal memori flash, dan program utama segera setelah bootloader. Ini harus ditentukan dalam pengaturan proyek di Keil. Bootloader dimulai dengan alamat 0x80000000 (itu darinya STM32 mulai mengeksekusi kode setelah peluncuran) dan memiliki ukuran 0x10000, kami menunjukkan ini dalam pengaturan.

Program utama akan mulai dengan 0x08010000 dan diakhiri dengan 0x08080000 untuk kenyamanan, kami akan mendefinisikan dengan semua alamat:
#define MAIN_PROGRAM_START_ADDRESS 0x08010000 #define MAIN_PROGRAM_END_ADDRESS 0x08080000
Kami juga menambahkan kunci enkripsi dan vektor inisialisasi AES ke program. Kunci-kunci ini dihasilkan secara acak.
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};
Dalam contoh ini, seluruh prosedur pembaruan firmware dibangun sebagai mesin keadaan. Ini memungkinkan proses pembaruan untuk menampilkan sesuatu di layar, mengatur ulang Watchdog dan melakukan tindakan lain apa pun. Untuk kenyamanan, kami akan mendefinisikan dengan status dasar otomat, agar tidak menjadi bingung dalam angka:
#define FW_START 5 #define FW_READ 1000 #define FW_WRITE 2000 #define FW_FINISH 10000 #define FW_ERROR 100000
Setelah menginisialisasi periferal, Anda perlu memeriksa kebutuhan untuk pembaruan firmware. Dalam keadaan pertama, upaya dilakukan untuk membaca kartu SD dan memeriksa keberadaan file di dalamnya.
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; }
Sekarang kita perlu memeriksa kebenaran firmware. Di sini, pertama datang kode verifikasi checksum, dieksekusi ketika file selesai membaca, dan kemudian membaca itu sendiri. Mungkin Anda tidak boleh menulis seperti itu, tulis di komentar apa pendapat Anda tentang hal itu. Membaca dilakukan pada 2 KB untuk kenyamanan bekerja dengan memori Flash, karena STM32F103VET6 memiliki ukuran halaman memori 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; }
Sekarang, jika firmware tidak rusak, maka Anda perlu membacanya lagi, tetapi kali ini tulis ke memori Flash.
case FW_READ + 20:
Sekarang untuk kecantikan, kami akan membuat status untuk penanganan kesalahan dan pembaruan yang berhasil:
case FW_ERROR: { break; } case FW_FINISH: { ExecMainFW(); break; }
Fungsi meluncurkan program utama ExecMainFW () patut dipertimbangkan secara lebih rinci. Ini dia:
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(); }
Segera setelah memulai file startup, ia menginisialisasi ulang semuanya, sehingga program utama harus mengatur kembali pointer ke vektor interupsi di dalam ruang alamatnya:
__disable_irq(); NVIC_SetVectorTable(NVIC_VectTab_FLASH, MAIN_PROGRAM_START_ADDRESS); __enable_irq();
Dalam proyek program utama, Anda perlu menentukan alamat yang benar:

Di sini, pada kenyataannya, seluruh prosedur pembaruan. Firmware diperiksa untuk kebenaran dan dienkripsi, semua tugas selesai. Jika terjadi kehilangan daya selama proses pembaruan, perangkat tentu saja akan rusak, tetapi bootloader akan tetap tidak tersentuh dan prosedur pembaruan dapat diulang. Untuk situasi yang sangat kritis, Anda dapat mengunci halaman di mana loader berada melalui Option bytes untuk ditulis.
Namun, dalam kasus kartu SD, Anda dapat mengatur sendiri di bootloader satu kenyamanan yang menyenangkan. Ketika pengujian dan debugging versi firmware baru selesai, Anda dapat memaksa perangkat itu sendiri untuk mengenkripsi dan mengunggah firmware yang sudah selesai ke kartu SD untuk beberapa kondisi khusus (misalnya, tombol atau pelompat di dalam). Dalam hal ini, tetap hanya menghapus kartu-SD dari perangkat, memasukkannya ke komputer dan meletakkan firmware di Internet untuk menyenangkan pengguna. Kami akan melakukan ini dalam bentuk dua keadaan lagi dari mesin keadaan terbatas:
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; }
Sebenarnya, itulah yang ingin saya sampaikan. Di akhir artikel, saya ingin mengucapkan selamat kepada Anda setelah membuat bootloader seperti itu agar tidak lupa menyertakan perlindungan terhadap pembacaan memori mikrokontroler dalam Opsi byte.
Referensi
tinyAESFatFS