Rating:

# Original description:

> Prove that you are a robot, build all the curves and send me an answer!
> P.s. base64.b85encode(bz2.compress(txt.encode())).decode("UTF-8")
> nc tasks.aeroctf.com 40002

# Problem:

You need to get the formula from the server, plot it and then solve the capcha in the plot. Then you need to solve this formula.

![](https://www.sigflag.at/assets/posts/very_funny_curves/capcha.png)

Once plotted, segmented and isolated, the letters new signs (in addition to funny pictures) look like this:

![](https://www.sigflag.at/assets/posts/very_funny_curves/A.png)
![](https://www.sigflag.at/assets/posts/very_funny_curves/B.png)
![](https://www.sigflag.at/assets/posts/very_funny_curves/C.png)
![](https://www.sigflag.at/assets/posts/very_funny_curves/D.png)

# Solution

Very similar to funny_curves, but now you have to use the classes of A-F for `+-/*()`
1. Get the formulas again, and plot the curves
2. The curves show an image that contains a formula
3. Plot the formula, use the classifier from funny_curves and sort images of `+-/*()` into the A-F folders
4. Replace them when creating the string, but use them for folder-name-saving
5. `eval` the formula, but with `//` instead of `/` (division without remainder / int-divison)
6. Send the result to the server, and repeat this ~100 times
7. Get the flag

# Code Runner

```python
import base64
import bz2
import numpy as np
import matplotlib.pyplot as plt
from io import BytesIO
from PIL import Image, ImageFilter, ImageOps
import tesserwrap
import os
from skimage import measure
from collections import Counter
import tensorflow.keras as keras

model = keras.models.load_model("model.keras")
%env TERMINFO=/usr/share/terminfo
%env PWNLIB_NOTERM=true

from pwn import *

# tr = tesserwrap.Tesseract()
# tr.set_variable("tessedit_char_whitelist", "0123456789ABCDEF")

def classify(letter):
global model
letter = np.array(letter.getdata(), dtype=np.float32).reshape((1,28,28,1))
return "0123456789ABCDEF"[np.argmax(model(letter))]

def detect(img):
img = ImageOps.invert(img.convert("L", colors=1))

for i in range(img.width):
for j in range(img.height):
col = 255 if img.getpixel((i,j)) > 128 else 0
img.putpixel((i,j), col)


# plt.imshow(img, cmap="gray"); plt.show() # debug: show image
all_labels = measure.label(np.array(img), background = 128) # segment image
# plt.imshow(all_labels); plt.show() # debug: show image

text = dict()
# create a list of only the letters being 1, ignore the background with >10000 pixels
letters = [all_labels == color for color, cnt in Counter(all_labels.reshape(-1)).items() if cnt < 10000]
positions = [list(np.where(letter)) for letter in letters]

for letter, position in zip(letters, positions):
pad = 2 # padding
minx = min(position[0])
maxx = max(position[0])
miny = min(position[1])
maxy = max(position[1])

# skip very small items as they are probably an error
size = (maxx-minx)*(maxy-miny)
if size < 10:
continue
q = False
for position2 in positions:
pminx = min(position2[0])
pmaxx = max(position2[0])
pminy = min(position2[1])
pmaxy = max(position2[1])
if minx in range(pminx, pmaxx) and maxx in range(pminx, pmaxx) and \
miny in range(pminy, pmaxy) and maxy in range(pminy, pmaxy) and not \
(pminx == minx and pmaxx == maxx and pminy == miny and pmaxy == maxy):
q = True
if q: continue
sub_image = np.array(img)[minx-1:maxx+2, miny-1:maxy+2].astype(np.uint8)
pil_image = Image.fromarray(sub_image > 0).resize((28,28))
# plt.imshow(sub_image, cmap="gray"); plt.show(); # for debugging initial digits

# text[miny] = tr.ocr_image(pil_image) # didn't fully work for me
char = classify(pil_image)
text[miny] = char.replace("A", "+").replace("B", "-").replace("C", "*").replace("D", "/").replace("E","(").replace("F", ")")
pil_image.save(f"letters/{char}/{random.random()}.png") # collect data to improve/retrain the classifier

detected = "".join([text[pos] for pos in sorted(text)])
#plt.imshow(img, cmap="gray"); plt.show(); print(detected)
return detected

line = ""
while True:
r = remote("tasks.aeroctf.com", 40002)
r.send("Y\n")
r.recvline();
while True:
line = r.recvline().decode("UTF-8").replace("Result: ", "").replace("Ready? <Y/n>", "").replace("\n", "")
if line == "Incorrect!":
break
elif "Flag" in line:
print(line)

fig, axs = plt.subplots(1,1)
axs.axis('equal')
curves = bz2.decompress(base64.b85decode(line)).decode("UTF-8")
for line in curves.split("\nf(t) = ")[1:]:
code = line.replace("^", "**").replace(";", ",").replace("{", "(").replace("}", ")")
xx, yy = [], []
for i in range(0, 101):
t = i / 100
x, y = eval(code)
xx.append(x); yy.append(y)
plt.plot(xx, -np.array(yy), "k")

axs.set_axis_off()
#buf = BytesIO()
buf = "tmp.png"
plt.savefig(buf,)
img = Image.open(buf)
text = detect(img).replace("/","//")
result = str(eval(text))
print(text, result)
r.send(result+"\n")
print(line)
```

# Code Build Model

```python
from __future__ import print_function
import tensorflow.keras as keras
from tensorflow.keras.datasets import mnist
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense, Dropout, Flatten
from tensorflow.keras.layers import Conv2D, MaxPooling2D
from tensorflow.keras import backend as K
from tensorflow.keras.utils import to_categorical

import os
import numpy as np
import random
from PIL import Image

batch_size = 64
num_classes = len(os.listdir("letters"))
epochs = 100

# input image dimensions
img_rows, img_cols = 28, 28

signs = "0123456789ABCDEF"

def load_data():
trainx, testx, trainy, testy = [], [], [], []
for dir in os.listdir("letters"):
for file in os.listdir("letters/"+dir):
img = Image.open("letters/"+dir+"/"+file)
img = np.array(img.getdata())
id = signs.index(dir)
if random.random() < 0.9:
trainx.append(img); trainy.append(id)
else:
testx.append(img); testy.append(id)
return (np.array(trainx), trainy), (np.array(testx), testy)

# the data, shuffled and split between train and test sets
(x_train, y_train), (x_test, y_test) = load_data()

if K.image_data_format() == 'channels_first':
x_train = x_train.reshape(x_train.shape[0], 1, img_rows, img_cols)
x_test = x_test.reshape(x_test.shape[0], 1, img_rows, img_cols)
input_shape = (1, img_rows, img_cols)
else:
x_train = x_train.reshape(x_train.shape[0], img_rows, img_cols, 1)
x_test = x_test.reshape(x_test.shape[0], img_rows, img_cols, 1)
input_shape = (img_rows, img_cols, 1)

x_train = x_train.astype('float32')
x_test = x_test.astype('float32')
x_train /= 255
x_test /= 255
print('x_train shape:', x_train.shape)
print(x_train.shape[0], 'train samples')
print(x_test.shape[0], 'test samples')

# convert class vectors to binary class matrices
y_train = to_categorical(y_train, num_classes)
y_test = to_categorical(y_test, num_classes)

model = Sequential()
model.add(Conv2D(32, kernel_size=(3, 3),
activation='relu',
input_shape=input_shape))
model.add(Conv2D(64, (3, 3), activation='relu'))
model.add(MaxPooling2D(pool_size=(2, 2)))
model.add(Dropout(0.25))
model.add(Flatten())
model.add(Dense(128, activation='relu'))
model.add(Dropout(0.5))
model.add(Dense(num_classes, activation='softmax'))

model.compile(loss=keras.losses.categorical_crossentropy,
optimizer=keras.optimizers.Adadelta(learning_rate=0.1),
metrics=['accuracy'])

model.fit(x_train, y_train,
batch_size=batch_size,
epochs=epochs,
verbose=1,
validation_data=(x_test, y_test))
score = model.evaluate(x_test, y_test, verbose=0)
model.save("model.keras")
print('Test loss:', score[0])
print('Test accuracy:', score[1])
```

Original writeup (https://www.sigflag.at/blog/2020/writeup-aeroctf2020-very-funny-curves/).