Points: 150

Solves: ???

Description:

I do declare this to challenge to be at least mildly engaging, and certainly useful going forwards

flag format: TUCTF{…}

Given:

A file with a set of restrictions in Prolog that the keys have to satisfy in order to pass the check.

Alt text

Solution

(by L0rdComm4nder)

TLDR

The prolog code encodes a series of constraints that are easily encoded in Z3

Description

Looking at the given files one notices the existence of two important functions tokenizer and checkkey.

Alt text

The tokenizer function basically transforms character codes into the corresponding characters 0 to 9 and a few others ( ) , - &.

The checkkey function checks the validity of the generated keys.

Alt text

These constraints are easily encoded in Z3 and we know that a key should be of the form (XX,XX-XX,XX&XX,XX-XX,XX) and satisfy some conditions.

#!/usr/bin/env python3
from z3 import *
from pwn import *

SERVER = "grammarlesson.tuctf.com"
PORT = 6661


s = Solver()
flag = [Int('flag%d' % i) for i in range(25)]
p = [Int('p%d' % i) for i in range(8)]
q = [Int('q%d' % i) for i in range(8)]
o = [Int('o%d' % i) for i in range(8)]

def is_digit(x):
    s.add(x >= 0)
    s.add(x <= 9)

s.add(flag[0] == ord('('))
is_digit(flag[1])
is_digit(flag[2])
s.add(flag[3] == ord(','))
is_digit(flag[4])
is_digit(flag[5])

s.add(flag[1]+flag[2] == p[0])
s.add(flag[4]+flag[5] == p[1])
s.add(p[0]*p[1] == q[0])

s.add(flag[6] == ord('-'))

is_digit(flag[7])
is_digit(flag[8])
s.add(flag[9] == ord(','))
is_digit(flag[10])
is_digit(flag[11])

s.add(flag[7]+flag[8] == p[2])
s.add(flag[10]+flag[11] == p[3])
s.add(p[2]*p[3] == q[1])
s.add(q[0] + q[1] == o[0])

s.add(flag[12] == ord('&'))

is_digit(flag[13])
is_digit(flag[14])
s.add(flag[15] == ord(','))
is_digit(flag[16])
is_digit(flag[17])

s.add(flag[13]+flag[14] == p[4])
s.add(flag[16]+flag[17] == p[5])
s.add(p[4]*p[5] == q[2])

s.add(flag[18] == ord('-'))

is_digit(flag[19])
is_digit(flag[20])
s.add(flag[21] == ord(','))
is_digit(flag[22])
is_digit(flag[23])

s.add(flag[19]+flag[20] == p[6])
s.add(flag[22]+flag[23] == p[7])
s.add(p[6]*p[7] == q[3])
s.add(q[2] + q[3] == o[1])

s.add(o[0] * o[1] == 82944)

s.add(flag[24] == ord(')'))


def hack(x):
    if x>=0 and x<=9:
        return chr(x +48)
    else:
        return chr(x)

chan = remote(SERVER, PORT, timeout=9999)
chan.recvuntil(': ')

rounds=0
while z3.sat == s.check():
    rounds += 1
    m = s.model()
    plaint = "".join([ hack(int(repr(m[flag[i]]))) for i in range(len(flag)) ])
    print plaint
    chan.sendline(plaint)
    if rounds == 10:
        break
    chan.recvuntil(': ')
    block = []
    for i in range(len(flag)):
        block.append( flag[i] != m[flag[i]])
    s.add(Or(block))

chan.interactive()

Running the above script, and submitting the 10 first retrieved flags one gets

(99,99-99,99&92,38-10,16)
(88,88-88,80&81,97-09,35)
(88,75-78,88&80,86-08,64)
(88,77-58,88&80,77-08,91)
(88,77-58,88&80,77-08,82)
(88,77-58,88&80,07-08,98)
(88,67-68,88&80,66-08,84)
(88,67-38,88&80,76-08,86)
(88,67-92,88&80,56-08,88)
(88,67-92,88&80,46-08,89)
TUCTF{Gr4mm3r_1s_v3ry_imp0rtAnT!_4ls0_Pr0log_iz_c00l!}