Skip to content

Instantly share code, notes, and snippets.

@iptq
Created August 18, 2015 18:34
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save iptq/560b2418c09896315198 to your computer and use it in GitHub Desktop.
Save iptq/560b2418c09896315198 to your computer and use it in GitHub Desktop.

Data:

http://www.apk4fun.com/apk/1299/

Solution:

This is just a really difficult challenge. First, decompile snapchat.apk using a tool or a service like decompileandroid. Extact the src folder. Yay. Java.

The first step is to do a little bit of research on Snapchat decryption. Your research will probably lead you to this repo. However, it's outdated. Cry slowly. You should realize at this point that you need the Android ID to do anything. Use grep to search for android_id in src. This will reveal locations in the code that obtain the Android ID from the phone. You will eventually find in com.flurry.sdk.ea the following:

static String b()
{
    String s = android.provider.Settings.Secure.getString(com.flurry.sdk.do.a().b().getContentResolver(), "android_id");
    if (!a(s))
    {
        return null;
    } else
    {
        return (new StringBuilder()).append("AND").append(s).toString();
    }
}

What's even better is that we have a flurry file called .flurryagent.4d4e3e72 in com.snapchat.android/files. Convenient. Opening the file reveals the following:

[snip]
ANDb5ab7a15e15cd40c
[snip]

This is great. According to our reverse engineered code, the Android ID should be b5ab7a15e15cd40c. Now for even more fun.

We must find the encryption method to create a decryption method. We can assume that the Snapchat programmers are lazy and didn't put too much effort into changing the underlying encryption flow of images. Reading the repository readme provides some preliminary information. Navigating to com.snapchat.android.util.crypto.SlightlySecurePreferences provides the following section of pertinent code:

protected SecretKeySpec a(Context context)
{
    String s = android.provider.Settings.Secure.getString(context.getContentResolver(), "android_id");
    if (s == null)
    {
        s = Build.FINGERPRINT;
    }
    MessageDigest messagedigest = MessageDigest.getInstance("MD5");
    messagedigest.update(s.getBytes());
    messagedigest.update("7f6as9d7f6dasf7".getBytes());
    byte abyte0[] = messagedigest.digest();
    StringBuilder stringbuilder = new StringBuilder();
    int i = abyte0.length;
    for (int j = 0; j < i; j++)
    {
        String s1;
        for (s1 = Integer.toHexString(0xff & abyte0[j]); s1.length() < 2; s1 = (new StringBuilder()).append("0").append(s1).toString()) { }
        stringbuilder.append(s1);
    }
    return new SecretKeySpec(stringbuilder.toString().getBytes(), "AES");
}

Hey, looks like they didn't change it much. It's still the same encryption method, but now the "secret" (no longer secret to us) key is the MD5 hash of the Android ID concatenated with 7f6as9d7f6dasf7. Note that in previous versions, it was seems legit.... Now, on to finding the file. According to the repository, it should be bananas1 but the new version is different. In the same file, you will find the following lines of code:

    File afile1[] = file.listFiles(new PrefixFilenameFilter("fdg78df67g5s6f", s));
    for (int l = afile1.length; i < l; i++)
    {
        if (afile1[i].delete())
        {
            flag = true;
        }
    }

Seems like the file fdg78df67g5s6f exists in the com.snapchat.android/cache folder. I think we are done reverse engineering. Our assumption that programmers are lazy will allow us to simply follow the procedure in the repository to decrypt the Snapchat photos. We'll just write some code or do it manually (the following steps can all be done manually using http://aes.online-domain-tools.com/).

First, we will obtain the initial MD5 hash to decrypt fdg78df67g5s6f.

>>> import hashlib
>>> m = hashlib.md5()
>>> m.update('b5ab7a15e15cd40c'.encode())
>>> m = hashlib.md5()
>>> m.update('b5ab7a15e15cd40c'.encode())
>>> m.update('7f6as9d7f6dasf7'.encode())
>>> m.hexdigest()
'12035216eef41c01724a63241472ed3b'

Next, apply AES-ECB on the aforementioned file.

>>> from Crypto.Cipher import AES
>>> decryptor = AES.new('12035216eef41c01724a63241472ed3b', AES.MODE_ECB)
>>> bananas = decryptor.decrypt(open('fdg78df67g5s6f2', 'rb').read()).decode()
>>> bananas[:30]
'[[["myStorySnapKeysAndIvs",0],'

Looks good. Let's parse and store it into a variable.

>>> import json
>>> bananas[-20:]
'gyMzc3MDl9\\"}"]]\x04\x04\x04\x04'
>>> bananas = json.loads(bananas[:-4])
>>> keys = json.loads(bananas[5][1]) # Load "friendStorySnapKeysAndIvs" section.

I will now pretend that I do not know which file contains the flag. Let's choose sesrh_dlw217438734607806895390.mp4.nomedia. In order to decrypt, we need to find the SnapID in order to obtain the correct key and IV. For this, we will need to read tcspahn.db.

>>> import sqlite3
>>> conn = sqlite3.connect('tcspahn.db')
>>> c = conn.cursor()
>>> c.execute("SELECT FriendStorySnapTable.ClientId FROM FriendStorySnapTable JOIN StoryVideoFiles ON StoryVideoFiles.SnapId = FriendStorySnapTable.StorySnapId AND StoryVideoFiles.FilePath LIKE '%%%s'" % "sesrh_dlw217438734607806895390.mp4.nomedia")
<sqlite3.Cursor object at 0x0444A3E0>
>>> c.fetchone()
('PANADERO_AFK~1B3039ED-D481-44B9-A811-CAA0BC332511',)

Go back and pull out the correct key (0) and IV (1). We will also need to base64 decode them.

>>> import base64
>>> keys = keys['PANADERO_AFK~1B3039ED-D481-44B9-A811-CAA0BC332511']
>>> key = base64.b64decode(keys[0].encode())
>>> iv = base64.b64decode(keys[1].encode())

Finally, use AES-CBC to decode the file!

>>> decryptor = AES.new(key, AES.MODE_CBC, iv)
>>> decrypted = decryptor.decrypt(open('sesrh_dlw217438734607806895390.mp4.nomedia', 'rb').read())
>>> decrypted[:30]
b'\x00\x00\x00\x18ftypisom\x00\x00\x00\x00isom3gp4\x00\x00\x0b\xf4mo'
>>> open('decrypted_1.mp4', 'wb').write(decrypted)
600608

Of course, that is the wrong file. So this must be redone with the correct file.

@adklco
Copy link

adklco commented Feb 14, 2017

When I apply AES-ECB onto the file, I get an error that "UnicodeDecodeError: 'utf-8' codec can't decode byte 0xa4 in position 24: invalid start byte". If I pass 'utf-8' and 'ignore' or 'replace' into decode, it runs fine but the resulting banana[:30] is "'\x0c\x00\x00\x00\x00\x00\x06\x00\x08\x00\x04\x00\x06\x00\x00\x00\x04\x00\x00\x00A\x00\x00\x00|\x00\x00\x10{\x00'"

I'm wondering if you could provide some insight, thanks.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment