Dissecting the Hype Machine

Blog

Dissecting the Hype Machine

share

There's a new version of the Hype Machine! Cool. The mp3 blog aggregator's gotten a new coat of paint and a different flash player. It looks pretty nice, although I'm not entirely sure what substantive changes have been made. Nevertheless, it's at least much more t-shirt-compatible.

I decided to celebrate the occasion by digging into the workings of the site a bit more. Hypem provides a lot of music, but is understandably hesitant to provide direct downloads lest they be busted by The Man. But how do you go about providing an mp3 for listening but not for saving? It's as fundamentally unsolvable as any other DRM problem — more so, given the relatively open technologies in use.

Still, they do their best. For instance, only requests from known web browsers are allowed — try to use a command-line tool like wget or curl to fetch content and you'll get an "access denied" message. But it's easy to fake user agent strings (or just to do the dirty work within your browser). So let's have a look at the anatomy of playing a song on hypem:

  1. You click the play button next to a track.
  2. An AJAX request is sent that looks something like this:

    http://hypem.com/inc/serve_nowplaying.php?id=401678_1

    The part in bold is an identifier that's unique to the song you requested.

  3. Some HTML is sent back and placed in the portion of the page where the play button used to live. It looks like this:
    <object class="play" classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=7,0,0,0" width="36" height="18">
       &lt;param name="movie" value="http://hypem.com/h2p.swf?autoplay=true&url=<strong>N2NjZGZkYTJkMjc0ZTZmNGY3OTVmNmQ0Mzg4MTEzYTVjMTgyM2NhY2ZmYzI2ZTAyMzE2MGIwMDY1NjJmOTA5MTJlMzE1ODA5MzYyYzBjODJiYjdjODBhNGI0ZDIwODkyMDRhNTQ3M2U4OWQwOGE2Mjk5YjQ1MWRjMjk1ZjFkNTlmYmIyZWIwZmU5YThlMDU1</strong>"&gt;
       &lt;param name="wmode" value="transparent"&gt;
       &lt;param name="quality" value="high"&gt;
       &lt;embed src="http://hypem.com/h2p.swf?autoplay=true&url=<strong>N2NjZGZkYTJkMjc0ZTZmNGY3OTVmNmQ0Mzg4MTEzYTVjMTgyM2NhY2ZmYzI2ZTAyMzE2MGIwMDY1NjJmOTA5MTJlMzE1ODA5MzYyYzBjODJiYjdjODBhNGI0ZDIwODkyMDRhNTQ3M2U4OWQwOGE2Mjk5YjQ1MWRjMjk1ZjFkNTlmYmIyZWIwZmU5YThlMDU1</strong>" quality="high" wmode="transparent" pluginspage="http://www.macromedia.com/go/getflashplayer" type="application/x-shockwave-flash" width="36" height="18"&gt;&lt;/embed&gt;
    &lt;/object&gt;

    This code tells the browser to load an Adobe Flash object called h2p.swf and pass it parameters telling it to start playing immediately (autoplay=1) and a mysterious url parameter, which I've highlighted in bold.

  4. Using the LiveHTTPHeaders plugin for Firefox, we can see that the flash video then requests a file named something like http://hypem.com/serve/f/509/401678/abcdefghijklmnopqrstuvwxyz1234567890.... That's straightforward enough. But how does it get that long URL from the even-longer string that's passed to the Flash movie?

To find out, we've got to take a look inside the seemingly black box of the flash movie. Fortunately there's a great tool that lets us do that: Flare, which is free, cross-platform, and will happily extract the ActionScript from a Flash movie. I grabbed the h2p.swf file and passed it to Flare. Here's the interesting part of what I got back:

this.m = new mp(this, com.meychi.ascrypt.RC4.decrypt(com.meychi.ascrypt.Base64.decode(_root.url), 'abcdef1234567890'));

Hello there... looks an awful lot like a decryption routine... and something that looks suspiciously like a decryption key! This line takes the aforementioned url querystring parameter, Base64-decodes it, then passes it to an RC4 algorithm decryption routine along with the decryption key abcdef1234567890 (key changed to protect the innocent/record executive). This turns the url parameter into a usable URL, which the flash player then fetches.

The meychi.ascrypt library's website is offline, but a little digging into its code (also returned by Flare) shows that, unlike most RC4 decryption libraries, it expects to receive a string of hexadecimal bytes which it first converts into a string of chars before passing to the normal RC4 routine. The need for this extra step had me scratching my head for a while, but eventually I figured out what was going on and cobbled together this script to replicate the functionality. It's in Perl, since I couldn't find any RC4 routines in Ruby.

#!/usr/bin/perl
 
use MIME::Base64;
use Crypt::RC4;
 
# hype mac/hine's secret encryption cipher... shhh!
$passphrase = 'abcdef1234567890';
 
if($src = <>)
{
        # decode the URL-safe parameter from base64
        $unencoded_src = decode_base64($src);
 
        # convert decoded input from hexadecimal bytes to a string of chars
        $charred_ciphertext = '';
        while(length($unencoded_src)>0)
        {
                $char = substr($unencoded_src, 0, 2);
                $charred_ciphertext .= chr(hex($char));
                $unencoded_src = substr($unencoded_src,2);
        }
 
        # decrypt with RC4 algorithm
        print RC4($passphrase,$charred_ciphertext);
}

Pipe the url parameter to that script and it'll spit out the URL of the actual file. Paste that into your browser and you'll be redirected to the file's actual location — your browser will begin downloading it quite happily.

Of course, this is all kind of a huge pain in the ass. It'd be much easier to follow the link to the source blog and keep your fingers crossed that the original mp3's still alive. But if you could just find Javascript libraries for Base64 encoding and RC4 decryption you could make a bookmarklet or Greasemonkey script that automatically adds a direct download link to every hype/m entry. Hmmmmm.

Anyway, I should probably finish by saying that none of this should be taken as an indictment of the programmers' skills. The Hype Machine is a truly impressive piece of software, and the countermeasures its creators have implemented to prevent direct downloading are pretty much everything I can think of doing. The problem is simply that allowing a user to hear content but not store it is an impossible task. And keeping secrets hidden in Flash — which is the only appropriate technology for this application — is similarly impossible, making whatever obfuscation they employ relatively easy to poke through.

The only improvement I can think to make would be to rotate encryption keys by serving a variety of different player SWFs, and invalidating an mp3's URL as soon as an incorrect key is used (I assume that the URLs produced by my script are temporary redirects that rotate fairly frequently and can be expired as necessary). This way a user couldn't cycle through the known keys. As far as I know, decompiling an SWF is not something that can be accomplished in Javascript.

But it probably could be done within a full-on Firefox plugin. And given browsers' enthusiasm for caching Flash (and Javascript's ability to easily differentiate SWFs with different names), the above proposal might not be a viable approach at all. Really, there's no way to completely secure this system. "Good enough" is all that one can reasonably hope for, and I think they've already achieved that.

I see you like to read printed material. You should check our Nicco's book The End of Big: http://endofbig.com