Rating:

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 :

```python
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}`.

Original writeup (http://blog.iodbh.net/picoctf2018-misc-script-me.html).