Implementing Anti-Rollback Protection on IoT Devices (STM32 Case Study)

Mr-IoTMr-IoT
September 22, 2024
5 min read

๐Ÿ”’ 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

  1. Secure Bootloader

    • Trusted root of execution at 0x08000000.
    • Verifies integrity, authenticity, and monotonic version before booting.
  2. Firmware Packaging Toolchain

    • Vendor signs firmware image with ECDSA P-256.
    • Prepends metadata header.
  3. 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.
  4. 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

  1. Initial Boot (v1.0)

    • Flash bootloader + v1.0 firmware.
    • Device boots โ†’ version counter = 1.
  2. Upgrade (v1.1)

    • Flash v1.1 with version = 2.
    • Device boots โ†’ version counter = 2.
  3. Rollback Attempt (v1.0)

    • Flash v1.0 again.
    • Bootloader compares 1 < 2 โ†’ Execution blocked.
  4. 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:

  1. Build Step: Compile firmware.
  2. Signing Step: Tool calculates SHA-256, signs with vendor private key.
  3. Header Injection: Append firmware header.
  4. Version Control: CI automatically increments firmware version.
  5. 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.


๐Ÿ“š References