Poof goes your crypto ...

Ledger Nano S: Bootloader Verification Bypass



Bug Type



Affected Wallet

Ledger Nano S


An attacker with physical access to the device can execute arbitrary code on the STM32 MCU.


Thomas Roth @stacksmashing

Affiliation: Keylabs and Wallet.Fail

LiveOverflow video

Vulnerability Details

The contents in this advisory are based on reverse-engineering. While well-verified, there might still be misunderstandings/mistakes in some of the details.

It was found that the Ledger Nano S bootloader can be tricked into flashing and executing untrusted firmware.

The bootloader is used to update the firmware of the ‘non-secure’ processor in the Ledger Nano S and has full control over the display, USB and the buttons.

Bootloader flashing process

The bootloader accepts the following flashing related APDUs:

APDU SECURE INS Name Description
5 SECUREINS_SELECT_SEGMENT Select segment, accepts an 8 byte address as a base for flashing.
6 SECUREINS_LOAD Load data, accepts 2 bytes of unsigned segment offset and then up to ca. 250 bytes of data per call.
7 SECUREINS_FLUSH Flushes the non-volatile memory (applies the write)
9 SECUREINS_BOOT Boot the flashed code, accepts an 8 byte entrypoint. The boot command also cryptographically verifies the flashed image and only allows booting if the verification is successful.

Boot protection

The boot protection of the Ledger Nano S MCU relies on the bytes 0xF00DBABE being present at address 0x0800_3000.

Those 4-bytes are cleared before applying any SECUREINS_LOAD instruction, so writing anywhere into the non-volatile memory clears the 0xF00DBABE magic number.

It is also not possible to directly write the data to 0x0800_3000, for example with the following pseudocode:


The current research shows that SECUREINS_LOAD works somewhat like this (pseudocode):

size_t destination_address = current_segment + apdu_supplied_offset
size_t buffer_size = apdu_supplied_size
uint8_t *buffer = apdu_supplied_data

// Check if currently the boot magic is set
if(0x0800_3000 == 0xF00DBABE) {
    // If yes clear it, so that after SECUREINS_LOAD
    // the magic is never set

// Prevent bootloader from overwriting itself?
if(0x0800_0000 <= destination_address < 0x0800_3000) {
    return error

// Check if flashing 0xF00DBABE magic address
if(destination_address == 0x0800_3000) {
    // Clear first 4 bytes
    memset(buffer, 0, 4)

// Finally write to non-volatile memory (needs to be flushed still)
nvm_write(destination_address, buffer, buffer_size)

This pseudocode shows that for verifying the destination address a black-list approach is used: It is forbidden to write directly from 0x0800_0000 - (including) 0x0800_3000.

Further research showed that the non-volatile memory is not only mapped to 0x0800_0000, but also to 0x0000_0000, which allows to use the following (pseudocode) APDUs for setting the 0xF00DBABE flag, bypassing the cryptographic verification in SECUREINS_BOOT and the blacklist in SECUREINS_LOAD:


Example/Proof of concept

The following (raw) APDUs can be used to flash an endless loop at the entry-point of 0x0800_30C0.

// Select segment 0x0000_0000
// Flash F00DBABE, the entrypoint and then later on an endless loop for PoC
// Flush

Further reading

Reference manual: STM32F0x1/STM32F0x2/STM32F0x8 advanced ARM®-based 32-bit MCUs

What is a firmware vulnerability?

Firmware vulnerabilities are vulnerabilities affecting the software that runs on the hardware wallet. Since most wallets provide update mechanisms, this class of bug can be patched in a future firmware release.

Using the Ledger Nano S?

Are you storing a substantial amount of cryptocurrency on your Ledger Nano S? If you would like a consultation on how to safely store your funds, please contact us at info@wallet.fail.

Our team of renowned security experts will help you assess the impact of these findings and whether they merit a change to how you store cryptocurrency. For a full list of services offered by our team, please visit our website.