Notes on Hacking the Roku Netflix Player

Eric Cooper <ecc@cmu.edu>

August 2008

Introduction

I've been trying to hack into my Roku Netflix Player, because I want to see if it can be used as an embedded Linux platform generally and as a MythTV frontend in particular. I have no interest in subverting or otherwise interfering with Netflix's video distribution service, nor am I encouraging anyone else to do so.

I've been unsuccessful so far, but I've found a lot of interesting information that I'll share in this note, in the hope that others will join the effort and make further progress.

Intercepting the Software Update Phase

Instead of connecting my Roku unit to the Internet, I connected it via an Ethernet patch cord to a laptop. (I also turned off the wireless access point in my house to make sure that it wouldn't use that instead.)

I configured the laptop to provide (very limited) DHCP and DNS service using the dnsmasq package. The Roku startup screen helpfully displays its MAC addresses. I configured the laptop Ethernet interface to have IP address 192.168.99.1, and added these lines to /etc/dnsmasq.conf:

    # respond to all DNS lookups with this IP
    address=/#/192.168.99.1
    # enable DHCP
    dhcp-range=192.168.99.50,192.168.99.150,12h
    # give the Roku player this IP
    dhcp-host=00:0D:4B:xx:yy:zz,roku,192.168.99.2

Then I turned on the Roku player and used wireshark to capture the network traffic.

The player first gets its IP address via DHCP. Then it does a DNS lookup for moviecontrol.netflix.com, which it attempts to contact via HTTPS. Since I didn't have a server listening on port 443, that failed.

The player then looks up netflix.software.rokulabs.com and tries to connect via HTTP. I tried again with a netcat process listening on port 80, and captured this request:

    GET /cgi-bin/manifest?sn=0123456789AB&version=011.00E00380A&category=release&force=false

(I've changed the 12-digit hex serial number field for privacy.)

On a different machine, I used wget to fetch that URL. The resulting "manifest" is:

    force=true
    /dev/mtd2=http://cdn-0.nflximg.com/us/011.00E00830A
    sha1(/dev/mtd2)=3f3c74a65c44a3d4364f61aa836e3a998709ff7a
    /dev/mtd4=http://netflix.software.rokulabs.com/cgi-bin/iblock?0123456789AB
    sha1(/dev/mtd4)=53aced340d9b11daea87852405e490d1dd0dd972
    privdat=[80-digit hex string]
    [128 bytes]

The iblock parameter is the same serial number supplied to the GET request.

Downloading the manifest URL with no parameters results in a different response:

    force=true
    /dev/mtd2=http://cdn-0.nflximg.com/us/011.00E00830A
    sha1(/dev/mtd2)=3f3c74a65c44a3d4364f61aa836e3a998709ff7a
    /dev/mtd0=http://cdn-0.nflximg.com/us/011.00E00830A.boot
    sha1(/dev/mtd0)=f8a6fabc972036944b659e34231de2acb844069d
    [128 bytes]

The 128-byte fields seem to be digital signatures of some kind.

The Flash Partition Images

So we have URLs for three flash partition images (mtd0, mtd2, and mtd4). I downloaded each of them, and verified that they had the given SHA1 checksums. Here are their sizes:

    mtd0:    294460
    mtd2:  23248924
    mtd4:       496

I haven't figured out the format of the mtd0 boot image, but running the "strings" command shows that it contains a bootloader:

    -- Griffin Primary Bootloader v0.1-1091d (17:15:35, Mar 20 2008)
    -- Andre McCurdy, NXP Semiconductors

The real bonanza is the 23-megabyte mtd2 file: it's a cramfs image of the root filesystem. You can mount it by doing

    mount -t cramfs mtd2.img /mnt -o loop,ro

I haven't determined the purpose of the mtd4 image. It starts with 256 bytes that count up from 0 to 255. Then 8 more bytes, then the 40 bytes that correspond to the "privdat" field in the manifest, then some zeroes and something that's probably a digital signature at the end.

Some Bits I've Learned So Far

The main set-top box application is in /bin/Application. A dump of the strings in this program is very informative. It's a C++ program that can be disassembled using objdump from a cross-tool version of GNU binutils for mipsel:

    mipsel-linux-gnu-objdump -C -d --no-show-raw-insn bin/Application

You can find the DebugServer (the "Frampton Debug Terminal") that runs on port 8080. I discovered that the "exec" command expects to read a signed binary program image over the connection. For example, if you do

    (echo "exec"; cat /bin/sh) | netcat 192.168.99.2 8080

the player rejects it with "invalid signature".

The public key for the signature appears to be

    -----BEGIN PUBLIC KEY-----
    MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCuuttw53s0RGiaRKdL2QhurzTq
    ulpB9TusX1d9FWjLeKdWTNtP/eAsHEUA8fZhOcYU03L/bRaTv2V+biuQeZKzMXkQ
    oxmk+uCUpH8mUlmCW/8fiqqDSeDI/etiqYUIA8+//3Vxx8VWkcf2187dt0YYCO2d
    6ky18JjVuO4tRbLm+QIDAQAB
    -----END PUBLIC KEY-----

Unless someone can find the corresponding private key, or an already-signed executable, this approach looks like a dead end.

I tried to spoof the software update process by delivering my own "manifest" with an altered cramfs image. I altered /etc/passwd, to allow a root login with no password, and /etc/init.d/S70servers.sh, to start telnetd and httpd servers. (Otherwise these only get started if the "dev=1" kernel boot parameter is present.) Then I rebuilt the cramfs image and modified the corresponding SHA1 field of the manifest.

I installed an HTTP server (mini-httpd) on my laptop and set up simple CGI shell scripts to deliver the modified manifest and flash partition images.

The player successfully retrieved the manifest, downloaded the modified mtd2 image, and updated the on-screen progress indicator from 0% to 100% while downloading. It then retrieved the mtd4 (iblock) image, but afterwards it put up a failure screen that said "can't connect to roku". So some kind of verification beyond just the SHA1 seems to have failed.

First I assumed that it was the 128-byte signature at the end of the manifest, so I tried delivering the unmodified manifest and mtd2 image. But that gives the same result.

After the player downloads the images, it makes another attempt to contact moviecontrol.netflix.com via HTTPS. I can't tell for sure whether this is what causes it to fail.

I tried to spoof the HTTPS server with certificates similar to those the Netflix server uses. I used

    gnutls-cli --print-cert --insecure moviecontrol.netflix.com | certtool -i > netflix.pem

to capture Netflix TLS certificates, then a lot of openssl hackery to mimic them. But the player rejects the Server Hello message with a TLS "decryption failed" error. A more elaborate "man-in-the-middle" setup appears to be needed.

Next Steps

There's a lot more to be learned from the root filesystem image. Someone skilled in MIPS assembler might be able to decompile the application further and find an exploit. Someone knowledgeable about TLS and cryptographic signatures might make more progress with a man-in-the-middle attack.

If others are interested in going further, please let me know. We can share progress on one of the existing lists or create another forum of some kind. I'll be happy to provide more details about anything I've tried so far.