Shooting Yourself in the .flags – Jailbreaking the Sonos Era 100

Research performed by Ilya Zhuravlev supporting the Exploit Development Group (EDG).

The Era 100 is Sonos’s flagship device, released on March 28th 2023 and is a notable step up from the Sonos One. It was also one of the target devices for Pwn2Own Toronto 2023. NCC found multiple security weaknesses within the bootloader of the device which could be exploited leading to root/kernel code execution and full compromise of the device.

According to Sonos, the issues reported were patched in an update released on the 15th of November with no CVE issued or public details of the security weakness. NCC is not aware of the full scope of devices impacted by this issue. Users of Sonos devices should ensure to apply any recent updates.

To develop an exploit eligible for the Pwn2Own contest, the first step is to dump the firmware, gain initial access to the firmware, and perhaps even set up debugging facilities to assist in debugging any potential exploits.

In this article we will document the process of analyzing the hardware, discovering several issues and developing a persistent secure boot bypass for the Sonos Era 100.

Exploitation was also chained with a previously disclosed exploit by bl4sty to obtain EL3 code execution and obtain cryptographic key material.

Initial recon

After opening the device, we quickly identified UART pins broken out on the motherboard:

The pinout is TX, RX, GND, Vcc

We can now attach a UART adapter and monitor the boot process:

SM1:BL:511f6b:81ca2f;FEAT:B0F02990:20283000;POC:F;RCY:0;EMMC:0;READ:0;0.0;0.0;CHK:0;
bl2_stage_init 0x01
bl2_stage_init 0xc1
bl2_stage_init 0x02

/* Skipped most of the log here */

U-Boot 2016.11-S767-Strict-Rev0.10 (Oct 13 2022 - 09:14:35 +0000)

SoC:   Amlogic S767
Board: Sonos Optimo1 Revision 0x06
Reset: POR
cpu family id not support!!!
thermal ver flag error!
flagbuf is 0xfa!
read calibrated data failed
SOC Temperature -1 C
I2C:   ready
DRAM:  1 GiB
initializing iomux_cfg_i2c
register usb cfg[0][1] = 000000007ffabde0
MMC:   SDIO Port C: 0
*** Warning - bad CRC, using default environment

In:    serial
Out:   serial
Err:   serial

Init Video as 1920 x 1080 pixel matrix
Net:   dwmac.ff3f0000
checking cpuid allowlist (my cpuid is 2b:0b:17:00:01:17:12:00:00:11:33:38:36:55:4d:50)...
allowlist check completed
Hit any key to stop autoboot:  0
pending_unlock: no pending DevUnlock
Image header on sect 0
    Magic:          536f7821
    Version                1
    Bootgen                0
    Kernel Offset         40
    Kernel Checksum 78c13f6f
    Kernel Length     a2ba18
    Rootfs Offset          0
    Rootfs Checksum        0
    Rootfs Length          0
    Rootfs Format          2
Image header on sect 1
    Magic:          536f7821
    Version                1
    Bootgen                2
    Kernel Offset         40
    Kernel Checksum 78c13f6f
    Kernel Length     a2ba18
    Rootfs Offset          0
    Rootfs Checksum        0
    Rootfs Length          0
    Rootfs Format          2
Both headers OK, bootgens 0 2
uboot: section-1 selected
boot_state 0
364 byte kernel signature verified successfully
JTAG disabled
disable_usb: DISABLE_USB_BOOT fuse already set
disable_usb: DISABLE_JTAG fuse already set
disable_usb: DISABLE_M3_JTAG fuse already set
disable_usb: DISABLE_M4_JTAG fuse already set
srk_fuses: not revoking any more SRK keys (0x1)
srk_fuses: locking SRK revocation fuses
Start the watchdog timer before starting the kernel...
get_kernel_config [id = 1, rev = 6] returning 22
## Loading kernel from FIT Image at 00100040 ...
   Using 'conf@23' configuration
   Trying 'kernel@1' kernel subimage
     Description:  Sonos Linux kernel for S767
     Type:         Kernel Image
     Compression:  lz4 compressed
     Data Start:   0x00100128
     Data Size:    9076344 Bytes = 8.7 MiB
     Architecture: AArch64
     OS:           Linux
     Load Address: 0x01080000
     Entry Point:  0x01080000
     Hash algo:    crc32
     Hash value:   2e036fce
   Verifying Hash Integrity ... crc32+ OK
## Loading fdt from FIT Image at 00100040 ...
   Using 'conf@23' configuration
   Trying 'fdt@23' fdt subimage
     Description:  Flattened Device Tree Sonos Optimo1 V6
     Type:         Flat Device Tree
     Compression:  uncompressed
     Data Start:   0x00a27fe8
     Data Size:    75487 Bytes = 73.7 KiB
     Architecture: AArch64
     Hash algo:    crc32
     Hash value:   adbd3c21
   Verifying Hash Integrity ... crc32+ OK
   Booting using the fdt blob at 0xa27fe8
   Uncompressing Kernel Image ... OK
   Loading Device Tree to 00000000417ea000, end 00000000417ff6de ... OK

Starting kernel ...

vmin:32 b5 0 0!

From this log, we can see that the boot process is very similar to other Sonos devices. Moreover, despite the marking on the SoC and the boot log indicating an undocumented Amlogic S767a chip, the first line of the BootROM log containing “SM1” points us to S905X3, which has a datasheet available.

Whilst it’s possible to interrupt the U-Boot boot process, Sonos has gone through several rounds of boot hardening and by now the U-Boot console is only accessible with a password that is stored hashed inside the U-Boot binary. Additionally, the set of accessible U-Boot commands is heavily restricted.

Dumping the eMMC

Continuing probing the PCB, it was possible to locate eMMC data pins next in order to attempt an in-circuit eMMC dump. From previous generations of Sonos devices, we knew that the data on the flash is mostly encrypted. Nevertheless, an in-circuit eMMC connection would also allow to rapidly modify the flash memory contents, without having to take the chip off and put it back on every time.

By probing termination resistors and test points located in the general area between the SoC and the eMMC chip, first with an oscilloscope and then with a logic analyzer, it was possible to identify several candidates for eMMC lines.

To perform an in-circuit dump, we have to connect CLK, CMD, DAT0 and ground at the minimum. While CLK and CMD are pretty obvious from the above capture, there are multiple candidates for the DAT0 pin. Moreover, we could only identify 3 out of 4 data pins at this point. Fortunately, after trying all 3 of these, it was possible to identify the following connections:

Note that the extra pin marked as “INT” here is used to interrupt the BootROM boot process. By connecting it to ground during boot, the BootROM gets stuck trying to boot from SPINOR, which allows us to communicate on the eMMC lines without interference.

From there, it was possible to dump the contents of eMMC and confirm that the bulk of the firmware including the Linux rootfs was encrypted.

Investigating U-Boot

While we were unable to get access to the Sonos Era 100 U-Boot binary just yet, previous work on Sonos devices enabled us to obtain a plaintext binary for the Sonos One U-Boot. At this point we were hoping that the images would be mostly the same, and that a vulnerability existed in U-Boot that could be exploited in a black-box manner utilizing the eMMC read-write capability.

Several such issues were identified and are documented below.

Issue 1: Stored environment

Despite the device not utilizing the stored environment feature of U-Boot, there’s still an attempt to load the environment from flash at startup. This appears to stem from a misconfiguration where the CONFIG_ENV_IS_NOWHERE flag is not set in U-Boot. As a result, during startup it will try to load the environment from flash offset 0x500000. Since there’s no valid environment there, it displays the following warning message over UART:

*** Warning - bad CRC, using default environment

The message goes away when a valid environment is written to that location. This enables us to set variables such as bootcmd, essentially bypassing the password-protected Sonos U-Boot console. However, as mentioned above, the available commands are heavily restricted.

Issue 2: Unchecked setenv() call

By default on the Sonos Era 100, U-Boot’s “bootcmd” is set to “sonosboot”. To understand the overall boot process, it was possible to reverse engineer the custom “sonosboot” handler. On a high level, this command is responsible for loading and validating the kernel image after which it passes control to the U-Boot “bootm” built-in. Because “bootm” uses U-Boot environment variables to control the arguments passed to the Linux kernel, “sonosboot” makes sure to set them up first before passing control:

setenv("bootargs",(char *)kernel_cmdline);

There is however no check on the return value of this setenv call. If it fails, the variable will keep its previous value, which in our case is the value loaded from the stored environment.

As it turns out, it is possible to make this setenv call fail. A somewhat obscure feature of U-Boot allows marking variables as read-only. For example, by setting “.flags=bootargs:sr”, the “bootargs” variable becomes read-only and all future writes without the H_FORCE flag fail.

All we have to do at this point to exploit this issue is to construct a stored environment that first defines the “bootargs” value, and then sets it as read-only by defining “.flags=bootargs:sr”. The execution of “sonosboot” will then proceed into “bootm” and it will start the Linux kernel with fully controlled command-line arguments.

One way to obtain code execution from there is to insert an “initrd=0xADDR,0xSIZE” argument which will cause the Linux kernel to load an initramfs from memory at the specified address, overriding the built-in image.

Issue 3: Malleable firmware image

The exploitation process described above, however, requires that controlled data is placed at a known static address. One way it was found to do that is to abuse the custom Sonos image header. According to U-Boot logs, this is always loaded at address 0x100000:

## Loading kernel from FIT Image at 00100040 ...
   Using 'conf@23' configuration
   Trying 'kernel@1' kernel subimage
     Description:  Sonos Linux kernel for S767
     Type:         Kernel Image
     Compression:  lz4 compressed
     Data Start:   0x00100128
     Data Size:    9076344 Bytes = 8.7 MiB
     Architecture: AArch64
     OS:           Linux
     Load Address: 0x01080000
     Entry Point:  0x01080000
     Hash algo:    crc32
     Hash value:   2e036fce
   Verifying Hash Integrity ... crc32+ OK

The image header can be represented in pseudocode as follows:

uint32_t magic;
uint16_t version;
uint16_t bootgen;
uint32_t kernel_offset;
uint32_t kernel_checksum;
uint32_t kernel_length;

The issue is that while the value of kernel_offset is normally 0x40, it is not enforced by U-Boot. By setting the offset to a higher value and then filling the empty space with arbitrary data, we can place the data at a known fixed location in U-Boot memory while ensuring that the signature check on the image still passes.

Combining all three issues outlined above, it is possible to achieve persistent code execution within Linux under the /init process as the “root” user.

Moreover, by inserting a kernel module this access can be escalated to kernel-mode arbitrary code execution.

Epilogue

There’s just one missing piece and that is to dump the one time programmable (OTP) data so that we can decrypt any future firmware. Fortunately, the factory firmware that the device came pre-flashed with does not contain a fix for the vulnerability disclosed in https://haxx.in/posts/dumping-the-amlogic-a113x-bootrom/

From there, slight modifications are required to adjust the exploit for the different EL3 binary of this device. The arbitrary read primitive provided by the a113x-el3-pwn tool works as-is and allows for the EL3 image to be dumped. With the adjusted exploit we were then able to dump full OTP contents and decrypt any future firmware update for this device.

Disclosure Timeline

Date Action
2023-09-04 NCC reports issues to Sonos
2023-09-07 Sonos has triaged report and is investigating
2023-11-29 NCC queries Sonos for expected patch date
2023-11-29 Sonos informs NCC that they already shipped a patch on the 15th Nov
2023-11-30 NCC queries why no release notes, CVE or credit for the issues
2023-12-01 NCC informs Sonos that technical details will be published the w/c 4th Dec
2023-12-04 NCC publishes blog and advisory

Call us before you need us.

Our experts will help you.

Get in touch