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 |