Exploiting the Sonos One Speaker Three Different Ways: A Pwn2Own Toronto Highlight
May 25, 2023 | The ZDI Research TeamDuring Pwn2Own Toronto 2022, three different teams successfully exploited the Sonos One Speaker. In total, $105,000 was awarded to the three teams, with the team of Toan Pham and Tri Dang from Qrious Secure winning $60,000 since their entry was first on the schedule. Part of Pwn2Own competitions involves a random drawing for order. Not only does the team selected first get the full award if they are successful, but the subsequent entries are also more likely to have bug collisions. That means if three teams show up with the same exploit, only the first one randomly selected will get full credit. That’s not exactly what happened during the event, but some bug collisions did occur. In fact, there were four unique bugs used by the three different teams. Let’s take a look at the vulnerabilities used during the contest and see which team used which bug.
CVE-2023-27354 – The Unique libsmb2 Info Leak
With three different teams targeting the Sonos speaker, it’s obviously a huge advantage to go first. The team from Qrious Secure was randomly selected to go first, and they successfully exploited the speaker using a two-bug chain. This first bug used was this info leak, and they ended up being the only team to use this particular bug during the contest.
On the speaker, there exists a daemon named anacapad
that handles all Sonos-specific functions, including accessing music services, LED control, and audio playback. The vulnerability exists in the way anacapad
handles SMBv2 replies from a server, specifically in the smb2_process_query_directory_fixed()
function that processes query directory reply data. It does this by allocating an smb2_query_directory_reply
struct and storing the result in the Protocol Data Unit (PDU) pdu->payload
field. The function then extracts the output buffer offset and length from the query directory reply from the server:
The speaker then checks that the output buffer does not overlap with the query directory reply header. The offset here will later be used to calculate the IOV_OFFSET
:
This will also be used at:
The output_buffer
here will later be used to decode file information from that directory PDU response from the server in the smb2_decode_fileidfulldirectoryinformation
field. However, it never checks if the offset is within the len
of a PDU packet. This can be leveraged to perform an information leak by acting as a SMB server and sending a malformed PDU query directory response with a large offset. The client will decode the file information from an out-of-bounds (OOB) memory region and send that information back to the malicious SMB server as part of a filename. By manipulating the offset, the SMB server can determine libc and heap addresses on the client, which are useful for the next step of the exploit chain.
CVE-2023-27353 – The Other Infoleak
The libsmb2 bug wasn’t the only infoleak we saw during the contest. Two of the teams used CVE-2023-27533/ZDI-23-448 to kick off their exploit. This vulnerability resides in the /msprox
endpoint, which serves as a proxy for Sonos Speaker array communication. It forwards the user-supplied SOAP request to other registered speakers. When a user sends a request to this endpoint, the request is handled by function sub_15DFA0()
, which in turn calls sub_1C86C0()
. The following code snippet is from sub_1C86C0
in the anacapad
binary, corresponding to assembly code from address 0x1C876C:
The code fails to check the return value of snprintf()
at [1], and later uses this value as the size of an outgoing HTTP header buffer at [2]. The team from STAR Labs used this bug by sending a crafted request that provides overly long parameters to the snprintf()
function at [1]. In this way, the return value from snprintf()
(request_len
) exceeds the size of the request
buffer, which is 0x1000 bytes. At [2], it calls ana_server_send_request()
to send the contents of the request
buffer, using request_len
as the length. STAR Labs used this out-of-bounds read (OOBR) vulnerability to leak the address of the .text segment.
The DEVCORE Team took a slightly different route to achieve the same effect. Their exploit relied on acting like a rogue Sonos Speaker adjacent on the network to the target. This allowed them to reach the following code, which contains an out-of-bounds read vulnerability analogous to the one discussed above. The result is leakage of stack data right after the request_body
buffer.
From address location 0x1C889C
:
While they took different approaches, both teams arrived at the same vulnerable code and leaked the data needed to continue their exploit.
CVE-2023-27352 – Remote Code Execution Through libsmb2
Now that we have our info leaks established, let’s take a look at how two teams leveraged that information for code execution. Both the Qrious Secure and STAR Labs teams leveraged a use-after-free (UAF) bug in the libsmb2 library. Again, since Qrious Securewas randomly chosen to go first, they won the full $60,000 while the bug collision resulted in STAR Labs earning $22,500.
Sonos provides SMB functionality by incorporating the open-source libsmb2 library with a few modifications. It runs within the anacapad
daemon and can be reached by unauthenticated users to play music via the smb2 shared directory.
smb2_closedir
is implemented as below in libsmb2:
And smb2_lazy_readdir
is implemented as follows:
The main function handling data returned from an SMB server is as follows:
The control flow proceeds as follows:
(1) smb2_lazy_readdir -> smb2_fetchfiles_async -> smb2_cmd_query_directory_async -> create pdu (PDU) with internal message_id (MID) -> add to wait_queue (3)
When the client receives data from a server (4), it will decode the header (5) and find the PDU via its message_id (6). The interesting thing is that smb2_fetchfiles_async()
function adds the PDU to wait_queue
, which then holds a callback to fetchfiles_cb()
at (7). This callback keeps the dir
structure within its private data (8). Before it finishes, it invokes dir->cb
callback at (9).
Back to (6), in a normal scenario, the SMB server will return the data with the valid message_id
as MID
, and the callback will be triggered before dir
is freed in close_dir
at (2). However, if the server sends an invalid message_id
(different from MID
), the PDU
will not be found and will still be alive in wait_queue
. Should this occur, the PDU will keep holding on to the dir
struct pointer. When (2) finishes, the dir
pointer will be freed, and the PDU will be left holding a dangling pointer.
The next time the client tries to read data from the server, if we reply with a previously valid message_id
of MID
, the client will decode the data and find the corresponding PDU via that MID
. This time the PDU
’s callback fetchfiles_cb
will be called, and at (4) it will access the dangling pointer. By reallocating the freed dir
structure before fetchfiles_cb
is called, we can control the value cb
and thereby gain control of $PC by pointing cb
to maliciously crafted data.
Combined with the memory address leak from CVE-2023-27354, this vulnerability can be used to achieve remote code execution.
The STAR Labs team took a different approach to hit the same vulnerability. As stated above, the speaker allows us to play media files remotely using SMB using libsmb2. One of the added functions to this library is smb2_lazy_readdir()
. Here is how they triggered the bug.
-- smb2_opendir()
is called, which will return an smbdir
(smb2dir
structure).
-- smbdir
is later passed into smb2_lazy_readdir()
together with a custom callback function.
smb2_cmd_query_directory_async()
receives a callback function with the following prototype and any user-defined structure (in this case smb2dir
):
This function will then insert a pdu
(smb2_pdu
) into a request queue waiting to be handled. After a reply is received, the callback will be invoked with the received data. The Sonos device passes the smbdir
structure as cb_data
, then populates it inside the callback. This code snippet is in sub_109C0()
function of the libsmb2.so.1 binary, corresponding to assembly code from address 0x10A04:
If wait_for_reply
fails, meaning smb2_cmd_query_directory_async
did not receive any valid response for its pdu
, smb2_closedir
is invoked to free thesmbdir
object. Then smb2_disconnect_share
function is called. However, during this process, a dangling pointer to smbdir
is left in the request queue.
In smb2_disconnect_share
, it calls wait_for_reply
->smb2_service
->smb2_service_fd
->smb2_read_from_socket
->smb2_read_data
. In smb2_read_data
, it uses smb2_find_pdu
to retrieve the pdu
based on the message_id
of the response packet, which is controllable by the attacker. If our response specifies the message_id
of the Query Directory pdu, smb2_find_pdu
will return the smb2_cmd_query_directory_async
’s pdu. Finally, when pdu->cb
is invoked from z_query_directory_cb_109C0
, the dangling pointer leads to $PC control.
Reclaiming the freed smb2dir
object is accomplished by appending extra data onto the response packet from server. The client will allocate a buffer to store this extra padding data.
The exploit uses a modified impacket
to implement a malicious smb server.
The exploit proceeds as follows.
- Send a command to the target device to add a new SMB share.
- Using a modified
smb2QueryDirectory()
function in the exploit’simpacket
SMB server, return a malformed response to the client’ssmb2_cmd_query_directory
request, producing a dangling pointer. - When the client calls
smb2_disconnect_share
function, it sends a disconnect tree request to server. Using a modifiedsmb2TreeDisconnect
function in theimpacket
SMB server, the exploit returns a Query Directory response with themessage_id
from step 2. Additionally, the exploit appends data to this response to reclaim thesmb2dir
object in the client. - The exploit gains control of $PC via the overwritten
smb2dir->cb
pointer and uses ROP to get shell.
CVE-2023-27355 – Remote Code Execution via the MPEG-TS Parser
This remote code execution bug was used only by the DEVCORE team. Due to the random draw, this was the third attempt on the Sonos, which left them at a disadvantage as they were more likely to run into a bug collision. They did collide regarding their info leak, which we have already discussed above. However, the remote code execution portion of their exploit chain was unique and earned its own CVE.
While parsing a .ts
audio file, the Sonos speaker does not check the length of the Adaptation field, which leads to a stack buffer overflow. The bug results from the ability to specify an arbitrary value to the afelen
field. The speaker will then read the specified number of bytes into the payload buffer, smashing the stack.
From address: 0x406604
Since Sonos enables exploit mitigations, the attacker needs to leak some information first. During the contest, the DEVCORE entry first leaked the stack canary, stack address and program base address using the previously described CVE-2023-27353. Once they obtained those values, exploitation is straightforward. They used the MPEG-TS parser vulnerability to overwrite the return address and jump to the exec() wrapper.
As the #x19
and #x22
registers are also controllable by the exploit, the attacker can set these registers to a controllable stack buffer and execute arbitrary commands.
The end result was a successful demonstration during the contest, but due to the collision of CVE-2023-27353, the DEVCORE team didn’t win the full amount. Still, they earned $22,500 for being the third team to exploit the Sonos speaker during the event.
Wrapping Things Up
It’s always interesting to see different teams reach similar conclusions when targeting a piece of software. It’s equally as interesting when they take completely different paths but still end up with the same result. In this example, we had three different teams use a various combination of three different bugs to get code execution on a Sonos speaker. In the end, we awarded these teams a total of $105,000 for their efforts. Situations like this also highlight how bug collisions can encourage more thorough and innovative research to avoid duplicate entries in the future. All three of these teams had participated in Pwn2Own before, and we certainly hope they return for future events.
Now that many of the bugs disclosed during Pwn2Own Toronto are patched, we’ll continue to disclose some of those details on this blog. Until then, follow the team on Twitter, Mastodon, LinkedIn, or Instagram for the latest in exploit techniques and security patches.