Contact CTF writeups Notes

[PicoCTF 2018] - misc - Script Me

This is one of my writeups for PicoCTF 2018

Problem

We started a little store, can you buy the flag? Connect with 2018shell3.picoctf.com 60893.

Hint :

Can you understand the language and answer the questions to retrieve the flag? Connect to the service with nc 2018shell3.picoctf.com 8672

Solution

When we connect to the server, we receive the following :

Rules:
() + () = ()()                                      => [combine]
((())) + () = ((())())                              => [absorb-right]
() + ((())) = (()(()))                              => [absorb-left]
(())(()) + () = (())(()())                          => [combined-absorb-right]
() + (())(()) = (()())(())                          => [combined-absorb-left]
(())(()) + ((())) = ((())(())(()))                  => [absorb-combined-right]
((())) + (())(()) = ((())(())(()))                  => [absorb-combined-left]
() + (()) + ((())) = (()()) + ((())) = ((()())(())) => [left-associative]

Example:
(()) + () = () + (()) = (()())

Let's start with a warmup.
(()(())) + (()()()) = ???

We need to understand and implement those rules in order to answer the server's prompts and get the flag. After some trial and error, I ended up with the following script :

import os
import socket


class Pars:

    def __init__(self, value):
        self.value = value

    def __repr__(self):
        return self.value

    def __eq__(self, other):
        if not isinstance(other, Pars):
            raise ValueError('can only compare with Pars instance')
        return self.value == other.value

    def __len__(self):
        # returns the "depth" of nested parentheses:
        depths = []
        cur_depth = 0
        for char_ in self.value:
            if char_ == '(':
                cur_depth += 1
            else:
                depths.append(cur_depth)
                cur_depth -= 1
        return max(depths)

    def __add__(self, other):
        if not isinstance(other, Pars):
            raise ValueError('can only add with Pars instance')
        if self == other or len(self) == len(other):
            val = self.value + other.value
        elif (len(self) < len(other) and not other.is_combined()) or self.is_combined():  # absorb left
            val = f'({self.value}{other.value[1:]}'
        else:  # absorb right
            val = f'{self.value[:-1]}{other.value})'
        return Pars(val)

    def is_combined(self):
        half1 = self.value[:len(self.value)//2]
        half2 = self.value[len(self.value)//2:]
        return half1 == half2


def solve(query):
    operands = [Pars(o.strip()) for o in query.split('+')]
    while len(operands) > 1:
        left = operands.pop(0)
        right = operands.pop(0)
        operands.insert(0, left + right)
    return operands[0]


def netsolve(timeout=5.0):
    import socket
    hostname = '2018shell3.picoctf.com'
    port = 61344
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.connect((hostname, port))
    s.settimeout(timeout)

    databuf = b''
    while 1:
        data = s.recv(1024)
        databuf += data
        if b'>' in databuf:
            break
    datastring = databuf.decode('ascii')
    send_solution(s, datastring)

    s.shutdown(socket.SHUT_WR)
    s.close()


def send_solution(sock, datastring):
    for line in datastring.split('\n'):
        if '= ???' in line:
            query = line.split('=')[0]
            solution = solve(query)
            sock.sendall((str(solution) + '\n').encode('ascii'))
            databuf = b''
            while 1:
                data = sock.recv(1024)
                databuf += data
                if b'>' in databuf:
                    break
                elif b'picoCTF{' in databuf:
                    exit(databuf.decode('ascii'))
            ds = databuf.decode('ascii')
            send_solution(sock, ds)


if __name__ == '__main__':
    netsolve()

This is using a class and python "magic methods" to overload the + and = operators as well the len() function. If we run the script, we get our flag : picoCTF{5cr1pt1nG_l1k3_4_pRo_cde4078d}.