Tuesday, October 29, 2024

Looking into the Nintendo Alarmo

While everyone was waiting on news for the successor of the Nintendo Switch, Nintendo released the Alarmo. A small plastic alarm clock that can wake you up with sounds from your favorite Nintendo games. While I was hesitant to buy one at first, I eventually decided to get one and look deeper into how it works.

Just an Alarm Clock?

The Alarmo has a small 2.8-inch LCD at the front, a back and notification button on top, and a dial on top which can be turned and pressed to act as a confirm button. The dial also includes an RGB LED. What makes it different from other alarm clocks? It has 2.4 GHz Wi-Fi to download software updates and additional themes, and it comes with a 24GHz mmWave presence sensor to react to your movements. With a hefty price of 99€, I didn't want to buy one at first. But after a few days I decided it could be a fun project to look into how it works behind the red plastic shell, so I bought one.

 

What's inside?

Since I placed my order right before the weekend, I had to wait until a week after launch before the Alarmo finally arrived. In the meantime, someone on Twitter named Spinda already found some SWD debugging pins on the board, so the first thing I did was open up the Alarmo.
Getting to the board is really straight forward. There's a single phillips screw at the bottom of the device, next to the USB-C port. After removing the screw the screen simply twists off the front together with the board attached to it. On the board, there's an STM32H730ZBI6 MCU and a KIOXIA 4GB eMMC.
After soldering wires to the SWD pins and connecting to the Alarmo with my Pi, I could use OpenOCD to peek and poke at the Alarmo's memory and registers. Unfortunately I couldn't read out the STM's internal flash where the system starts running the firmware from, due to a mechanism called readout protection (RDP). This mechanism prevents any access to the internal flash once a debugger has been detected. To defeat RDP we need to find a way to achieve code execution without attaching a debugger.
What's awesome about the STM32H7 is that the reference manual and lots of example code and libraries are freely available. The debug port also allows loading a payload into memory and executing it, so there's already lots of potential things which can be done here.

 

Is This Flash?

Since Spinda was posting some massive progress on Twitter, I decided to contact her. She told me to take a look at the 0x70000000 range in memory. Dumping from there indeed reveals a lot of ARM instructions. This area seems to contain most of the firmware!
According to the reference manual this is the so called OCTOSPI2 range. OCTOSPI is a low-level interface which is used for single/dual/quad/octal SPI communication. SPI? Is there an SPI flash on the board that I missed? There seems to be a small chip without any useful markings next to the MCU. Could that be it? After starting to reverse engineer the firmware, which I just dumped, it seems to treat the OCTOSPI range as RAM. And indeed, writing a value to an unused part of this area and resetting the system causes the value to reset back to 0x55. After examining the OCTOSPI register configuration, this seems to be 32 MiB of HYPERRAM used for external RAM. Unfortunately this doesn't help us to defeat RDP, since we can't have a persisting payload in external RAM.

 

Picture of what seems to be the external RAM on the board.
 

Decrypting the Contents

So where is this firmware in RAM coming from? It's getting loaded from the eMMC on startup. Spinda had already dumped the eMMC at this point, using the eMMC functions provided by the firmware in RAM. The eMMC contains a content folder with files for each of the game themes, a system file, a factory file and a file called 2ndloader.bin. Unfortunately all of the content files are encrypted. But how does the system decrypt them? The STM32H7 contains a cryptographic processor called CRYP. This peripheral can be configured and then used to decrypt content in memory. The firmware dumped from external RAM doesn't configure CRYP, so it's most likely getting configured by the code on the internal flash, which runs on startup. But the firmware is using the CRYP interface to decrypt the contents, and so can we! The CRYP interface is configured for AES-128-CTR, which makes things easier. Since, in CTR mode, a keystream is created, which is then combined with the plaintext to encrypt and decrypt files, we can simply create a large amount of this keystream using the CRYP interface, and then combine it with the encrypted files to decrypt them. After dumping around 100MB of keystream using the debug interface, we were able to decrypt all files on the flash.

Bonus: Obtaining the Key

When configuring the CRYP interface, the key is placed into four 32-bit registers. Unfortunately reading out the key from those registers isn't possible, since they are write-only. Brute-forcing also isn't a viable option since there are 2128 different possible combinations.
While Spinda was already looking into the contents of the eMMC (She found lots of interesting stuff, keep an eye on her Twitter!), I started talking with hexkyz about the findings. Hexkyz noticed that the CRYP interface is vulnerable to a partial overwrite attack. And indeed, since the key is split up into 4 different registers it's possible to only update 32 bits of the key and then try out all 232 different possibilities until matching output is produced by the crypto processor. This needs to be done for all four parts of the key, so we need to test for a total of 4×232 different combinations, which is possible to do in a few hours. After writing a small payload to perform this, I let it run overnight. The next morning I checked the progress and it was done, I had successfully obtained the AES-128-CTR key used to encrypt and decrypt the Alarmo content files.


sha256(alarmo_content_key)=47238c47d21165fdb2f9a26c128e4b620a39139f6514588f5edb8a16397a9201

 
The initialization vector can simply be read out from the IV registers, as these aren't write-only.

Update 2024-10-31:

It's also possible to encrypt known plaintext blocks on the Alarmo and then progressively overwrite parts of the key with zeros, while dumping the resulting ciphertext for each part. Then the brute force attack can be performed on a PC, by incrementing the key until the output matches the ciphertext obtained with only parts of the key not set to zero. For this I've created a PC tool using the AES-NI x86 extensions. Doing this on a PC reduces the time from several hours to a few minutes (on a somewhat modern PC). I've uploaded the tools for this to the GitHub repository. Thanks to @SciresM and @PoroCYon for pointing this out!

File Format Overview

Now that we are able to decrypt the files, let's take a look at them. All content files on the eMMC are encrypted and prefixed with a CIPH file signature magic. Let's call them CIPH- or cipher-files throughout this post. The bodies of those CIPH files are AES-128-CTR encrypted. The last 256 bytes of the encrypted body make up a RSA-2048 signature (PKCS#1 v1.5 with SHA256).
All themes, as well as the system and factory files, are .shpac files. These files are simply ZIP files wrapped inside of a CIPH file.
Every shpac archive contains all sorts of assets and a firmware binary. These firmware binaries start with a BINF file signature magic, so let's call them BINF-files. Every BINF file has a header, which contains the address where the file should be loaded to in memory, the address of the vector table, and the total size of the file in memory.

The 2ndloader

The most interesting part of the contents on the eMMC is the 2ndloader. As the name implies, this is a secondary loader that's loaded from the eMMC. The 2ndloader is the only content file which isn't a shpac file; Instead, it's loaded and decrypted by the loader on the internal flash, directly into SRAM (@0x24000000). During a normal boot, the 2ndloader will load a firmware from the encrypted system.shpac file on the eMMC, copy it into the external RAM, and then jump to it. Surprisingly the signature of the system.shpac is not checked during a normal boot, only when performing a firmware update.

 

Firmware Updates

Firmware updates are relatively straightforward. The system firmware queries an endpoint, using a device unique certificate, for the latest firmware version, which then responds with a CDN link to an updated system.shpac. This file then gets downloaded and stored as system.update.shpac on the eMMC and the device reboots into the 2ndloader. The 2ndloader verifies the signature of the updated file and then copies it over the existing system.shpac. During this copy process a small progress bar is drawn on the screen. Then the signature of the new system.shpac is checked.

 

USB Loader

The most interesting part of the 2ndloader is the USB mode. When all three buttons on top of the Alarmo are held during boot, the 2ndloader sets up a USB mass storage device with a FAT32 formatted buffer in external RAM. It then waits for a MarkFile to be placed on the device. Once that file is found, it reads a CIPH file from the device, which contains a BINF firmware. Like all other CIPH files, this file is encrypted and signed. After decrypting the file and loading it into external RAM, the 2ndloader jumps to the newly loaded reset vector, like it does for the regular system firmware.
They are checking the signature for the USB payload, right? Right? Well you could say they tried... Their code looks something like this:
if (!IsSignatureValid("2:/a.bin")) {
// Do nothing
}

// Continue with loading the firmware


This allows us to load arbitrary firmware binaries over USB without even opening up the Alarmo. The file still needs to be encrypted, but that's easy now that we have the key or even just a large enough keystream.

Picture of a custom payload running on the Alarmo, displaying a picture of a cat.

 

Defeating RDP

Now that we can run arbitrary code without needing a debugger attached, it's possible to avoid triggering the flash readout protection. So I ended up writing a small payload which attempts to copy the contents of the internal flash to RAM. Unfortunately this didn't work and I was only reading zeroes. While the read protection error was no longer being set, there was another error in the way: The secure error flag was set.
Turns out the STM32H7 has another protection mechanism in the way called "Secure Access mode".

Secure Access Mode

In Secure Access mode the MCU always boots into a secure bootloader made by STM. This bootloader supports setting up a secure area containing secure user code, which can then perform various tasks. After the secure user code finishes, it jumps to the regular application and gets locked out. While we don't know what the secure user code exactly does in case of the Alarmo, it has to be responsible for setting up the CRYP interface and is going to load, decrypt, and jump to the 2ndloader. Right before jumping to the 2ndloader the secure user area gets locked out and is no longer readable. I unfortunately haven't been able to dump either the secure bootloader nor the secure user area. So what exactly happens in there remains a mystery for the time being.

Bonus: Exploiting the 2ndloader

While reverse engineering the 2ndloader, I noticed that it uses RSS_exitSecureArea to jump to the loaded firmware. That's the function which is used to lock out and leave the secure user area. So the 2ndloader might still run in secure mode? So I started looking for a way to gain code execution in the 2ndloader before it jumps to the loaded code and exits the secure area. Remember the BINF file which gets loaded in USB mode? It has an address in the header where it's supposed to get loaded to. Spinda asked, "Will it copy to any address you specify in the bin file header?". That's a good question! I remember seeing code which verifies that it needs to be within external RAM. After double checking, only the vector table address needs to be within external RAM. The file itself can be loaded anywhere. This allows overwriting instructions from the 2ndloader, causing a jump to custom code before RSS_exitSecureArea gets called. Unfortunately reading out the internal flash still triggered the secure error. So it looks like the 2ndloader doesn't actually still run in secure mode, even though it's using the function to exit from the secure area.

 

What's next?

So what's next? There's now a way to run custom code on the Alarmo without opening it up. This currently still works on software version 2.0.0, and there doesn't seem to be a system in place for updating the 2ndloader yet. Of course, it's technically possible to update the 2ndloader on the eMMC, so we'll see what's going to happen.
What about the secure user area? I'm currently out of ideas on how to dump it for now. Since we already have the key, there probably aren't many other interesting things we could gain from dumping it, besides of course getting confirmation on how it works.

When this blog post goes public, I'm releasing my testing USB payload which performs display initialization and shows a picture of a cat. I'm also going to release the payload I used to obtain the content key. You can find both of those in the GitHub repository here: https://github.com/GaryOderNichts/alarmo
Maybe more people are going to look into writing custom code for the Alarmo.

Credits

  • Spinda for figuring out the SWD pins, writing code to dump the eMMC, and listening to my 2ndloader ramblings.
  • hexkyz for helping with finding resources about the secure area and giving me the idea to dump the key.


2 comments:

  1. This is amazing! Can you get it to load Doom when the alarm clock time is triggered and have it put you in a random location in a random level? It would be an insane way to wake up in the morning! Grab the alarm clock and blast your way to being awake.

    ReplyDelete