The team/club I organize at Boston University just got done competing in the CSAW Qual CTF 2016. It was a bunch of fun, and we came in 119th out of 1274 active teams, top 10%!

scoreboard

Here's some writeups of the challenges I worked on. Other people's writeups can be found at https://0xbu.com/writeups.

Writeups

Neo

(Crypto 200)

Summary:
The challenge was a basic padding oracle attack. A padding oracle attack is present when: 1) A server attempts decryption with padding on controllable data, and 2) The server returns different a different result (i.e. error message) if the cipher text did not unpad correctly (i.e. an oracle). By attempting decryption of specially crafted data, the oracle reveals if unpadding was successful. From that oracle the plaintext can be recovered without the original key.

The challenge presents to you a website that states "Decrypt the matrix", and an input box that generates a new seemingly random base64 string on refresh. Playing around with submitting the box prints a different message depending on the input, either:

  1. Nothing
  2. AES Exception Thrown
  3. Invalid Base64

The random generated base64 string always returns "Nothing", meaning it probably worked successfully.

So there's a few hints here to tell you what to do:

  • "Decrypt the matrix" - Probably have to decrypt something.
  • The Matrix has a very important character named "The Oracle".
  • We get thrown 2 different messages depending on if our input succeeded, or didn't - ala we have an oracle.

So putting those pieces together, this is clearly a Padding Oracle exploit exercise.

This was my first padding oracle exploit. The best website while trying to learn this attack for the challenge was: http://robertheaton.com/2013/07/29/padding-oracle-attack/

From that, I then found some things already built for crafting/testing differently crafted data. I choose, https://github.com/mwielgoszewski/python-paddingoracle (I also have it easily installable in my sec-tools), as the best one for me. It's a nice Python API, where basically all you have to do is define an oracle function, and make it either Throw a 'BadPaddingException' based on your own logic for what throws that, or make it return successfully. The logic to throw a BadPaddingException can be actual crypto, or it can be a web request, it just has to be logic that causes an oracle to speak.

from paddingoracle import BadPaddingException, PaddingOracle  
from base64 import b64encode, b64decode  
from urllib import quote, unquote  
import requests  
import socket  
import time

class PadBuster(PaddingOracle):  
    def __init__(self, **kwargs):
        super(PadBuster, self).__init__(**kwargs)

    def oracle(self, data, **kwargs):
        print("[*] Trying: {}".format(b64encode(data)))

        # Do Crypto that throws something different if padding error
        r = requests.post('http://crypto.chal.csaw.io:8001/', data={'matrix-id':b64encode(data)})

        if 'AES' in r.text:
            print ("[*] Padding error!")
            raise BadPaddingException
        else:
            print ("[*] No padding error")

if __name__ == '__main__':  
    import logging
    import sys

    logging.basicConfig(level=logging.DEBUG)
    # This is a random string the server printed for us, 
    # assuming have to decrypt this, and see what we get
    encrypted_value = 'XImKWrDW5dFvUVDuwbwLy+nHJmDClEXWLcJRmf44atNU2tKZcVFoyT81bmUkL6WPWg7Dn8HMeeWwhiC+CI8QhDtYqGCBidtHZ+alNqnyTn4='
    padbuster = PadBuster()

    value = padbuster.decrypt(b64decode(encrypted_value), block_size=16, iv=bytearray(16))

    print('Decrypted: %s => %r' % (encrypted_value, value))

Note:
It's kind of deceptive in the challenge how a new random base64 string is printed to you on every website refresh. Because of that it can be difficult to figure out what you're supposed to decrypt. I ended up actually editing python-paddingoracle to also print the 'IV' (see the padding oracle article above, but basically IV = intermediate value ^ decrypted 1st block). I saw that the IV was \00*16 (typical default for CBC), so that gave me hope that I was on the right path, and that this really was some usefully encrypted data, and that my padding oracle attack was in fact working.

Running the script, and waiting about 30 minutes gets the flag:

Decrypted: XImKWrDW5dFvUVDuwbwLy+nHJmDClEXWLcJRmf44atNU2tKZcVFoyT81bmUkL6WPWg7Dn8HMeeWwhiC+CI8QhDtYqGCBidtHZ+alNqnyTn4= => bytearray(b'\xeeX6\x0c\x99\xb1\xed\x0c4!\xcf\xf0w\xf8u_flag{what_if_i_told_you_you_solved_the_challenge}\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f')  

The Rock

(Reversing 100)

Summary:
Fairly straight forward ELF64 C++ reverse engineering. Using a mix of static, and dynamic tools you can get a feel of what the program is doing. It was mostly figuring out which functions do something useful.

In this challenge we're give an ELF64 binary. Opening it up in IDA reveals the main function, and that it's written in C++, which means there's going to be a lot more garbage data (virtualism, object-oriented, std library, etc. cruft) to sift through.

There's 3 ways to go down from this:

  1. Purely static analysis - Figure out what each function does, if it's useful or not, typically starting by backtracking from the function that looks most likely to be a validate function. There should be an algorithm that some how compares some form of the input key, to some form of the known good key. The algorithm maybe tricky, and the keys may be shuffled in all sorts of ways, but it boils down to essentially a comparison.

  2. Purely dynamic analysis - Input a key, and follow it down the call stack, and find what it's compared to. Feed that known good key as your input key. This will really only work if the input key is compared without any encoding to it.

  3. Some combination of the above 2.

I ended up doing (1), but (2) was the smarter choice as the input key was being compared directly to an encoded known good key, so simply putting a breakpoint, recording that known good key, and passing it as my input key would do the trick.
But, for whatever reason I wrote the code for (1) instead.

#!/usr/bin/env python
"""Original binary (rock.elf) is verifying a key via:

    input_key = <input>
    encoded_key = 'FLAG23456912365453475897834567'
    clean_key = ''
    for c in k:
        tmp = (c+20) ^ 0x50
        clean_key += (c1+9) ^ 0x10

    return input_key == clean_key

So to reverse it you just have to do the opposite to get the clean_key  
and provide that as the input key  
"""
known_good = "FLAG23456912365453475897834567"  
ans = ""

for c in known_good:  
    tmp = (ord(c)-9) ^ 0x10
    ans += chr( (tmp - 20) ^ 0x50) 

# Let's make sure we did this right
should_be_known_good = ""  
for c in ans:  
    tmp = (ord(c) ^ 0x50) + 20
    should_be_known_good += chr((tmp ^ 0x10) + 9)

if known_good == should_be_known_good:  
    print("Round trip successful, printing the flag: ")

print(ans)  
Round trip successful, printing the flag:  
IoDJuvwxy\tuvyxwxvwzx{\z{vwxyz  

Key

(Reversing 125)

Summary: In this challenge you're given a PE32 executable. The executable looks inside of a key file, and compares the contents to an encoded key. The contents of the file are compared as is to the encoded key.

Executing the program gives us:

"W?h?a?t h?a?p?p?e?n?" 

Opening it up in IDA, and searching for that string, and then backtracing for the root cause reveals to us another interesting string:

Note:
Executing the program in wine on Linux crashes... apparently the PE32 uses something that wine hasn't implemented, so you really do need to run this on a Windows box.

So it's pretty obvious that file has to exist. The key is probably put into it and compared directly (it is), but you can verify through running the program in a debugger. That's what I ended up doing, but you can actually skip that and solve this quicker by not.

After getting past the error print, spending some more time debugging, and tracking memory flow reviews to us that the algorithm of this program is basically:

1. Encode the string 'themidathemidathem' by XORing it with a constant key  
2. Encode the result from (1) via some more gibberish.  
3. Compare (3) to the input file key.  

We can follow the flow of the program, and find the result of (2):

idg_cni~bjbfi|gsxb  

Regexpire

(Misc 100)

Summary:
In this challenge you connect to a server, and it prompts you to provide a correct string that will satisfy a regex expression. You must answer with a satisfiable string, and repeat this process until the server spits out the flag.

I googled for 'reverse regex', and ended up finding the python library rstr. It takes in a regex expression, and returns a string that satisfies it, exactly what we want.

import rstr  
import telnetlib  
import socket

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  
s.connect(("misc.chal.csaw.io", 8001))

s.recv(1024) # junk read

while True:  
    question = s.recv(1024)

    print("Asking for: " + question)

    if 'Irregular' in question: # printed when we messed up
        break
    if 'flag' in question: # hoping this is printed when we're done
        break

    ans = rstr.xeger(question)
    print("Answering: " + ans)
    # The server can't handle '\n' in the middle of an answer
    # as it thinks that's the end of the answer, so we have to ask
    # again for an answer that doesn't have '\n' in it.
    while '\n' in ans[:-1]:
        print('we fucked')
        ans = rstr.xeger(question)
        print("Answering: " + ans)

    s.send(ans)
nc misc.chal.csaw.io 8001  
Can you match these regexes?  
Asking for: [QSIb][QVvVxYxS]+(bernie|clementine)0{8}[aWeyq]ag*[a-z]6J{9}[a-z]{6}[T\D\DR0vAp]*(tiger|clinton){9}(table|table)[8gpy]

Answering: ISYVYSvvvvxSxSQVxVxYvVVxQQxvvYVQSxQxYvxxvxVYxxSVxQvxvxQQVvxxQVvxxQVVYxYSSVVVVvxQvQYvxvVxVVVvQclementine00000000eagggggggggggggggggggggggy6JJJJJJJJJvqcnnyYEp]!l)Z'?wUXrtz!TpGekY`wh_|W=Sdj{iE+~=+IZUSR*XRUpxjswI=dIu:PFRd^VrLy$(j!Z&$Ts+>)[email protected]+)X>BStigerclintontigertigertigerclintonclintonclintontigertable8

[Repeated a bunch more times...]

flag{^regularly_express_yourself$}