Rapid Arithmetic is a misc challenge that was part of the 2022 TUCTF. To get the flag, we had to solve various math problems in a short amount of time. The description of the challenge was:

Hope you are fluent in braille.

nc chals.tuctf.com 30200

But no braille was used…

Solution

We were prompted with math problems that we had to solve. The problems were in the form of text, Morse code, Roman numerals, and even a “picture”.

Challenge #1

The first challenges were easy, just some basic math equations. The first one was:

(100 * 25)
Answer: 

Then these got more complex but were still manageable:

((((5575663  / 107 )  + 16271 )  - 68379 )  / 1 )
Answer:

To solve these we just had to use the eval() function in python. The code for this was:

from pwn import remote

r = remote('chals.tuctf.com', 30200)

while True:
    try:
        line = r.recvuntil(b"Answer: ").decode()
    except:
        print(r.recvall().decode())
        break

    line = line.split("\n")[-2]

    answer = eval(line)
    answer = round(answer)

    r.sendline(str(answer).encode())

After ~125 equations, the response changed from:

b'Correct!\n'
...

to

b'Correct! exec('\nimport os\nscript_path = os.path.realpath( \
    __file__ )\nnew_program = ""\nwith open( script_path, r" ) \
        as f:\n
...

which was a python script that we didn’t want to execute. But we already bypassed this by using line.split("\n")[-2].

Challenge #2

The next challenge started after ~338 equations.

b'Correct!\n'
b'one thousand, four hundred and sixty-eight minus twenty-four \
     minus two hundred and ninety-five  plus two hundred and \
        sixteen  minus five hundred and ninety-eight  minus \
            five hundred and ninety-four \n'
b'Answer: '

This was a bit more complex:

from word2number import w2n

if line[0] != "(":
    line = line.replace("plus", "+").replace("minus", "-") \
        .replace("times", "*") .replace("divided by", "/") \
            .replace(",", "")
    tmp_line = []
    tmp_number = []
    for word in line.split(' '):
        if word == '+' or word == '-' or word == '*' or word == '/':
            if tmp_number[0] == "negative":
                tmp_line.append("-" + str(w2n.word_to_num( \
                    " ".join(tmp_number[1:]))))
            else:
                tmp_line.append(str(w2n.word_to_num( \
                    " ".join(tmp_number))))
            tmp_number = []
            tmp_line.append(word)
        else:
            tmp_number.append(word)
    tmp_line.append(str(w2n.word_to_num(" ".join(tmp_number))))

    line = " ".join(tmp_line)

Challenge #3

Then, after ~506 equations, the response changed to:

b'Correct!\n'
b'((CCXLVIII + III)  - XIII) \n'
b'Answer: '

The solution for this was:

def roman_to_int(s):
    rom_val = {'I': 1, 'V': 5, 'X': 10, 'L': 50, 'C': 100, 'D': 500, \
        'M': 1000}
    int_val = 0
    for i in range(len(s)):
        if i > 0 and rom_val[s[i]] > rom_val[s[i - 1]]:
            int_val += rom_val[s[i]] - 2 * rom_val[s[i - 1]]
        else:
            int_val += rom_val[s[i]]
    return int_val

romanReplace = []
tmp_number = []
for i in range(len(line)):
    if line[i] in "IVXLCDM":
        tmp_number.append(line[i])
    else:
        if tmp_number:
            romanReplace.append("".join(tmp_number))
            tmp_number = []
for i in range(len(romanReplace)):
    line = line.replace(romanReplace[i], \
        str(roman_to_int(romanReplace[i])), 1)

After ~674 equations the response changed back to #1.

Challenge #4

The 4th challenge was:

b'Correct!\n'
b'(((.---- .---- ..--- --... ..--- ...--    /  .---- ...--  )   - \
     --...  )   /  ...-- ---..  ) \n'
b'Answer: '

after ~842 equations. Morse code:

MORSECODE = {
    '.----': '1',
    '..---': '2',
    '...--': '3',
    '....-': '4',
    '.....': '5',
    '-....': '6',
    '--...': '7',
    '---..': '8',
    '----.': '9',
    '-----': '0'
}

for i in MORSECODE:
    line = line.replace(f"{i} ", MORSECODE[i])

Challenge #5

The last challenge started after ~1010 equations.

b'Correct!\n'
b'\n'
b' 2222   2222   8888  44  44         2222   0000   3333  777777 \n'
b'22  22 22  22 88  88 44  44        22  22 00  00 33  33    77  \n'
b'   22     22   8888  444444  ====     22  00  00    333   77   \n'
b'  22     22   88  88     44          22   00  00 33  33  77    \n'
b'222222 222222  8888      44        222222  0000   3333  77     \n'
b'\n'
b'Answer: '

This wasn’t fun:

x = line.split("\n")[-2]
    
if x == "":
    lines = []
    for l in line.split("\n")[2:-2]:
        lines.append(l)
    chars = []
    for i in range(len(lines[0])):
        char = ""
        for l in lines:
            if l[i] != " ":
                char += l[i]
        chars.append(char)
    if chars[0] == "":
        del chars[0]
    tmp_line = []
    tmp_tmp_char = chars[0]
    tmp_char = tmp_tmp_char[0]
    tmp_line.append(tmp_char)
    for i in range(len(chars)):
        if chars[i].startswith(tmp_char) and chars[i].endswith(tmp_char):
            pass
        else:
            if chars[i] == "":
                tmp_line.append(" ")
                tmp_char = " "
            else:
                tmp_tmp_char = chars[i]
                tmp_char = tmp_tmp_char[0]
                tmp_line.append(tmp_char)
    line = "".join(tmp_line)
    line = line.replace(" ", "").replace("=", "-")

Final code

#!/usr/bin/env python
from pwn import remote
from word2number import w2n

MORSECODE = {
    '.----': '1',
    '..---': '2',
    '...--': '3',
    '....-': '4',
    '.....': '5',
    '-....': '6',
    '--...': '7',
    '---..': '8',
    '----.': '9',
    '-----': '0'
}

def roman_to_int(s):
    rom_val = {'I': 1, 'V': 5, 'X': 10, 'L': 50, 'C': 100, 'D': 500, \
        'M': 1000}
    int_val = 0
    for i in range(len(s)):
        if i > 0 and rom_val[s[i]] > rom_val[s[i - 1]]:
            int_val += rom_val[s[i]] - 2 * rom_val[s[i - 1]]
        else:
            int_val += rom_val[s[i]]
    return int_val

r = remote('chals.tuctf.com', 30200)

run = 0
while True:
    run += 1
    print(f"run: {run}")
    
    line = ""
    
    try:
        line = r.recvuntil(b"Answer: ").decode()
    except:
        print(r.recvall().decode())
        break

    x = line.split("\n")[-2]
    
    if x == "":
        lines = []
        for l in line.split("\n")[2:-2]:
            lines.append(l)
        chars = []
        for i in range(len(lines[0])):
            char = ""
            for l in lines:
                if l[i] != " ":
                    char += l[i]
            chars.append(char)
        if chars[0] == "":
            del chars[0]
        tmp_line = []
        tmp_tmp_char = chars[0]
        tmp_char = tmp_tmp_char[0]
        tmp_line.append(tmp_char)
        for i in range(len(chars)):
            if chars[i].startswith(tmp_char) and chars[i] \
                .endswith(tmp_char):
                pass
            else:
                if chars[i] == "":
                    tmp_line.append(" ")
                    tmp_char = " "
                else:
                    tmp_tmp_char = chars[i]
                    tmp_char = tmp_tmp_char[0]
                    tmp_line.append(tmp_char)
        line = "".join(tmp_line)
        line = line.replace(" ", "").replace("=", "-")
    else:
        line = x
        if line[0] != "(":
            line = line.replace("plus", "+").replace("minus", "-") \
                .replace("times", "*").replace("divided by", "/") \
                    .replace(",", "")
            tmp_line = []
            tmp_number = []
            for word in line.split(' '):
                if word == '+' or word == '-' or word == '*' \
                        or word == '/':
                    if tmp_number[0] == "negative":
                        tmp_line.append("-" + str(w2n.word_to_num( \
                            " ".join(tmp_number[1:]))))
                    else:
                        tmp_line.append(str(w2n.word_to_num( \
                            " ".join(tmp_number))))
                    tmp_number = []
                    tmp_line.append(word)
                else:
                    tmp_number.append(word)
            tmp_line.append(str(w2n.word_to_num(" ".join(tmp_number))))

            line = " ".join(tmp_line)
        
        romanReplace = []
        tmp_number = []
        for i in range(len(line)):
            if line[i] in "IVXLCDM":
                tmp_number.append(line[i])
            else:
                if tmp_number:
                    romanReplace.append("".join(tmp_number))
                    tmp_number = []
        for i in range(len(romanReplace)):
            line = line.replace(romanReplace[i], \
                str(roman_to_int(romanReplace[i])), 1)
        
        for i in MORSECODE:
            line = line.replace(f"{i} ", MORSECODE[i])

    answer = eval(line)
    answer = round(answer)
    
    r.sendline(str(answer).encode())

Output:

$ ./solve-rapid.py
[+] Opening connection to chals.tuctf.com on port 30200: Done
run: 1
run: 2
run: 3
...
run: 1174
run: 1175
run: 1176
[+] Receiving all data: Done (140B)
[*] Closed connection to chals.tuctf.com port 30200
Correct!
You got everything correct!
Here is your flag: TUCTF{7h3_k1n6_0f_7h3_m47h_c457l3_15_m3_425927}
Was there something else in there??

Flag

The flag is “TUCTF{7h3_k1n6_0f_7h3_m47h_c457l3_15_m3_425927}”.