Stm32wb55 FUS 2.0
published on Sat 07 June 2025 by Xilokar
A few days before my SSTIC presentation Récupération de la clé des firmwares radio du stm32wb55 (in french), while doing my slides, I was made aware of a new release of the FUS for the stm32wb55. And of course, the attack I was going to present 5 days later was no longer working...
No release for 4 years, and a month before my talk, a new release with fixes ?

So I was good for a speedrun analysis of FUS 2.0.
What was fixed
Race condition in flash_erase_page
The code that was handling flash page erasure was changed from something like
void flash_erase_page(int page) {
uint32_t cr;
cr = Flash.C2CR;
Flash.C2CR = page << 3 | cr & 0xfffff807 | 0x2;
Flash.C2CR |= cr 0x10000;
}
to something like that:
void flash_erase_page(int page) {
uint32_t cr;
cr = Flash.C2CR;
Flash.C2CR = page << 3 | cr & 0xfffff807 | 0x10002;
}
Preventing indeed a race condition that was allowing us to change the page FUS was wanting to erase.
Ensuring OBL_LAUNCH
In FUS 1.2.0, the code to launch an OBL_LAUNCH was like this:
void launch_obl(void)
{
dword dVar1;
int iVar2;
iVar2 = wait_flash_done(0xf00000);
if (iVar2 == 0) {
dVar1 = read_volatile_4(Flash.CR);
/* Trigger OBL_LAUNCH */
write_volatile_4(Flash.CR,dVar1 | 0x8000000);
}
return;
}
So, if OBL_LAUNCH was forbidden by OPTLOCK, the reset would not occure and the FUS state machine was running the next step.
In FUS 2.0 the code is more like this:
void launch_obl(void)
{
dword dVar1;
int iVar2;
iVar2 = wait_flash_done(0xf00000);
if (iVar2 == 0) {
dVar1 = read_volatile_4(Flash.CR);
/* Trigger OBL_LAUNCH */
write_volatile_4(Flash.CR,dVar1 | 0x8000000);
/* Trigger ARM reset */
SCB.AIRCR = 0x05fa0004;
while(1);
}
return;
}
Thus, enforcing a reset, and so preventing the state machine to run the next step unprotected (more details on attack in the original blog post)
Changing the FUS state machine
The FUS 1.2.0 state machine for firmware installation was this one:

My original attack was at step 3, preventing the flash protection to be effective and then messing with the flash during step 4 to gain code executin on CPU2.
But there was another attack that could maybe have been done. During step 2, the CPU2 was calculating the hash of the ecnrypted firmware to feed it to the PKA. This hash is slow to compute (especially if you slowdown CPU2). So it would have been possible to change the encrypted binary (tricky, changing the start is easy but since the last page of the firmware has a hash of the unencrypted firmware, you must manage to at least write the last page of the firmware).
ST probably spotted this potential issue and then choose to change the FUS 2.0 state machine to:

Closing the window...
So, with this new state machine, The first step is to protect the flash by changing SFSA and performing an OBL_LAUCNH (with the protections mentionned above).
So, as the flash was protected from CPU1 during both validation ans decryption of the image, we were no longer capable of altering the flash.
I was starting to think that my talk was going to end by "But this no longer works".
Wait
...and opening the door
When FUS 2.0 has to protect the flash, it must know what size to protect right ? Ha yes, the header in the wireless image has a Flash (nb of 4K sectors) byte. And this header is validated by the firmware signature.
The code handling this is somehow like this:
void protect_flash(struct header *header) {
int start_page;
start_page = 0xf4 - header->pages;
/* write SFSA */
FLASH.SFR = FLASH.SFR & 0xffffff00 | (start_page & 0xff);
obl_laucnh();
}
Appreciate the & 0xff... so by feeding 0xf5 in the header, the CPU2 would only protect the upper page of the flash...
Yes, but the header is validated by the signature.
...
Omg, the signature is only checked after the flash is protected !!!
...
____ _ _ | _ \__ ___ __ ___ __| | | | | |_) \ \ /\ / / '_ \ / _ \/ _` | | | | __/ \ V V /| | | | __/ (_| | |_| |_| \_/\_/ |_| |_|\___|\__,_| (_)
TLDR
- Write 0xf5 in the wireless image page info
- Start the installation (FUS perform a reboot)
- Notice that SFSA is at 0xff
- Patch FUS to no longer check signature
- Enjoy
Conclusion
This is how, with a 3 night work, instead of finishing my talk with shame, I got applaused ;)