My circle of friends use it basically as an extension of weird Twitter – most
snaps I send and receive are strange angles of weird objects; the completely
mundane but somehow therapeutic (7 seconds of the camera pointed outside the
window of a tram, pointed at the ground moving below); or just closeups of
Curtis Stone’s face,
wherever we see him.

Of course, the promise that they won’t get retained is just that: a promise.
Since your phone receives this image and shows it to you at some point, it must
be downloaded by your phone. If it can be downladed by the phone, it can be
downloaded by something else. We decided to find out how.

My first thought was to use
Cain to re-route
the phone’s traffic to Snapchat via a computer with ARP poisoning, then
Wireshark to packet-sniff. For whatever reason, we weren’t able to make this
work; while we did see some of the traffic (the non-HTTPS stuff), HTTPS
wouldn’t seem to pass through my friend’s computer.

Another got to work using a different set of tools on a Linux machine to do ARP
stuff, and I took a more direct route.

First, I set my phone’s proxy on WiFi to point to my machine. Then I just
listened with netcat. After receiving lots of apparently unrelated requests
(and the aforementioned HTTP requests1), I found that Snapchat was
requesting an SSL forward:

We open a regular SSL socket to Snapchat’s GAE server, forward the request from
the phone, and read back what Snapchat said.

It turns out this is enough to start getting sensitive data in a form where we
can attack it offline3.

Add some logging of responses to file; you’ll see a request to /ph/sync,
followed by a bulk of data indicating who our friends are, and information
regarding new snaps. Then, the phone will try to fetch those snaps: you’ll see
requests to /ph/blob, like:

It turns out all the data required is in the URI; no funny header business.
You can paste the requested URL directly into a browser and fetch the blob.

What you get is identified by the BSD file tool as data – not obviously an
image, video, or whatever, nor any headers indicating encryption or
compression.

First, I had a look at the size: 19,712 bytes, which is divisible by 256. This
feels like too much of a coincidence; I’d be surprised by divisibility by
anything above 4 or 8. My going assumption is that images are transferred in
JPEG, and ~half the JPEGs I looked at on my disk have odd numbers of bytes, so
I’m guessing there’s nothing frame-y about JPEG that would cause the plaintext
to be in a regular block of bytes – so conclusion, it’s probably a block
cipher.

Next, it was time to see if there were any obvious cryptographic errors. A
repeated block in the ciphertext might give us a hint about the encryption
mode.

So apparently a 16-byte (128-bit) block cipher, in ECB
mode
at that. (Not a good thing.) Seeing as there wasn’t a demonstration of a lot
of intelligence this far, I started to wonder if it wasn’t just XORed, as it’d
look the same.

A friend on Twitter
noted that the repeated
block was at the same location as JPEG files have a string of repeated bytes.

Note that the 32 bytes are more than 2x 16-byte blocks in length: they extend
well before and after the 16-byte alignment. But the ciphertext doesn’t show
that at all: this rules out a plain repeating XOR, as we’d otherwise expect to
see something more like this:

… assuming the 32 bytes above are exactly the number you’d expect to find
anywhere, which isn’t the case; but you get the point: there’d be some, but
there are none. The key and data are totally mixed, which suggests a real
block cipher.

Since there aren’t really good ways to attack this directly (at least, not for
me, an utter novice), it seemed much faster just look for the cipher/key/etc.
in the source.

I was thinking I’d have to do some MitM of Google Play or root my Android, but
it turns out Googling ‘snapchat apk download’ is enough. Hah.

The first tool I found for getting the contents and decompiling the APK was
android-apktool; there are surely
better tools (this gives you smali output, not Java or Java-ish), but it was
easy enough to peruse, given I just wanted to know what the key was and what
primitives were being used.

The code was totally unobfuscated, so it wasn’t hard to find the
com.snapchat.android.api.SnapchatServer: the .smali file is a bit weird to
read, but sure enough there’s:

or something. I don’t actually do Java, so maybe that’s all backwards, but the
point seems pretty clear. It occurs to me I’m reading the encryption code, but
while there are two keys, only ENCRYPT_KEY_2 is ever used.

Looks like it. Note the padding scheme; seems weird to use PKCS#5 which has
apparently “only been defined for block ciphers that use 64 bit (8 byte) block
size”, when the size here is 128-bit. Let’s give it a go.

The conclusion is that it’s easy to intercept and decrypt the data Snapchat on
your phone receives; it’d be one, maybe two hours of work to turn the above
code into something I could just switch on and forget about, while it happily
archives every Snapchat I ever receive.

Truth be told, I can’t be bothered – it’s fun, and I don’t want to ruin the
unique feeling it has by virtue of being an ephemeral medium – but don’t think
it’s hard for someone who cared enough to.

I used this guide, but there are better ones out there which are probably more explanatory. ↩

Note that this client doesn’t speak HTTP; accordingly, it doesn’t know when to close the connection (GAE’s HTTP server sends the responses with Content-Length, so we should parse that and know when we’ve received all the data, but it’s easier just to restart the server over and over at this stage). ↩