RC3 CTF 2016 - Misc500 Writeup (Finding Phil)

This was an entertaining and fun challenge, and reminded me a bit of the Cicada 3301 challenges, so it immediately caught my interest. Our task seems easy enough, to find Phil, except of course we have no idea who or what Phil is.

Step 1.

We start with a PNG image of a hangman game. Some analysis of the image reveals that there is extra data after the IEND marker of the PNG and that it is a zip file.

Since zip reads a file from the end, all we need to do is rename the .png to .zip and we can unzip the data.

Step 2.

Once unzipped we have a folder with fragments of a larger image and a text file called hint.txt that reads:

Hints:
3 vowels
14 alphanumeric characters

After putting the images together and we get the following:

Once we have completed that we can see lots of letters but we can just about make out termbin.com/vf6t which contains 3 vowels and 14 alphanumeric chars.

Step 3.

We go to the URL and we get the following text:

You have proved yourself to be at least some what formidable. Congratulations. Now that you have proven your technical skills, you need to prove your   loyalty.
There is a new company that is creating a rather large buzz, EnergyCorp. They are run by the former creator and CEO of Xanadu, LordReverend Kane. 
We hear that there is a secret to their power, hidden behind their protected website. We need you to gain access to their secrets, and return them to us.
The website can be found here:
http://ec2-52-71-70-98.compute-1.amazonaws.com:6100
Don't get caught.

We go to the URL and see the EnergyCorp website.

Looking around there isn't much interesting except the login page contains an upload form that accepts png, .jpg and jpeg file extensions and tells us to upload a company ID.

At first it looked like we would need to exploit this upload form, but we remembered seeing a FaceBook link for this fake company when we were looking around, so we go to that link and we see a valid FaceBook page. Someone has gone through a lot of effort to make this page, so perhaps we can find something there.

There are two employees on the company page:

Ben Franklin - Chief Imagination Engineer
Jennifer Jones - Synergistic Energy Researcher

Browsing Ben Franklin's page doesn't reveal much, just his company title. Browsing Jennifer Jones' page, however, is more fruitful as she seems to have uploaded cat photos.

Going a bit deeper, we decided to see what other photos she has uploaded, perhaps she was dumb enough to have uploaded her company ID.

Bingo. So we decoded that QR code to see what it contains:

BEGIN:VCARD
VERSION:3.0
FN:Jennifer Jones
N:Jennifer;Jones
ORG:EnergyCorp
TITLE:Synergistic Energy Researcher
TEL:407-539-5287
URL:
EMAIL:jjones@energycorp.com
NOTE:
ADR:
END:VCARD

Attempting to use this ID to login to EnergyCorp did not work as we see a message saying this User already logged in, so we try create a VCARD for Ben, which allowed us to login:

BEGIN:VCARD
VERSION:3.0
FN:Ben Franklin
N:Ben;Franklin
ORG:EnergyCorp
TITLE:Chief Imagination Engineer
TEL:407-539-5287
URL:
EMAIL:bfranklin@energycorp.com
NOTE:
ADR:
END:VCARD

Step 4.

Once we login as Ben we go to /vault which says "This is where all of our most highly guarded secrets are kept." but all we see are two uninteresting images of a puppy and tortoise.

After seeing nothing in the HTML source, we hit the jackpot by noticing the HTTP headers contained base64 encoded data.

Now it is important to note that this part of the challenge was later changed, so I am not sure if any other teams went through the pain we did but we were only presented with 5 HTTP headers containing base64 encoded data. We tried to decode it but it only seemed to contain another base64 encoded string. Later all the relevant base64 strings would be displayed at once (as it is in the screenshot).

Reloading the page revealed different base64 encoded data, so we collected some of it and saw that one of the lines contained the string "-----BEGIN RSA PRIVATE KEY-----", so each of these lines must be a different line of an RSA private key. We were a bit stupid at this part as we brute forced the order of the lines until we had a valid RSA key, but returning to this step later we would see that all the base64 encoded lines would be present (which is probably what other teams saw when they got to this step) and one of those lines contained:

accept, content-security-policy, server, content-length, content-type, date, pragma, expires, cache-control, warning, downlink, save-data, width, etag, keep-alive

Which must obviously be the order the lines would be used, leaving one line remaining, which was the data to decrypt:

YWNjZXB0LCBjb250ZW50LXNlY3VyaXR5LXBvbGljeSwgc2VydmVyLCBjb250ZW50LWxlbmd0aCwgY29udGVudC10eXBlLCBkYXRlLCBwcmFnbWEsIGV4cGlyZXMsIGNhY2hlLWNvbnRyb2wsIHdhcm5pbmcsIGRvd25saW5rLCBzYXZlLWRhdGEsIHdpZHRoLCBldGFnLCBrZWVwLWFsaXZl

The completed RSA key:

-----BEGIN RSA PRIVATE KEY-----
MIICXAIBAAKBgQCXtUQtyYGGvWbvPlRTQQa0FcHZF7ACS9Xjaklp7xukl2G//In/
6EDpV1ji5bNhiqtJgXQI4PicgxnAWiN1AUXa8URsD/6UaCtamK2XOi3Ul7pZ1/ha
1VMJ2GgN0ptKJIGv6M0c2OhdvARjAiEimqTzbGH4iLJOtzHwsjY/IIzd0QIDAQAB
AoGANUYcSQ/oAk7kpML4jbvaBMdXKUseLdA/rfqXCTJEPMpEM00VkN4YXVilCBit
o2U0vT1OaAfIhf2rv1Zn+SCXTLw8/SP9A2+Qu4fqMsXeCiLuxEQw4tqK+nv0GdI0
h2kjiipzIXD63rE5Hbet0+eNFGsn0Hu1mSR2CxGr1BCjVVkCQQDveU0lltujiPC3
xIo8BczosH+yV/H4MbdPMQaIpu0dqP7Uq2/8PtwCpWTRPKriOgeRQxeMSN98JuVO
MZpoPBHfAkEAoi1wSRaMM0S9ehBgWlFo9eLcif5kmUFkQbpP/WAW1xIkXUngm6bo
TjUztY7uarJhTAXxkMdDpcAZNZ9asqvmTwJBAJfbjS90Dc4TbcqrCJntd7ZrDl8y
489m/35pcWJAsIapfwevu3DzD6Nh7J++4AJbmCbmq80a/RWGuMywKeFFjnMCQCNx
kM+4aM2voUVzHMvAbRMIELDr8yp3WyTuRhsXDAbXBTGKOtdpw+2LvRBZ+4tADvmh
dujwU71+3UOV3ymbXgsCQG6zcy5lkeXF+rxZ5aRwwxpiAJp/TAlVj8v6HZa8Iz/M
pHm3I6FAD9xcZhS2X8ETGtxurIqYUAZwq8uVEa8Oh7A=
-----END RSA PRIVATE KEY-----

Looking back we probably should have taken a closer look before attempting to bruteforce the order of the lines as it wasn't immediately obvious to us that one of these lines would be the data to decrypt.

Once we had gotten this far, it was obvious the key had erroneous padding for base64 so we add an = character to the key and save as decode.key and the decoded data as cipher.dat and then we decode the cipher:

    $ openssl rsautl -decrypt -inkey decode.key < cipher.dat
    /supersecretvaultthiswaywowimtiredthisnameshouldprobablybealotbetterbutohwellitssolateandthectfisinlikeliteralhours

Step 5.

Once we go to that long URL we get the following text:

Welcome to the real vault ;)
I see you have found the real vault. You see, we have the decoy vault to fool all the sheep. Make them happy, make them think that everything we do here at EnergyCorp is all puppies and turtles and strawberries. You see, the real power of our work lies with Phil. He was the genius that helped make Xanadu as amazing as it was, and he is what will make EnergyCorp just as amazing. In this vault lies access to all his files, so we can keep tabs on him and see what he's working on. Phil will do great things for us here at EnergyCorp.
magic here.

Clicking the magic link we get to a file called phils_magic.tar containing 2 files, notes.txt and chatsalot.apk. Inside notes.txt we are greeted with the following text:

They've captured me and made me use my talents for their own gain. If you are to find where I am you must use this android application and talk to the admin. He will know how to find me. As a precaution to Kane finding where I have gone, I have taken steps to reduce the chance of Kane being able to use talk to the admin through this app. My hopes are if you have gotten this far in your search for me, you will be able to break the apps security and reach out to the admin.

Once you are able to initate contact with the admin, use the phrase 'security through obscurity' in a sentence. This will let him know I sent you. The admin does not use many words, but may be able to help you find me.

My credentials to log into the application are: imnotphil:canttouchthis

We get to work reverse engineering the chatsalot.apk file and start wading through the Java source. It seems to be a chat application under the namespace hax0rz.a1337.chatsalot that communicates with JSON requests through a remote server. From the source we can see that we must have admin group privileges in order to communicate to the admin user and provide the "security through obscurity" string. In order to get the admin group privileges we must generate a valid administrator group ID. Here is the function that does that inside of BobChatActivity:

public void generateGroupId() {  
    int i;
    String bob = "hi";
    String private_key = "MIICWwIBAAKBgHRgopx/WH3uj1Nw3Y/XgZQASILjAl2HCPkUYtILz79GqkZuOySX\nohebPDCjltyJ82FOoornwew52cQCLCV3NctlZVZwT99VMiem3vjl1bJRDFLnwv2l\nQ5Ujvu/wSuOisRxKnvQxZaaitAHpYQvQqZOJiyWdMqsso712Q9wkkJAxAgMBAAEC\ngYBxIaPSWKVQvoEMD1MDSu9HTcMvobih7OxXHm82W48YFXzfvLa3ysQjCKBJdC3q\ntBwpQwUV3VgR6Ob9+VKrFSjwAgi2d1bbFQlRuhlxDCClO5hkq6WXQV9EL8xSE6uz\ncHS7Uf5uBOFCs1dl7BdgMw5l8+sFahTLPfocpoxBhXkUAQJBALN83zT9MyFolMKm\nHapvg063ODaoexIIsl1Xy9e+5BM/I5bMRPLDgK7aG4kPpIojnBb8sFCUVwk7lSnd\nC4GrgHECQQCl/KroDx6s2zT4hIgo9/T+CtyTxOWtiRlnNDna19vYZnXFr2mAl5kT\n3UUqT6ylKcmU6KWeW3elCOwAa6cXrevBAkBAlw874mIkA56E7YJ/cuGt0gFIqhif\nxMFrFc0lNmydAHuuKJQnSHNmeNav3BE6JNZm70gDt14a1HY5OnKJl04BAkBS2llQ\n9mMgc1bwie8RTBvtRuytkgX3ZkzY2Bfc5gyl6xb0c0edWY6efL+OjDCoTMCDZNFu\nx0dkiJyM5S+FwVnBAkEAsOuA/x/tCZZvRsjq2csNb51vdwnV1pkUWZDwK5I0C+eV\nmkMZ8mbuxAwEUrNjTxzpVrJs1ywQHJ11eaqmEovbaQ==";
    String user_group = "Users are the coolest people in the world";
    StringBuilder sb = new StringBuilder();
    int loler = 0;
    for (int toPrepend = private_key.length() - user_group.length(); toPrepend > 0; toPrepend--) {
        loler++;
        if (loler % 2 == 0) {
            sb.append(user_group);
            sb.append(user_group);
        } else {
            sb.append('0');
            sb.append(user_group);
        }
    }
    sb.append(user_group);
    String result = sb.toString();
    String b_private_key = Base64.encodeToString(private_key.getBytes(), 2);
    String b_user_group = Base64.encodeToString(result.getBytes(), 2);
    Log.d("[+] b_private_key ", b_private_key);
    Log.d("[+] b_user_group ", b_user_group);
    int total1 = 0;
    int total2 = 0;
    char[] ascii1 = b_private_key.toCharArray();
    char[] ascii2 = b_user_group.toCharArray();
    for (i = 0; i < b_private_key.length(); i++) {
        char c = b_private_key.charAt(i);
        total1 += c * 5;
        total2 += b_user_group.charAt(i);
    }
    float divi = (float) (((total1 * 5) / total2) * 50);
    String converted = BuildConfig.FLAVOR;
    for (i = 0; i < b_private_key.length(); i++) {
        int k = b_private_key.charAt(i) ^ ((int) divi);
        converted = converted + String.valueOf(k);
    }
    String str = converted.substring(0, 13) + converted.substring(24, 26);
    str = "Group ID: " + str;
    Log.d("MYINT", "finished at: " + str);
    ((TextView) findViewById(C0217R.id.tvGroupId)).setText(str);
}

This was another of those time waster moments because we were trying things like "Admin" in the user_group variable, but as it turns out, if we had used a different decompiler we would have seen the following Java comment:

/**
    ====== User Groups ====== 
    There are three types of user groups technically, but only two are implemented. 
    1. "Users are the coolest people in the world" 
    2. "administrators" 
    "administrators" get special access within the application. Users are meant to feel 
    special and loved so they have a 'fun' and hip group name, while not having any actual power. 
*/

After we had generated a valid admin groupid (106610671042110, with user_groups set to "administrators"), we posted a message to the server ("security through obscurity") and we get back the following message:

Ah, you must be searching for phil. While I can't tell you where he is, I can give you some data from our last conversation that may help you find him. https://drive.google.com/file/d/0Bw7N3lAmY5PCVlpvaW9tQUs1MVU/view

Step 6.

Following the download link we get another zip file. Inside this zip file are some holiday themed JPG images, except one of them is actually a PNG masquerading as a JPG. We focused on that PNG image but it turned out to be a waste of time (we even ended up decompressing the PNG stream thinking we were seeing patterns in the noise). Looking back at the zip file it becomes clear that only one of the images is stored uncompressed, it is also the only image that is not a holiday image.

And at the bottom there is extra data:

 ZnVuNGR2M243dXIzNXcxN2hwaDFsCg==

After base64 decoding the data it reveals a string:

fun4dv3n7ur35w17hph1l

That isn't the flag, so what can it be used for? After wasting lots of time looking at minor details in all of the images, we end up looking back at the zip file. Although binwalk did not reveal anything, we start noticing high density bytes at the end of the zip that look unusual.

8C 0D 04 07 03 02 0C 52 32 7D 6F F7 CB F5 60 D2 4F 01 20 7F 89 93 22 AE CF 72 2E 6F 03 B2 65 63 C7 33 32 3D 59 7C 22 41 89 98 E7 DE 03 DA 7F B6 3B C1 CD 7C 7D 41 A7 B5 C4 30 05 35 CA 7C 67 A0 37 4B B2 C2 A9 E2 20 97 18 61 7B F5 54 63 BC 55 67 FA 1C 88 17 E2 4F 7B AE 95 E6 6C 31 91 0E 55  

After comparing with other zips and confirming this is unusual we grab the data and run file on it:

    $ file zip.dat 
    zip.dat: GPG symmetrically encrypted data (AES cipher)

That must be what the string is used for. Let's decrypt that with the probable passphrase, fun4dv3n7ur35w17hph1l:

    $ gpg --decrypt zip.dat
    gpg: AES encrypted data
    gpg: encrypted with 1 passphrase
    RC3-2016-PhilTrip

Boom! First blood with Misc500. Was a fun challenge, thanks to the creators and the rest of the blackbunny team. This was a a fun team effort.

Lessons Learned:

  • Don't question ak42 when I am doing something obviously stupid.
  • blackndoor is magic.