CrikeyCon 2017 - Fast Math Writeup

Mar 8, 2017
4 minutes read 654 words
Category: Coding
Points: 300
Solves: 14
Description:  crikeyconctf.dook.biz:23776

Enumeration

Before doing anything else on this host I attempted to connect to it, receiving the following:

Math Preview

The time between being presented with the challenge and receiving a timeout was a mere two seconds. Although handy with a calculator this wouldn’t be possible without a script/bot. I also noted that the response and timing to answer didn’t change on a second connection, but the base operator did. I then felt comfortable writing a script to connect to the host and return an answer to basic math questions (+-/*).

First Answer Script Attempt

I put together a bot which would connect to the host and parse the challenge question using regex so it could answer it using an expression. This resulted in the following:

#!/usr/bin/python3
 
import socket
import re
import operator
 
 
MAXBUF = 4096
SENTINEL = 'flag'
CTF_BOT = ('crikeyconctf.dook.biz', 23776)
 
if __name__ == '__main__':
    client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    client.connect(CTF_BOT)
 
    while True:
        data = b''
 
        # receive and store data
        while True:
            chunk = client.recv(MAXBUF)
            data += chunk
            if len(chunk) < MAXBUF:
                break
       
        # store decoded data for future usage
        decoded = data.decode('utf-8')
       
        #temporary
        print(decoded)
        #
 
        # our flag likely contains flag{}, once it's revealed print received data and exit
        if SENTINEL in decoded:
            print(decoded)
            break
 
        match = re.search('[^\:\s]\d+.{3}\d+', decoded)
 
        if not match:
            raise ValueError("Invalid expression string")
       
        expression = match.group(0)
 
        # properly handle division
        if '/' in expression:
            expression = expression.replace('/', '//')
 
        result = eval(expression)
 
        # print results to screen to see script progress
        print(expression + ' = ' + str(result))
 
        # encode and transfer
        data = str(result).encode('utf-8') + b'\n'
        print('Sending: ' + str(result))
        client.send(data)

Regular Expression (attempt)

The most important line here was the regex which consisted of the following (see if you can spot the mistake!):

match = re.search('[^\:\s]\d+.{3}\d+', decoded)

This would skip everything proceeding the colon and whitespace and then group both sets of numbers, and the whitespace/operator between them for evaluation.

The response to this was the following:

FastMath Regex First Attempt

I spent far more time on this part of the challenge than I care to admit. I didn’t entirely read my logs and spent my time stuck on the two lines where I have Sending 1766845 and the response 459060 is not correct and put a lot of focus into trying to identify why a different response was being sent to what was calculated (not the truth, but it’s what I was thinking).

Adding further regular expressions to catch x=x

Eventually I identified that the response to an answer was:

AnswerProvided is correct
AnswerProvided = AnswerProvided

And then the next question would be presented. This was causing issues with my regex as I was passing values from the previous answer into my group, ultimately preventing the correct evaluation from sending.

I resolved this by continuing back to the beginning of my loop if there wasn’t a mathematical operator in my decoded string, putting this after the sentinel check as I didn’t want to miss my flag (which likely wouldn’t contain an operator). This looked like the following:

if not re.search('[-+/*]', decoded):
    continue

Updating regular expression to handle more than one digit

This got me to question number 10 – at which point I realized that my regex from earlier:

match = re.search('[^\:\s]\d+.{3}\d+', decoded)

Would only work if I were to be presented with a single digit question. I updated this to the following:

match = re.search('[^\:\s]\d+\d+.{3}\d+', decoded)

Essentially the same as above, but it would now capture two digits over one as I didn’t expect the challenges to go past 99 (although could use \d* if they were to).

Flag Reveal

After making the changes above and letting the bot run for fifty questions I was then rewarded with the flag (redacted):

FastMath Flag Reveal

Success!

I hope this helped you to better understand how to approach this kind of programming based ctf item. If you’re Brisbane based, or find yourself here be sure to check out SecTalks.