Extracting stm32wb55 CPU2 firmware key

published on Thu 21 July 2022 by

The stm32wb55 mcu is a dual core (cortex-m4/cortex-m0) mcu with integrated wireless capabilities (ble/thread/zigbee).

The first core (the m4, later referred as CPU1) is for the customer. The second core (m0 known as CPU2 is dedicated to the low level wireless handling. The customer does not have access to this second core, neither to the firmwares running on it. Plus there is a bunch of security features embedded in the mcu to prevent access.

Why does this suck ?

I could understand the company did not want to provide the customers direct access to the wireless core (I assume that the low level stacks are valuable assets).

But

If you choose to go this way, at least don't mess up with the customer code.

It appears that various operation involving the CPU2 (the wireless dedicated core) are not tolerant to interruption (power outage, cpu1 reset, more...) and, as stated in some documentation of the mcu, may lead to a bricked device, or to the need of a manual factory reset (which means a full erase of the cpu1 customer code, which is equivalent to a bricked device)

This clearly sucks and I don't like it, so I decided to give it a look, to see what I could find.

A short presentation

The mcu comes with an embedded sram and flash. Both are shared between CPU1 and CPU2, but parts of the sram and the flash are dedicated to CPU2. There is an integrated features that prevent all access to customizable regions of the flash/sram except for CPU2. Neither CPU1, dma or anything else can access it.

We will focus on the flash, which is splitted by pages of 4k.

The region dedicated to the cpu2 is selected by setting the first page to be locked. All other upper pages are locked.

Setting this first locked page is done by writting in the so-called option-bytes registers (SFSA for the secure flash start address) which is a small dedicated flash. Of course, write access to this part of the options bytes is allowed only to CPU2.

Another CPU2 related option bytes is the boot address of the cpu (SBRV). Once again, write access is restricted.

page 0x00 +-------------+ <- 0x0800000 start of flash
          | customer    |
          |   code      |
          |             |
          ~ ~ ~ ~ ~ ~ ~ ~

          ~ ~ ~ ~ ~ ~ ~ ~
          |             |
          |             |
          +-------------+ <- SFSA
          |             |
          |             |
          | CPU2-only   | <- SBRV
          |             |
page 0xff +-------------+ <- 0x080fffff end of flash

             Overview

And obviously, dbg access to the CPU2 can also be forbidden.

Introducing FUS

FUS stands for Firmware Update Services. Basically, it allows to switch between different wireless stacks, handling all of the secure processing.

The mcu is sold to the customer with FUS already installed at the end of the flash.

page 0x00 +-------------+ <- 0x0800000 start of flash
          | customer    |
          |   code      |
          |             |
          ~ ~ ~ ~ ~ ~ ~ ~

          ~ ~ ~ ~ ~ ~ ~ ~
          |             |
          |             |
          |             |
page 0xf4 +-------------+ <- SFSA / SBRV
          |             |
          |     FUS     |
          |             |
page 0xff +-------------+ <- 0x080fffff end of flash

             FUS only

The last pages of the flash are marked as secure, and the boot vector of the CPU2 is set to the start of FUS.

With a shared mem and an hardware inter-processor communication mechanism (out of scope), the customer application code can order FUS to perform some tasks.

For example, it can request to install a wireless firmware. To do so, the ST-provided image must be written in the internal flash, and an FUS_FW_UPGRADE command must be sent to FUS.

page 0x00 +-------------+ <- 0x0800000 start of flash
          | customer    |
          |   code      |
          |             |
          ~ ~ ~ ~ ~ ~ ~ ~

          ~ ~ ~ ~ ~ ~ ~ ~
          |             |
page 0xdc +-------------+
          |             |
          |  encrypted  |
          |   wireless  |
          |   firmware  |
          |             |
page 0xf4 +-------------+ <- SFSA / SBRV
          |             |
          |     FUS     |
          |             |
page 0xff +-------------+ <- 0x080fffff end of flash

           Before install

As stated in the documentation (AN5185 on ST site), there will be some expected reboots during the install procedure. This is due to the fact that option bytes modifications are only applied after a reset. In fact there is even a special bit (OBL_LAUNCH) in a register in the flash controller that performs this reset.

Let's monitor SFSA/SBRV during this install process.

After first reset

page 0x00 +-------------+ <- 0x0800000 start of flash
          | customer    |
          |   code      |
          |             |
          ~ ~ ~ ~ ~ ~ ~ ~

          ~ ~ ~ ~ ~ ~ ~ ~
          |             |
page 0xdc +-------------+ <- SFSA
          |             |
          |  encrypted  |
          |   wireless  |
          |   firmware  |
          |             |
page 0xf4 +-------------+ <- SBRV
          |             |
          |     FUS     |
          |             |
page 0xff +-------------+ <- 0x080fffff end of flash

           First reset

The firmware and FUS pages are secure, but the CPU2 still boots on FUS.

Then there is another reboot and we have:

page 0x00 +-------------+ <- 0x0800000 start of flash
          | customer    |
          |   code      |
          |             |
          ~ ~ ~ ~ ~ ~ ~ ~

          ~ ~ ~ ~ ~ ~ ~ ~
          |             |
page 0xdc +-------------+ <- SFSA / SBRV
          |             |
          |             |
          |   wireless  |
          |   firmware  |
          |             |
page 0xf4 +-------------+
          |             |
          |     FUS     |
          |             |
page 0xff +-------------+ <- 0x080fffff end of flash

           After install

At this point the wireless firwmare is runing. We can still however ask it to switch back to FUS. We then have again

page 0x00 +-------------+ <- 0x0800000 start of flash
          | customer    |
          |   code      |
          |             |
          ~ ~ ~ ~ ~ ~ ~ ~

          ~ ~ ~ ~ ~ ~ ~ ~
          |             |
page 0xdc +-------------+ <- SFSA
          |             |
          |  encrypted  |
          |   wireless  |
          |   firmware  |
          |             |
page 0xf4 +-------------+ <- SBRV
          |             |
          |     FUS     |
          |             |
page 0xff +-------------+ <- 0x080fffff end of flash

           FUS activated

We can ask to delete the current wireless firmware with FUS_FW_DELETE, and after another reset:

page 0x00 +-------------+ <- 0x0800000 start of flash
          | customer    |
          |   code      |
          |             |
          ~ ~ ~ ~ ~ ~ ~ ~

          ~ ~ ~ ~ ~ ~ ~ ~
          |             |
page 0xdc +-------------+
          |             |
          |    Empty    |
          |    Pages    |
          |             |
          |             |
page 0xf4 +-------------+ <- SBRV / SFSA
          |             |
          |     FUS     |
          |             |
page 0xff +-------------+ <- 0x080fffff end of flash

           FUS running

(of course, the unciphered wireless image is erased...)

A similar procedure can be used to upgrade FUS itself, involving more reset (probably due to a phase of relocation of the new FUS on top of the old FUS).

Firmware image ...

Let's look at the provided wireless firmware / FUS upgrade images provided by ST. They are (sort of) documented in the ST application note. Obviously, they are signed by ST, and encrypted, so not much to do as of now. Still, dig inside the github repository.

... hidden in plain sight

More precisely, look at the history of the firmware binaries. We can see updates andnew wireless protocols released. But there is more... during release v1.5.0, a file with a different pattern name appears: stm32wb5x_FUS_fw_1_0_2.bin...

And yes...

above all odds,

it's the unciphered binary !!!

Well, at least, we have something to work on. Even if it is not the latest FUS version, we may learn some valuable info.

Wireless install steps

After a bit of reversing of the FUS image, the wireless image install process can be more detailled.

Step 1 Image authentication

When ordering to procede to a wireless image install, FUS first search for 2 footers. The one with the ST signature, and the one with the image info.

A sha256 of the image is then calculated and an edcsa verification is performed, using an embedded (but hidden) public key. This ecdsa verification uses the PKA (public key accelerator) hardware block.

int verify_signature(void *start,int len,
                     void *pub_key, void *signature)
{
  int iVar1;
  int ret;
  char hash [32];

  ret = 1;
  zero_mem(hash,0x20);
  iVar1 = init_pka();
  if (iVar1 != 1) {
    ret = sha256(start, len, hash);
    if (ret == 0) {
        iVar1 = pka_ecdsa_verif(hash, pub_key, signature);
        if (iVar1 != 1) {

         ret = 1;
        }
    }
    stop_pka();
  }
  return ret;
}

If the image is authenticated, FUS saves it state in a dedicated page at the end of the secured part of the flash, set the SFSA register to the beginning of the image and trigger an OBL reset.

Step 2 Deciphering the image

FUS restart and knows it must now decipher the image. This is done in place by using AES in CTR mode with a nonce provided in the image footer, a start number of 2 (because why not) and a key stored in the end of the secured part of the flash. Each page is deciphered in a temporary memory buffer, erased, and the rewritten with the cleartext.

Once done, the next step is to save the state in the internal flash, set SBRV to the entry point of the wireless image and trigger an OBL reset.

The PKA

This hardware block accelerate calculation for various public key algorithms. It has a dedicated ram used to store various parameters (with hardcoded offset for each mode of calculation). Once setup and started, the block is busy during a while until the result beeing available (again, at an hardcoded offset of the PKA ram).

So, why don't we try to monitor the PKA registers during step 1 of the install process ?

So, I wrote a small loop on my demoboard that would monitor PKA status register, and indeed, I saw the FUS using the block.

Next steps is to see if we can override the PKA result, and thus bypass signature verification.

First, slightly modify the image so that the signature does not match, but still the image is valid, by modifying the build version in the image footer. Indeed, an attempt to install this binary fails with an FUS_STATE_IMG_NOT_AUTHENTIC error.

Then retry the install whilemonitoring the PKA End of Operation flag, and then quickly write 0 at the right place in the PKA ram.

And install continues !

Good, we can bypass signature verification.

I was about to focus on how to exploit this signature bypass, but unfortunately, later on, while poking with the demoboard, I accidentally bricked it (don't remember exactly how).

So I try to reproduce the signature bypass with a custom board, but it constantly failed...

Hardened FUS - 1

In facts, the demoboard was programmed with the v1.1.1 of FUS, but the purchased stm32wb55 were programmed with v1.2.0. There must have been some changes in this area.

I retry my monitoring of the PKA status register and found that it seems not to be used. Reading the reference manual, I spotted a feature I was not aware of. Access to the PKA block can dynamically be restricted to CPU2 by writting in the SYSCFG_SIPCR. And indeed, FUS v1.2.0 seems to use this feature. So goodbye signature bypass on this version...

You shall not erase !

At that point, it was logic to try to recover the cleartext of this new version of FUS.

I liked the approach of monitoring FUS activity by watching the various block status/control registers, so I wrote a small monitor of the flash activity of FUS during the deletion of firmware. And we can clearly see FUS erasing pages one at a time before switching back SFSA.

But look at the (old) code:

void flash_erase_page(int page)

{
  dword cr;

  cr = read_volatile_4(Flash.C2CR);
  /* write page to erase */
  write_volatile_4(Flash.C2CR,page << 3 | cr & 0xfffff805 | 2);
  /* re read */
  cr = read_volatile_4(Flash.C2CR);
  /* start erase */
  write_volatile_4(Flash.C2CR,cr | 0x10000);
  return;
}

See how the register is re-read ?

So, I wrote a little code that monitor Flash.C2CR, wait for the desired page to be scheduled for an erase and overwrite the value with a different page. And it worked ! Not a 100% success, but still enough to grab each erased page one by one.

Applied this technic during a FUS self-update from v1.2.0 to v1.2.0 and I managed to get the code for this latest version of FUS.

Hardened FUS - 2

First, I wanted to go back to the PKA call just to confirm what I suspected.

int verify_signature(void *start, int len,
                     void *pub_key, void *signature)
{
  int iVar1;
  int iVar2;
  int ret;
  char hash [32];
  undefined4 local_18;

  ret = 0x37921932;
  memset(hash,0,0x20);
  iVar2 = init_pka();
  if (iVar2 != 1) {
    /* lock pka access */
    iVar1 = read_volatile_4(SYSCFG.SIPCR);
    write_volatile_4(SYSCFG.SIPCR,iVar1 | 4);

    ret = probably_hash(start, len, hash);
    if (ret == 0) {
        ret = pka_ecdsa_verif(hash,pub_key, signature);
    }
    deinit_pka();

    /* unlock pka access */
    iVar1 = read_volatile_4(SYSCFG.SIPCR);
    write_volatile_4(SYSCFG.SIPCR,iVar1 & 0xfffffffb);
 }
 return ret;
 }

Yes, it is now locking PKA access from CPU1 during signature check. Also, quite funny:

i = verify_signature(next_sfsa, len + 0x20, &st_pub_key, &signature);
if (i == 0x29912337) {
   /* signature ok */
   write_flash_sfsa((int) next_sfsa);
}

verify_signature is now returning a weird 0x29912337.... Looks like someone attended an "How to protect your code from fault attacks" course...

Burn after reading

Remember that any changes to sfsa requires a reset, triggered by a write to the OBL_LAUNCH bit of the FLASH_CR register of the flash controller ?

3.10.5 - Flash memory control register (FLASH_CR)

...

Bit 27 OBL_LAUNCH: Forces the option byte loading

When set to 1, this bit forces the option byte reloading.
This bit is cleared only when the option byte loading is complete.
It cannot be written if OPTLOCK is set.

0: Option byte loading complete
1: Option byte loading requested

Yes, there is a mechanism to prevent unwanted write to the flash controller by first entering a sequence 2-words key sequence before beeing abale to perform some write and erase. And another sequence for option bytes.

Turns out that you can also relock access by writing a 1 to the LOCK bit of the controller.

With that in mind, I came with a strategy:

  • let the PKA verify signature
  • monitor the SYSCFG.SIPCR to know when it is has done so
  • wait for the state and SFSA to be writtent in flash/option bytes by monitoring flash activity
  • lock OPTLOCK to prevent OBL reset
  • rewrite the signed firmware with my modified one
  • unlock OPTLOCK to reset and make FUS continue

As I did not want to brick my board on the first try, I just made a first test without the flash rewriting step.

Here I must first admit I had made a mistake when thinking at my strategy. Instead of the real code:

void launch_obl(void)

{
  dword dVar1;
  int iVar2;

  iVar2 = wait_flash_done(0xf00000);
  if (iVar2 == 0) {
    dVar1 = read_volatile_4(Flash.CR);
    write_volatile_4(Flash.CR,dVar1 | 0x8000000);
  }
  return;
}

I had this version in mind:

void launch_obl(void)

{
  dword dVar1;
  int iVar2;

  iVar2 = wait_flash_done(0xf00000);
  while(1) {
    dVar1 = read_volatile_4(Flash.CR);
    write_volatile_4(Flash.CR,dVar1 | 0x8000000);
  }
  return;
}

I was thinking that, locking the OPTLOCK would let the CPU2 in an endless loop, and after unlocking OPTLOCK the board would reset and continue the install process. So, I was expecting a reset and this situation:

page 0x00 +-------------+ <- 0x0800000 start of flash
          | customer    |
          |   code      |
          |             |
          ~ ~ ~ ~ ~ ~ ~ ~

          ~ ~ ~ ~ ~ ~ ~ ~
          |             |
page 0xdc +-------------+ <- SFSA
          |             |
          |  encrypted  |
          |   wireless  |
          |   firmware  |
          |             |
page 0xf4 +-------------+ <- SBRV
          |             |
          |     FUS     |
          |             |
page 0xff +-------------+ <- 0x080fffff end of flash

           FUS activated

CPU2 code execution

I did have a reset, but after it, I was in this sitation:

page 0x00 +-------------+ <- 0x0800000 start of flash
          | customer    |
          |   code      |
          |             |
          ~ ~ ~ ~ ~ ~ ~ ~

          ~ ~ ~ ~ ~ ~ ~ ~
          |             |
page 0xdc +-------------+ <- SFSA / SBRV
          |             |
          |             |
          |   wireless  |
          |   firmware  |
          |             |
page 0xf4 +-------------+
          |             |
          |     FUS     |
          |             |
page 0xff +-------------+ <- 0x080fffff end of flash

           After install

With a running wireless firmware.

Indeed, this is coherent: the FUS had stored its state in flash, but was expecting a reset in order for SFSA to be updated. Instead, it just returned from the launch_obl() function and continue the next step in his state machine, until the next OBL reset (the one needed to change th SBRV)

But what is the next step after image signature verification ? Yes... unciphering.

So instead of waiting for the reset after unlocking back the OPTLOCK, I monitored the flash pages where the image was written, and bingo, I clearly saw pages beeing erased and rewrittent. I had access to the unciphered image...

Good, but I had already access to the code...

...

Wait...

I have read access to the unciphered image but I can also modify it !

The path now was straightforward: change the first page with a reset vector pointing to an unsecure flash page while FUS was unciphering the other pages and wait for the OBL reset.

Of course, after the reset the CPU2 was stuck, but after writing at the controlled page a jump back to the expected reset vector, the CPU2 was back online and functionnal.

Key extraction

Then, instead of jumping back to the firmware, I wrote a small dumper code that will exfilter the upper pages of the secure zone of the flash to a CPU1 readable area.

Within thoses pages, lies the AES key used by FUS to uncipher the firmware images.

 ____                          _   _
|  _ \__      ___ __   ___  __| | | |
| |_) \ \ /\ / / '_ \ / _ \/ _` | | |
|  __/ \ V  V /| | | |  __/ (_| | |_|
|_|     \_/\_/ |_| |_|\___|\__,_| (_)

Conclusion

  • Security through obscurity is not security.
  • FUS also has a service to store customer AES keys inside the secure flash. My advice is not to use this to store valuable encryption keys, since they can now been extracted also.

If you enjoyed this read, feel free to tweet about it, or to contact me

Social Network

Categories

Feeds