Implementing Anti-Rollback Protection on IoT Devices (STM32 Case Study)
๐ Introduction
IoT devices today are widely deployed in sensitive environments: industrial automation, healthcare, smart cities, and consumer electronics.
While firmware signing and secure boot are increasingly common, they are insufficient on their own.
The missing puzzle piece? Anti-Rollback Protection (ARP).
An attacker who gains access to firmware flashing (via UART, SWD, OTA, or malicious supply chain injection) may attempt to downgrade the device to a previously vulnerable firmware version.
Without ARP, the device will happily accept it. This allows exploitation of known CVEs even after the vendor patches them.
This blog demonstrates a practical STM32F4 implementation of ARP, going beyond minimal code into cryptographic validation, MCU flash protection mechanisms, secure version storage, and CI/CD integration.
๐ก๏ธ Threat Model
Attacker Capabilities
- Access to firmware images (old + new).
- Physical access to device interfaces (UART/SWD).
- Ability to initiate OTA downgrade (if server compromised).
Goals of Attacker
- Downgrade to old firmware that:
- Exposes vulnerable network services.
- Has weaker crypto (e.g., MD5-based authentication).
- Contains debug backdoors.
Defender Goals
- Guarantee device never executes firmware older than last installed valid version.
- Prevent persistent downgrades even across power cycles, resets, or flash erasure attempts.
๐๏ธ System Architecture
-
Secure Bootloader
- Trusted root of execution at
0x08000000. - Verifies integrity, authenticity, and monotonic version before booting.
- Trusted root of execution at
-
Firmware Packaging Toolchain
- Vendor signs firmware image with ECDSA P-256.
- Prepends metadata header.
-
Version Counter Storage
- Stored in:
- Flash protected sector (STM32F4 example: sector 11).
- Option Bytes / OTP memory for stronger guarantees.
- Or external secure element (e.g., Microchip ATECC608A) for monotonic counters.
- Stored in:
-
Update Workflow
- Device compares version in header vs stored version.
- If
fw_version < stored_version, bootloader halts or enters recovery.
๐ฆ Firmware Header Format
We define a cryptographically bound header:
typedef struct __attribute__((packed)) {
uint32_t magic; // Magic constant 0xAABBCCDD
uint32_t fw_version; // Monotonic firmware version
uint32_t fw_size; // Application firmware size
uint8_t fw_hash[32]; // SHA-256 hash of firmware
uint8_t fw_signature[64]; // ECDSA P-256 signature
} firmware_header_t;
Why these fields?
- Magic: Guards against random flash garbage.
- fw_version: Core anti-rollback parameter.
- fw_size: Prevents buffer overflows during verification.
- fw_hash: Ensures firmware integrity.
- fw_signature: Ensures authenticity (signed by vendor private key).
๐ Cryptography Considerations
- Hash: SHA-256 chosen (NIST approved, resistant to collisions).
- Signature: ECDSA P-256 is lightweight enough for MCUs, widely standardized (FIPS 186-4).
- Key Management:
- Private key: held securely by vendor, used in CI/CD pipeline.
- Public key: hardcoded in bootloader, optionally burned into OTP/RO memory.
โก Bootloader Implementation (STM32F4 Example)
The secure bootloader runs on reset, verifies firmware, and enforces anti-rollback.
#include "stm32f4xx_hal.h"
#include "crypto.h" // SHA256 + ECDSA libraries
#define MAGIC_CONST 0xAABBCCDD
#define VERSION_ADDR 0x0800FC00 // Reserved sector for version counter
#define APP_START_ADDR 0x08004000
typedef void (*app_entry_t)(void);
typedef struct {
uint32_t magic;
uint32_t fw_version;
uint32_t fw_size;
uint8_t fw_hash[32];
uint8_t fw_signature[64];
} firmware_header_t;
firmware_header_t *fw_hdr = (firmware_header_t *) APP_START_ADDR;
uint32_t read_stored_version(void) {
return *((uint32_t *)VERSION_ADDR);
}
void write_stored_version(uint32_t version) {
HAL_FLASH_Unlock();
FLASH_Erase_Sector(FLASH_SECTOR_11, VOLTAGE_RANGE_3);
HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, VERSION_ADDR, version);
HAL_FLASH_Lock();
}
int verify_firmware(void) {
if (fw_hdr->magic != MAGIC_CONST) return -1;
uint8_t calc_hash[32];
sha256((uint8_t *)(APP_START_ADDR + sizeof(firmware_header_t)),
fw_hdr->fw_size, calc_hash);
if (memcmp(calc_hash, fw_hdr->fw_hash, 32) != 0) return -2;
if (!ecdsa_verify(fw_hdr->fw_signature, fw_hdr->fw_hash)) return -3;
uint32_t stored_version = read_stored_version();
if (fw_hdr->fw_version < stored_version) return -4;
return 0;
}
void jump_to_app(void) {
app_entry_t app_entry = (app_entry_t) (*((uint32_t *)(APP_START_ADDR + 4)));
__set_MSP(*(uint32_t *)APP_START_ADDR);
app_entry();
}
int main(void) {
HAL_Init();
int res = verify_firmware();
if (res == 0) {
write_stored_version(fw_hdr->fw_version);
jump_to_app();
} else {
while(1) {
// Optionally blink LED or enter DFU recovery
}
}
}
๐งช Testing & Debugging Rollback Protection
-
Initial Boot (v1.0)
- Flash bootloader + v1.0 firmware.
- Device boots โ version counter = 1.
-
Upgrade (v1.1)
- Flash v1.1 with version = 2.
- Device boots โ version counter = 2.
-
Rollback Attempt (v1.0)
- Flash v1.0 again.
- Bootloader compares 1 < 2 โ Execution blocked.
-
Tampering Attempt
- Modify header version number.
- Signature check fails โ Execution blocked.
๐ ๏ธ MCU Hardening Techniques
- Flash Write Protection: Lock version storage sector (using STM32 Option Bytes).
- Readout Protection (RDP): Prevent attacker from dumping public key or modifying bootloader.
- Secure Debug: Disable SWD/JTAG after provisioning.
- External Secure Element: Use devices like ATECC608A to maintain tamper-proof monotonic counters.
๐ CI/CD Integration
Anti-rollback protection should be part of the firmware release pipeline:
- Build Step: Compile firmware.
- Signing Step: Tool calculates SHA-256, signs with vendor private key.
- Header Injection: Append firmware header.
- Version Control: CI automatically increments firmware version.
- Deployment: Release OTA package with metadata.
โ Conclusion
Rollback attacks are an underrated threat in IoT ecosystems.
By combining:
- Signed firmware headers,
- Monotonic version counters,
- MCU flash protections,
- CI/CD enforcement,
โฆwe create a robust Anti-Rollback Protection framework.
The STM32 example here can be generalized to:
- ESP32 (using eFuse counters),
- NXP i.MX (using HAB + secure counters),
- Nordic nRF52 (using UICR + firmware signing).
Bottom line: Firmware downgrades should be treated with the same severity as unsigned code execution.