tyler

2026-04-2617 minUpdated 2026-06-28securityembeddedhardwarefirmware

Embedded Security In Practice: Secure Boot, Debug Lockdown, Key Storage

Layered defense for when the attacker holds the hardware: secure boot, debug lockdown, key storage, safe updates, and the basics that catch most attackers.

Most of the defenses you build in software assume the attacker can’t touch the hardware. Take that assumption away and a lot of the stack falls over. In 2020, Kraken Security Labs pulled the seed key off a Trezor hardware wallet in about 15 minutes with roughly $75 of equipment, by glitching the chip’s supply voltage to downgrade its readout protection. A device whose entire job is protecting a secret, beaten by someone holding it.

So this isn’t a checklist to rubber-stamp. It’s a way of thinking: any embedded device with value attached will eventually end up on an attacker’s bench with a logic analyzer and a power glitcher, and good security is a stack of controls where each one still buys you something after the layer above it fails. The boring part, which I’ll keep returning to, is that almost no real-world compromise comes from the exotic glitching attacks. They come from skipping the basics. Default passwords, debug ports left open, firmware nobody bothered to sign.

This is Part 1 of a series: the conceptual, vendor-neutral foundation. Later parts put each control into practice on a specific board (an NXP FRDM-MCXN947), but everything here applies whether you’re shipping a Cortex-M sensor or a Linux gateway.

Start with the threat model

Before any specific control, get the threat model straight, because it sets how much of the rest you need.

A device reachable only over the network faces a bounded attacker: they get whatever your software interfaces expose and nothing more. A device an attacker can physically hold faces a different world. They can desolder the flash and read it directly, probe buses, glitch the power and clock lines, fire EM pulses at the package, even decap the silicon. The clean way to hold this in your head: physical access turns “the CPU always executes instructions correctly” into a maybe. Fault injection works by breaking exactly that assumption, so any check enforced purely in software can in principle be skipped.

This doesn’t mean you armor every product against a nation-state. It means you decide up front which attacker is real for your device and stop building once an attack costs more than what it protects. A $20 toy and a hardware wallet don’t get the same budget. The mistake is leaving the decision implicit, then finding out you assumed a network-only attacker for a device bolted to a lamppost.

Two current frameworks formalize exactly this. MITRE EMB3D (full release September 2024) maps device properties to threats to mitigations and ranks those mitigations into Foundational, Intermediate, and Leading tiers, directing vendors to address Foundational controls first; its threat model explicitly assumes the attacker already has physical access for reverse engineering and exploit development. OWASP’s IoT Security Testing Guide (ISTG) grades the attacker on a physical-access axis from PA-1 (remote, over the network) to PA-4 (invasive, enclosure open). Both back the staged approach this post takes: nail the baseline controls first, then harden, and only pay for bench-level physical defense when the threat model demands it.

Secure boot: the anchor everything hangs from

If an attacker can replace your firmware, nothing else you did matters, because they’re no longer running your code. Secure boot makes firmware replacement detectable and, ideally, useless.

The mechanism is a chain of trust. It starts at a root of trust that can’t be modified, which in practice means a mask ROM bootloader burned into silicon at manufacture. The immutable ROM verifies the next stage before handing off; that stage verifies the one after; and so on up to your application. “Verify” means checking a digital signature, usually RSA or ECDSA, against a public key whose hash is fused into one-time-programmable memory. Tamper with any stage and its signature check fails.

Two things trip people up. First, where the verification logic lives: if the whole check sits in updatable flash, an attacker who can write flash replaces the verifier itself, and your chain of trust is anchored to nothing. The root has to be immutable, which is why it lives in ROM. Second, anti-rollback. A signature confirms code is authentic, not that it’s current, so an attacker can install a genuinely signed but old image with a vulnerability you’ve since patched. The defense is a monotonic version counter in eFuse that refuses anything older than its value. The catch is real: it’s irreversible by design, so a buggy release that bumps the counter permanently closes the downgrade path. Test your release process before it touches that fuse.

Worth separating two terms that get conflated. Verified boot checks signatures and halts (or drops to recovery) on failure; it prevents bad code from running. Measured boot hashes each stage into append-only registers without stopping anything, producing a record a remote server can later attest against. Prevention versus evidence. Plenty of systems do both.

For a cautionary tale, Fusée Gelée (CVE-2018-6242), disclosed by Kate Temkin in April 2018, is a buffer overflow in the boot ROM of NVIDIA’s Tegra X1. The USB recovery path reads an attacker-controlled length and hands it to a memory copy with no bounds check, corrupting the stack before the chip’s lockouts engage. Because the bug is in immutable ROM, it can’t be patched on shipped units, and it affected something like 17.8 million Nintendo Switch consoles. The lesson isn’t that ROM is bad. It’s that an immutable root of trust is only as good as the code you burn into it, so that code has to be minimal and audited, because once it ships it’s permanent.

Lock the doors you left open for yourself

JTAG and SWD exist so you can develop and debug. They halt the CPU, read and write memory, set the program counter, and dump firmware. Which is why an open debug port in the field is one of the most damaging things you can ship: functionally, a vendor-installed root shell.

The naive fix is blowing a fuse to disable debug forever. That works, and for some products it’s right, but you can then never debug a field return and a bricked unit is landfill. The modern answer is authenticated debug: the port stays physically present but locked, unlocking only for a host that completes a cryptographic challenge-response. Arm’s Authenticated Debug Access Control and the CoreSight SDC-600 secure debug channel do exactly this, gating debug on the device’s lifecycle state and a signed credential, with per-device scoping and credential expiry. You keep your RMA path; an attacker without the key gets a locked door.

That lifecycle idea matters more broadly. A device should know whether it’s in development (debug open), deployed (debug locked), or returned for analysis (re-openable with the right credential), with those states held in one-time-programmable (OTP) memory and one-way transitions between them. STMicroelectronics’ readout protection shows the tradeoff nicely: RDP Level 1 can roll back to Level 0, but doing so mass-erases flash, so downgrading protection destroys the secrets along the way. RDP Level 2 is permanent and kills the system bootloader entirely, which is great for security and terrible if you ever need to recover a unit. No free lunch, just a choice about which failure you’d rather have.

And “permanent” is doing a lot of work in that sentence. Obermaier and Tobisch (USENIX WOOT 2017) showed an STM32F0 SWD race condition where the flash-readout lockout fires two cycles too late, letting them power-cycle and extract firmware at about 45 bytes per second. They also showed RDP Level 2 is one bit away from Level 1 in the option bytes, and since those bytes are flash rather than true OTP, UV-C light on a decapsulated die flips that bit and re-enables debug (CVE-2017-18347). The datasheet’s “irreversible” is a software convention, not a physical fact.

The single most common mistake here is dull: shipping the development image, debug settings and all, straight to production.

Don’t keep your keys where anyone can read them

A key in plaintext flash belongs to whoever dumps the flash, and dumping flash is easy, whether through an open debug port, an exposed bootloader command, or a hot air station and a chip reader. So the rule is that secrets don’t live in flash in the clear, and the keys protecting other keys live in hardware.

The mechanisms form a rough ladder of cost and assurance. eFuse or OTP can hold key material or the hash of your secure-boot public key; cheap and on-die, but blown eFuse bits are visible under an electron microscope to anyone who decaps the chip, whereas anti-fuse OTP (which breaks down an oxide layer) leaves no visible trace. A secure element is a separate tamper-resistant chip that holds keys in encrypted storage and runs crypto without ever exposing them, often with the storage key itself derived from a Physically Unclonable Function (PUF) so it’s never stored at all. A PUF derives a device-unique secret from tiny manufacturing variations rather than storing one; it needs error-correction logic (a “fuzzy extractor”) to produce a stable value across temperature and voltage, and it’s not magic, the response can drift as the part ages.

Whatever you use, keys should be device-unique, and this is the part teams skip under cost pressure. Share one key across the fleet and breaking one device breaks all of them. Not hypothetical: in 2015, SEC Consult analyzed firmware from over 4,000 device models and found more than 580 SSH and HTTPS private keys hard-coded and shared across devices, with at least 230 of them live on more than four million internet-connected devices. One certificate from a Broadcom SDK turned up in over 480,000 devices. Anyone with that firmware could impersonate or intercept all of them.

Encrypting data or firmware at rest follows the same principle: the encryption key sits at the root of the hierarchy in hardware-protected storage, and per-device beats shared. Just don’t store the wrapping key in the same flash as the thing it wraps, which sounds obvious and happens anyway.

Isolate, so one bug isn’t game over

Even with signed code and protected keys, your application will have bugs. Isolation keeps a bug in non-critical code from reaching your secrets: assume any component can be compromised and structure things so that compromise doesn’t hand over everything.

Arm’s TrustZone-M (Armv8-M cores, like the Cortex-M33) splits the chip into a secure and a non-secure world, switching state on instruction fetch. Secure-world code holds the keys and sensitive peripherals; non-secure code can only call in through sanctioned entry points and can’t read secure memory directly. A memory protection unit enforces region permissions within each world, including execute-never so injected data can’t run as code.

The trap is subtle enough to have caught real designs. TrustZone-M attributes accesses made by the CPU core, but a Direct Memory Access (DMA) controller is also a bus master. If a programmable DMA engine can issue transactions into secure memory, an attacker reads secrets while sidestepping the core’s checks entirely. Attribute every bus master and peripheral, not just the processor. TrustZone-M is a primitive, not a finished security architecture, and it does nothing against physical fault injection.

Treat the update channel as shield and target

Over-the-air update is the only practical way to patch a fielded fleet, which makes it indispensable, and a direct path to running your code on every device you’ve shipped, which makes it a prime target. Both are true at once, and people underestimate the second.

Signing is non-negotiable. Every image carries a signature over its contents, verified with a key provisioned at manufacture and verified again at boot so it chains into secure boot. An update path that just downloads a file and flashes it gives no assurance the file came from you. That gap is part of how the 2015 Jeep Cherokee hack worked: Charlie Miller and Chris Valasek found a controller they could reflash with modified firmware with no authentication check, which bridged them from the infotainment system onto the vehicle’s CAN bus. Chrysler recalled 1.4 million vehicles.

Beyond signing you want a fail-safe apply mechanism, in practice A/B partitions or dual banks. Write the new image to the inactive slot, verify it, switch over, run a health check, and revert to the working slot if the device doesn’t come back. Without this, a power cut mid-flash bricks the unit. And you want anti-rollback on updates, same downgrade logic as secure boot. The open-source reference here is MCUboot. It verifies each image’s signature against one or more public keys baked into the bootloader at build time (RSA-2048/3072, ECDSA P-256, or Ed25519), then boots a new image on a trial swap that auto-reverts unless the application confirms itself. Anti-rollback is a separate per-image security counter in protected storage, independent of the version number.

Whether to encrypt firmware on top of signing is where practitioners genuinely disagree, so here’s both sides. Encryption buys confidentiality: it slows reverse engineering and hides secrets baked into the image. It costs a decryption key that now has to live protected on the device, plus key-management overhead. Where I land is that signing is mandatory and encryption is a threat-model call, worth it to deter casual reverse engineering or protect embedded IP, not worth it if it just adds a key you’ll mishandle. What encryption is not is a substitute for signing, because it protects confidentiality, not integrity. A device that decrypts an image and runs it without checking a signature is still wide open.

The basics that catch most attackers

If you do nothing exotic, do these, because the incident reports are dominated by their absence.

Kill default credentials. The Mirai botnet, which knocked large parts of the internet offline in late 2016, broke no crypto. It scanned for Telnet and tried a built-in list of 62 username and password pairs, hard-coded defaults like root / xc3511 baked into cheap cameras and DVRs that users couldn’t change. That built a botnet that hit OVH with over a terabit per second and took down Dyn’s DNS, dragging Twitter, GitHub, and Netflix down with it. Every device needs a unique identity and no shipped default password.

Strip the build. Remove debug and test code from production images, and disable the serial-download and DFU bootloader modes vendors include for factory programming. These are backdoors by accident. Two of my favorite cases are deliberate ones that escaped into shipping firmware: the Sercomm “port 32764” interface (CVE-2014-0659), an undocumented service letting anyone on the network run commands and read credentials, found across a couple dozen router models in late 2013; and the D-Link backdoor (CVE-2013-6026), where an HTTP User-Agent of xmlset_roodkcableoj28840ybtide bypassed authentication outright. Reverse that string: “edit by joel backdoor.” Someone meant to put it there, and it shipped.

Seed randomness from real entropy. Cryptography assumes good random numbers, and embedded devices are notorious for not having them, especially headless ones generating keys at first boot before any entropy has accumulated. In 2012, Heninger and colleagues scanned the internet and found a meaningful fraction of TLS and SSH keys on embedded devices generated with so little entropy that distinct devices shared prime factors. When two keys share a factor, a GCD computation factors both private keys instantly. That shared-prime sweep alone broke about 0.50% of the TLS hosts they scanned and 0.03% of the SSH hosts; a separate flaw in DSA signing randomness pushed the SSH total to roughly 1%. The fix is a hardware RNG, seeding entropy at the factory, and generating keys on first use rather than first boot.

Use the crypto that’s already there, constant-time. The chip’s hardware crypto accelerator is faster than software and often hardened against side channels, so reach for it before your own code, and never roll your own cipher. Where you do implement crypto, make it constant-time: the instructions executed and memory accessed mustn’t depend on secret values, because timing leaks keys. Lucky Thirteen (CVE-2013-0169) recovered TLS plaintext in 2013 purely from timing differences in padding checks. Compilers can quietly undo constant-time code while optimizing, so verify at the binary level if it matters.

When the attacker is on your bench

Everything above holds against a remote attacker. Once physical access is real, and only then, you start spending on the next tier: these controls are expensive and pointless for a threat model that doesn’t include someone holding the device.

Tamper detection is a sensor or a conductive mesh in the enclosure that notices intrusion and triggers zeroization, wiping keys in hardware without waiting on firmware. FIPS 140-3 Level 3 and up mandates this detect-and-erase behavior. The cost is real, false positives included: a unit someone dropped can come back bricked.

Resisting side-channel and fault-injection attacks directly means decorrelating power and EM emissions from the secret being processed (masking, hiding) and adding redundancy: double-read critical fuses, randomize timing, add glitch detectors, because glitching’s whole premise is making one security check execute wrong. The ESP32 has a useful public paper trail: a voltage-glitch secure-boot bypass (CVE-2019-15894), an EM-glitch bypass of encrypted secure boot (CVE-2020-13629, reserved on NVD; see the Raelize disclosure), and a 2024 USENIX WOOT result defeating both secure boot and flash encryption on a later revision with a single EM glitch despite added countermeasures. No defense is absolute against a sufficiently resourced attacker with a microscope and a focused ion beam. The job is pushing attack cost above the value of what’s protected.

The takeaway

The pattern underneath all of it: embedded security is layered defense for a world where the attacker eventually holds the hardware, and no layer is meant to stand alone. Secure boot is designed for the case where keys eventually leak. Key isolation keeps a code bug from reaching those keys. And tamper response is the fallback once every software control above it has already failed. The design that holds is one where each layer still does useful work after any single piece gives way. Those baseline controls roughly map to NIST IR 8259A’s core baseline for IoT device cybersecurity, though the goal here is a way to prioritize rather than a compliance checklist.

But defense against bench attacks is the second conversation, not the first. The first is the unglamorous stuff (unique credentials, signed firmware, locked debug ports, real entropy) that shows up in nearly every breach writeup precisely because it was missing. Get those right before spending a dollar on tamper mesh. The attacker with the electron microscope is rare. The one running a script against your default password is running it right now.

The next posts in this series put each of these controls on real silicon on the NXP FRDM-MCXN947: the threat model and lifecycle plan for the board, then secure boot, authenticated debug, hardware root of trust and key storage, TrustZone-M isolation, flash encryption, and secure OTA. I’ll add links here as each part lands.

Further reading

← OlderISO/SAE 21434 TARA: A Worked Automotive Risk Assessment and Its Limits