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:
http://hypem.com/inc/serve_nowplaying.php?id=401678_1
<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"> <param name="movie" value="http://hypem.com/h2p.swf?autoplay=true&url=N2NjZGZkYTJkMjc0ZTZmNGY3OTVmNmQ0Mzg4MTEzYTVjMTgyM2NhY2ZmYzI2ZTAyMzE2MGIwMDY1NjJmOTA5MTJlMzE1ODA5MzYyYzBjODJiYjdjODBhNGI0ZDIwODkyMDRhNTQ3M2U4OWQwOGE2Mjk5YjQ1MWRjMjk1ZjFkNTlmYmIyZWIwZmU5YThlMDU1"> <param name="wmode" value="transparent"> <param name="quality" value="high"> <embed src="http://hypem.com/h2p.swf?autoplay=true&url=N2NjZGZkYTJkMjc0ZTZmNGY3OTVmNmQ0Mzg4MTEzYTVjMTgyM2NhY2ZmYzI2ZTAyMzE2MGIwMDY1NjJmOTA5MTJlMzE1ODA5MzYyYzBjODJiYjdjODBhNGI0ZDIwODkyMDRhNTQ3M2U4OWQwOGE2Mjk5YjQ1MWRjMjk1ZjFkNTlmYmIyZWIwZmU5YThlMDU1" quality="high" wmode="transparent" pluginspage="http://www.macromedia.com/go/getflashplayer" type="application/x-shockwave-flash" width="36" height="18"></embed> </object>
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.
Also, I know you changed the
Also, I know you changed the key, but you didn't alter the url which points to the Flashing Lights mp3...
If Flare can decompile the
If Flare can decompile the source, then why did you rewrite the decryption routines when they're all contained in the players' source files?
DiscGoIsUm: Yeah, if you
DiscGoIsUm: Yeah, if you just want the MP3s live HTTP (or any other wire-sniffing utility) is perfectly sufficient. If you want a more general-purpose client-side solution, it isn't.
Jim: An oversight on my part -- thank you for pointing it out. I've removed the URL.
Anonymous: I'm not really interested in creating other Flash clients, so the actionscript decryption routines weren't something I felt like delving into more than necessary. I think it's more interesting to take the routine and move it into a generalized form. I've successfully ported it to javascript as well, although of course I won't be posting that...
Of course, ActionScript isn't too far removed from JS, so this last point may seem unimpressive. But again, I wanted to avoid simply relying on meychi's library
It's a good question. The
It's a good question. The simple answer is just that I'm not familiar enough with Flash to have thought of it (or have been comfortable relying on it). But I also wanted something that'd run on the server side, and porting an encryption library from one language to another seemed like a recipe for hard-to-replicate bugs. But congrats on doing it -- and thanks for letting me know that it isn't too horrific a process. It's good to know that I can use that technique in the future.
Thanks. A friend of mine has
Thanks.
A friend of mine has uploaded a greasemonkey script that adds direct download links to hype machine pages.
great article! I feel bad,
great article! I feel bad, but when a blog's mp3 dries up, what are you going to do?
Post new comment