Author Archives: Shafeeque Olassery Kunnikkal

  1. Building a Secure Boot Chain on the STM32F411 (Part 2): Validation, Hidden Triggers, and Reverse Engineering

    Leave a Comment

    This post is part of my STM32F411 Secure Boot Lab series, a hands-on embedded security project built around a Black Pill board, a compact cryptographic bootloader, and a reusable firmware analysis workflow. Across the series, I cover authenticated boot, fail-closed validation, hidden trigger engineering, and reverse engineering preparation in Ghidra.

    Series page: STM32F411 Secure Boot Lab series

    Project repository: This lab is part of my IoT_Projects GitHub repository, where I collect IoT, embedded security, and firmware research projects.

    Part 1 of this project ended with a major milestone: a working secure boot chain on the STM32F411CEU6 Black Pill. The bootloader validated the image header, checked the application vectors, verified the SHA-256 and ECDSA-P256 signature path, and cleanly jumped to the diagnostic application.

    That was a satisfying result, but it was not the end of the work.

    A secure boot lab becomes far more useful once it moves beyond the happy path. It is one thing to show that valid, signed firmware boots. It is another to show, through controlled testing, that malformed, modified, or otherwise untrusted firmware does not.

    In this second phase, I shifted from implementation to validation. I wanted to pressure-test the fail-closed behavior of the boot chain, finalize an interrupt-driven hidden trigger inside the payload, and shape the platform into a realistic target for binary diffing and reverse engineering in Ghidra.

    Because in embedded security, a working demo is only half the story. The other half is whether the system rejects bad input in the right places, for the right reasons, and in a way that can be analyzed later.

    STM32F411 Black Pill connected to a Tigard interface during secure boot validation and reverse engineering workflow preparation
    STM32F411 validation setup used while extending the lab toward hidden-trigger analysis and reverse engineering workflows.

    Why Validation Matters

    In security engineering, success cases are not enough.

    A bootloader that accepts valid firmware is useful, but only partially convincing. A trust chain becomes more meaningful when it predictably rejects invalid firmware. Validation has to answer the harder questions. What happens if a single byte changes in the packed image? What happens if the signature metadata is malformed? What happens if the image is flashed to the right location but no longer matches the embedded public key?

    The first major validation task for this phase was deliberately simple: modify the packed firmware and confirm that the bootloader refused to continue.

    By flashing an image with an intentionally corrupted signature, I was able to observe the bootloader fail closed and prevent the application from running. That matters because it moves the project from “the good path works” to “the bad path is enforced.”

    Defining and documenting these rejection cases is what starts turning a working implementation into a real security boundary.

    Engineering the Payload: An Interrupt-Driven Hidden Trigger

    To make the lab more realistic as an analysis target, the application running behind the secure bootloader needed to be more than a blinking LED and a visible console. It needed a hidden behavior that could later be hunted down from the compiled binary.

    So I introduced an intentional trigger into the application’s UART path.

    While the main loop continues printing its regular output, a hardware interrupt in USART1_IRQHandler listens to the RX buffer in the background. If it sees the exact six-byte sequence REBOOT, it sets a volatile flag and causes the main loop to call NVIC_SystemReset().

    This was not added as a stealth feature for deployment. It was added as a controlled lab artifact: something small, intentional, and useful for later reverse engineering.

    Getting that UART RX path working exposed a classic embedded systems issue.

    At one stage, the application would boot, print its success banner, and then appear to ignore UART input completely. The problem was not the payload logic itself. The problem was inherited CPU state.

    Right before handing execution over to the application, the secure bootloader disables interrupts globally with __disable_irq(). That is a sensible step during the jump, but the application inherits that state. In other words, the payload started life with interrupts still disabled.

    The fix was simple once the root cause was clear: explicitly call __enable_irq() at the start of the application’s main() function.

    Once I made that change, the interrupt-driven trigger behaved as expected.

    This was a useful lesson because it had nothing to do with cryptography and everything to do with real boot-chain behavior. In embedded work, the handoff between stages includes CPU state, not just memory addresses.

    Preparing for Reverse Engineering in Ghidra

    One reason I like this project as a longer-term platform is that it naturally produces good reverse engineering targets.

    With this payload in place, I am no longer only the developer. I am also creating a sandbox for the analyst.

    The compiled application binary can be imported directly into Ghidra as a raw Cortex-M image. Once the binary is mapped at the correct base address, 0x08008200, the higher-level C code disappears and the reverse engineering problem becomes much more interesting.

    The intentionally introduced REBOOT trigger becomes a clear hunting point. A researcher can search for the string, follow cross-references, locate the comparison logic, and then trace how it reaches the flag that ultimately triggers a reset.

    That makes the binary useful as more than just a compiled firmware blob. It becomes a controlled challenge.

    This is also where binary diffing becomes valuable. A clean baseline application and a deliberately modified version create an ideal comparison pair. That makes it possible to study exactly how a behavioral change appears at the binary level, both in raw diffing and in Ghidra’s disassembly and decompiler views.

    For me, that is one of the biggest benefits of this lab. It is not just a secure boot demonstration. It is also a bridge into firmware RE workflow.

    The Hardening Roadmap

    With the software trust chain behaving as expected, the next logical layer is hardware hardening.

    For the STM32 platform, the most obvious candidates are Write Protection (WRP) and Read-Out Protection (RDP). Secure boot ensures that only trusted code executes. RDP helps prevent an attacker from trivially attaching a debugger and extracting firmware or other useful material directly from flash.

    I see that as the logical next phase.

    There is value in first validating that the software trust chain behaves the way I expect. Once that foundation is solid, adding hardware-backed controls will push the platform beyond a secure boot demo and closer to a more complete defensive research lab.

    That is also where the project gets more interesting from a security perspective. The conversation shifts from “can I verify code before boot?” to “how difficult is it to tamper with, extract, analyze, or bypass this platform in practice?”

    Conclusion

    This second phase of the STM32F411 secure boot lab was less about building and more about validating.

    The happy path was already established. The real work in this phase was proving that failure cases fail safely, debugging the interrupt-driven UART trigger, and shaping the resulting firmware into something that can be analyzed through binary diffing and reverse engineering.

    That shift matters.

    In embedded security, confidence comes from careful validation, clear failure handling, and an honest understanding of what has and has not yet been proven. For me, this phase marks the transition from a working secure boot demo to a more defensible and reusable embedded security lab.

    A secure boot chain is only convincing once it proves not just that valid firmware boots, but that invalid firmware does not.

    Start from the beginning: Part 1 — Building a Compact Cryptographic Secure Boot Chain on STM32F411: My First AI-Co-Authored Embedded Security Lab

    Series page: STM32F411 Secure Boot Lab Series

  2. Building a Compact Cryptographic Secure Boot Chain on STM32F411

    Leave a Comment

    This post is part of my STM32F411 Secure Boot Lab series, a hands-on embedded security project built around a Black Pill board, a compact cryptographic bootloader, and a reusable firmware analysis workflow covering authenticated boot, fail-closed validation, hidden trigger engineering, and reverse engineering preparation.

    Project repository: This lab is part of my IoT_Projects GitHub repository, where I collect IoT, embedded security, and firmware research projects.

    Most embedded projects are built around a simple assumption: if the board powers on and the firmware runs, the system is fine.

    For embedded security, that assumption is not enough.

    If a device will boot any image placed in flash, then firmware integrity and authenticity are largely being taken on faith. I wanted to build something better: a small STM32-based lab that would only boot an application if that application was well-formed, correctly placed, and cryptographically signed.

    That became this project.

    In this lab, I built a compact secure boot chain on the STM32F411CEU6 Black Pill using STM32CubeIDE, SHA-256, and ECDSA-P256. The bootloader validates the image header, checks the application vectors, hashes the application image, verifies the signature, and only then jumps to the application. If anything is wrong, it fails closed.

    I chose this as my serious embedded security pilot because it is small enough to complete on inexpensive hardware, but rich enough to expose the kinds of problems that matter in real firmware work: flash layout, linker behavior, VTOR relocation, control transfer, and crypto integration on a microcontroller.

    By the end of the project, the secure boot chain was working end to end.

    STM32F411 Black Pill wired to a Tigard interface during secure boot bootloader and signing workflow testing
    STM32F411 secure boot lab setup used during bootloader development, signing, and verified-boot testing.

    Why I Started Here

    I did not want my IoT or embedded security project to be flashy but shallow.

    I wanted something that solved a real problem, forced me to deal with practical constraints, and could serve as a base for later work. Secure boot checked all three boxes.

    At a high level, firmware trust sits underneath many other security claims. If a system cannot verify what it is about to execute, then update security, platform integrity, and even later reverse engineering exercises become less meaningful.

    That is why I picked this use case.

    I also chose the STM32F411CEU6 Black Pill very deliberately. It is affordable, widely available, well supported in STM32CubeIDE, and capable enough to run a compact public-key verification flow without making the project unreasonably heavy for this lab.

    This was never meant to be a one-off demo. I wanted signed binaries, a clean bootloader/application split, visible UART evidence, and a structure I could reuse later for tamper tests, binary diffing, Ghidra work, and hardening experiments.

    Project Goal

    The goal was to implement a fail-closed secure boot chain on the STM32F411CEU6 Black Pill.

    • validate the image header
    • validate the application vector table
    • compute SHA-256 over the application image
    • verify an ECDSA-P256 signature
    • jump only if all checks passed
    • fail closed on invalid header, invalid vectors, invalid signature, or malformed image layout

    I also wanted the application itself to be visible and testable. Instead of a hidden payload, I built a diagnostic app with UART output and LED behavior so I could confirm that the boot chain was really working.

    Lab Environment

    Hardware Used

    • STM32F411CEU6 Black Pill V2.0
    • ST-Link V2
    • Tigard / FTDI dual-channel UART adapter

    Software Used

    • STM32CubeIDE
    • OpenSSL
    • ST-packaged Mbed TLS
    • Python helper scripts for packing and key conversion

    I intentionally kept the setup simple and practical. I wanted a workflow that felt close to real embedded development rather than a heavily abstracted demo.

    Final Memory Map

    The final working flash layout was:

    • Bootloader: 0x08000000 .. 0x08007FFF (32 KB)
    • Image header: 0x08008000 .. 0x080081FF (512 bytes)
    • Application vectors + code: 0x08008200 and above

    This layout made the trust boundaries very clear.

    The bootloader lives in a fixed region. The signed header sits at a known address. The application vector table begins immediately after the header, followed by the rest of the application code.

    That separation made the rest of the project easier to reason about, especially the linker configuration, vector checks, and host-side packing flow.

    Bootloader Design

    The bootloader project was named Project_1_1_Bootloader.

    Its job was intentionally narrow:

    • validate the image header
    • validate the app vectors
    • calculate SHA-256 over the app image
    • verify the ECDSA-P256 signature using an embedded public key
    • jump to the app if valid
    • fail closed otherwise

    That simplicity was a design choice. For secure boot, small and predictable is better than complicated.

    Important Bootloader Files

    • mbedtls_config_boot.h
    • image_header.h
    • verify.c
    • updated main.c
    • pack_image.py
    • public_key_to_c.py

    Mbed TLS Source Files Required

    For the ST-packaged Mbed TLS copy I used, the minimum working set ended up being:

    • bignum.c
    • bignum_core.c
    • constant_time.c
    • ecp.c
    • ecp_curves.c
    • ecdsa.c
    • sha256.c
    • platform_util.c

    One useful lesson here was that bignum.c alone was not enough. Because of the way the ST package was structured, I also had to add bignum_core.c and constant_time.c to resolve missing mbedtls_ct_* and mbedtls_mpi_core_* linker symbols.

    Bootloader Size

    text = 22136
    data = 92
    bss  = 1980

    That kept total flash use at about 22.2 KB, safely inside the 32 KB bootloader region.

    Application Design

    The application project was named Project_1_2_App.

    Its purpose was not stealth. Its purpose was observability.

    The app was linked to run from 0x08008200 and included:

    • USART1 diagnostic console
    • LED heartbeat on PC13
    • simple commands:
      • STATUS
      • VERSION
      • LEDON
      • LEDOFF

    Application Configuration

    • USART1 on PA9 / PA10
    • PC13 LED
    • 115200 8N1
    • VTOR relocation enabled
    • linked in flash, not RAM

    Important Linker Fix

    One of the early app failures came from the linker script behaving like a debug-in-RAM configuration. That placed vectors and code in RAM, which made the image invalid for this boot flow.

    The corrected section behavior was:

    • .isr_vector -> FLASH
    • .text -> FLASH
    • .rodata -> FLASH
    • .data -> RAM with AT > FLASH
    • .bss -> RAM

    VTOR Relocation

    In system_stm32f4xx.c, the app uses:

    #define USER_VECT_TAB_ADDRESS
    #define VECT_TAB_OFFSET  0x00008200U

    That relocation is required so the application uses its own vector table after the bootloader jumps to it.

    Key Generation and Signing Workflow

    The signing workflow was kept simple and repeatable.

    First, I generated the keypair on the host:

    • private.pem
    • public.der

    Then I converted the public key into a C array using public_key_to_c.py and embedded it into the bootloader verification code.

    The application was built as a raw .bin, then packed and signed with:

    python3 pack_image.py Project_1_2_App.bin app_packed.bin --key private.pem --version 1

    This produced a packed image where:

    • the 512-byte signed header is written at 0x08008000
    • the application vectors and code start at 0x08008200

    Flash Order

    1. Flash Bootloader.elf to 0x08000000
    2. Flash app_packed.bin to 0x08008000

    That separation felt clean and correct. The PC signs the image. The MCU verifies it.

    The Most Important Bug

    The most important runtime problem was not in the application and not in flashing.

    It was in the bootloader’s vector sanity check.

    At one point, the bootloader kept printing:

    [boot] invalid vectors
    [boot] fail closed

    That was confusing because the image header looked correct and the app vectors at 0x08008200 also looked valid.

    A memory inspection showed:

    • SP = 0x20020000
    • Reset = 0x08008AF5

    Those values were valid, but the bootloader still refused to continue.

    Root Cause

    The original stack pointer check used this logic:

    if ((sp & 0x2FFE0000UL) != 0x20000000UL) return 0;

    That rejected 0x20020000, even though it is a valid top-of-RAM initial MSP value for the STM32F411.

    Fix

    I replaced that brittle bitmask test with a proper range check:

    if (sp < 0x20000000UL || sp > 0x20020000UL) return 0;
    if ((sp & 0x3U) != 0U) return 0;
    if ((rv & 1U) == 0U) return 0;
    if ((rv & ~1U) < APP_VECTOR_ADDR || (rv & ~1U) >= FLASH_END_ADDR) return 0;

    As soon as I made that change, secure boot worked.

    This was a very useful lesson. In embedded security, a trust chain can fail even when the cryptography is correct. Low-level validation logic still has to be right.

    Hardware and UART Notes

    ST-Link Wiring

    • SWDIO -> DIO
    • SWCLK -> SCK
    • GND -> GND
    • 3.3V only if intentionally powering from ST-Link

    UART Wiring

    • board PA9 / USART1_TX -> adapter RX
    • board PA10 / USART1_RX -> adapter TX
    • GND -> GND

    On Kali Linux, the FTDI/Tigard adapter appeared as:

    • /dev/ttyUSB0
    • /dev/ttyUSB1

    The correct console output was on:

    • /dev/ttyUSB0

    Picocom Command

    picocom -b 115200 /dev/ttyUSB0

    or:

    picocom -b 115200 --echo /dev/ttyUSB0

    At the end of the session, application TX was confirmed working, but the command RX path still needed final validation. The command code exists, but interactive RX did not respond in the terminal during the last session.

    The most likely remaining issue is UART RX wiring, FTDI channel selection, or final interrupt-path validation. This does not block the secure boot result, but it is the next thing to finish.

    Final Result

    The secure boot chain worked.

    The bootloader successfully verified the signed application and jumped to it. The known-good UART output was:

    [boot] secure boot starting
    [boot] signature valid
    
    =================================
    [app] Application Boot Successful
    [app] Diagnostic Console Enabled
    Commands: STATUS, VERSION, LEDON, LEDOFF
    =================================
    [app] tick...

    That output confirms several important things at once:

    • the bootloader is running
    • the header is valid
    • signature verification succeeds
    • the app vectors are acceptable
    • the jump to the application works
    • the application runtime is alive

    That made this an end-to-end success, not just a cryptographic unit test.

    What This Lab Taught Me

    A few takeaways stood out.

    First, memory layout is part of the security model. Flash addresses, linker behavior, and vector placement are not background details in a secure boot design.

    Second, embedded crypto is practical, but integration still matters. Even a minimal library setup can involve missing pieces and linker surprises.

    Third, a valid signature path is not enough by itself. Startup assumptions, vector checks, and jump conditions can make or break the trust chain.

    And finally, visible diagnostics are worth it. A UART-speaking test app with an LED heartbeat is far more useful in an early lab than a silent payload.

    Next Steps

    There are several obvious follow-up tasks for this lab:

    • finalize the UART RX command path
    • tamper with the packed image and confirm signature failure
    • enable WRP
    • test RDP Level 1
    • create a clean app and a modified app for binary diffing
    • use the resulting binaries in Ghidra
    • explore secure boot bypass ideas from a defensive research angle

    That is exactly why this project matters to me. It is not just a finished lab. It is a strong base for the next labs.

    Conclusion

    This project achieved its main goal: I built a compact cryptographic secure boot chain on the STM32F411CEU6 Black Pill that validates image structure, checks application vectors, computes a SHA-256 hash over the application, verifies an ECDSA-P256 signature, and transfers execution only when all checks pass.

    If the image is invalid, it fails closed.

    Just as importantly, the project forced me through the details that make embedded security real: flash layout, linker fixes, vector validation, boot-time assumptions, and practical crypto integration on a constrained MCU.

    So while this article covers a single lab, I see it as the foundation for a larger embedded security track. It already supports tamper testing, binary comparison, reverse engineering, and future hardening work.

    That makes it a strong pilot project and a useful platform for what comes next.

    A secure boot chain is not just cryptography. It is memory layout, vector sanity, linker discipline, and a refusal to run code that has not earned trust.

    Continue the series: Part 2 — Building a Secure Boot Chain on the STM32F411 (Part 2): Validation, Hidden Triggers, and Reverse Engineering

    Series page: STM32F411 Secure Boot Lab Series

  3. STM32F411 Secure Boot Lab Series

    Leave a Comment

    This series documents my work building and extending a compact cryptographic secure boot lab on the STM32F411CEU6 Black Pill.

    The project started as a focused firmware integrity exercise: build a bootloader that validates a signed application image using SHA-256 and ECDSA-P256, verifies its vector table, and refuses to boot if anything is wrong. From there, the lab grew into something more useful: a reusable platform for fail-closed validation, hidden trigger analysis, binary diffing, and reverse engineering in Ghidra.

    The goal of this series is not just to show a working demo. It is to document the decisions, debugging, validation work, and next-step research that turn a one-off embedded project into a defensible security lab.

    STM32F411 Black Pill connected to a Tigard debug interface with jumper wires during secure boot lab testing
    A practical STM32F411 secure boot lab series covering authenticated boot, fail-closed validation, and reverse engineering prep.

    Repository: This STM32F411 secure boot lab series is part of my broader IoT_Projects GitHub repository, where I collect IoT, embedded security, and firmware research projects.

    Part 1: Building the Secure Boot Chain

    The first part covers the implementation of the secure boot chain itself: flash layout, bootloader design, application placement, signing flow, Mbed TLS integration, linker corrections, VTOR relocation, and the debugging work that made verified boot succeed.

    Read Part 1: Building a Compact Cryptographic Secure Boot Chain on STM32F411

    Part 2: Validation, Hidden Triggers, and Reverse Engineering

    The second part moves beyond the happy path. It focuses on fail-closed validation, an interrupt-driven hidden UART trigger inside the payload, and shaping the firmware into a better target for binary diffing and reverse engineering in Ghidra.

    Read Part 2: Building a Secure Boot Chain on the STM32F411 (Part 2): Validation, Hidden Triggers, and Reverse Engineering

    Why This Series Matters

    Secure boot is one of those topics that sits at the center of embedded security. It touches firmware trust, memory layout, linker behavior, startup assumptions, debugging discipline, and later hardening steps such as write protection and readout protection.

    That is why I like this STM32F411 lab as a research platform. It is small enough to be practical, but rich enough to support future work in tamper testing, firmware comparison, reverse engineering, and defensive analysis.

    More parts may follow as the platform expands into hardening, binary diffing, and Ghidra-driven firmware analysis.

    A secure boot lab becomes much more useful once it moves beyond the happy path.

Categories

Tags

Archives